Files
Genarrative/docs/【编辑器】画布Agent对话面板-2026-07-03.md
kdletters 071faa482c 统一 Rust 与 TypeScript 格式化门禁
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口

完成项目 TypeScript/Prettier 与 Rust 全量格式化

修复 Pingora expected executable 门禁的空白敏感误报

同步开发运维文档与 AGC skill pack 格式化忽略规则
2026-09-01 16:28:34 +08:00

36 KiB
Raw Permalink Blame History

画布Agent对话面板

日期:2026-07-29

定位与边界

  • 画布Agent对话 是图片画布工程(/editor/canvas)右侧的对话式编辑器工具:用户通过自然语言调度画布已有的图片类生成与编辑能力,并可附加画布资源或素材库图片作为参考。
  • 它是画布域工具,不承接玩法创作、不产出玩法作品或模板,与 CONTEXT.md 中「表单/图片输入创作工作台」的 Avoid 边界不冲突。
  • 独立于拼图专用的 /api/runtime/creative-agent/sessions(该会话为 api-server 内存态、拼图领域专用,不复用)。

能力范围

对话 Agent 可通过 function-calling 触发以下八类工具,全部复用既有计费收口接口:

工具 后端接口
生成图片 POST /api/editor/images/generations
修改图片(基于附件/画布素材) POST /api/editor/images/edits
生成角色形象 既有角色形象生成入口对应接口
生成图标素材 POST /api/editor/icon-spritesheets/generations
生成 UI 设计图 既有 UI 设计图生成入口对应接口
生成视频 POST /api/editor/videos/generations
生成游戏音效 POST /api/editor/audios/sound-effects/generations
生成背景音乐 POST /api/editor/audios/background-music/generations
  • 意图解析与工具编排在后端 api-server,前端只渲染状态,不承接业务规则。
  • 下面的工具选择口径属于 Agent 规划 prompt / function-calling 约束,不是侧边栏 UI 说明文案;侧边栏面板不展示这些规则解释。
  • 用户要求“规范图 / 视觉规范图 / 风格规范图 / 素材规范展板”时,规划默认选择 generate_image,并在 prompt 中明确要求生成规范展板,包含统一视角、线条粗细、色卡、材质、阴影、圆角、状态层级、尺寸标注等可落地的视觉规范元素。
  • 用户要求“角色规范图”且语义是角色的规范展板、风格展板或设定板时,仍走 generate_image,不要误分流到 generate_character;只有实际生成角色立绘、角色主形象或角色视觉资产时才走 generate_character。用户要求多个图标素材、图集或 spritesheet 时才走 generate_icon_spritesheet
  • 所有生成必须走 execute_billable_asset_operation_with_cost 与模型定价配置,禁止绕过定价收口。
  • function-calling 的 JSON Schema 必须与参数默认值和运行时校验保持一致,不能只在 description 中提示会被运行时拒绝的组合。generate-ui-design 固定 gpt-image-2,因此 image_size 只暴露 1K / 2K;其它可切换图片模型的工具通过共享条件 schema 在显式选择 gpt-image-2 时同样把 image_size 限制为 1K / 2K,省略模型时仍按默认 nanobanana2 允许 0.5Kgenerate-video 省略 model 时按默认 seedance2.0-fast 约束 resolution480p / 720p,显式选择其它模型时仍使用其现有分辨率范围。运行时强类型校验继续作为最终防线。
  • generate-sound-effect 与站内 / External v1 的 SFX V2 契约一致:Prompt 使用 ECMAScript String.trim() 等值 canonicalization 且限制 12048 Unicode code pointsmodel 固定 eleven_text_to_sound_v2duration 缺省为手动 5s、显式 null 为自动时长、数值范围为有限 0.530 小数,loop 缺省 false。显式 duration:null 必须绕过通用“顶层 null 当缺省”兼容层,不能在 job payload 中变回 5s;确认后的 canonical payload 继续进入现有 editor_sound_effect_generation Worker,不新增 Agent 专属音频链路。
  • 图层操作及其他未注册的画板功能第一期不进入对话工具面,仍走现有面板。

当前分支落地状态

  • 已落地:会话元数据、OSS 消息文档、会话 CRUD、带 clientMessageId 幂等键的普通 JSON 消息请求、后端 LLM 工具规划、右侧对话面板、会话历史、新建 / 软删会话、附件从画布资源 / 账号素材库选择,以及八类图片 / 音视频工具对既有生成入口的复用。
  • 已落地:工具确认 / 取消、external generation task 轮询与会话懒回填。LLM 未配置、请求失败或规划结果解析失败时,后端把 role=system、正文以 ERROR 开头的消息写入 OSS,并通过 deltaMessages 返回,errorMessage 保持为空;前端隐藏 wire 前缀并以红色错误气泡展示。工具执行失败继续保存 status=failed、模型和错误信息,不能只返回瞬时错误。
  • 未落地:附件弹窗末尾上传格。external_generation_job 继续作为后台任务队列真相,对话消息只保存确认、回填状态和轻量媒体结果引用。

会话与持久化

  • 对话归属单个图片画布工程;每个工程有自己的会话列表,可新开会话。
  • SpacetimeDB 新表 editor_agent_conversation 只存会话元数据:会话 ID、projectId、ownerUserId、标题、软删标记、聊天记录 OSS 对象引用、创建/更新时间。
  • 完整消息内容存 OSSeditor-agent/{conversationId}.json会话粒度整体读写(追加消息=重写对象),不按消息拆对象。
  • 不把对话塞进画布工程快照 payload,不在 api-server 内存中保存会话真相。
  • 会话标题:新会话默认「新对话」,首条含文本的用户消息发出后自动截取前 N 字作为标题;列表摘要、详情和消息回包均携带同一必填标题,前端只展示该标题,不以会话 ID 或本地推导兜底。标题写入失败会使该消息请求失败,不能静默继续。
  • 会话删除:列表项 hover 出删除按钮 + 确认;软删(表打 deleted 标记,OSS 对象保留)。
  • 每次用户主动发送生成一个最长 128 字符的 clientMessageIdeditorAgentClient 对网络错误和通用瞬时状态码显式启用 1 次 POST transport 重试,重试复用同一个已序列化 body、clientMessageIdx-request-id。该字段独立于数字 message.id 并随用户消息写入 OSS。旧消息缺失时按 None 兼容;早期 SSE 文档若把客户端键存成用户消息字符串 id,读取时将其迁入 clientMessageId,同时重建数字定位符。后端在会话锁内检查重复键:内容一致时返回已持久化的同一回合结果,尚无结果时复用原用户消息继续规划;文本或附件身份不同则返回 409,不得再次追加用户消息或调用 LLM。

生成结果落画板(对现有占位规则的例外)

  • 对话入口触发的生成不创建"即将生成"画布占位(区别于其余生成面板);生成中状态由工具消息和外部任务状态承载。
  • 生成完成后:结果图按统一 placement 避让模型(视口中心就近、避开现有图层、32px 间距)落画板为新图层,同时登记到默认项目素材库;前端轮询到任务终态或收到已经包含结果媒体的会话增量时,直接通过编辑器作用域 ImageCanvasActionsContext.refreshCanvas() 重新读取工程快照并刷新素材库,不经过 Stage、Agent 面板或消息组件逐层透传 callback。重新读取会话后,以回填的轻量媒体引用显示缩略图。携带有效 resourceId 的图片结果在普通单击时直接调用同一 Context 的 focusResource(resourceId);图片、视频或音频结果仍可从右键素材菜单选择“在画布中定位”。两种入口都请求画布视口在 420ms 内平滑 fit 到对应图层;同一 resourceId 对应多个画布图层时只聚焦当前图层顺序中的首个,不同时聚焦或轮询多个图层。结果卡片不进入键盘 Tab 顺序,Enter / Space 不触发画布聚焦,视频和音频的点击、播放、暂停、拖动、音量等原生交互只操作播放器。定位不选中图层、不切换工具或侧栏、不收起 Agent 面板,也不为 Agent 面板预留可见区域。定位返回 { successed, reason? }:画布中无对应图层时为 reason="not-found-on-canva",右键菜单显示「画布上不存在」;其余失败使用 reason="other",仅显示「失败」。
  • 消息内生成结果缩略图必须携带并优先使用 objectKey / assetObjectId,前端通过 ResolvedAssetImage / /api/assets/read-url 换签后渲染,不能把裸 /generated-* 私有路径直接交给 <img>
  • 既有编辑器 worker 通过 canvasCompletion 写回工程快照;刷新后由 external generation task 状态和会话懒回填恢复结果。
  • 该例外已同步登记在《生成类面板Lovart统一改造方案-2026-06-17》「画布占位落点」节。

右侧布局

  • 对话框与既有任务侧栏(ImageCanvasTaskSidebarView互斥展开:展开一个自动收起另一个;各自收起后保留入口按钮。
  • 对话框与左侧素材 / 图层侧栏不互斥,允许同时展开,便于在对话中选取和核对画布素材;左侧栏切换不改变 Agent 面板开关状态。
  • 桌面端对话框固定宽约 360–400px;移动端抽屉式全宽覆盖;收起态为胶囊/圆形入口按钮。
  • 底部消息输入框随输入内容从单行高度自动增长,最大高度为 128px;原生自适应和兼容降级统一由公共 AutoGrowTextArea 组件承载,EditorAgentDraftTextarea 只保留 Agent 专属视觉、粘贴和 Enter 提交语义。支持 field-sizing: content 的浏览器使用原生内容尺寸自适应;不支持该属性的旧 Safari / iOS WebView 使用前端测量降级,受控 value 更新、非受控输入事件和宽度变化都必须重新计算换行高度,同时继续转发调用方的 onInput。降级测量必须按元素实际 box-sizing 区分 border-box 与 content-box,不能重复计入 padding 或 border。内容超过最大高度后停止增长并启用内部纵向滚动,内容缩短或清空后同步收缩。内部滚动条使用浅灰窄滑块和透明轨道,上下留白不得溢出输入框圆角边界;输入框在窄屏下允许收缩且不产生横向滚动。
  • Enter 发送必须同时排除 isComposing 和旧 Safari / WebKit 候选词确认事件的 keyCode === 229,避免输入法选词时误发送。
  • 用户消息必须包含去除首尾空白后的非空文本;附件只能随文本消息发送,前端发送门禁与后端 module-editor-agent 领域校验必须同时拒绝纯附件消息。
  • 会话管理入口在对话框头部:当前会话标题 + 历史会话下拉(按更新时间倒序)+ 新建对话按钮,全部包在对话框内。
  • 右上角删除当前对话的危险确认框继续使用 PlatformDangerConfirmDialog;其 portal 主题由 UnifiedModal 统一恢复,panel 使用 platform-remap-surface,且层级与附件选择弹窗一致,避免背景透明、错色或被画布控件遮挡。
  • 当前会话没有任何已发送消息时,新建对话按钮置灰且不可点击;输入框草稿和未发送附件不算会话内容。当前会话已有消息时可新建,新建成功后只切换到返回的空白会话,输入文字、附件及附件选择状态与切换历史会话时一样原样保留,旧会话继续保留在历史会话下拉中;创建失败同样不修改草稿。
  • 新会话创建请求 pending 时禁用历史会话下拉和发送动作,但输入框与附件仍可编辑;会话列表或历史消息加载期间同样禁用发送。表单提交处理器必须复用相同门禁,不能先清空草稿再由 hook 静默跳过发送。
  • 快速切换会话或会话轮询刷新产生并发详情请求时,每个请求必须获得唯一且单调递增的请求序号;前端只允许最后发起且有权生效的请求更新当前会话、消息、错误和加载态。被正在进行的会话切换压制的旧会话 refresh 不得提前结束新切换的加载态,旧响应也不得覆盖用户最新选择。
  • 普通 JSON 消息请求的回包必须绑定发送时的会话:用户在等待期间切换到其他会话后,只更新原会话的列表摘要,不得把原会话的 deltaMessages 、错误或画布刷新副作用应用到当前面板。
  • 收起对话框只是隐藏面板,不卸载当前会话 hook;普通 JSON 消息请求的等待态和外部生成任务状态必须在收起 / 重新打开之间保持一致。

附件

  • 输入区 + 按钮打开图片选择弹窗(仅图片,无音视频):
    • 「画布」页签(默认):展示当前工程图层引用的图片资源;
    • 「素材库」页签:账号级素材库(复用 ImageCanvasAssetLibrary 数据源);
    • 多选 + 底部「取消 / 应用」。
  • 网格末尾上传格为后续补齐项;在上传格未落地前,对话附件只从已有画布资源和账号素材库选择。后续若从对话入口上传图片,必须复用素材库 / 画布资源登记链路,不新增对话私有图片类型。
  • 附件选择弹窗使用 PlatformToolModalShell 承接白底 panel 和标准间距;底层 UnifiedModal 会把当前 platform-theme 自动注入 portal overlay,保证 --platform-modal-filldocument.body 下仍有效。
  • 应用后附件以胶囊 chip 挂在输入框上方;发出的消息内附件渲染为纯文本胶囊 chip(名称 + 小图标),默认无缩略图,鼠标悬浮才浮出缩略图预览
  • 附件领域形状:统一为画布资源 / 素材库对象引用(resourceId / assetId + 可选 objectKey),不存在只属于对话的第三种图;单条消息上限 9 张(前后端共同校验)。前端可携带展示用 imageSrc / thumbnailSrc,后端必须按当前工程和当前账号重新归一、校验归属与 objectKey
  • 附件 label 是人类可读的展示元数据,统一限制为最多 24 个 Unicode 码点。归一化时先去掉首尾空白,删除控制字符以及除 -_. 之外的 ASCII 标点,把连续空白折叠为一个半角空格,再按 24 码点截断;只含被过滤字符的 label 视为缺失。中文等非 ASCII 标点不属于本轮过滤范围。
  • 前端在创建画布、素材库和粘贴上传附件引用,以及把历史消息附件重新引用到输入区时,先执行上述归一化;原 label 无有效内容时依次归一化并使用调用方提供的 fallback(默认 referenceId)和固定文案「图片」。后端不能信任前端结果:校验当前工程 / 当前账号归属和 objectKey 后,必须用同一套字符规则、同一 24 码点上限再次归一化并重建权威附件;素材库附件的提交 label 缺失或过滤为空时,才回退到同样归一化后的素材库 label。前后端常量和规则必须保持同步。
  • 输入区附件临时状态统一收口到 useConversationAttachments,选择弹窗由独立的 AttachmentPicker 负责纯展示;选择、引用、粘贴上传完成、移除、发送清空和失败恢复都必须经同一最新状态更新入口。引用历史消息附件时,原消息继续保留存量展示快照;进入输入区的新引用先归一化 label 并保留其他展示快照字段,最终发送前再按 source + referenceId 从当前画布和素材库选项刷新,避免提前刷新后又被失败恢复的旧快照覆盖。发送失败时,已发送附件必须与等待期间新增的附件去重合并,不得因输入区已非空而丢弃;同一 source + referenceId 冲突时保留等待期间的当前草稿快照,失败请求快照只补充缺失 identity。合并后超过 9 张时优先保留等待期间的最新附件,不恢复失败请求的附件,并立即显示上限错误。异步粘贴完成时基于当时的最新附件去重并重新校验 9 张上限,不能用上传开始时捕获的旧列表覆盖期间新增的引用。

工具调用确认展示契约

  • Agent 规划出生成或编辑工具后,先把 status=not_completed 且没有 externalJobId 的工具消息持久化为待确认记录;确认卡必须在真正调用生成 provider 前展示本次提示词、规格参数、目标图和参考图缩略图,用户确认后才执行,取消后保留同一条 status=cancelled 记录。内部 system 文本和图片哈希 ID 不直接展示给用户。
  • 工具消息只保存 statusnot_completed / completed / failed / cancelled)和可选 externalJobIdexternal_generation_job 仍是队列、执行、lease 与计费结算真相;OSS status 仅表示该条对话消息是否已经回填完成结果或失败,不复制 queued / running。
  • 确认接口必须先把工具参数转换为既有编辑器 worker payload,再使用 editor-agent:{conversationId}:{messageId}:{toolName} 稳定 dedupe key 入队;同一确认的请求重试只能得到同一个 external job。入队成功后把返回的 job id 写回同一条 OSS 工具消息,不新增 Agent 工具执行关联表。
  • 前端根据 externalJobId 查询通用 external-generation job 状态;worker 继续通过 canvasCompletion 把生成结果写回工程与素材库。浏览器断线、刷新或 api-server 重启不得导致确认接口重新扣费或重新提交 provider。
  • GET /conversation 会在同一个 conversation lock 内扫描 status=not_completed 且已有 externalJobId 的工具消息:只对这些消息按 job id 定向读取主任务;任务完成后复用对应工具的 format_execute_message 替换 system text、回填轻量图片 / 视频 / 音频引用并写为 completed,任务失败则回填 error 并写为 failed。任务结果读取或 completed payload 解析 / formatter 回填失败时,必须在同一次 GET 内完成首次尝试及最多 3 次重试,三次重试各间隔 100ms 并重新读取任务结果;仍失败才把该工具消息写为 failed 并保存最后错误。该重试不依赖前端再次刷新。排队和执行中都保持 not_completed,整轮扫描结果一次性写回 OSS。
  • EditorAgentToolCall.args 的正式持久化契约是校验后的规范参数 JSON,不是 LLM 返回的原始 JSON。api-server 收到工具调用后,必须先按已注册的 ToolArgs 反序列化、补齐字段默认值、删除未进入 ToolArgs 的未知 / 退役字段、执行工具参数校验,再重新序列化并写入 args;校验失败的调用不得持久化为待确认消息。所有有明确默认值的工具标量参数在强类型 ToolArgs 中必须使用非 Option 字段:调用方省略字段或把顶层字段显式传为 null 时,统一在 ToolArgs 反序列化前视为未提供,由 Serde 补齐默认值,并把具体默认值写入规范 args;没有默认值的必填字段显式传为 null 时同样按缺失处理.(for compatibility) 后续计价、确认展示和 job payload 不得再次使用 unwrap_or 补同一默认值。LLM 原始参数只作为本次规范化的瞬时输入,不作为执行或审计真相;确认、取消、任务回填与后续上下文统一读取同一条消息中的规范 args。图片参数继续只保存由真实 data key 计算出的 opaque SHA-256 imageId;不得为了前端预览把 args 中的图片 ID 改写成 objectKey、URL 或展示对象,也不得由前端重组或回传一份新的执行参数。
  • api-server 内画布 Agent 工具统一实现 object-safe EditorAgentTool: ToolDynvalidate_args、计价、确认展示、worker job 构建、format_execute_message 和结果媒体投影都使用统一 JSON 边界;每个具体工具实现负责把 JSON 反序列化为自己的强类型 Args / 结果,并把校验与完成消息格式化转发到 platform-editor-agent 中既有的 typed validate_args / format_execute_message,不得在调用方复制工具规则。editor_agent_tool(toolName, context) 是唯一按工具名分派的位置,规划、确认和任务回填只调用返回的 dyn tool;新增工具必须补齐同一个 trait 实现和该工厂分支。LLM builder 的 .tool(...) 注册列表仍是独立显式清单,不属于本次动态分派。framework runner 必须在 ToolCallOutput 中保留工具返回的结构化 outputrunner 写入 LLM memory 与 api-server 使用规范参数持久化 system text 时统一调用公开的 format_tool_call_message,不得丢弃 TOOL_CALL_PENDING_MESSAGE 后自行拼另一套“等待确认”输出。
  • EditorAgentToolCall.displayArgs 是必填、只读的用户确认展示投影,与 args 分离:
    • stringArgs 保存提示词、比例、清晰度、模型、时长等可展示参数的稳定名称、用户可见标题和值;前端渲染模型字段时复用图片编辑器公共展示名映射,gemini-3.1-flash-image-preview 显示为 nanobanana2eleven_text_to_sound_v2 显示为 ElevenLabs、历史 audio1.0 显示为 Viduchirp-v5 显示为 Suno,视频模型显示现有产品标签,不得改写后端参数真相;
    • imageArgs 按“目标图片 / 参考图片”等参数分组,每个 refs 项包含与规范参数对应的 imageId,以及后端从已校验会话上下文解析出的 objectKeyimageSrc、可选 thumbnailSrc / label / width / height
    • extras.priceMudPoints 保存创建待确认消息时按后端运行时模型定价快照计算的预计泥点消耗;前端统一展示为“预计消耗 N泥点”,不自行计算价格。
  • displayArgs 只能由 api-server 按已注册 tool 白名单,基于已经通过 ToolArgs 校验的 args 和当前请求开始时从 OSS 会话文档一次性构建的 EditorToolContext 生成;该 context 必须按 opaque ImageId 同时保存执行所需的 dataKey 与展示所需的图片地址、Object Key、缩略图、label、宽高,参数校验、确认展示和 job payload 统一查同一份 context。不能信任 LLM 自报的展示地址、标题或素材元数据。展示投影不参与确认执行,确认接口仍只读取同一条持久化 tool call 的 args,避免“看到的素材”和“实际执行的素材”分叉。
  • extras.priceMudPoints 同样只属于展示投影,不作为扣费输入;确认后仍由既有生成 BFF 按后端运行时定价执行预扣费,因此该字段表达用户确认时看到的价格快照,而不是前端可提交或覆盖的计费真相。
  • EditorAgentToolCall.summary 只是 args 的重复字符串且没有稳定语义,当前契约删除该字段,不再作为展示或执行输入。
  • 前端待确认卡只消费必填 displayArgs,不解析各 tool 私有的 snake_case / camelCase schema,也不把 sha256:* ID 当标题或图片地址。图片统一通过 ResolvedAssetImage 使用 objectKey 换签后显示,签名 URL 不进入消息文档。模块尚未上线,不保留缺少 displayArgs 时读取 raw args 的旧消息降级路径。

LLM 与计费

  • 编排复用 creative_agent_gpt5_client 的 LLM 接入配置(同 provider/env,独立用途标识),画布 Agent 规划请求固定使用 VectorEngine gpt-5.4-mini Chat Completionsfunction-calling 注册八类工具。
  • 每个用户回合必须由 LLM 返回结构化计划;单次 completion 不是有效 JSON 时,runner 先把无效原文作为 assistant message 追加到当前 staged turn,再追加 system 纠正消息,明确要求下一轮只按既定 JSON Response Format 重试;下一次 completion 必须同时看到该无效原文和纠正指令。无效原文只是重试上下文,不进入对外 PromptOutput 或用户可见的会话增量;后续规划成功时随 staged turn 一并提交,无工具活动且最终失败时按下文事务规则整体回滚。LLM 未配置、连接已经断开、请求明确失败、达到最终安全上限或多轮重试后仍不可解析时,后端写入正文为 ERROR <错误内容> 的 system 消息,不使用本地关键词或“收到:...”回显兜底。面向用户的规划错误使用中文语义,不暴露 completion error 等 framework 内部前缀或原始配置/定价诊断;原始错误只记录在后端日志。该错误消息与其它 system 消息一样进入后续 LLM memory,使 Agent 能看到上一轮失败上下文。普通 JSON POST 尚未结束只表示 provider request future 仍在等待,不能伪装成已持久化失败。
  • 工具参数中的图片 ID 是由真实 object key 或图片地址计算的稳定 SHA-256 标识;真实 data key 仅存于 api-server 的工具上下文映射,所有图片工具在执行时查表恢复,不能把 object key 或图片地址作为 LLM 可见的工具 ID。
  • 规划 prompt 必须显式区分“规范展板”和“实际素材产出”:规范图、视觉规范图、风格规范图、素材规范展板、角色规范图等规范展板请求走 generate-image,并补齐统一视角、线条粗细、色卡、材质、阴影、圆角、状态层级、尺寸标注等要求;实际角色立绘才走 generate-character,多个图标素材 / 图集才走 generate-icon-spritesheet
  • 用户的当前消息确实在确认或取消一条已存在且仍为 pending 的工具调用时,画布 Agent 只引导使用该卡片的确认 / 取消按钮,本条确认 / 取消意图不产生新 tool call。这条边界必须使用“匹配 pending 调用时如何处理”的正向、条件化描述,不得改写成“不得重新发起相同工具调用”一类全局否定话术:实测中模型会把这类否定句过度泛化为拒绝后续新请求。已 cancelled 的卡片不再处理;用户明确要求修改、重做或发起新任务时必须允许新 tool call,pending 卡片也不阻塞无关的新请求。
  • 画布 Agent 规划请求使用 Chat Completions 和 1024 生成 token 预算;VectorEngine 专用 client 发送 max_completion_tokens,预算包含可见输出与隐藏 reasoning token,不等于可见正文长度。发送后 120 秒是前端软提示阈值,不是 provider 失败 deadline:若普通 JSON POST 仍 pending,消息流临时显示“仍在处理中,请耐心等待”并继续等待,提示不写入 OSS 消息历史;连接或请求明确失败则立即按正式错误收口。provider 单 attempt 保留 8 分钟 hard timeout;请求发起阶段的 timeout、连接失败、4084295xx 读取 GENARRATIVE_LLM_MAX_RETRIES,但画布 Agent 最多重试 1 次,专用重试退避最多 60 秒。消息规划生命周期从 handler 入口开始计入 18 分钟总 deadline,进入 agent.prompt(...) 时使用扣除会话锁和上下文准备后的剩余预算;该 deadline 必须作为 runner 内部 deadline future 参与 completion await,并在每个 tool 开始前、返回后检查,不能用外层 tokio::timeout 丢弃整个 prompt future,也不能中途 drop 已开始的工具。工具一旦开始就等待其返回,再按 deadline 携带结果收口;当前八类画布工具只做同步参数校验并返回待确认,因此不会延长正式生成链。deadline 命中时仍按 PromptRunError 返回已经完成的工具结果、提交对应 staged memory 并追加终态错误。该 deadline 覆盖非法 JSON/工具校验失败触发的后续规划轮,并为错误持久化和 HTTP 返回保留约 2 分钟,不再让前端 20 分钟 transport timeout 先触发。已收到成功响应头后的响应体读取或解析失败直接按明确失败收口,错误计数/日志使用该响应所属的真实 attempt。规划重试发生在任何生成工具执行之前,不会重复提交生成任务或扣费;生成图片/编辑图片仍走对应生成工具和模型计费。
  • function-calling runner 必须把“等待用户确认”作为显式工具语义:当本批所有工具都校验成功并进入待确认状态时,立即以成功结果结束当前规划回合并持久化助手文本与待确认卡,不得继续依赖 LLM 自行停止;未知工具、参数错误、普通连续工具和不可解析响应仍受 max_turns 保护。
  • runner 失败必须返回显式的 PromptRunError { error, partial_outputs },不得只返回终态错误而丢弃本轮已产生的文本或工具事实。prompt 执行使用 AgentMemory::begin_staged 创建行为等价且写入隔离的 StagedAgentMemory 事务,限长、摘要、脱敏等 append 规则必须在本轮 completion 前生效;成功或已发生工具活动时必须显式调用 commit(),直接 drop staged transaction 表示回滚,不得统一复制成 VecMemory 或仅替换 box 冒充持久化提交。本轮无工具活动失败时回滚 staged 用户消息、助手文本和不可解析响应;已有工具活动时在末尾追加 terminal error closure 后提交。外部 drop / abort 若尚无工具活动则回滚并保持原 committed memory;若工具已完成则提交结果与取消闭环,若工具仍在执行则提交“已启动、结果未知”事实与取消闭环,后续必须先 reconcile 再决定是否重试。
  • ToolFailure 必须以结构化工具失败输出暴露给 harness 调用方:调用方能读取 kindretryablefatal 和工具返回的原始 output;不得把它们压成单一错误字符串。这些字段只提供流程决策与诊断事实,是否重试、如何展示或持久化仍由业务调用方决定。
  • api-server 收到带 partial_outputs 的终态失败时,必须先按原顺序把其中已成功工具转成 status=not_completed 待确认消息并写入同一会话增量,再追加 ERROR <错误内容> 终态 system 消息;不得因后续轮次、其它工具或 max_turns 失败而吞掉已经执行并返回的工具结果。
  • 通用 JSON function-calling 协议、工具 schema 注入、memory / hook、max_turns 和“全部工具待确认即结束回合”统一由现役 platform-agent-harness 承载;无工具时也必须输出同一 JSON 响应格式。画布角色 prompt、规范展板 / 已有图路由、模型与超时 profile、八类工具、计费、OSS 会话和 external job 编排继续留在 platform-editor-agent / api-server,不得回流已退役的旧 platform-agent
  • 对话回合免费(聊天、分析回复不扣泥点),仅 Agent 实际触发生成工具时按对应模型定价扣泥点。
  • 工具调用前后端校验泥点余额;不足时该次生成失败并在对话中以明确错误气泡告知,对话本身可继续。

Lovart 参照做/不做清单(验收标准)

做(第一期):

  1. 助手文本随普通 JSON 消息响应一次性返回;
  2. 消息请求等待态,以及工具任务的待确认、生成中、完成 / 失败状态;
  3. 工具/模型标注行(生成时显示模型名 + 图标);
  4. 消息内生成结果缩略图不显示名称;携带有效 resourceId 的图片可单击定位,图片、视频和音频都可从素材右键菜单选择“在画布中定位”,以平滑视口动画定位对应画布图层;视频和音频的普通点击只保留媒体自身交互;
  5. 生成中的进行中动画;
  6. 错误气泡(失败/余额不足,带原因);
  7. 普通消息请求等待期间禁用发送按钮,不提供客户端停止操作;前端持续等待后端响应,超过 120 秒但 POST 仍 pending 时在思考气泡中显示“仍在处理中,请耐心等待”,最终成功或失败后自动移除,避免后端已持久化消息但前端中断请求后产生会话状态错位。
  8. 桌面端右键消息正文可复制该条可见文本;右键消息附件或生成结果可下载素材,图片额外支持复制图片本体和“引用”到当前输入区,带有效 resourceId 的图片、视频和音频生成结果额外支持“在画布中定位”。引用复用附件去重、9 张上限和发送链路;
  9. 消息右键菜单遵循 Canva 式单实例交互:任一菜单已打开时,下一次右键必须先关闭旧菜单;新落点是消息正文或素材时再在新位置打开对应菜单,新落点没有右键动作时仅收起旧菜单,不允许多个消息菜单并存。复制、引用或下载成功后自动关闭菜单;失败时保留菜单和失败状态,避免错误无提示消失。

不做(明确排除,防止后人补齐):

  • 点赞/点踩反馈按钮;
  • 分享/导出整段对话;
  • Agent 模式切换下拉(固定单一 Agent);
  • 语音输入、@引用、多 Agent 协作;
  • Lovart 的积分/加速档位显示(泥点扣费只在生成动作上体现)。

顺手需求

  • 左侧侧边栏默认隐藏:useImageCanvasEditorChrome.tsactiveSidebarPanel 初始值 'assets'null
  • 小地图默认隐藏:isMinimapOpen 初始值 truefalse
  • 纯默认值修改,不加 localStorage 偏好记忆。

后端分层落位

  • module-editor-agent(新 crate):领域规则——会话/消息校验、状态机、附件上限、软删规则、工具清单领域定义;纯逻辑无 IO。
  • spacetime-module:新表 editor_agent_conversation + procedure;同步 migration.rs、表目录、生成绑定,运行 npm run check:spacetime-schema
  • spacetime-clientfacade 读写方法。
  • api-server
    • GET/POST /api/editor/projects/{projectId}/agent-conversations(列表/新建);
    • GET/DELETE /api/editor/agent-conversations/{conversationId}(详情/软删);
    • POST /api/editor/agent-conversations/{conversationId}/messagesJSON);
    • 通用 function-calling harness 放 platform-agent-harness;画布 Agent profile 与工具放 platform-editor-agent;会话、计费、OSS 和工具内部生成执行链路由 api-server 编排层承接,不复用 creative_agent.rs 内存会话。
  • shared-contracts + packages/sharededitorAgent 会话、消息、工具确认展示与轻量媒体结果 DTO;消息响应返回 conversationdeltaMessages 和可选 errorMessage

实施顺序

  1. 契约与领域规则(shared-contracts / packages/shared + module-editor-agent);
  2. 存储层(spacetime-module 表 + procedure + migration + spacetime-client + schema check);
  3. api-server 会话 CRUD + 消息 OSS 读写 + JSON 回显桩(不接 LLM,先保证会话链路端到端真实落库);
  4. 前端最小纵切(对话框、会话管理、消息流、附件弹窗、侧边栏/小地图默认值)——可与 3 并行:3 只碰 server-rs/4 只碰 src/ 且先以契约 mock 客户端联调,汇合点在 4 末接真实 API;
  5. LLM 编排 + 八类工具接入 + 生成落画板 + 请求等待 / 错误态(替换回显桩这一个点);
  6. 验证与文档:定向测试、类型检查、npm run check:encodinggit diff --checknpm run check:spacetime-schema、api-server smoke /healthz;同步 CONTEXT.md 与相关文档。

第一阶段验收补充

  • 打开画布 Agent 后任务侧栏应关闭,再次打开任务侧栏时 Agent 面板应关闭;素材 / 图层面板与 Agent 可同时展开,互不改写开关状态。
  • 发送消息时先本地追加用户消息,再应用 JSON 响应中的 deltaMessages;请求等待期间发送按钮保持禁用,前端不主动中断当前回合。
  • Agent 消息内生成结果缩略图不显示名称;轮询到任务终态并完成会话懒回填后,通过实例级 refreshCanvas() 统一刷新工程快照和素材库,禁止重新引入中间刷新 props。有效图片结果的普通单击和三类媒体的“在画布中定位”右键菜单项都可触发画布定位;视频 / 音频普通点击与原生播放器操作不得触发。定位时必须保持 Agent 面板、当前图层选择、工具和侧栏不变,只平滑调整 viewport 到对应图层;旧消息缺少 resourceId 时单击无动作且不显示定位菜单项,目标图层已删除时保持无动作。
  • 对话内容可被用户选中复制;用户从输入框或对话内容点击回画布图层 / 生成器时,焦点应回到画布对象,Backspace / Delete 等画布快捷键继续生效。
  • 对话正文右键菜单只复制当前气泡展示的完整文本,隐藏的内部 system 文本不得进入菜单;素材右键菜单优先于正文菜单,私有素材继续通过既有读取链路换签或代理下载,不复制会过期的临时链接。