feat(canvas): 云端 Agent - 完成策略分层、节点能力注册与可解释审批改造

This commit is contained in:
ddCat
2026-09-14 17:58:21 +08:00
parent 4b0e9d8965
commit 01486872c4
65 changed files with 5347 additions and 918 deletions

View File

@@ -17,10 +17,11 @@ description: 影策后端核心持久化表与状态边界
| --- | --- | --- |
| `schema_migrations` | 保存已成功应用的结构版本、名称、校验和与时间 | 已应用版本的校验和发生变化时拒绝启动;数据库版本高于当前二进制支持版本时拒绝旧程序连接 |
| `cloud_agent_executions` | 保存 Agent 每轮的持久化执行状态、修订号和运行状态 | `(id, user_id)` 归属受服务端校验;状态通过 revision CAS 更新;`state_json` 包含技能快照、工具调用、审批、活动子任务和事件,不保存浏览器密钥 |
| `agent_profiles` | 保存用户、项目和画布层的 Agent 常驻偏好、哈希和独立 revision | `(user_id, scope, project_id, canvas_id)` 唯一;归属由 service 校验,写入使用 revision CAS;正文不能授权新能力 |
容器内命令 `migrate-schema up` 应用缺失迁移,`migrate-schema status` 输出当前和期望版本,`migrate-schema verify` 只检查兼容性。`CANVAS_AUTO_MIGRATE=true` 仅用于开发和旧部署兼容;受管生产部署设置为 `false`,并由 Compose 的一次性 `migrate` 服务先完成迁移。后续迁移必须新增版本,不得修改已经发布迁移的实现或校验和。
当前结构版本为 12。版本 2 为 `schema_migrations.applied_at` 增加 `idx_schema_migrations_applied_at` 索引;版本 3 `asset_taxonomy_candidate_identity` 为资产候选增加规范化名称和来源,收敛资产分类,并为待确认候选建立项目内部分唯一索引;版本 4 `resource_upload_key` 为资源上传幂等补充 `resources.upload_key` 列和用户维度唯一索引;版本 5 `payment_topup` 创建积分充值、支付通知和对账所需结构;版本 6 `resource_playback_variant` 增加资源播放副本状态;版本 7 `asset_library_folders` 增加用户素材自定义分类及 `assets.folder_id`;版本 8 `logical_model_active_code` 将前台模型 code 的唯一约束限定为未归档记录;版本 9 `channel_presentation` 为渠道添加 `public_alias`(默认空字符串)与 `sort_order`(默认 0),为渠道模型添加 `sort_order`(默认 0),不重写既有凭据、价格或能力配置;版本 10 `creation_runtime` 创建创作运行与提交表,并为任务增加可空且唯一的 `creation_submission_id`,旧任务无需伪造提交标识即可继续使用;版本 11 `cloud_agent_runtime` 创建 `cloud_agent_executions`,保存 Agent 的 durable runtime 状态和 revision CAS 信息;版本 12 `agent_token_charge_limit` 为账单增加可空语义的 `charge_limit_microcredits`,仅 Agent Token 任务写入非零值,将服务端预授权报价固化为最终用户扣费上限。
当前结构版本为 15。版本 2 为 `schema_migrations.applied_at` 增加 `idx_schema_migrations_applied_at` 索引;版本 3 `asset_taxonomy_candidate_identity` 为资产候选增加规范化名称和来源,收敛资产分类,并为待确认候选建立项目内部分唯一索引;版本 4 `resource_upload_key` 为资源上传幂等补充 `resources.upload_key` 列和用户维度唯一索引;版本 5 `payment_topup` 创建积分充值、支付通知和对账所需结构;版本 6 `resource_playback_variant` 增加资源播放副本状态;版本 7 `asset_library_folders` 增加用户素材自定义分类及 `assets.folder_id`;版本 8 `logical_model_active_code` 将前台模型 code 的唯一约束限定为未归档记录;版本 9 `channel_presentation` 为渠道添加 `public_alias`(默认空字符串)与 `sort_order`(默认 0),为渠道模型添加 `sort_order`(默认 0),不重写既有凭据、价格或能力配置;版本 10 `creation_runtime` 创建创作运行与提交表,并为任务增加可空且唯一的 `creation_submission_id`,旧任务无需伪造提交标识即可继续使用;版本 11 `cloud_agent_runtime` 创建 `cloud_agent_executions`;版本 12 `agent_token_charge_limit` 为账单增加 Agent Token 收费上限;版本 13 `cloud_agent_canvas_mutation` 持久化 Agent 画布变更;版本 14 `cloud_agent_recovery_control` 增加运行恢复控制字段和旧运行回填;版本 15 `agent_profiles` 创建常驻偏好表。已发布版本的实现和校验和不得修改。
历史发布曾使用版本 6 `asset_library_folders`,之后合并引入了上述 6/7 顺序冲突。迁移器仅在该历史记录的名称和校验和均精确匹配时选择历史顺序:保留版本 6 的素材目录记录及时间,在版本 7 应用 `resource_playback_variant`,再继续版本 8。新数据库及已采用“6 播放副本、7 素材目录”的数据库保持原顺序;状态检查与启动校验采用同一套识别规则。未知名称、被改动的校验和或冲突的版本 7 记录仍会拒绝迁移,不通过删除、改名或伪造已应用记录绕过检查。

View File

@@ -9,7 +9,7 @@ description: 从产品功能快速定位主要代码入口
## 前端页面
云端 Agent:`web/src/components/canvas/canvas-cloud-agent-panel.tsx` 协调对话、Skills 和审批,`services/api/agent.ts` 适配运行快照与事件流;后端 `handler/agent.go` 负责 HTTP 合同,`app/cloud_agent.go` 负责权限、幂等、可信历史与画布摘要,`app/cloud_agent_runtime.go` 负责持久化模型循环,`app/cloud_agent_tools.go` 负责技能快照、画布工具和生成工具,`app/task_creation.go` 执行预算 admission,运行沿用任务 Worker。协议见 HTTP API 专题,未确认的真实登录态与上游联调见待测试清单。
云端 Agent:`web/src/components/canvas/canvas-cloud-agent-panel.tsx` 协调对话、Skills 和审批,`canvas-cloud-agent-settings.tsx` 编辑长期偏好,`services/api/agent.ts` 适配运行快照与事件流;画布手工编辑与引用同步共用 `web/src/lib/canvas/canvas-node-prompt.ts`,已有媒体结果只更新 `composerContent` 草稿,不覆盖生成结果和已提交提示词。后端 `handler/agent.go` 负责 HTTP 合同,`app/cloud_agent.go` 负责权限、幂等、可信历史,`app/cloud_agent_profile.go` / `repository/agent_profile.go` 管理用户、项目、画布偏好及 revision CAS,`internal/prompts/agent-*-policy.md` 是随发布物编译、并在文档头声明 ID/版本的行为资源,`app/cloud_agent_policy.go` 编译本轮策略快照,`internal/canvas/capability/` 注册服务端节点能力和字段映射,`app/cloud_agent_canvas_state.go` 投影画布摘要和精读,`app/cloud_agent_runtime.go` 负责持久化模型循环,`app/cloud_agent_tools.go` 根据能力定义生成工具 schema,并从固定技能快照渐进读取 `SKILL.md` / reference,`app/cloud_agent_media.go` 负责收费媒体任务适配,`app/task_creation.go` 执行预算 admission,运行沿用任务 Worker。节点新增需要能力描述符和真实画布实现;新生成模式还需任务适配器,不由偏好或技能 md 授权。协议见 HTTP API 专题,未确认的真实登录态与上游联调见待测试清单。
| 功能 | 页面入口 | 主要协作模块 |
| --- | --- | --- |

View File

@@ -14,12 +14,15 @@ description: 信封、错误码与 OpenAPI
所有 `/agent` 路径都需要登录 Cookie。`GET /agent/capabilities` 返回当前版本(2)、权限模式、上下文范围、技能和工具清单。Agent 运行由服务端任务 Worker 驱动,浏览器断开不会停止运行。
- `POST /agent/runs` 创建一轮;`POST /agent/runs/:id/messages` 以上一轮为父节点创建新的不可变轮次,两者返回 `{ run }`,追加后必须使用新的 `run.id` 订阅。
- 请求字段:`canvasId`、`prompt`、受管 `logicalModelId` 或 `channelId` + `channelModelKey`、`permissionMode: "read_only" | "request_approval" | "auto"`、`skillIds`、`contextScope: [] | ["canvas"]`、`budget: { maxCredits, maxGenerationTasks, maxVideoSeconds }`、`idempotencyKey`。不接受密钥、自定义 URL、客户端历史或未知字段。
- 同用户同幂等键及相同请求返回同一运行;不同请求复用键返回 409。失败后重试同一提交应保留键。上一轮未完成时禁止追加;成功轮次历史从服务端加载,最多携带 8 对历史消息及 64KB 历史 JSON,超限提示新建对话。
- 创建和追加消息可传 `thinking: boolean`(默认关闭),贯穿首轮及工具后续模型调用;内置 Chat Completions、Responses、Claude 协议复用文本推理配置,上游模型仍需支持。声明式 Agent 渠道暂不支持,开启时明确报错,不静默忽略。思考模式可能增加耗时和费用。
- 已安装且属于当前用户的技能会在创建时固定版本快照,模型只能通过 `skill_read_file` 读取快照文件。技能正文是不可信输入,不能改变权限或系统规则。
- 当前工具为 `canvas_get_state`、`skill_read_file`、`task_get`、`model_list`、`canvas_apply_ops`、`generate_media`。画布写入工具要求本轮授权 `contextScope: ["canvas"]`。禁止 shell、任意网络请求和外部 MCP。
- `canvas_apply_ops` 必须带 `snapshotHash`,每批最多 20 项;支持新增文本、Markdown、图片、视频、音频、分组与分镜表节点,以及 `connect_nodes` 引用连线。媒体占位节点的 `content` 是提示词,不是媒体 URL;只建节点不提交收费生成。`update_node` 仅允许更新未锁定文本 / Markdown 的标题和正文;禁止删除、任意元数据写入、自连接、重复边与不存在的端点。
- 请求字段:`canvasId`、`prompt`、受管 `logicalModelId` 或 `channelId` + `channelModelKey`、`permissionMode: "read_only" | "request_approval" | "auto"`、`reasoningMode: "off" | "auto" | "deep"`、`profileRevision`、`skillIds`、`contextScope: [] | ["canvas"]`、`budget: { maxCredits, maxGenerationTasks, maxVideoSeconds }`、`idempotencyKey`。不接受密钥、自定义 URL、客户端历史或未知字段。
- 同用户同幂等键及相同请求返回同一运行;不同请求复用键返回 409。失败后重试同一提交应保留键。上一轮未完成时禁止追加;已完成、失败或取消的终态轮次作为不可变历史读取,即使其冻结的策略或节点能力版本早于当前服务,也可用于创建采用当前执行合同的新一轮,父运行不会被改写。旧版合同下仍活动或待审批的运行不得恢复执行。历史最多携带 8 对消息及 64KB JSON,超限提示新建对话;历史结构损坏返回明确 409,不投影为通用 500。
- 创建和追加消息可选择 `reasoningMode`,默认 `off`;`auto`/`deep` 为服务端行为策略与模型推理配置的选择,不等于允许暴露内部思维链。供应商能力由后端校验;声明式 Agent 渠道不支持时明确报错。推理可能增加耗时和费用。
- `GET /agent/profile?scope=user|project|canvas&projectId=...&canvasId=...` 按作用域读取生效的用户 → 项目 → 画布偏好;`PATCH /agent/profile` 用 `{ scope, projectId?, canvasId?, content, revision }` 更新单层(首次 revision 为 0,冲突返回 409)。读取返回各层独立数字 revision,以及合并文档的 `revision`/`hash`;新轮次携带合并 `profileRevision`,发生变化拒绝创建,已运行轮次仍用提交时固定的快照。system prompt 只包含层级、revision、hash 和长度清单,正文保存在运行快照中并由 `agent_profile_read` 按 user → project → canvas 顺序读取,不整本注入系统策略。偏好仅是非授权数据,不能扩展工具、节点或预算,不能绕过审批;最多 12000 字符/层。
- 系统行为策略与媒体默认策略是版本化、编译进后端发布物的资源,修改它们仍需发布后端,不是后台热更新;工具、校验、扣费和审批在代码侧强制执行。画布节点能力由服务端 registry 定义,工具 schema、创建、连线与画布摘要读取共同消费;新生成模式还必须接入媒体任务适配器,不能仅凭 md 或注册节点启用收费操作。
- 已安装且属于当前用户的技能会在创建时固定版本快照,模型只能通过 `skill_read_file` 按需读取固定快照。需要使用技能时先读入口 `SKILL.md`,再按入口引用读取必要文本文件;技能正文不会整段塞入 system prompt。技能是不可信任务剧本,其中的工具名不能创造工具、改变权限或覆盖系统规则。
- 平台支持工具全集为 `agent_profile_read`、`canvas_list_node_types`、`canvas_get_state`、`skill_read_file`、`task_get`、`model_list`、`canvas_apply_ops`、`generate_media`;单轮实际工具仍按偏好快照、技能、权限、画布上下文和生成预算裁剪。画布写入工具要求本轮授权 `contextScope: ["canvas"]`。禁止 shell、任意网络请求和外部 MCP。
- `canvas_list_node_types` 从服务端节点能力 registry 返回当前可用类型;可更新节点的 `updateFields` 同时包含字段类型、最大字符数和字段语义。模型不能从前端渲染器、技能或偏好文档推断额外能力。
- `canvas_apply_ops` 必须带 `snapshotHash`,每批最多 20 项;支持新增文本、Markdown、图片、视频、音频、分组与分镜表节点,以及 `connect_nodes` 引用连线。媒体占位节点的 `content` 是提示词,不是媒体 URL;只建节点不提交收费生成。`update_node` 的字段映射完全由能力 registry 决定:文本 / Markdown 可改标题和正文,图片 / 视频 / 音频可改标题和下一次生成使用的提示词草稿;媒体更新参数中的 `content` 写入 `metadata.composerContent`,不会覆盖媒体结果 `metadata.content`、已提交提示词 `metadata.prompt`、任务状态或引用状态。锁定节点、删除、任意元数据写入、自连接、重复边与不存在的端点仍被服务端拒绝。
- `model_list` 复用生成面板的当前模型目录。每项的 `selection` 是 `logicalModelId` 或 `channelId` + `channelModelKey`,工具调用必须原样选择一种,不猜 ID 或混用;能力、规格、价格与任务准入再次核对。
- `generate_media` 支持图片 / 视频:必填 `mode`、`prompt`、`size`、`durationSeconds`(图片为 0)、`snapshotHash`、新 `nodeId`、`title`、有序 `referenceNodeIds`(无参考传空数组),再加一种模型选择;可传来源节点 `sourceNodeId`、`quality`、视频 `videoGenerateAudio`。`sourceNodeId` 仅接文本 / 镜头提示词节点;参考图片、视频、音频只放 `referenceNodeIds`,无文本来源时留空且不重复传入。引用从当前画布节点的账号资源解析,校验归属、类型、就绪状态和模型能力,不接受任意 URL。图片生成仅接图片参考;视频可按模型能力接图片、视频、音频参考。
- 参数选择与收费授权分开:用户明确说“随便 / 你决定 / 按默认”时,Agent 可从当前目录选择能力及预算匹配的模型,优先用户最近明确使用的可用模型,其次价格清楚的低成本选项;支持时沿用参考图原始比例,否则选目录默认,时长用目录默认。未要求音频且模型不支持时用默片。用户接受已推荐方案后不再要求特定确认口令;仅真正缺失或冲突的阻塞项一次性询问。本轮已有目录结果应复用。以上是 Agent 的参数选择策略,服务端仍强校验具体规格,不提供绕过预算 / 能力校验的默认兜底,也不绕过独立收费审批。
@@ -30,14 +33,15 @@ description: 信封、错误码与 OpenAPI
- `GET /agent/runs/:id` 返回运行信息;`POST /agent/runs/:id/cancel` 取消当前运行及活动子任务;`POST /agent/runs/:id/approvals/:approvalId/decision` 接受 `{"decision":"approve"|"reject","reason":"..."}`,审批决定幂等且服务端校验归属。
- `GET /agent/runs/:id/events` 返回 `agent_event` 和首次 / 状态版本变化时的紧凑 `run_snapshot` SSE;无变化时只发送 heartbeat。事件 `Seq` 在运行内递增;支持 `?after=` / `Last-Event-ID`,游标不是运行 ID。`cleanupPending: true` 时即使运行已进入终态也会继续推送,直到后台收尾完成。前端应以快照恢复状态,代理只对这一精确路径关闭缓冲。
- `generation_task_created` 携带 `canvasId`、`nodeId`、`taskId`、`title`、`mode`、`referenceNodeIds`。草稿、提交、画布工具写入,以及媒体成功 / 失败 / 取消的真实节点变更,均在画布写入与运行检查点的同一事务持久化 `canvas_updated`。节点已删除或换任务时不伪造更新;`run_failed` 本身不代表节点媒体任务失败。
- `canvas_updated` 包含 `canvasId`、`operation`、`callId`、`actions` 和 `canvasPatch`。`canvasPatch` 为 `{ canvasId, updatedAt, nodes: [{ before, after }], connections: [{ before, after }] }`;新增对象的 `before` 为 `null`,仅包含有变化的节点和连线。当前工具不开放删除。`actions` 固定保存操作类型、节点 ID、标题、类型,引用操作还带目标节点 ID,供历史消息显示和点击定位;`callId` 用于合并同一画布操作的工具完成消息。
- `approval.preview` 和 `canvas_updated.actions` 是服务端根据同一解析、快照与 Capability 规划流程生成的解释信息,不是授权凭证。审批卡片明确列出新增/修改节点、节点类型、修改字段、名称变化和连线两端;执行事件保存同样的可读信息,供历史消息显示和点击定位。实际写入仍重新校验用户归属、`snapshotHash`、Capability、节点锁、预算和审批 `callHash`。
- `canvas_updated` 包含 `canvasId`、`operation`、`callId`、`actions`、可选的 `preview`/`text` 和 `canvasPatch`。`canvasPatch` 为 `{ canvasId, updatedAt, nodes: [{ before, after }], connections: [{ before, after }] }`;新增对象的 `before` 为 `null`,仅包含有变化的节点和连线。当前工具不开放删除。节点 action 保存操作、节点 ID、执行前标题、类型、修改字段和可选的修改后标题;引用 action 同时保存来源与目标节点 ID、标题和类型,不要求前端从内部 ID 猜测对象。`callId` 用于合并同一画布操作的工具完成消息。
- 前端对增量按 40ms 窗口合批,三方合并远端基线、当前 Store 和编辑器,正常完成 / 失败无需全量 GET;保留无关节点编辑、拖动与视口,同字段冲突、删除或新任务绑定不强行覆盖。只有事件缺口、断流、增量冲突或没有增量的事件源需要全量校准,同一画布协调器最多每秒发起一次,单请求在途,期间重复通知合并为一次尾随校准。已识别增量协议后,相邻 `tool_failed` / `run_failed` 不重复触发整图刷新。
- Agent 创建 / 续聊请求在客户端先持久化完整正文、幂等键、父运行和消息 ID,再提交;网络超时或响应丢失只允许用同一正文和 key 有界重放,不能换 key 生成新收费轮次。待确认记录损坏时对话暂停发送,不把它当成“从未提交”。
- Agent 创建 / 续聊请求在客户端先持久化完整正文、幂等键、父运行和消息 ID,再提交;网络超时或响应丢失只允许用同一正文和 key 有界重放,不能换 key 生成新收费轮次。客户端区分“服务端已明确返回错误”和“未收到服务端确认”,不再把 HTTP 409/500 一律描述成请求未确认。待确认记录损坏时对话暂停发送,不把它当成“从未提交”。
- 生成按钮与提交入口共用节点任务状态:排队 / 运行中不可重复生成,终态解除任务锁,历史 `taskId` 不永久禁用。聊天留痕保留节点名、引用与失败任务 ID,点击使用现有画布聚焦;已删除节点只提示,不自动重建。聊天缓存不重复保存增量中的完整节点正文。
Agent 的聊天列表缓存只负责 UI 恢复;运行、技能快照、审批、工具调用、子任务和事件均以服务端持久化状态为准。提交响应丢失时,客户端应使用同一幂等键重试,不生成新的提交。
Skills 的 `tool_completed` / `tool_failed` 事件附带 `skillId`、`skillName`(已启用技能)和 `path`;空路径表示列目录,不表示已读取文件。目录结果中的 `files` 为本轮固定快照的可读路径,空数组时应使用已加载正文继续。同一运行内同技能同路径只允许请求一次(包括不存在的路径),重复请求返回工具失败和继续执行指引,状态随运行持久化。模型初始上下文包含技能 ID 和可读文件清单。
Skills 的 `tool_completed` / `tool_failed` 事件附带 `skillId`、`skillName`(已启用技能)和 `path`;空路径表示列目录,不表示已读取文件。目录结果中的 `entryPath` 固定为 `SKILL.md`,`files` 为本轮固定快照的可读路径;Agent 先读取入口,再按入口引用读取必要文件。同一运行内同技能同路径只允许请求一次(包括不存在的路径),重复请求返回工具失败和继续执行指引,状态随运行持久化。模型初始上下文只包含技能版本、哈希、入口和可读文件清单,不包含技能正文。
模型任务失败的 `run_failed` 事件保留 `taskId`,并提供安全分类 `reason`:`model_connection_reset`、`model_request_timeout`、`model_connection_refused` 或 `model_task_failed`。`text` 不透传原始上游错误,避免泄露地址和凭证;前端用事件类型而非摘要关键词判断工具成功 / 失败。失败不会触发自动付费重试。