bot-reference.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. // Bot API(/bot/v1)协议参考(docs/design/13-bot-teams.md §9、ADR-0024)。
  2. // test/api-reference.test.ts 会核对这里列出的接口与实际注册的 /bot/v1 路由完全一致,新增或删除接口时必须同步修改。
  3. import type { ApiEndpointGroup, ApiErrorDef, ApiEventDef, ApiOverview, ApiTypeDef } from '@agent-server/shared'
  4. export const BOT_OVERVIEW: ApiOverview = {
  5. points: [
  6. '**基础地址**:`$BASE`,所有接口位于 `/bot/v1` 下;请求和响应为 JSON(UTF-8),时间为 ISO 8601。',
  7. '**鉴权**:`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`。',
  8. '**可见范围**:每个 Bot Key 只能使用后台为它勾选的机器人,只能看到自己创建的工作组。',
  9. '**工作组**:所有对话都在工作组中进行。一个工作组有 1 个成员(人,由平台提供 `{ id, name }`,AgentServer 不做认证)、0–1 个组长、0–多个组员,组长和组员至少 1 个。',
  10. '**唤醒规则**:消息 @ 了谁就交给谁;不 @ 时,有组长交给组长,只有一个组员时交给它,否则返回 `mention_required`。同一机器人按顺序执行,执行期间收到的消息合并到下一轮。',
  11. '**任务**:复杂工作由组长开始一项**任务**(job),拆成**子任务**(task)分派给组员,组员完成后组长汇总交付。同一工作组同一时间只有一项任务;进度见 `job.updated` / `task.updated` 事件和 `GET /bot/v1/teams/:id/jobs`。超出限额时任务暂停,由成员选择继续或取消。',
  12. '**ID 前缀**:工作组 `team_`、机器人 `bot_`、消息 `tmsg_`、任务 `job_`、子任务 `task_`、交互 `itx_`、文件 `file_`;机器人的执行过程是会话 `ses_` 与 Run `run_`。',
  13. '**错误格式**与 Agent API 相同:`{ "error": { "code": "…", "message": "…", "details": {} } }`。',
  14. '**SDK**:`@agent-server/client` 中的 `BotClient` 与 `@agent-server/chat-ui` 中的 `<TeamChat>`,安装地址见 `$BASE/sdk/index.json`。',
  15. ],
  16. flow: [
  17. '平台后端用 Bot Key 换取浏览器令牌;前端 `GET /bot/v1/bots` 查看可用的机器人;',
  18. '`POST /bot/v1/teams` 创建工作组,订阅 `GET /bot/v1/teams/:id/stream`;',
  19. '需要附件时先 `POST /bot/v1/teams/:id/files` 上传,再 `POST /bot/v1/teams/:id/messages` 发消息(用 mentions 指定机器人);',
  20. '事件流中依次出现 `bot.status`(工作中)、`message.created`(机器人回复,交付文件在 files 中);',
  21. '出现 `interaction.required` 时由成员决定,调用 `POST /bot/v1/teams/:id/interactions/:iid` 回复;交付文件用 `GET /bot/v1/files/:id` 下载。',
  22. ],
  23. }
  24. const team = (s: string) => `/bot/v1/teams/:id${s}`
  25. export const BOT_GROUPS: ApiEndpointGroup[] = [
  26. {
  27. id: 'auth',
  28. title: '令牌',
  29. desc: '业务平台的后端用 Bot Key 换取短期浏览器令牌,交给前端使用;Bot Key 不要出现在前端代码中。',
  30. endpoints: [
  31. {
  32. method: 'POST', path: '/bot/v1/browser-tokens', summary: '签发浏览器令牌',
  33. desc: '只能使用 Bot Key 调用(浏览器令牌不能再签发令牌)。令牌只能用于 `/bot/v1`;停用或删除 Bot Key 后,它签发的全部令牌立即失效。',
  34. body: [{ name: 'ttlSeconds', type: 'number', desc: '有效期(秒),60 ~ 43200,默认 3600' }],
  35. response: { status: 201, type: 'BrowserToken' },
  36. sampleBody: { ttlSeconds: 600 },
  37. errors: ['400 bad_request:ttlSeconds 超出范围', '403 forbidden:使用浏览器令牌或后台身份调用'],
  38. example: `curl -X POST $BASE/bot/v1/browser-tokens \\
  39. -H "Authorization: Bearer $BOT_KEY" -H "Content-Type: application/json" \\
  40. -d '{"ttlSeconds":3600}'`,
  41. },
  42. ],
  43. },
  44. {
  45. id: 'bots',
  46. title: '机器人',
  47. endpoints: [
  48. {
  49. method: 'GET', path: '/bot/v1/bots', summary: '本平台可用的机器人',
  50. desc: '只返回在后台为该 Bot Key 勾选、且已启用的机器人,附带能力卡片。kind 为 lead(组长)或 worker(组员)。',
  51. response: { status: 200, type: 'BotCard[]' },
  52. },
  53. ],
  54. },
  55. {
  56. id: 'teams',
  57. title: '工作组',
  58. desc: '工作组归属于创建它的 Bot Key,其他平台看不到。组内所有机器人共用一个共享目录(uploads/ 是成员上传的文件,outputs/ 是机器人交付的文件)。',
  59. endpoints: [
  60. {
  61. method: 'POST', path: '/bot/v1/teams', summary: '创建工作组',
  62. desc: '组长和组员加起来至少一个;组长必须是 lead 类型的机器人,组员必须是 worker 类型。',
  63. body: [
  64. { name: 'human', type: '{ id, name }', required: true, desc: '工作组中的成员(人)。由平台提供,用于显示和审计' },
  65. { name: 'title', type: 'string', desc: '标题(≤ 100 字),省略时用机器人名称' },
  66. { name: 'leadBotId', type: 'string', desc: '组长(0–1 个)' },
  67. { name: 'workerBotIds', type: 'string[]', desc: '组员(0–20 个)' },
  68. { name: 'limits', type: 'Partial<TeamLimits>', desc: '限额,只能比系统设置低;省略时使用系统设置' },
  69. ],
  70. response: { status: 201, type: 'TeamDetail' },
  71. sampleBody: { title: 'API 调试', human: { id: 'u1', name: '张三' }, workerBotIds: [] },
  72. errors: ['400 bad_request:没有机器人、机器人类型不符、已停用,或限额高于系统设置', '403 bot_not_allowed:机器人不在本平台可用列表中'],
  73. example: `curl -X POST $BASE/bot/v1/teams -H "Authorization: Bearer $TOKEN" \\
  74. -H "Content-Type: application/json" \\
  75. -d '{"human":{"id":"u1","name":"张三"},"workerBotIds":["bot_01…"]}'`,
  76. },
  77. {
  78. method: 'GET', path: '/bot/v1/teams', summary: '工作组列表',
  79. desc: '本平台创建的工作组,按更新时间倒序。',
  80. query: [
  81. { name: 'status', type: '"active" | "paused" | "closed"', desc: '只列出该状态' },
  82. { name: 'limit', type: 'number', desc: '1–500,默认 100' },
  83. ],
  84. response: { status: 200, type: 'Team[]' },
  85. },
  86. { method: 'GET', path: team(''), summary: '工作组详情', desc: '包含各机器人的当前状态和未处理的交互。', response: { status: 200, type: 'TeamDetail' } },
  87. {
  88. method: 'PATCH', path: team(''), summary: '修改标题或限额',
  89. body: [
  90. { name: 'title', type: 'string', desc: '1–100 字' },
  91. { name: 'limits', type: 'Partial<TeamLimits>', desc: '只能比系统设置低;对之后开始的任务生效' },
  92. ],
  93. response: { status: 200, type: 'TeamDetail' },
  94. sampleBody: { title: '新标题' },
  95. },
  96. {
  97. method: 'DELETE', path: team(''), summary: '删除工作组',
  98. desc: '中断正在执行的工作,删除消息、共享目录中的文件和各机器人的会话;用量统计保留。',
  99. response: { status: 204, type: '—' },
  100. errors: ['409 team_busy:仍有机器人在执行,稍后再试'],
  101. },
  102. {
  103. method: 'POST', path: team('/pause'), summary: '暂停',
  104. desc: '中断正在执行的工作;暂停期间的消息只记录,恢复后交给机器人。',
  105. response: { status: 200, type: 'TeamDetail' }, errors: ['409 invalid_status:当前状态不能执行这个操作'],
  106. },
  107. { method: 'POST', path: team('/resume'), summary: '恢复', response: { status: 200, type: 'TeamDetail' }, errors: ['409 invalid_status:当前状态不能执行这个操作'] },
  108. {
  109. method: 'POST', path: team('/close'), summary: '关闭',
  110. desc: '不再接收消息,进行中的工作被中断;历史与文件只读保留,可以重新打开(各机器人的会话可以续接)。',
  111. response: { status: 200, type: 'TeamDetail' }, errors: ['409 invalid_status:当前状态不能执行这个操作'],
  112. },
  113. { method: 'POST', path: team('/reopen'), summary: '重新打开', response: { status: 200, type: 'TeamDetail' }, errors: ['409 invalid_status:当前状态不能执行这个操作'] },
  114. ],
  115. },
  116. {
  117. id: 'members',
  118. title: '成员',
  119. endpoints: [
  120. {
  121. method: 'POST', path: team('/bots'), summary: '加入机器人',
  122. desc: '类型由机器人决定(组长或组员);一个工作组最多一个组长。',
  123. body: [{ name: 'botId', type: 'string', required: true, desc: '机器人 id' }],
  124. response: { status: 200, type: 'TeamDetail' },
  125. sampleBody: { botId: '' },
  126. errors: ['409 already_member:已经在工作组中', '403 bot_not_allowed:机器人不在本平台可用列表中', '409 team_closed:工作组已关闭'],
  127. },
  128. {
  129. method: 'DELETE', path: team('/bots/:botId'), summary: '移出机器人',
  130. desc: '正在执行的工作会被中断。不能移出最后一个机器人。它在本组的会话保留,再次加入时续接。',
  131. response: { status: 200, type: 'TeamDetail' },
  132. errors: ['400 bad_request:不能移出最后一个机器人'],
  133. },
  134. {
  135. method: 'POST', path: team('/bots/:botId/interrupt'), summary: '中断机器人的本轮工作',
  136. response: { status: 202, type: '{ ok: true }' },
  137. },
  138. {
  139. method: 'POST', path: team('/bots/:botId/reset-session'), summary: '让机器人开始新的会话',
  140. desc: '下一轮不再续接之前的对话(组长在当前任务结束后)。会话的上下文超过 `limits.sessionMaxContextTokens` 或闲置超过 `limits.sessionIdleHours` 时也会自动开始新的会话;组长可以从共享目录 `.team/jobs/` 下的任务纪要了解之前的工作。',
  141. response: { status: 200, type: '{ ok: true }' },
  142. },
  143. ],
  144. },
  145. {
  146. id: 'messages',
  147. title: '消息与事件流',
  148. desc: '工作组的时间线由事件组成:消息、交互卡片与状态共用一个递增的 seq,断线后按 `Last-Event-ID` 续传。',
  149. endpoints: [
  150. {
  151. method: 'POST', path: team('/messages'), summary: '发送消息(下达命令)',
  152. desc: '以工作组成员的身份发送。返回后机器人在后台开始执行,进度和回复通过事件流推送。暂停期间只记录,不唤醒机器人。',
  153. body: [
  154. { name: 'text', type: 'string', required: true, desc: '1–100000 字' },
  155. { name: 'mentions', type: 'string[]', desc: '被 @ 的机器人 id;省略时按唤醒规则(见概述)' },
  156. { name: 'fileIds', type: 'string[]', desc: '已上传到本工作组的文件,作为附件交给机器人(图片会直接给模型看)' },
  157. ],
  158. response: { status: 201, type: 'TeamMessage' },
  159. sampleBody: { text: '你好,介绍一下你能做什么', mentions: [] },
  160. errors: ['400 mention_required:多个组员且没有组长时必须 @', '400 bad_request:被 @ 的机器人不在工作组中', '409 team_closed:工作组已关闭'],
  161. example: `curl -X POST $BASE/bot/v1/teams/$TEAM/messages -H "Authorization: Bearer $TOKEN" \\
  162. -H "Content-Type: application/json" \\
  163. -d '{"text":"统计一下附件里的销售额","mentions":["bot_01…"],"fileIds":["file_01…"]}'`,
  164. },
  165. {
  166. method: 'GET', path: team('/messages'), summary: '消息历史',
  167. desc: '只包含 message.created 事件。',
  168. query: [
  169. { name: 'after', type: 'number', desc: '只返回 seq 大于它的事件' },
  170. { name: 'limit', type: 'number', desc: '默认 500,最多 5000' },
  171. ],
  172. response: { status: 200, type: '{ items: TeamEvent[], lastSeq }' },
  173. },
  174. {
  175. method: 'GET', path: team('/events'), summary: '全部事件(历史)',
  176. desc: '加载工作组时使用:先取历史事件构建时间线,再从 lastSeq 开始订阅事件流。',
  177. query: [
  178. { name: 'after', type: 'number', desc: '只返回 seq 大于它的事件' },
  179. { name: 'limit', type: 'number', desc: '默认 5000,最多 5000' },
  180. ],
  181. response: { status: 200, type: '{ items: TeamEvent[], lastSeq }' },
  182. },
  183. {
  184. method: 'GET', path: team('/stream'), summary: '订阅事件流(SSE)',
  185. desc: '先补发 seq 大于 `Last-Event-ID`(或 after 参数)的事件,然后实时推送;每 15 秒发送一次注释行保持连接。',
  186. query: [{ name: 'after', type: 'number', desc: '不能设置请求头时使用,等同于 Last-Event-ID' }],
  187. response: { status: 200, type: 'text/event-stream', desc: '每条为 `id: <seq>`、`event: <type>`、`data: <TeamEvent JSON>`' },
  188. },
  189. {
  190. method: 'POST', path: team('/interactions/:iid'), summary: '回复审批或提问',
  191. desc: '机器人触发审批或提问时,事件流中出现 interaction.required;由成员在这里回复。超时按拒绝处理。',
  192. body: [
  193. { name: 'decision', type: '"allow_once" | "allow_session" | "deny"', desc: '审批的决定' },
  194. { name: 'reason', type: 'string', desc: '拒绝的理由(可选)' },
  195. { name: 'answers', type: 'Record<问题 id, string | string[]>', desc: '提问的回答' },
  196. ],
  197. response: { status: 200, type: '{ ok: true }' },
  198. sampleBody: { decision: 'allow_once' },
  199. errors: ['409 already_resolved:已被回复、超时或取消', '404 not_found:交互不属于这个工作组'],
  200. },
  201. ],
  202. },
  203. {
  204. id: 'jobs',
  205. title: '任务与子任务',
  206. desc: '任务由组长开始和结束,平台只能查看、取消,或在超出限额后选择继续。',
  207. endpoints: [
  208. { method: 'GET', path: team('/jobs'), summary: '任务与子任务', desc: '全部任务(新的在前)和子任务(按编号)。', response: { status: 200, type: '{ jobs: Job[], tasks: Task[] }' } },
  209. {
  210. method: 'POST', path: team('/jobs/:jobId/cancel'), summary: '取消任务',
  211. desc: '还没有结束的子任务一并取消(正在执行的被中断);组长下一轮会看到取消的消息。',
  212. response: { status: 200, type: '{ ok: true }' },
  213. errors: ['409 job_ended:任务已经结束'],
  214. },
  215. {
  216. method: 'POST', path: team('/jobs/:jobId/continue'), summary: '超出限额后继续',
  217. desc: '把造成暂停的那一项限额放宽一倍,任务恢复,组长被唤醒。',
  218. response: { status: 200, type: '{ ok: true }' },
  219. errors: ['409 job_not_blocked:任务没有因为限额暂停'],
  220. },
  221. { method: 'GET', path: team('/usage'), summary: '用量', desc: '按机器人、任务、子任务分别统计 Run 数、token 与成本。', response: { status: 200, type: 'TeamUsage' } },
  222. {
  223. method: 'POST', path: team('/tasks/:taskId/stub'), summary: '填写替身结果(仅后台)',
  224. desc: 'Bot Playground 的调试功能:分派给替身组员的子任务不执行,由管理员填写结果。Bot Key 调用返回 403。',
  225. body: [
  226. { name: 'outcome', type: '"completed" | "failed"', required: true, desc: '' },
  227. { name: 'result', type: 'string', required: true, desc: '结果或失败原因' },
  228. { name: 'fileIds', type: 'string[]', desc: '共享目录中的文件' },
  229. ],
  230. response: { status: 200, type: '{ ok: true }' },
  231. errors: ['403 forbidden:不是后台调用', '409 task_not_stub:子任务不在等待替身结果'],
  232. },
  233. {
  234. method: 'POST', path: team('/steps/:stepId/continue'), summary: '逐步执行:继续(仅后台)',
  235. desc: 'Bot Playground 的调试功能:逐步执行模式下,每次唤醒机器人前停下,调用它放行这一步。Bot Key 调用返回 403。',
  236. response: { status: 200, type: '{ ok: true }' },
  237. errors: ['403 forbidden:不是后台调用'],
  238. },
  239. ],
  240. },
  241. {
  242. id: 'files',
  243. title: '文件',
  244. endpoints: [
  245. {
  246. method: 'POST', path: team('/files'), summary: '上传文件',
  247. bodyNote: '`multipart/form-data`,字段名任意,可以一次上传多个文件(最多 20 个)。文件保存到共享目录的 uploads/ 下,重名时自动加序号。',
  248. response: { status: 201, type: 'File[]' },
  249. errors: ['413 file_too_large:超过大小上限', '409 team_closed:工作组已关闭'],
  250. },
  251. { method: 'GET', path: team('/files'), summary: '共享目录中的文件', desc: '扫描后返回,包括机器人的中间文件。', response: { status: 200, type: 'File[]' } },
  252. {
  253. method: 'GET', path: '/bot/v1/files/:id', summary: '下载文件',
  254. desc: '只能下载本平台工作组中的文件。预览 HTML / SVG 时禁止执行脚本。',
  255. query: [{ name: 'inline', type: '1', desc: '以 inline 方式返回(用于预览)' }],
  256. response: { status: 200, type: '文件内容' },
  257. },
  258. ],
  259. },
  260. ]
  261. export const BOT_TYPES: ApiTypeDef[] = [
  262. {
  263. name: 'BrowserToken', fields: [
  264. { name: 'token', type: 'string', desc: '以 bt_ 开头,只能用于 /bot/v1' },
  265. { name: 'expiresAt', type: 'string', desc: '过期时间(ISO 8601)' },
  266. ],
  267. },
  268. {
  269. name: 'BotSummary', fields: [
  270. { name: 'id', type: 'string', desc: 'bot_ 开头' },
  271. { name: 'name / avatar', type: 'string', desc: '名称(唯一,不含空格和 @)与头像(emoji)' },
  272. { name: 'kind', type: '"lead" | "worker"', desc: '组长 / 组员' },
  273. { name: 'description', type: 'string', desc: '岗位描述' },
  274. { name: 'provider / model', type: 'string | null', desc: '所用引擎与模型' },
  275. ],
  276. },
  277. {
  278. name: 'BotCard', desc: 'BotSummary 的全部字段,另外包括能力卡片:', fields: [
  279. { name: 'skills', type: '{ name, description }[]', desc: '技能' },
  280. { name: 'mcpServers', type: '{ name, title, description }[]', desc: '外部服务' },
  281. { name: 'knowledge', type: 'string[]', desc: '可访问的资料库' },
  282. { name: 'permissionMode', type: '"ask" | "auto" | "readonly" | null', desc: '权限模式' },
  283. ],
  284. },
  285. {
  286. name: 'TeamLimits', desc: '每项任务的限额', fields: [
  287. { name: 'maxTasksPerJob', type: 'number', desc: '子任务数,默认 20' },
  288. { name: 'maxLeadRunsPerJob', type: 'number', desc: '组长的执行轮数,默认 30' },
  289. { name: 'maxCostUsdPerJob', type: 'number | null', desc: '成本上限(美元),null 不限' },
  290. { name: 'maxMinutesPerJob', type: 'number', desc: '时长(分钟),默认 120' },
  291. { name: 'maxConcurrentRuns', type: 'number', desc: '工作组同时执行的 Run 数,默认 4' },
  292. ],
  293. },
  294. {
  295. name: 'Job', fields: [
  296. { name: 'id / no', type: 'string / number', desc: 'job_ 开头;no 是工作组内的序号' },
  297. { name: 'title / plan', type: 'string', desc: '组长填写的标题与计划' },
  298. { name: 'status', type: '"running" | "blocked" | "completed" | "failed" | "cancelled"', desc: 'blocked:超出限额,等待成员选择继续或取消' },
  299. { name: 'summary', type: 'string | null', desc: '完成时的总结,或失败、取消的原因' },
  300. { name: 'files', type: '{ fileId, path, mime, size }[]', desc: '交付文件' },
  301. { name: 'blockedBy', type: 'string | null', desc: '造成暂停的限额,如 maxTasksPerJob' },
  302. { name: 'limits', type: 'object', desc: '本任务生效的限额(继续后放宽)' },
  303. { name: 'leadRuns / taskCount / costUsd', type: 'number', desc: '组长执行轮数、子任务数、成本合计' },
  304. { name: 'createdAt / endedAt', type: 'string', desc: '' },
  305. ],
  306. },
  307. {
  308. name: 'Task', fields: [
  309. { name: 'id / no', type: 'string / number', desc: 'task_ 开头;no 是工作组内的序号(消息中显示为 #no)' },
  310. { name: 'jobId / botId', type: 'string', desc: '所属任务、执行的组员' },
  311. { name: 'title / instructions', type: 'string', desc: '组长写的标题与说明' },
  312. { name: 'dependsOn', type: 'number[]', desc: '依赖的子任务序号' },
  313. { name: 'status', type: '"waiting" | "queued" | "running" | "stub" | "completed" | "failed" | "cancelled"', desc: 'waiting:等待依赖;stub:等待替身结果(仅后台)' },
  314. { name: 'result / files', type: 'string | null / object[]', desc: '结果(或失败原因)与交付文件' },
  315. { name: 'runId / sessionId', type: 'string | null', desc: '执行它的 Run 与会话' },
  316. { name: 'costUsd', type: 'number', desc: '' },
  317. ],
  318. },
  319. {
  320. name: 'TeamUsage', fields: [
  321. { name: 'total', type: 'UsageRow', desc: '`{ runs, inputTokens, outputTokens, costUsd }`' },
  322. { name: 'bots / jobs / tasks', type: 'UsageRow & { botId | jobId | taskId }[]', desc: '分项统计' },
  323. ],
  324. },
  325. {
  326. name: 'Team', fields: [
  327. { name: 'id', type: 'string', desc: 'team_ 开头' },
  328. { name: 'title', type: 'string', desc: '' },
  329. { name: 'human', type: '{ id, name }', desc: '成员(人)' },
  330. { name: 'status', type: '"active" | "paused" | "closed"', desc: '进行中 / 已暂停 / 已关闭' },
  331. { name: 'bots', type: 'TeamBot[]', desc: '组长排在最前面' },
  332. { name: 'limits', type: 'TeamLimits', desc: '生效的限额' },
  333. { name: 'activeJobId', type: 'string | null', desc: '进行中(含因限额暂停)的任务' },
  334. { name: 'lastSeq', type: 'number', desc: '最后一个事件的 seq' },
  335. { name: 'createdAt / updatedAt', type: 'string', desc: 'ISO 8601' },
  336. ],
  337. },
  338. {
  339. name: 'TeamBot', desc: 'BotSummary 的全部字段,另外包括:', fields: [
  340. { name: 'role', type: '"lead" | "worker"', desc: '在本组中的角色' },
  341. { name: 'status', type: '"idle" | "queued" | "working" | "waiting_input"', desc: '空闲 / 排队 / 工作中 / 等待成员处理交互' },
  342. { name: 'sessionId', type: 'string | null', desc: '它在本组的会话(还没工作过时为 null)' },
  343. { name: 'joinedAt', type: 'string', desc: '' },
  344. ],
  345. },
  346. {
  347. name: 'TeamDetail', desc: 'Team 的全部字段,另外包括:', fields: [
  348. { name: 'pendingInteractions', type: '{ botId, sessionId, request: InteractionRequest }[]', desc: '未处理的交互' },
  349. { name: 'pendingSteps', type: '{ id, botId, reason, createdAt }[]', desc: '逐步执行时等待继续的步骤(仅后台的工作组)' },
  350. ],
  351. },
  352. {
  353. name: 'TeamMessage', fields: [
  354. { name: 'id', type: 'string', desc: 'tmsg_ 开头' },
  355. { name: 'sender', type: 'object', desc: '`{ kind: "human", id, name }`、`{ kind: "bot", botId, name, avatar }` 或 `{ kind: "system" }`' },
  356. { name: 'text', type: 'string', desc: '机器人消息为 Markdown' },
  357. { name: 'mentions', type: 'string[]', desc: '被 @ 的机器人' },
  358. { name: 'targets', type: 'string[]', desc: '这条消息唤醒的机器人' },
  359. { name: 'files', type: '{ fileId, path, mime, size }[]', desc: '附件或交付文件' },
  360. { name: 'runId / sessionId', type: 'string?', desc: '机器人消息:产生它的 Run 与会话' },
  361. { name: 'level', type: '"info" | "warning" | "error"', desc: '系统消息的级别' },
  362. { name: 'card', type: 'object?', desc: '任务卡片:`{ kind: "job", jobId, event }`、`{ kind: "task", taskId, no, event }`、`{ kind: "limit", jobId, limit }`;后台调试时还有 stub、step。状态以 Job / Task 的最新值为准' },
  363. ],
  364. },
  365. {
  366. name: 'TeamEvent', desc: '工作组事件的信封', fields: [
  367. { name: 'seq', type: 'number', desc: '工作组内单调递增,即 SSE 的 id' },
  368. { name: 'type', type: 'string', desc: '事件类型,见"事件类型"' },
  369. { name: 'teamId', type: 'string', desc: '' },
  370. { name: 'ts', type: 'string', desc: 'ISO 8601' },
  371. { name: 'payload', type: 'object', desc: '随类型不同' },
  372. ],
  373. },
  374. {
  375. name: 'File', fields: [
  376. { name: 'id', type: 'string', desc: 'file_ 开头' },
  377. { name: 'path', type: 'string', desc: '共享目录中的相对路径,如 uploads/销售.xlsx、outputs/报告.docx' },
  378. { name: 'origin', type: '"upload" | "agent"', desc: '成员上传 / 机器人产出' },
  379. { name: 'mime / size', type: 'string / number', desc: '' },
  380. ],
  381. },
  382. {
  383. name: 'InteractionRequest', desc: '与 Agent API 相同:kind 为 approval(审批)或 question(提问)', fields: [
  384. { name: 'interactionId', type: 'string', desc: 'itx_ 开头' },
  385. { name: 'kind', type: '"approval" | "question"', desc: '' },
  386. { name: 'expiresAt', type: 'string', desc: '超时时间,超时按拒绝处理' },
  387. { name: 'tool / title / input / detail / options', type: 'approval', desc: '要执行的工具、可读摘要、原始参数、详情、可选的决定' },
  388. { name: 'questions', type: 'question', desc: '[{ id, question, header?, multiSelect, options: [{ label, description? }] }]' },
  389. ],
  390. },
  391. ]
  392. export const BOT_EVENTS: ApiEventDef[] = [
  393. { type: 'message.created', payload: 'TeamMessage', desc: '新消息:成员的命令、机器人的回复(交付文件在 files 中)、系统消息(成员变化、执行失败、暂停等)', persisted: true },
  394. { type: 'interaction.required', payload: '{ botId, sessionId, request: InteractionRequest }', desc: '机器人需要成员审批或回答问题', persisted: true },
  395. { type: 'interaction.resolved', payload: '{ botId, sessionId, interactionId, resolution, answers?, reason? }', desc: 'resolution:allowed / denied / answered / timeout / cancelled', persisted: true },
  396. { type: 'bot.status', payload: '{ botId, status, sessionId, runId }', desc: '机器人状态变化:idle / queued / working / waiting_input', persisted: false },
  397. { type: 'file.changed', payload: '{ file: File, kind: "created" | "modified" }', desc: '共享目录中新建或修改了文件', persisted: false },
  398. { type: 'team.updated', payload: 'Team', desc: '工作组的标题、状态或成员变化', persisted: false },
  399. { type: 'job.updated', payload: 'Job', desc: '任务开始、暂停、继续或结束', persisted: true },
  400. { type: 'task.updated', payload: 'Task', desc: '子任务分派、开始、完成、失败或取消', persisted: true },
  401. { type: 'step.updated', payload: '{ steps: { id, botId, reason, createdAt }[] }', desc: '逐步执行时等待继续的步骤(仅后台的工作组)', persisted: false },
  402. ]
  403. export const BOT_ERRORS: ApiErrorDef[] = [
  404. { status: 400, code: 'bad_request', desc: '参数不正确(details 中有逐项说明)' },
  405. { status: 400, code: 'mention_required', desc: '工作组有多个组员且没有组长,消息必须 @ 机器人' },
  406. { status: 401, code: 'unauthorized', desc: '缺少 Authorization' },
  407. { status: 401, code: 'invalid_api_key', desc: 'Bot Key 无效或已停用(Agent Key 也会得到这个错误)' },
  408. { status: 401, code: 'invalid_token', desc: '浏览器令牌无效、过期、不是 Bot 令牌,或签发它的 Bot Key 已停用' },
  409. { status: 403, code: 'forbidden', desc: '无权执行该操作' },
  410. { status: 403, code: 'bot_not_allowed', desc: '机器人不对本平台开放' },
  411. { status: 404, code: 'not_found', desc: '资源不存在,或不属于本平台' },
  412. { status: 409, code: 'team_closed', desc: '工作组已关闭' },
  413. { status: 409, code: 'invalid_status', desc: '工作组当前状态不能执行这个操作' },
  414. { status: 409, code: 'already_member', desc: '机器人已经在工作组中' },
  415. { status: 409, code: 'team_busy', desc: '删除时仍有机器人在执行' },
  416. { status: 409, code: 'job_ended', desc: '任务已经结束' },
  417. { status: 409, code: 'job_not_blocked', desc: '任务没有因为限额暂停' },
  418. { status: 409, code: 'task_not_stub', desc: '子任务不在等待替身结果(仅后台)' },
  419. { status: 409, code: 'already_resolved', desc: '交互已被回复、超时或取消' },
  420. { status: 413, code: 'file_too_large', desc: '上传文件超过大小上限' },
  421. { status: 500, code: 'internal_error', desc: '服务器内部错误' },
  422. ]