Merge branch 'worktree/agent-execution-context-rfc' into worktree/explicit-turn-signal

This commit is contained in:
Yichen Jiang
2026-07-18 21:33:17 +08:00
683 changed files with 37770 additions and 8712 deletions

View File

@@ -33,16 +33,14 @@
},
"devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-agent-execution": "workspace:^",
"@deepseek-ai/dsh-agent-loop": "workspace:^",
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-subagent-inprocess": "workspace:^",
"@deepseek-ai/dsh-subagent-spawn": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"@cordisjs/plugin-loader": "^1.0.0-rc.5",
"cordis": "^4.0.0-rc.7"
}

View File

@@ -1,12 +1,8 @@
import { describe, expect, it } from 'vitest'
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, { AgentId } from '@deepseek-ai/dsh-agent'
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
import { AgentId } from '@deepseek-ai/dsh-agent'
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 * as Spawn from '@deepseek-ai/dsh-subagent-spawn'
@@ -27,19 +23,14 @@ function start(ctx: Context, provider: string, request: Omit<SubagentStartReques
*/
async function setup(script: Script) {
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(Invariants)
await ctx.plugin(AgentExecutionProvider)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
await ctx.plugin(Spawn, { providerName: 'spawn' })
await ctx.plugin(fork, { providerName: 'fork' })
ctx.llm.registerAdapter(['mock'], new MockAdapter(script))
const parent = ctx.agentLoop.create(AgentId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(AgentId('parent'), { provider: 'mock', model: 'mock' })
return { ctx, parent }
}

View File

@@ -1,13 +1,9 @@
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, { AgentId } from '@deepseek-ai/dsh-agent'
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
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, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
@@ -34,18 +30,13 @@ const emptyStop: StreamChunk[] = [{ type: 'finish', reason: { kind: 'stop' } }]
*/
async function setup(script: Script) {
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(Invariants)
await ctx.plugin(AgentExecutionProvider)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
await ctx.plugin(fork, { providerName: 'fork' })
ctx.llm.registerAdapter(['mock'], new MockAdapter(script))
const parent = ctx.agentLoop.create(AgentId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(AgentId('parent'), { provider: 'mock', model: 'mock' })
return { ctx, parent }
}

View File

@@ -32,8 +32,8 @@
},
"devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-agent-execution": "workspace:^",
"@deepseek-ai/dsh-agent-loop": "workspace:^",
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",

View File

@@ -107,8 +107,10 @@ export async function startInProcessRun(
const childId = AgentId(randomUUID())
const seedLength = options.seed?.length ?? 0
const parentHeader = parent.session.header
const parentProvider = parent.options.provider
const parentModel = parent.options.model
const agentOptions: AgentOptions = {
...parentProvider !== undefined ? { provider: parentProvider } : {},
...parentModel !== undefined ? { model: parentModel } : {},
...request.agentOptions,
subagentDepth: childDepth,

View File

@@ -1,13 +1,10 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import LlmService, { CallId, type ContentBlock, type GenerateOptions } 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, { AgentId } from '@deepseek-ai/dsh-agent'
import { CallId, type ContentBlock, type GenerateOptions } from '@deepseek-ai/dsh-llm'
import { AgentId } from '@deepseek-ai/dsh-agent'
import type { ContinuationDecision } from '@deepseek-ai/dsh-agent'
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
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 type { Config as ToolConfig, StructuredOutputSchema } from '@deepseek-ai/dsh-tools'
@@ -44,10 +41,9 @@ const SCHEMA: StructuredOutputSchema = {
async function setup(script: Script, options: SetupOptions = {}) {
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, { mode: options.toolMode ?? 'native' })
await mountAgentLoopTestDependencies(ctx, {
tools: { mode: options.toolMode ?? 'native' },
})
if (options.toolMode === 'code' || options.toolMode === 'both') {
ctx.provide('codeRuntime', {
language: 'typescript',
@@ -55,9 +51,7 @@ async function setup(script: Script, options: SetupOptions = {}) {
run: options.codeRun ?? (() => Promise.resolve({ logs: [] })),
} as never)
}
await ctx.plugin(AgentRegistry)
await ctx.plugin(Invariants)
await ctx.plugin(AgentExecutionProvider)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
const disposeProvider = ctx.subagents.registerProvider({
@@ -67,7 +61,7 @@ async function setup(script: Script, options: SetupOptions = {}) {
start: (request: SubagentStartRequest) => startInProcessRun(request, {}),
})
ctx.llm.registerAdapter(['mock'], adapter)
const parent = ctx.agentLoop.create(AgentId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(AgentId('parent'), { provider: 'mock', model: 'mock' })
return { ctx, parent, adapter, disposeProvider }
}

View File

@@ -1,12 +1,8 @@
import { describe, expect, it } from 'vitest'
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, { AgentId, type Agent } from '@deepseek-ai/dsh-agent'
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
import { AgentId, 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 * as Invariants from '@deepseek-ai/dsh-invariants'
import SubagentService from '@deepseek-ai/dsh-subagent'
import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
@@ -16,18 +12,13 @@ type Script = ConstructorParameters<typeof MockAdapter>[0]
async function setup(script: Script) {
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(Invariants)
await ctx.plugin(AgentExecutionProvider)
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(SubagentService)
const adapter = new MockAdapter(script)
ctx.llm.registerAdapter(['mock'], adapter)
const parent = ctx.agentLoop.create(AgentId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(AgentId('parent'), { provider: 'mock', model: 'mock' })
return { ctx, parent, adapter }
}

View File

@@ -31,8 +31,8 @@
},
"devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-agent-execution": "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:^",
@@ -40,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,11 +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 AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
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'
@@ -22,18 +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 ctx.plugin(AgentExecutionProvider)
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(AgentId('e2e-parent'), { model: 'deepseek-v4-flash' })
const parent = ctx.agentLoop.create(AgentId('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, { AgentId } from '@deepseek-ai/dsh-agent'
import { SessionId } from '@deepseek-ai/dsh-session'
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
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,18 +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(AgentExecutionProvider)
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(AgentId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(AgentId('parent'), { provider: 'mock', model: 'mock' })
return { ctx, parent, adapter }
}
@@ -237,7 +228,7 @@ describe('dsh-subagent-spawn', () => {
agentId: AgentId('cwd-parent'),
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
@@ -259,7 +250,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')
@@ -305,18 +296,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(AgentExecutionProvider)
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(AgentId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(AgentId('parent'), { provider: 'mock', model: 'mock' })
const controller = new AbortController()
const run = await start(ctx, 'spawn', {
prompt: [{ type: 'text', text: 'q' }],
@@ -339,16 +325,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 ctx.plugin(AgentExecutionProvider)
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(AgentId('parent'), { model: 'mock' })
const parent = ctx.agentLoop.create(AgentId('parent'), { provider: 'mock', model: 'mock' })
const parentEffects = parent.ctx.fiber.getEffects().length
const published: string[] = []
ctx.on('session/created', () => void published.push('session/created'))
@@ -443,7 +424,7 @@ describe('dsh-subagent-spawn', () => {
const parentHandle = await ctx.agents.create({
agentId: AgentId('doomed-parent'),
sessionId: SessionId('doomed-s'),
agentOptions: { model: 'mock' },
agentOptions: { provider: 'mock', model: 'mock' },
})
await parentHandle.dispose()
const before = ctx.agents.list().length
@@ -466,7 +447,7 @@ describe('dsh-subagent-spawn', () => {
const parentHandle = await ctx.agents.create({
agentId: AgentId('setup-race-parent'),
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'))

View File

@@ -67,8 +67,9 @@ export const Config: z<Config> = z.object({
enableRunInBackground: z.boolean().default(true),
// Prevent Schemastery from materializing omitted agentOptions as `{}`.
agentOptions: z.object({
provider: z.string(),
model: z.string(),
}).default(undefined as unknown as { model: string }),
}).default(undefined as unknown as { provider: string; model: string }),
persona: z.string(),
// Preserve omission; Schemastery's `{ allow: [] }` default would deny every tool.
toolFilter: z.object({