P1: host-agnostic quercus-core with agent loop, providers, and tool schemas

- Core types: Rational time, EntityId, ActionBatch, PngBytes (no Pillow dep)
- Config: ~/.quercus/config.toml with 0600 perms, env var override for CI
- LLMProvider: Claude (anthropic SDK) and OpenAI-compatible endpoint
  (covers custom gateways and llama.cpp server mode); graceful degradation
  without API keys
- Tool schemas: 26 curated tools with JSON Schema and read_only flags
- AgentLoop: sync dialogue loop with confirmation gate (read-only batches
  bypass, rejected mutations are reported back to the LLM), PNG frame
  feedback for multimodal providers
- Session log: JSONL recording with frame sha256 hashes, replayable
- HostAdapter ABC (signatures only, aligned with OPP/1 method families)
- Mock LLM provider (scripted/replay/record) for network-free CI

Tests: 69 passed via uv run pytest (no network access)
This commit is contained in:
2026-08-25 02:06:10 +08:00
parent c9870b4faf
commit e0db5ab2cf
33 changed files with 3957 additions and 0 deletions
+254
View File
@@ -0,0 +1,254 @@
# AI Agent 插件设计(基于 OPP/1 外部插件系统)
> 本文是 Oak 引入 AI 能力的长期设计,**已按外部插件系统重写**(旧版假设
> RIIR 拆分后面对一堆 C ABI 小库、引擎内置 `oak-mcp-server`,已作废)。
> AI 能力现在是一个**外部功能插件**:独立进程、经 OPP/1 协议操作 Oak。
>
> 依赖文档(冲突时以它们为准):
> - [`external-plugin-system.md`](external-plugin-system.md)——插件系统总体设计
> (进程模型、能力位、确认模式、里程碑 P1–P6)
> - [`external-plugin-protocol.md`](external-plugin-protocol.md)——**OPP/1 协议全文**
> (方法/事件/事务/shm/UI 的冻结定义,本文引用的 §n 均指该文档)
>
> **一句话**:多模态 LLM 跑在一个独立插件进程里,经 OPP/1 的策展宿主 API
> 操作 Oak(事务化编辑、确认后执行),经 `render.get_frame/get_thumbnails`
> 的 shm 取帧通路把画面回喂模型,形成"编辑 → 看图 → 再编辑"的视觉闭环。
---
## 1. 定位与前提
### 1.1 前提(未满足不动工)
- 插件系统 **P1–P3 完成**:传输与生命周期、宿主 API 核心(事务 +
project/media/timeline/node 方法族)、取帧与导出的 shm 数据面可用。
- AI 面板需要 **P4**(声明式 UI);无头模式(脚本/CI)只依赖 P1–P3。
- Python SDK `oakxp`(P6 的一部分)是本插件的载体,二者同期开发、互为验证。
### 1.2 设计铁律(继承旧版,按插件系统重述)
1. AI Agent **只经 OPP/1** 访问 Oak——插件进程内一行 Oak 代码都没有,
不链接任何 Oak 产物(铁律:协议是唯一边界)。
2. 一切编辑动作**必须包在 `edit.begin/commit` 事务里**(协议 §6),
历史面板一次 Ctrl-Z 整段撤销;UI 默认"确认后执行"。
3. 不为 AI 发明新的引擎内部机制;Agent 工具面是 OPP/1 方法的组合,
OPP/1 方法又是现有 `graphops/renderops/oak_task` 的组合。
4. API key 绝不写入 **Oak 侧**的任何文件(`.ove` 工程、Oak 自身配置);
允许存**插件自己的**配置文件(§5.1),环境变量仅作 CI/无头场景的覆盖项。
## 2. 总体架构
### 2.1 插件进程内部分层
插件名 **`oak-plugin-ai`**(Python 3,实现语言与发行形态的论证见 §2.3;
参考实现即插件系统的 `examples/plugin-roughcut` 的完整版):
```
┌─ Oak 主进程 ────────────────────────────────┐
│ oak-plugin-host(P1–P4 提供) │
│ ├ OPP/1 控制面(JSON-RPC over stdio) │
│ └ shm down/up 区域 │
└───────┬─────────────────────────────────────┘
│ stdio + shm
┌───────┴─── oak-plugin-ai(独立进程)────────┐
│ ⑤ LLMProvider:Claude/GPT │ llama.cpp 本地 │
│ ④ Agent 编排:对话 loop、工具调用、视觉闭环 │
│ ③ 工具适配层:OPP 方法 → LLM tool schema │
│ ② 会话状态:事务令牌、帧缓存、操作日志 │
│ ① oakxp SDK:分帧/收发/shm attach/帧→PNG │
└─────────────────────────────────────────────┘
```
- 插件可以**纯无头**跑(CI、批处理脚本:spawn 后不注册面板,只走工具面);
注册面板时才是用户可见的"AI 剪辑助手"。
- **MCP 的位置**:如要让外部 LLM 客户端(Claude Desktop、agent 框架)直连,
由**插件自己**在进程内起一个 MCP server,把 §3 的工具面按 MCP 再暴露一次。
Oak 内核始终对 AI、对 MCP 无感知——这是与旧版"引擎内置 oak-mcp-server"
的根本区别。
### 2.2 视觉闭环(本设计的核心)
```
多模态 LLM ──► Agent 编排 ──► OPP 事务化编辑 ──► render.get_frame ──► PNG ──► 回喂 LLM
▲ │
└────────────────── 看图判断(效果/切点/内容定位) ◄─────────────────────────┘
```
- **验证式**:`edit.commit` 后立即 `render.get_frame` 取切口/效果帧,LLM 判断
"效果对不对",不对则 `edit.undo` 或追加修正事务。
- **内容感知式**:`render.get_thumbnails(range, count≤64)` 等间隔采样拼
contact sheet,LLM 扫图定位("人何时进画面""哪里该切"),据此下刀——
自动粗剪/打点的雏形。
- **连续回放**:`playback.play` + `playback.playhead_moved` 事件(≤30 Hz)+
按间隔 `get_frame` 采样。注意受 §12 限流约束(默认 8 帧/s),采样间隔
不得小于限流周期。
- 完整报文流水示例见协议文档 §14(握手→缩略图→事务下刀→验证帧→事件),
可直接作为本插件的集成测试夹具。
### 2.3 实现语言与跨宿主复用(决策已定)
**定为 Python,解释器嵌入式发行**:插件包内含私有 CPython(PyInstaller 或
python-build-standalone),`.oakplugin` 单包交付,用户无需自行安装 Python。
插件以 **GPLv3** 开源发布;作为独立进程经 stdio/JSON-RPC 与 Oak 通信,
许可证选择不影响 Oak 本体。
选 Python 而非 Rust 的理由:
1. **性能无关**:插件只做协议编解码、LLM 编排、PNG 编码(Pillow 为 C 实现);
延迟大头是 LLM 往返(秒级)与 Oak 侧渲染(与插件语言无关),插件进程
没有任何重计算。
2. **跨宿主复用**:同一套 AI 能力规划覆盖 Oak / DaVinci Resolve / Premiere,
但三家宿主的扩展 API 语言各异——Resolve 是 Python/Lua;**Premiere 没有
Python 入口**(UXP/JavaScript 面板 + C++ SDK)。因此正确结构不是
"一种语言通吃",而是**一份与宿主无关的 AI core + 各宿主薄适配层**:
```
┌─ AI core(宿主无关,一份代码)───────────────┐
│ Agent 编排 / LLMProvider / tool schema │
│ 提示词策略 / 会话状态 │
└──────┬───────────┬──────────────┬───────────┘
Oak 适配层 Resolve 适配层 Premiere 适配层
(OPP/1, (Resolve (UXP/JS 面板 →
oakxp SDK) Python API, localhost 调
直接 import) core 本地服务)
```
- Oak 适配层 = oakxp SDK(OPP/1),即本文的 `oak-plugin-ai`;
- Resolve 适配层直接 import core(Python 母语,零成本复用);
- Premiere 用 UXP 面板做壳,经 localhost 与本机 core 进程通信。
3. **生态**:主流 LLM SDK(anthropic / openai / llama.cpp 绑定)均为 Python
一等公民,Agent 框架与评测工具链也最全。
## 3. 工具面(LLM tool schema → OPP/1 方法映射)
Agent 暴露给 LLM 的是约 20 个**策展工具**,每个是 OPP/1 方法的薄组合
(协议 §8 是方法全文,下表"OPP 方法"列即最终调用):
| Agent 工具 | OPP 方法 | 说明 |
|---|---|---|
| `open_project` / `save_project` | `project.open/save` | 工程生命周期(事务 + 确认类) |
| `get_project_overview` | `project.get_info` + `timeline.get_structure` | 一次返回序列/轨道/块树,供 LLM 建立上下文 |
| `probe_media` / `import_footage` / `list_footage` | `media.probe/import_footage/list_footage` | 媒体探测与导入 |
| `add_track` / `place_clip` / `split_clip` / `trim_clip` / `move_clip` / `ripple_delete` / `add_transition` / `add_marker` | `timeline.*` | 时间线编辑,全部在事务内 |
| `add_effect` / `set_param` / `set_keyframe` / `list_effects` | `node.add_effect/set_param/set_keyframe/list_types` + `get_params` | 效果与关键帧;`get_params` 的 min/max/choices 回填进 tool schema,约束 LLM 出参 |
| `get_frame(time)` / `scan_timeline(range,n)` / `get_audio_levels` | `render.get_frame/get_thumbnails/get_audio_levels` | **视觉闭环取帧口** |
| `play` / `pause` / `seek` | `playback.*` | 回放控制 |
| `export_render(preset)` | `export.start` + `export.progress/done` 事件 | 导出(确认类) |
| `undo_last_action` | `edit.undo` | 仅用户明确要求时调用(协议 §6 规则 5) |
**事务编排是适配层的职责,不暴露给 LLM**:LLM 的一次"动作"(可能含多个
`timeline.*` 调用)由适配层包成一个事务——先 `edit.begin(label=LLM 动作摘要)`,
串行执行(协议保证同事务内按到达顺序),任一失败则 `edit.abort` 并把错误
回喂 LLM,全成功才 `commit`。LLM 看不到 `txn` 令牌,从根上避免"忘记 commit"
"嵌套事务"这类误用。
**取帧→PNG 通路**(关键路径):`render.get_frame` 返回 `FrameRef`(shm 形态:
`bgra8` + 槽位号),SDK `frame.to_png()`(Pillow)编码 → base64 → 作为图片
消息发给 LLM;随后**立即 `shm.release`**——批量扫描时必须流水线化释放,
否则 8 槽耗尽触发 `SHM_EXHAUSTED`(协议 §10.3)。小图(≤64 KiB)Oak 可能
直接 inline PNG,SDK 对两种形态透明。
## 4. AI 面板(声明式 UI,协议 §11.1)
面板在 `session.hello.panels` 声明 `"ui":"declarative"`,控件树:
```
column
├── chat_log #log 对话与操作日志(用户/助手/系统三角色)
├── list #pending 待确认动作清单(确认模式,见 §5)
├── row
│ ├── text_input #prompt 剪辑意图输入
│ └── button #send 执行
└── progress #job 扫描/导出进度
```
- 交互经 `ui.event` 上行(`submit`/`click`/`select`),插件用
`ui.set_props` 增量追加 `chat_log` 条目、更新进度。
- **"撤销整段会话"**:面板放一个按钮,逐个 `edit.undo` 回滚本会话提交的
事务(插件在自己的会话状态里记事务顺序)。
- 面板被关闭会收到 `panel_closed`,重开收到 `panel_shown` 时重发
`ui.set_tree` 恢复(协议 §11.1)。
- 像素面 UI(§11.2)本插件**不用**——聊天面板声明式足够。
## 5. 模型层与安全
### 5.1 LLMProvider
抽象接口(输入:消息 + 图片;输出:文本 + tool_calls),后端:
- **云端 BYOK**:Claude / GPT 多模态,用户自带 key(效果优先)。
- **本地**:llama.cpp 跑 Qwen-VL / LLaVA 类多模态模型(隐私、离线优先)。
- **自定义 endpoint**(OpenAI 兼容接口):把 provider 指向任何兼容
`/v1/chat/completions` 的服务(自建代理、企业网关等)——为后续接入
托管服务预留通用通路,不绑定特定厂商。
API key 存**插件自己的配置文件**(如 `~/.oak/plugins/oak-plugin-ai/config.toml`),
面板提供密钥输入框(`text_input`),用户无需手配环境变量;环境变量只作
CI/无头场景的覆盖项(优先级:环境变量 > 配置文件)。配置文件权限 0600、
不进版本库。存插件自己的配置是插件的内部事务,Oak 不感知——铁律 4 约束的
只是 **Oak 侧**的文件。无 key 时优雅降级:面板仍可用,但只做"工具说明 +
手动执行",不做对话编排。
### 5.2 能力位与确认(协议 §7 的具体化)
manifest 声明**最小必要集**:
```toml
capabilities = ["project.read", "media.read", "media.import",
"timeline.read", "timeline.edit", "node.read", "node.edit",
"render.frame", "playback", "export", "ui.panel"]
```
双层确认,职责分清:
1. **Oak 协议层**(§7.3):`timeline.edit` 等确认类方法默认弹窗
"插件 oak-plugin-ai 请求:split_clip …"。用户可选"本会话内允许"。
2. **插件会话层**:Agent 把 LLM 规划出的整段动作先列入 `#pending` 清单,
用户点"执行"才发事务——这是体验层确认,与协议层弹窗不冲突:
建议插件引导用户在 Oak 侧对本插件设"本会话允许",确认交互集中在面板内。
### 5.3 其他安全约束
- **限流遵守**:扫描采样按握手 `limits` 下发的配额规划(默认 8 帧/s、
短边 ≤1080),收到 `RATE_LIMITED` 按 `retry_after_ms` 退避,不得重试轰炸。
- **沙箱会话**(可选增强):对破坏性大改,Agent 可先 `project.save` 副本到
临时路径操作,用户接受后再回真实工程。有了事务 + 整段撤销后,此项降为
可选,默认不启用。
- 插件崩溃不丢编辑:已 commit 的事务都在 UndoStack 里,未决事务 Oak 自动
abort(协议 §6 规则 4)——AI 死在哪都不会留下半截剪辑。
## 6. 可测试(与项目风格一致)
1. **Mock LLM server**:录制/回放 tool_call 序列与固定回复,Agent loop 在
CI 无 key 无网络跑通。
2. **Mock Oak(协议级)**:`oakxp` SDK 自带回放 harness——把协议文档 §14 的
报文流水当夹具,插件不连真 Oak 也能单测工具适配层与事务编排。
3. **黄金帧校验**:连真 Oak 的端到端测试复用 render-worker harness
(真实渲染 ≥2 帧 + 像素非全黑 + 一致性断言),验证"Agent 的编辑确实
改变了画面"。
4. **会话回放**:tool_call + 帧哈希落盘日志,可回放复现、可作测试夹具。
## 7. 里程碑(对齐插件系统 P1–P6)
| 里程碑 | 内容 | 依赖 | 验收 |
|---|---|---|---|
| **A1 骨架** | `oakxp` SDK + 握手/心跳/重连;插件注册空面板 | P1、P4 | 杀掉插件 Oak 不崩、面板徽标与重启正常 |
| **A2 工具适配层** | §3 全表映射 + 事务编排(自动 begin/commit/abort)+ 取帧→PNG 通路 | P2、P3 | Mock Oak 夹具全绿;真 Oak 上"导入→铺轨→切开→删除→加效果"可整段撤销 |
| **A3 无头闭环** | Agent 编排 + LLMProvider + Mock LLM | A2 | CI 无网络跑通"LLM→工具→取帧→回喂";黄金帧校验过 |
| **A4 AI 面板** | §4 面板 + 待确认清单 + 撤销会话 | A3 | 真机对话粗剪一段素材,确认/撤销交互完整 |
| **A5 内容感知** | contact sheet 扫描打点、自动粗剪策略、本地模型 provider | A4 | 对 10 分钟素材自动出粗剪版,人工抽检切点可用 |
## 8. 风险与边界(明确不做)
- **不**把 LLM/推理放进任何 Oak 进程或引擎模块(引擎对 AI 无感知);
MCP server 如需存在,只在插件进程内。
- **不**绕过 OPP/1 访问 Oak(协议是唯一边界);LLM 不直接接触 `txn`
令牌(适配层封装事务)。
- **不**把 API key 写入 Oak 工程文件或 Oak 自身配置(插件自己的配置文件
除外,见 §5.1)。
- **不**让取帧回路阻塞 Oak GUI 线程——`render.*` 本来就走引擎 ticket/进程池
路径(协议 §8.6),插件侧并发流水线化而不是串行等帧。
- **不**突破协议限流;需要更高帧率的"连续回放分析"场景,先按 §13 版本
演进规则给协议加配额项,不在插件侧硬挤。
- 第三方大模型客户端的接入细节(OAuth、计费、配额)**超出本文范围**,
按需另立文档。
+380
View File
@@ -0,0 +1,380 @@
# Quercus AI 剪辑助手 — 分阶段实施计划
> 本文是 [`ai-agent-design.md`](ai-agent-design.md)(设计)的**施工计划**(怎么做、
> 分几步、每步验收什么)。设计决策以设计文档为准,本文不重复论证,只把
> "一份宿主无关 AI core + 各宿主薄适配层"的结构落成可执行的阶段计划。
>
> **范围调整(相对设计文档)**:
> - 设计文档的落地形态原是 Oak 插件(`oak-plugin-ai`,依赖 OPP/1 与插件系统
> P1–P4)。**Oak 插件系统仍在开发中**(见 `../../oak/docs/zh/plans/` 下的
> `external-plugin-system.md` / `external-plugin-protocol.md`),因此本计划
> **只做 DaVinci Resolve 与 Premiere Pro 两个宿主**;Oak 适配层**只预留接口
> (stub 包),不实现、不排期**,等 OPP/1 P1–P3 落地后按 §8 启动。
> - 设计文档中"工具面 = OPP/1 方法映射"的表述,在本计划中改为"工具面 =
> HostAdapter 接口映射"——接口形状**刻意对齐 OPP/1 方法族**,使未来的 Oak
> 适配层只是 oakxp SDK 的薄封装(§3.4)。
>
> 外部参考(实现时以官方最新文档为准):
> - Resolve 脚本 API:<https://extremraym.com/cloud/resolve-scripting-doc/>(X-Raym 整理,
> 源自 Resolve 安装目录 `Developer/Scripting/README.txt`)
> - Premiere UXP 示例:<https://github.com/AdobeDocs/uxp-premiere-pro-samples>
---
## 1. 目标与非目标
### 1.1 目标
1. **quercus-core**:宿主无关的 Python AI 核心——LLMProvider 抽象、Agent 编排
(对话 loop + 工具调用 + 视觉闭环)、约 20 个策展工具的 schema、会话状态与
回放日志、密钥管理。
2. **Resolve 适配层**(第一优先级):Python 母语直连 Resolve 脚本 API,支持
无头(`-nogui`)跑 CI,完整实现工具面与取帧闭环。
3. **Premiere 适配层**:UXP 面板(TypeScript)+ localhost 桥接 core 本地服务,
实现工具面的 Premiere 可用子集,能力缺口显式降级而非伪造。
4. **统一交互**:core 内建本地 Web 聊天面板(Resolve / 无头 / 调试通用);
Premiere 用 UXP 原生面板。两层确认(会话层"待确认清单"为主)。
5. **Oak 预留**:`quercus-oak` stub 包,实现 HostAdapter 接口签名 + OPP/1
映射注释 + manifest 草案,所有方法 `raise NotImplementedError`。
### 1.2 非目标(明确不做)
- **不**实现 Oak 适配层(等 OPP/1 P1–P3,见 §8)。
- **不**把 LLM 推理放进任何宿主进程;core 永远是独立进程。
- **不**做 MCP server 对外暴露(设计文档 §2.1 已定位:需要时由 core 自起,
本计划不排期)。
- **不**支持 Resolve 免费版的 UI 集成(v19.1 起免费版移除 UIManager;
免费版能否外部脚本调用在 P0 验证,若不可行则 Resolve 侧仅支持 Studio)。
- **不**为宿主伪造原子事务:Resolve / Premiere 无事务 API,用快照 + 补偿
实现"尽力回滚"(§3.3),对用户诚实标注。
- **不**在 Premiere 侧使用 ExtendScript / QE DOM 作为正式通路(EOL 风险;
仅允许 P0 spike 里做能力对照)。
## 2. 仓库结构与进程拓扑
### 2.1 仓库布局(monorepo)
```
quercus/
├── docs/ # 设计 + 本计划
├── core/ # quercus-core(PyPI 风格包,宿主零依赖)
│ ├── providers/ # claude / openai_compat / llamacpp
│ ├── tools/ # ~20 个工具 schema + 事务编排
│ ├── agent/ # 对话 loop、视觉闭环、待确认清单
│ ├── session/ # 会话状态、ActionBatch 记录、回放日志
│ ├── host/ # HostAdapter ABC + 类型(§3.1)
│ └── webui/ # 本地 Web 聊天面板(FastAPI + 静态页)
├── adapters/
│ ├── resolve/ # quercus-resolve(Python,直连脚本 API)
│ ├── premiere/
│ │ ├── bridge/ # core 侧:WebSocket server + 协议编解码
│ │ └── panel/ # UXP 面板(TS + Vite,结构对齐 Adobe 官方示例)
│ └── oak/ # quercus-oak(stub,仅接口 + 映射注释)
├── tests/
│ ├── mock_llm/ # 录制/回放 LLM server
│ ├── mock_host/ # HostAdapter 内存实现(行为对齐 OPP/1 语义)
│ └── e2e/ # Resolve -nogui 黄金帧测试;Premiere 手工清单
└── pyproject.toml # workspace(uv/pdm 均可,实现时定)
```
### 2.2 进程拓扑
```
┌─ DaVinci Resolve ─┐ ┌─ Premiere Pro ────────┐ ┌─ Oak(预留)─┐
│ 脚本 API(进程内) │ │ UXP 面板(Chromium) │ │ oak-plugin- │
└───────┬───────────┘ └──────────┬────────────┘ │ host (OPP/1) │
│ fusionscript │ WebSocket └──────┬───────┘
│ (同机直连) │ localhost+token │ stdio+shm
┌───────┴───────────┐ ┌──────────┴────────────┐ ┌──────┴───────┐
│ quercus-resolve │ │ quercus-premiere │ │ quercus-oak │
│ (core 的进程内 │ │ bridge(core 进程内 │ │ (stub) │
│ adapter 对象) │ │ WebSocket server) │ │ │
└───────┬───────────┘ └──────────┬────────────┘ └──────────────┘
└──────────► quercus-core ◄┘
(Agent / LLM / Web UI / 会话)
```
- **Resolve**:core 进程 import `DaVinciResolveScript`,adapter 是 core 进程内的
一个对象(Resolve 要求宿主在跑;core 直连,无中间进程)。也可反向:Resolve
脚本菜单拉起 core——安装形态在 P3 定,默认**core 为主动方**(便于无头与 CI)。
- **Premiere**:UXP 无法 import Python,core 在 localhost 起 WebSocket server
(随机端口 + 启动时写 token 文件到 `~/.quercus/bridge.json`,权限 0600;
面板连接须带 token)。面板只做 UI 与宿主 API 调用,Agent 逻辑全在 core。
- **Oak**:未来 core 进程内 import `oakxp`,同 Resolve 形态;本计划只留 stub。
### 2.3 关键决策(本计划新增,设计文档未覆盖的)
| # | 决策 | 理由 |
|---|---|---|
| D1 | 面板主形态 = core 内建本地 Web UI;Premiere 另有 UXP 原生面板 | Resolve 免费版无 UI 能力、无头场景无 UI;Web UI 一份代码三处复用 |
| D2 | Premiere 桥用 WebSocket localhost + token 文件鉴权 | UXP 网络能力需 manifest 声明域名,localhost 可用;token 防同机其他进程误连 |
| D3 | 事务语义统一为 `ActionBatch`:宿主有事务用事务,无事务用快照 + 补偿 | Resolve/Premiere 均无事务/撤销分组 API,诚实降级(§3.3) |
| D4 | 取帧通路每宿主一条主通路 + 一条备用,P0 先验证再定主备 | 三家取帧能力差异最大,是整个视觉闭环的生死点(§5 表) |
| D5 | 内部时间模型统一有理秒(对齐 OPP/1 `Rational`) | 避免 Resolve 帧号 / Premiere ticks 渗透进 core;换算全部在适配层 |
## 3. 统一抽象:HostAdapter 接口
### 3.1 接口形状(刻意对齐 OPP/1 §8 方法族)
```python
# core/host/adapter.py(示意,非最终实现)
class HostAdapter(ABC):
# 会话与能力
def capabilities(self) -> set[Capability]: ...
def limits(self) -> Limits: ... # 取帧速率/分辨率上限等,各宿主自报
# 工程 / 媒体 / 时间线 / 节点(方法族与 OPP/1 §8.2–8.5 一一对应)
def get_project_overview(self) -> ProjectOverview: ...
def probe_media(self, path: str) -> MediaInfo: ...
def import_footage(self, paths: list[str]) -> list[FootageId]: ...
def get_timeline_structure(self, seq: SeqId) -> Timeline: ...
# 取帧(视觉闭环生死通路)
def get_frame(self, target: Target, time: Rational, max_size: Size) -> PngBytes: ...
def get_thumbnails(self, target: Target, range: TimeRange, count: int) -> list[PngBytes]: ...
def get_audio_levels(self, seq: SeqId, range: TimeRange, resolution: int) -> Levels: ...
# 回放 / 导出
def play / pause / seek / get_state: ...
def export(self, seq: SeqId, output: str, preset: str) -> JobHandle: ... # 事件经回调
# 编辑(ActionBatch 语义,见 §3.3)
def execute(self, batch: ActionBatch) -> BatchResult: ...
def undo_last(self) -> None: ... # 仅用户明确要求时
```
- `EntityId` 为不透明字符串(对齐 OPP/1 §5),适配层内部维护
id ↔ 宿主对象 的映射,工程重载后全部作废并通知 core 重新拉取。
- 事件(结构变化 / 播放头 / 导出进度)以回调注入 core;宿主无事件源的
(Resolve 脚本 API 无事件)由适配层**轮询合成**,轮询频率进 `limits`。
### 3.2 工具面(core 对 LLM 暴露 ~20 个策展工具)
沿用设计文档 §3 的工具清单(`open_project` … `undo_last_action`),一处修改:
"OPP 方法"列改为"HostAdapter 方法"。事务编排职责不变——LLM 看不到事务/
快照细节,core 把一次 LLM 动作包成 `ActionBatch` 交给适配层。
### 3.3 ActionBatch:三档事务语义(诚实分级)
| 档位 | 宿主 | 实现 | 用户可见承诺 |
|---|---|---|---|
| 真事务 | Oak(未来) | OPP/1 `edit.begin/commit/abort` | 一次 Ctrl-Z 整段撤销,失败自动回滚 |
| 快照 + 补偿 | Resolve | 执行前 `Timeline.DuplicateTimeline("quercus-snapshot-*")`;失败/撤销时切回快照时间线 | "已为你保留编辑前快照时间线",非原子 |
| 快照 + 补偿 | Premiere | 复制当前序列(UXP 支持则快照,不支持则仅反向操作日志)+ 逐步反向操作 | 面板内"撤销本段会话"按钮回放补偿;非原子 |
约束(写进 core,不依赖适配层自觉):
- 破坏性批次(删除/波纹删除/批量移动)**必须**先快照;只读批次(打点、
标记)可免快照。
- 快照命名带时间戳与会话 id,面板提供"清理快照"入口;快照数量进 `limits`
上限(防时间线列表被刷爆)。
- 任一子操作失败:已执行的保持(与 OPP/1 §6 规则 3 语义对齐),错误回喂
LLM,由 Agent 决定补偿或中止。
### 3.4 Oak 预留形态
`adapters/oak/` 包含:`OakAdapter(HostAdapter)` 全部方法
`raise NotImplementedError("awaiting OPP/1 P1-P3")`;每个方法 docstring 标注
对应 OPP/1 方法(如 `# -> timeline.split_clip,协议 §8.4`);`plugin.toml`
草案(capabilities 用设计文档 §5.2 最小集)。**不写任何 oakxp 调用代码。**
## 4. 分阶段计划
> 工时为单人粗略估算,仅用于排序与预期管理。阶段间依赖见 §9 图。
### P0 技术验证(1–2 周,其余一切的闸门)
四个 spike,每个产出可运行脚本 + 一页结论,全部通过才进 P1:
| Spike | 验证内容 | 通过标准 |
|---|---|---|
| S1 Resolve 连接 | 外部 Python 经 `fusionscript` 连 Resolve;记录免费版/Studio 差异(外部调用、UIManager、`-nogui`) | 脚本拿到 `resolve` 对象并列出工程;明确版本门槛写进 README |
| S2 Resolve 取帧 | 三条通路对比:`Project.ExportCurrentFrameAsStill(path)`(主候选)、`Timeline.GetCurrentClipThumbnailImage()`(仅 Color 页)、Gallery `GrabAllStills` + 导出 | 能按给定时间点稳定拿到 PNG;测出单帧延迟与 `-nogui` 下可用性,定下主/备通路 |
| S3 Premiere UXP 基座 | UDT 加载 TS 面板(照官方 `premiere-api` 示例结构);面板 ↔ 本地 WebSocket server 通信(manifest network 权限、token 握手) | 面板按钮触发本地 server 往返 < 50 ms |
| S4 Premiere 取帧与编辑面 | UXP export API 导单帧(官方示例 `export.ts`);`sequenceEditor` 的 overwrite/insert/remove、markers、effects、transitions、keyframes 实际可用性;**split 与 ripple delete 是否存在原生 API** | 得到 Premiere 侧能力清单(✅/⚠️/❌),填进 §5 表;单帧导出延迟实测 |
Go/No-Go 规则:S2、S4 的取帧延迟若 > 2 s/帧,视觉闭环体验不成立,
回到设计层改交互(如纯 contact-sheet 低帧率模式)再开工。
### P1 quercus-core(约 3 周,不碰任何宿主)
1. **类型与配置**:`Rational`/`EntityId`/`FrameRef` 等核心类型;
`~/.quercus/config.toml`(权限 0600、gitignore;环境变量仅 CI 覆盖,
优先级:环境变量 > 配置文件;密钥绝不写任何宿主侧文件——继承设计铁律 4)。
2. **LLMProvider**:统一接口(消息 + 图片 → 文本 + tool_calls);
后端:Claude、OpenAI 兼容 endpoint(自定义网关/代理通用通路)、
llama.cpp 本地多模态(Qwen-VL/LLaVA 类)。无 key 优雅降级:
面板可浏览工具说明并手动执行,不做对话编排。
3. **Agent 编排**:对话 loop、tool_call 派发、视觉闭环(工具返回可携带
图片消息回喂)、待确认清单(整段动作先入清单,用户确认后才执行——
宿主无协议层弹窗,**确认只有这一层**,默认全开)。
4. **工具 schema 注册表**:约 20 个工具的 JSON Schema;参数 min/max/choices
由 `get_params` 类方法回填(同设计 §3 的约束思路)。
5. **会话状态与回放**:ActionBatch 顺序记录、帧哈希、tool_call 日志落盘,
可回放复现(测试夹具同格式)。
6. **Mock LLM server**:录制/回放固定 tool_call 序列。
验收:`pytest` 全绿;无网络无 key 环境下跑通
"mock LLM → 工具派发 → 回喂 → 第二轮对话"。
### P2 HostAdapter + Mock 宿主 + 工具适配层(约 2–3 周)
1. HostAdapter ABC 与 `Limits`/`Capability`(§3.1–3.2)。
2. **MockHostAdapter**:内存时间线模型,行为对齐 OPP/1 语义(事务档位、
id 作废、`RATE_LIMITED` 式退避、帧非全黑校验)——它就是未来 Oak 适配层
的验收替身,也是 P1 的测试底座。
3. **工具适配层**:tool schema → adapter 方法组合;ActionBatch 编排
(快照策略、失败补偿、补偿顺序);取帧流水线(并发取帧 + 立即释放,
对齐 OPP/1 §10.3 的教训:批量扫描绝不串行等帧);限流客户端
(按 `limits` 令牌桶,退避不重试轰炸)。
4. **本地 Web 面板**:聊天、待确认清单、进度、撤销会话按钮、快照管理。
验收:Mock 宿主上"导入 → 铺轨 → 切/重建 → 删除 → 加效果 → 取帧验证 →
整段会话撤销"全链路自动化通过;Web 面板手工走查。
### P3 Resolve 适配层(约 3–4 周,依赖 P0 S1/S2、P2)
1. **连接与生命周期**:`fusionscript` 连接(环境变量
`RESOLVE_SCRIPT_API/LIB` 探测 + 报错指引)、断线重连、`-nogui` 模式支持、
版本门槛检查(S1 结论)。
2. **方法映射**(详表见 §5):工程/媒体池/时间线/标记/导出全量实现;
时间模型换算(帧号 ↔ 有理秒,按时间线 fps);`EntityId` ↔ 对象映射
(优先 `GetUniqueId()`)。
3. **取帧通路**:按 S2 结论实现主/备两条;contact sheet 拼贴(Pillow)。
4. **ActionBatch 档位**:快照(`DuplicateTimeline`)+ 补偿操作表;面板
"撤销会话"回放到快照。
5. **安装形态**:安装脚本写 Scripts 目录菜单项(`Utility/`)+ core 独立
启动两种入口;文档写清 Studio/免费版差异。
验收:`-nogui` CI 端到端——真实工程"导入 → 建时间线 → 粗剪 → 打点 →
导出",黄金帧校验(取到的帧非全黑、编辑前后切口帧像素不同);快照撤销
恢复一致性断言;GUI 模式手工走查 Web 面板对话粗剪一段真实素材。
### P4 Premiere 适配层(约 4–5 周,依赖 P0 S3/S4、P2)
1. **bridge**:core 内 WebSocket server(随机端口 + token 文件 0600);
NDJSON/JSON 消息协议(复用 core 的 HostAdapter 调用语义,一份 schema
两用);断线重连与版本协商。
2. **UXP 面板**(TS + Vite,结构照官方示例):聊天 UI、待确认清单、
进度;`manifest.json` 只声明必要权限(localhost 网络)。
3. **方法映射**:按 S4 能力清单实现 ✅ 项;⚠️ 项做变通(split 用
出入点重建、ripple 用移动补偿等);❌ 项在 schema 层显式隐藏并给
LLM 一份"本宿主不支持"说明(防幻觉调用)。
4. **取帧**:S4 定下的通路实现;延迟进 `limits` 让扫描策略自适应。
5. **ActionBatch 档位**:序列快照(若 API 支持复制序列)+ 补偿日志。
6. **打包**:开发走 UDT;发布形态(独立分发 `.ccx`)与签名要求调研
并落地最简可用路径。
验收:Premiere 内面板对话完成"铺轨 → 打点 → 加转场/效果 → 导出",
待确认与撤销交互完整;bridge 断连/重连不丢会话;手工 E2E 清单全过
(Premiere 无无头模式,此阶段接受手工测试为主)。
### P5 内容感知能力(约 3–4 周,依赖 P3,P4 可并行)
1. **Contact sheet 扫描打点**:等间隔采样拼图回喂,LLM 输出时间点 →
`add_marker`(两宿主 markers API 都全)。
2. **自动粗剪策略**:LLM 判定废片段 → ActionBatch(Resolve 直删/补偿;
Premiere 按能力变通);人工抽检闭环。
3. **转录利用**(能力增强,可选):Premiere 有 transcript 导出、Resolve
Studio 有 `TranscribeAudio`——按 `capabilities` 探测启用"按台词剪辑"。
4. **本地模型 provider 打磨**:llama.cpp 多模态的提示词与分辨率预算。
验收:对 10 分钟素材自动出粗剪版,人工抽检切点可用(同设计 A5 标准);
CI 有 mock 回放的粗剪回归。
### P6 发布与硬化(约 2 周)
- 两宿主安装器/打包(Resolve 脚本目录 + core 单包;Premiere `.ccx`);
core 用 PyInstaller / python-build-standalone 嵌入式发行(用户不装 Python,
同设计 §2.3)。
- 崩溃与断线恢复矩阵测试;日志与诊断包;密钥配置引导;限流与大图内存
压测。
- 文档:安装、能力矩阵(§5 表面向用户版)、故障排查。
### P7 Oak 适配(**预留,不启动**)
启动前提:OPP/1 P1–P3 完成(传输生命周期、宿主 API 核心、取帧 shm 数据面)。
届时 `quercus-oak` = HostAdapter → oakxp 的薄封装:事务档位直接升到"真事务"
(`edit.begin/commit`),取帧走 shm `FrameRef` + `shm.release` 流水线,确认
双层(协议弹窗 + 会话清单)按设计文档 §5.2。预计工作量显著小于 P3/P4——
这正是接口对齐 OPP/1 的收益。**本计划不为其分配资源。**
## 5. 宿主能力映射表(工具 × 宿主)
> ✅ 原生支持;⚠️ 有变通(注明);❌ 缺失(schema 层隐藏)。
> Resolve 方法名见 X-Raym 文档;Premiere 为 UXP API 域(以 S4 实测为准修订)。
| core 工具 | Resolve | Premiere(UXP) | Oak(预留 → OPP/1) |
|---|---|---|---|
| `open/save_project` | ✅ `ProjectManager.LoadProject/SaveProject` | ✅ project open/save | `project.open/save` |
| `get_project_overview` | ✅ `GetCurrentTimeline` + `GetItemListInTrack` 遍历 | ✅ activeSequence 遍历 | `project.get_info` + `timeline.get_structure` |
| `probe_media` | ⚠️ `MediaPoolItem.GetClipProperty()`(无独立 probe) | ✅ projectItem 元数据 | `media.probe` |
| `import_footage` | ✅ `MediaPool.ImportMedia` | ✅ `importFiles` | `media.import_footage` |
| `add_track` | ✅ `Timeline.AddTrack` | ✅ sequence tracks | `timeline.add_track` |
| `place_clip` | ⚠️ `AppendToTimeline`(clipInfo 带 startFrame/endFrame/recordFrame/trackIndex) | ✅ `sequenceEditor` overwrite/insert | `timeline.place_clip` |
| `split_clip` | ⚠️ 无直接 API——按出入点重建相邻两段(S4 复核新版是否已加) | ⚠️ 视 S4 结论;否则同样重建 | `timeline.split_clip` |
| `trim_clip` | ⚠️ `SetProperty`/重建 | ✅ trackItem 出入点 | `timeline.trim_clip` |
| `move_clip` | ⚠️ 删除 + `AppendToTimeline` 重放 | ✅ move track item | `timeline.move_clip` |
| `ripple_delete` | ✅ `Timeline.DeleteClips(items, True)` | ⚠️ 视 S4;否则补偿移动 | `timeline.ripple_delete` |
| `add_transition` | ⚠️ `TimelineItem` 属性/新建(有限) | ✅ transition 模块 | `timeline.add_transition` |
| `add_marker` | ✅ `Timeline.AddMarker`(含 customData,可存 AI 元数据) | ✅ markers 模块 | `timeline.add_marker` |
| `add_effect/set_param/keyframe` | ⚠️ 效果面有限(Fusion comp / 属性),调色走节点图另议 | ✅ effects + keyframe 模块 | `node.*` |
| `get_frame` | ✅ S2 主通路(`ExportCurrentFrameAsStill`) | ⚠️ S4:export 单帧,延迟实测 | `render.get_frame`(shm) |
| `scan_timeline` | ✅ `GrabAllStills` / 逐点取帧拼图 | ⚠️ 批量导帧拼 contact sheet | `render.get_thumbnails` |
| `get_audio_levels` | ❌(v1 隐藏;Fairlight 侧另议) | ⚠️ 视 API;否则隐藏 | `render.get_audio_levels` |
| `play/pause/seek` | ⚠️ `SetCurrentTimecode` 可 seek;播放控制弱 | ✅ sourceMonitor play/pause/position | `playback.*` |
| `export_render` | ✅ `LoadRenderPreset` + `AddRenderJob` + `StartRendering` + `GetRenderJobStatus` 轮询 | ✅ encoderManager / export | `export.start` + 事件 |
| `undo_last_action` | ❌ 无 undo API → 快照恢复(§3.3) | ❌ 同左(补偿回放) | `edit.undo` |
| 事件(结构/进度) | ⚠️ 轮询合成 | ✅ eventManager(工程/编码事件) | OPP/1 §9 原生事件 |
## 6. 安全与密钥(继承设计铁律,按本计划重述)
1. core 只经 HostAdapter 操作宿主;Premiere 桥接受 token 鉴权、只绑
`127.0.0.1`。
2. 一切编辑走 ActionBatch,破坏性批次先快照;UI 默认"确认后执行"
(宿主无协议层确认,会话层清单是唯一确认点,不可默认关闭)。
3. API key 只存 `~/.quercus/config.toml`(0600);绝不写入 Resolve 工程/
Premiere 工程/未来 `.ove` 的任何位置。
4. 遵守各宿主实测限流:取帧并发与分辨率预算写进 `limits`,core 侧令牌桶
统一执行,退避不轰炸。
## 7. 测试策略
1. **Mock LLM**(P1):无 key 无网络 CI 跑通 Agent loop。
2. **MockHostAdapter**(P2):协议级行为夹具;未来 Oak 适配层先对它达标
再连真 Oak。
3. **Resolve 黄金帧 E2E**(P3):`-nogui` 起真实实例,导入测试素材 →
编辑 → 取帧,断言帧非全黑且切口前后像素变化——对齐设计 §6.3 的
"Agent 的编辑确实改变了画面"。
4. **Premiere 手工清单**(P4):无无头模式,维护一份逐步 checklist +
预期画面截图基线,发布前必跑。
5. **会话回放**:tool_call + 帧哈希日志可直接作回归夹具。
## 8. 风险登记册
| 风险 | 影响 | 对策 |
|---|---|---|
| Resolve 免费版外部脚本/UI 受限(v19.1 移除 UIManager) | Resolve 侧受众收窄 | S1 定门槛;必要时仅支持 Studio 并在文档明示 |
| Resolve/Premiere 均无 undo/事务 API | "撤销会话"非原子,可能残留 | 快照 + 补偿(§3.3);UI 文案不承诺原子性 |
| Premiere UXP 取帧慢或无直接 API | 视觉闭环降格 | S4 实测;降格方案 = 低帧率 contact-sheet 模式 |
| Premiere split/ripple 无原生 API | 粗剪动作变通复杂易错 | 重建法封装进适配层 + 黄金帧类断言兜底 |
| UXP 网络/面板权限政策变化 | 桥接失效 | manifest 最小权限;关注 Adobe 公告;bridge 协议版本协商 |
| LLM 成本与延迟 | 体验差 | 本地 provider、扫描降采样、contact sheet 合并请求 |
| 时间模型换算(帧号/ticks/fps 下拉) | 切点漂移 | 有理秒唯一内部模型 + 适配层换算单测(NTSC fps 用例) |
## 9. 里程碑总表与依赖
```
P0(spike) ──► P1(core) ──► P2(adapter+mock) ──┬──► P3(Resolve) ──► P5(内容感知) ──► P6(发布)
│ └──► P4(Premiere) ────────────────►──┘
└──(P7 Oak:等 OPP/1 P1–P3,不占本计划资源)
```
| 里程碑 | 出口标准(一句话) | 估算 |
|---|---|---|
| P0 | 四个 spike 通过,两宿主取帧通路定案 | 1–2 周 |
| P1 | Mock LLM 下 Agent loop CI 全绿 | ~3 周 |
| P2 | Mock 宿主全链路 + Web 面板可用 | ~2–3 周 |
| P3 | Resolve 无头 E2E 黄金帧通过,GUI 对话粗剪走通 | ~3–4 周 |
| P4 | Premiere 面板对话编辑 + 撤销会话走通 | ~4–5 周 |
| P5 | 10 分钟素材自动粗剪,抽检可用 | ~3–4 周 |
| P6 | 两宿主安装包 + 文档 + 硬化完成 | ~2 周 |
| P7 | (预留)Oak 适配 = oakxp 薄封装,等 OPP/1 P1–P3 | 未排期 |
P4 与 P3 可由两人并行;单人执行顺序建议 P3 先于 P4(Resolve API 面更全、
可无头,能在最硬的宿主上先把 HostAdapter 接口的真实形状磨出来)。