fix: harden skill discovery

This commit is contained in:
Yichen Jiang
2026-07-05 18:33:27 +08:00
parent f626e569a4
commit dca2cc257d
29 changed files with 555 additions and 61 deletions

View File

@@ -47,6 +47,7 @@
"@deepseek-ai/dsh-tool-bash": "workspace:^",
"@deepseek-ai/dsh-tool-skill": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"cordis": "^4.0.0-rc.6"
"cordis": "^4.0.0-rc.6",
"schemastery": "^3.18.0"
}
}

View File

@@ -44,11 +44,13 @@
import type { Context } from 'cordis'
import Timer from '@cordisjs/plugin-timer'
import z from 'schemastery'
import type Schema from 'schemastery'
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 SkillService from '@deepseek-ai/dsh-skill'
import SkillService, { type Config as SkillConfig } from '@deepseek-ai/dsh-skill'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import * as invariants from '@deepseek-ai/dsh-invariants'
import * as toolBash from '@deepseek-ai/dsh-tool-bash'
@@ -58,16 +60,39 @@ import AgentLoop, { type Config as AgentLoopConfig } from '@deepseek-ai/dsh-agen
export const name = 'agent-core'
/**
* Bundle config: the agent-loop `agents` list, forwarded verbatim. Default `[]`
* an app that pre-creates no agents (the ACP bridge creates them on demand at
* `session/new`) simply omits it; an app that needs a pre-created `main` (the
* stdio chat) supplies one. This IS {@link AgentLoopConfig}, so the schema and
* the forwarded shape can never drift.
* Bundle config: the agent-loop `agents` list plus skill discovery config.
* Default `agents: []` means an app that pre-creates no agents (the ACP bridge
* creates them on demand at `session/new`) can omit it; an app that needs a
* pre-created `main` (the stdio chat) supplies one. `skills` is forwarded to
* {@link @deepseek-ai/dsh-skill}, so leaf cordis.yml files can change DSH/user
* skill roots and caps without code changes.
*/
export type Config = AgentLoopConfig
export interface Config extends AgentLoopConfig {
/** Skill discovery roots, system-skill installation, and prompt/cache bounds. */
skills?: SkillConfig
}
/** Forward the loop's own schema so validation + defaulting stay identical. */
export const Config = AgentLoop.Config
/** Local schema for the forwarded skill config. Keep this in sync with `SkillService.Config`. */
export const SkillConfigSchema: Schema<SkillConfig> = z.object({
dshHome: z.string(),
agentsHome: z.string(),
extraRoots: z.array(z.string()).default([]),
installSystemSkills: z.boolean().default(true),
promptFieldMaxLength: z.number().default(500),
collectCacheMaxEntries: z.number().default(128),
})
/** Bundle schema: keep the loop agent shape aligned and expose skill config. */
export const Config: Schema<Config> = z.object({
agents: z.array(z.object({
id: z.string().required(),
model: z.string(),
systemPrompt: z.string(),
cwd: z.string(),
resumeSessionId: z.string(),
})).default([]),
skills: SkillConfigSchema,
}) as unknown as Schema<Config>
/**
* Load the spine. Each `ctx.plugin(...)` mounts one child of the bundle fiber;
@@ -83,10 +108,12 @@ export function apply(ctx: Context, config: Config): void {
ctx.plugin(SessionStore)
ctx.plugin(SystemPrompt)
ctx.plugin(ToolRegistry)
ctx.plugin(SkillService)
ctx.plugin(SkillService, config.skills ?? {})
ctx.plugin(AgentRegistry)
ctx.plugin(invariants)
ctx.plugin(toolBash)
ctx.plugin(toolSkill)
ctx.plugin(AgentLoop, { agents: config.agents })
}
export type { SkillConfig }

View File

@@ -19,7 +19,9 @@ import { AgentId } from '@deepseek-ai/dsh-agent'
*/
async function mount(config?: agentCore.Config): Promise<Context> {
const oldDshHome = process.env.DSH_HOME
const oldAgentsHome = process.env.DSH_AGENTS_HOME
process.env.DSH_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-core-home-'))
process.env.DSH_AGENTS_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-core-agents-'))
const ctx = new Context()
try {
await ctx.plugin(agentCore, config)
@@ -33,6 +35,11 @@ async function mount(config?: agentCore.Config): Promise<Context> {
} else {
process.env.DSH_HOME = oldDshHome
}
if (oldAgentsHome === undefined) {
delete process.env.DSH_AGENTS_HOME
} else {
process.env.DSH_AGENTS_HOME = oldAgentsHome
}
}
}
@@ -78,6 +85,21 @@ describe('dsh-agent-core bundle', () => {
await ctx.fiber.dispose()
})
it('forwards skill config to the skill service', async () => {
const home = await mkdtemp(join(tmpdir(), 'dsh-agent-core-skill-home-'))
const agentsHome = await mkdtemp(join(tmpdir(), 'dsh-agent-core-skill-agents-'))
const ctx = await mount({
agents: [],
skills: {
dshHome: join(home, '.dsh'),
agentsHome: join(agentsHome, '.agents'),
installSystemSkills: false,
},
})
expect(await ctx.skills.list()).toEqual([])
await ctx.fiber.dispose()
})
it('re-exports the loop config schema as its own', () => {
expect(agentCore.Config).toBeDefined()
expect(agentCore.name).toBe('agent-core')

View File

@@ -14,6 +14,9 @@
{
"path": "../../../vendor/timer"
},
{
"path": "../../../vendor/schemastery"
},
{
"path": "../../llm/llm"
},