diff --git a/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.i18n.yaml index a9a502ff18..e9974a024b 100644 --- a/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.i18n.yaml @@ -15,5 +15,5 @@ en: 35323ba83e6e577a zh: 5ec0cf5f2fb04fae /agent-note-team-messages-use-the-target-inbox/consequences: - en: 162f2a2a528fc7d0 - zh: b09e90e1b21cfc3a + en: c734e4546b1d99c8 + zh: 51cb58038a5afabc diff --git a/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.md b/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.md index a6dc2d5e4a..930f3f55e3 100644 --- a/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.md +++ b/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.md @@ -24,4 +24,4 @@ Historical pending messages retain compatibility recovery, receipt inspection, a ## Consequences -New attempts have one identity and one inbox owner. A failed attempt is visible to its caller and has no Team retry promise. Historical logs still cost decoder, projection, and recovery code; new sends do not wait for or trigger that recovery. Tests cover direct Lead/peer acceptance, cold recovery, sender attribution, failures, cancellation, disposal, and historical receipt recovery. +New attempts have one identity and one inbox owner. A failed attempt is visible to its caller and has no Team retry promise. Historical logs still cost decoder, projection, and recovery code; new sends do not directly invoke or await recovery, although cold resume triggers the target’s Agent-creation recovery hook. Tests cover direct Lead/peer acceptance, cold recovery, sender attribution, failures, cancellation, disposal, and historical receipt recovery. diff --git a/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.zh.md b/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.zh.md index e75dacc73c..01f917e40d 100644 --- a/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.zh.md +++ b/.agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.zh.md @@ -24,4 +24,4 @@ Status: implemented ## 后果 -新尝试只有一个身份和一个 inbox owner。失败尝试会向调用方报告,且没有 Team 重试承诺。历史日志仍需要 decoder、投影和恢复代码;新发送既不等待也不触发该恢复。测试覆盖直接 Lead/peer 接收、冷恢复、发送者归属、失败、取消、dispose(资源释放)和历史回执恢复。 +新尝试只有一个身份和一个 inbox owner。失败尝试会向调用方报告,且没有 Team 重试承诺。历史日志仍需要 decoder、投影和恢复代码;新发送不直接调用或等待恢复,但冷恢复会触发目标的 Agent 创建恢复钩子。测试覆盖直接 Lead/peer 接收、冷恢复、发送者归属、失败、取消、dispose(资源释放)和历史回执恢复。 diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index 9982152e55..84cb0ff6b9 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.i18n.yaml @@ -6,5 +6,5 @@ en: 41d772192075e133 zh: 5ef3da69b4de1d8a /capability-seams-and-core-services: - en: 8d7ae3eb1265031d - zh: 970ce559f5f95a65 + en: 9fc7d396f78e95c7 + zh: d06b4797d3016efc diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 093c5852e5..03d74df234 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -650,7 +650,7 @@ flowchart LR | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route. | | `ctx.speechToText` | `seam` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | Routes explicit recognizers; the browser uses the authenticated Remote and keeps transcripts in the draft until submission. | -| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | Owns the implicit-root roster, durable peer mailbox, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes model controls. | +| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | Owns the implicit-root roster, direct peer inbox delivery, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes model controls. | | `ctx.inspector` | `core` | `inspector` | - | - | - | Owns the Worker-hosted CDP target and the transport-independent Host and Client observation and Cordis-tree query API. | | `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | Producers (background bash/pwsh, PTY sends, and subagent delegations) register running work; record-declaring jobs additionally stream raw output for non-consuming observers; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry. | | `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index 4e41705687..0b82198975 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -652,7 +652,7 @@ flowchart LR | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 提供方实现传输;该服务还负责可选的、基于 Activation 的延续编排,tool-subagent 选择一次性或可延续委派,tool-subagent-control 传递后续消息,而 tool-ralph 要求一条全新的结构化输出路由。 | | `ctx.speechToText` | `seam` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | 路由显式选择的识别器;浏览器使用带认证的 Remote,并在提交前将转写保留在草稿中。 | -| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | 负责隐式 Root roster、持久 peer mailbox、共享任务 DAG 与 continuable child 生命周期;tool-agent-team 提供模型控制工具。 | +| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | 负责隐式 Root roster、直接 peer inbox 投递、共享任务 DAG 与 continuable child 生命周期;tool-agent-team 提供模型控制工具。 | | `ctx.inspector` | `core` | `inspector` | - | - | - | 负责 Worker 托管的 CDP target,以及独立于传输的 Host 和 Client observation 与 Cordis tree query API。 | | `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | 生产方(后台 bash/pwsh、PTY 发送和 subagent 委派)登记正在运行的工作;声明 record 的 job 还为非消费观察者流式提供原始输出;tool-jobs 是面向模型的控制器,用于读取、列出和终止这些工作;jobs-local 是进程本地注册表。 | | `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | 搜索和抓取提供方注册到同一个 ctx.web seam;tool-web 负责稳定的面向模型名称。 | diff --git a/docs/config-catalog.md b/docs/config-catalog.md index e596385c67..461ca9d39e 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -818,7 +818,7 @@ export interface Config { readonly maxTasks?: number /** Maximum UTF-8 bytes in one complete sender-framed delivery. */ readonly maxMessageBytes?: number - /** Maximum milliseconds allowed for Team-owned runtime disposal. */ + /** Maximum milliseconds for shared operation settlement and for each Team child drain. */ readonly disposalTimeoutMs?: number } ``` diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 548cc55289..b3ead74141 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -820,7 +820,7 @@ export interface Config { readonly maxTasks?: number /** Maximum UTF-8 bytes in one complete sender-framed delivery. */ readonly maxMessageBytes?: number - /** Maximum milliseconds allowed for Team-owned runtime disposal. */ + /** Maximum milliseconds for shared operation settlement and for each Team child drain. */ readonly disposalTimeoutMs?: number } ``` diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index c7b095aa0b..ad7452d86d 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -982,7 +982,7 @@ Source: [`packages/experimental/agent-team/src/types.ts:230`](../packages/experi #### `team/message/delivered` — log-only ```ts persistence-catalog -/** Durable acknowledgement that the target Session recorded the message. */ +/** Compatibility acknowledgement that the target Session recorded a historical message. */ 'team/message/delivered': { version: 2 teamId: TeamId @@ -1000,7 +1000,7 @@ Source: [`packages/experimental/agent-team/src/types.ts:236`](../packages/experi #### `team/message/queued` — log-only ```ts persistence-catalog -/** Durable mailbox enqueue, stored before delivery is attempted. */ +/** Historical mailbox enqueue; new sends use the target Agent inbox. */ 'team/message/queued': { version: 2; teamId: TeamId; message: TeamMessageSnapshot } ``` diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 62cf274074..8a0ea0641d 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -984,7 +984,7 @@ export type SessionEvent = { #### `team/message/delivered` — log-only ```ts persistence-catalog -/** Durable acknowledgement that the target Session recorded the message. */ +/** Compatibility acknowledgement that the target Session recorded a historical message. */ 'team/message/delivered': { version: 2 teamId: TeamId @@ -1002,7 +1002,7 @@ export type SessionEvent = { #### `team/message/queued` — log-only ```ts persistence-catalog -/** Durable mailbox enqueue, stored before delivery is attempted. */ +/** Historical mailbox enqueue; new sends use the target Agent inbox. */ 'team/message/queued': { version: 2; teamId: TeamId; message: TeamMessageSnapshot } ``` diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml index 7051478b51..2e9baf3603 100644 --- a/docs/subsystems/README.i18n.yaml +++ b/docs/subsystems/README.i18n.yaml @@ -3,5 +3,5 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/README.md /subsystems: - en: ab6cd11804f6b644 - zh: 7a82dc5b22c40703 + en: 448d009bdb9533ff + zh: f7806ef67ee4001c diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md index e2f8dc51e2..96b0640ee8 100644 --- a/docs/subsystems/README.md +++ b/docs/subsystems/README.md @@ -47,7 +47,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [compaction.md](compaction.md) | the compaction seam: the `compaction/*` session events, `CompactionResult`, the `CompactionEngine` interface | | [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split | | [voice-input.md](voice-input.md) | experimental named recognizers, transient audio and revision-guarded draft insertion | -| [agent-team.md](agent-team.md) | Agent Teams: implicit Lead identity, named continuable teammates, durable peer mailbox, and shared task DAG | +| [agent-team.md](agent-team.md) | Agent Teams: implicit Lead identity, named continuable teammates, direct peer inbox delivery, and shared task DAG | | [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider availability, `WebError` | | [spill.md](spill.md) | the spill storage seam: `SaveTextSpill`, `SpillOwner`/`SpillSource`, `SpillRef`, the branded `SpillLocator` | | [workflow.md](workflow.md) | the workflow seam: `WorkflowStartRequest`, `WorkflowMeta`, `WorkflowRun`/`Result`, the `workflow/*` event payloads, `WorkflowError` fatality | diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md index 31f4489d43..77349af142 100644 --- a/docs/subsystems/README.zh.md +++ b/docs/subsystems/README.zh.md @@ -47,7 +47,7 @@ | [compaction.md](compaction.zh.md) | 压缩(compaction)seam:`compaction/*` 会话事件、`CompactionResult`、`CompactionEngine` 接口 | | [subagent.md](subagent.zh.md) | subagent seam:命名提供方注册表、`SubagentStartRequest`/`Result`/`Run`、启动时与运行时能力拆分 | | [voice-input.md](voice-input.zh.md) | 实验性具名语音识别、临时音频和受版本保护的草稿插入 | -| [agent-team.md](agent-team.zh.md) | Agent Teams:隐式 Lead 身份、具名 continuable teammate、持久 peer mailbox 与共享任务 DAG | +| [agent-team.md](agent-team.zh.md) | Agent Teams:隐式 Lead 身份、具名 continuable teammate、直接 peer inbox 投递 与共享任务 DAG | | [web.md](web.zh.md) | Web 访问 seam:`WebSearchRequest`/`Result`、`WebFetchRequest`/`Result`、`WebFetchBody`、提供方可用性、`WebError` | | [spill.md](spill.zh.md) | spill 存储 seam:`SaveTextSpill`、`SpillOwner`/`SpillSource`、`SpillRef`、品牌类型 `SpillLocator` | | [workflow.md](workflow.zh.md) | 工作流 seam:`WorkflowStartRequest`、`WorkflowMeta`、`WorkflowRun`/`Result`、`workflow/*` 事件载荷、`WorkflowError` 致命性 | diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index dca78d5b4b..06fb6e43a5 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.i18n.yaml @@ -9,8 +9,8 @@ en: 6cc2cd9ced70bb09 zh: 1c0e6cb4ba2ed637 /tool-schema-catalog/tool-package-map: - en: 72e2a375567f5850 - zh: b51b8ea5b52814fd + en: 0616f479d8bfd8e2 + zh: 3625cb279093d6a5 /tool-schema-catalog/deepseek-ai-dsh-plugin-manager: en: e0143916bb634762 zh: e0143916bb634762 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 92bc66c4f9..1781e688cb 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -40,7 +40,7 @@ This table connects model-visible tool names to the plugin package and service s | `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`, `subagent` | `ctx.tools`, `ctx.subagents`, `ctx.systemPrompt`, `ctx.llm for model discovery and selected-route validation` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered delegation name is the load-time `toolName` config (default `subagent`); the default schema above has model selection off, while the discovery schema is shown as the fixed companion available in an enabled Session. Web presets sample the Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Each instance independently controls whether it reads model-selection settings and its background behavior through `modelSelectionSettings`, `backgroundMode`, and `enableRunInBackground`. | | `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`, `list_agents`, `send_message` | `ctx.tools`, `ctx.subagents`, `ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`, `tool/result`, `child session events through ctx.subagents` | - | The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` and `interrupt_agent` once, plus `list_agents` from its separately loaded `/list-agents` plugin (whose catalog rows use the sessionProjections and live Agent registries). | | `@deepseek-ai/dsh-tool-jobs` | `job_kill`, `job_list`, `job_output` | `ctx.tools`, `ctx.jobs`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `user/message via agent.inject() for background completion notices` | - | The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. | -| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`, `list_agents`, `send_message`, `spawn_teammate`, `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update`, `wait_agent` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `an exact live Team member Agent` | `tool/call`, `team/member`, `team/message/queued`, `team/message/delivered`, `team/task`, `tool/result` | - | All nine tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. | +| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`, `list_agents`, `send_message`, `spawn_teammate`, `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update`, `wait_agent` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `an exact live Team member Agent` | `tool/call`, `team/member`, `team/task`, `agent/inbox/spliced`, `user/message on inbox claim`, `tool/result` | - | All nine tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. | | `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist. `allowParallelInProgress` is required with no default, so the catalog states its choice: `true`, whose description invites several `in_progress` items. A deployment choosing `false` receives the same tool with a description asking for exactly one active task. | | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflowEngine`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-tool-workspace-dependencies` | `load_workspace_dependencies` | `ctx.tools` | `tool/call`, `tool/result` | - | - | diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index f4faf3b8a8..970538449c 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -44,7 +44,7 @@ | `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`、`subagent` | `ctx.tools`、`ctx.subagents`、`ctx.systemPrompt`、`用于模型发现和所选路由校验的 ctx.llm` | `tool/call`、`tool/result`、`child session events through the chosen provider` | `subagent`、`subagent_fork` | 注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述默认 schema 关闭模型选择,而发现 schema 则展示为已启用 Session 中可用的固定配套工具。Web preset 会在每个新顶层 Session 创建时读取插件页偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。每个实例通过 `modelSelectionSettings`、`backgroundMode` 与 `enableRunInBackground` 独立控制是否读取模型选择设置及其后台行为。 | | `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`、`list_agents`、`send_message` | `ctx.tools`、`ctx.subagents`、`ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`、`tool/result`、`child session events through ctx.subagents` | - | 这些是控制可继续后台 subagent 的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message` 和 `interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。 | | `@deepseek-ai/dsh-tool-jobs` | `job_kill`、`job_list`、`job_output` | `ctx.tools`、`ctx.jobs`、`ctx.systemPrompt` | `tool/call`、`tool/result`、`user/message via agent.inject() for background completion notices` | - | 与任务种类无关的后台任务控制器:后台 bash 命令、PTY 发送和 subagent 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 | -| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`、`list_agents`、`send_message`、`spawn_teammate`、`team_task_create`、`team_task_get`、`team_task_list`、`team_task_update`、`wait_agent` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agentTeams`、`an exact live Team member Agent` | `tool/call`、`team/member`、`team/message/queued`、`team/message/delivered`、`team/task`、`tool/result` | - | 这 9 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 | +| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`、`list_agents`、`send_message`、`spawn_teammate`、`team_task_create`、`team_task_get`、`team_task_list`、`team_task_update`、`wait_agent` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agentTeams`、`an exact live Team member Agent` | `tool/call`、`team/member`、`team/task`、`agent/inbox/spliced`、`user/message on inbox claim`、`tool/result` | - | 这 9 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 | | `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`、`owning Agent session` | `tool/call`、`todo/write`、`tool/result` | - | todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为检查清单。`allowParallelInProgress` 是没有默认值的必填项,因此本目录明确选择 `true`,对应描述允许同时存在多个 `in_progress` 项。选择 `false` 的部署会获得同一工具,但描述会要求只能有 1 个活动任务。 | | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`、`ctx.workflowEngine`、`ctx.systemPrompt`、`a calling Agent (exec.agent parents the script children)` | `tool/call`、`tool/result` | - | - | | `@deepseek-ai/dsh-tool-workspace-dependencies` | `load_workspace_dependencies` | `ctx.tools` | `tool/call`, `tool/result` | - | - | diff --git a/packages/client/ui-chat/README.i18n.yaml b/packages/client/ui-chat/README.i18n.yaml index 43fe0e4ac9..f84b4c7633 100644 --- a/packages/client/ui-chat/README.i18n.yaml +++ b/packages/client/ui-chat/README.i18n.yaml @@ -18,8 +18,8 @@ en: 3fda3d75483ce7c1 zh: 4d96ba654f70c021 /deepseek-ai-dsh-client-ui-chat/hidden-chat-rows: - en: 5706ae03ceacf251 - zh: 2e59d0815351349f + en: b502d2a006a36be5 + zh: 1a191a628129ca8c /deepseek-ai-dsh-client-ui-chat/command-and-failure-rows: en: 2157fcd6b0d100b5 zh: 9e865ac3953ebe30 diff --git a/packages/client/ui-chat/README.md b/packages/client/ui-chat/README.md index b3343fd9c3..050ff3417c 100644 --- a/packages/client/ui-chat/README.md +++ b/packages/client/ui-chat/README.md @@ -40,7 +40,7 @@ Settings → General → Open chat links in selects the destination for ordinary ## Hidden Chat rows -Chat omits system-prompt, ordinary Context injection, and `permission` command rows in every work-details mode. Context containing tool additions or removals remains visible. The filter changes neither recorded Session events nor Trajectory inspection. Non-human Turn triggers remain independent notices; other command rows remain in Chat. +Chat omits system-prompt, ordinary Context injection, and `permission` command rows in every work-details mode. Context containing tool additions or removals remains visible. The filter changes neither recorded Session events nor Trajectory inspection. Non-human Turn triggers remain independent notices; Agent relays whose first text block is the exact `Team message from :` framing retain the Team title and icon; other command rows remain in Chat. When an Assistant attempt retires without a visible message, Chat hides its already-published Node instead of removing its key. A retry in the same Step reuses that key when visible content returns. This also applies when the loaded window lacks the Step start. diff --git a/packages/client/ui-chat/README.zh.md b/packages/client/ui-chat/README.zh.md index b524f85580..22ecda6d8c 100644 --- a/packages/client/ui-chat/README.zh.md +++ b/packages/client/ui-chat/README.zh.md @@ -40,7 +40,7 @@ Chat 在节点列表外通过一个 `MarkdownDelegateProvider` 提供文件及 H ## Chat 隐藏的行 -Chat 在所有工作步骤展示模式下都不显示系统提示词行、普通上下文注入和 `permission` 命令行。包含工具添加或移除记录的上下文仍然可见。该过滤不改变已记录的 Session 事件或 Trajectory 查看能力。非人工轮次触发仍作为独立通知显示,其他命令行仍保留在 Chat 中。 +Chat 在所有工作步骤展示模式下都不显示系统提示词行、普通上下文注入和 `permission` 命令行。包含工具添加或移除记录的上下文仍然可见。该过滤不改变已记录的 Session 事件或 Trajectory 查看能力。非人工轮次触发仍作为独立通知显示;首个文本块完全匹配 `Team message from :` 前缀的 Agent relay 保留 Team 标题和图标;其他命令行仍保留在 Chat 中。 Assistant 尝试结束且没有可见消息时,Chat 隐藏已发布的 Node,不移除其 key。同一 Step 的重试再次产生可见内容时,复用该 key。已加载窗口缺少 Step 起点时也遵循此规则。 diff --git a/packages/client/ui-chat/src/client/chat/turn-trigger.ts b/packages/client/ui-chat/src/client/chat/turn-trigger.ts index 0e560572e2..8cf5106a48 100644 --- a/packages/client/ui-chat/src/client/chat/turn-trigger.ts +++ b/packages/client/ui-chat/src/client/chat/turn-trigger.ts @@ -32,10 +32,14 @@ export function turnTriggerDetails(node: ContextMessageNode): { icon = 'goal' break } - case 'agent-message': - title = 'message.trigger.agent' - icon = 'agent' + case 'agent-message': { + const first = node.content[0] + const team = field(source, 'form') === 'relay' && first?.type === 'text' + && /^Team message from [a-z0-9]+(?:-[a-z0-9]+)*:$/u.test(first.text) + title = team ? 'message.trigger.team' : 'message.trigger.agent' + icon = team ? 'team' : 'agent' break + } case 'team-message': title = 'message.trigger.team' icon = 'team' diff --git a/packages/client/ui-chat/tests/turn-trigger.client.spec.tsx b/packages/client/ui-chat/tests/turn-trigger.client.spec.tsx index 851111fd14..965058db0f 100644 --- a/packages/client/ui-chat/tests/turn-trigger.client.spec.tsx +++ b/packages/client/ui-chat/tests/turn-trigger.client.spec.tsx @@ -6,7 +6,7 @@ import type { ChatNode } from '../src/client/contract/chat-nodes.ts' import { TurnTriggerNodeView } from '../src/client/chat/TurnTriggerNodeView.tsx' import { turnTriggerDetails } from '../src/client/chat/turn-trigger.ts' import { contextForm, contextProducer } from '../src/client/conversation-nodes/event-projection.ts' -import { en } from '../src/client/locale.ts' +import { en, zh } from '../src/client/locale.ts' afterEach(cleanup) @@ -45,6 +45,30 @@ describe('Turn trigger notices', () => { expect(view.container.querySelector('time')?.dateTime).toBe('2023-11-14T22:13:20.000Z') }) + it.each([ + [en, 'Team message received'], + [zh, '收到团队消息'], + ])('preserves the Team title and icon for a direct inbox relay', (locale, title) => { + const node = trigger({ kind: 'agent-message', form: 'relay', senderSessionId: 'sender' }) + node.data.content = [{ type: 'text', text: 'Team message from reviewer-2:' }, { type: 'text', text: 'Ready.' }] + expect(turnTriggerDetails(node.data)).toEqual({ title: 'message.trigger.team', icon: 'team' }) + const view = render() + expect(view.getByRole('button').textContent).toContain(title) + fireEvent.click(view.getByRole('button')) + expect(view.container.querySelector('[data-context-text]')?.textContent).toContain('Team message from reviewer-2:') + }) + + it.each([ + [{ kind: 'agent-message', form: 'notice' }, ['Team message from reviewer:']], + [{ kind: 'agent-message', form: 'relay' }, ['Ordinary message', 'Team message from reviewer:']], + [{ kind: 'agent-message', form: 'relay' }, ['Team message from reviewer: extra text']], + [{ kind: 'agent-message', form: 'relay' }, []], + ])('keeps other Agent messages in the generic presentation', (source, texts) => { + const node = trigger(source) + node.data.content = texts.map(text => ({ type: 'text', text })) + expect(turnTriggerDetails(node.data)).toEqual({ title: 'message.trigger.agent', icon: 'agent' }) + }) + it('opens the recorded notice body and closes it independently of the Turn', () => { const view = render() const button = view.getByRole('button') diff --git a/packages/experimental/agent-team/README.i18n.yaml b/packages/experimental/agent-team/README.i18n.yaml index c320067bd5..b5137164e4 100644 --- a/packages/experimental/agent-team/README.i18n.yaml +++ b/packages/experimental/agent-team/README.i18n.yaml @@ -48,11 +48,11 @@ en: 39fc445dd624b453 zh: 26dbdcb0067fbb07 /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/team-identity-and-roster: - en: 85f97723e45e43de - zh: def66721e21339cc + en: 2389048c1a494ed3 + zh: 7e72acd6b83ea248 /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/direct-messages-and-historical-compatibility: - en: d48f93263041ba66 - zh: ff0fb3d96e019953 + en: 43d69c3e5f597a88 + zh: c776f66aae7c0950 /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/shared-task-board: en: 2dc37ba97a8a7178 zh: 96bd24033030e19d @@ -60,11 +60,11 @@ en: 29eebf85f25d8d8b zh: 6976a482f65b4989 /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/durability-model: - en: 0407607c0cd39bb6 - zh: 089ef28dad527f09 + en: f5161fff909de4db + zh: a463315d58f05c75 /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/disposal: - en: 2276823a7a1b65a7 - zh: 4f4970a93fabe0be + en: daefac636194f387 + zh: 59420c5c2fbd5d52 /deepseek-ai-dsh-experimental-agent-team/further-exploration: en: 2bbfd4ffbaeedd00 zh: d1afb5d320b469a1 diff --git a/packages/experimental/agent-team/README.md b/packages/experimental/agent-team/README.md index aed84b018d..cf20ef071a 100644 --- a/packages/experimental/agent-team/README.md +++ b/packages/experimental/agent-team/README.md @@ -125,11 +125,11 @@ The [Agent Teams Agent Note](../../../.agents/notes/implemented/feature/2026-08- ### Team identity and roster -Every ordinary runtime root is the implicit Lead of a Team whose `TeamId` equals its `SessionId`; there is no creation event, and durable state begins with the first member, message, or task record. `spawnTeammate()` first appends and flushes a `provisioning` member record, then asks the configured provider to create the reserved child; a provider failure appends a durable `failed` member. A fresh child starts with no Lead history; a fork child captures the Lead's completed-turn prefix once. Recovery reconciles an unterminated provisioning record against the child's independently persisted Session: a matching direct-parent and continuable descriptor plus a recorded initial user message produces `active`, and anything else produces `failed`. If recovery wins a same-process race, the creator accepts the terminal state or reports `TEAM_PROVISIONING_CONFLICT` and drains the child. Names are reserved by the first provisioning record and never reused. +Every ordinary runtime root is the implicit Lead of a Team whose `TeamId` equals its `SessionId`; there is no creation event, and durable Team state begins with the first member or task record. `spawnTeammate()` first appends and flushes a `provisioning` member record, then asks the configured provider to create the reserved child; a provider failure appends a durable `failed` member. A fresh child starts with no Lead history; a fork child captures the Lead's completed-turn prefix once. Recovery reconciles an unterminated provisioning record against the child's independently persisted Session: a matching direct-parent and continuable descriptor plus a recorded initial user message produces `active`, and anything else produces `failed`. If recovery wins a same-process race, the creator accepts the terminal state or reports `TEAM_PROVISIONING_CONFLICT` and drains the child. Names are reserved by the first provisioning record and never reused. ### Direct messages and historical compatibility -`sendMessage()` checks exact caller membership, rejects self-messaging, and bounds the complete sender-framed UTF-8 content. It returns the existing inbox identity and emits Team activity only after acceptance. The target stores `agent-message` source with the actual `senderSessionId`; the first content block is `Team message from :`. No new `team/message/queued` or `team/message/delivered` records are written for these sends. +`sendMessage()` checks exact caller membership, rejects self-messaging, and bounds the complete sender-framed UTF-8 content. It returns the existing inbox identity and emits Team activity only after acceptance. The target stores `agent-message` source with the actual `senderSessionId`; the first content block is `Team message from :`. No new `team/message/queued` or `team/message/delivered` records are written for these sends. Team adds no per-target send ordering or historical catch-up barrier; delivery order belongs to the target inbox. The client recognizes the recorded Team framing for its message title and icon. Lead delivery calls `Agent.steer()` directly. Teammate delivery uses the continuation owner's host-only Steer path, which preserves the Team sender source while authorizing the Lead-to-child edge and cold-resuming inactive targets. Sibling messages never impersonate the Lead through the public adjacent-Agent messaging operation. @@ -143,15 +143,15 @@ Tasks are complete versioned snapshots; every mutation carries `expectedRevision ### Durability model -Team events are appended to the exact live Lead Session and flushed before the operation reports success or wakes waiters. `team/member`, `team/task`, `team/message/queued`, and `team/message/delivered` are log-only: they never enter the conversation surface, so derived model history is untouched by coordination records. Session event `seq` and `time` own ordering and timing; snapshots do not duplicate them. The `./invariant` companion replays each candidate Team event against its committed prefix and rejects invalid transitions before append. +Roster and task mutations append `team/member` and `team/task` to the exact live Lead Session and flush before reporting success or waking waiters. Historical `team/message/queued` records remain readable; compatibility recovery writes `team/message/delivered` only after confirming a persisted target receipt. All four event types are log-only: they never enter the conversation surface, so derived model history is untouched by coordination records. Session event `seq` and `time` own ordering and timing; snapshots do not duplicate them. The `./invariant` companion replays each candidate Team event against its committed prefix and rejects invalid transitions before append. -Historical mailbox events and `team-message` sources remain readable. Plugin load and Agent creation drain old queued-minus-delivered records through Steer, checking target history and pending inbox before retrying and flushing target receipts before legacy acknowledgements. Failed inspection or delivery logs a warning and leaves the old item pending for a later recovery pass; new sends neither trigger nor wait for this drain. Recovery passes serialize per Team and participate in disposal. +Historical mailbox events and `team-message` sources remain readable. Plugin load schedules recovery for loaded members. Lead creation drains all old queued-minus-delivered records; teammate creation drains only records addressed to that teammate. Recovery checks target history and pending inbox before retrying and flushes target receipts before legacy acknowledgements. A failed item blocks later old items for the same target in that pass. Failures warn and retain pending data until a later load or Agent creation; there is no receipt listener or periodic retry. New sends do not directly invoke or await recovery; cold resume still triggers the target’s Agent-creation hook. Recovery passes serialize per Team and participate in disposal. Mailbox projection and checkpoint admission preserve every decoded JSON field of accepted content outside the locally declared validators, including an own `__proto__` key. Local field checks cover `text`, `reasoning`, `image`, and `tool-call`; accepted unknown tags remain opaque. Team projection cache version 4 rebuilds checkpoints from earlier cache versions from the Session log; the Session format version is unchanged. ### Disposal -Disposal closes admission, aborts and awaits admitted sends, creations, and legacy recovery, then releases the roster’s live children and descendants. Non-Team children remain untouched. Cleanup failures are visible and bounded by `disposalTimeoutMs`. +Disposal closes admission, aborts and awaits admitted sends, creations, and legacy recovery, then releases the roster’s live children and descendants. Non-Team children remain untouched. Admitted operations share one `disposalTimeoutMs` settlement deadline; each subsequent Team child drain has its own deadline. This value is not a whole-service shutdown bound. Cleanup failures are reported. diff --git a/packages/experimental/agent-team/README.zh.md b/packages/experimental/agent-team/README.zh.md index 279e329f6f..5e1a69cf8b 100644 --- a/packages/experimental/agent-team/README.zh.md +++ b/packages/experimental/agent-team/README.zh.md @@ -125,11 +125,11 @@ Lead 可以停止 teammate 的当前轮次,而不会删除其排队的消息 ### Team 身份与 roster -每个普通运行时 root 都是一个隐式 Team 的 Lead,其 `TeamId` 等于 `SessionId`;不存在创建事件,持久状态从第一条成员、消息或任务记录开始。`spawnTeammate()` 先追加并 flush 一条 `provisioning` 成员记录,再要求配置的提供方创建预留 child;提供方失败会追加一条持久的 `failed` 成员。fresh child 不携带 Lead 历史;fork child 只捕获一次 Lead 的已完成 turn 前缀。恢复把未终结的 provisioning 记录对照 child 独立持久化的会话进行对账:直接 parent 与 continuable descriptor 匹配、且初始用户消息已记录则产生 `active`,其他任何情况都产生 `failed`。如果恢复在同进程竞争中先完成,creator 会接受终态,或报告 `TEAM_PROVISIONING_CONFLICT` 并 drain 该 child。名字由第一条 provisioning 记录保留,且永不复用。 +每个普通运行时 root 都是一个隐式 Team 的 Lead,其 `TeamId` 等于 `SessionId`;不存在创建事件,持久 Team 状态从第一条成员或任务记录开始。`spawnTeammate()` 先追加并 flush 一条 `provisioning` 成员记录,再要求配置的提供方创建预留 child;提供方失败会追加一条持久的 `failed` 成员。fresh child 不携带 Lead 历史;fork child 只捕获一次 Lead 的已完成 turn 前缀。恢复把未终结的 provisioning 记录对照 child 独立持久化的会话进行对账:直接 parent 与 continuable descriptor 匹配、且初始用户消息已记录则产生 `active`,其他任何情况都产生 `failed`。如果恢复在同进程竞争中先完成,creator 会接受终态,或报告 `TEAM_PROVISIONING_CONFLICT` 并 drain 该 child。名字由第一条 provisioning 记录保留,且永不复用。 ### 直接消息与历史兼容 -`sendMessage()` 检查确切调用方的成员身份,拒绝自发消息,并限制包含发送者前缀的完整 UTF-8 内容大小。它返回现有 inbox 身份,并仅在接收后发出 Team activity。目标保存带真实 `senderSessionId` 的 `agent-message` source;首个内容块为 `Team message from :`。这些发送不写入新的 `team/message/queued` 或 `team/message/delivered` 记录。 +`sendMessage()` 检查确切调用方的成员身份,拒绝自发消息,并限制包含发送者前缀的完整 UTF-8 内容大小。它返回现有 inbox 身份,并仅在接收后发出 Team activity。目标保存带真实 `senderSessionId` 的 `agent-message` source;首个内容块为 `Team message from :`。这些发送不写入新的 `team/message/queued` 或 `team/message/delivered` 记录。Team 不额外保证同目标发送顺序,也不等待历史消息补送;投递顺序由目标 inbox 管理。客户端识别已记录的 Team 前缀,以展示对应消息标题和图标。 投递给 Lead 时直接调用 `Agent.steer()`。投递给 teammate 时使用 continuation owner 的 host-only Steer 路径;该路径会保留 Team 发送者 source,同时授权 Lead-to-child edge 并冷恢复 inactive target。sibling 消息绝不会通过公开的相邻 Agent 消息操作伪装成 Lead。 @@ -143,15 +143,15 @@ Lead 可以停止 teammate 的当前轮次,而不会删除其排队的消息 ### 持久性模型 -Team 事件追加到精确的 live Lead 会话,并在操作报告成功或唤醒等待者之前 flush。`team/member`、`team/task`、`team/message/queued` 与 `team/message/delivered` 仅存在于日志:它们从不进入会话表面,因此派生模型历史不受协作记录影响。顺序与时间由会话事件的 `seq` 与 `time` 负责,快照不重复保存。`./invariant` 伴生插件把每条候选 Team 事件对照已提交前缀回放,并在 append 前拒绝非法转换。 +roster 与任务变更将 `team/member` 和 `team/task` 追加到精确的 live Lead 会话,并在报告成功或唤醒等待者之前 flush。历史 `team/message/queued` 记录仍可读取;兼容恢复仅在确认目标回执已持久化后写入 `team/message/delivered`。这四种事件仅存在于日志:它们从不进入会话表面,因此派生模型历史不受协作记录影响。顺序与时间由会话事件的 `seq` 与 `time` 负责,快照不重复保存。`./invariant` 伴生插件把每条候选 Team 事件对照已提交前缀回放,并在 append 前拒绝非法转换。 -历史 mailbox 事件与 `team-message` source 仍可读取。插件加载和 Agent 创建通过 Steer 补送旧 queued-minus-delivered 记录;重试前检查目标历史与 pending inbox,旧确认前 flush 目标回执。检查或投递失败会记录警告,并将旧项保留为 pending,等待后续恢复;新发送既不触发也不等待此补送。恢复按 Team 串行执行,并参与 dispose 等待。 +历史 mailbox 事件与 `team-message` source 仍可读取。插件加载为已加载的成员安排恢复。Lead 创建补送全部旧 queued-minus-delivered 记录;teammate 创建仅补送发给该 teammate 的记录。恢复在重试前检查目标历史与 pending inbox,并在旧确认前 flush 目标回执。一项失败会阻止本轮中发给同一目标的后续旧项。失败会记录警告,并保留 pending 数据,直到后续加载或 Agent 创建;没有收据监听器或定时重试。新发送不直接调用或等待恢复;冷恢复仍会触发目标的 Agent 创建钩子。恢复按 Team 串行执行,并参与 dispose 等待。 Mailbox 投影与 checkpoint 准入保留本地声明的校验器之外获准内容中全部已解码 JSON 字段,包括自有 `__proto__` 键。本地字段检查覆盖 `text`、`reasoning`、`image` 和 `tool-call`;获准的未知标签保持不透明。Team 投影缓存版本 4 从 Session 日志重建较早缓存版本的 checkpoint;Session 格式版本保持不变。 ### Dispose -dispose 关闭准入,中止并等待已获准的发送、创建和旧消息恢复,然后释放 roster 中的 live child 及其后代。非 Team child 不受影响。清理失败会明确报告,并受 `disposalTimeoutMs` 限制。 +dispose 关闭准入,中止并等待已获准的发送、创建和旧消息恢复,然后释放 roster 中的 live child 及其后代。非 Team child 不受影响。已获准操作共享一个 `disposalTimeoutMs` 等待期限;随后每个 Team 的 child drain 各有独立期限。此值不是整个服务关闭的总时限。清理失败会明确报告。 diff --git a/packages/experimental/agent-team/src/index.ts b/packages/experimental/agent-team/src/index.ts index d5456da33c..9c8c41e93e 100644 --- a/packages/experimental/agent-team/src/index.ts +++ b/packages/experimental/agent-team/src/index.ts @@ -277,9 +277,11 @@ export class TeamService extends Service { this.activity.close() const failures: unknown[] = [] - await this.lifecycle.settle(this.lifecycle.pending(), failures) - await this.lifecycle.settle(this.roster.pendingCreations(), failures) - await this.lifecycle.settle(this.legacyDelivery.pendingDispatches(), failures) + await this.lifecycle.settle([ + ...this.lifecycle.pending(), + ...this.roster.pendingCreations(), + ...this.legacyDelivery.pendingDispatches(), + ], failures) for (const [root, childIds] of this.roster.liveChildrenByRoot()) { try { await this.roster.stopTeammates(root, childIds) diff --git a/packages/experimental/agent-team/src/legacy-delivery.ts b/packages/experimental/agent-team/src/legacy-delivery.ts index f4924e2134..e4e29015df 100644 --- a/packages/experimental/agent-team/src/legacy-delivery.ts +++ b/packages/experimental/agent-team/src/legacy-delivery.ts @@ -42,12 +42,15 @@ export class LegacyTeamDelivery { const existing = this.recoveries.get(root.id) if (existing !== undefined) { await existing + await this.recoverFor(agent, signal) return } const operation = Promise.resolve().then(async () => { const state = this.journal.state(root) + // A failed old message must not be overtaken by later messages to its target. const blockedTargets = new Set() for (const message of state.messages) { + if (membership.role !== 'lead' && message.targetId !== agent.id) continue if (state.delivered.includes(message.id) || blockedTargets.has(message.targetId)) continue signal.throwIfAborted() if (!await this.dispatchOnce(root, message, signal)) blockedTargets.add(message.targetId) @@ -121,7 +124,7 @@ export class LegacyTeamDelivery { return true } - /** Record delivery unless the acknowledgement already exists. */ + /** Record the receipt selected by the serialized recovery pass. */ private async markDelivered(root: Agent, messageId: TeamMessageId, targetId: SessionId): Promise { await this.journal.transact(root.id, async () => { await this.journal.appendAndFlush(root, 'team/message/delivered', { diff --git a/packages/experimental/agent-team/src/types.ts b/packages/experimental/agent-team/src/types.ts index 197e73a3be..e41faab2d2 100644 --- a/packages/experimental/agent-team/src/types.ts +++ b/packages/experimental/agent-team/src/types.ts @@ -157,7 +157,7 @@ export interface Config { readonly maxTasks?: number /** Maximum UTF-8 bytes in one complete sender-framed delivery. */ readonly maxMessageBytes?: number - /** Maximum milliseconds allowed for Team-owned runtime disposal. */ + /** Maximum milliseconds for shared operation settlement and for each Team child drain. */ readonly disposalTimeoutMs?: number } @@ -230,9 +230,9 @@ declare module '@deepseek-ai/dsh-session/types' { 'team/member': { version: 2; teamId: TeamId; member: TeamMemberSnapshot } /** Whole shared-task value, stored only in the Team Lead Session. */ 'team/task': { version: 2; teamId: TeamId; task: TeamTaskSnapshot } - /** Durable mailbox enqueue, stored before delivery is attempted. */ + /** Historical mailbox enqueue; new sends use the target Agent inbox. */ 'team/message/queued': { version: 2; teamId: TeamId; message: TeamMessageSnapshot } - /** Durable acknowledgement that the target Session recorded the message. */ + /** Compatibility acknowledgement that the target Session recorded a historical message. */ 'team/message/delivered': { version: 2 teamId: TeamId diff --git a/packages/experimental/agent-team/tests/team.spec.ts b/packages/experimental/agent-team/tests/team.spec.ts index fc262f238f..1c7ab55335 100644 --- a/packages/experimental/agent-team/tests/team.spec.ts +++ b/packages/experimental/agent-team/tests/team.spec.ts @@ -86,6 +86,7 @@ function content(text: string) { interface TeamServiceInternals { readonly legacyDelivery: LegacyTeamDelivery + readonly lifecycle: TeamRuntimeLifecycle readonly roster: { readonly inFlightCreations: Set> checkpointInitialPrompt(childId: SessionId, messageId: string, signal: AbortSignal): Promise @@ -905,6 +906,58 @@ describe('Historical Team message compatibility', () => { await lead.whenIdle() }) + it('recovers only the newly created teammate target through the Agent lifecycle', async () => { + const { ctx, lead } = await setup(['hang', textResponse('initial'), textResponse('other initial'), 'hang']) + lead.followup(createUserMessage({ content: content('busy lead'), source: { kind: 'user' } })) + await waitRunning(ctx, lead.id) + const target = await spawn(ctx, lead, 'target') + await waitNoAgent(ctx, target.member.id) + const other = await spawn(ctx, lead, 'other') + await waitNoAgent(ctx, other.member.id) + const message = enqueue(lead, 'target-old', target.member.id) + const untouched = enqueue(lead, 'other-old', other.member.id) + const accepted = await ctx.agentTeams.sendMessage(lead, { target: 'target', content: content('resume'), signal: SIGNAL }) + await vi.waitFor(() => { expect(durable(lead).pendingMessages.map(item => item.id)).toEqual([untouched.id]) }) + const live = await waitRunning(ctx, target.member.id) + const recorded = [...live.inbox.nextStep, ...live.session.snapshotEvents().flatMap(event => event.type === 'user/message' ? [event.data] : [])] + expect(recorded.some(item => item.id === accepted.messageId)).toBe(true) + expect(recorded.filter(item => item.source.kind === 'team-message' && item.source.messageId === message.id)).toHaveLength(1) + expect(ctx.agents.get(other.member.id)).toBeUndefined() + }) + + it('retains a second teammate recovery trigger while another target is checkpointing', async () => { + const { ctx, lead } = await setup(['hang', textResponse('first'), textResponse('second'), 'hang', 'hang']) + lead.followup(createUserMessage({ content: content('busy lead'), source: { kind: 'user' } })) + await waitRunning(ctx, lead.id) + const first = await spawn(ctx, lead, 'first') + await waitNoAgent(ctx, first.member.id) + const second = await spawn(ctx, lead, 'second') + await waitNoAgent(ctx, second.member.id) + enqueue(lead, 'first-old', first.member.id) + enqueue(lead, 'second-old', second.member.id) + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const flush = ctx.sessions.flush.bind(ctx.sessions) + const checkpoint = vi.spyOn(ctx.sessions, 'flush').mockImplementation(async (session) => { + if (session.id === first.member.id && session.snapshotEvents().some(event => event.type === 'agent/inbox/spliced' + && event.data.inserted.some(message => message.source.kind === 'team-message'))) { + entered.resolve(undefined) + await release.promise + } + return await flush(session) + }) + try { + await ctx.agentTeams.sendMessage(lead, { target: 'first', content: content('resume first'), signal: SIGNAL }) + await entered.promise + await ctx.agentTeams.sendMessage(lead, { target: 'second', content: content('resume second'), signal: SIGNAL }) + release.resolve(undefined) + await vi.waitFor(() => { expect(durable(lead).pendingMessages).toEqual([]) }) + } finally { + release.resolve(undefined) + checkpoint.mockRestore() + } + }) + it('retains old pending data and reports inspection and delivery failures', async () => { const { ctx, lead } = await setup([textResponse('done')]) const target = await spawn(ctx, lead, 'target') @@ -1408,6 +1461,29 @@ describe('Team direct messages and waiting', () => { expect(ctx.get('agentTeams')).toBeUndefined() }) + it('shares one settlement deadline across sends, creations, and legacy recovery', async () => { + const { ctx } = await setup([], { disposalTimeoutMs: 25 }) + const internal = teamInternals(ctx) + const release = Promise.withResolvers() + void internal.lifecycle.track(release.promise) + internal.roster.inFlightCreations.add(release.promise) + const pending = vi.spyOn(internal.legacyDelivery, 'pendingDispatches').mockReturnValue([release.promise]) + vi.useFakeTimers() + let failure: unknown + const disposal = internal.disposeRuntime().catch((error: unknown) => { failure = error }) + try { + await vi.advanceTimersByTimeAsync(25) + expect(failure).toBeInstanceOf(AggregateError) + await disposal + } finally { + release.resolve(undefined) + internal.roster.inFlightCreations.delete(release.promise) + pending.mockRestore() + vi.useRealTimers() + await disposal + } + }) + it('bounds disposal while an admitted creation ignores cancellation', async () => { const { ctx, lead } = await setup([], { disposalTimeoutMs: 25 }) const internal = teamInternals(ctx) diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 2d02cb8398..fe644b7a10 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -740,7 +740,7 @@ const SERVICE_ROLES: ServiceRole[] = [ title: 'Agent Teams coordination domain', mode: 'core', consumers: ['experimental-tool-agent-team'], - note: 'Owns the implicit-root roster, durable peer mailbox, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes model controls.', + note: 'Owns the implicit-root roster, direct peer inbox delivery, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes model controls.', }, { key: 'inspector', diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index 59aaf94af0..6a8723e8cd 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -585,7 +585,7 @@ const TOOL_PACKAGES: ToolPackage[] = [ dir: 'tool-agent-team', source: 'packages/experimental/tool-agent-team/src/index.ts', requires: ['ctx.tools', 'ctx.systemPrompt', 'ctx.agentTeams', 'an exact live Team member Agent'], - writes: ['tool/call', 'team/member', 'team/message/queued', 'team/message/delivered', 'team/task', 'tool/result'], + writes: ['tool/call', 'team/member', 'team/task', 'agent/inbox/spliced', 'user/message on inbox claim', 'tool/result'], async mount(ctx) { await ctx.plugin(AgentRegistry) await ctx.plugin(SessionStore) diff --git a/snapshots/session/team-direct-inbox/session.1.v4.jsonl b/snapshots/session/team-direct-inbox/session.1.v4.jsonl new file mode 100644 index 0000000000..7316d89461 --- /dev/null +++ b/snapshots/session/team-direct-inbox/session.1.v4.jsonl @@ -0,0 +1,22 @@ +{"type":"session","version":4,"id":"{{session:2}}","createdAt":1783951000000,"cwd":"{{cwd}}","parentSession":"{{session:1}}","isSeeded":false,"origin":"subagent","delegationDepth":1} +{"type":"subagent/descriptor","data":{"version":3,"mode":"continuable","provider":"spawn","label":"Check direct inbox delivery","agentProvider":"deepseek-official","agentModel":"deepseek-v4-flash"}} +{"type":"sandbox/mode","data":{"mode":"danger-full-access","source":"delegation"}} +{"type":"approval/policy","data":{"policy":"never","source":"delegation"}} +{"type":"permission/preset","data":{"preset":"danger-full-access"}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"\nYou are teammate \"reviewer\".\nYour Team Lead is named \"lead\".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: \"lead\", message: \"...\" }).\nTo message another teammate, use send_message({ target: \"\", message: \"...\" }).\n\n\n"},{"type":"text","text":"Create the empty file team-ready, then wait in Bash until interrupted."}],"source":{"kind":"user"},"role":"user","id":"{{message:13}}"}]}} +{"type":"turn/start","data":{"turn":1}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"step/start","data":{"turn":1,"step":1}} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:14}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"\nYou are teammate \"reviewer\".\nYour Team Lead is named \"lead\".\nUse list_agents({}) to find your teammates and their names.\nTo message your Team Lead, use send_message({ target: \"lead\", message: \"...\" }).\nTo message another teammate, use send_message({ target: \"\", message: \"...\" }).\n\n\n"},{"type":"text","text":"Create the empty file team-ready, then wait in Bash until interrupted."}],"source":{"kind":"user"},"role":"user","id":"{{message:13}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{message:15}}"},"surfaceOp":"append"} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} +{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} +{"type":"session/title","data":{"title":" You are teammate \"revi","messageSeqs":[9],"source":{"kind":"fallback"}}} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"direct-1","name":"bash","arguments":"{\"command\":\"touch team-ready; while :; do sleep 1; done\",\"description\":\"Signal readiness and wait for interruption\",\"timeoutMs\":60000}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:16}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1790429365968,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790429365968,"index":0,"dt":[],"id":"direct-1","name":"bash","args":["{\"command\":\"touch team-ready; while :; do sleep 1; done\",\"description\":\"Signal readiness and wait for interruption\",\"timeoutMs\":60000}"]},{"type":"chunk","time":1790429365968,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"direct-1","name":"bash","arguments":"{\"command\":\"touch team-ready; while :; do sleep 1; done\",\"description\":\"Signal readiness and wait for interruption\",\"timeoutMs\":60000}"}}},{"type":"chunk","time":1790429365968,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1790429365969,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":1,"callId":"direct-1","name":"bash","arguments":"{\"command\":\"touch team-ready; while :; do sleep 1; done\",\"description\":\"Signal readiness and wait for interruption\",\"timeoutMs\":60000}"}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Team message from lead:"},{"type":"text","text":"Review the direct inbox delivery."}],"source":{"kind":"agent-message","form":"relay","senderSessionId":"{{session:1}}"},"role":"user","id":"{{message:17}}"}]}} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"direct-1"},"toolCallId":"direct-1","content":[{"type":"text","text":"Error: tool call aborted"}],"isError":true,"id":"{{message:18}}"},"error":{"name":"AbortError","code":"ABORTED"}},"sourceEventSeqs":[15],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":1}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"aborted","reason":{"kind":"parent"}}}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}} diff --git a/snapshots/session/team-direct-inbox/session.v4.jsonl b/snapshots/session/team-direct-inbox/session.v4.jsonl new file mode 100644 index 0000000000..9c6b1532d1 --- /dev/null +++ b/snapshots/session/team-direct-inbox/session.v4.jsonl @@ -0,0 +1,40 @@ +{"type":"session","version":4,"id":"{{session:1}}","createdAt":1783951000000,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} +{"type":"permission/preset","data":{"preset":"danger-full-access"}} +{"type":"sandbox/mode","data":{"mode":"danger-full-access"}} +{"type":"approval/policy","data":{"policy":"never"}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Spawn teammate reviewer, wait for its team-ready file, send it the message Review the direct inbox delivery., interrupt its waiting turn, then reply TEAM_DIRECT_INBOX_OK."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} +{"type":"turn/start","data":{"turn":1}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"step/start","data":{"turn":1,"step":1}} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Spawn teammate reviewer, wait for its team-ready file, send it the message Review the direct inbox delivery., interrupt its waiting turn, then reply TEAM_DIRECT_INBOX_OK."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} +{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} +{"type":"session/title","data":{"title":"Spawn teammate reviewer, wait for","messageSeqs":[8],"source":{"kind":"fallback"}}} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"direct-1","name":"spawn_teammate","arguments":"{\"name\":\"reviewer\",\"description\":\"Check direct inbox delivery\",\"prompt\":\"Create the empty file team-ready, then wait in Bash until interrupted.\",\"context\":\"fresh\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1790429365916,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790429365916,"index":0,"dt":[],"id":"direct-1","name":"spawn_teammate","args":["{\"name\":\"reviewer\",\"description\":\"Check direct inbox delivery\",\"prompt\":\"Create the empty file team-ready, then wait in Bash until interrupted.\",\"context\":\"fresh\"}"]},{"type":"chunk","time":1790429365916,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"direct-1","name":"spawn_teammate","arguments":"{\"name\":\"reviewer\",\"description\":\"Check direct inbox delivery\",\"prompt\":\"Create the empty file team-ready, then wait in Bash until interrupted.\",\"context\":\"fresh\"}"}}},{"type":"chunk","time":1790429365916,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1790429365916,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":1,"callId":"direct-1","name":"spawn_teammate","arguments":"{\"name\":\"reviewer\",\"description\":\"Check direct inbox delivery\",\"prompt\":\"Create the empty file team-ready, then wait in Bash until interrupted.\",\"context\":\"fresh\"}"}} +{"type":"team/member","data":{"version":2,"teamId":"{{session:1}}","member":{"id":"{{session:2}}","name":"reviewer","description":"Check direct inbox delivery","provider":"spawn","context":"fresh","phase":"provisioning"}}} +{"type":"subagent/catalog","data":{"version":0,"childId":"{{session:2}}","childCreatedAt":1790429365929,"mode":"continuable","label":"Check direct inbox delivery"}} +{"type":"team/member","data":{"version":2,"teamId":"{{session:1}}","member":{"id":"{{session:2}}","name":"reviewer","description":"Check direct inbox delivery","provider":"spawn","context":"fresh","phase":"active"}}} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"direct-1"},"toolCallId":"direct-1","content":[{"type":"text","text":"{\"member\":{\"target\":\"reviewer\",\"role\":\"teammate\",\"status\":\"running\",\"description\":\"Check direct inbox delivery\",\"provider\":\"spawn\",\"context\":\"fresh\",\"model\":\"deepseek-v4-flash\",\"diagnostics\":[]}}"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":1}} +{"type":"step/start","data":{"turn":1,"step":2}} +{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"direct-2","name":"bash","arguments":"{\"command\":\"while [ ! -f team-ready ]; do sleep 0.01; done\",\"description\":\"Wait for the teammate to start\",\"timeoutMs\":60000}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1790429365995,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790429365995,"index":0,"dt":[],"id":"direct-2","name":"bash","args":["{\"command\":\"while [ ! -f team-ready ]; do sleep 0.01; done\",\"description\":\"Wait for the teammate to start\",\"timeoutMs\":60000}"]},{"type":"chunk","time":1790429365995,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"direct-2","name":"bash","arguments":"{\"command\":\"while [ ! -f team-ready ]; do sleep 0.01; done\",\"description\":\"Wait for the teammate to start\",\"timeoutMs\":60000}"}}},{"type":"chunk","time":1790429365995,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1790429365995,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":2,"callId":"direct-2","name":"bash","arguments":"{\"command\":\"while [ ! -f team-ready ]; do sleep 0.01; done\",\"description\":\"Wait for the teammate to start\",\"timeoutMs\":60000}"}} +{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"direct-2"},"toolCallId":"direct-2","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:7}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":2}} +{"type":"step/start","data":{"turn":1,"step":3}} +{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"direct-3","name":"send_message","arguments":"{\"target\":\"reviewer\",\"message\":\"Review the direct inbox delivery.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1790429366017,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790429366017,"index":0,"dt":[],"id":"direct-3","name":"send_message","args":["{\"target\":\"reviewer\",\"message\":\"Review the direct inbox delivery.\"}"]},{"type":"chunk","time":1790429366017,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"direct-3","name":"send_message","arguments":"{\"target\":\"reviewer\",\"message\":\"Review the direct inbox delivery.\"}"}}},{"type":"chunk","time":1790429366017,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1790429366017,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":3,"callId":"direct-3","name":"send_message","arguments":"{\"target\":\"reviewer\",\"message\":\"Review the direct inbox delivery.\"}"}} +{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"direct-3"},"toolCallId":"direct-3","content":[{"type":"text","text":"{\"messageId\":\"{{message:17}}\"}"}],"isError":false,"id":"{{message:9}}"}},"sourceEventSeqs":[27],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":3}} +{"type":"step/start","data":{"turn":1,"step":4}} +{"type":"assistant/message","data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"direct-4","name":"interrupt_agent","arguments":"{\"target\":\"reviewer\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:10}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1790429366033,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790429366033,"index":0,"dt":[],"id":"direct-4","name":"interrupt_agent","args":["{\"target\":\"reviewer\"}"]},{"type":"chunk","time":1790429366033,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"direct-4","name":"interrupt_agent","arguments":"{\"target\":\"reviewer\"}"}}},{"type":"chunk","time":1790429366033,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1790429366033,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"tool/call","data":{"turn":1,"step":4,"callId":"direct-4","name":"interrupt_agent","arguments":"{\"target\":\"reviewer\"}"}} +{"type":"tool/result","data":{"turn":1,"step":4,"message":{"role":"tool","source":{"kind":"tool","callId":"direct-4"},"toolCallId":"direct-4","content":[{"type":"text","text":"{\"previousStatus\":\"running\"}"}],"isError":false,"id":"{{message:11}}"}},"sourceEventSeqs":[32],"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":4}} +{"type":"step/start","data":{"turn":1,"step":5}} +{"type":"assistant/message","data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"text","text":"TEAM_DIRECT_INBOX_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:12}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1790429366052,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1790429366052,"index":0,"dt":[],"texts":["TEAM_DIRECT_INBOX_OK"]},{"type":"chunk","time":1790429366052,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"TEAM_DIRECT_INBOX_OK"}}},{"type":"chunk","time":1790429366052,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1790429366052,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"step/end","data":{"turn":1,"step":5}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/snapshots/session/team-direct-inbox/snapshot.yml b/snapshots/session/team-direct-inbox/snapshot.yml new file mode 100644 index 0000000000..94e0d3d3c8 --- /dev/null +++ b/snapshots/session/team-direct-inbox/snapshot.yml @@ -0,0 +1,11 @@ +version: 1 +scenario: team-direct-inbox +profile: headless +composition: team-targets +recording: authored +header: + class: team-targets + systemPromptSource: team-targets + toolSchemasSource: team-targets +workspace: + final: true diff --git a/snapshots/session/team-direct-inbox/workspace.expected/team-ready b/snapshots/session/team-direct-inbox/workspace.expected/team-ready new file mode 100644 index 0000000000..e69de29bb2