feat: v0.3.8–v0.3.14 工作区汇总(依赖升级、整理来源、手动入库、原地入库等)
生产 v0.3.8~v0.3.13 均由 8013196 加工作区修改构建部署,此前未提交;本提交补齐源码,
并含未部署的 v0.3.14 原地入库。中间各版本源码状态未保存,无法逐版拆分,详见
docs/plan/17-full-upgrade.md 与 docs/plan/18-organize-release.md。
- v0.3.8 全面升级:Deno workspace 依赖、Router 8 / Vite 8、camofox 1.18.0 / Node 24、
PostgreSQL 18.6、备份客户端 18.6、演示 qB 5.2.4
- v0.3.9 媒体整理:分类规则、整理来源与目录快照(0059)、文件管理整理入口
- v0.3.10 手动下载与入库:独立手动下载、详情页资源下载、TMDB / Local 元数据兜底
(metadata.resolve_organize)、正常多版本 / 一次性洗版(0060)
- v0.3.11 搜索详情与桌面黑边修复
- v0.3.12 列表分页、搜索历史、首页概况
- v0.3.13 统一整理弹窗与队列
- v0.3.14 原地入库(0061,未部署)
同批审计修复:
- 刮削入队:确认原记录存在后才删重放产生的新记录;写回 run 关联失败时保持 queued,
由处理器补关联(对齐 MP 先登记、只在入队失败时记失败)
- 识别升级链只在结果更确定时采用,不再用 no_match 顶替原有歧义候选
- bluray_dir 资产以原盘目录本身为刮削根(MP 同做法)
- metadata.resolve_organize 登记到入库排障 Skill 与能力地图
- db/src/types.ts 按迁移重新生成;contracts 整理动作枚举顺序与数据库对齐
- 测试:原地入库用例改为直接查记录表;domain 测试模板补 version
- 前端:通知铃铛弹层与用户菜单统一样式;站点卡片共用卡头、底栏贴底、分页间距
依赖升级证据只提交摘要文件,registry-snapshot.json 移至 .local/(SHA-256 记于文档)。
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8013196e3f
commit
ceb66f8c77
265 files changed
+17751
-2483
No files matched your search
@@ -52,7 +52,7 @@ AG-3 补的只读摘要(类别 read,scope `read`,都在后台调查 / 巡检白
|
||||
| `POST /approvals {planType, planId}`、`PATCH /approvals/{id} {status, payloadHash, reason?}` | 单次审批。**只接受控制台管理员会话**(API Key 一律 403,持 `organize.execute` 的 Key 不能给自己的计划开审批再自批)。批准时要回传 payloadHash;计划 payload hash / revision 变化、过期、撤销后旧审批失效,执行成功一次即 consumed,不可重放(`test/agent/approvals.test.ts`)。会话内审批(见「会话交互」)决定后写关联待处理事项并唤醒挂起的会话 |
|
||||
| `GET /capabilities`、`POST /capabilities/{id}/invoke` | 能力目录与调用,HTTP scope 按能力描述符再校验一次 |
|
||||
| `GET /tasks[/{id}]`、`POST /tasks/{id}/cancel`、`POST /tasks/{id}/retry` | 业务任务;`?subjectId=` 按业务对象查(submitDownload 的 subject 是计划,verifyAndImport 的是下载任务) |
|
||||
| `POST /acquisition-plans {sourceReleaseId, slotId? \| entityId?, note?}` | 手动提交下载(工作台下载入口):依次调 `download.plan` → `download.submit`,和 Agent 同一条能力路径(授权 / 审计 / 幂等键 / 账本)。没给 slotId 时按作品的 Slot 创建顺序逐个尝试,第一个生成计划的胜出;都没生成时 `submitted=false` 并逐个 Slot 给出原因。控制台管理员算人工授权;API Key 需要持续授权或单次审批 |
|
||||
| `POST /acquisition-plans {sourceReleaseId, entityId?, downloaderInstanceId?, savePath?, allowUnrecognized?, importMode?, replacementAssetIds?, note?}` | 独立手动下载,不要求订阅或 Slot;未知资源返回 `requiresConfirmation`,确认后允许提交。下载计划复用已有任务提交、审计、幂等与缓存卷检查;自动订阅评估和 Agent `download.plan` 保持自动规则。`importMode` 为 `normal`/`replace`,洗版选择仅本次有效。此批已部署为 v0.3.10,迁移 `0060` 已执行。 |
|
||||
| `GET /artifacts[/{id}]` | 产物只读:整理 dryRun 计划(kind=plan)、目标汇报(report)、候选评估(evidence)等 |
|
||||
|
||||
**增量推送**:事件先写 `agent_run_event`(run_id + seq 唯一),再经 `ctx.events` 推到全局 SSE(`GET /events`)。推送的类型有 `agent.run_status`、`agent.text_delta`、`agent.tool_call`;未授权导致的工具失败,`phase` 为 `denied`。断线后,先用全局 `?after=<seq>` 补读总线事件,再用 `GET /agent/runs/{id}?afterSeq=<最后收到的 runSeq>` 补读单个 run 的完整事件。SSE 只给控制台(cookie 会话);API Key 调用者用 `GET /events?after=<seq>&limit=<n>` 取 JSON 页轮询(见 auth.md「SSE 多进程总线」)。
|
||||
|
||||
@@ -18,41 +18,41 @@
|
||||
先写到 `.<时间戳>.partial/`,全部成功才改名,不会留下半截备份。`BACKUP_KEEP=N`(compose 缺省 14)时只保留最近 N 份。
|
||||
|
||||
**SECRET_MASTER_KEY 不在备份里。** 它和备份必须分开保管:备份泄露时密文仍然安全;主密钥丢失时备份里的站点/下载器/模型凭证无法解密(需要重新录入)。
|
||||
恢复到新机器时,`.env` 里的 `SECRET_MASTER_KEY` 必须与备份时一致。
|
||||
恢复到新机器时,`.env.deploy` 里的 `SECRET_MASTER_KEY` 必须与备份时一致。
|
||||
|
||||
## 日常备份
|
||||
|
||||
```sh
|
||||
docker compose exec app umt backup # 在线备份(pg_dump 一致性快照,不停服)
|
||||
docker compose exec app ls /data/backups
|
||||
docker compose --env-file .env.deploy exec app umt backup # 在线备份(pg_dump 一致性快照,不停服)
|
||||
docker compose --env-file .env.deploy exec app ls /data/backups
|
||||
# 拷到机器外(示例):
|
||||
docker compose cp app:/data/backups/20260927T120000Z ./offsite/
|
||||
docker compose --env-file .env.deploy cp app:/data/backups/20260927T120000Z ./offsite/
|
||||
```
|
||||
|
||||
定时:宿主 cron `0 4 * * * cd /opt/umt && docker compose exec -T app umt backup >> /var/log/umt-backup.log 2>&1`。
|
||||
单独导出映射清单:`docker compose exec app umt export-mappings`(写到 `/data/exports/`)。
|
||||
定时:宿主 cron `0 4 * * * cd /opt/umt && docker compose --env-file .env.deploy exec -T app umt backup >> /var/log/umt-backup.log 2>&1`。
|
||||
单独导出映射清单:`docker compose --env-file .env.deploy exec app umt export-mappings`(写到 `/data/exports/`)。
|
||||
|
||||
## 恢复
|
||||
|
||||
恢复会删除并重建备份里的全部对象。**先停 app**,脚本检测到库里还有其它连接会拒绝(`--force` 可跳过,不建议)。
|
||||
|
||||
```sh
|
||||
docker compose stop app
|
||||
docker compose run --rm --no-deps app restore 20260927T120000Z --yes # 或绝对路径 /data/backups/...
|
||||
docker compose up -d app
|
||||
docker compose --env-file .env.deploy stop app
|
||||
docker compose --env-file .env.deploy run --rm --no-deps app restore 20260927T120000Z --yes # 或绝对路径 /data/backups/...
|
||||
docker compose --env-file .env.deploy up -d app
|
||||
```
|
||||
|
||||
`restore.sh` 依次:
|
||||
|
||||
1. 校验 `SHA256SUMS`;
|
||||
2. `pg_restore --clean --if-exists --no-owner` 生成 SQL,经 `psql --single-transaction -v ON_ERROR_STOP=1` 回放(失败整体回滚,库保持恢复前状态)。镜像里是 17 版客户端,回放前去掉 16 版服务端不认识的 `SET transaction_timeout`;
|
||||
2. `pg_restore --clean --if-exists --no-owner` 生成 SQL,经 `psql --single-transaction -v ON_ERROR_STOP=1` 回放(失败整体回滚,库保持恢复前状态)。当前镜像使用 18.6 客户端,支持 PG18 备份;回放前仍过滤 `SET transaction_timeout`,以兼容回滚到 PG16;
|
||||
3. 还原 `agent-sessions.tar.gz`;
|
||||
4. `--role read-only-on`:把 `automation.readOnlyReconcile` 置为 true(保留 automation 其它字段),写审计 `ops.read_only_reconcile_on`;
|
||||
5. 重新导出映射清单到 `DATA_DIR/tmp/restore-check-*.json`,逐表与备份里的 `counts` 比对,不一致以退出码 3 结束;
|
||||
6. 打印"恢复后先对账再关闭只读"提示。
|
||||
|
||||
从别的机器恢复:把备份目录拷进目标机器的 `umt-data` 卷(`docker compose cp ./20260927T120000Z app:/data/backups/`),
|
||||
`.env` 用原来的 `SECRET_MASTER_KEY`,空库上照上面的命令执行。备份早于当前镜像版本时,`up -d app` 会自动跑后续迁移。
|
||||
从别的机器恢复:把备份目录拷进目标机器的 `umt-data` 卷(`docker compose --env-file .env.deploy cp ./20260927T120000Z app:/data/backups/`),
|
||||
`.env.deploy` 用原来的 `SECRET_MASTER_KEY`,空库上照上面的命令执行。备份早于当前镜像版本时,`up -d app` 会自动跑后续迁移。
|
||||
|
||||
## 恢复后:先对账,再关闭只读
|
||||
|
||||
|
||||
+50
-24
@@ -4,44 +4,50 @@
|
||||
代码:`packages/adapters/src/browser/**`(适配器)、`apps/server/src/modules/browser/**`(会话/接管/能力)、
|
||||
`apps/web/app/components/browser/**`(原站视图)。
|
||||
|
||||
> 当前状态:已对真实 camofox-browser **v1.17.0**(jo-inc,commit `389c996`)容器逐项核验(§7),十项能力全部通过;
|
||||
> 当前版本:**v1.18.0**(jo-inc,commit `f94c81b`)已重新核验原有十项能力,六阶段 25 项检查和隔离重启验证通过(§16)。
|
||||
> §7 等带日期的 v1.17.0 验收表保留历史证据;
|
||||
> 探测到其它版本 / fork 时一律显示"未核验",需按 §6 重新核验。
|
||||
|
||||
## 1. 浏览器服务从哪来
|
||||
|
||||
### 1.1 部署自带(缺省)
|
||||
|
||||
`docker compose up -d` 同时起 `camofox` 服务(`docker-compose.yml`,只在内网、不映射端口,卷 `umt-camofox`)。
|
||||
当前部署配置使用 `.env.deploy`。首次启动前,先完成本节下方的基础镜像构建;已有镜像时再执行启停命令。
|
||||
|
||||
`docker compose --env-file .env.deploy up -d` 同时起 `camofox` 服务(`docker-compose.yml`,只在内网、不映射端口,卷 `umt-camofox`)。
|
||||
app 的 `BROWSER_PROVIDER_URL` 缺省 `http://camofox:9377`,启动后第一次用到浏览器时登记为 **`camofox-env`**
|
||||
(`browser_provider.config.managedBy = "env"`,访问凭据取 `BROWSER_ACCESS_KEY` / `CAMOFOX_ACCESS_KEY`,只在进程内,不入库),
|
||||
并在后台探测一次能力。地址与凭据只能改部署配置;设置页只能改它的启用。
|
||||
|
||||
```sh
|
||||
# .env(部署目录)
|
||||
# .env.deploy(部署目录)
|
||||
CAMOFOX_ACCESS_KEY=$(openssl rand -hex 24) # camofox 与 app 共用
|
||||
docker compose --env-file .env.deploy up -d --build
|
||||
docker compose ps camofox # healthy 即可
|
||||
docker compose --env-file .env.deploy ps camofox # healthy 即可
|
||||
```
|
||||
|
||||
上游不发布镜像。compose 缺省用的是**浏览器桌面镜像** `umt-browser-desktop:1.17.0`(`docker/browser-desktop`,§8),
|
||||
它从纯截图镜像 `camofox-browser:1.17.0` 复制 camofox,所以**先构建旧镜像**:`docker/camofox/Dockerfile` 从 git tag `v1.17.0`
|
||||
上游不发布镜像。compose 缺省用的是**浏览器桌面镜像** `umt-browser-desktop:1.18.0`(`docker/browser-desktop`,§8),
|
||||
当前默认版本为 1.18.0,构建补丁、锁文件和 Playwright 1.60 协议兼容例外见 [镜像说明](../../docker/camofox/README.md)。以下旧日期的验收表保留历史版本。
|
||||
|
||||
它从纯截图镜像 `camofox-browser:1.18.0` 复制 camofox,所以**先构建基础镜像**:`docker/camofox/Dockerfile` 从 git tag `v1.18.0`
|
||||
构建(修了上游 Dockerfile 的两个问题,见文件头)。首次构建要从 GitHub 拉源码与约 630 MB 的 Camoufox 浏览器包:
|
||||
|
||||
```sh
|
||||
git clone --depth 1 --branch v1.17.0 https://github.com/jo-inc/camofox-browser.git /tmp/camofox-src
|
||||
docker build -t camofox-browser:1.17.0 --build-arg APT_MIRROR=mirrors.ustc.edu.cn \
|
||||
git clone --depth 1 --branch v1.18.0 https://github.com/jo-inc/camofox-browser.git /tmp/camofox-src
|
||||
git -C /tmp/camofox-src rev-parse HEAD # 必须为 f94c81babf4bfd44cdd4bd46e1f2e1fb1d91c753
|
||||
docker build -t camofox-browser:1.18.0 --build-arg APT_MIRROR=mirrors.ustc.edu.cn \
|
||||
--build-context upstream=/tmp/camofox-src docker/camofox
|
||||
# 走宿主代理时加 --network host --build-arg https_proxy=http://127.0.0.1:7890 --build-arg http_proxy=...
|
||||
docker compose build camofox # 再构建桌面镜像(或 docker build -t umt-browser-desktop:1.17.0 docker/browser-desktop)
|
||||
docker compose --env-file .env.deploy build camofox # 再构建桌面镜像(或 docker build -t umt-browser-desktop:1.18.0 docker/browser-desktop)
|
||||
```
|
||||
|
||||
只要截图模式(不要桌面、省约 400 MB 常驻内存与 3 GB 磁盘):`.env` 里 `CAMOFOX_IMAGE=camofox-browser:1.17.0`,同一个卷,登录状态通用。
|
||||
只要截图模式(不要桌面、省约 400 MB 常驻内存与 3 GB 磁盘):`.env.deploy` 里 `CAMOFOX_IMAGE=camofox-browser:1.18.0`,同一个卷,登录状态通用。
|
||||
|
||||
### 1.2 接自有实例
|
||||
|
||||
`.env` 里 `BROWSER_PROVIDER_URL=http://<host>:9377`、`BROWSER_ACCESS_KEY=<它的 CAMOFOX_ACCESS_KEY>`、`CAMOFOX_REPLICAS=0`
|
||||
`.env.deploy` 里 `BROWSER_PROVIDER_URL=http://<host>:9377`、`BROWSER_ACCESS_KEY=<它的 CAMOFOX_ACCESS_KEY>`、`CAMOFOX_REPLICAS=0`
|
||||
(不再起自带容器)。或者不设环境变量,在 **设置 → 站点默认 → 浏览器服务** 里"添加浏览器服务":名称、服务地址、访问凭据
|
||||
(加密存凭证库,只写不回显)、启用。自有实例的版本不是 1.17.0 时能力一律显示"未核验"。
|
||||
(加密存凭证库,只写不回显)、启用。自有实例的版本不是 1.18.0 时能力一律显示"未核验"。
|
||||
|
||||
### 1.3 不用浏览器
|
||||
|
||||
@@ -127,7 +133,7 @@ execute 类需要持续授权(`automation_grant`)或审批。元素引用只来
|
||||
- **坐标系**:服务端对每次截图读页面视口与 DPR(`evaluate`),`页面 CSS 像素 = 截图像素 × 视口宽 / 截图宽`;
|
||||
输出的 `coordinates` 记录截图尺寸、原始尺寸、视口、DPR(取不到时 `assumed = true`,按原图、DPR 1)。动作之后旧坐标系作废,
|
||||
必须用新截图的坐标。点击(CF-2 起)先走 umt 插件的 `POST /umt/tabs/{id}/mouse-click`——Playwright `page.mouse.click` 发真实鼠标事件,
|
||||
**跨域 iframe 里的控件(Cloudflare Turnstile)和画布也点得到**;插件没有这个端点(旧镜像、纯截图镜像)时退回 1.17.0 的做法:
|
||||
**跨域 iframe 里的控件(Cloudflare Turnstile)和画布也点得到**;插件没有这个端点(旧镜像、纯截图镜像)时退回 1.18.0 的做法:
|
||||
`elementFromPoint` 打标记再按选择器点(跨域 iframe / 画布点不到具体位置)。
|
||||
- **动作前提**:会话在自动化控制下(人工接管 / 等待处理中一律拒绝),当前页面主机在站点域名白名单内。
|
||||
- **敏感截图**:登录页地址、页面上有密码框、地址带凭据 → `sensitive = true`,默认不交给模型(不做像素遮蔽),改给已脱敏的文字快照;
|
||||
@@ -156,7 +162,12 @@ execute 类需要持续授权(`automation_grant`)或审批。元素引用只来
|
||||
|
||||
不承诺通过 Cloudflare 等挑战(REQ §4.4):上游浏览器指纹与点击方式都可能被识别,失败按上面第 4 步转人工。
|
||||
|
||||
## 4. 下载产物
|
||||
## 4. 详情页下载与下载产物
|
||||
|
||||
本轮源码新增:进入站点资源详情页后,在串流/截图窗口外点「下载当前资源」,解析当前详情并用默认下载器和目录提交。点「下载选项」可选择下载器、保存目录与一次性洗版。未识别资源会提示确认继续下载,整理阶段再补全元数据;不用点击源站下载按钮,也不用先建立订阅或本地搜索索引。此变更已于 2026-10-01 随 v0.3.10 部署,迁移 `0060` 已执行。
|
||||
|
||||
`POST /browser/sessions/{id}/resource {tabId?}` 返回登记后的资源与详情地址。默认读取唯一可见的已登记标签页,能发现上游登记的 popup;无法确定当前标签时明确报错,不提交旧页。显式 `tabId` 读取指定标签。下面的文件捕获接口仍保留给已有 Agent 流程使用。
|
||||
|
||||
|
||||
- 暂存:`DATA_DIR/tmp/browser-downloads/<artifactId>.bin`(目录 0700、文件 0600),超过 24 小时的在下一次捕获时清理。
|
||||
合同里只出现引用(`bytes.ref`),来源地址去掉 passkey。
|
||||
@@ -176,10 +187,10 @@ execute 类需要持续授权(`automation_grant`)或审批。元素引用只来
|
||||
| "连接中断" | 截图请求失败;看 API 日志 `component: browser` 的方法 / 路径 / 状态(日志不含正文与凭据) |
|
||||
| 测试连通提示"访问凭据不对" | `/health` 免认证所以能连上,但 `/openapi.json` 401:`BROWSER_ACCESS_KEY` 与 camofox 的 `CAMOFOX_ACCESS_KEY` 不一致 |
|
||||
| 原站视图"浏览器已重启,正在重新连接…" | camofox 重启 / 会话空闲回收后的自动重连(§10),正常几秒内恢复 |
|
||||
| 503 `browser_reconnect_failed` / "浏览器连接已断开" | 自动重连失败,消息里是原因(camofox 没起来、`POST /tabs` 5xx 等);`docker compose ps camofox`、容器日志;恢复后点"重新连接" |
|
||||
| 503 `browser_reconnect_failed` / "浏览器连接已断开" | 自动重连失败,消息里是原因(camofox 没起来、`POST /tabs` 5xx 等);`docker compose --env-file .env.deploy ps camofox`、容器日志;恢复后点"重新连接" |
|
||||
| 409"标签页已被浏览器服务回收" | 只在显式指定了已被回收的 `tabId` 时出现(会话里还有别的标签页活着);不指定 `tabId` 重试即可 |
|
||||
| 重启 camofox 后要重新登录 | 容器是被 SIGKILL / OOM 杀掉的(`docker inspect` 看 `OOMKilled`、退出码 137):只有 SIGTERM 正常停止、会话空闲回收、关闭会话时才落盘登录状态;加大 `CAMOFOX_MEM_LIMIT` |
|
||||
| 设置页显示"未核验" | 探测到的上游版本不在核验表里(不是 1.17.0,或 fork);按 §6 核验后在适配器 `CAMOFOX_VERIFIED_RELEASES` 加一行,或临时写 `config.verified` |
|
||||
| 设置页显示"未核验" | 探测到的上游版本不在核验表里(不是 1.18.0,或 fork);按 §6 核验后在适配器 `CAMOFOX_VERIFIED_RELEASES` 加一行,或临时写 `config.verified` |
|
||||
|
||||
## 6. 真实实例核验清单(REQ §4.7.4)
|
||||
|
||||
@@ -191,7 +202,7 @@ execute 类需要持续授权(`automation_grant`)或审批。元素引用只来
|
||||
|
||||
自动化核验脚本:`scripts/browser-verify/run.sh`(临时栈 `umtbr`:postgres 127.0.0.1:55447、camofox 127.0.0.1:49377、
|
||||
compose 网络里的 mock NexusPHP 站 `mocknexus:8080`;服务层在宿主进程里装配,与生产同一套代码;
|
||||
阶段之间重启 / SIGKILL camofox;结果写 `.browser-verify-out/results-*.json` 与截图)。跑完 `docker compose -p umtbr down -v`。
|
||||
阶段之间重启 / SIGKILL camofox;结果写 `.browser-verify-out/results-*.json` 与截图)。跑完 `docker compose --env-file .env.deploy -p umtbr down -v`。
|
||||
|
||||
| # | 项目 | 做法 | 通过标准 | 对应 verified 键 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@@ -206,7 +217,7 @@ compose 网络里的 mock NexusPHP 站 `mocknexus:8080`;服务层在宿主进程
|
||||
|
||||
| 日期 | 仓库 / fork | 版本 | 启动方式 / 认证 | 通过项 | 备注 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-09-28 | jo-inc/camofox-browser | v1.17.0(`389c996`),Camoufox 152.0.4-beta.28 | `docker/camofox/Dockerfile` + compose;`CAMOFOX_ACCESS_KEY` Bearer | 十项全部 | mock NexusPHP 站 + example.com;四个真实站点未测,见 §7 |
|
||||
| 2026-09-28 | jo-inc/camofox-browser | v1.18.0(`f94c81b`),Camoufox 152.0.4-beta.28 | `docker/camofox/Dockerfile` + compose;`CAMOFOX_ACCESS_KEY` Bearer | 十项全部 | mock NexusPHP 站 + example.com;四个真实站点未测,见 §7 |
|
||||
|
||||
## 7. 真机核验(版本 1.17.0)
|
||||
|
||||
@@ -272,7 +283,7 @@ pixelflux + labwc Wayland 桌面 + Selkies 2.0 Web 客户端)+ camofox 1.17.0 **
|
||||
| 自愈(INT-3 / FIX-5) | 同上 | 打开桌面先调一次幂等 `POST /browser/sessions/{id}/reconnect`(iframe 直连 Selkies 不触发被动重连),等它返回后才加载桌面 iframe(期间显示「正在确认浏览器会话…」,FIX-5 起不再先连上黑屏);画面 5 秒未加载或视频流未出帧 → 每个会话自动重连一次并重建桌面连接,之后靠「重新连接」按钮 |
|
||||
| HTTPS | compose `caddy`(profile `tls`)+ `docker/browser-desktop/Caddyfile` | 可选;已有反代终结 TLS 时不用 |
|
||||
|
||||
窗口:无标题栏、最大化铺满 1280×900(`BROWSER_DESKTOP_WIDTH/HEIGHT`),`HARDEN_DESKTOP` 去掉终端 / sudo / xdg-open,
|
||||
窗口:由内置 `/defaults/umt-labwc.xml` 设为无服务端装饰、最大化铺满 1280×900(`BROWSER_DESKTOP_WIDTH/HEIGHT`),`HARDEN_DESKTOP` 去掉终端 / sudo / xdg-open,
|
||||
`HARDEN_OPENBOX` 去掉关闭按钮与右键 / 中键菜单、禁 Alt+F4 等。音频、麦克风、摄像头、手柄、文件传输、打印、命令、共享全部关闭并锁定,
|
||||
剪贴板只允许文本。
|
||||
|
||||
@@ -303,21 +314,23 @@ pixelflux + labwc Wayland 桌面 + Selkies 2.0 Web 客户端)+ camofox 1.17.0 **
|
||||
|
||||
### 8.4 HTTPS
|
||||
|
||||
当前测试部署于 2026-10-01 将 **4380 改为 Caddy 自签 HTTPS**,访问 `https://服务器地址:4380`,原 443 HTTPS 入口保留。首次访问需在浏览器继续接受内部 CA 证书。app 的 HTTP 调试映射为 `127.0.0.1:4381`,容器间仍使用 `app:4380`。管理员登录、6 个页面加载与 Chromium `isSecureContext` 检查通过。此映射写在本机 `docker-compose.override.yml`,应用监听参数写入 `.env.deploy`。
|
||||
|
||||
WebCodecs 视频解码与剪贴板 API 只在安全上下文(HTTPS 或 `localhost`)可用;用 IP / 局域网主机名经 HTTP 访问时只能用截图模式。
|
||||
|
||||
```sh
|
||||
# 已有反代(nginx / Caddy / Traefik)终结 TLS:无需改动,WebSocket 需透传 Upgrade、读超时 ≥ 1 小时、不缓冲
|
||||
# 没有反代:启用 compose 自带 Caddy
|
||||
docker compose --profile tls up -d # 自签:Caddy 内部 CA 按访问的主机名现场签发,浏览器需放行或导入根证书
|
||||
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt ./umt-root.crt # 导入到客户端受信任根
|
||||
# 有公网域名:.env 里 TLS_DOMAIN=media.example.com、[email protected](ACME,80/443 需可达)
|
||||
docker compose --env-file .env.deploy --profile tls up -d # 自签:Caddy 内部 CA 按访问的主机名现场签发,浏览器需放行或导入根证书
|
||||
docker compose --env-file .env.deploy cp caddy:/data/caddy/pki/authorities/local/root.crt ./umt-root.crt # 导入到客户端受信任根
|
||||
# 有公网域名:.env.deploy 里 TLS_DOMAIN=media.example.com、[email protected](ACME,80/443 需可达)
|
||||
```
|
||||
|
||||
`TLS_PORT`(缺省 443)、`TLS_BIND` 可改;4380 仍可直连(截图模式)。
|
||||
`TLS_PORT`(缺省 443)、`TLS_BIND` 可改;标准 Compose 可通过 app 的 `UMT_PORT` 保留 HTTP 直连。本机已将 4380 用于 HTTPS,HTTP 调试改为 `127.0.0.1:4381`。
|
||||
|
||||
### 8.5 回退
|
||||
|
||||
- 桌面容器起不来 / 桌面异常:`.env` 设 `CAMOFOX_IMAGE=camofox-browser:1.17.0` 后 `docker compose up -d camofox`,原站视图自动回到截图模式,
|
||||
- 桌面容器起不来 / 桌面异常:`.env.deploy` 设 `CAMOFOX_IMAGE=camofox-browser:1.17.0` 后 `docker compose --env-file .env.deploy up -d camofox`,原站视图自动回到截图模式,
|
||||
Agent 能力不受影响(实测同一个卷,切换前后登录状态都在)。
|
||||
- 只关桌面、保留镜像:不设 `CAMOFOX_ACCESS_KEY` 即可(桌面口令随机,app 探测为不可用)。
|
||||
- 容器内输入法(本机输入法不可用时的备选):`BROWSER_DESKTOP_IME=fcitx5`,Ctrl+Space 切换;与本机输入法同时开会重复组字,缺省关闭。
|
||||
@@ -674,3 +687,16 @@ camofox 按 **API 调用**判断活动,有三道空闲回收(`docker-compose.yml
|
||||
| 日期 | 做法 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 2026-09-30 | 临时容器 `umt-cf2-camofox`(镜像 `umt-browser-desktop:1.17.0`,只读挂载新插件目录),容器内 node 起两个本地页面:父页 `localhost:8001`(标题 Just a moment...)内嵌**跨域** iframe `localhost:8002`(复选框,`change` 事件 `isTrusted` 才向父页 postMessage);适配器 `openSession` → `pageContent` → `click({x, y})` → `pageContent` | 插件路径:`POST /umt/tabs/{id}/mouse-click` 命中复选框,iframe 里收到可信事件,父页标题变为 passed;`pageContent` 读到 UA `…Windows NT 10.0; Win64; x64; rv:152.0…`。旧路径(elementFromPoint)只能标到 iframe 元素本身、按元素中心点击,点不到指定坐标。未访问任何外部站点,容器已删除 |
|
||||
|
||||
|
||||
## 16 2026-10-01 的 1.18 升级复测
|
||||
|
||||
当前默认桌面镜像为 `umt-browser-desktop:1.18.0`,基础镜像 `camofox-browser:1.18.0` 锁定上游 `f94c81babf4bfd44cdd4bd46e1f2e1fb1d91c753`。Node 24.21.0、Camoufox 152.0.4-beta.30、camoufox-js 0.12.0;Playwright 保持官方协议可兼容的 1.60.0,原因和独立锁见 [构建说明](../../docker/camofox/README.md)。
|
||||
|
||||
用生产 profile 与桌面配置的独立副本验证了鉴权、原身份保持、Cookie 正常重启落盘、上下文/页面、evaluate、快照、PNG、选择器与可信坐标点击、profile 隔离。现有真实 BrowserService/Agent 能力核验脚本 `scripts/browser-verify/verify.ts` 的 setup/a/b/idle/c/d 共 25 项严格通过,包含登录、脱敏、提取、下载文件哈希及资源关联、正常重启与空闲回收自动重连、旧引用拒绝、SIGKILL 未落盘登录丢失识别。适配器能力表现已登记 1.18.0 的原十项能力,未知版本不继承核验结果。
|
||||
|
||||
完整执行与恢复/清理状态见 [升级记录](../plan/17-full-upgrade.md),机器可读结果见 [验证证据](../plan/evidence/2026-10-01-dependencies/upgrade-verification.json)。这些检查不增加真实 PT 挑战、ARM64 或精确 URL 恢复的验收声明。SIGKILL/OOM 后仅能使用上次落盘状态的限制继续保留。
|
||||
|
||||
### 桌面上下黑边排查记录
|
||||
|
||||
2026-10-01 已修复底座初始化脚本生成无效 labwc XML 的问题;部署镜像为 `umt-browser-desktop:1.18.0-umt.1`。初始化在 `init-selkies-config` 完成后写入完整 XML,浏览器窗口自动最大化。实际视频尺寸与前端 iframe 同为 1280×900;前后串流截图确认上下黑边消失。若需要调整桌面尺寸,可改 `BROWSER_DESKTOP_WIDTH/HEIGHT`,不要以拉伸或裁剪视频修正窗口布局。证据见 [发布记录](../plan/18-organize-release.md#v0311-搜索详情与桌面黑边修复)。
|
||||
+45
-27
@@ -1,18 +1,18 @@
|
||||
# 部署运行手册(W4-B)
|
||||
|
||||
形态:**除 PostgreSQL 外全部在一个容器里**。镜像 `uhd-media-tool` 内一个 `deno compile` 出的二进制 `umt-server`,
|
||||
形态:**核心应用在一个容器里,PostgreSQL 与浏览器分别部署**。镜像 `uhd-media-tool` 内一个 `deno compile` 出的二进制 `umt-server`,
|
||||
容器里同时跑 api、worker(io 组)、worker:agent(Agent 会话)三个进程,api 同端口托管 web 静态产物(`WEB_ROOT=/app/public`),对外只有 **4380** 一个端口。
|
||||
compose 有三个服务:`app` + `postgres` + `camofox`(浏览器服务,只在内网,不映射端口;可关掉或换成自有实例,见 §2.1)。
|
||||
compose 默认有三个服务:`app` + `postgres` + `camofox`(浏览器服务,只在内网,不映射端口;可关掉或换成自有实例,见 §2.1);`tls` profile 可额外启用 Caddy 提供 HTTPS。
|
||||
|
||||
## 1. 构建镜像
|
||||
|
||||
```sh
|
||||
docker build -t uhd-media-tool:latest . # 或 docker compose build
|
||||
APP_VERSION=v0.1.0 docker compose build # 版本号写进 web 的 version.json
|
||||
docker build -t uhd-media-tool:latest . # 或 docker compose --env-file .env.deploy build
|
||||
APP_VERSION=v0.3.11 docker compose --env-file .env.deploy build app # 示例:发布时填写对应版本,写进 web 的 version.json
|
||||
```
|
||||
|
||||
- 多阶段:`denoland/deno:2.9.7`(装依赖 → web 构建 → server 编译)→ `debian:trixie-slim` 运行镜像。
|
||||
- 运行镜像只额外装 `gosu`(降权)与 `postgresql-client`(备份/恢复用 pg_dump/pg_restore/psql,17 版客户端兼容 16 版服务端),
|
||||
- 运行镜像额外装 `gosu`(降权)及 PG 客户端动态库,从固定的 `postgres:18.6-trixie` 官方镜像复制 pg_dump/pg_restore/psql 与 libpq 18,
|
||||
外加 `/usr/local/bin/ffprobe`:从 `mwader/static-ffmpeg:9.0.2` 拷单个静态二进制(约 135 MB;Debian `ffmpeg` 包要 200+ 个依赖约 450 MB)。
|
||||
换来源用 `--build-arg FFPROBE_IMAGE=<含 /ffprobe 的镜像>`。
|
||||
- `/app` 下的二进制、静态产物、脚本全部 root 属主只读;运行用户改不了它们。
|
||||
@@ -27,32 +27,33 @@ APP_VERSION=v0.1.0 docker compose build # 版本号写
|
||||
|
||||
## 2. 启动
|
||||
|
||||
部署目录须包含完整仓库构建上下文。首次启用默认浏览器前,先完成 [浏览器手册 §1.1](browser.md#11-部署自带缺省) 的基础镜像构建,再执行以下 compose 命令。部署配置统一放 `.env.deploy`,开发配置仍用仓库根 `.env`。
|
||||
|
||||
```sh
|
||||
mkdir -p /opt/umt && cd /opt/umt # 部署目录(放 docker-compose.yml 与 .env)
|
||||
mkdir -p /opt/umt && cd /opt/umt # 部署目录(完整仓库构建上下文与 .env.deploy)
|
||||
cp <仓库>/docker/compose.env.example .env.deploy # 部署配置独立于开发用 .env
|
||||
sed -i "s/^SECRET_MASTER_KEY=.*/SECRET_MASTER_KEY=$(openssl rand -hex 32)/" .env
|
||||
sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$(openssl rand -hex 16)/" .env
|
||||
sed -i "s/^SECRET_MASTER_KEY=.*/SECRET_MASTER_KEY=$(openssl rand -hex 32)/" .env.deploy
|
||||
sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$(openssl rand -hex 16)/" .env.deploy
|
||||
docker compose --env-file .env.deploy up -d --build
|
||||
docker compose ps # app 变成 healthy 即可访问
|
||||
docker compose --env-file .env.deploy ps # app 变成 healthy 即可访问
|
||||
curl -s localhost:4380/api/v1/health
|
||||
```
|
||||
|
||||
### 2.1 浏览器服务(camofox)
|
||||
|
||||
缺省随栈起一个 camofox-browser 容器(jo-inc/camofox-browser **v1.17.0**,commit `389c996`),给原站视图与 Agent 浏览器能力用。
|
||||
缺省随栈起一个 camofox-browser 容器(jo-inc/camofox-browser **v1.18.0**,commit `f94c81b`),给原站视图与 Agent 浏览器能力用。
|
||||
app 启动时按 `BROWSER_PROVIDER_URL`(缺省 `http://camofox:9377`)自动登记名为 `camofox-env` 的浏览器服务,
|
||||
`depends_on` 只要求已启动、不等健康:camofox 挂了 app 照常运行,界面标"浏览器不可用"。
|
||||
|
||||
- **镜像**:上游不发布镜像,compose 用 `docker/camofox/Dockerfile` 从锁定的 git tag 构建(修了上游 v1.17.0 Dockerfile 的
|
||||
构建错误与信号转发,见文件头注释)。首次 `up --build` 要拉 GitHub 源码与约 630 MB 的 Camoufox 浏览器包,
|
||||
镜像约 3.6 GB;国内网络按 `docs/runbook/browser.md` §1 先在本机构建同名镜像 `camofox-browser:1.17.0`,之后 compose 直接复用。
|
||||
- **访问凭据**:`.env` 里设 `CAMOFOX_ACCESS_KEY`(`openssl rand -hex 24`),camofox 与 app 共用;不设则内网免认证。
|
||||
- **资源**:空闲约 200 MB(浏览器空闲 5 分钟自动退出后更低);每个站点会话一个 BrowserContext,
|
||||
`mem_limit` 缺省 2g、`shm_size` 2gb(Firefox 多进程需要)。
|
||||
- **镜像**:上游不发布镜像。先按 [浏览器手册 §1.1](browser.md#11-部署自带缺省),用 `docker/camofox/Dockerfile` 构建基础镜像 `camofox-browser:1.18.0`(锁上游 git tag,修正构建与信号转发)。
|
||||
compose 再用 `docker/browser-desktop/Dockerfile` 从该基础镜像构建默认桌面镜像 `umt-browser-desktop:1.18.0`。基础镜像首次构建需要 GitHub 源码和约 630 MB 的 Camoufox 包;桌面层复用浏览器包。
|
||||
- **访问凭据**:`.env.deploy` 里设 `CAMOFOX_ACCESS_KEY`(`openssl rand -hex 24`),camofox 与 app 共用;不设则内网免认证。
|
||||
- **资源**:当前默认桌面镜像 `mem_limit` 为 3g、`shm_size` 为 2gb;每个站点会话一个 BrowserContext,另有桌面常驻进程。
|
||||
早期约 200 MB 的空闲测量属于纯截图镜像,不能直接作为桌面模式的内存基准。
|
||||
- **登录状态**:按站点/账号存在卷 `umt-camofox`(`/root/.camofox/profiles`,cookies + localStorage),
|
||||
在会话空闲回收、关闭会话与 `docker compose stop/restart`(SIGTERM)时落盘;**容器被 SIGKILL / OOM 杀掉时,
|
||||
在会话空闲回收、关闭会话与 `docker compose --env-file .env.deploy stop/restart`(SIGTERM)时落盘;**容器被 SIGKILL / OOM 杀掉时,
|
||||
上次落盘之后的新登录会丢**(需重新登录)。
|
||||
- **接自有实例**:`.env` 里 `BROWSER_PROVIDER_URL=<地址>`、`BROWSER_ACCESS_KEY=<它的 CAMOFOX_ACCESS_KEY>`、`CAMOFOX_REPLICAS=0`。
|
||||
- **接自有实例**:`.env.deploy` 里 `BROWSER_PROVIDER_URL=<地址>`、`BROWSER_ACCESS_KEY=<它的 CAMOFOX_ACCESS_KEY>`、`CAMOFOX_REPLICAS=0`。
|
||||
- **不要浏览器**:`CAMOFOX_REPLICAS=0` + `BROWSER_ENABLED=false`。
|
||||
|
||||
**SECRET_MASTER_KEY** 生成一次后不再更换:站点 Cookie、下载器密码、模型 API Key 都用它加密存在 `secret` 表。
|
||||
@@ -77,7 +78,7 @@ app 启动时按 `BROWSER_PROVIDER_URL`(缺省 `http://camofox:9377`)自动登
|
||||
api 首次启动时在日志里打印一次性 setup token(重启会换新的,旧的作废):
|
||||
|
||||
```sh
|
||||
docker compose logs app | grep 'setup token'
|
||||
docker compose --env-file .env.deploy logs app | grep 'setup token'
|
||||
```
|
||||
|
||||
浏览器打开 `http://<主机>:4380`,登录页进入初始化模式,填 token、用户名、密码(≥10 字符)。
|
||||
@@ -90,7 +91,7 @@ docker compose logs app | grep 'setup token'
|
||||
| `/data` | 命名卷 `umt-data`(或宿主目录) | `agent-sessions/`(Pi JSONL 会话,崩溃恢复点)、`backups/`、`exports/`(映射清单)、`tmp/`(`TMPDIR`) | entrypoint 自动 chown 给 `PUID:PGID` |
|
||||
| `/media/...` | 用户 bind mount | 媒体库根:控制台 设置 → 媒体库根 登记的 `localPath` | 需对 `PUID:PGID` 可读写;**不会被自动 chown** |
|
||||
| `/downloads/...` | 用户 bind mount | 下载缓存:路径映射的 local 前缀(下载器报告的 remote 路径 → 容器内路径) | 同上 |
|
||||
| `/var/lib/postgresql/data`(postgres) | 命名卷 `umt-pg-data` | 数据库 | — |
|
||||
| `/var/lib/postgresql`(postgres) | 命名卷 `umt-pg18-data`,内部 `18/docker` | PG18 数据库;旧16卷不能直接挂入 | — |
|
||||
| `/root/.camofox`(camofox) | 命名卷 `umt-camofox` | 浏览器登录状态(`profiles/`,按站点/账号)、上游 cookies/uploads/traces 目录 | — |
|
||||
|
||||
- 媒体目录在 `docker-compose.yml` 的 `app.volumes` 里取消注释或另写 `docker-compose.override.yml`:
|
||||
@@ -106,7 +107,7 @@ docker compose logs app | grep 'setup token'
|
||||
- bind mount 的相对路径按**第一个 compose 文件所在目录**解析,不是按当前目录;不确定时写绝对路径。
|
||||
源目录不存在时 Docker 会以 root 属主新建它,库根登记就会因哨兵写不进而失败。
|
||||
- 路径映射:下载器容器里看到的是 `/downloads/complete/...`,本容器也挂成 `/downloads` 时 remote 前缀与 local 前缀相同;
|
||||
挂到别处就登记 `remote_prefix=/downloads`、`local_prefix=<本容器内路径>`。硬链接要求下载目录与库根在同一文件系统、同一挂载点内。
|
||||
挂到别处就登记 `remote_prefix=/downloads`、`local_prefix=<本容器内路径>`。目前整理只支持复制和移动。
|
||||
- 登记库根时服务端会在目录里写哨兵文件 `.uhd-root`,写不进去直接 400(提示检查挂载与属主)。
|
||||
- **PUID/PGID**:设成宿主媒体目录的属主(`stat -c '%u:%g' /srv/media`)。改 PUID/PGID 后重启容器,entrypoint 会把 `/data` 重新 chown。
|
||||
也可以 `docker run --user` 直接指定(此时跳过 chown,`/data` 需事先可写)。
|
||||
@@ -141,25 +142,30 @@ docker compose logs app | grep 'setup token'
|
||||
|
||||
## 6. 升级
|
||||
|
||||
发布前检查旧镜像的实际在途任务:在任务页核对 queued / running / waiting,尤其是本次变更的任务名称和版本。优先等它们完成;需要中止的任务先保留原输入、冻结计划和操作记录,确认中止后通过现有入口重新发起。已经产生文件操作证据的任务要先核实执行结果,再决定后续处理。没有旧在途任务时不长期保留旧 handler;业务表前滚不能替代任务版本兼容判断。
|
||||
|
||||
本次整理来源功能增加 `scanOrganizeSource@1` 和迁移 `0059_organize_source_snapshot`,沿用 `verifyAndImport`。迁移不会扫描或整理已有目录;用户新增来源后先建立基线,存量必须单独触发。
|
||||
|
||||
|
||||
```sh
|
||||
cd <仓库> && git pull
|
||||
docker compose exec app umt backup # 升级前先备份
|
||||
docker compose --env-file .env.deploy exec app umt backup # 升级前先备份
|
||||
docker compose --env-file .env.deploy up -d --build app # 重建镜像并替换容器;启动时自动迁移
|
||||
docker compose ps && docker compose logs --tail=50 app
|
||||
docker compose --env-file .env.deploy ps && docker compose --env-file .env.deploy logs --tail=50 app
|
||||
```
|
||||
|
||||
迁移只前滚。升级失败要回退时:用旧镜像 + 升级前的备份恢复(见 [backup-restore.md](backup-restore.md)),不要用旧镜像直接连新库。
|
||||
|
||||
## 7. 日志与排障
|
||||
|
||||
- `docker compose logs -f app`:pino JSON,一行一条;worker 日志带 `group`(io / agent)。
|
||||
- `docker compose --env-file .env.deploy logs -f app`:pino JSON,一行一条;worker 日志带 `group`(io / agent)。
|
||||
- `worker 事件循环延迟超过阈值`(warn,`lagMs`):worker 主线程被同步重活占住超过 5s,租约续期可能受影响,带上前后日志排查。json-file 驱动,单文件 10 MB × 5 份。
|
||||
- entrypoint 自身的输出(`entrypoint: ...`)说明哪个进程先退出、退出码多少。
|
||||
- 自检失败(`check=data_dir`):`/data` 卷不可写,检查是否挂了只读卷或 `--user` 与卷属主不一致。
|
||||
- 挂载路径告警(`挂载路径不可用`):看审计页 `preflight.path_unavailable` 事件,核对 bind mount 与 PUID/PGID。
|
||||
- 进容器排查:`docker compose exec app bash`(root);以运行用户执行命令用 `gosu 10001:10001 <cmd>`。
|
||||
- 一次性子命令:`docker compose exec app umt migrate|preflight|export-mappings|backup`、
|
||||
`docker compose run --rm --no-deps app restore <备份>`。
|
||||
- 进容器排查:`docker compose --env-file .env.deploy exec app bash`(root);以运行用户执行命令用 `gosu 10001:10001 <cmd>`。
|
||||
- 一次性子命令:`docker compose --env-file .env.deploy exec app umt migrate|preflight|export-mappings|backup`、
|
||||
`docker compose --env-file .env.deploy run --rm --no-deps app restore <备份>`。
|
||||
|
||||
## 验证记录
|
||||
|
||||
@@ -303,3 +309,15 @@ API Key 在 qB 5.2+ WebUI 选项 → WebUI → API Key 处生成 / 轮换(等价
|
||||
- 实测矩阵(`packages/adapters/src/qbittorrent/qbittorrent.real.test.ts`,设 `QB_REAL` 才跑):5.1.2 账密 / 无认证 / 失败分类,
|
||||
5.2.4 账密 / API Key / 无认证 / 失败分类,各跑 登录 → 添加(testkit `makeTorrent({ realPieces: true })` 真实 piece 哈希,
|
||||
内容写进保存目录,qB 校验即完成)→ 查询 → 标签 → 暂停恢复 → 删除(不删数据)。
|
||||
|
||||
## v0.3.10 发布补充
|
||||
|
||||
2026-10-01 已发布 v0.3.10,执行 `0060_manual_acquisition`:`acquisition_plan.slot_id` 允许为空,支持独立手动下载;`download_task.import_plan_id` 保存本次入库规划,并以外键引用计划。部署前备份与校验通过,只替换 app 容器;线上接口、版本一致及页面冒烟通过。后续普通 Compose 启动使用已更新的 `.env.deploy` 版本与源码标记。具体证据见 [发布记录](../plan/18-organize-release.md#v0310-手动下载与入库发布)。
|
||||
|
||||
### 当前测试入口
|
||||
|
||||
用户要求测试自签 HTTPS 后,4380 已由 Caddy 接管为 `https://服务器地址:4380`;原 443 入口保留,app 本机 HTTP 调试入口为 `http://127.0.0.1:4381`。配置保存在 `.env.deploy` 与本机 `docker-compose.override.yml`,重建了 app/Caddy 容器,镜像及数据库版本保持 v0.3.10/0060。证书由原 Caddy 内部 CA 签发,健康、管理员接口和 HTTPS 页面冒烟通过。
|
||||
|
||||
## v0.3.11 修复发布
|
||||
|
||||
搜索结果标题接入已有资源详情抽屉,已发布 v0.3.11;数据库保持 `0060`。浏览器桌面修复镜像为 `umt-browser-desktop:1.18.0-umt.1`,内置有效的最大化配置,已验证上下黑边消失。`.env.deploy` 已记录当前应用版本、源码标记和浏览器镜像,4380 HTTPS/443 HTTPS/本机 4381 HTTP 映射保留。具体验证见 [发布记录](../plan/18-organize-release.md#v0311-搜索详情与桌面黑边修复)。
|
||||
@@ -109,6 +109,7 @@ ci 逐行核对这张表与任务注册表(`apps/server/test/worker-lanes.test.t
|
||||
| `refreshPiTokens` | network | 模型服务 OAuth 令牌刷新 |
|
||||
| `reconcileLibrary` | maintenance | 资产对账(扫库) |
|
||||
| `purgeTrash` | maintenance | 回收站到期清除 |
|
||||
| `scanOrganizeSource` | maintenance | 整理来源基线与增量快照,候选整理进入文件道 |
|
||||
| `runAgent` | agent | Agent 会话(worker:agent) |
|
||||
| `resolveDecision` | agent | 决策处理(worker:agent) |
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
| delete | 默认回收站:`<根>/.umt-trash/<yyyymmdd>/<uuid>/<原相对路径>`(同卷 rename);根不可写/跨卷 → `DATA_DIR/trash/...` 复制(上限 `FILES_TRASH_CROSS_MAX_BYTES`,默认 2 GiB,超出请永久删除)。引用检查:纳管任务未完成或 H&R 未满足 409;已登记资产需 `retireAssets`(location→removed、asset 无其他位置→retired、满足的 Slot→attention + 待处理);绑定目录需 `unbind`(→superseded);根本身/只读根拒绝;离线 503。永不连带下载器任务。执行写 `organization_record(action=trash|delete)`,文件级结果(目录展开到 2000 个文件,超过只记目录一行),返回 `recordId` |
|
||||
| 永久删除 | `FILES_ALLOW_PERMANENT_DELETE=true` 才可用,且仅管理员会话;单项需 `confirmName` = 文件名 |
|
||||
| 回收站 | `GET /files/trash`;`restore`(原位被占 → 该项 `exists`,可带 `target` 还原到别处;还原不恢复资产状态,需要时重新整理登记;有成功项时写 `organization_record(action=restore)`,返回 `recordId`);`purge` 仅管理员会话,缺省只清过期项。"还原到…"的目标必须是可写的 managed 库根(否则 403 `not_writable`);原位还原时缺失的父目录在根内逐级重建(已存在的必须是真目录,符号链接拒绝 `path_escape`);跨卷还原受 `FILES_TRASH_CROSS_MAX_BYTES` 上限(超出该项 `too_large`),复制经 fsproxy。持久任务 `purgeTrash` 每日入队(`FILES_TRASH_RETENTION_DAYS`,默认 7);清除前复核行仍存在,且路径必须在某根的 `.umt-trash` 或 `DATA_DIR/trash` 下 |
|
||||
| identify | 只读:`parseReleasePath`(文件 + 父/祖父目录)→ 本地召回(别名 pg_trgm + 外部身份)→ `catalog.lookup` 能力取远端候选(经进程共享的 TMDB 信号量与 15 s 超时;单次请求去重后最多 20 个远端查询,超出的项只本地召回并给 warning;失败只给 warning)→ `rankCandidates` 前 5;目录展开视频上限 200;不建作品、不进待识别池 |
|
||||
| identify | 默认先本地解析并入队 `identifyRemote`,以下远端查询过程描述 `?wait=true` 的同步模式(默认异步结果经 SSE / 复查获取)。 只读:`parseReleasePath`(文件 + 父/祖父目录)→ 本地召回(别名 pg_trgm + 外部身份)→ `catalog.lookup` 能力取远端候选(经进程共享的 TMDB 信号量与 15 s 超时;单次请求去重后最多 20 个远端查询,超出的项只本地召回并给 warning;失败只给 warning)→ `rankCandidates` 前 5;目录展开视频上限 200;不建作品、不进待识别池 |
|
||||
| probe | 需要 `organize.execute`;≤20 项;已登记资产写回 `media_asset.media_info.probe`;未登记缓存 24 h(`DATA_DIR/cache/probe`) |
|
||||
| scrape | SCR-2 照 MP:任意视频文件或目录就地刮削(不要求已登记):已登记目录用其作品,否则按路径识别(本地 + TMDB),认不准返回 `needsChoice` 候选,选定后带 `mediaEntityId` / `external` 再提交;`addToLibrary` 顺手建作品;每项一条刮削记录,排 `scrapeMedia`,请求内最多等 `FILES_TRANSFER_WAIT_SEC`;`GET /files/scrape-records?ids=` 回查。只登记库根跳过。见 `scrape.md`。界面「刷新元数据」只刷字段(`scrape-batch`),「刮削…」走 `/files/scrape` |
|
||||
| download | `GET /files/download?root=&path=&inline=`:仅管理员会话(API Key 含 admin scope 也 403);只允许 NFO/字幕/图片(视频 403),≤ `FILES_DOWNLOAD_MAX_BYTES`(默认 20 MiB,超出 413 `too_large:`);路径守卫同其它接口,打开前再 realpath 复核、按文件句柄 stat;流式返回(非 JSON 信封),`inline=true` 仅对图片生效(SVG 一律附件),带 `nosniff` + `CSP sandbox`;写审计 `files.download` |
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ curl -s -X POST https://umt.example.lan/api/v1/mcp \
|
||||
| resources | `umt://settings/summary`(自动化开关、预算、Agent 上限,不含凭证)、`umt://decisions/pending`(未结束的待处理,最多 50 条)、`umt://subscriptions/overview`(各状态计数 + 最近 20 个订阅);需要 `read` scope |
|
||||
| prompts | `triage_pending`、`acquire_title(title, year?)`、`diagnose_task(taskId)` |
|
||||
|
||||
工具可见性(`mcpToolVisible`):Key 的 scope 覆盖能力的 scope;没有 `organize.execute` / `admin` 时只给 read / preview 类。`agent.*`(派生子会话等)只在 Agent 会话内可用,不经 MCP 暴露。当前能力集下:`read` 9 个,`read + organize.execute` 18 个,`admin` 23 个(界面「可见工具」按实时注册表计算)。
|
||||
工具可见性(`mcpToolVisible`):Key 的 scope 覆盖能力的 scope;没有 `organize.execute` / `admin` 时只给 read / preview 类。`agent.*`(派生子会话等)只在 Agent 会话内可用,不经 MCP 暴露。当前 41 个能力下:`read` 18 个,`read + organize.execute` 28 个,`admin` 37 个(2026-10-01 按 `mcpToolVisible` 核对;界面「可见工具」按实时注册表计算)。
|
||||
|
||||
## 安全边界:MCP 不绕过授权
|
||||
|
||||
|
||||
+67
-10
@@ -1,12 +1,56 @@
|
||||
# 整理记录(`/console/organize`,HIS-1)
|
||||
# 媒体整理(`/console/organize`)
|
||||
|
||||
整理记录页列出每次整理(复制 / 移动 / 硬链接)以及文件管理的改名、删除、还原。HIS-1 按 MoviePilot 历史记录页逐项补齐。
|
||||
原地入库为 v0.3.14 的待部署改动;当前生产 v0.3.13 的整理方式仍为复制/移动,独立就地刮削继续可用。
|
||||
|
||||
整理页分为「整理队列」与「整理记录」。队列显示等待、执行和等待继续的后台入库任务;记录显示复制/移动/原地入库的处理结果。文件管理的改名、删除、还原账本继续保存,通用记录接口仍可查询。
|
||||
|
||||
实现:
|
||||
- 列表 / 统计 / 详情:`apps/server/src/modules/acquisition-organize/routes/organization.ts`(筛选 `recordFilters`,通配 `likePattern`);公开字段在 `services/organize.ts` 的 `loadRecordsPublic`。
|
||||
- 删除:`apps/server/src/modules/file-manager/record-delete.ts`(复用文件管理删除 `deleteFiles`)。
|
||||
- 契约:`packages/contracts/src/organization.ts`(`OrganizationListQuery`、`OrganizationStats*`、`OrganizationDelete*`),端点 `Api.organization.stats / remove`。
|
||||
- 前端:`apps/web/app/routes/console/organize.tsx`、`organize.$id.tsx`,`components/organize/`(删除确认框、标题 / 季集小件),批量重新整理走整理向导的 `{ kind: "records" }` 来源。
|
||||
- 前端:`apps/web/app/routes/console/organize.tsx`、`organize.$id.tsx`,`components/organize/`(删除确认框、标题 / 季集小件),批量重新整理走共用整理弹窗的 `{ kind: "records" }` 来源。
|
||||
|
||||
## 统一整理弹窗(v0.3.13)
|
||||
|
||||
手动选目录、文件管理器、下载详情与历史重新整理打开同一个弹窗。单文件只处理该文件,目录递归展开视频,多选合并去重。媒体类型可自动、电影或剧集;作品可自动识别、搜索本地/TMDB,也可选定类型后输入 TMDB ID 定位。手动指定作品与季号作用于整个本次范围,混合批次中需单独纠正的作品另行选取;文件明细可核对和修正季集、排除文件,没有另一套批量勾选语义。
|
||||
|
||||
各入口匹配相同来源配置,唯一匹配自动带出方式和分类/目标,多个匹配在弹窗内选定;不使用来源配置时沿用全局默认并手动指定。下载详情优先按已有 qB 标签匹配覆盖当前路径的来源。历史原配置仍覆盖可用输入时优先采用;成功移动后的目标不再属于旧来源时显示当前匹配配置或手动选项。
|
||||
|
||||
本次可选择原地入库、复制或移动。复制/移动可调整目标媒体库或具体目录、命名、目标冲突及整理后刮削。刮削默认沿用全局配置,显式开关只影响本次;整理成功的文件再进入现有 NFO/图片任务,TMDB 和 Local 均适用。做种义务未满足时不提供移动,复制与原地登记不改变视频文件,服务端继续核对实际状态。
|
||||
|
||||
手动来源发现真正成功历史时显示数量及「跳过成功记录」,**默认关闭**,开启后仅本次跳过;失败/部分成功记录不算完整成功,最新失败也不会遮盖较早成功。目标已存在策略单独处理。从历史记录进入直接采用重新整理语义。修改识别或选项后旧预览失效,需重新生成。
|
||||
|
||||
确认预览后只提供「加入整理队列」。后台受理成功后弹窗自动关闭,可以继续操作,进度到队列查看,整理后的刮削任务也显示在队列,整理详情单独展示刮削结果;部分受理时已受理文件从本次范围移除,剩余文件可修改后再预览提交。队列复用现有后台调度,不要求浏览器停留到文件处理结束。记录、队列及弹窗明细均复用统一分页。
|
||||
|
||||
## 来源、分类与存量整理
|
||||
|
||||
设置 → 媒体库与目录中维护分类 YAML 和整理来源。新功能已于 2026-10-01 部署为 v0.3.9,迁移 `0059_organize_source_snapshot` 已执行,见 [发布记录](../plan/18-organize-release.md)。
|
||||
|
||||
- 一条来源配置选一个来源目录、一个媒体库目标目录、复制或移动方式。通过弹窗浏览已登记的目录选择,不需要填写宿主机任意路径。不同来源可以使用相同分类,规则只维护一次。
|
||||
- 分类一级固定为 `movie` / `tv`。规则按顺序首次匹配;同一规则的字段同时满足,同字段逗号值为任一匹配,`!` 排除。`release_year` 支持年份或区间;原始 TMDB 详情的一级字段也可使用。
|
||||
- 空条件是兜底,应放在最后。默认保留「欧美电影」「欧美剧集」承接所有未命中的习惯。TV 默认顺序为儿童剧集、动漫剧集、纪录片、综艺、华语剧集、亚洲剧集、欧美剧集。尚未取得 TMDB 详情不能冒充分类成功。
|
||||
- 自动分类在目标目录下追加分类目录,再使用现有作品/季目录模板。固定分类直接以所选目标为分类目录,避免追加第二遍分类名称。预览显示完整目标路径。已有作品沿用路径绑定,保存新规则不会移动旧库。
|
||||
- YAML 保存后用于后续计划,重启保留;语法或目录名不合法时拒绝保存,已有规则保留。
|
||||
|
||||
来源触发方式可以选手动、qB 标签或目录快照。qB 标签同时限定下载器实例和来源范围;只有完成的任务进入整理,去掉标签后停止接管,已有管理标签继续有效。
|
||||
|
||||
新增来源或更改来源路径时,先将当前视频文件记录为基线,**这些文件不会因此自动入库**。快照持久化在数据库,重启保留。后续新增或改变的文件,在连续两次扫描确认大小和修改时间稳定后再处理。已知 qB 任务由下载器接管流程负责,避免两种触发重复整理。下载器目录会查询当前全部 qB 任务,即使未打标签、尚无数据库任务行,也会等下载完成。下载器不可达或目录读取失败会记录错误并保留此前快照。
|
||||
|
||||
「整理存量」是可重复触发的一次性扫描:排除同一源文件事实已成功整理的文件及未完成下载,历史部分成功中的失败文件、同路径发生变化的文件仍可选,候选进入同一个整理弹窗,确认识别、操作和完整目标路径后执行。单次候选按识别上限分批;完成后再次触发可处理余下文件。这个操作不重建或清空基线。识别不确定时也可打开现有智能助手协助判断,再加入整理队列。
|
||||
|
||||
文件管理器选中文件或目录后点「整理」,会读取来源配置并按同根、目录边界和最长来源路径匹配所选全部项目。唯一匹配自动带入复制/移动方式、分类和目标策略;多个同等匹配让用户选择,也可不用配置手动指定。不同来源混合选择或没有匹配时使用共用整理弹窗的手动选项。媒体类型在预览时核对,配置目标可由本次明确的目标选择覆写。
|
||||
|
||||
实现复用既有识别、冻结计划、文件操作账本、整理记录与任务引擎;未增加软链接、硬链接或另一套 AI 执行流程。
|
||||
|
||||
## 手动下载、Local 元数据与一次性洗版(已部署 v0.3.10)
|
||||
|
||||
搜索、站点工作台和原站详情均可独立下载,不要求订阅。下载时默认正常入库,也可选择一次性洗版并勾选旧版本;只有一个旧版本时默认选中,多个由用户选择。媒体详情与下载窗口展示已有文件和规格。正常入库保留已有版本;不同版本路径由可编辑的命名模板决定,同名冲突仍需处理。
|
||||
|
||||
未识别资源确认后允许下载。整理时先匹配 TMDB,再由现有网络/Agent 元数据补全能力核对译名、原名和别名;能够找到正确条目时回到 TMDB,找不到且详情资料充分时使用 Local 身份。Local 共用现有分类、命名、复制/移动和 NFO/图片任务,并保留字段来源;信息不足或候选冲突保留待处理。Agent 搜寻依赖已配置的模型/搜索能力和现有每日补全预算;没有封面资料时不会虚构图片。Local 入库安排元数据文件写入,普通 TMDB 自动刮削保持原设置。
|
||||
|
||||
洗版在本次冻结计划中保存选中的旧文件。新文件核验登记成功后才清理所选旧版本,剧集只处理成功对应的季集与 Part;覆盖不完整的合并集保留。相同目标路径会先暂存旧文件,失败后恢复。旧版的私有字幕等随之清理,共享 NFO/图片、未选版本及下载缓存保留;按现有文件替换设置移入回收站或删除。
|
||||
|
||||
关联迁移 `0060_manual_acquisition`,已于 2026-10-01 随 v0.3.10 发布执行。业务依据与验收见 [MP 对照 §5.6](../plan/15-moviepilot-v3-comparison.md#56-本轮实现与验收已部署-v0310)。
|
||||
|
||||
## 与 MP 的对照
|
||||
|
||||
@@ -16,13 +60,13 @@ MP 对照源:`MoviePilot-Frontend/src/views/reorganize/TransferHistoryView.vue`
|
||||
| --- | --- | --- |
|
||||
| 海报 | 新增 | 作品海报(`media.posterPath`),没有时显示首字占位 |
|
||||
| 标题 / 年份 / 季集 | 已有 | `作品 (年) S01E02`;未识别的显示「未识别 · 源文件名」 |
|
||||
| 二级分类(华语电影等) | 不做 | 我们没有二级分类;按类型(电影 / 剧集)筛选代替 |
|
||||
| 二级分类(华语电影等) | 已接入 | 分类规则在设置编辑 YAML;电影 / 剧集为母分类,综艺属于剧集 |
|
||||
| 源路径 → 目标路径 | 新增 | 列表加「源 → 目标」列(只显示末两段,悬停看全路径);详情「整理前 / 整理后」已有 |
|
||||
| 存储标签(本地 / SMB…) | 不做 | 只有本地存储,标签不提供信息 |
|
||||
| 整理方式 复制 / 移动 / 硬链接 | 已有 | 列表「方式」列;文件管理的改名、移入回收站、还原也在这里 |
|
||||
| 整理方式 复制 / 移动 | 已有 | 列表「方式」列;整理页聚焦复制/移动结果 |
|
||||
| 软链接、Rclone 复制 / 移动 | 不做 | 整理服务不支持这几种方式 |
|
||||
| 大小 | 新增 | 逐文件源大小之和(`sizeBytes`) |
|
||||
| 状态 成功 / 失败 + 失败原因 | 已有,改 | 加「部分成功」「执行中」;成功不加徽章;失败原因走 `reasons.ts` 一句话,原码进详情「技术信息」 |
|
||||
| 状态 成功 / 失败 + 失败原因 | 已有,改 | 支持「部分成功」;执行中任务在队列查看;成功不加徽章;失败原因走 `reasons.ts` 一句话,原码进详情「技术信息」 |
|
||||
| 时间 | 已有,改 | 相对时间,悬停看完整时间 |
|
||||
| 来源 下载器 / 手动 / 自动 | 新增下载器 | 来源列加下载器名称;详情加「下载任务」一行,种子 hash 进「技术信息」 |
|
||||
| 关键字搜索(标题、源、目标;`*` `?` 通配) | 已有,补 | 补通配、逐文件的源 / 目标路径、种子 hash |
|
||||
@@ -31,17 +75,17 @@ MP 对照源:`MoviePilot-Frontend/src/views/reorganize/TransferHistoryView.vue`
|
||||
| 类型筛选 | 新增 | 电影 / 剧集 / 未识别(`kind=movie\|series\|none`) |
|
||||
| 时间筛选 | 新增 | 今天 / 最近 7 天 / 最近 30 天(`since`)。MP 没有,任务单要求 |
|
||||
| 统计(总数 / 成功 / 失败) | 新增 | 列表上方一行计数,只列非零项,点一下按该状态筛选(`GET /organization-records/stats`) |
|
||||
| 分页、每页条数 | 已有(不同) | 游标「加载更多」,MP 手机端同样是触底加载;不做页码与每页条数 |
|
||||
| 分页、每页条数 | 已有(不同) | 共享分页组件,10/20/50/100 条和翻页;游标按需加载,无总数时显示「已加载」 |
|
||||
| 列排序 | 不做 | 按时间倒序;分组模式下按作品聚合 |
|
||||
| 分组模式(按标题) | 新增 | 「按作品分组」,组头可折叠 |
|
||||
| 筛选写进地址栏 | 新增 | 状态 / 类型 / 来源 / 时间 / 关键字 / 分组都在 URL,刷新不丢 |
|
||||
| 单条:重新整理 | 已有,补 | 详情已有;列表行菜单新增。走整理向导(识别 → 选项 → 预览执行) |
|
||||
| 单条:重新整理 | 已有,补 | 详情已有;列表行菜单新增。走共用弹窗(配置、展开预览、加入队列) |
|
||||
| 单条:智能助手整理 | 已有(不同) | 「让智能体排查」:失败 / 部分成功的记录在行菜单和详情里 |
|
||||
| 单条:删除(只删记录 / 连源文件 / 连媒体库文件 / 全部) | 新增 | 确认框里两个勾选项,先核对再执行,写清删几个文件、多大、哪些删不了、会不会被自动整理再处理 |
|
||||
| 查看详情 | 已有 | 详情加海报、大小、下载任务、删除按钮 |
|
||||
| 批量选择 / 全选 | 新增 | 「批量」进入选择模式,表头全选 |
|
||||
| 批量删除 | 新增 | 同单条删除确认框;删不了的逐条列出,其余照删 |
|
||||
| 批量重新整理 | 新增 | 选中记录的源文件一起进整理向导;同属一个作品时预选作品 |
|
||||
| 批量重新整理 | 新增 | 选中记录的可用文件一起进整理弹窗(成功移动取目标,其他取源);同属一个作品时预选作品 |
|
||||
| 清空整理记录 | 不做 | 整理记录同时是文件变更的追溯记录;清理按保留期由计划页负责 |
|
||||
|
||||
## 删除
|
||||
@@ -50,7 +94,7 @@ MP 对照源:`MoviePilot-Frontend/src/views/reorganize/TransferHistoryView.vue`
|
||||
|
||||
- 文件一律移入回收站,走文件管理删除(`deleteFiles`,mode=trash,`retireAssets=true`):已入库文件同时从媒体库移除(位置 removed、资产 retired、满足的收录版本转需关注 + 待处理)。回收站里能还原。
|
||||
- 源文件 = 该记录逐文件结果的 `source_path`(只认执行进程本地视图);媒体库文件 = 成功文件的 `target_path`(相对库根时按库根拼)。已经不在的文件跳过、不拦。
|
||||
- 只有复制 / 移动 / 硬链接记录有文件可删;改名、移入回收站、还原类记录只删记录本身。
|
||||
- 只有复制 / 移动 / 原地入库 / 硬链接记录有文件可删;改名、移入回收站、还原类记录只删记录本身。
|
||||
- 一条记录的任一文件不能删(做种未满足或下载中 `download_active`、绑定目录 `needs_unbind`、不在登记目录 `outside_roots`、目录离线等),这条记录连同它的文件都不动;还在整理的记录(`running`)不删。其余照删。
|
||||
- 文件移入回收站会写一条「移入回收站」整理记录(同文件管理删除),`trashRecordId` 返回它。
|
||||
- 下载器里的任务不删(同文件管理删除原则)。
|
||||
@@ -63,3 +107,16 @@ MP 对照源:`MoviePilot-Frontend/src/views/reorganize/TransferHistoryView.vue`
|
||||
- 删媒体库文件只删整理记录里的文件,目录里的 NFO、图片与空目录保留(MP 在没有剩余视频时会连目录删掉)。
|
||||
- 分组只对已加载的记录生效(同 MP 分组模式)。
|
||||
- 统计的「进行中」= 排队 + 执行中;点它时列表只筛执行中。
|
||||
|
||||
## 原地入库(v0.3.14 待部署)
|
||||
|
||||
文件管理器选择已登记媒体库目录中的视频或目录,进入整理弹窗,默认选择「原地入库 · 保留位置与名称」。识别作品、手动指定电影/剧集、TMDB 和季集的方式与复制/移动相同,生成预览后加入现有整理队列。
|
||||
|
||||
- 源与目标路径相同;不复制、不移动、不重命名,不清理媒体目录中的临时文件。配对字幕/音轨同步登记,文件名和内容保持原样。
|
||||
- 登记视频资产、所在库根、实际相对路径、版本信息与季集覆盖,已有同作品资产不重复创建;不新建会改变以后整理目标的目录绑定。
|
||||
- 原地模式不使用来源配置的搬运目标、命名模板或替换策略,弹窗隐藏这些选项;不支持借此洗版或替换已有文件。
|
||||
- 开启「整理后刮削」时,仅针对本次成功登记的视频在原目录补写 NFO/图片,不递归刮削整个同目录或该作品的其它版本。只登记/只读库根可原地入库,刮削关闭。
|
||||
- 文件必须位于已登记、启用的媒体库根;下载缓存仍使用复制/移动或独立「刮削」。希望将现有目录直接作为媒体库时,先在设置登记该目录,然后原地入库。
|
||||
- 预览与入队复用原计划校验;执行时确认文件未变化,不把已经归属其它作品的文件悄悄改绑。结果标为「原地入库」,可从整理记录重新整理。
|
||||
|
||||
此功能代码已实现,当前生产仍为 v0.3.13;本会话不能访问 Docker 或测试 PostgreSQL,部署及真实数据库回归尚待补验,见发布记录。
|
||||
@@ -120,7 +120,7 @@
|
||||
- 憨憨详情页(HH-2 按真实页面 `probe.labels` 核对):没有 `#kdescr`,内容分节为「标签 div + 相邻值 div」:`mediainfo`、`海报`、`豆瓣`(内有 `span.font-bold` 小标签 片名 / 年代 / 类别 / 导演 …)、`截图`、`种子文件`(只有「查看列表」按钮)、`发布者`、`发布时间`。对应规则:简介没有容器时取「豆瓣 / 影片信息 / IMDb」信息块,小标签补冒号、一项一行;截图取「截图」一节的 img(`data-src` 优先,海报不算截图),没有再取简介里的图;IMDb 依次取页面链接 → 简介 / 信息块 / MediaInfo 里的 `imdb.com/title/tt…` 或 `IMDB : tt…` 行(只收 `tt` + 7~8 位,不收孤立 tt 串),TMDB 取链接或 `TMDB : tv/294990` 行,豆瓣取链接或 `douban.com/subject/…`;兜底出的 IMDb / 豆瓣也回填到 `SiteRelease.imdbId / doubanId`;发布时间没有 `span[title]` 时取「发布时间 / 上传时间」字段文本。文件清单:定义 `umt.detail.page.fileListPath`(憨憨 `viewfilelist.php?id={id}`)配置了且页面上没有内联清单时,同一次详情刷新里再请求 1 次(走同一限流器、计数),失败只是没有清单。fixture `HHANCLUB_DETAIL_PAGE_2026` / `HHANCLUB_FILELIST_FRAGMENT`:标签名称与顺序来自真实页面,嵌套按已验证的标题 / 副标题结构推定,部署后打开一条憨憨资源的详情抽屉核对一次。
|
||||
- 观众详情页(DET-2 按真实页面核对,样本 `packages/adapters/test/differential/inputs/detail/rl-audiences-detail-*.html.txt` 已脱敏,回归测试 `test/differential/detail.test.ts`;列表页样本 `inputs/indexer/rl-audiences-list.html.txt` 进 MP 差分):标准 rowhead 表格,但有几处与通用 NexusPHP 不同,写在定义里。h1 的促销是 `font.free / font.halfdown …`(列表是 `img.pro_*`)加「剩余时间:」和到期 `span[title]`,用 `umt.detail.promo` 覆盖列表的促销字段;h1 的「剩余时间:」文本不进标题,标题区里的 `span[title]` 也不当发布时间(发布时间取「下载」行「发布于」的 span)。没有「同伴」行,做种 / 下载 / 完成数取基本信息行右侧 `#seeders / #leechers / a[title=完成]`(`umt.detail.fields.seeders / leechers / snatched`);种子 Hash 取 `#copyTorrentHashLink[data-hash]`(`fields.infoHash`,回填 `infoHash`)。副标题行右侧有豆瓣 / IMDb / 烂番茄 / Metacritic 评分徽标(`[title*=评分]`),解析时去掉;评分本身本工具没有字段,不收。标签是 `span.detail-tag-list > span.tags`,只取最内层。上传者在「下载」行的 `span.torrent-uploader-wrap`(匿名发布为「匿名」);另有「发布者带宽」一行,所以上传者标签改为精确匹配。简介开头的站点声明 `#ad_torrentdetail` 用 `umt.detail.page.descriptionStrip` 去掉;MediaInfo / BDInfo 是 `div.codetop`(文件名 / BDInfo)+ `div.show`(摘要表或同一份 BDInfo)+ `div.hide > div.codemain`(原文),三者都从简介里去掉;简介是 `<br />\r\n`,源码换行不再算一行(以前每行之间多一空行)。截图排除豆瓣海报(站点经 `dbimgs.audiences.me/?…doubanio.com…poster` 代理)与站点横幅 `/banners/`。IMDb / 豆瓣 ID 取简介里的链接;TMDB 页面上没有;单种 H&R 标记页面上没有(列表也没有),保持「未知」。文件清单不在页面上(点「N个文件」后 AJAX 加载,端点未核对),暂不另发请求。列表副标题改取 `span.torrent-subtitle-text`(`span.torrent-subtitle-line` 里先是标签,以前副标题前面会粘着「官方中字DIY…」)。
|
||||
- 截图代理的 Referer:截图与站点同根域(憨憨图床 `pic.hhanclub.net`、`proxy-cover.hhanclub.net`)时带 `Referer: <站点地址>/`(防盗链),外部图床(如 pixhost)不带;任何情况都不带 Cookie。
|
||||
- 解析:`packages/adapters/src/sites/spider/detail-page.ts`。简介取 MovieClaw `detail_description_css`(缺省 `#kdescr`);MediaInfo 取定义 `umt.detail.page.mediaInfo` → 「MediaInfo」字段标签 → 简介里第一个像 MediaInfo 的引用 / 代码块;文件清单取 `umt.detail.page.files` → MovieClaw `detail_file_list_css` → 「文件列表」字段 → `#filelist` 表格,不额外请求 `viewfilelist.php`。字段标签除 `td.rowhead` 外可在 `umt.detail.page.labelSelectors` 加(憨憨:`div.font-bold.leading-6`、`span.font-bold`)。`page.probe` 记录页面上找到的字段标签,只存缓存不展示,站点改版时用来排查。馒头从 API 的 `descr`(BBCode)与 `mediainfo` 取同样的结构。
|
||||
- 解析:`packages/adapters/src/sites/spider/detail-page.ts`。简介取 MovieClaw `detail_description_css`(缺省 `#kdescr`);MediaInfo 取定义 `umt.detail.page.mediaInfo` → 「MediaInfo」字段标签 → 简介里第一个像 MediaInfo 的引用 / 代码块;文件清单取 `umt.detail.page.files` → MovieClaw `detail_file_list_css` → 「文件列表」字段 → `#filelist` 表格,默认不额外请求;定义配置了 `umt.detail.page.fileListPath` 且页面没有内联清单时,按配置补请求(憨憨为 `viewfilelist.php`)。字段标签除 `td.rowhead` 外可在 `umt.detail.page.labelSelectors` 加(憨憨:`div.font-bold.leading-6`、`span.font-bold`)。`page.probe` 记录页面上找到的字段标签,只存缓存不展示,站点改版时用来排查。馒头从 API 的 `descr`(BBCode)与 `mediainfo` 取同样的结构。
|
||||
|
||||
## Agent 能力(W10-pre2)
|
||||
|
||||
@@ -135,8 +135,8 @@
|
||||
|
||||
- 授权:两个 execute 能力走通用授权,Agent / API Key / 后台调用必须有 `automation_grant`(`action` 为能力 ID 或 `*`),自动化总开关关闭或只读对账时一律拒绝;控制台管理员从 Web 直接调用视为人工授权。没有计划可绑定,所以不走单次审批。
|
||||
- 脱敏:输出按白名单逐字段拼装,没有 `secretRef` / `sessionSecretRef` / `passkeySecretRef`、Cookie、密码、登录名、UA 与站点 `config`;凭证只给 `hasSession` / `hasPasskey` 两个布尔。测试 `apps/server/test/sources-identity/site-capabilities.test.ts` 逐项断言。
|
||||
- 注册数:首批能力 17 → 21,启动断言 `assertCapabilityCount` 跟随 `CAPABILITY_IDS.length`。
|
||||
- 未接:`agent/invoke-limits.ts` 的站点并发上限还没把 `site.health_check` / `site.refresh_login` 算作外部站点调用;Agent 默认只读白名单(`agent/runner.ts`)也还没加 `site.list` / `site.user_data`。两处都在 agent 模块,留给该模块的负责人。
|
||||
- 注册数:这组站点能力最初将能力集由 17 扩到 21;当前完整能力集为 41,启动断言 `assertCapabilityCount` 跟随 `CAPABILITY_IDS.length`。
|
||||
- Agent 默认只读白名单(`agent/runner.ts`)已包含 `site.list` / `site.user_data`;`agent/invoke-limits.ts` 的能力级站点并发上限尚未纳入 `site.health_check` / `site.refresh_login`,底层站点访问仍走站点限流。
|
||||
|
||||
## 出口:哪些请求走哪条(CF-2)
|
||||
|
||||
|
||||
@@ -178,3 +178,10 @@ PATCH `status` 仍可直接改(旧行为),完成写 `subscription_history` 的
|
||||
- 日历不做 MP 的"日历缓存预热"任务;季详情只在元数据刷新时拉。综艺按播出日期的分段单元仍只在矩阵列上标 `segment`。
|
||||
- 综艺分段列(同一播出日的上/中/下拆成独立列)等身份层按分段建单元后启用;现在只在列上标 `segment`。
|
||||
- 版本目标新建(`POST /acquisition-slots`)position 缺省 0,与主版本同值时按 created_at 排在后面;需要调整顺序用 slot-order。
|
||||
|
||||
|
||||
## 本轮完成口径调整(已部署 v0.3.10)
|
||||
|
||||
普通订阅按下载器确认接收和目标覆盖判断完成,完成原因 `download_accepted`;Slot 与下载/整理任务仍继续记录实际下载、核验和入库状态。多目标必须全部满足,剧集只有明确季集范围才能计覆盖,未知总集数不按整个季的通配预留伪造完成。洗版订阅仍按入库结果判断。
|
||||
|
||||
手动搜索、工作台和原站详情下载独立于订阅,不创建假订阅,也不会占用原订阅 Slot。要对现有订阅补全仍使用上面的 Slot 补全或自动评估入口。来源与验证见 [MP 对照 §5](../plan/15-moviepilot-v3-comparison.md#5-下载入口与浏览器详情页交接讨论记录)。
|
||||
Reference in new issue
Block a user