feat: subagent list use preparation + projection

This commit is contained in:
imccyu
2026-08-06 18:45:03 +08:00
parent 949d406086
commit a328fd34d5
22 changed files with 696 additions and 504 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/subagent/tool-subagent-control/README.md
README.md: 5d775a524c38750953c6389b9ebdea67a33df7ca
README.zh.md: 3b989fca8b79cea3e3b10bb2e65805e0cee79c69
README.md: ea95a45b85e01d1f5f1c478a35c80c65151724ac
README.zh.md: 2cc876c8b39caa19fdf30eae7c8def0ba81fe7b1

View File

@@ -2,7 +2,7 @@
English | [中文](README.zh.md)
The optional, globally named `send_message` and `list_agents` tools are thin adapters over `ctx.subagents`. Provider-bound `@deepseek-ai/dsh-tool-subagent` instances register distinct delegation tools per transport; this separately loaded package registers shared control tools once, so multiple delegation tools never register duplicate global controls. The root plugin registers `send_message` and requires only `subagents`; the separately loadable `./list-agents` plugin registers `list_agents`, declares `sessionQuery` as a load-time dependency, and remains inactive until that service is available. A deployment without session query keeps `send_message` and omits the list tool. Neither tool's presence determines whether a delegation tool starts continuable work. These tools own only the parent-to-child direction; the independently installed [`@deepseek-ai/dsh-tool-subagent-report`](../tool-subagent-report/README.md) owns the child-to-parent direction.
The optional, globally named `send_message` and `list_agents` tools are thin adapters over `ctx.subagents`. Provider-bound `@deepseek-ai/dsh-tool-subagent` instances register distinct delegation tools per transport; this separately loaded package registers shared control tools once, so multiple delegation tools never register duplicate global controls. The root plugin registers `send_message` and the separately loadable `./list-agents` plugin registers `list_agents`; both require only `subagents`, so a deployment can keep `send_message` while omitting the list tool. Neither tool's presence determines whether a delegation tool starts continuable work. These tools own only the parent-to-child direction; the independently installed [`@deepseek-ai/dsh-tool-subagent-report`](../tool-subagent-report/README.md) owns the child-to-parent direction.
The tool performs no lifecycle routing — residency and cold resume belong to the subagent service. It passes `exec.agent` as the exact live parent that authorizes delivery and attributes every message as durable provenance `{ kind: 'coordinator', senderSessionId: parent.id }`, which the service retains but never treats as authority. Every message becomes the subagent's next FIFO turn through `Agent.followup()`: if the child is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. The tool forwards its execution signal, which owns admission only until inbox acceptance; once the child accepts the message the accepted turn cannot be cancelled through this tool. This call returns no child reply — its transcript by that id is the source of what it did — and a child with `report` sends content on its own initiative as a separate parent message. A delivery failure becomes an errored tool result stating the message was not delivered.

View File

@@ -2,7 +2,7 @@
[English](README.md) | 中文
可选的全局具名 `send_message``list_agents` 工具是 `ctx.subagents` 之上的轻量适配器。绑定提供方的 `@deepseek-ai/dsh-tool-subagent` 实例会为每种传输注册不同的委派工具;这个单独加载的包只注册一次共享控制工具,因此多个委派工具绝不会重复注册全局控制工具。根插件注册 `send_message`且只要求 `subagents`可单独加载的 `./list-agents` 插件注册 `list_agents`,将 `sessionQuery` 声明为加载时依赖,并在该服务可用前保持未激活状态。没有会话查询服务的部署可保留 `send_message` 省略列表工具。是否加载这些工具不会决定委派工具是否启动可继续工作。这些工具只负责父到子的方向;单独安装的 [`@deepseek-ai/dsh-tool-subagent-report`](../tool-subagent-report/README.md) 负责子到父的方向。
可选的全局具名 `send_message``list_agents` 工具是 `ctx.subagents` 之上的轻量适配器。绑定提供方的 `@deepseek-ai/dsh-tool-subagent` 实例会为每种传输注册不同的委派工具;这个单独加载的包只注册一次共享控制工具,因此多个委派工具绝不会重复注册全局控制工具。根插件注册 `send_message`,可单独加载的 `./list-agents` 插件注册 `list_agents`;两者都只要求 `subagents`部署可保留 `send_message` 省略列表工具。是否加载这些工具不会决定委派工具是否启动可继续工作。这些工具只负责父到子的方向;单独安装的 [`@deepseek-ai/dsh-tool-subagent-report`](../tool-subagent-report/README.md) 负责子到父的方向。
本工具不执行生命周期路由:驻留与冷恢复归 subagent 服务所有。它将 `exec.agent` 作为授权投递的确切在线父级传入,并把每条消息的来源标记为持久化来源 `{ kind: 'coordinator', senderSessionId: parent.id }`;服务会保留该来源,但绝不将其视为权限。每条消息都会通过 `Agent.followup()` 成为子 agent智能体的下一个 FIFO 轮次:如果子 agent 仍在工作,该消息会等待其当前轮次结束,因此无法重定向已经在进行的工作。本工具会转发其执行信号,该信号只在 inbox 接受之前掌管准入;一旦子 agent 接受消息,已接受的轮次便无法再通过本工具取消。本次调用不会返回子 agent 的回复;通过该 id 查看其 transcript文本记录才是了解它完成了哪些工作的真源。拥有 `report` 的子 agent 会自行把内容作为一条单独的父级消息发回。投递失败会变为出错的工具结果,并明确说明消息未送达。

View File

@@ -33,16 +33,10 @@
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"@deepseek-ai/dsh-session-query": "^0.0.1",
"@deepseek-ai/dsh-subagent": "^0.0.1",
"@deepseek-ai/dsh-tools": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"peerDependenciesMeta": {
"@deepseek-ai/dsh-session-query": {
"optional": true
}
},
"devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-agent-loop": "workspace:^",
@@ -52,7 +46,6 @@
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-persistence": "workspace:^",
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
"@deepseek-ai/dsh-session-query": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-subagent-spawn": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",

View File

@@ -1,20 +1,17 @@
/**
* The globally named `list_agents` tool: a thin model-facing adapter over
* the continuable projection of `ctx.subagents.listChildren()`. It is
* separately loadable from the
* root `send_message` plugin because it additionally requires the session
* query service — a deployment may use `send_message` without loading session
* query, and this plugin remains inactive until that service is available.
* the continuable projection of `ctx.subagents.listChildren()`. It stays
* separately loadable from the root `send_message` plugin so a deployment
* can register `send_message` without exposing the list tool.
* @module @deepseek-ai/dsh-tool-subagent-control/list-agents
*/
import type { Context } from 'cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-session-query'
import type {} from '@deepseek-ai/dsh-subagent'
export const name = 'tool-subagent-list-agents'
export const inject = ['tools', 'subagents', 'sessionQuery']
export const inject = ['tools', 'subagents']
type ListAgentsEntry =
| {
@@ -31,7 +28,7 @@ type ListAgentsEntry =
/**
* Register the `list_agents` tool.
* @param ctx - context carrying the tool registry, subagent service, and session query.
* @param ctx - context carrying the tool registry and subagent service.
*/
export function apply(ctx: Context): void {
ctx.tools.register(defineTool({

View File

@@ -12,7 +12,6 @@ import SubagentService from '@deepseek-ai/dsh-subagent'
import type { SubagentListEntry } from '@deepseek-ai/dsh-subagent'
import * as SubagentSpawn from '@deepseek-ai/dsh-subagent-spawn'
import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
import { TestSessionQueryService } from '../../../session-query/session-query/tests/test-service.ts'
import * as tool from '../src/list-agents.ts'
const testToolSignal = new AbortController().signal
@@ -31,7 +30,6 @@ async function setup(script: ConstructorParameters<typeof MockAdapter>[0]) {
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
await ctx.plugin(SubagentSpawn, { providerName: 'spawn' })
await ctx.plugin(TestSessionQueryService)
await ctx.plugin(tool)
ctx.llm.registerAdapter(['mock'], new MockAdapter(script))
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
@@ -177,17 +175,16 @@ describe('dsh-tool-subagent-control/list-agents', () => {
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
await ctx.plugin(TestSessionQueryService)
const fiber = await ctx.plugin(tool)
expect(ctx.tools.schemas().some(schema => schema.name === 'list_agents')).toBe(true)
await fiber.dispose()
expect(ctx.tools.schemas().some(schema => schema.name === 'list_agents')).toBe(false)
})
it('has the namespace-plugin export shape and requires sessionQuery at load', () => {
it('has the namespace-plugin export shape', () => {
expect('default' in tool).toBe(false)
expect(tool.name).toBe('tool-subagent-list-agents')
expect(tool.inject).toEqual(['tools', 'subagents', 'sessionQuery'])
expect(tool.inject).toEqual(['tools', 'subagents'])
expect(typeof tool.apply).toBe('function')
})
})

View File

@@ -26,9 +26,6 @@
{
"path": "../subagent"
},
{
"path": "../../session-query/session-query"
},
{
"path": "../../support/invariants"
}