Files
deepseek-harness/packages/subagent/subagent-spawn/tests/harness.ts
T
Tianyi Cui 0d6bfd8856 refactor(process): split the process manager out of the bash executor
New process/ capability family: @deepseek-ai/dsh-process owns ctx.processes —
abstract ProcessManager.spawn(spec) over a fully-explicit ProcessSpawnSpec —
plus the shared DSH_* managed-environment and CollectedOutput vocabulary;
@deepseek-ai/dsh-process-local carries the former bash-local run.ts plumbing
(detached groups, tail-keep spill-backed output, credential scrub, kill
escalation, kill-and-join disposal) with no config of its own.

dsh-bash-local becomes a consumer: it keeps command defaulting, the fused
deadline timedOut/aborted classification, the model-friendly terminal env
(now merged through the ordinary env channel), and the [stderr]-marked
background read merge, and spawns through ctx.processes. Background-process
lifetime moves to the manager, so an executor reload no longer kills live
background work; a background spawn failure is injected once into the read
path instead of being buffered as fake stderr. dsh-bash re-exports the moved
vocabulary so bash consumers keep one import root; dsh-bash-sandbox only
redeclares the inherited inject.

Every composition loading a bash executor now loads dsh-process-local (CLI,
examples, python bundled runtime, create-sdk bash feature, inline test
configs).
2026-07-26 06:59:01 +08:00

51 lines
2.2 KiB
TypeScript

import { Context } from 'cordis'
import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
import LocalProcessManager from '@deepseek-ai/dsh-process-local'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import SubagentService from '@deepseek-ai/dsh-subagent'
import * as Spawn from '../src/index.ts'
import * as ToolSubagent from '@deepseek-ai/dsh-tool-subagent'
/**
* Shared harness for the spawn-backend e2e: the full real stack (DeepSeek
* adapter + real bash tool + the subagent tool bound to the spawn backend), so
* a real parent agent can delegate to a real in-process child that does real
* work (writes a file). Lives outside the *.e2e.ts pattern so importing it never
* re-registers another file's tests.
*/
export async function spawnHarness(workdir: string): Promise<Context> {
const ctx = new Context()
// This harness installs only the global default persona, so both parent and
// spawned children render it. It stays neutral for both roles; the
// delegation nudge lives in the e2e's user prompt and the subagent tool's
// own description.
await mountAgentLoopTestDependencies(ctx, {
systemPrompt: { persona: 'You are a coding agent. Report only when the requested work is done.' },
})
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(LlmDeepSeek)
await ctx.plugin(LocalProcessManager)
await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 })
await ctx.plugin(ToolBash)
await ctx.plugin(SubagentService)
await ctx.plugin(Spawn, { providerName: 'spawn' })
// The model-facing subagent tool, bound to the spawn backend.
await ctx.plugin(ToolSubagent, { provider: 'spawn' })
return ctx
}
export function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
return new Promise((resolve) => {
const dispose = ctx.on('agent/status', (subject, status) => {
if (subject === agent && status === 'idle') {
dispose()
resolve()
}
})
})
}