refactor(subagent): narrow continuation interface
This commit is contained in:
@@ -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: 0f1eb7a966689d1540c47f41e2f3fa89d2011d2b
|
||||
README.zh.md: bd140f93f7338a6b1f0e89a285b273080cc5d3cb
|
||||
README.md: 44fbd44b035ce283e404c491d9fa143a08b71127
|
||||
README.zh.md: 3fa1d1e543d1d390975c3aab16504954f283c2f4
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
The optional, globally named `send_message` tool: a thin adapter over `ctx.subagents.sendMessage()`. Provider-bound `@deepseek-ai/dsh-tool-subagent` instances register distinct delegation tools per transport; this separately loaded package registers one shared follow-up tool, so multiple delegation tools never register duplicate global controls. Its presence does not determine whether a delegation tool starts continuable work.
|
||||
The optional, globally named `send_message` tool: a thin adapter over `ctx.subagents.followup()`. Provider-bound `@deepseek-ai/dsh-tool-subagent` instances register distinct delegation tools per transport; this separately loaded package registers one shared follow-up tool, so multiple delegation tools never register duplicate global controls. Its presence does not determine whether a delegation tool starts continuable work.
|
||||
|
||||
The tool performs no lifecycle routing. It attributes every follow-up as `{ kind: 'coordinator', senderSessionId: parent.id }`; the subagent service preserves that source while deciding between live delivery to the running activation's existing Task and a fresh Task that cold-resumes the durable child. The tool forwards its execution signal, so cancellation while live delivery awaits admission cancels the shared activation and settles only after the child reaches quiescence. The tool renders which route was taken and the relevant Task id. A delivery failure becomes an errored tool result stating the message was not delivered.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
可选的全局具名 `send_message` 工具:`ctx.subagents.sendMessage()` 之上的轻量适配器。绑定提供方的 `@deepseek-ai/dsh-tool-subagent` 实例会为每种传输注册不同的委派工具;这个单独加载的包(package)只注册一个共享后续操作工具,因此多个委派工具绝不会重复注册全局控制工具。是否加载本工具不会决定委派工具是否启动可继续工作。
|
||||
可选的全局具名 `send_message` 工具:`ctx.subagents.followup()` 之上的轻量适配器。绑定提供方的 `@deepseek-ai/dsh-tool-subagent` 实例会为每种传输注册不同的委派工具;这个单独加载的包(package)只注册一个共享后续操作工具,因此多个委派工具绝不会重复注册全局控制工具。是否加载本工具不会决定委派工具是否启动可继续工作。
|
||||
|
||||
本工具不执行生命周期路由。它将每条后续消息的来源标记为 `{ kind: 'coordinator', senderSessionId: parent.id }`;subagent 服务会保留该来源,并在向运行中激活的现有 Task 在线投递消息与创建新 Task、从持久化存储恢复子 agent 之间做出选择。本工具会转发其执行信号,因此,若在在线投递等待准入期间取消,则会取消共享激活,并仅在子 agent 完全停稳后结算。本工具会渲染实际采用的路由及相关 Task id。投递失败会变为出错的工具结果,并明确说明消息未送达。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The globally named `send_message` tool: a thin model-facing adapter over
|
||||
* `ctx.subagents.sendMessage()`. It performs no lifecycle routing of its
|
||||
* `ctx.subagents.followup()`. It performs no lifecycle routing of its
|
||||
* own — steer-or-resume orchestration belongs to the subagent service — and it
|
||||
* lives apart from the provider-bound `@deepseek-ai/dsh-tool-subagent`
|
||||
* instances so multiple delegation tools share one control tool.
|
||||
@@ -60,21 +60,23 @@ export function apply(ctx: Context): void {
|
||||
: `message started task ${value.taskId} continuing subagent ${args.subagent_id}`,
|
||||
}],
|
||||
},
|
||||
execute(args, exec) {
|
||||
async execute(args, exec) {
|
||||
const parent = exec.agent
|
||||
if (!parent) {
|
||||
// Non-agent callers have no session to authorize Task access with.
|
||||
throw new Error('send_message requires a calling agent (exec.agent was undefined)')
|
||||
}
|
||||
const message: ContentBlock[] = [{ type: 'text', text: args.message }]
|
||||
const result = ctx.subagents.sendMessage(
|
||||
const result = await ctx.subagents.followup(
|
||||
parent,
|
||||
SessionId(args.subagent_id),
|
||||
message,
|
||||
{ kind: 'coordinator', senderSessionId: parent.id },
|
||||
exec.signal,
|
||||
{
|
||||
source: { kind: 'coordinator', senderSessionId: parent.id },
|
||||
signal: exec.signal,
|
||||
},
|
||||
)
|
||||
return Promise.resolve(result)
|
||||
return result
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -106,9 +106,9 @@ describe('dsh-tool-subagent-control', () => {
|
||||
// Reach past the tool into the subagent service to fake a running route
|
||||
// deterministically: the tool is a thin adapter, so its steered wording is
|
||||
// what this test pins.
|
||||
ctx.subagents.sendMessage = async (agent, _childId, message, messageSource) => {
|
||||
ctx.subagents.followup = async (agent, _childId, message, options) => {
|
||||
steered = (message[0] as { text: string }).text
|
||||
source = messageSource
|
||||
source = options.source
|
||||
return { route: 'steered', taskId: ctx.tasks.list(agent)[0]?.id ?? ('subagent-9' as never) }
|
||||
}
|
||||
const result = await callTool(ctx, 'send_message', {
|
||||
@@ -130,9 +130,9 @@ describe('dsh-tool-subagent-control', () => {
|
||||
})
|
||||
await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) })
|
||||
const deliveryStarted: PromiseWithResolvers<void> = Promise.withResolvers()
|
||||
const sendMessage = ctx.subagents.sendMessage.bind(ctx.subagents)
|
||||
ctx.subagents.sendMessage = (agent, childId, message, source, signal) => {
|
||||
const delivery = sendMessage(agent, childId, message, source, signal)
|
||||
const followup = ctx.subagents.followup.bind(ctx.subagents)
|
||||
ctx.subagents.followup = (agent, childId, message, options) => {
|
||||
const delivery = followup(agent, childId, message, options)
|
||||
deliveryStarted.resolve()
|
||||
return delivery
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user