The bundle split emptied `apps/cli`'s plugin dependencies, and the flat module fallback links only that manifest's closure — so a preset row naming `@deepseek-ai/dsh-persona` resolved to nothing and every preset mount failed, leaving each session with an agent that had no tools, persona, or token meter. Which packages the shipped presets compose is not implied by any bundle: the presets live beside this manifest, so this is where their closure is declared. The hermetic skill test now addresses the registry through the composed agent, the only shape that can see a preset's `isolate` realm.
76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { expect, it } from 'vitest'
|
|
import type {} from '@deepseek-ai/dsh-skill'
|
|
import { SessionId } from '@deepseek-ai/dsh-session'
|
|
import type {} from '@deepseek-ai/dsh-agent-presets'
|
|
import { launchWebScaffold, type WebScaffold } from './scaffold.ts'
|
|
|
|
async function writeSkill(root: string, name: string): Promise<void> {
|
|
const bundle = join(root, name)
|
|
await mkdir(bundle, { recursive: true })
|
|
await writeFile(join(bundle, 'SKILL.md'), `---
|
|
name: ${name}
|
|
description: Must not enter the Web replay scaffold
|
|
---
|
|
|
|
Ambient host state.
|
|
`)
|
|
}
|
|
|
|
it('isolates replay skill discovery from every ambient host root', async () => {
|
|
const ambient = await mkdtemp(join(tmpdir(), 'dsh-web-ambient-skills-'))
|
|
const dshHome = join(ambient, 'dsh-home')
|
|
const agentsHome = join(ambient, 'agents-home')
|
|
const bundled = join(ambient, 'bundled')
|
|
await Promise.all([
|
|
writeSkill(join(dshHome, 'skills'), 'ambient-dsh'),
|
|
writeSkill(join(agentsHome, 'skills'), 'ambient-agents'),
|
|
writeSkill(bundled, 'ambient-bundled'),
|
|
])
|
|
|
|
const originalDshHome = process.env.DSH_HOME
|
|
const originalAgentsHome = process.env.DSH_AGENTS_HOME
|
|
const originalBundled = process.env.DSH_BUNDLED_SKILL_DIR
|
|
process.env.DSH_HOME = dshHome
|
|
process.env.DSH_AGENTS_HOME = agentsHome
|
|
process.env.DSH_BUNDLED_SKILL_DIR = bundled
|
|
let scaffold: WebScaffold | undefined
|
|
try {
|
|
scaffold = await launchWebScaffold()
|
|
const ctx = scaffold.ctx
|
|
// The skill registry belongs to one agent's preset, behind an `isolate`
|
|
// realm the host cannot resolve by name — so the roots under test are only
|
|
// reachable through a composed agent, which is also the only shape that
|
|
// ever asks. `serviceFor` is the same addressing the gateway's `skill.list`
|
|
// uses for a browser request about a session.
|
|
const handle = await ctx.agents.create({
|
|
sessionId: SessionId('hermetic-skills'),
|
|
setup: agentCtx => ctx.agentPresets.mount(agentCtx).then(() => undefined),
|
|
})
|
|
try {
|
|
const skills = ctx.agentPresets.serviceFor(handle.agent, 'skills')
|
|
if (skills === undefined) throw new Error('composed agent mounts no skill registry')
|
|
const names = (await skills.list({ cwd: scaffold.workspaceCwd })).map(skill => skill.name)
|
|
expect(names).not.toContain('ambient-dsh')
|
|
expect(names).not.toContain('ambient-agents')
|
|
expect(names).not.toContain('ambient-bundled')
|
|
} finally {
|
|
await handle.dispose()
|
|
}
|
|
} finally {
|
|
try {
|
|
await scaffold?.close()
|
|
} finally {
|
|
if (originalDshHome === undefined) delete process.env.DSH_HOME
|
|
else process.env.DSH_HOME = originalDshHome
|
|
if (originalAgentsHome === undefined) delete process.env.DSH_AGENTS_HOME
|
|
else process.env.DSH_AGENTS_HOME = originalAgentsHome
|
|
if (originalBundled === undefined) delete process.env.DSH_BUNDLED_SKILL_DIR
|
|
else process.env.DSH_BUNDLED_SKILL_DIR = originalBundled
|
|
await rm(ambient, { recursive: true, force: true })
|
|
}
|
|
}
|
|
})
|