Mai 3.0 · Ledger Engine

引擎切换后的前端协议变更

适用:测试环境,分支 feat/mai-engine-m1(PR #438)。本文只收录有行为变化的协议面;未提及的帧、字段、REST 接口一律不变。所有 JSON 形状取自 ws_adapter.py / gateway.py 当前实现,非设计稿。

2026-07-21 · ignis-cc 核对代码后重写 · 变更共 5 组 + 迁移清单 14 条

Overview

变更总览

#协议面性质不改会怎样
1忙时消息:message_rejected → 排队新帧 + 语义变化用户第二条消息看起来"消失",或被误报错
2ask_user_question 中断4 处字段/状态变化问题卡丢失、UUID 校验拒掉合法 ID、选项渲染崩
3工具名:1 改名 + 3 新增 + 3 删除枚举变化新工具卡渲染失败,删除工具永远转圈
4工具结果:bash/mai-uploadspawn_subagent结构位置变化上传结果解析不到,子代理进度永不收尾
5generation_bundle 恢复模型列表字段内容变化前端继续用硬编码模型表,与服务端脱节

Change 1 · Message Queue

忙时消息:拒绝改排队

旧行为:会话正在处理时再发 chat_message,收到 message_rejected(reason:"processing"),消息被丢弃,用户只能重发。

新行为:忙时的 chat_message 进入服务端持久化队列;当前轮结束后自动取出,作为下一条用户输入处理。时序两帧:

入队时立即下发

{
  "type": "message_queued",
  "session_id": "session_x",
  "data": {
    "queue_id": "queue_abc123",      // 配对键,保存到对应的用户气泡上
    "position": 1,                   // 入队时的位置;不会持续推送位置更新
    "timestamp": "2026-07-21T18:30:00Z"
  }
}

原轮结束、该消息开始处理时下发

{
  "type": "message_received",
  "session_id": "session_x",
  "data": {
    "message_id": "queue_abc123",    // 恒等于之前的 queue_id,用它把气泡从"排队中"切到"处理中"
    "nano_id": "",
    "checkpoint_id": "seq:18",       // 可能缺席;不透明字符串,见变更 2
    "timestamp": "2026-07-21T18:30:08Z"
  }
}

会话状态接口的完整 payload

字段类型说明
status"idle" | "running" | "pending_question"状态主判据。pending_question 是新增态,见变更 2
queued_countint仍在等待的排队消息数(已取出的不计)
is_processingbool等价于 status=="running"
can_send_messagebool兼容字段:running 时为 false,但排队是允许的——不得据此禁用输入框
current_task_id / processing_start_timestring? / iso?仅 running 时有值

前端要做的

  • 收到 message_queued:保留用户气泡,标"排队中"。不报错、不自动重发。
  • queue_id === message_received.data.message_id 配对,把气泡切到"处理中"。
  • message_rejected 没有删除,但触发面收窄到一条:忙碌中收到 regenerate。普通消息不会再收到它;处理分支保留即可。

Change 2 · ask_user_question

提问中断:4 处变化

#前端动作
2.1挂起等答案时查状态可能返回 idle明确返回 status:"pending_question"此状态下保留问题卡并显示"等你选择",不当作轮次已结束
2.2checkpoint_id 通常是 UUID不透明字符串:可能是 entry:123seq:18 或其他删除 UUID 格式校验。收到什么存什么,回答时原样带回
2.3options 元素有时是裸字符串 ["横版","竖版"]统一为对象(见下)显示与回传都用 label;渲染老账本时字符串兜底当 label
2.4中断帧难与轮次关联metadata 新增可选 turn_id(中断帧与工具 call/result 均可能带)有则用于轮次归属;配对键仍是 tool_call_id / checkpoint_id,不变

interrupt_question 帧(当前实现的完整形状)

{
  "type": "interrupt_question",
  "session_id": "session_x",
  "data": {
    "questions": [{
      "question": "选哪个画幅?",
      "header": "画幅",                        // ≤12 字符,回答 dict 的 key
      "options": [
        { "label": "横版", "description": "适合桌面展示" },
        { "label": "竖版", "description": "",
          "media": { "fileId": "Ab12Cd.png" } } // 可选预览;注意驼峰 fileId
      ]
    }],
    "checkpoint_id": "entry:123",
    "metadata": {
      "questions": [...],                      // 与 data.questions 同
      "thread_id": "session_x",
      "interrupt_id": "entry:123",
      "turn_id": "turn_00044fdc-..."           // 可选
    }
  }
}

option 对象约束:label 必填(既是显示文本也是回传值);description 可为空串;media.fileId 可选。每题至少 2 个选项。

中断帧族(名称与字段均未变,列出备查)

data 关键字段时机
interrupt_responseanswers, cancelled, checkpoint_id, auto_cancelled, cancel_reason回答提交后回显
interrupt_cancelledreason(如 user_sent_new_message), auto_cancelled, checkpoint_id问题被取消(含用户直接发新消息导致的自动取消)
interrupt_resumedcheckpoint_id, cancelled轮次从中断点继续

Change 3 · Tool Names

工具名:1 改名 + 3 新增 + 3 删除

变化前端动作
改名file_shellbash新卡显示"终端";老账本里的 file_shell 仍映射到终端卡
新增memory加显示名/图标;参数结果先走通用 JSON 卡
新增session_search同上
新增feishu同上;action 枚举见下
删除generate_imageskill_toolcall_curl 调下游 generate-*新会话不再出现此工具名;老账本仍要能渲染
删除create_videos同上同上
删除in_context_update_preference无替代(偏好体系已裁)删除依赖其结果显示"偏好保存成功"的逻辑

硬性要求

工具名不得做成封闭枚举。未知 tool_name 必须落到通用工具卡(名称 + 参数/结果 JSON),否则每次服务端新增工具前端都会崩一次。

feishu 工具速查

arguments.action 枚举:read / create / edit / extract_media / append_records / update_records / send_card / request_auth,另有可选 hint(面向用户的操作短标题,适合直接当卡片标题渲染)。

授权相关的两条协议事实:业务动作未授权时返回 AUTH_REQUIRED(不含任何 URL);request_auth 触发网关内部把授权卡直接推到用户飞书,授权链接不进模型消息、不进工具结果——前端不需要渲染任何授权链接,失败态只有 AUTH_CARD_FAILED(details.reason,如 FEISHU_USER_NOT_FOUND)。

Change 4 · Tool Results

工具结果:两处结构变化

4.1  bash / mai-upload:BatchResult 上移到 content 顶层

旧 · data.content

{
  "exit_code": 0,
  "stdout": "Ab12Cd.png\n",
  "batch_result": {
    "type": "batch_result",
    "successful": [...]
  }
}

新 · data.content

{
  "type": "batch_result",
  "model": "sandbox_upload",
  "successful": [...],
  "meta": {
    "exit_code": 0,
    "stdout": "Ab12Cd.png\n"
  }
}

4.2  spawn_subagent:终态字段不再是假值

旧实现可能在已产出文件的情况下仍返回 total=0、successful=[]、status=running。现在终态可信:

{
  "type": "batch_result",
  "tool": "spawn_subagent",
  "title": "Apollo 17 资料检索",
  "execution_time": 42.318,
  "total": 3,
  "successful": [{ "file_id": "Ab12Cd.jpg", "title": "地球照片" }],
  "subagent": { "status": "success", "artifacts": [...] }
}

前端以 subagent.statussuccessful[] 收尾进度卡,不再出现永远转圈。

Change 5 · generation_bundle

模型列表:generation_bundle 恢复供数

请求/响应信封与 3.0 之前完全一致(事件名、四个字段名都没变),变的是内容:preferences 恒为 {}(会话级偏好已裁撤,不复活);scope 从偏好提示改装模型目录,内容与 tools-backend /imagine/modelsdata.models 逐字段一致。

{
  "type": "generation_bundle",
  "data": {
    "session_id": "session_x",
    "preferences": {},                         // 恒空
    "scope": {
      "models": [{
        "model": "seedream",
        "tool": "generate-seedream",
        "description": "Generate or edit images with Seedream 5 Pro",
        "media_type": "image",                 // image | video | audio
        "modes": ["create", "edit"],
        "default_mode": "create",
        "aspect_ratios": ["auto", "16:9", "1:1", ...]
        // 视频类另有 duration:{default,min?,max?};音频类另有 input_media_types/file_count
      }]
    },
    "generation_version": 1784624703           // 上游版本号,无则为目录刷新时间戳;60s 缓存内稳定
  }
}

Checklist

迁移检查清单

  1. 收到 message_queued:保留用户气泡,标"排队中",不报错不自动重发。
  2. queue_id === message_received.data.message_id 配对气泡,切"处理中"。
  3. 状态主判据改读 status(三态)与 queued_count;can_send_message=false 不禁用输入框。
  4. status === "pending_question" 时保留问题卡,不当作轮次结束。
  5. 所有 checkpoint_id / 消息 ID 取消 UUID 校验,按不透明字符串原样保存与回传。
  6. 问题选项读 option.label(含预览读 media.fileId);老账本字符串选项兜底当 label。
  7. 中断帧与工具 call/result 的 metadata 接受可选 turn_id
  8. 新增 bash / memory / session_search / feishu 的显示名与图标;未知工具名一律落通用卡。
  9. 老账本 file_shell 映射到终端卡。
  10. 停止等待 generate_image / create_videos 的新调用;老账本仍可渲染。
  11. 删除旧 provider / 偏好 / "保存成功"整套 UI 与 in_context_update_preference 依赖。
  12. bash/mai-upload 结果按 content.type === "batch_result" 顶层识别,exit_code/stdoutmeta
  13. 资产库支持 document 类型;audio/document 不进画布;子代理进度以 subagent.status + successful[] 收尾。
  14. 模型目录唯一来源:generation_bundle.data.scope.models;删除硬编码表与已下线 REST 调用。

字段与帧形状均核对自 feat/mai-engine-m1 当前代码(ws_adapter.py · gateway.py · ask_user_question.py · imagine_models.py)。发现与实现不符,以代码为准并回报。