소스 검색

test(cli): prove the global skill layer reaches preset-composed agents

The shipped-Web e2e enables the bundled badge skill as a stand-in for a
repository plugin's deployment-level provider and asserts the layering both
ways: a standard-preset agent's view merges the global layer with its
preset's local discovery and its loader tool resolves the global skill,
while the host view stays global-only and a core-web agent can read the
layer but composes no loader tool.
Yichen Jiang 1 개월 전
부모
커밋
4b9bde551b
1개의 변경된 파일과 65개의 추가작업 그리고 0개의 파일을 삭제
  1. 65 0
      apps/cli/tests/web-agent-presets.e2e.ts

+ 65 - 0
apps/cli/tests/web-agent-presets.e2e.ts

@@ -10,6 +10,8 @@ import type { PatchOptions } from '@cordisjs/plugin-include'
 import { beforeAll, describe, expect, it } from 'vitest'
 import { settingsNamespace } from '@deepseek-ai/dsh-settings'
 import { SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-agent-presets'
+import { CallId } from '@deepseek-ai/dsh-llm'
+import type {} from '@deepseek-ai/dsh-skill'
 import type {} from '@deepseek-ai/dsh-tools'
 
 const CONFIG_DIR = fileURLToPath(new URL('../config/', import.meta.url))
@@ -41,6 +43,10 @@ async function bootWeb(settingsFile: string): Promise<Context> {
     // Waits for `httpServer`, which the disabled webserver above provides.
     { id: 'web-runtime', disabled: true },
     { id: 'telemetry-otel', disabled: true },
+    // A deployment-level skill on the host registry's GLOBAL layer — the same
+    // registration shape a repository plugin's skill root uses. The layered
+    // skills test below proves it reaches preset-composed agents.
+    { id: 'skill-badge', disabled: false },
     { id: 'modules', disabled: true },
     { id: 'connection', disabled: true },
     // NOT a side-effect row: the api-proxy cannot mount in THIS layer at all,
@@ -161,6 +167,65 @@ describe('the shipped Web composition', () => {
     }
   })
 
+  it('merges the global skill layer into a preset agent\'s catalog, keeping local discovery preset-side', async () => {
+    const proj = await mkdtemp(join(tmpdir(), 'dsh-preset-skill-proj-'))
+    await mkdir(join(proj, '.dsh', 'skills', 'project-proof'), { recursive: true })
+    await writeFile(join(proj, '.dsh', 'skills', 'project-proof', 'SKILL.md'), [
+      '---',
+      'name: project-proof',
+      'description: Proves the preset layer discovers project skills beside global ones.',
+      '---',
+      '',
+      'Project proof body.',
+      '',
+    ].join('\n'))
+
+    const handle = await ctx.agents.create({
+      sessionId: SessionId('preset-skills-standard'),
+      setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'standard').then(() => undefined),
+    })
+    try {
+      // The host (global) view carries the deployment-level provider alone:
+      // local discovery moved behind the presets with `skill-local`.
+      expect((await ctx.skills.list({ cwd: proj })).map(skill => skill.name)).toEqual(['dsh-badge'])
+
+      // The standard agent's view merges the global layer with its preset's
+      // own local discovery over the session cwd.
+      const scoped = (await ctx.skills.list({ cwd: proj, scope: handle.agent })).map(skill => skill.name)
+      expect(scoped).toContain('dsh-badge')
+      expect(scoped).toContain('project-proof')
+
+      // The preset's own loader tool resolves the global-layer skill.
+      const loaded = await ctx.tools.execute({
+        callId: CallId('preset-skills-load'),
+        name: 'skill',
+        arguments: { name: 'dsh-badge' },
+        signal: new AbortController().signal,
+        agent: handle.agent,
+      })
+      expect(loaded.isError).toBe(false)
+      expect(JSON.stringify(loaded.content)).toContain('powered by dsh')
+    } finally {
+      await handle.dispose()
+    }
+  })
+
+  it('shows a core-web agent the global layer but no loader tool', async () => {
+    const handle = await ctx.agents.create({
+      sessionId: SessionId('preset-skills-core-web'),
+      setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'core-web').then(() => undefined),
+    })
+    try {
+      // Layer visibility is the registry's; whether an agent can USE skills
+      // stays the preset's choice — core-web mounts no `tool-skill`, so its
+      // tool table has no loader even though the global layer is readable.
+      expect((await ctx.skills.list({ scope: handle.agent })).map(skill => skill.name)).toContain('dsh-badge')
+      expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'str_replace_editor'])
+    } finally {
+      await handle.dispose()
+    }
+  })
+
   it('never rewrites the preset file it composed from', async () => {
     // The Loader persists a tree whose plugin self-disposed, and tearing an
     // agent down disposes its whole subtree. Inherited, that rewrote the