Merge branch 'codex/simp-hide-concrete-agent-loop' into codex/simp-hide-subagent-internals

This commit is contained in:
Tianyi Cui
2026-07-18 12:34:11 +08:00
657 changed files with 36714 additions and 7631 deletions

View File

@@ -32,6 +32,7 @@
"devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-agent-loop": "workspace:^",
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-bash-local": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
@@ -39,10 +40,8 @@
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-subagent-inprocess": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-tool-bash": "workspace:^",
"@deepseek-ai/dsh-tool-subagent": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"@cordisjs/plugin-loader": "^1.0.0-rc.5",
"cordis": "^4.0.0-rc.7"
}

View File

@@ -1,10 +1,7 @@
import { Context } from 'cordis'
import LlmService from '@deepseek-ai/dsh-llm'
import SessionStore from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
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 * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
@@ -21,17 +18,15 @@ import * as ToolSubagent from '@deepseek-ai/dsh-tool-subagent'
*/
export async function spawnHarness(workdir: string): Promise<Context> {
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(SessionStore)
// 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 ctx.plugin(SystemPrompt, { persona: 'You are a coding agent. Report only when the requested work is done.' })
await ctx.plugin(ToolRegistry)
await ctx.plugin(AgentRegistry)
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, { models: ['deepseek-v4-flash'] })
await ctx.plugin(LlmDeepSeek)
await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 })
await ctx.plugin(ToolBash)
await ctx.plugin(SubagentService)

View File

@@ -29,7 +29,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('spawn backend with-key smoke', (
it('a parent delegates to a child that writes a file on disk', async () => {
workdir = await mkdtemp(join(tmpdir(), 'dsh-subagent-spawn-e2e-'))
ctx = await spawnHarness(workdir)
const parent = ctx.agentLoop.create(SessionId('e2e-parent'), { model: 'deepseek-v4-flash' })
const parent = ctx.agentLoop.create(SessionId('e2e-parent'), { provider: 'deepseek', model: 'deepseek-v4-flash' })
parent.send([{ type: 'text', text:
'Use the subagent tool to delegate this exact task: "Use the bash tool to write the text '

View File

@@ -1,14 +1,10 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import Loader from '@cordisjs/plugin-loader'
import LlmService from '@deepseek-ai/dsh-llm'
import SessionStore from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import { SessionId } from '@deepseek-ai/dsh-session'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import * as Invariants from '@deepseek-ai/dsh-invariants'
import SubagentService, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
import { MockAdapter, maxTokensResponse, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
@@ -27,17 +23,13 @@ type Script = ConstructorParameters<typeof MockAdapter>[0]
async function setup(script: Script) {
const ctx = new Context()
const adapter = new MockAdapter(script)
await ctx.plugin(LlmService)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(AgentRegistry)
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(Invariants)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
await ctx.plugin(spawn, { providerName: 'spawn' })
ctx.llm.registerAdapter(['mock'], adapter)
const parent = ctx.agentLoop.create(SessionId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
return { ctx, parent, adapter }
}
@@ -235,7 +227,7 @@ describe('dsh-subagent-spawn', () => {
const parentHandle = await ctx.agents.create({
sessionId: SessionId('cwd-parent-session'),
meta: { cwd: '/tmp/parent-workspace' },
agentOptions: { model: 'mock' },
agentOptions: { provider: 'mock', model: 'mock' },
})
const run = await start(ctx, 'spawn', { prompt: [{ type: 'text', text: 'p' }], parent: parentHandle.agent })
await run.result
@@ -256,7 +248,7 @@ describe('dsh-subagent-spawn', () => {
const run = await start(ctx, 'spawn', {
prompt: [{ type: 'text', text: 'p' }],
parent: parentHandle.agent,
agentOptions: { model: 'mock' },
agentOptions: { provider: 'mock', model: 'mock' },
})
const result = await run.result
expect(result.stopReason).toBe('completed')
@@ -302,17 +294,13 @@ describe('dsh-subagent-spawn', () => {
// Rebuild the stack by hand so we hold the backend's fiber.
const ctx = new Context()
const adapter = new MockAdapter(['hang'])
await ctx.plugin(LlmService)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(AgentRegistry)
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(Invariants)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
const fiber = await ctx.plugin(spawn, { providerName: 'spawn' })
ctx.llm.registerAdapter(['mock'], adapter)
const parent = ctx.agentLoop.create(SessionId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
const controller = new AbortController()
const run = await start(ctx, 'spawn', {
prompt: [{ type: 'text', text: 'q' }],
@@ -335,15 +323,11 @@ describe('dsh-subagent-spawn', () => {
it('a start racing an already-unloading backend cannot begin child creation', async () => {
const ctx = new Context()
await ctx.plugin(LlmService)
await ctx.plugin(SessionStore)
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(AgentRegistry)
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
const fiber = await ctx.plugin(spawn, { providerName: 'spawn' })
const parent = ctx.agentLoop.create(SessionId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
const parentEffects = parent.ctx.fiber.getEffects().length
const published: string[] = []
ctx.on('session/created', () => void published.push('session/created'))
@@ -437,7 +421,7 @@ describe('dsh-subagent-spawn', () => {
// A handle-owned parent we can dispose (config agents dispose with the loop fiber).
const parentHandle = await ctx.agents.create({
sessionId: SessionId('doomed-s'),
agentOptions: { model: 'mock' },
agentOptions: { provider: 'mock', model: 'mock' },
})
await parentHandle.dispose()
const before = ctx.agents.list().length
@@ -459,7 +443,7 @@ describe('dsh-subagent-spawn', () => {
const { ctx } = await setup([])
const parentHandle = await ctx.agents.create({
sessionId: SessionId('setup-race-parent-session'),
agentOptions: { model: 'mock' },
agentOptions: { provider: 'mock', model: 'mock' },
})
const published: string[] = []
ctx.on('session/created', () => void published.push('session/created'))