// Bot 工作组(docs/design/13-bot-teams.md):机器人、Bot Key、工作组与工作组事件 import type { FileDto, PermissionMode } from './api' import type { InteractionRequest, InteractionResolution, ProviderId } from './events' /** 组长(lead)负责拆分和分派任务;组员(worker)执行具体工作 */ export type BotKind = 'lead' | 'worker' export interface BotDto { id: string name: string /** 一个 emoji 或一两个字 */ avatar: string kind: BotKind /** 岗位描述:擅长什么、不负责什么 */ description: string profileId: string enabled: boolean createdAt: string updatedAt: string } export type BotInput = Pick /** Bot API 中可见的机器人信息(不含 Profile 配置) */ export interface BotSummary { id: string name: string avatar: string kind: BotKind description: string provider: ProviderId | null model: string | null } /** 能力卡片:组长据此了解组员会什么(由服务端根据机器人配置生成,docs/design/13-bot-teams.md §5.1) */ export interface BotCard extends BotSummary { /** Profile 中的技能(name + SKILL.md 中的 description) */ skills: { name: string; description: string }[] /** 第三方 MCP 服务(内置工具不列出) */ mcpServers: { name: string; title: string; description: string }[] /** 可访问的资料库名称 */ knowledge: string[] permissionMode: PermissionMode | null } export interface BotKeyDto { id: string name: string prefix: string enabled: boolean allowedBotIds: string[] createdAt: string lastUsedAt: string | null } export type TeamStatus = 'active' | 'paused' | 'closed' export type TeamBotStatus = 'idle' | 'queued' | 'working' | 'waiting_input' /** 工作组里的人类成员:由调用方提供的显示身份,AgentServer 不做认证 */ export interface TeamHuman { id: string name: string } export interface TeamBotDto extends BotSummary { role: BotKind status: TeamBotStatus /** 这个机器人在本工作组中的引擎会话(还没有工作过时为 null) */ sessionId: string | null joinedAt: string } /** 每个任务的限额(docs/design/13-bot-teams.md §7);超出后任务暂停,由成员选择继续或取消 */ export interface TeamLimits { /** 每个任务的子任务数 */ maxTasksPerJob: number /** 每个任务中组长的执行轮数 */ maxLeadRunsPerJob: number /** 每个任务的成本上限(美元),null 表示不限 */ maxCostUsdPerJob: number | null /** 每个任务的时长上限(分钟) */ maxMinutesPerJob: number /** 工作组同时执行的 Run 数 */ maxConcurrentRuns: number /** 机器人会话的上下文超过这个 token 数时,下一轮开始新的会话(组长在两项任务之间);null 表示不限 */ sessionMaxContextTokens: number | null /** 机器人闲置超过这个小时数后,下一轮开始新的会话;null 表示不限 */ sessionIdleHours: number | null } /** 任务级的限额(超出时任务暂停) */ export type TeamLimitKey = Exclude /** 调试设置:只有来源为 admin 的工作组(Bot Playground)可以设置 */ export interface TeamDebug { /** 逐步执行:每次唤醒机器人之前先停下,由管理员点"继续" */ stepMode: boolean /** 替身组员:分派给它们的子任务不执行,由管理员手动填写结果 */ stubBotIds: string[] } /** 逐步执行模式下等待"继续"的一步 */ export interface TeamStepDto { id: string botId: string /** 例如"子任务 #2:清洗数据"或"处理 2 条新消息" */ reason: string createdAt: string } export interface TeamDto { id: string title: string human: TeamHuman status: TeamStatus /** Bot Key id 或 'admin' */ source: string bots: TeamBotDto[] limits: TeamLimits debug: TeamDebug /** 进行中(含因限额暂停)的任务 */ activeJobId: string | null lastSeq: number createdAt: string updatedAt: string } export interface TeamInteractionDto { botId: string sessionId: string request: InteractionRequest } export interface TeamDetailDto extends TeamDto { pendingInteractions: TeamInteractionDto[] pendingSteps: TeamStepDto[] } export interface CreateTeamBody { title?: string human: TeamHuman leadBotId?: string workerBotIds?: string[] /** 只能调低默认限额 */ limits?: Partial /** 仅 admin */ debug?: Partial } export interface PatchTeamBody { title?: string limits?: Partial /** 仅 admin */ debug?: Partial } // ---- 任务与子任务(docs/design/13-bot-teams.md §5.2)---- /** blocked:超出限额,等待成员选择继续或取消 */ export type JobStatus = 'running' | 'blocked' | 'completed' | 'failed' | 'cancelled' export interface JobDto { id: string teamId: string /** 工作组内的序号,从 1 开始 */ no: number title: string plan: string status: JobStatus /** 完成时的总结或失败、取消的原因 */ summary: string | null files: TeamFileRef[] /** 因哪项限额暂停 */ blockedBy: TeamLimitKey | null /** 已放宽的限额(成员选择"继续"后加倍) */ limits: Record leadRuns: number taskCount: number costUsd: number createdAt: string endedAt: string | null } /** waiting:等待依赖的子任务完成;stub:分派给替身,等待管理员填写结果 */ export type TaskStatus = 'waiting' | 'queued' | 'running' | 'stub' | 'completed' | 'failed' | 'cancelled' export interface TaskDto { id: string jobId: string teamId: string /** 工作组内的序号,从 1 开始 */ no: number botId: string title: string instructions: string /** 依赖的子任务序号 */ dependsOn: number[] status: TaskStatus /** 结果或失败原因 */ result: string | null files: TeamFileRef[] runId: string | null sessionId: string | null costUsd: number createdAt: string startedAt: string | null endedAt: string | null } export interface TeamJobsDto { jobs: JobDto[] tasks: TaskDto[] } /** 替身组员的子任务结果(仅 admin) */ export interface StubTaskBody { outcome: 'completed' | 'failed' result: string fileIds?: string[] } export interface TeamUsageRow { runs: number inputTokens: number outputTokens: number costUsd: number } export interface TeamUsageDto { total: TeamUsageRow bots: (TeamUsageRow & { botId: string })[] jobs: (TeamUsageRow & { jobId: string })[] tasks: (TeamUsageRow & { taskId: string })[] } export interface SendTeamMessageBody { text: string /** 被 @ 的机器人 id;不 @ 时:有组长交给组长,只有一个组员时交给它,否则返回 400 */ mentions?: string[] /** 已上传到本工作组的文件 */ fileIds?: string[] } export type TeamSender = | { kind: 'human'; id: string; name: string } | { kind: 'bot'; botId: string; name: string; avatar: string } | { kind: 'system' } export interface TeamFileRef { fileId: string path: string mime: string size: number } /** 消息上附带的卡片:任务、子任务、限额、替身、逐步执行(状态以 jobs / tasks / steps 的最新值为准) */ export type TeamCard = | { kind: 'job'; jobId: string; event: 'started' | 'completed' | 'failed' | 'cancelled' } | { kind: 'task'; taskId: string; no: number; event: 'assigned' | 'completed' | 'failed' | 'cancelled'; stub?: boolean } | { kind: 'limit'; jobId: string; limit: TeamLimitKey } | { kind: 'stub'; taskId: string; no: number } | { kind: 'step'; stepId: string } export interface TeamMessage { id: string sender: TeamSender text: string mentions: string[] files: TeamFileRef[] /** 这条消息唤醒的机器人 */ targets: string[] /** 机器人消息:产生它的 Run 与会话,用于查看完整执行过程 */ runId?: string sessionId?: string /** 系统消息的级别 */ level?: 'info' | 'warning' | 'error' card?: TeamCard } export interface TeamEventPayloads { 'message.created': TeamMessage 'bot.status': { botId: string; status: TeamBotStatus; sessionId: string | null; runId: string | null } 'interaction.required': TeamInteractionDto 'interaction.resolved': { botId: string; sessionId: string; interactionId: string; resolution: InteractionResolution; answers?: Record; reason?: string } 'file.changed': { file: FileDto; kind: 'created' | 'modified' } 'team.updated': TeamDto 'job.updated': JobDto 'task.updated': TaskDto 'step.updated': { steps: TeamStepDto[] } } export type TeamEventType = keyof TeamEventPayloads export interface TeamEventOf { seq: number type: T teamId: string ts: string payload: TeamEventPayloads[T] } export type TeamEvent = { [T in TeamEventType]: TeamEventOf }[TeamEventType] /** 不落库、只保留在内存缓冲中的事件(状态类,断线续传后以接口返回的最新状态为准) */ export const TRANSIENT_TEAM_EVENT_TYPES: ReadonlySet = new Set(['bot.status', 'file.changed', 'team.updated', 'step.updated']) // ---- 回归用例(仅后台,§12.2) ---- /** 回归用例:成员组成 + 第一条命令。重放时按它新建一个后台工作组并发出命令,结果由人工比对 */ export interface TeamCaseDto { id: string name: string leadBotId: string | null workerBotIds: string[] /** 重放时设为替身的组员 */ stubBotIds: string[] limits: Partial | null humanName: string text: string mentions: string[] files: { name: string; size: number }[] createdAt: string updatedAt: string runCount: number lastRun: TeamCaseRunDto | null } /** 一次重放:对应一个工作组 */ export interface TeamCaseRunDto { teamId: string createdAt: string /** running:还有机器人在工作或在等审批 / 替身结果;done:都空闲了 */ state: 'running' | 'done' /** 最后一项任务(组长开了任务时) */ job: Pick | null /** 没有任务时:机器人的最后一条回复 */ reply: string | null runs: number costUsd: number } export interface CreateTeamCaseBody { name: string /** 从这个工作组保存:取它的成员组成、替身设置、限额和成员发出的第一条消息 */ teamId: string }