// Bot API(/bot/v1)协议参考(docs/design/13-bot-teams.md §9、ADR-0024)。 // test/api-reference.test.ts 会核对这里列出的接口与实际注册的 /bot/v1 路由完全一致,新增或删除接口时必须同步修改。 import type { ApiEndpointGroup, ApiErrorDef, ApiEventDef, ApiOverview, ApiTypeDef } from '@agent-server/shared' export const BOT_OVERVIEW: ApiOverview = { points: [ '**基础地址**:`$BASE`,所有接口位于 `/bot/v1` 下;请求和响应为 JSON(UTF-8),时间为 ISO 8601。', '**鉴权**:`Authorization: Bearer <凭证>`。**Bot Key**(`bk_…`)每个业务平台一个,在后台"API Keys › Bot Keys"签发,只在平台后端使用;**浏览器令牌**(`bt_…`)由平台后端调用 `POST /bot/v1/browser-tokens` 换取。Bot Key 与 Agent Key 互不通用:Bot Key 不能调用 `/v1`,Agent Key 也不能调用 `/bot/v1`。', '**可见范围**:每个 Bot Key 只能使用后台为它勾选的机器人,只能看到自己创建的工作组。', '**工作组**:所有对话都在工作组中进行。一个工作组有 1 个成员(人,由平台提供 `{ id, name }`,AgentServer 不做认证)、0–1 个组长、0–多个组员,组长和组员至少 1 个。', '**唤醒规则**:消息 @ 了谁就交给谁;不 @ 时,有组长交给组长,只有一个组员时交给它,否则返回 `mention_required`。同一机器人按顺序执行,执行期间收到的消息合并到下一轮。', '**任务**:复杂工作由组长开始一项**任务**(job),拆成**子任务**(task)分派给组员,组员完成后组长汇总交付。同一工作组同一时间只有一项任务;进度见 `job.updated` / `task.updated` 事件和 `GET /bot/v1/teams/:id/jobs`。超出限额时任务暂停,由成员选择继续或取消。', '**ID 前缀**:工作组 `team_`、机器人 `bot_`、消息 `tmsg_`、任务 `job_`、子任务 `task_`、交互 `itx_`、文件 `file_`;机器人的执行过程是会话 `ses_` 与 Run `run_`。', '**错误格式**与 Agent API 相同:`{ "error": { "code": "…", "message": "…", "details": {} } }`。', '**SDK**:`@agent-server/client` 中的 `BotClient` 与 `@agent-server/chat-ui` 中的 ``,安装地址见 `$BASE/sdk/index.json`。', ], flow: [ '平台后端用 Bot Key 换取浏览器令牌;前端 `GET /bot/v1/bots` 查看可用的机器人;', '`POST /bot/v1/teams` 创建工作组,订阅 `GET /bot/v1/teams/:id/stream`;', '需要附件时先 `POST /bot/v1/teams/:id/files` 上传,再 `POST /bot/v1/teams/:id/messages` 发消息(用 mentions 指定机器人);', '事件流中依次出现 `bot.status`(工作中)、`message.created`(机器人回复,交付文件在 files 中);', '出现 `interaction.required` 时由成员决定,调用 `POST /bot/v1/teams/:id/interactions/:iid` 回复;交付文件用 `GET /bot/v1/files/:id` 下载。', ], } const team = (s: string) => `/bot/v1/teams/:id${s}` export const BOT_GROUPS: ApiEndpointGroup[] = [ { id: 'auth', title: '令牌', desc: '业务平台的后端用 Bot Key 换取短期浏览器令牌,交给前端使用;Bot Key 不要出现在前端代码中。', endpoints: [ { method: 'POST', path: '/bot/v1/browser-tokens', summary: '签发浏览器令牌', desc: '只能使用 Bot Key 调用(浏览器令牌不能再签发令牌)。令牌只能用于 `/bot/v1`;停用或删除 Bot Key 后,它签发的全部令牌立即失效。', body: [{ name: 'ttlSeconds', type: 'number', desc: '有效期(秒),60 ~ 43200,默认 3600' }], response: { status: 201, type: 'BrowserToken' }, sampleBody: { ttlSeconds: 600 }, errors: ['400 bad_request:ttlSeconds 超出范围', '403 forbidden:使用浏览器令牌或后台身份调用'], example: `curl -X POST $BASE/bot/v1/browser-tokens \\ -H "Authorization: Bearer $BOT_KEY" -H "Content-Type: application/json" \\ -d '{"ttlSeconds":3600}'`, }, ], }, { id: 'bots', title: '机器人', endpoints: [ { method: 'GET', path: '/bot/v1/bots', summary: '本平台可用的机器人', desc: '只返回在后台为该 Bot Key 勾选、且已启用的机器人,附带能力卡片。kind 为 lead(组长)或 worker(组员)。', response: { status: 200, type: 'BotCard[]' }, }, ], }, { id: 'teams', title: '工作组', desc: '工作组归属于创建它的 Bot Key,其他平台看不到。组内所有机器人共用一个共享目录(uploads/ 是成员上传的文件,outputs/ 是机器人交付的文件)。', endpoints: [ { method: 'POST', path: '/bot/v1/teams', summary: '创建工作组', desc: '组长和组员加起来至少一个;组长必须是 lead 类型的机器人,组员必须是 worker 类型。', body: [ { name: 'human', type: '{ id, name }', required: true, desc: '工作组中的成员(人)。由平台提供,用于显示和审计' }, { name: 'title', type: 'string', desc: '标题(≤ 100 字),省略时用机器人名称' }, { name: 'leadBotId', type: 'string', desc: '组长(0–1 个)' }, { name: 'workerBotIds', type: 'string[]', desc: '组员(0–20 个)' }, { name: 'limits', type: 'Partial', desc: '限额,只能比系统设置低;省略时使用系统设置' }, ], response: { status: 201, type: 'TeamDetail' }, sampleBody: { title: 'API 调试', human: { id: 'u1', name: '张三' }, workerBotIds: [] }, errors: ['400 bad_request:没有机器人、机器人类型不符、已停用,或限额高于系统设置', '403 bot_not_allowed:机器人不在本平台可用列表中'], example: `curl -X POST $BASE/bot/v1/teams -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -d '{"human":{"id":"u1","name":"张三"},"workerBotIds":["bot_01…"]}'`, }, { method: 'GET', path: '/bot/v1/teams', summary: '工作组列表', desc: '本平台创建的工作组,按更新时间倒序。', query: [ { name: 'status', type: '"active" | "paused" | "closed"', desc: '只列出该状态' }, { name: 'limit', type: 'number', desc: '1–500,默认 100' }, ], response: { status: 200, type: 'Team[]' }, }, { method: 'GET', path: team(''), summary: '工作组详情', desc: '包含各机器人的当前状态和未处理的交互。', response: { status: 200, type: 'TeamDetail' } }, { method: 'PATCH', path: team(''), summary: '修改标题或限额', body: [ { name: 'title', type: 'string', desc: '1–100 字' }, { name: 'limits', type: 'Partial', desc: '只能比系统设置低;对之后开始的任务生效' }, ], response: { status: 200, type: 'TeamDetail' }, sampleBody: { title: '新标题' }, }, { method: 'DELETE', path: team(''), summary: '删除工作组', desc: '中断正在执行的工作,删除消息、共享目录中的文件和各机器人的会话;用量统计保留。', response: { status: 204, type: '—' }, errors: ['409 team_busy:仍有机器人在执行,稍后再试'], }, { method: 'POST', path: team('/pause'), summary: '暂停', desc: '中断正在执行的工作;暂停期间的消息只记录,恢复后交给机器人。', response: { status: 200, type: 'TeamDetail' }, errors: ['409 invalid_status:当前状态不能执行这个操作'], }, { method: 'POST', path: team('/resume'), summary: '恢复', response: { status: 200, type: 'TeamDetail' }, errors: ['409 invalid_status:当前状态不能执行这个操作'] }, { method: 'POST', path: team('/close'), summary: '关闭', desc: '不再接收消息,进行中的工作被中断;历史与文件只读保留,可以重新打开(各机器人的会话可以续接)。', response: { status: 200, type: 'TeamDetail' }, errors: ['409 invalid_status:当前状态不能执行这个操作'], }, { method: 'POST', path: team('/reopen'), summary: '重新打开', response: { status: 200, type: 'TeamDetail' }, errors: ['409 invalid_status:当前状态不能执行这个操作'] }, ], }, { id: 'members', title: '成员', endpoints: [ { method: 'POST', path: team('/bots'), summary: '加入机器人', desc: '类型由机器人决定(组长或组员);一个工作组最多一个组长。', body: [{ name: 'botId', type: 'string', required: true, desc: '机器人 id' }], response: { status: 200, type: 'TeamDetail' }, sampleBody: { botId: '' }, errors: ['409 already_member:已经在工作组中', '403 bot_not_allowed:机器人不在本平台可用列表中', '409 team_closed:工作组已关闭'], }, { method: 'DELETE', path: team('/bots/:botId'), summary: '移出机器人', desc: '正在执行的工作会被中断。不能移出最后一个机器人。它在本组的会话保留,再次加入时续接。', response: { status: 200, type: 'TeamDetail' }, errors: ['400 bad_request:不能移出最后一个机器人'], }, { method: 'POST', path: team('/bots/:botId/interrupt'), summary: '中断机器人的本轮工作', response: { status: 202, type: '{ ok: true }' }, }, { method: 'POST', path: team('/bots/:botId/reset-session'), summary: '让机器人开始新的会话', desc: '下一轮不再续接之前的对话(组长在当前任务结束后)。会话的上下文超过 `limits.sessionMaxContextTokens` 或闲置超过 `limits.sessionIdleHours` 时也会自动开始新的会话;组长可以从共享目录 `.team/jobs/` 下的任务纪要了解之前的工作。', response: { status: 200, type: '{ ok: true }' }, }, ], }, { id: 'messages', title: '消息与事件流', desc: '工作组的时间线由事件组成:消息、交互卡片与状态共用一个递增的 seq,断线后按 `Last-Event-ID` 续传。', endpoints: [ { method: 'POST', path: team('/messages'), summary: '发送消息(下达命令)', desc: '以工作组成员的身份发送。返回后机器人在后台开始执行,进度和回复通过事件流推送。暂停期间只记录,不唤醒机器人。', body: [ { name: 'text', type: 'string', required: true, desc: '1–100000 字' }, { name: 'mentions', type: 'string[]', desc: '被 @ 的机器人 id;省略时按唤醒规则(见概述)' }, { name: 'fileIds', type: 'string[]', desc: '已上传到本工作组的文件,作为附件交给机器人(图片会直接给模型看)' }, ], response: { status: 201, type: 'TeamMessage' }, sampleBody: { text: '你好,介绍一下你能做什么', mentions: [] }, errors: ['400 mention_required:多个组员且没有组长时必须 @', '400 bad_request:被 @ 的机器人不在工作组中', '409 team_closed:工作组已关闭'], example: `curl -X POST $BASE/bot/v1/teams/$TEAM/messages -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -d '{"text":"统计一下附件里的销售额","mentions":["bot_01…"],"fileIds":["file_01…"]}'`, }, { method: 'GET', path: team('/messages'), summary: '消息历史', desc: '只包含 message.created 事件。', query: [ { name: 'after', type: 'number', desc: '只返回 seq 大于它的事件' }, { name: 'limit', type: 'number', desc: '默认 500,最多 5000' }, ], response: { status: 200, type: '{ items: TeamEvent[], lastSeq }' }, }, { method: 'GET', path: team('/events'), summary: '全部事件(历史)', desc: '加载工作组时使用:先取历史事件构建时间线,再从 lastSeq 开始订阅事件流。', query: [ { name: 'after', type: 'number', desc: '只返回 seq 大于它的事件' }, { name: 'limit', type: 'number', desc: '默认 5000,最多 5000' }, ], response: { status: 200, type: '{ items: TeamEvent[], lastSeq }' }, }, { method: 'GET', path: team('/stream'), summary: '订阅事件流(SSE)', desc: '先补发 seq 大于 `Last-Event-ID`(或 after 参数)的事件,然后实时推送;每 15 秒发送一次注释行保持连接。', query: [{ name: 'after', type: 'number', desc: '不能设置请求头时使用,等同于 Last-Event-ID' }], response: { status: 200, type: 'text/event-stream', desc: '每条为 `id: `、`event: `、`data: `' }, }, { method: 'POST', path: team('/interactions/:iid'), summary: '回复审批或提问', desc: '机器人触发审批或提问时,事件流中出现 interaction.required;由成员在这里回复。超时按拒绝处理。', body: [ { name: 'decision', type: '"allow_once" | "allow_session" | "deny"', desc: '审批的决定' }, { name: 'reason', type: 'string', desc: '拒绝的理由(可选)' }, { name: 'answers', type: 'Record<问题 id, string | string[]>', desc: '提问的回答' }, ], response: { status: 200, type: '{ ok: true }' }, sampleBody: { decision: 'allow_once' }, errors: ['409 already_resolved:已被回复、超时或取消', '404 not_found:交互不属于这个工作组'], }, ], }, { id: 'jobs', title: '任务与子任务', desc: '任务由组长开始和结束,平台只能查看、取消,或在超出限额后选择继续。', endpoints: [ { method: 'GET', path: team('/jobs'), summary: '任务与子任务', desc: '全部任务(新的在前)和子任务(按编号)。', response: { status: 200, type: '{ jobs: Job[], tasks: Task[] }' } }, { method: 'POST', path: team('/jobs/:jobId/cancel'), summary: '取消任务', desc: '还没有结束的子任务一并取消(正在执行的被中断);组长下一轮会看到取消的消息。', response: { status: 200, type: '{ ok: true }' }, errors: ['409 job_ended:任务已经结束'], }, { method: 'POST', path: team('/jobs/:jobId/continue'), summary: '超出限额后继续', desc: '把造成暂停的那一项限额放宽一倍,任务恢复,组长被唤醒。', response: { status: 200, type: '{ ok: true }' }, errors: ['409 job_not_blocked:任务没有因为限额暂停'], }, { method: 'GET', path: team('/usage'), summary: '用量', desc: '按机器人、任务、子任务分别统计 Run 数、token 与成本。', response: { status: 200, type: 'TeamUsage' } }, { method: 'POST', path: team('/tasks/:taskId/stub'), summary: '填写替身结果(仅后台)', desc: 'Bot Playground 的调试功能:分派给替身组员的子任务不执行,由管理员填写结果。Bot Key 调用返回 403。', body: [ { name: 'outcome', type: '"completed" | "failed"', required: true, desc: '' }, { name: 'result', type: 'string', required: true, desc: '结果或失败原因' }, { name: 'fileIds', type: 'string[]', desc: '共享目录中的文件' }, ], response: { status: 200, type: '{ ok: true }' }, errors: ['403 forbidden:不是后台调用', '409 task_not_stub:子任务不在等待替身结果'], }, { method: 'POST', path: team('/steps/:stepId/continue'), summary: '逐步执行:继续(仅后台)', desc: 'Bot Playground 的调试功能:逐步执行模式下,每次唤醒机器人前停下,调用它放行这一步。Bot Key 调用返回 403。', response: { status: 200, type: '{ ok: true }' }, errors: ['403 forbidden:不是后台调用'], }, ], }, { id: 'files', title: '文件', endpoints: [ { method: 'POST', path: team('/files'), summary: '上传文件', bodyNote: '`multipart/form-data`,字段名任意,可以一次上传多个文件(最多 20 个)。文件保存到共享目录的 uploads/ 下,重名时自动加序号。', response: { status: 201, type: 'File[]' }, errors: ['413 file_too_large:超过大小上限', '409 team_closed:工作组已关闭'], }, { method: 'GET', path: team('/files'), summary: '共享目录中的文件', desc: '扫描后返回,包括机器人的中间文件。', response: { status: 200, type: 'File[]' } }, { method: 'GET', path: '/bot/v1/files/:id', summary: '下载文件', desc: '只能下载本平台工作组中的文件。预览 HTML / SVG 时禁止执行脚本。', query: [{ name: 'inline', type: '1', desc: '以 inline 方式返回(用于预览)' }], response: { status: 200, type: '文件内容' }, }, ], }, ] export const BOT_TYPES: ApiTypeDef[] = [ { name: 'BrowserToken', fields: [ { name: 'token', type: 'string', desc: '以 bt_ 开头,只能用于 /bot/v1' }, { name: 'expiresAt', type: 'string', desc: '过期时间(ISO 8601)' }, ], }, { name: 'BotSummary', fields: [ { name: 'id', type: 'string', desc: 'bot_ 开头' }, { name: 'name / avatar', type: 'string', desc: '名称(唯一,不含空格和 @)与头像(emoji)' }, { name: 'kind', type: '"lead" | "worker"', desc: '组长 / 组员' }, { name: 'description', type: 'string', desc: '岗位描述' }, { name: 'provider / model', type: 'string | null', desc: '所用引擎与模型' }, ], }, { name: 'BotCard', desc: 'BotSummary 的全部字段,另外包括能力卡片:', fields: [ { name: 'skills', type: '{ name, description }[]', desc: '技能' }, { name: 'mcpServers', type: '{ name, title, description }[]', desc: '外部服务' }, { name: 'knowledge', type: 'string[]', desc: '可访问的资料库' }, { name: 'permissionMode', type: '"ask" | "auto" | "readonly" | null', desc: '权限模式' }, ], }, { name: 'TeamLimits', desc: '每项任务的限额', fields: [ { name: 'maxTasksPerJob', type: 'number', desc: '子任务数,默认 20' }, { name: 'maxLeadRunsPerJob', type: 'number', desc: '组长的执行轮数,默认 30' }, { name: 'maxCostUsdPerJob', type: 'number | null', desc: '成本上限(美元),null 不限' }, { name: 'maxMinutesPerJob', type: 'number', desc: '时长(分钟),默认 120' }, { name: 'maxConcurrentRuns', type: 'number', desc: '工作组同时执行的 Run 数,默认 4' }, ], }, { name: 'Job', fields: [ { name: 'id / no', type: 'string / number', desc: 'job_ 开头;no 是工作组内的序号' }, { name: 'title / plan', type: 'string', desc: '组长填写的标题与计划' }, { name: 'status', type: '"running" | "blocked" | "completed" | "failed" | "cancelled"', desc: 'blocked:超出限额,等待成员选择继续或取消' }, { name: 'summary', type: 'string | null', desc: '完成时的总结,或失败、取消的原因' }, { name: 'files', type: '{ fileId, path, mime, size }[]', desc: '交付文件' }, { name: 'blockedBy', type: 'string | null', desc: '造成暂停的限额,如 maxTasksPerJob' }, { name: 'limits', type: 'object', desc: '本任务生效的限额(继续后放宽)' }, { name: 'leadRuns / taskCount / costUsd', type: 'number', desc: '组长执行轮数、子任务数、成本合计' }, { name: 'createdAt / endedAt', type: 'string', desc: '' }, ], }, { name: 'Task', fields: [ { name: 'id / no', type: 'string / number', desc: 'task_ 开头;no 是工作组内的序号(消息中显示为 #no)' }, { name: 'jobId / botId', type: 'string', desc: '所属任务、执行的组员' }, { name: 'title / instructions', type: 'string', desc: '组长写的标题与说明' }, { name: 'dependsOn', type: 'number[]', desc: '依赖的子任务序号' }, { name: 'status', type: '"waiting" | "queued" | "running" | "stub" | "completed" | "failed" | "cancelled"', desc: 'waiting:等待依赖;stub:等待替身结果(仅后台)' }, { name: 'result / files', type: 'string | null / object[]', desc: '结果(或失败原因)与交付文件' }, { name: 'runId / sessionId', type: 'string | null', desc: '执行它的 Run 与会话' }, { name: 'costUsd', type: 'number', desc: '' }, ], }, { name: 'TeamUsage', fields: [ { name: 'total', type: 'UsageRow', desc: '`{ runs, inputTokens, outputTokens, costUsd }`' }, { name: 'bots / jobs / tasks', type: 'UsageRow & { botId | jobId | taskId }[]', desc: '分项统计' }, ], }, { name: 'Team', fields: [ { name: 'id', type: 'string', desc: 'team_ 开头' }, { name: 'title', type: 'string', desc: '' }, { name: 'human', type: '{ id, name }', desc: '成员(人)' }, { name: 'status', type: '"active" | "paused" | "closed"', desc: '进行中 / 已暂停 / 已关闭' }, { name: 'bots', type: 'TeamBot[]', desc: '组长排在最前面' }, { name: 'limits', type: 'TeamLimits', desc: '生效的限额' }, { name: 'activeJobId', type: 'string | null', desc: '进行中(含因限额暂停)的任务' }, { name: 'lastSeq', type: 'number', desc: '最后一个事件的 seq' }, { name: 'createdAt / updatedAt', type: 'string', desc: 'ISO 8601' }, ], }, { name: 'TeamBot', desc: 'BotSummary 的全部字段,另外包括:', fields: [ { name: 'role', type: '"lead" | "worker"', desc: '在本组中的角色' }, { name: 'status', type: '"idle" | "queued" | "working" | "waiting_input"', desc: '空闲 / 排队 / 工作中 / 等待成员处理交互' }, { name: 'sessionId', type: 'string | null', desc: '它在本组的会话(还没工作过时为 null)' }, { name: 'joinedAt', type: 'string', desc: '' }, ], }, { name: 'TeamDetail', desc: 'Team 的全部字段,另外包括:', fields: [ { name: 'pendingInteractions', type: '{ botId, sessionId, request: InteractionRequest }[]', desc: '未处理的交互' }, { name: 'pendingSteps', type: '{ id, botId, reason, createdAt }[]', desc: '逐步执行时等待继续的步骤(仅后台的工作组)' }, ], }, { name: 'TeamMessage', fields: [ { name: 'id', type: 'string', desc: 'tmsg_ 开头' }, { name: 'sender', type: 'object', desc: '`{ kind: "human", id, name }`、`{ kind: "bot", botId, name, avatar }` 或 `{ kind: "system" }`' }, { name: 'text', type: 'string', desc: '机器人消息为 Markdown' }, { name: 'mentions', type: 'string[]', desc: '被 @ 的机器人' }, { name: 'targets', type: 'string[]', desc: '这条消息唤醒的机器人' }, { name: 'files', type: '{ fileId, path, mime, size }[]', desc: '附件或交付文件' }, { name: 'runId / sessionId', type: 'string?', desc: '机器人消息:产生它的 Run 与会话' }, { name: 'level', type: '"info" | "warning" | "error"', desc: '系统消息的级别' }, { name: 'card', type: 'object?', desc: '任务卡片:`{ kind: "job", jobId, event }`、`{ kind: "task", taskId, no, event }`、`{ kind: "limit", jobId, limit }`;后台调试时还有 stub、step。状态以 Job / Task 的最新值为准' }, ], }, { name: 'TeamEvent', desc: '工作组事件的信封', fields: [ { name: 'seq', type: 'number', desc: '工作组内单调递增,即 SSE 的 id' }, { name: 'type', type: 'string', desc: '事件类型,见"事件类型"' }, { name: 'teamId', type: 'string', desc: '' }, { name: 'ts', type: 'string', desc: 'ISO 8601' }, { name: 'payload', type: 'object', desc: '随类型不同' }, ], }, { name: 'File', fields: [ { name: 'id', type: 'string', desc: 'file_ 开头' }, { name: 'path', type: 'string', desc: '共享目录中的相对路径,如 uploads/销售.xlsx、outputs/报告.docx' }, { name: 'origin', type: '"upload" | "agent"', desc: '成员上传 / 机器人产出' }, { name: 'mime / size', type: 'string / number', desc: '' }, ], }, { name: 'InteractionRequest', desc: '与 Agent API 相同:kind 为 approval(审批)或 question(提问)', fields: [ { name: 'interactionId', type: 'string', desc: 'itx_ 开头' }, { name: 'kind', type: '"approval" | "question"', desc: '' }, { name: 'expiresAt', type: 'string', desc: '超时时间,超时按拒绝处理' }, { name: 'tool / title / input / detail / options', type: 'approval', desc: '要执行的工具、可读摘要、原始参数、详情、可选的决定' }, { name: 'questions', type: 'question', desc: '[{ id, question, header?, multiSelect, options: [{ label, description? }] }]' }, ], }, ] export const BOT_EVENTS: ApiEventDef[] = [ { type: 'message.created', payload: 'TeamMessage', desc: '新消息:成员的命令、机器人的回复(交付文件在 files 中)、系统消息(成员变化、执行失败、暂停等)', persisted: true }, { type: 'interaction.required', payload: '{ botId, sessionId, request: InteractionRequest }', desc: '机器人需要成员审批或回答问题', persisted: true }, { type: 'interaction.resolved', payload: '{ botId, sessionId, interactionId, resolution, answers?, reason? }', desc: 'resolution:allowed / denied / answered / timeout / cancelled', persisted: true }, { type: 'bot.status', payload: '{ botId, status, sessionId, runId }', desc: '机器人状态变化:idle / queued / working / waiting_input', persisted: false }, { type: 'file.changed', payload: '{ file: File, kind: "created" | "modified" }', desc: '共享目录中新建或修改了文件', persisted: false }, { type: 'team.updated', payload: 'Team', desc: '工作组的标题、状态或成员变化', persisted: false }, { type: 'job.updated', payload: 'Job', desc: '任务开始、暂停、继续或结束', persisted: true }, { type: 'task.updated', payload: 'Task', desc: '子任务分派、开始、完成、失败或取消', persisted: true }, { type: 'step.updated', payload: '{ steps: { id, botId, reason, createdAt }[] }', desc: '逐步执行时等待继续的步骤(仅后台的工作组)', persisted: false }, ] export const BOT_ERRORS: ApiErrorDef[] = [ { status: 400, code: 'bad_request', desc: '参数不正确(details 中有逐项说明)' }, { status: 400, code: 'mention_required', desc: '工作组有多个组员且没有组长,消息必须 @ 机器人' }, { status: 401, code: 'unauthorized', desc: '缺少 Authorization' }, { status: 401, code: 'invalid_api_key', desc: 'Bot Key 无效或已停用(Agent Key 也会得到这个错误)' }, { status: 401, code: 'invalid_token', desc: '浏览器令牌无效、过期、不是 Bot 令牌,或签发它的 Bot Key 已停用' }, { status: 403, code: 'forbidden', desc: '无权执行该操作' }, { status: 403, code: 'bot_not_allowed', desc: '机器人不对本平台开放' }, { status: 404, code: 'not_found', desc: '资源不存在,或不属于本平台' }, { status: 409, code: 'team_closed', desc: '工作组已关闭' }, { status: 409, code: 'invalid_status', desc: '工作组当前状态不能执行这个操作' }, { status: 409, code: 'already_member', desc: '机器人已经在工作组中' }, { status: 409, code: 'team_busy', desc: '删除时仍有机器人在执行' }, { status: 409, code: 'job_ended', desc: '任务已经结束' }, { status: 409, code: 'job_not_blocked', desc: '任务没有因为限额暂停' }, { status: 409, code: 'task_not_stub', desc: '子任务不在等待替身结果(仅后台)' }, { status: 409, code: 'already_resolved', desc: '交互已被回复、超时或取消' }, { status: 413, code: 'file_too_large', desc: '上传文件超过大小上限' }, { status: 500, code: 'internal_error', desc: '服务器内部错误' }, ]