feat(agent-presets): add Codex and Claude Code subagent tools

This commit is contained in:
pku-xht
2026-08-10 12:45:05 +08:00
parent e637f95d78
commit b1d67a6935
45 changed files with 1708 additions and 45 deletions

View File

@@ -8,7 +8,7 @@ import { boot, healProfilesModuleFallback, loadOverlayPatches } from '@deepseek-
import { SessionId } from '@deepseek-ai/dsh-session'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { PatchOptions } from '@cordisjs/plugin-include'
import { beforeAll, describe, expect, it } from 'vitest'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { resolveSessionPreset, SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-agent-presets'
import { CallId } from '@deepseek-ai/dsh-llm'
@@ -95,6 +95,18 @@ async function bootWeb(settingsFile: string, extra: PatchOptions[] = []): Promis
const toolNames = (ctx: Context, agent?: Agent): string[] =>
ctx.tools.schemas(agent).map(schema => schema.name).sort()
function enablePresetTool(composition: string, id: string): string {
const row = ` - id: ${id}\n`
const start = composition.indexOf(row)
if (start < 0) throw new Error(`missing preset row ${id}`)
const end = composition.indexOf('\n - id:', start + row.length)
const disabled = composition.indexOf(' disabled: true\n', start)
if (disabled < 0 || (end >= 0 && disabled > end)) {
throw new Error(`preset row ${id} is not disabled`)
}
return composition.slice(0, disabled) + composition.slice(disabled + ' disabled: true\n'.length)
}
let ctx: Context
beforeAll(async () => {
const settingsFile = join(await mkdtemp(join(tmpdir(), 'dsh-web-presets-')), 'settings.yaml')
@@ -120,6 +132,24 @@ describe('the shipped Web composition', () => {
expect(ctx.agentPresets.defaultId).toBe('standard')
})
it('keeps product providers on the host while shipped presets leave their tools disabled', async () => {
expect(ctx.subagents.list()).toEqual(expect.arrayContaining([
'spawn', 'fork', 'codex', 'claude-code',
]))
const handle = await ctx.agents.create({
sessionId: SessionId('preset-products-disabled'),
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'standard').then(() => undefined),
})
try {
expect(toolNames(ctx, handle.agent)).not.toEqual(expect.arrayContaining([
'subagent_codex', 'subagent_claude_code',
]))
} finally {
await handle.dispose()
}
})
it('composes the full agent from `standard`', async () => {
const handle = await ctx.agents.create({
sessionId: SessionId('preset-standard'),
@@ -355,6 +385,98 @@ describe('the shipped Web composition', () => {
})
})
describe('product subagent rows in user presets', () => {
let productCtx: Context
const ids = ['products-none', 'products-codex', 'products-claude', 'products-both'] as const
beforeAll(async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-product-presets-'))
const userRoot = join(root, 'presets')
const settingsFile = join(root, 'settings.yaml')
const standard = await readFile(join(CONFIG_DIR, 'agent-presets', 'standard', 'agent.cordis.yml'), 'utf8')
await writeFile(settingsFile, '{}\n')
for (const id of ids) {
let composition = standard
if (id === 'products-codex' || id === 'products-both') {
composition = enablePresetTool(composition, 'tool-subagent-codex')
}
if (id === 'products-claude' || id === 'products-both') {
composition = enablePresetTool(composition, 'tool-subagent-claude-code')
}
const directory = join(userRoot, id)
await mkdir(directory, { recursive: true })
await writeFile(join(directory, 'agent.cordis.yml'), composition)
}
productCtx = await bootWeb(settingsFile, [{
id: 'agent-presets',
config: {
default: 'standard',
roots: [
{ path: join(CONFIG_DIR, 'agent-presets'), trust: 'system' },
{ path: userRoot, trust: 'user' },
],
},
}])
}, 120_000)
afterAll(async () => {
await productCtx.fiber.dispose()
})
it('composes none, either product, or both without changing the shared host registry', async () => {
const expected = new Map<string, string[]>([
['products-none', []],
['products-codex', ['subagent_codex']],
['products-claude', ['subagent_claude_code']],
['products-both', ['subagent_claude_code', 'subagent_codex']],
])
expect(productCtx.subagents.list()).toEqual(expect.arrayContaining([
'spawn', 'fork', 'codex', 'claude-code',
]))
for (const [id, productTools] of expected) {
const handle = await productCtx.agents.create({
sessionId: SessionId(`preset-${id}`),
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, id).then(() => undefined),
})
try {
const tools = toolNames(productCtx, handle.agent)
expect(tools.filter(name => name === 'subagent_codex' || name === 'subagent_claude_code'))
.toEqual(productTools)
} finally {
await handle.dispose()
}
}
})
it('applies a product-row edit only to later sessions on the preset', async () => {
const preset = await productCtx.agentPresets.resolve('products-none')
const original = await readFile(preset.path, 'utf8')
const existing = await productCtx.agents.create({
sessionId: SessionId('preset-product-generation-existing'),
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, 'products-none').then(() => undefined),
})
try {
expect(toolNames(productCtx, existing.agent)).not.toContain('subagent_codex')
await writeFile(preset.path, enablePresetTool(original, 'tool-subagent-codex'))
const later = await productCtx.agents.create({
sessionId: SessionId('preset-product-generation-later'),
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, 'products-none').then(() => undefined),
})
try {
expect(toolNames(productCtx, existing.agent)).not.toContain('subagent_codex')
expect(toolNames(productCtx, later.agent)).toContain('subagent_codex')
} finally {
await later.dispose()
}
} finally {
await existing.dispose()
await writeFile(preset.path, original)
}
})
})
describe('a switch survives the session', () => {
it('records the choice so the log states what the agent runs', async () => {
const handle = await ctx.agents.create({