teams.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. // Bot 工作组(docs/design/13-bot-teams.md):机器人、Bot Key、工作组与工作组事件
  2. import type { FileDto, PermissionMode } from './api'
  3. import type { InteractionRequest, InteractionResolution, ProviderId } from './events'
  4. /** 组长(lead)负责拆分和分派任务;组员(worker)执行具体工作 */
  5. export type BotKind = 'lead' | 'worker'
  6. export interface BotDto {
  7. id: string
  8. name: string
  9. /** 一个 emoji 或一两个字 */
  10. avatar: string
  11. kind: BotKind
  12. /** 岗位描述:擅长什么、不负责什么 */
  13. description: string
  14. profileId: string
  15. enabled: boolean
  16. createdAt: string
  17. updatedAt: string
  18. }
  19. export type BotInput = Pick<BotDto, 'name' | 'avatar' | 'kind' | 'description' | 'profileId' | 'enabled'>
  20. /** Bot API 中可见的机器人信息(不含 Profile 配置) */
  21. export interface BotSummary {
  22. id: string
  23. name: string
  24. avatar: string
  25. kind: BotKind
  26. description: string
  27. provider: ProviderId | null
  28. model: string | null
  29. }
  30. /** 能力卡片:组长据此了解组员会什么(由服务端根据机器人配置生成,docs/design/13-bot-teams.md §5.1) */
  31. export interface BotCard extends BotSummary {
  32. /** Profile 中的技能(name + SKILL.md 中的 description) */
  33. skills: { name: string; description: string }[]
  34. /** 第三方 MCP 服务(内置工具不列出) */
  35. mcpServers: { name: string; title: string; description: string }[]
  36. /** 可访问的资料库名称 */
  37. knowledge: string[]
  38. permissionMode: PermissionMode | null
  39. }
  40. export interface BotKeyDto {
  41. id: string
  42. name: string
  43. prefix: string
  44. enabled: boolean
  45. allowedBotIds: string[]
  46. createdAt: string
  47. lastUsedAt: string | null
  48. }
  49. export type TeamStatus = 'active' | 'paused' | 'closed'
  50. export type TeamBotStatus = 'idle' | 'queued' | 'working' | 'waiting_input'
  51. /** 工作组里的人类成员:由调用方提供的显示身份,AgentServer 不做认证 */
  52. export interface TeamHuman {
  53. id: string
  54. name: string
  55. }
  56. export interface TeamBotDto extends BotSummary {
  57. role: BotKind
  58. status: TeamBotStatus
  59. /** 这个机器人在本工作组中的引擎会话(还没有工作过时为 null) */
  60. sessionId: string | null
  61. joinedAt: string
  62. }
  63. /** 每个任务的限额(docs/design/13-bot-teams.md §7);超出后任务暂停,由成员选择继续或取消 */
  64. export interface TeamLimits {
  65. /** 每个任务的子任务数 */
  66. maxTasksPerJob: number
  67. /** 每个任务中组长的执行轮数 */
  68. maxLeadRunsPerJob: number
  69. /** 每个任务的成本上限(美元),null 表示不限 */
  70. maxCostUsdPerJob: number | null
  71. /** 每个任务的时长上限(分钟) */
  72. maxMinutesPerJob: number
  73. /** 工作组同时执行的 Run 数 */
  74. maxConcurrentRuns: number
  75. /** 机器人会话的上下文超过这个 token 数时,下一轮开始新的会话(组长在两项任务之间);null 表示不限 */
  76. sessionMaxContextTokens: number | null
  77. /** 机器人闲置超过这个小时数后,下一轮开始新的会话;null 表示不限 */
  78. sessionIdleHours: number | null
  79. }
  80. /** 任务级的限额(超出时任务暂停) */
  81. export type TeamLimitKey = Exclude<keyof TeamLimits, 'maxConcurrentRuns' | 'sessionMaxContextTokens' | 'sessionIdleHours'>
  82. /** 调试设置:只有来源为 admin 的工作组(Bot Playground)可以设置 */
  83. export interface TeamDebug {
  84. /** 逐步执行:每次唤醒机器人之前先停下,由管理员点"继续" */
  85. stepMode: boolean
  86. /** 替身组员:分派给它们的子任务不执行,由管理员手动填写结果 */
  87. stubBotIds: string[]
  88. }
  89. /** 逐步执行模式下等待"继续"的一步 */
  90. export interface TeamStepDto {
  91. id: string
  92. botId: string
  93. /** 例如"子任务 #2:清洗数据"或"处理 2 条新消息" */
  94. reason: string
  95. createdAt: string
  96. }
  97. export interface TeamDto {
  98. id: string
  99. title: string
  100. human: TeamHuman
  101. status: TeamStatus
  102. /** Bot Key id 或 'admin' */
  103. source: string
  104. bots: TeamBotDto[]
  105. limits: TeamLimits
  106. debug: TeamDebug
  107. /** 进行中(含因限额暂停)的任务 */
  108. activeJobId: string | null
  109. lastSeq: number
  110. createdAt: string
  111. updatedAt: string
  112. }
  113. export interface TeamInteractionDto {
  114. botId: string
  115. sessionId: string
  116. request: InteractionRequest
  117. }
  118. export interface TeamDetailDto extends TeamDto {
  119. pendingInteractions: TeamInteractionDto[]
  120. pendingSteps: TeamStepDto[]
  121. }
  122. export interface CreateTeamBody {
  123. title?: string
  124. human: TeamHuman
  125. leadBotId?: string
  126. workerBotIds?: string[]
  127. /** 只能调低默认限额 */
  128. limits?: Partial<TeamLimits>
  129. /** 仅 admin */
  130. debug?: Partial<TeamDebug>
  131. }
  132. export interface PatchTeamBody {
  133. title?: string
  134. limits?: Partial<TeamLimits>
  135. /** 仅 admin */
  136. debug?: Partial<TeamDebug>
  137. }
  138. // ---- 任务与子任务(docs/design/13-bot-teams.md §5.2)----
  139. /** blocked:超出限额,等待成员选择继续或取消 */
  140. export type JobStatus = 'running' | 'blocked' | 'completed' | 'failed' | 'cancelled'
  141. export interface JobDto {
  142. id: string
  143. teamId: string
  144. /** 工作组内的序号,从 1 开始 */
  145. no: number
  146. title: string
  147. plan: string
  148. status: JobStatus
  149. /** 完成时的总结或失败、取消的原因 */
  150. summary: string | null
  151. files: TeamFileRef[]
  152. /** 因哪项限额暂停 */
  153. blockedBy: TeamLimitKey | null
  154. /** 已放宽的限额(成员选择"继续"后加倍) */
  155. limits: Record<TeamLimitKey, number | null>
  156. leadRuns: number
  157. taskCount: number
  158. costUsd: number
  159. createdAt: string
  160. endedAt: string | null
  161. }
  162. /** waiting:等待依赖的子任务完成;stub:分派给替身,等待管理员填写结果 */
  163. export type TaskStatus = 'waiting' | 'queued' | 'running' | 'stub' | 'completed' | 'failed' | 'cancelled'
  164. export interface TaskDto {
  165. id: string
  166. jobId: string
  167. teamId: string
  168. /** 工作组内的序号,从 1 开始 */
  169. no: number
  170. botId: string
  171. title: string
  172. instructions: string
  173. /** 依赖的子任务序号 */
  174. dependsOn: number[]
  175. status: TaskStatus
  176. /** 结果或失败原因 */
  177. result: string | null
  178. files: TeamFileRef[]
  179. runId: string | null
  180. sessionId: string | null
  181. costUsd: number
  182. createdAt: string
  183. startedAt: string | null
  184. endedAt: string | null
  185. }
  186. export interface TeamJobsDto {
  187. jobs: JobDto[]
  188. tasks: TaskDto[]
  189. }
  190. /** 替身组员的子任务结果(仅 admin) */
  191. export interface StubTaskBody {
  192. outcome: 'completed' | 'failed'
  193. result: string
  194. fileIds?: string[]
  195. }
  196. export interface TeamUsageRow {
  197. runs: number
  198. inputTokens: number
  199. outputTokens: number
  200. costUsd: number
  201. }
  202. export interface TeamUsageDto {
  203. total: TeamUsageRow
  204. bots: (TeamUsageRow & { botId: string })[]
  205. jobs: (TeamUsageRow & { jobId: string })[]
  206. tasks: (TeamUsageRow & { taskId: string })[]
  207. }
  208. export interface SendTeamMessageBody {
  209. text: string
  210. /** 被 @ 的机器人 id;不 @ 时:有组长交给组长,只有一个组员时交给它,否则返回 400 */
  211. mentions?: string[]
  212. /** 已上传到本工作组的文件 */
  213. fileIds?: string[]
  214. }
  215. export type TeamSender =
  216. | { kind: 'human'; id: string; name: string }
  217. | { kind: 'bot'; botId: string; name: string; avatar: string }
  218. | { kind: 'system' }
  219. export interface TeamFileRef {
  220. fileId: string
  221. path: string
  222. mime: string
  223. size: number
  224. }
  225. /** 消息上附带的卡片:任务、子任务、限额、替身、逐步执行(状态以 jobs / tasks / steps 的最新值为准) */
  226. export type TeamCard =
  227. | { kind: 'job'; jobId: string; event: 'started' | 'completed' | 'failed' | 'cancelled' }
  228. | { kind: 'task'; taskId: string; no: number; event: 'assigned' | 'completed' | 'failed' | 'cancelled'; stub?: boolean }
  229. | { kind: 'limit'; jobId: string; limit: TeamLimitKey }
  230. | { kind: 'stub'; taskId: string; no: number }
  231. | { kind: 'step'; stepId: string }
  232. export interface TeamMessage {
  233. id: string
  234. sender: TeamSender
  235. text: string
  236. mentions: string[]
  237. files: TeamFileRef[]
  238. /** 这条消息唤醒的机器人 */
  239. targets: string[]
  240. /** 机器人消息:产生它的 Run 与会话,用于查看完整执行过程 */
  241. runId?: string
  242. sessionId?: string
  243. /** 系统消息的级别 */
  244. level?: 'info' | 'warning' | 'error'
  245. card?: TeamCard
  246. }
  247. export interface TeamEventPayloads {
  248. 'message.created': TeamMessage
  249. 'bot.status': { botId: string; status: TeamBotStatus; sessionId: string | null; runId: string | null }
  250. 'interaction.required': TeamInteractionDto
  251. 'interaction.resolved': { botId: string; sessionId: string; interactionId: string; resolution: InteractionResolution; answers?: Record<string, string | string[]>; reason?: string }
  252. 'file.changed': { file: FileDto; kind: 'created' | 'modified' }
  253. 'team.updated': TeamDto
  254. 'job.updated': JobDto
  255. 'task.updated': TaskDto
  256. 'step.updated': { steps: TeamStepDto[] }
  257. }
  258. export type TeamEventType = keyof TeamEventPayloads
  259. export interface TeamEventOf<T extends TeamEventType> {
  260. seq: number
  261. type: T
  262. teamId: string
  263. ts: string
  264. payload: TeamEventPayloads[T]
  265. }
  266. export type TeamEvent = { [T in TeamEventType]: TeamEventOf<T> }[TeamEventType]
  267. /** 不落库、只保留在内存缓冲中的事件(状态类,断线续传后以接口返回的最新状态为准) */
  268. export const TRANSIENT_TEAM_EVENT_TYPES: ReadonlySet<TeamEventType> = new Set(['bot.status', 'file.changed', 'team.updated', 'step.updated'])
  269. // ---- 回归用例(仅后台,§12.2) ----
  270. /** 回归用例:成员组成 + 第一条命令。重放时按它新建一个后台工作组并发出命令,结果由人工比对 */
  271. export interface TeamCaseDto {
  272. id: string
  273. name: string
  274. leadBotId: string | null
  275. workerBotIds: string[]
  276. /** 重放时设为替身的组员 */
  277. stubBotIds: string[]
  278. limits: Partial<TeamLimits> | null
  279. humanName: string
  280. text: string
  281. mentions: string[]
  282. files: { name: string; size: number }[]
  283. createdAt: string
  284. updatedAt: string
  285. runCount: number
  286. lastRun: TeamCaseRunDto | null
  287. }
  288. /** 一次重放:对应一个工作组 */
  289. export interface TeamCaseRunDto {
  290. teamId: string
  291. createdAt: string
  292. /** running:还有机器人在工作或在等审批 / 替身结果;done:都空闲了 */
  293. state: 'running' | 'done'
  294. /** 最后一项任务(组长开了任务时) */
  295. job: Pick<JobDto, 'id' | 'no' | 'title' | 'status' | 'summary' | 'files' | 'leadRuns' | 'taskCount' | 'endedAt'> | null
  296. /** 没有任务时:机器人的最后一条回复 */
  297. reply: string | null
  298. runs: number
  299. costUsd: number
  300. }
  301. export interface CreateTeamCaseBody {
  302. name: string
  303. /** 从这个工作组保存:取它的成员组成、替身设置、限额和成员发出的第一条消息 */
  304. teamId: string
  305. }