test(subagent-sdk): keyless Loader-composition e2e across the SDK wire
A test-only cordis.yml under examples/jsonrpc-agent boots the headless app through the Loader; a scripted model delegates once to the SDK backend, whose child is a COMPLETE second harness runtime (own cordis.yml, jsonrpc serving surface, scripted cwd-echo model, own JSONL persistence). Asserts the parent tool result AND the child's own persisted transcript both carry the parent session's workspace cwd; child launch resolves through the shared example-launch resolver so src/lib modes both hold.
This commit is contained in:
32
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/child-mock-llm.ts
vendored
Normal file
32
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/child-mock-llm.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { Context } from 'cordis'
|
||||
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/**
|
||||
* Scripted model for the CHILD runtime: answers every request with its own
|
||||
* process cwd, so the driving e2e can prove the parent session's workspace
|
||||
* reached the child process across the SDK wire. `options` carries the
|
||||
* request; the reply depends only on process state.
|
||||
*/
|
||||
class CwdEchoAdapter extends LlmAdapter {
|
||||
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
void options
|
||||
const reply = `child cwd: ${process.cwd()}`
|
||||
yield { type: 'block-start', index: 0, blockType: 'text' }
|
||||
yield { type: 'text-delta', index: 0, text: reply }
|
||||
yield { type: 'block-end', index: 0, block: { type: 'text', text: reply } }
|
||||
yield { type: 'usage', usage: { inputTokens: 3, outputTokens: reply.length } }
|
||||
yield { type: 'finish', reason: { kind: 'stop' } }
|
||||
}
|
||||
}
|
||||
|
||||
export const name = 'child-mock-llm'
|
||||
export const inject = ['llm']
|
||||
|
||||
/**
|
||||
* Register the cwd-echo adapter under the `mock` provider.
|
||||
* @param ctx - the plugin context supplying `ctx.llm`.
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
ctx.llm.registerAdapter(['mock'], new CwdEchoAdapter())
|
||||
}
|
||||
36
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/child.cordis.yml
vendored
Normal file
36
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/child.cordis.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# The CHILD runtime for the SDK subagent composition test: a complete
|
||||
# stdio JSON-RPC harness whose scripted model echoes its process cwd. The
|
||||
# parent's subagent-sdk backend spawns this composition per run; stdout is
|
||||
# reserved for JSON-RPC frames.
|
||||
- id: jsonrpc
|
||||
name: '@deepseek-ai/dsh-jsonrpc'
|
||||
|
||||
- id: child-mock-llm
|
||||
name: './child-mock-llm.ts'
|
||||
|
||||
- id: agent-core
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
persona: 'Echo where you run.'
|
||||
workspaceContext: false
|
||||
skills:
|
||||
enabled: false
|
||||
toolBash:
|
||||
enableRunInBackground: false
|
||||
toolTasks: false
|
||||
|
||||
# The child persists its own session log beside the parent's (distinct root),
|
||||
# so the driving e2e can inspect both transcripts after the run.
|
||||
- id: sessions
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js process.env.DSH_SESSION_ROOT ?? './.child-sessions'
|
||||
compression: none
|
||||
|
||||
- id: session-checkpoints
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
|
||||
- id: bash
|
||||
name: '@deepseek-ai/dsh-bash-local'
|
||||
config:
|
||||
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
||||
42
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/cordis.yml
vendored
Normal file
42
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/cordis.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# Test-only composition: the SDK subagent backend on the real Loader/app path.
|
||||
# The scripted model delegates once; the child — a COMPLETE second harness
|
||||
# runtime speaking stdio JSON-RPC — echoes its process cwd, so parent-session
|
||||
# cwd inheritance is asserted keylessly end to end across the SDK wire.
|
||||
# `cwd` is deliberately omitted — the inheritance branch under test. The child
|
||||
# launch is machine-absolute, so the driving e2e supplies it via
|
||||
# DSH_TEST_CHILD_COMMAND / DSH_TEST_CHILD_ARGS / DSH_TEST_CHILD_ENV (resolved
|
||||
# through the shared example-launch resolver, per testing policy).
|
||||
- id: mock-llm
|
||||
name: './mock-delegating-llm.ts'
|
||||
|
||||
- id: subagent
|
||||
name: '@deepseek-ai/dsh-subagent'
|
||||
|
||||
- id: subagent-sdk
|
||||
name: '@deepseek-ai/dsh-subagent-sdk'
|
||||
config:
|
||||
providerName: sdk
|
||||
command: !!js process.env.DSH_TEST_CHILD_COMMAND
|
||||
args: !!js JSON.parse(process.env.DSH_TEST_CHILD_ARGS ?? '[]')
|
||||
provider: mock
|
||||
model: mock-echo
|
||||
env: !!js JSON.parse(process.env.DSH_TEST_CHILD_ENV ?? '{}')
|
||||
|
||||
- id: tool-subagent
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: sdk
|
||||
toolName: subagent
|
||||
# The SDK backend advertises no depthLimit: the child harness owns its own
|
||||
# recursion budget, so the local numeric default cannot apply here.
|
||||
maxDepth: 'provider-managed'
|
||||
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
config:
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
persona: 'Test SDK subagent cwd inheritance.'
|
||||
persistenceRoot: './.sessions'
|
||||
persistenceCompression: 'none'
|
||||
workspaceContext: false
|
||||
15
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/driver.ts
vendored
Normal file
15
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/driver.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
/** Test driver: one delegation turn through a headless Loader composition. */
|
||||
|
||||
import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
|
||||
import { runOneShot } from '@deepseek-ai/dsh-cli-demo/src/cli.ts'
|
||||
|
||||
const configPath = process.argv[2]
|
||||
if (configPath === undefined) throw new Error('sdk-subagent cwd driver requires a config path')
|
||||
|
||||
const ctx = await boot('sdk-subagent-cwd-e2e', resolveConfigPath(configPath, undefined))
|
||||
try {
|
||||
await runOneShot(ctx, { task: 'delegate' })
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
48
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/mock-delegating-llm.ts
vendored
Normal file
48
examples/jsonrpc-agent/tests/fixtures/subagent/subagent-sdk/mock-delegating-llm.ts
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { Context } from 'cordis'
|
||||
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/**
|
||||
* Test adapter for the `mock-delegate` model: the first request calls the
|
||||
* `subagent` tool once, and the follow-up streams the tool result text back
|
||||
* verbatim — so the SDK child runtime's answer (the scripted child model's
|
||||
* cwd echo) reaches the parent session log for the driving e2e to assert.
|
||||
*/
|
||||
class MockDelegatingAdapter extends LlmAdapter {
|
||||
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
const toolResultText = options.messages.at(-1)?.content
|
||||
.filter(block => block.type === 'tool-result')
|
||||
.flatMap(block => block.content)
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('') ?? ''
|
||||
|
||||
if (toolResultText.length === 0) {
|
||||
const args = JSON.stringify({ description: 'cwd probe', prompt: 'report your workspace' })
|
||||
yield { type: 'block-start', index: 0, blockType: 'tool-call' }
|
||||
yield { type: 'tool-call-delta', index: 0, id: CallId('call-delegate'), name: 'subagent', argumentsDelta: args }
|
||||
yield { type: 'block-end', index: 0, block: { type: 'tool-call', id: CallId('call-delegate'), name: 'subagent', arguments: args } }
|
||||
yield { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } }
|
||||
yield { type: 'finish', reason: { kind: 'tool-calls' } }
|
||||
return
|
||||
}
|
||||
|
||||
const reply = `child reported:\n${toolResultText}`
|
||||
yield { type: 'block-start', index: 0, blockType: 'text' }
|
||||
yield { type: 'text-delta', index: 0, text: reply }
|
||||
yield { type: 'block-end', index: 0, block: { type: 'text', text: reply } }
|
||||
yield { type: 'usage', usage: { inputTokens: 10, outputTokens: reply.length } }
|
||||
yield { type: 'finish', reason: { kind: 'stop' } }
|
||||
}
|
||||
}
|
||||
|
||||
export const name = 'mock-llm'
|
||||
export const inject = ['llm']
|
||||
|
||||
/**
|
||||
* Register the delegating mock adapter under the `mock` provider.
|
||||
* @param ctx - the plugin context supplying `ctx.llm`.
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
ctx.llm.registerAdapter(['mock'], new MockDelegatingAdapter())
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"description": "Workspace umbrella for runnable demos and example-owned test compositions: declares their cordis.yml packages so plain Node resolves real exports\u2192lib. Not a build target.",
|
||||
"description": "Workspace umbrella for runnable demos and example-owned test compositions: declares their cordis.yml packages so plain Node resolves real exports→lib. Not a build target.",
|
||||
"dependencies": {
|
||||
"@cordisjs/plugin-hmr": "workspace:*",
|
||||
"@cordisjs/plugin-include": "workspace:*",
|
||||
@@ -30,24 +30,24 @@
|
||||
"@deepseek-ai/dsh-llm-replay": "workspace:*",
|
||||
"@deepseek-ai/dsh-lsp": "workspace:*",
|
||||
"@deepseek-ai/dsh-lsp-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-plan-mode": "workspace:*",
|
||||
"@deepseek-ai/dsh-permission": "workspace:*",
|
||||
"@deepseek-ai/dsh-plan-mode": "workspace:*",
|
||||
"@deepseek-ai/dsh-pty": "workspace:*",
|
||||
"@deepseek-ai/dsh-pty-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-repeat-tool-guard": "workspace:*",
|
||||
"@deepseek-ai/dsh-sandbox-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-checkpoint-policy": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-query": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-query-sqlite": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-title-first-message-llm": "workspace:*",
|
||||
"@deepseek-ai/dsh-spill-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-spill-policy": "workspace:*",
|
||||
"@deepseek-ai/dsh-tui-demo": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-title-first-message-llm": "workspace:*",
|
||||
"@deepseek-ai/dsh-subagent": "workspace:*",
|
||||
"@deepseek-ai/dsh-subagent-acp": "workspace:*",
|
||||
"@deepseek-ai/dsh-subagent-fork": "workspace:*",
|
||||
"@deepseek-ai/dsh-subagent-sdk": "workspace:*",
|
||||
"@deepseek-ai/dsh-subagent-spawn": "workspace:*",
|
||||
"@deepseek-ai/dsh-tasks-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-time-context": "workspace:*",
|
||||
@@ -57,15 +57,16 @@
|
||||
"@deepseek-ai/dsh-tool-cordis": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-fs": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-fs-search": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-pty": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-goal": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-lsp": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-pty": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-ralph": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-session-query": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-subagent": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-todo": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-workflow": "workspace:*",
|
||||
"@deepseek-ai/dsh-tools": "workspace:*",
|
||||
"@deepseek-ai/dsh-tui-demo": "workspace:*",
|
||||
"@deepseek-ai/dsh-user-approval": "workspace:*",
|
||||
"@deepseek-ai/dsh-web": "workspace:*",
|
||||
"@deepseek-ai/dsh-web-fetch-local": "workspace:*",
|
||||
|
||||
Reference in New Issue
Block a user