scaffold-hermetic.e2e.ts 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { expect, it } from 'vitest'
  5. import type {} from '@deepseek-ai/dsh-skill'
  6. import { SessionId } from '@deepseek-ai/dsh-session'
  7. import type {} from '@deepseek-ai/dsh-agent-presets'
  8. import { launchWebScaffold, type WebScaffold } from './scaffold.ts'
  9. async function writeSkill(root: string, name: string): Promise<void> {
  10. const bundle = join(root, name)
  11. await mkdir(bundle, { recursive: true })
  12. await writeFile(join(bundle, 'SKILL.md'), `---
  13. name: ${name}
  14. description: Must not enter the Web replay scaffold
  15. ---
  16. Ambient host state.
  17. `)
  18. }
  19. it('isolates replay skill discovery from every ambient host root', async () => {
  20. const ambient = await mkdtemp(join(tmpdir(), 'dsh-web-ambient-skills-'))
  21. const dshHome = join(ambient, 'dsh-home')
  22. const agentsHome = join(ambient, 'agents-home')
  23. const bundled = join(ambient, 'bundled')
  24. await Promise.all([
  25. writeSkill(join(dshHome, 'skills'), 'ambient-dsh'),
  26. writeSkill(join(agentsHome, 'skills'), 'ambient-agents'),
  27. writeSkill(bundled, 'ambient-bundled'),
  28. ])
  29. const originalDshHome = process.env.DSH_HOME
  30. const originalAgentsHome = process.env.DSH_AGENTS_HOME
  31. const originalBundled = process.env.DSH_BUNDLED_SKILL_DIR
  32. process.env.DSH_HOME = dshHome
  33. process.env.DSH_AGENTS_HOME = agentsHome
  34. process.env.DSH_BUNDLED_SKILL_DIR = bundled
  35. let scaffold: WebScaffold | undefined
  36. try {
  37. scaffold = await launchWebScaffold()
  38. const ctx = scaffold.ctx
  39. // Local skill discovery belongs to the agent's preset LAYER of the host
  40. // registry, so the roots under test are only reachable through a composed
  41. // agent's view — the same scope the `skills/list` Remote resolves for a
  42. // browser request about a session.
  43. const handle = await ctx.agents.create({
  44. sessionId: SessionId('hermetic-skills'),
  45. setup: agentCtx => ctx.agentPresets.mount(agentCtx).then(() => undefined),
  46. })
  47. try {
  48. const skills = ctx.get('skills')
  49. if (skills === undefined) throw new Error('the composition mounts no skill registry')
  50. const names = (await skills.list({ cwd: scaffold.workspaceCwd, scope: handle.agent })).map(skill => skill.name)
  51. expect(names).not.toContain('ambient-dsh')
  52. expect(names).not.toContain('ambient-agents')
  53. expect(names).not.toContain('ambient-bundled')
  54. } finally {
  55. await handle.dispose()
  56. }
  57. } finally {
  58. try {
  59. await scaffold?.close()
  60. } finally {
  61. if (originalDshHome === undefined) delete process.env.DSH_HOME
  62. else process.env.DSH_HOME = originalDshHome
  63. if (originalAgentsHome === undefined) delete process.env.DSH_AGENTS_HOME
  64. else process.env.DSH_AGENTS_HOME = originalAgentsHome
  65. if (originalBundled === undefined) delete process.env.DSH_BUNDLED_SKILL_DIR
  66. else process.env.DSH_BUNDLED_SKILL_DIR = originalBundled
  67. await rm(ambient, { recursive: true, force: true })
  68. }
  69. }
  70. })