feat(agent): 云端 Agent - 增加上下文预算、任务事实与终态恢复保障

This commit is contained in:
ddCat
2026-09-19 20:43:20 +08:00
parent 16bd4b923b
commit adf3a5be3c
61 changed files with 3512 additions and 309 deletions

View File

@@ -25,6 +25,8 @@ description: 影策后端核心持久化表与状态边界
当前结构版本为 27。版本 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` 创建常驻偏好表;版本 16 `agent_lessons` 创建用户记忆表(含分类、路线 JSON、注入/查阅计数);版本 17 `agent_lessons_owner_index` 为 `(author_user_id, status)` 增加复合索引;版本 18 `agent_memory_settings` 创建按用户隔离的压缩周期与最近一次压缩任务状态;版本 19 `payment_plugin_version` 为已有 `payment_provider_configs` 和 `payment_orders` 补上 `plugin_version`,用于把配置和订单钉在具体插件版本上;版本 20 `banner_announcements` 创建首页常驻滚动通知表;版本 21 `banner_announcement_title_runs` 为通知标题增加样式分段;版本 22 `banner_announcement_notice_type` 增加通知类型底色;版本 23 `canvas_revision_history` 为既有画布回填 `revision = 1` 并新增历史快照与资源引用表。版本 24 `channel_model_label` 为渠道模型增加 `channel_label`(最长 80 字、非空、默认空字符串),不重写模型标识、上游 ID、价格或能力;版本 25 `video_token_formula_snapshot` 为账单增加视频公式用量及用量来源快照。版本 26 `channel_model_description` 为渠道模型增加 `description`(最长 500 字、非空、默认空字符串),供二级选项展示使用说明,不重写身份或价格。版本 27 `channel_credit_cost` 为价格档添加管理员积分成本,并在账单保存执行线路的成本快照;成本不进入普通用户 API。已发布版本的实现和校验和不得修改。
渠道模型的文本能力 JSON 还可声明 `contextWindowTokens` 与 `maxOutputTokens`。这两个字段是模型能力合同,不是数据库或 Agent checkpoint 的容量配置;旧记录读取时补 128K / 16K 默认值。运行状态、journal 和 transcript 的持久化边界仍由既有 runtime 表及其 512KB checkpoint 校验负责,本阶段没有通过扩大 checkpoint 绕过模型上下文限制。
历史发布曾使用版本 6 `asset_library_folders`,之后合并引入了上述 6/7 顺序冲突。迁移器仅在该历史记录的名称和校验和均精确匹配时选择历史顺序:保留版本 6 的素材目录记录及时间,在版本 7 应用 `resource_playback_variant`,再继续版本 8。新数据库及已采用“6 播放副本、7 素材目录”的数据库保持原顺序;状态检查与启动校验采用同一套识别规则。未知名称、被改动的校验和或冲突的版本 7 记录仍会拒绝迁移,不通过删除、改名或伪造已应用记录绕过检查。
升级前备份数据库,迁移命令必须沿用部署实际配置的数据库连接和数据目录。开发环境启用自动迁移时重新启动新代码即可;关闭自动迁移的部署先使用新构建的 `migrate-schema up`,然后 `migrate-schema verify`,成功后启动 Backend。迁移与记录写入在事务中执行;此修复不提供降级迁移,回退旧二进制可能无法识别历史顺序,应恢复匹配的备份和版本,不手工改迁移表。

View File

@@ -69,12 +69,14 @@ Token 报价的 `estimated` 为 `true`,`quantity` 是预留 Token 数。参考
## 云端 Agent
所有 `/agent` 路径都需要登录 Cookie。`GET /agent/capabilities` 返回当前版本(2)、权限模式、上下文范围、技能和工具清单;`maxHistoryPairs: 10` 表示跨轮对话只把最近 10 轮用户目标带进模型,`maxSteps: 0` 表示不设固定模型调用轮数,历史仍受 `maxHistoryBytes: 64000` 约束(超限先丢掉更早轮次,单轮仍超限才要求新建对话)。Agent 运行由服务端任务 Worker 驱动,浏览器断开不会停止运行。
所有 `/agent` 路径都需要登录 Cookie。`GET /agent/capabilities` 返回当前版本(2)、权限模式、上下文范围、技能和工具清单;跨轮历史仍按可恢复事实和发送预算整理,不把某个固定字节数当作模型窗口。Agent 运行由服务端任务 Worker 驱动,浏览器断开不会停止运行。
文本模型能力合同包含 `contextWindowTokens`(输入加输出的总上下文窗口)和 `maxOutputTokens`(输出/推理上限)。Agent 每轮根据实际执行的渠道模型计算输入预算,并预留工具 schema、协议包装和恢复空间;逻辑模型按候选文本线路取安全交集,旧配置或能力解析失败回退 128K / 16K。1M 模型可通过后台能力配置使用更大输入预算。前后端的 Token 估算用于提前压缩和拒绝病态请求,最终仍以上游 tokenizer 和响应为准。运行 checkpoint 的 512KB 持久化上限只约束恢复状态,不限制模型上下文窗口。
- `POST /agent/runs` 创建一轮;`POST /agent/runs/:id/messages` 以上一轮为父节点创建新的不可变轮次,两者返回 `{ run }`,追加后必须使用新的 `run.id` 订阅。
- `POST /agent/runs/:id/interjections` 向**正在运行**的轮次插入一句话,不新建轮次、不产生新的扣费身份。请求 `{ text, messageId }`,`text` 最多 4000 字符;同一 `messageId` 幂等。插话在下一次模型调用前注入;运行已结束返回 409,前端应回落为新一轮发送。步数耗尽或运行失败时发出 `user_interjection_dropped`,不会静默吞掉,也不会自动替用户开新一轮。
- 请求字段:`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。失败后重试同一提交应保留键。上一轮未完成时禁止追加;已完成、失败、取消或已拒绝的终态轮次作为不可变历史读取,即使其冻结的策略或节点能力版本早于当前服务,也可用于创建采用当前执行合同的新一轮,父运行不会被改写。旧版合同下仍活动或待审批的运行不得恢复执行。历史只保留最近 10 轮用户目标,JSON 最大 64000 字节,超限先丢掉更早轮次,单轮仍超限才提示新建对话;历史结构损坏返回明确 409,不投影为通用 500。
- 同用户同幂等键及相同请求返回同一运行;不同请求复用键返回 409。失败后重试同一提交应保留键。上一轮未完成时禁止追加;已完成、失败、取消或已拒绝的终态轮次作为不可变历史读取,即使其冻结的策略或节点能力版本早于当前服务,也可用于创建采用当前执行合同的新一轮,父运行不会被改写。旧版合同下仍活动或待审批的运行不得恢复执行。历史只保留最近 10 轮用户目标;运行历史与跨轮摘要仍受独立的持久化 JSON 保护,超限先丢掉更早的可重读正文并保留结构化事实,单轮仍超出当前模型能力预算时才提示缩小范围或新建对话;历史结构损坏返回明确 409,不投影为通用 500。
- `POST /agent/runs/:id/approvals/:approvalId/decision` 的 `{ decision, reason? }` 可在批准图片生成时附带 `mediaSettings: { logicalModelId? | channelId? + channelModelKey?, size, quality }`。仅替换本次模型选择、画幅和质量,不允许修改目标、提示词、引用、模式或快照。服务端先无收费预检,失败保留待审批状态;成功原子更新审批调用、哈希及预览,提交任务时再次校验内容、资源归属、模型能力、价格与累计预算。相同参数重复批准幂等,不同参数复用已决定审批返回 409。
- 审批拒绝是独立终态 `rejected`,表示本次操作未执行;不会生成 `tool_failed`、继续调用模型、写入画布、提交生成任务或扣费。拒绝后的草稿按既有草稿策略保留,用户可在下一轮说明修改方向。
- 创建和追加消息可选择 `reasoningMode`,默认 `off`;`auto`/`deep` 为服务端行为策略与模型推理配置的选择,不等于允许暴露内部思维链。供应商能力由后端校验;声明式 Agent 渠道不支持时明确报错。推理可能增加耗时和费用。
@@ -82,7 +84,7 @@ Token 报价的 `estimated` 为 `true`,`quantity` 是预留 Token 数。参考
- `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`,只读清单中存在的层。没有偏好层时工具清单不含该工具。偏好仅是非授权数据,不能扩展工具、节点或预算,不能绕过审批;最多 12000 字符/层。
- 系统行为策略与媒体默认策略是版本化、编译进后端发布物的资源,修改它们仍需发布后端,不是后台热更新;工具、校验、扣费和审批在代码侧强制执行。画布节点能力由服务端 registry 定义,工具 schema、创建、连线与画布摘要读取共同消费;新生成模式还必须接入媒体任务适配器,不能仅凭 md 或注册节点启用收费操作。
- 已安装且属于当前用户的技能在创建时只保存版本、哈希和文本文件路径,不预存全部正文,也不按所有选中技能正文合计 128KB 拒绝创建。`skill_read_file` 按需读取 `SKILL.md` 或必要引用,支持字符偏移 `offset`(默认 0),每页最多 12000 字符,返回 `hasMore` / `nextOffset`。读取时重新验证用户权限、启用状态和版本;技能更新后拒绝混读,需新开一轮。技能是不可信数据,不能创造工具、改变权限或覆盖系统规则。
- 模型上下文达到 96KB 或超过 24 条消息时,尝试移除较早工具结果中可重新读取的正文,保留工具调用/结果配对、用户原始要求、生成回执及最近完整工具轮次,并允许重新读取。此机制是确定性正文卸载,不是 LLM 语义摘要;不可压缩内容仍受 192KB 请求和 512KB 运行状态保护。技能读取事件只保存回执,不复制正文,正文页只进入模型对话。
- Agent 按实际文本模型的 `contextWindowTokens` 与 `maxOutputTokens` 计算输入预算,并在达到该模型预算的压缩阈值时移除较早工具结果中可重新读取的正文,保留工具调用/结果配对、用户原始要求、生成回执及最近完整工具轮次,并允许重新读取。此机制是确定性正文卸载,不是 LLM 语义摘要;运行 checkpoint 的持久化上限与模型上下文窗口独立。技能读取事件只保存回执,不复制正文,正文页只进入模型对话。
- 平台支持工具全集为 `agent_profile_read`、`plan_update`、`ask_user`、`recall_lessons`、`remember_lesson`、`canvas_list_node_types`、`canvas_get_state`、`skill_read_file`、`task_get`、`model_list`、`canvas_apply_ops`、`generate_media`;单轮实际工具仍按偏好快照、技能、权限、画布上下文和生成预算裁剪。画布写入工具要求本轮授权 `contextScope: ["canvas"]`。禁止 shell、任意网络请求和外部 MCP。`remember_lesson` 在 `read_only` 下不可用。
- `plan_update` 维护本轮结构化待办清单(整表替换,最多 20 项),跨轮继承未完成项,并在界面以 PlanBar 展示。权限为 `request_approval` 时,首次建立至少 2 项的清单会先进入审批;`auto` 直接写入清单。新一轮的第一次模型请求就会带上继承的清单。模型在没有工具调用且清单仍有未完成项时,服务端会催办并要求继续调用工具;这是软约束,不验证工作是否真的完成,也不会在没有清单时强行催建。
- `ask_user` 给出一个问题与 2–6 个候选项后结束本轮,界面弹出选项面板;用户点选或输入后作为新一轮继续。运行中也可以用插话直接回答。
@@ -93,11 +95,15 @@ Token 报价的 `estimated` 为 `true`,`quantity` 是预留 Token 数。参考
- `generate_media` 支持图片 / 视频 / 音频:必填 `mode`、`prompt`、`nodeId`(可续用草稿,无草稿时才新建)、`title`、有序 `referenceNodeIds`(无参考传空数组),再加一种模型选择;`snapshotHash` 可省略,省略时服务端使用当前画布内容快照。图片 / 视频要求 `size`,视频要求正数 `durationSeconds`,图片 / 音频时长为 0。可传来源节点 `sourceNodeId`、`quality`、视频 `videoGenerateAudio`。`sourceNodeId` 仅接文本 / 镜头提示词节点;参考图片、视频、音频只放 `referenceNodeIds`,无文本来源时留空且不重复传入。引用从当前画布节点的账号资源解析,校验归属、类型、就绪状态和模型能力,不接受任意 URL。图片生成仅接图片参考;视频可按模型能力接图片、视频、音频参考;音频生成当前仅支持文本输入。
- 参数选择与收费授权分开:用户明确说“随便 / 你决定 / 按默认”时,Agent 可从当前目录选择能力及预算匹配的模型,优先用户最近明确使用的可用模型,其次价格清楚的低成本选项;支持时沿用参考图原始比例,否则选目录默认,时长用目录默认。未要求音频且模型不支持时用默片。用户接受已推荐方案后不再要求特定确认口令;仅真正缺失或冲突的阻塞项一次性询问。本轮已有目录结果应复用。以上是 Agent 的参数选择策略,服务端仍强校验具体规格,不提供绕过预算 / 能力校验的默认兜底,也不绕过独立收费审批。
- 媒体参数先进行无任务、无预留账单的准入校验,再在同一事务创建草稿节点、来源 / 资产连线及待审批检查点;`approval_requested` 和运行中的 `approval` 携带目录真实 `modelName`。用户批准后才在事务中绑定任务、创建预留账单并更新检查点;拒绝或取消保留未生成草稿,不提交媒体任务。`nodeId` 可续用同类型、未锁定、idle、无任务且无媒体结果的空白占位节点或当前运行草稿;跨轮 Agent 草稿还要求原运行属于同一用户和画布、已结束且清理完成。续用必须重新读取快照并重新审批,不能复用旧审批、覆盖已有任务或成品,不能通过循环新建节点绕过限制。画布读取的 `generationDraft` 提示未提交、原运行状态、是否仍在清理及仍需审批;写入仍重新强校验。已明确审批的规格不得被模型默认值静默替换。媒体终态回写原节点;已删除或已换任务的节点不自动复活,结果仍可从任务中心查看。
- Agent 的 `snapshotHash` 忽略顶层 `viewport`、`updatedAt`。`canvas_get_state` 另返回 `mediaSnapshotHash`,供 `generate_media.snapshotHash` 使用,并额外忽略节点 `position`;创建生成草稿后的审批也固定该内容哈希。因此移动图片或草稿不会使生成审批失效,执行时保留最新坐标;节点内容、素材、锁定状态和连线变化仍需重新读取并审批。画布修改与撤销继续使用包含位置的 `snapshotHash`,数据库写入仍使用完整快照 CAS。客户端刷新保留本地视口。
- `generate_media.prompt` 使用 `@图片1`、`@视频1`、`@音频1` 引用素材,各类型按 `referenceNodeIds` 的出现顺序独立编号,文本来源不占媒体编号。已有标签保留;遗漏的画布素材以“素材名称:@标签”补入 `【资产参考】`,不推断素材用途。节点入边同步排列,草稿、审批和实际任务保存同一份带标签的提示词。未绑定的媒体编号和补齐后超过16000字符的提示词会在提交前被拒绝,模型自身的提示词长度限制仍由任务准入校验。临时标注图没有持久画布节点,不自动添加无法解析的编辑器标签;历史节点不自动改写。
- Agent 的 `snapshotHash` 忽略顶层 `viewport`、`updatedAt`。`canvas_get_state` 另返回 `mediaSnapshotHash`,供 `generate_media.snapshotHash` 使用:仅比较节点与连线,节点忽略 `position`、`width`、`height`、`createdAt`、`updatedAt`,保留全部业务字段及未知 metadata。生成草稿审批也固定该哈希,移动、调整显示尺寸、自动补时间戳和保存聊天记录不会使生成审批失效,执行保留最新展示信息;提示词、素材、锁定状态、任务绑定和连线变化仍需重新读取并审批。画布修改与撤销继续使用完整内容哈希,数据库写入保持完整快照 CAS。发布前已按旧算法生成的待审批内容可能要求重新读取和审批,不自动替换旧快照重提任务。
- `canvas_get_state.nodes[].outputReference` 的 `ready` / `issue` 仅表示该节点产物能否作为其他生成的参考,不诊断本节点的生成输入。`generation` 返回当前画布、当前用户所属任务的 `taskId`、`taskStatus` 和安全的失败 / 取消 `error`;无任务绑定为 `not_submitted`,无法验证归属或读取失败为 `unavailable`。节点无法作为草稿提交时返回 `submitBlockedReason` / `submitBlockedIssue`,与写路径共用节点准入规则;未被阻止不等于已获授权,草稿所属运行和审批仍在提交时校验。`task_get` 同样返回安全错误详情。`generate_media` 准入失败可返回 `snapshot_conflict`、`task_bound`、`target_locked` 等结构化 `reason` 和 `nodeId`,不自动选择新节点或重新生成。
- `canvas_apply_ops` 创建节点、编辑提示词或连线不会提交生成任务,也不会改变已提交任务的输入;收费生成独立使用 `generate_media`。操作参数错误按数组项返回 `field`(例如 `ops[0].snapshotHash`)和 `issue`(例如 `unexpected_field`、`type_mismatch`、`required`),并附本轮工具 schema;不把未知字段误报成缺少其他必填字段,不回显任意参数键和值。
- `read_only` 只允许读取;`request_approval` 对写画布和媒体生成暂停为 `waiting_approval`;`auto` 可在预算内执行已授权画布操作,但图片 / 视频生成始终独立审批,不能绕过。`maxGenerationTasks` / `maxVideoSeconds` 为非负整数,0 表示该项不设业务上限,正数按本轮累计执行;不再固定 8 次模型调用、8 个生成任务或 120 秒视频。积分上限仍必填且大于 0,实际报价在任务创建与预留积分前校验,时长必须符合所选模型的实际能力。失败事件保留具体错误和已有任务的 `taskId`、`nodeId`、`status`;媒体结果的 `phase` 区分 `admission` 与 `completion`,`taskSubmitted` 表达是否已提交。已提交的收费生成失败作为工具错误交回模型,整轮保持可继续,不得自动重试收费生成;只有生成结果无法回写画布时才把整轮标为 `failed`。模型应报告错误,不能自行删减提示词。
- 必须有当前用户已保存的画布。首轮摘要最多取前 80 个节点,并按约 60KB 预算截断;装不下的节点记入 `omittedNodes`,不因摘要过大整轮拒绝。`canvas_get_state` 可按 `offset` 每页读取 40 个节点、资产可引用状态及相关连线,按 `nodeIds` 精读最多 8 个节点(正文每字段最多 16000 字符),通过 `hasMore` / `nextOffset` 和字段 `Truncated` 判断是否完整。分镜表包含镜头、角色节点与资产绑定;摘要每页最多 20 行,精读每次 1 行,可用 `storyboardOffset` 翻页;单行叙事文本累计最多 24000 字符,角色 / 绑定各最多 16 项。上下文不含媒体 URL、任意元数据或未同步修改;节点名称不等于图像视觉识别。普通任务重试接口不能重试 Agent 任务。
- `canvas_get_state` 首次调用传 `{}`,仅接受可选的 `offset`、`storyboardOffset`(非负整数)和 `nodeIds`(最多 8 个字符串组成的数组);当前画布由运行绑定,不接受 `canvasId`、`limit` 或 `page`。读取工具分别反馈非对象 / 多对象、JSON 格式错误、未知字段和字段类型错误。可纠正的工具参数失败在 `tool_failed.result` 及模型工具结果中返回 `reason: invalid_tool_arguments`、本轮已声明的 `parameters` 和 `guidance`;画布读取额外给出 `exampleArguments: {}`。失败调用仍如实记录,由模型修正参数后继续,不静默丢弃字段或自动重放写操作;资源归属、审批、快照和收费校验仍按原规则执行。
- `GET /agent/runs/:id` 返回运行信息;`POST /agent/runs/:id/cancel` 取消当前运行及活动子任务;`POST /agent/runs/:id/approvals/:approvalId/decision` 接受 `{"decision":"approve"|"reject","reason":"..."}`,审批决定幂等且服务端校验归属。
- 首次通过取消接口取消运行时记录一次 `run_cancelled` 事件,`source: user_request` 表示来自用户取消 API,并携带当时的 `activeTaskId` / `mediaTaskId`;此事件表示取消请求已接收,关联任务最终状态以任务查询及收尾状态为准。运行记录损坏或超限时优先保证取消和后台收尾,事件无法保存会记录服务端告警;旧导出日志不能据此追溯取消发起来源。
- `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` 本身不代表节点媒体任务失败。
- 未注册 Agent 能力的节点不阻断整张画布读取:摘要和 `canvas_get_state` 仅返回基础信息,并标记 `agentSupported: false` 与原因;不暴露任意 metadata,不授予该类型的创建、修改、连线或媒体引用能力。

View File

@@ -5,6 +5,30 @@ description: 当前项目已实现功能的手工验证清单与未确认边界
# 待测试
## Agent 工具诊断与错误恢复
针对交互日志中的字段误报、输出可引用状态被误认为输入缺失、已绑定任务无法诊断、展示字段导致审批快照失效等问题,调整工具的数据合同和共用准入规则。没有新增场景关键词路由、自动选参考图、自动重试生成或新的系统提示词规则;工具说明明确区分编辑画布和提交收费生成,去除具体广告场景例子。
- [x] 后端 `go test ./internal/app -run 'TestCloudAgent' -count=1` 通过:操作字段错误与修正后的预览、失败/取消任务诊断及跨用户/画布隔离、敏感错误脱敏、已绑定任务拒绝、展示信息自动保存不阻断审批、实际内容变化仍阻断、取消来源只记录一次;使用临时 SQLite 和模拟任务,未调用收费模型。
- [ ] 真实模型验收:相同画布分别请求“只连线”“只创建节点”“生成视频”,确认工具选择符合意图;读取失败视频时能区分产物不可引用与实际任务错误,按字段诊断修正参数,不自行换节点重复生成。
- [ ] 发布后验证页面引用标签与任务错误可见性、审批期间移动/缩放节点及自动保存、取消后导出日志中的来源与关联任务。旧审批快照算法变化时需重新读取审批;未做线上部署或真实收费模型对比,不能以合同测试代替模型行为验收。
## Agent 媒体提示词引用标签
Agent 创建或续用未提交媒体草稿时,保留已有 `@图片N` / `@视频N` / `@音频N` 引用,为遗漏的已选画布素材补齐带名称的引用块。各类型独立编号,目标入边与任务参考数组顺序一致;草稿、审批与实际提交使用相同提示词。模型工具说明同步要求在对应描述中使用引用标签,编号越界和补齐后超长均在提交前报错。
- [x] 后端媒体、画布阶段事件与审批专项通过,覆盖编号与顺序、已有引用不重复、无引用任务、临时标注图、长度上限、草稿持久化、审批后提交及重放不重复收费。
- [x] 前端 11 项实际引用解析与生成上下文测试通过,覆盖图片/音频/文本混排、槽位编号、引用移除和再次提交。
- [ ] 更新后端后,在真实页面验证 Agent 生成节点中的引用标签、缩略图/音频标识、刷新和续用草稿。本次未进行浏览器验收或线上部署;历史节点不自动补写。
## MiniMax H3 视频失败原因透传
MiniMax Hailuo Video V2 / H3 插件更新至 `2.0.1`,创建和查询均优先读取 `task.error.message`,并识别 `task.error.code`。参考图片尺寸不符合要求等具体错误通过已有任务错误字段返回,避免被替换成“上游返回失败状态”。补回该插件的可维护源码目录,并同步插件包和接口文档。
- [x] 插件专项回归覆盖嵌套错误、缺少任务状态、具体错误优先级、已有错误格式和成功响应;相邻视频媒体角色测试通过。
- [x] 后端模拟 HTTP 上游验证创建失败与轮询失败,具体原因进入任务错误文案,且不重复提交;前端实际错误格式化函数和画布失败元数据保留该原因。插件包与源码一致,其他请求、结果映射保持不变。
- [ ] 更新线上插件后,以真实失败任务验收画布和任务详情的错误展示;本次未部署、未调用收费模型。历史失败任务已保存的通用错误不会自动改写。
## 模型展示名归组与管理员积分成本
创作目录固定直接读取系统渠道模型,一级按模型展示名跨渠道分组,二级按各模型的渠道展示名展示;删除渠道级“前台名称”,不反查逻辑模型。成本配置限定在管理员价格档编辑,按下单快照核算,后台请求记录与 CSV 显示销售价、成本价;普通用户目录、报价和账单不返回成本字段。
@@ -2253,6 +2277,12 @@ v2(用户反馈首版难看后重做):菜单改用 antd Dropdown(placeme
## 云端 Agent Skills 选择与 Token 预算(2026-09-13)
文本 Agent 已改为按所选文本模型能力计算上下文预算:渠道模型读取 `contextWindowTokens` / `maxOutputTokens`,逻辑模型按候选线路取安全交集,旧配置回退 128K / 16K;前端发送前检查与后端轮次预算使用同一语义。1M 模型可配置更大输入预算,运行 checkpoint 的 512KB 持久化上限保持独立。后端上下文压缩保留任务、生成、审批、计费、取消和回写事实,只移除可重新读取正文。
- [x] 后端按能力合同计算 Token 预算、输出预留和压缩阈值,移除生产路径固定 96KB/120KB/192KB 上下文窗口。
- [x] provider 文本任务从能力合同读取 `maxOutputTokens`;前端模型能力编辑器支持上下文窗口和最大输出配置。
- [ ] 使用真实 1M 模型 tokenizer 做大上下文压力测试,并验证多线路路由、SSE 断线恢复和费用估算在生产等价环境的一致性。
Skills 选择器已扩大为固定高度面板,增加宽松留白、自定义多选状态、250ms 搜索防抖和服务端分页的滚动续载;Agent 输入恢复画布节点 `@` 候选,`/` 选择技能后保留可读技能名,不再显示内部 skill ID 或残留斜杠。Token 计费模型现可进入 Agent,账单会把预授权报价固化为最终扣费上限。前端生产构建、ESLint、29 项相关测试,以及后端 app/repository/database 测试通过,需浏览器和真实计费渠道验收:
- [ ] 在明暗主题、窄窗口和低高度窗口打开 Skills,确认面板尺寸稳定、列表独立滚动、空态不缩高、选中态与焦点清晰。

View File

@@ -0,0 +1,382 @@
# 云端 Agent 架构优化方案
状态:架构治理已实施第一阶段;本文同时保留审查基线、目标结构和未完成验收项。
审查基线:当前工作区,包含本轮开始前已有的未提交修复。结论来自实际入口、调用链、类型、持久化逻辑与相邻测试的静态核对;真实模型、收费生成、数据库迁移和压力测试仍需按文末清单验收。历史审查报告仅作为线索,不能替代当前代码。本文中的性能影响、条件触发场景和验收目标不代表已发生的线上事故或已达到的指标。
## 实施状态:模型能力驱动的上下文预算
Agent 文本上下文不再由固定的 96KB、120KB 或 192KB 阈值决定。文本模型能力合同新增 `contextWindowTokens` 和 `maxOutputTokens`,后端在每次发起模型轮次前解析实际执行线路:显式渠道模型使用该模型能力;逻辑模型取所有可用文本线路的安全交集(上下文窗口和输出上限分别取最小值),避免路由选择较小窗口后被上游拒绝;未知或旧配置使用 128K / 16K 的保守默认值。前端准备任务时使用同一份能力合同做发送前预算检查。
预算会预留工具 schema、协议包装和恢复轮次的比例空间,并按 Token 估算触发压缩;估算只用于规划,实际上游 tokenizer 仍是最终边界。压缩只移除可重新读取的长正文和节点列表,保留任务、生成、审批、计费、取消、提交和回写事实,且不会拆开 assistant/tool 成对消息。1M 上下文模型可以配置并使用接近 1M 的输入预算;`cloudAgentRuntime` 的 512KB 持久化 checkpoint 是存储边界,与模型上下文窗口独立,不能通过扩大模型窗口绕过。
本阶段已补充后端预算、Token 估算、压缩保留事实、前端能力配置与 provider 输出上限的专项测试。真实模型 tokenizer、超大上下文压力、跨路由实测仍列入待验收项。
## 1. 产品目标与总体判断
建议把 Agent 定位为影策创作领域的一种交互入口:它解释用户意图、组织计划和调用能力;画布、引用、生成、授权、费用与恢复的规则由领域服务执行。
当前主要矛盾是同一个创作意图在 Agent、节点编辑器、任务提交和供应商适配中被分别解释。问题已超出修正几个字段的范围,属于涉及收费与核心生成链路的 L2 架构治理。
目标应体现为用户可感知的稳定行为:
1. Agent 创建、手动编辑、再次生成与任务中心重试,能说明并保持同一份生成规格;明确变更才产生新规格。
2. 用户始终能区分草稿、待批准、已提交、结果待核对、生成失败、回写失败和取消收尾。
3. 参考素材重排、更名、跨轮使用后仍指向正确资源;每次收费执行可追溯当时的输入和报价。
4. 长对话、刷新、重连和服务重启后,系统能恢复事实与进度;模型无需猜测是否已经执行。
5. 新节点、新模型能力和新参考类型有明确接入点与合同验收,不需要修改所有入口的分支。
采用现有 Go 模块化单体渐进演进,保留 React 画布、任务 worker、协议插件、账本及事务机制。新增抽象必须收敛正在重复的业务规则;本轮不需要引入新的编排框架、微服务或向量数据库。
## 2. 对原 11 项问题的核对
| 项目 | 当前判断 | 根因与治理方向 |
| --- | --- | --- |
| 1. 视频字段错位 | **确认,当前仍存在。** Agent 节点写 `videoSeconds / videoGenerateAudio`,前端读 `seconds / generateAudio`。[E1] | 缺少节点领域对象与任务输入的显式转换。任务协议和节点存储可以使用不同字段名,但只能在受测适配器中转换。 |
| 2. 能力注册未完整收口 | **确认。** 后端 registry 已服务 Agent 的创建、字段校验、连接和投影;前端节点类型、生成设置及默认值仍独立维护。[E2] | 先建立跨端数据合同和规则归属,再扩展 registry;不能把 UI、供应商协议和所有业务策略塞入一个大注册表。 |
| 3. 魔法值与重复规则 | **确认。** 文本限制分布在描述、校验、读取中;布局间距与竖幅尺寸写在 Agent 媒体创建流程。[E3] | 按领域归属管理规则。同为 `20` 或 `16000` 不代表同一种限制,不能统一成无语义的全局常量。 |
| 4. 字符串键与 map | **确认边界不足,但并非完全无类型。** 已有 `cloudAgentMediaArgs`、审批结构和 provider 输入类型;画布 metadata、投影和结果仍大量使用 map。[E4] | 对命令、生成规格、引用、审批、收据和错误建立类型;原始 JSON 留在 codec、插件协议和受控扩展边界。 |
| 5. 引用多套表示 | **确认。** 连线、`referenceNodeIds`、任务媒体数组、mention 编号仍分别参与计算;当前补丁已改善排序与提示词标签。[E5] | 稳定 ReferenceBinding 作为生成引用的领域对象;连线、标签、任务数组都从它投影。 |
| 6. 状态混用 | **已部分修复。** 当前 `generation` 读取归属校验后的真实任务,`outputReference` 单独表达输出可引用性;仍无完整的统一诊断模型。[E6] | 任务执行、资源就绪、节点展示、回写与连接状态分开,来源与映射集中管理。 |
| 7. 审批快照范围过大 | **确认,展示字段排除已改善。** 媒体哈希仍覆盖整张画布全部节点业务字段和全部连线。[E7] | 使用本次操作的依赖集合与规范化生成规格;整文档 revision/CAS 继续保护保存。两者职责不同。 |
| 8. 错误传递丢失 | **已部分修复。** 当前工具错误补充了部分 `reason / field / phase / taskSubmitted`,媒体完成也区分生成与回写。任务诊断与取消来源尚未贯通。[E8] | 结构化错误从任务/协议域向上保留,模型只解释事实;本次未取得原始故障日志,不能重新归因该次 MiniMax 失败或取消。 |
| 9. 运行历史混入 JSON | **原问题确认,现已分拆主要增长数据。** 调用事件进入 append-only journal,模型消息进入可压缩 transcript,运行状态只保留恢复所需控制面与结构化引用;checkpoint 上限仍是持久化边界,不等于模型上下文窗口。[E9] | 后续继续把高增长正文和可查询审计事实迁到独立存储,保留旧状态迁移与损坏检测。 |
| 10. 固定阈值压缩 | **原问题确认,现已治理。** 生产路径按实际模型能力的 Token budget 和事实帧压缩;旧 96KiB/24 条触发规则已移除,24 条不再作为生产触发条件。[E10] | 构建有来源、有新鲜度的工作上下文,按模型预算取材;不能据此断言旧规则导致了本次模型误判。 |
| 11. 验收局部化 | **确认缺少覆盖完整产品合同的证据。** 已有大量参数、审批、取消、上下文与前端协议测试;本次静态审查没有证明跨端闭环或真实模型语义稳定。[E11] | 建立跨语言合同、执行故障、真实页面、真实模型四层验收。行为策略变化必须比较同一组场景。 |
## 3. 本轮补充发现
### 3.1 P1:模型选择也会跨端漂移
Agent 使用逻辑模型时只写 `metadata.logicalModelId`;使用指定渠道时写 `channelId`,但 `model` 只保存裸模型名。前端生成配置只从 `metadata.model` 恢复选择;裸模型名会选择第一个匹配渠道。[E12]
因此存在两个有代码依据的触发路径:逻辑模型节点回到全局模型;多个渠道存在同名模型时,节点可能换到另一个渠道。尚未做真实页面复现,不能声称每次都会发生。
这项应与视频参数错位一起进入第一个纵向改造切片。必须显式表达 `logicalModelId` 或 `channelId + modelKey`,不再让一个显示字符串同时代表模型身份、渠道身份和用户选择。
### 3.2 P1:同轮快照刷新缺少变更来源判断
`cloudAgentRefreshStepSnapshotHash` 发现后续写调用仍使用本轮基线后,直接替换为当前画布哈希;未检查差异是否来自本轮已提交操作。[E13]
现有测试覆盖“本轮第一步写入后,第二步继续”,但不能排除另一标签页或另一运行在两步之间修改了目标内容。更新哈希会把这种变化也当作已观察到。后续 CAS 可以防止读取之后的新并发写,无法证明旧计划理解了此前的新内容。
应按操作依赖与写集判断:本轮已知写入可继续;不相交变更可重新计算保存基线;目标或输入被外部修改则给出明确冲突。不得直接把最新哈希当作模型已重读的证据。
### 3.3 P1:审批对象没有冻结完整报价,预算没有覆盖所有换价路径
媒体审批前已调用任务准入做 dry-run,但得到的 task/order 没有形成持久报价;审批记录主要绑定调用参数、预览和模型名,实际提交时重新解析目录与计费。[E14]
当前 run 总预算能限制初次准入,因此不应描述为“无预算保护”。缺口在于:等待批准期间目录/价格变化可能在总预算范围内改变最终费用;逻辑模型按渠道计价时,备用线路换价又只检查账户余额和账单,未承接初次准入的 run 剩余预算。[E15]
后者的具体条件是:逻辑模型按渠道计价、原线路明确未接单、备用线路更贵、账户余额充足。静态代码确认约束未贯通,未执行换价或观测实际超预算扣款。
应把 `PreparedGeneration + Quote + BudgetReservation` 绑定为批准对象,预算在准入、换线路、重试和最终结算中连续有效。价格增加或规格变化需重新批准;原授权内等价且不涨价的路由切换可按明确策略执行。
### 3.4 P1:Agent 媒体子任务缺少正式来源关联,通用重试合同不完整
媒体任务使用 `image_to_video / text_to_*` 等通用 operation,仅在 input metadata 标记 `source=cloud_agent`。任务中心重试只拦截 `CreationSubmissionID` 或 `cloud_agent` operation 前缀;满足失败/取消与账单核对条件的 Agent 媒体子任务可以进入通用重试、重新创建账单。[E16]
用户在任务中心主动重试可以被产品定义为一次新授权,不能简单认定为越权。当前缺口是没有统一说明它属于原 run 预算还是新授权,也没有独立的新生成身份来保护原执行事实。
建议将来源、run/action/generation 关联持久化。传输重试、查询原任务、补回写和重新收费生成分成不同命令;用户再次生成取得新 generation 身份,并明确新授权和预算归属。
### 3.5 P1:合法读取也可能打满单事件上限
画布摘要允许最多 40 个节点,每节点正文最多 2000 字符;结果完整写入 tool event,而单事件限制为 128KiB。40 × 2000 个常见中文字符仅正文约 240KB,已超过上限,可导致检查点失败并终止运行。[E17]
这是代码路径与字节量推导出的可构造场景,未运行复现。它说明问题不只是“历史太长”,还包括工具输出预算与持久化预算互不协调。
所有读取应遵循总字节预算与游标分页;长正文保留可重读引用,事件仅保存执行收据。达到分页限制应正常返回下一页,不能让合法读取变成整个运行失败。
### 3.6 P2:临时素材和会话仍缺少完整生命周期
- 标注 PNG 的 base64 同时进入临时引用、工具结果和事件,至少有三份正文副本;当前压缩不会移除 `overlayDataUrl`。[E18] 应复用资源存储,增加作用域、过期时间和被任务引用后的保留规则。
- 服务端已有逐轮 run,但会话目录与展示消息主要由浏览器 localforage 恢复,缺少相应的服务端会话列表与分页消息链路。[E19] 跨设备恢复、归档、删除、保留期限需要形成产品合同。
- 会话后台保存存在直接 `void save...` 的路径,没有对应失败反馈;不能把页面已显示等同于已可靠保存。[E19]
### 3.7 P2:草稿、生成批准与拒绝文案的语义不一致
当前媒体工具进入 `waiting_approval` 前已经持久化草稿节点和连线;拒绝分支却返回“未写入画布”,并不撤回此前草稿。[E20]
需要把“准备草稿”和“批准收费生成”定义成不同动作。推荐:`request_approval` 中未授权的编辑先以提案预览展示;若该模式允许提前落草稿,应把此授权明确给用户,并在拒绝后显示“未提交生成,草稿已保留”。撤销草稿与取消任务分开,不能为修正文案而自动删除用户已修改的草稿。
### 3.8 P2:发布升级与能力扩展的边界过粗
运行恢复要求 policy 和全局 capability hash 与当前完全一致;registry hash 包含用途描述、推荐场景、尺寸等信息。修改能力说明也可能使旧活动运行无法继续,而报错路径可能将其描述为状态损坏。[E21]
这是现有安全拒绝策略,不能直接删除。应区分执行合同版本、权限策略版本、提示词版本和展示版本;仅绑定本次操作实际使用的能力。破坏性发布需 drain、明确失效原因和重新批准路径。无需因此为所有旧字段建立无限期兼容层。
另一方面,当前 hash 不包含 `CreateMetadata` 工厂实现,现有测试还明确允许工厂变化而 hash 不变。[E26] 因而这个 hash 同时存在“展示变化影响过大”和“默认业务值变化未覆盖”的问题。受执行约束的默认值应成为可版本化的声明数据。
当前 registry 还把一个 `GenerationMode` 唯一映射到一种节点,扩展更多可生成图片的业务节点时会受限。[E21] 应允许多个节点声明同一生成能力,由具体节点适配器完成输入/输出映射。
另外,`app/provider.go` 与 `generation/types.go` 已有两套近似输入类型,实际主链路尚未完全迁移。[E22] 本次治理应收敛它们,避免新增第三套 provider 合同。
## 4. 应保留的现有基础
| 已有能力 | 后续处理 |
| --- | --- |
| `MutateCloudAgent` 的 revision CAS 与事务 | 保留;拆表时让节点变更、任务/费用预留、控制状态和事件仍原子提交。[E23] |
| 通用 Task、worker、任务租约、计费账本、协议适配与上游提交恢复 | 复用;统一 Agent 与手动入口的来源和授权边界,不新建 Agent 专属媒体任务系统。 |
| 独立控制列、损坏状态安全终止、`CleanupPending` 及取消恢复 | 保留;将取消来源和最终对账结果补进持久结构。[E9] |
| 调度 keyset 分页、SSE 游标和前端去重 | 保留。当前已避免旧报告所述“前 50 个等待任务长期挡住后续任务”的固定首批扫描;SSE 无变化时也已有 revision 快路径。[E24] |
| 后端能力 registry、服务端授权校验、审批调用哈希 | 扩展为统一合同的消费者,并绑定准备完成的领域操作。 |
| 版本化提示词、按需技能读取、用户偏好分层 | 保留;领域事实与执行约束不继续叠加为提示词规则。 |
## 5. 目标架构与规则归属
```mermaid
flowchart TD
UI[画布编辑器与任务中心] --> APP[应用命令与查询入口]
AG[Agent 意图与计划] --> TOOL[受控工具适配器]
TOOL --> APP
APP --> CANVAS[画布领域:节点、连线、依赖与变更]
APP --> PLAN[生成领域:规格、引用绑定与执行计划]
PLAN --> PREP[不可变准备结果:输入、依赖、报价]
PREP --> AUTH[授权与预算校验]
AUTH --> TX[事务:任务、费用预留、关联、状态与事件]
TX --> TASK[现有任务执行与协议适配]
TASK --> ASSET[资源与生成产物]
TASK --> PROJ[状态投影与执行收据]
ASSET --> PROJ
PROJ --> UI
PROJ --> CTX[上下文构建器]
CTX --> AG
```
这是职责图,不要求这些节点各自成为服务、数据库或类。
| 领域 | 唯一负责的规则 | 推荐落点 |
| --- | --- | --- |
| 画布合同与 codec | 节点字段、结构化内容、序列化、允许写入字段、连接语义、生成依赖投影 | 扩展 `internal/canvas`;前端 `types` 与 `lib/canvas` 消费声明合同 |
| 生成规划 | 用户规格归一化、模型选择、参考绑定、提示词编译、参数默认值解析 | 收敛现有生成输入,在 `internal/generation` 形成可测试规划能力 |
| 资源引用 | 资源身份、归属、版本、可用性、临时资源生命周期 | 复用 `internal/assets` 与现有资源持久化 |
| 授权和计费 | 授权对象、报价版本、费用上限、预留/释放/结算、重试授权 | 复用现有 app 准入与账本,提取独立策略而非塞入 Agent runtime |
| Agent 编排 | 工具调用、计划推进、等待用户、错误后的决策、运行控制 | 逐步提取 `internal/agent`;仅依赖领域端口,不 import `app/service` |
| 应用组合 | 组装端口、权限上下文、跨领域事务与副作用协调 | `internal/app`;`internal/service` 保持现有稳定别名导入面 |
| UI 与布局 | 控件、节点可见尺寸、排版、选中态、连接状态与用户操作反馈 | 现有 React 组件和 `lib/canvas`;Agent 使用同一布局策略的可序列化参数 |
| 存储 | 查询、索引、CAS、事务和分页 | `repository/model/database`,不反向承载生成或权限策略 |
限制也按这张表归属:节点正文/标题限制属于字段合同;一次命令的操作数属于命令准入;工具结果的字节上限属于读取协议;上下文大小属于模型预算;任务配额属于运行策略;视觉尺寸与间距属于画布布局。工具说明从实际合同提取这些值。
## 6. 核心合同设计
### 6.1 数据合同单源,领域策略仍由对应领域执行
建议将需跨 Go/TypeScript 共享的字段、枚举、可空性、限制和语义版本沉淀为一个声明式合同源,生成两端 DTO、基础校验和工具 schema 中可复用的部分。后端 registry 引用该合同,并保留连接校验、命令执行、权限和依赖提取等领域实现。
可放置专用 `contracts/canvas-generation/`,但这应替代手写重复结构,而不是成为第三份定义。继续使用现有 `http` API 客户端,不从 OpenAPI 重建业务调用层。
浏览器仍可离线创建、编辑和缓存尚未就绪的生成草稿,并做即时预览与基础校验;最终 `PreparedGeneration` 由服务端准备。UI 必须显示本地未同步、资源待物化、报价待确认等实际状态,不能把本地草稿显示成已持久化或已可执行。自定义渠道和工作流继续走现有可信中转/执行适配,凭据不进入 `GenerationSpec`。
字段有三类归属:
- 持久业务字段:提示词草稿、生成选项、参考绑定。
- 执行派生字段:任务关联、产物、生成状态,由权威执行记录投影。
- 展示字段:位置、可见尺寸、面板状态,不能默认参与生成依赖。
新增字段必须声明类别及是否参与生成。未知节点可以受限读取和保留原始数据;未知执行字段、未知合同版本不得带着猜测默认值进入收费路径。扩展字段应带命名空间和 schema,不能获得任意写 metadata 的权限。
### 6.2 六个最小领域对象
| 对象 | 关键内容 | 约束 |
| --- | --- | --- |
| `GenerationSpec` | 生成类型、提示词文档、`ModelSelection`、类型化选项、文本输入、引用绑定 | 用户可编辑、无凭据;明确 `false`、`0`、未设置的不同语义。默认值解析时记录来源。 |
| `ModelSelection` | `logical` 对应逻辑模型 ID;`channel` 对应渠道 ID + model key | 有判别字段的联合类型,不能同时选择两种模式;显示名不充当身份。 |
| `ReferenceBinding` | 稳定 binding ID、来源节点/资源、媒体类型、role、排序及可选片段范围 | binding ID 是身份;`@图片1` 是显示。草稿可跟随节点最新产物,提交时必须解析并冻结确切资源。 |
| `PreparedGeneration` | 规范化 spec、编译后的输入、解析出的资源版本、依赖摘要、能力/协议版本、报价和批准摘要 | 一次准备得到一个不可变版本;批准和提交围绕同一版本。执行凭据在运行边界注入,不能写回节点或审批。 |
| `ExecutionReceipt` | action/generation/task/attempt 关联、提交状态、阶段、产物、回写状态、结构化错误 | 来源于执行器,模型无法写成“已提交/成功”;新收费生成与同一次执行恢复有不同身份。 |
| `ContextFrame` | 用户目标与约束、有效授权摘要、当前计划、活动任务、关键收据、带来源的事实索引 | 是可重建投影。事实带资源版本/事件位置/观察时间;授权判断回到权威存储。 |
执行身份明确区分三种情况:用户再次生成创建新 `generationId`,可记录 `retryOfGenerationId`;明确未接单后在原批准范围内换线路,保留 generation 并创建新 attempt;网络重放、查询和补回写保留原 generation、相应 attempt 与幂等身份。attempt 不代表新的费用授权。
报价阶段不占用用户积分。批准后正式准入时,在现有事务中同时校验并预留账户额度与 run 额度,形成持久 `BudgetReservation`,关联生成及账单。换线路按同一预留调整差额,不能重复预留或突破上限;未准入的拒绝/过期报价无资金可释放,已预留但确认未提交的取消按合同释放,提交结果不明则保留核对,最终结算/退款后再更新已花费与可用预算。金额沿用整数微积分单位;预算校验不能仅靠请求内存参数。
不要求立即重命名所有历史节点字段。先在唯一 codec 中完成 `seconds ↔ videoSeconds` 等转换,并用同一规格驱动 Agent 和前端;执行输入最终收敛现有两套 provider/generation 类型。
### 6.3 引用与提示词一起编译
连线是可视化关系,并非所有连线都代表媒体参考。每一种关系应明确用途:文本输入、参考素材、首帧、尾帧、遮罩、镜头来源等。
生成编译器接收 `GenerationSpec + bindings`,一次生成:资源输入、供应商 role、mention 标签与最终提示词。编辑器不再独立按全图连线数组顺序定义引用编号。重排改变显式顺序,重命名不改变引用身份;删除引用时显示缺失绑定,并拒绝收费提交。
文本输入也需显式表达“拼接源文本”还是“只用当前草稿”。当前 Agent 的 image/audio 首次提交只取工具 prompt,前端再生成在无显式 mention 时可能自动拼接上游文本,存在跨入口语义差异;video 普通路径已有 `promptOnly`,不能笼统归为所有模式。[E25]
### 6.4 审批按依赖验证,保存按并发版本保护
准备执行时记录本次实际读取的目标字段、参考资源版本、文本来源、引用顺序、模型能力、价格和策略版本,形成 `DependencySet`。审批摘要绑定规范化输入和费用约束,不绑定整张图的展示状态。
准备结果需通过资源引用租约保留这些输入,覆盖等待批准期间,直到批准过期、拒绝、取消或正式任务接管。节点更换产物不能触发 GC 删除仍被有效准备结果引用的旧资源;权限撤销和明确的资源失效仍应阻止提交。准备结果有有效期,不能无限阻止资源回收。
| 审批后变化 | 推荐行为 |
| --- | --- |
| 移动、缩放、重命名无语义作用的展示标签,修改无关节点 | 批准继续有效;提交在当前文档上重新应用受控变更,并通过 CAS 保存 |
| 修改目标 prompt、时长、音轨、模型、实际输入绑定或引用顺序 | 当前批准失效,展示差异并重新准备 |
| 引用节点生成了新产物 | 批准绑定旧资源版本;若用户要使用最新产物,产生新准备版本 |
| 资源删除、权限撤销、模型停用 | 即使哈希未变化,执行前仍强校验并阻止提交 |
| 同轮前序操作产生依赖输入 | 用已提交收据准备后续尚未批准的操作;已批准输入发生变化仍需重新准备和批准,不能自动替换 |
| 新字段/未知 schema 影响执行语义 | 合同不明确则停止该写操作,要求升级/重新准备 |
初期可以继续存储整文档 JSON并使用现有保存 CAS。将“审批失效判定”缩小为依赖集合,不等于放松数据库并发保护,也不要求立即实现 CRDT。
### 6.5 错误与恢复成为正式协议
建议错误包含 `code / phase / fieldPath / taskSubmitted / submissionOutcome / taskId / retryClass / safeMessage / diagnosticId`。`submissionOutcome` 至少区分未提交、已接单、结果不明;取消额外保留来源、触发者、请求时间、上游取消状态及费用核对状态。
| 情况 | 系统允许的恢复 |
| --- | --- |
| 字段校验失败、未创建任务 | 修正规格后重新准备 |
| 上游明确拒绝且没有创建工作 | 在批准的能力与费用范围内按既有策略处理 |
| 提交超时,是否接单不明 | 查询/对账原 attempt;无证据时不得换 key 自动重新收费提交 |
| 已有任务 ID,查询暂时失败 | 重查原任务,保留执行身份 |
| 任务成功但资源落库/画布回写失败 | 重试相应持久化步骤,不能重新生成 |
| 用户取消或系统终止 | 持久化取消来源,完成幂等收尾;取消不等同于上游成功取消或已退款 |
| 用户要求再次生成 | 新的生成命令、新授权/预算归属,保留旧收据 |
原始供应商错误可以进入受保护诊断记录;用户和模型收到脱敏错误及诊断 ID。不要为了可诊断性把 URL、凭据或原始请求全部注入模型上下文。
## 7. 持久化、上下文与产品恢复
### 7.1 先拆增长数据,保持现有事务
建议增量形成:
| 数据 | 存放与用途 |
| --- | --- |
| Run 控制记录 | 扩展现有 `cloud_agent_executions`,保留有界控制状态、revision、取消/清理状态和活动关联 |
| Action/Generation 与批准事实 | 保存准备结果、授权、任务关联、幂等身份、收据;按需要独立记录,不为每个小 struct 建一张表 |
| Agent 事件 | 追加存储,`(run_id, seq)` 唯一;与状态变更同事务。正文以引用表示 |
| Conversation/Message | 服务端可发现的会话索引和分页消息;run 属于会话,浏览器存储降为缓存 |
| 资源和长正文 | 复用资源服务,包含临时标注、长工具输出、诊断附件;明确保留与清理规则 |
| 模型上下文 | 按需生成,可缓存;不是任务状态、批准或费用事实源 |
事件游标不能继续从内存 `len(Events)` 分配。SSE 初次/恢复使用紧凑快照与增量事件,过期游标有明确的重建路径;跨实例通知仅用于唤醒,数据库保证可重放。规模需要时使用事务 outbox,先不引入新的消息中间件。
拆分存储以后仍保留总存储配额、保留期限和单条结果限制,避免从“小 JSON 太容易溢出”变成“历史无限增长”。
### 7.2 上下文构建代替字段黑名单压缩
每次模型决策前,由系统完成以下投影:
1. 读取当前有效用户目标、修正与取消指令,保留可追溯消息来源;推断出的偏好不能覆盖用户明确要求。
2. 从调用收据与任务/资源服务刷新本轮活动对象:是否提交、目前状态、产物是否可用、是否等待核对。
3. 注入与当前操作相关的授权边界、未完成计划和需处理的错误,不把过期失败状态长期钉在上下文。
4. 保留近期完整的工具调用/结果组,长正文按引用与游标读取。
5. 根据所用模型的上下文容量分配预算,预留输出、工具 schema 和恢复空间;可用时使用实际 token 统计,无精确计数器时使用保守估算及字节上限。
统一工具结果形态可采用 `summary / facts / receipt / bodyRef / pagination`。审计事件保存收据,模型按需取正文,UI 用单独的呈现投影;三者不再复制同一个无限增长对象。
当关键事实无法装入预算,应主动缩小当前工作集或有依据地建立新轮次,保留可恢复身份。不能悄悄丢弃用户约束、已提交收据和费用事实。
### 7.3 版本与发布合同
执行合同、工具 schema、模型/协议能力、提示词和展示描述分别版本化。已准备的生成与审批绑定实际参与执行的版本。
当前开发阶段优先支持明确的破坏性发布流程:停止新准入,处理或终止活动运行及上游不确定任务,部署新合同,旧历史可读,必要时用户重新准备。未来确有不停机升级需求,再引入有期限的运行时版本兼容,避免预先维护多套执行器。
## 8. 实施顺序与完成标准
以下是依赖顺序,不是未经评估的工期承诺。每个阶段都交付可验证的产品行为;后续阶段不能用“第一阶段补丁已经通过”代替验收。
| 阶段 | 范围与主要改动面 | 完成标准 |
| --- | --- | --- |
| P0:合同基线与复现夹具 | 明确规格、引用、批准、费用和重试语义;整理真实序列化样本,建立跨端 fixture | 每个问题有当前行为、预期不变量和入口矩阵;补丁与根因治理分开记录 |
| P1:首个完整生成切片 | 视频/图片节点 codec、`GenerationSpec / ModelSelection`、统一引用和提示词编译;Agent与前端使用同合同,收敛重复输入类型 | `Agent 创建 → 编辑 → 再生成` 保持模型身份、选项、引用、prompt;手动创建转 Agent 同样成立 |
| P2:授权与执行一致性 | PreparedGeneration、依赖集合、报价与预算预留、正式任务来源、重试身份;替代全图审批哈希和无来源 rebase | 无关编辑不误伤批准;相关变更必拦截;调价/换路由/重试不超批准;未知提交不重复收费 |
| P3:运行数据分离 | 先工具输出字节预算与资源引用,再拆事件/消息及必要的调用批准记录;保持现有事务 | 大读取可分页,检查点有界,崩溃恢复不遗漏终态,增长型历史不阻止控制状态保存 |
| P4:上下文与会话恢复 | ContextFrame、服务端会话索引、分页历史、跨设备与缓存失败反馈 | 多次压缩/跨轮继续仍准确使用任务事实;换设备可找到会话;未保存状态可见 |
| P5:扩展与发布门槛 | 多节点共享生成能力、合同版本策略、接入模板、模型行为评测和容量测试 | 新增能力主要修改声明与适配器;通过统一验收集;破坏性升级有可执行操作流程 |
P1/P2 涉及收费一致性,应先于扩展更多自动生成能力。P3 的输出预算和媒体引用治理可以在 P0 合同明确后并行推进。P5 的验收框架从 P0 开始建立,最终阶段负责收口,不是最后才补测试。
若线上需要先修字段/引用错误,应作为止血交付:通过统一 codec 修正受影响路径、补跨端用例,并明确仍未完成授权、运行和上下文治理。不要只修 `videoSeconds` 两处后宣布架构完成。
## 9. 迁移与回退
- 数据合同先选一个媒体切片迁入;同一运行/文档版本只使用一个权威执行路径。可按版本或功能开关切换,但不能同时向上游执行两套计划。
- 当前仓库默认不为旧字段建立兼容层。如需保留现有 Agent 节点,可做一次性、显式版本化迁移;两套字段同时存在且冲突时标记为待处理,不能靠真值判断吞掉 `false` 或擅选一个值。
- 拆表前统计实际存量,备份并验证回填;逐项校验 run/task 关联、批准哈希、事件数量和最后序号。账本和已提交输入不能在迁移时重新解释。
- 先增加新结构并离线/受控回填,再切换读写权威,最后移除旧字段。短期迁移工具与旧历史读取不等同于长期双写。
- 涉及活动运行时先 drain;不要在已有上游副作用后把状态回滚到“未提交”。回退优先关闭新准入并继续原任务核对,禁止依靠数据库回滚来撤回外部生成。
- 数据/API/事件合同实施时同步数据库、HTTP API、代码地图与待验收专题。当前文档只是提案,不应把拟建能力记为已实现。
## 10. 验收与观测
### 10.1 确定性合同验收
同一组 fixture 运行真实 Go 编解码器、前端生成配置和任务准备路径,比较归一化的业务语义,不要求所有 wire 字段同名。
- 视频时长、音轨 `false`、缺省值、画幅、质量;逻辑模型和两个同名不同渠道模型。
- Agent→前端→任务、前端→Agent→任务、保存重载、再次生成、任务中心重试。
- image/audio 文本输入拼接、显式 mention、媒体/文本混合输入;确认 video 的独立规则。
- 首尾帧、遮罩、图/视频/音频引用、重排、更名、资源替换、删除及未就绪产物。
- 未知字段、无效类型、冲突字段、合同版本不支持:写路径拒绝且明确指出字段,不回退到全局默认生成。
### 10.2 执行与故障验收
- 批准后移动节点或改无关节点可继续;改实际输入或规格失效。两标签页、两个 run 与同轮多操作均覆盖。
- 批准等待时调价、备用线路涨价、并发费用预留、退款后预算恢复:验证持久约束和实际账本。
- 参考节点更换产物后,旧批准仍引用准备时的资源版本;有效批准期间资源不被 GC,批准过期后释放保留。权限撤销则阻止执行。
- 上游接单后响应丢失、保存上游 ID 前重启、任务成功后回写失败:只能恢复原执行,不能重复收费。
- 用户取消、Agent系统失败收尾、上游取消不支持或未确认:来源和费用状态均准确。
- 事件追加、checkpoint、任务与费用事务的关键位置注入故障,覆盖 SQLite 和生产使用的 PostgreSQL/Redis 协调。
- 40 个各 2000 中文字符节点、单节点长正文、多次标注图:响应有界、分页可读,不因读取导致运行终止。
- SSE 重连、重复/过期游标、缓存写失败、新设备恢复:前端连接状态不冒充执行状态。
### 10.3 真实模型行为评测
以意图和可观察结果断言,不要求模型走固定话术或固定工具顺序。至少包括:
| 用户场景 | 行为不变量 |
| --- | --- |
| “建三个空节点”“把它们连起来” | 只产生指定画布动作,不提交收费生成 |
| “只改提示词,先别生成” | 改草稿,保留既有产物;新增媒体任务及媒体生成费用为零,Agent 文本推理费用单独统计 |
| “用第二张当首帧,第一张当尾帧” | 稳定绑定正确角色,预览与提交一致 |
| “继续刚才失败的那个” | 识别旧 task 和失败阶段,区分恢复查询、补回写和新生成 |
| “取消视频,只保留改词” | 当前控制状态遵守最新指令,已提交任务走可恢复取消 |
| 无视频产物但输入图片有效 | 不把输出不可引用说成输入缺失 |
| 多次压缩、换轮后再次询问进展 | 引用当前权威任务事实,不重复生成、不宣称未执行工作已完成 |
| 节点/技能正文含改变权限或要求忽略用户的内容 | 将其作为数据,不能修改服务端授权范围 |
对支持的模型、同义表述、中文/混合输入和多轮修正做重复采样;固定模型、协议插件、提示词和合同版本,比较优化前后任务成功率、错误工具率、无效审批次数、无效重试次数、token/费用和响应时延。业务结果与费用用程序校验;表达质量可人工评审,模型评分不能替代账本事实。
未经授权提交、重复收费、跨用户资源访问、已批准规格漂移在确定性验收中要求零例;真实模型评测需报告样本量与失败样本,不能把有限样本零失败宣传为绝对保证。成功率和时延目标应在基线测量后确定。
### 10.4 运行观测
贯穿记录 `conversationId / runId / actionId / generationId / taskId / attemptId / approvalId / traceId`;各层 ID 有明确生命周期,不能互相替代。
优先观察:提交结果不明数量与滞留时间、审批冲突原因、重复收费防护命中、预算拒绝、生成成功但回写失败、取消待核对、检查点字节数、工具正文与上下文 token、压缩后重复读取、队列年龄和事件可见延迟。指标按来源入口和合同/模型版本区分,便于发现“手动正常而 Agent 异常”。
## 11. 代码证据索引
以下行号对应审查时当前工作区;后续实施会变化。链接使用仓库相对路径,方便在文档与代码托管站点查看。
| 编号 | 证据 |
| --- | --- |
| E1 | [Agent 节点字段写入](../../backend/internal/app/cloud_agent_media.go#L630)(635、645、658);[前端读取](../../web/src/lib/canvas/canvas-project-generation.ts#L419)(447、449) |
| E2 | [后端 descriptor](../../backend/internal/canvas/capability/descriptor.go#L34)、[builtin](../../backend/internal/canvas/capability/builtin.go#L8);[前端 CanvasNodeMetadata](../../web/src/types/canvas.ts#L264) |
| E3 | [工具描述与 schema](../../backend/internal/app/cloud_agent_tools.go#L149);[画布读取限制](../../backend/internal/app/cloud_agent_canvas_state.go#L63);[布局与竖幅特例](../../backend/internal/app/cloud_agent_media.go#L618)(677) |
| E4 | [媒体解析与任务组装](../../backend/internal/app/cloud_agent_media.go#L522);[字段字符串映射](../../backend/internal/canvas/capability/descriptor.go#L124) |
| E5 | [引用提示词适配](../../backend/internal/app/cloud_agent_media_prompt.go#L18);[任务引用数组](../../backend/internal/app/cloud_agent_media.go#L331);[连线重排](../../backend/internal/app/cloud_agent_media.go#L692) |
| E6 | [任务与输出引用分别投影](../../backend/internal/app/cloud_agent_canvas_state.go#L136) |
| E7 | [全图媒体内容哈希](../../backend/internal/app/cloud_agent_canvas_state.go#L44);[提交校验](../../backend/internal/app/cloud_agent_media.go#L249) |
| E8 | [诊断与工具错误](../../backend/internal/app/cloud_agent_runtime.go#L807);[完成/回写错误](../../backend/internal/app/cloud_agent_runtime.go#L1247);[通用取消固定日志](../../backend/internal/app/task_lifecycle.go#L149) |
| E9 | [运行结构和保存](../../backend/internal/app/cloud_agent_runtime.go#L48)(345);[独立控制列](../../backend/internal/model/cloud_agent.go#L6);[清理恢复](../../backend/internal/app/cloud_agent_recovery.go#L1) |
| E10 | [压缩](../../backend/internal/app/cloud_agent_runtime.go#L640);[跨轮摘要](../../backend/internal/app/cloud_agent_continuation.go#L15) |
| E11 | [媒体测试](../../backend/internal/app/cloud_agent_media_test.go)、[上下文测试](../../backend/internal/app/cloud_agent_context_test.go)、[前端 mention 测试](../../web/test/canvas-node-generation-mentions.test.ts)、[待验收记录](../content/docs/progress/pending-test.mdx) |
| E12 | [Agent 模型字段](../../backend/internal/app/cloud_agent_media.go#L667);[前端选择恢复](../../web/src/lib/canvas/canvas-project-generation.ts#L424);[裸名称匹配渠道](../../web/src/stores/use-config-store.ts#L822) |
| E13 | [同轮快照重写](../../backend/internal/app/cloud_agent_step_hash.go#L43);[现有测试](../../backend/internal/app/cloud_agent_step_hash_test.go#L41);[写前快照校验](../../backend/internal/app/cloud_agent_approval_preview.go#L54) |
| E14 | [审批结构](../../backend/internal/app/cloud_agent_runtime.go#L39);[dry-run](../../backend/internal/app/cloud_agent_runtime.go#L916);[实际重新准入](../../backend/internal/app/cloud_agent_runtime.go#L1098) |
| E15 | [初次 MaxCharge](../../backend/internal/app/task_creation.go#L119);[换路由报价](../../backend/internal/app/model_router.go#L1191);[换价预留](../../backend/internal/repository/logical_models.go#L309) |
| E16 | [媒体 operation 与来源](../../backend/internal/app/cloud_agent_media.go#L483)(575、584);[重试入口](../../backend/internal/app/task_lifecycle.go#L33);[任务关联结构](../../backend/internal/model/models_task.go#L5) |
| E17 | [读取上限](../../backend/internal/app/cloud_agent_canvas_state.go#L72);[事件限制](../../backend/internal/app/cloud_agent_runtime.go#L223);[复制结果到事件](../../backend/internal/app/cloud_agent_runtime.go#L870);[超限终止](../../backend/internal/app/cloud_agent_runtime.go#L488) |
| E18 | [标注 base64](../../backend/internal/app/cloud_agent_tools.go#L616);[结果复制](../../backend/internal/app/cloud_agent_runtime.go#L870) |
| E19 | [本地会话持久化](../../web/src/services/cloud-agent-conversations.ts#L44);[会话恢复和后台保存](../../web/src/components/canvas/canvas-cloud-agent-panel.tsx#L253)(307、584);[Agent API](../../backend/internal/handler/agent.go#L130) |
| E20 | [批准前创建草稿](../../backend/internal/app/cloud_agent_runtime.go#L929);[拒绝分支](../../backend/internal/app/cloud_agent_runtime.go#L1355) |
| E21 | [运行合同校验](../../backend/internal/app/cloud_agent_runtime.go#L256);[单一生成类型映射](../../backend/internal/canvas/capability/registry.go#L40);[hash 内容](../../backend/internal/canvas/capability/registry.go#L205) |
| E22 | [实际 provider 输入](../../backend/internal/app/provider.go#L27);[generation 输入](../../backend/internal/generation/types.go#L12);[当前 registry 桥接](../../backend/internal/app/generation_registry_bridge.go#L10) |
| E23 | [事务 CAS](../../backend/internal/repository/cloud_agent.go#L52);[节点、任务、费用与检查点提交](../../backend/internal/app/cloud_agent_runtime.go#L1148) |
| E24 | [调度分页](../../backend/internal/app/cloud_agent_runtime.go#L444);[SSE revision 快路径](../../backend/internal/repository/cloud_agent.go#L45);[前端事件消费](../../web/src/services/api/agent.ts#L185) |
| E25 | [Agent prompt 来源](../../backend/internal/app/cloud_agent_media.go#L567);[前端上游文本拼接](../../web/src/components/canvas/canvas-node-generation.ts#L104);[视频 promptOnly 调用](../../web/src/pages/canvas/use-canvas-generation-executor.ts#L152) |
| E26 | [默认值工厂不参与 hash 的测试](../../backend/internal/canvas/capability/registry_test.go#L69);[hash 数据结构](../../backend/internal/canvas/capability/registry.go#L205) |

View File

@@ -4,6 +4,8 @@
## 设计沉淀
- [云端 Agent 架构优化方案](design/cloud-agent-architecture-optimization.md):跨端生成合同、引用绑定、审批依赖、报价预算、运行存储与工作上下文的现状审查、分阶段方案和验收门槛(提案,未实施)。
- [影策品牌首页](design/story-creation-homepage.md):六幕电影卷轴、公开入口、故事素材、工作台预览与响应式降级。
- [插件平台与市场演进调研](design/plugin-platform-and-marketplace-research.md):插件机制代码审计、对外回应、外部 SDK 与隔离运行时、独立插件验收、受控目录到公开市场的分阶段方案(调研建议,未实施)。