refactor(subagent): merge continuation control service
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# @deepseek-ai/dsh-tool-subagent-control
|
||||
|
||||
The globally named `send_message` tool: a thin adapter over `ctx.subagentControl.sendMessage()`. Provider-bound `@deepseek-ai/dsh-tool-subagent` instances register distinct delegation tools per transport; this separately loaded package registers the one shared control tool, so multiple delegation tools never register duplicate global controls.
|
||||
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 tool performs no lifecycle routing. It attributes every follow-up as `{ kind: 'coordinator', senderSessionId: parent.id }`; the control 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 renders which route was taken and the relevant Task id. A control-service throw becomes an errored tool result stating the message was not delivered.
|
||||
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 renders which route was taken and the relevant Task id. A delivery failure becomes an errored tool result stating the message was not delivered.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-tool-subagent-control",
|
||||
"description": "Globally named send_message tool over the continuable-subagent control service",
|
||||
"description": "Globally named send_message tool over ctx.subagents continuations",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -30,7 +30,7 @@
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session": "^0.0.1",
|
||||
"@deepseek-ai/dsh-subagent-control": "^0.0.1",
|
||||
"@deepseek-ai/dsh-subagent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tools": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
@@ -44,7 +44,6 @@
|
||||
"@deepseek-ai/dsh-session-persistence": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
|
||||
"@deepseek-ai/dsh-subagent": "workspace:^",
|
||||
"@deepseek-ai/dsh-subagent-control": "workspace:^",
|
||||
"@deepseek-ai/dsh-subagent-spawn": "workspace:^",
|
||||
"@deepseek-ai/dsh-tasks": "workspace:^",
|
||||
"@deepseek-ai/dsh-tasks-local": "workspace:^",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* The globally named `send_message` tool: a thin model-facing adapter over
|
||||
* `ctx.subagentControl.sendMessage()`. It performs no lifecycle routing of its
|
||||
* own — steer-or-resume orchestration belongs to the control service — and it
|
||||
* `ctx.subagents.sendMessage()`. 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.
|
||||
* @module @deepseek-ai/dsh-tool-subagent-control
|
||||
@@ -11,14 +11,14 @@ import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-subagent-control'
|
||||
import type {} from '@deepseek-ai/dsh-subagent'
|
||||
|
||||
export const name = 'tool-subagent-control'
|
||||
export const inject = ['tools', 'subagentControl']
|
||||
export const inject = ['tools', 'subagents']
|
||||
|
||||
/**
|
||||
* Register the `send_message` tool.
|
||||
* @param ctx - context carrying the tool registry and the control service.
|
||||
* @param ctx - context carrying the tool registry and subagent service.
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
ctx.tools.register(defineTool({
|
||||
@@ -67,7 +67,7 @@ export function apply(ctx: Context): void {
|
||||
throw new Error('send_message requires a calling agent (exec.agent was undefined)')
|
||||
}
|
||||
const message: ContentBlock[] = [{ type: 'text', text: args.message }]
|
||||
const result = ctx.subagentControl.sendMessage(
|
||||
const result = ctx.subagents.sendMessage(
|
||||
parent,
|
||||
SessionId(args.subagent_id),
|
||||
message,
|
||||
|
||||
@@ -16,7 +16,7 @@ export const inject = ['invariants']
|
||||
|
||||
/**
|
||||
* No runtime invariant: this model-facing adapter has no independent lifecycle stream; delivery
|
||||
* and activation relations are owned by the control service it calls.
|
||||
* and activation relations are owned by the subagent service it calls.
|
||||
*/
|
||||
const install: InvariantInstaller = () => {}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-test
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
import SubagentControlService from '@deepseek-ai/dsh-subagent-control'
|
||||
import * as SubagentSpawn from '@deepseek-ai/dsh-subagent-spawn'
|
||||
import LocalTaskService from '@deepseek-ai/dsh-tasks-local'
|
||||
import * as ToolTasks from '@deepseek-ai/dsh-tool-tasks'
|
||||
@@ -34,7 +33,6 @@ async function setup(script: ConstructorParameters<typeof MockAdapter>[0]) {
|
||||
await ctx.plugin(SubagentSpawn, { providerName: 'spawn' })
|
||||
await ctx.plugin(LocalTaskService)
|
||||
await ctx.plugin(ToolTasks, {})
|
||||
await ctx.plugin(SubagentControlService)
|
||||
await ctx.plugin(tool)
|
||||
ctx.llm.registerAdapter(['mock'], new MockAdapter(script))
|
||||
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
|
||||
@@ -68,7 +66,7 @@ describe('dsh-tool-subagent-control', () => {
|
||||
|
||||
it('cold-resumes a settled child and renders the started route with its task id', async () => {
|
||||
const { ctx, parent } = await setup([textResponse('first answer'), textResponse('second answer')])
|
||||
const started = ctx.subagentControl.startContinuable({
|
||||
const started = ctx.subagents.startContinuable({
|
||||
provider: 'spawn',
|
||||
label: 'work',
|
||||
request: { prompt: [{ type: 'text', text: 'child task' }], parent },
|
||||
@@ -98,10 +96,10 @@ describe('dsh-tool-subagent-control', () => {
|
||||
const { ctx, parent } = await setup([])
|
||||
let steered: string | undefined
|
||||
let source: unknown
|
||||
// Reach past the tool into the control service to fake a running route
|
||||
// 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.subagentControl.sendMessage = async (agent, _childId, message, messageSource) => {
|
||||
ctx.subagents.sendMessage = async (agent, _childId, message, messageSource) => {
|
||||
steered = (message[0] as { text: string }).text
|
||||
source = messageSource
|
||||
return { route: 'steered', taskId: ctx.tasks.list(agent)[0]?.id ?? ('subagent-9' as never) }
|
||||
@@ -116,7 +114,7 @@ describe('dsh-tool-subagent-control', () => {
|
||||
expect(text(result)).toBe('message delivered to running task subagent-9')
|
||||
})
|
||||
|
||||
it('reports a control-service failure as an errored, not-delivered result', async () => {
|
||||
it('reports a delivery failure as an errored, not-delivered result', async () => {
|
||||
const { ctx, parent } = await setup([])
|
||||
const result = await callTool(ctx, 'send_message', {
|
||||
subagent_id: 'no-such-child',
|
||||
@@ -148,7 +146,6 @@ describe('dsh-tool-subagent-control', () => {
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(LocalTaskService)
|
||||
await ctx.plugin(SubagentControlService)
|
||||
const fiber = await ctx.plugin(tool)
|
||||
expect(ctx.tools.schemas().some(schema => schema.name === 'send_message')).toBe(true)
|
||||
await fiber.dispose()
|
||||
@@ -158,7 +155,7 @@ describe('dsh-tool-subagent-control', () => {
|
||||
it('has the namespace-plugin export shape (no stray default)', () => {
|
||||
expect('default' in tool).toBe(false)
|
||||
expect(tool.name).toBe('tool-subagent-control')
|
||||
expect(tool.inject).toEqual(['tools', 'subagentControl'])
|
||||
expect(tool.inject).toEqual(['tools', 'subagents'])
|
||||
expect(typeof tool.apply).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"path": "../../core/tools"
|
||||
},
|
||||
{
|
||||
"path": "../subagent-control"
|
||||
"path": "../subagent"
|
||||
},
|
||||
{
|
||||
"path": "../../support/invariants"
|
||||
|
||||
Reference in New Issue
Block a user