Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-8-authoring

# Conflicts:
#	apps/cli/tests/web-agent-presets.e2e.ts
#	packages/client/connection/README.i18n.yaml
#	packages/client/connection/README.md
#	packages/client/connection/README.zh.md
#	packages/client/ui-conversation/src/client/contract/slots.ts
#	packages/client/ui-primitives/tests/icons.spec.tsx
#	packages/client/ui-settings/src/client/contract/slots.ts
#	packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts
This commit is contained in:
Yichen Jiang
2026-08-09 23:02:37 +08:00
825 changed files with 2990 additions and 2066 deletions

View File

@@ -64,11 +64,11 @@ describe('parseDshArgs', () => {
.toEqual({ mode: 'dump-config', profile: 'web', defaultOnly: true, patches: [] })
})
it('rejects missing profile, removed flags, and contradictory inputs', () => {
it('rejects missing profile, flags outside the current grammar, and contradictory inputs', () => {
expect(exitCode([])).toBe(1)
expect(exitCode(['tui'])).toBe(1) // a bare word is a task without --profile
expect(exitCode(['--config', 'c.yml'])).toBe(1) // removed
expect(exitCode(['-p', 'task'])).toBe(1) // removed
expect(exitCode(['--config', 'c.yml'])).toBe(1) // outside the current grammar
expect(exitCode(['-p', 'task'])).toBe(1) // outside the current grammar
expect(exitCode(['--profile', 'headless', 'task'])).toBe(1) // tasks belong to `run`
expect(exitCode(['run'])).toBe(1)
expect(exitCode(['run', ''])).toBe(1)

View File

@@ -174,7 +174,7 @@ function createEnvironmentProbeProfile(home: string, project: string): void {
}
describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', () => {
it('requires --profile and rejects removed commands', async () => {
it('requires --profile and rejects inputs outside the current grammar', async () => {
const bare = await runBuiltBin()
expect(bare.code).toBe(1)
expect(bare.stdout).toBe('')
@@ -185,8 +185,8 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
expect(help.stdout).toContain('dsh run "run the tests"')
expect(help.stdout).toContain('dsh plugin --profile')
expect(help.stdout).not.toMatch(/^\s+(?:tui|meta|upgrade)\b/mu)
for (const removed of [['tui'], ['--config', 'x.yml'], ['-p', 'task'], ['--profile', 'headless', 'task']]) {
const result = await runBuiltBin(removed)
for (const outsideGrammar of [['tui'], ['--config', 'x.yml'], ['-p', 'task'], ['--profile', 'headless', 'task']]) {
const result = await runBuiltBin(outsideGrammar)
expect(result.code).toBe(1)
}
}, 30_000)
@@ -297,9 +297,9 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
}, 30_000)
it('reports a patch-overlay boot failure without hanging', async () => {
// The HMR main watcher's initial scan once refreshed the include
// mid-initial-apply, deadlocking the failing apply's rollback against the
// refresh drain: dsh exited 13 with no diagnostic instead of settling
// An HMR main-watcher initial scan that refreshes the include
// mid-initial-apply deadlocks the failing apply's rollback against the
// refresh drain: dsh exits 13 with no diagnostic instead of settling
// ([Agent Note](../../../.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md)).
const home = mkdtempSync(join(tmpdir(), 'dsh-invalid-patch-'))
try {

View File

@@ -12,6 +12,9 @@
"prepack": "tsc --noEmit && tsdown src/plugin.ts src/mcp-server.ts --no-config --tsconfig tsconfig.json --out-dir lib --platform node --target es2024 --clean && dsh-plugin-prepare"
},
"dsh": {
"skills": [
"../skills"
],
"mcpServers": "./.mcp.json",
"entry": "./lib/plugin.mjs"
},

View File

@@ -0,0 +1,6 @@
---
name: github-source-proof
description: Proves that dsh installed a private repository Plugin from an exact GitHub source.
---
This skill exists only in the GitHub repository source fixture.

View File

@@ -125,11 +125,7 @@ async function startPublishedPackageRegistry(root: string): Promise<PublishedPac
}
describe.skipIf(!enabled)('dsh run GitHub repository Plugin installation', () => {
// The fixture deliberately carries no skill root: this composition's agent
// plane lives behind agent presets, whose per-preset `skills` realm has no
// seam for a deployment-level provider yet — see the repository-plugin
// README's Known Limitations.
it('installs the published prepare dependency, then builds and runs MCP and TypeScript Plugin contributions from a private exact GitHub source', async () => {
it('installs the published prepare dependency, then builds and runs skill, MCP, and TypeScript Plugin contributions from a private exact GitHub source', async () => {
expect(existsSync(dshBin), 'the repository Plugin acceptance must run the built dsh entry').toBe(true)
expect(source, 'DSH_GITHUB_REPOSITORY_PLUGIN_SOURCE is required by this CI lane').toMatch(
/^github:[^/\s#&]+\/[^/\s#&]+#[0-9a-f]{40}&path:\/.*\/\.dsh-plugin$/u,
@@ -205,6 +201,9 @@ describe.skipIf(!enabled)('dsh run GitHub repository Plugin installation', () =>
expect(registry.requests, runtimeDiagnostic).toContain('GET /@deepseek-ai/dsh-repository-plugin/-/dsh-repository-plugin-0.0.1.tgz')
const firstRequest = JSON.stringify(server.requests[0]!.body)
const secondRequest = JSON.stringify(server.requests[1]!.body)
expect(firstRequest, runtimeDiagnostic).toContain(
'Proves that dsh installed a private repository Plugin from an exact GitHub source.',
)
expect(firstRequest, runtimeDiagnostic).toContain('mcp__github_repository__proof')
expect(firstRequest, runtimeDiagnostic).toContain('Proves that an MCP server compiled from the exact GitHub repository package is active.')
expect(secondRequest, runtimeDiagnostic).toContain('MCP_FROM_GITHUB_REPOSITORY')
@@ -222,6 +221,7 @@ describe.skipIf(!enabled)('dsh run GitHub repository Plugin installation', () =>
prepack: 'tsc --noEmit && tsdown src/plugin.ts src/mcp-server.ts --no-config --tsconfig tsconfig.json --out-dir lib --platform node --target es2024 --clean && dsh-plugin-prepare',
},
dsh: {
skills: ['../skills'],
mcpServers: './.mcp.json',
entry: './lib/plugin.mjs',
},
@@ -235,6 +235,8 @@ describe.skipIf(!enabled)('dsh run GitHub repository Plugin installation', () =>
typescript: '6.0.3',
},
})
expect(readFileSync(join(installed, 'dsh-plugin-assets/skills/0/github-source-proof/SKILL.md'), 'utf8'))
.toContain('This skill exists only in the GitHub repository source fixture.')
expect(readFileSync(join(installed, 'dsh-plugin-assets/.mcp.json'), 'utf8')).toContain('lib/mcp-server.mjs')
expect(readFileSync(join(installed, 'lib/plugin.mjs'), 'utf8')).toContain('TS_PLUGIN_FROM_GITHUB_REPOSITORY')
expect(readFileSync(join(installed, 'lib/mcp-server.mjs'), 'utf8')).toContain('MCP_FROM_GITHUB_REPOSITORY')

View File

@@ -1,3 +1,4 @@
import { randomUUID } from 'node:crypto'
import { mkdir, mkdtemp, readFile, stat, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
@@ -10,6 +11,8 @@ import type { PatchOptions } from '@cordisjs/plugin-include'
import { 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'
import type {} from '@deepseek-ai/dsh-skill'
import type {} from '@deepseek-ai/dsh-tools'
const CONFIG_DIR = fileURLToPath(new URL('../config/', import.meta.url))
@@ -55,6 +58,10 @@ async function bootWeb(settingsFile: string, extra: PatchOptions[] = []): Promis
// agent's capabilities, which is all this file asserts.
{ 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 },
// The shipped `-auto` chooser resolves its interaction from a running
@@ -184,11 +191,11 @@ describe('the shipped Web composition', () => {
// And it keeps the standard agent's own tools rather than replacing them.
expect(tools).toEqual(expect.arrayContaining(['bash', 'read', 'edit', 'skill']))
// The skill registry sits in this preset's entry-local realm, so it is
// invisible to the host AND to the agent's own scope — only the rows
// inside that group resolve it, which is what makes `tool-skill` the
// agent's own rather than a shared one.
expect(ctx.get('skills')).toBeUndefined()
// The preset's own authoring skill registers into ITS layer of the host
// registry: the cordis agent's view carries it, the global view does not.
const scoped = (await ctx.skills.list({ scope: handle.agent })).map(skill => skill.name)
expect(scoped).toContain('editing-cordis-compositions')
expect((await ctx.skills.list()).map(skill => skill.name)).not.toContain('editing-cordis-compositions')
} finally {
await handle.dispose()
}
@@ -249,6 +256,67 @@ describe('the shipped Web composition', () => {
expect((await readFile(skill, 'utf8')).startsWith('---\nname: editing-cordis-compositions')).toBe(true)
})
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({
// Unique per run: the composition persists into the ambient DSH home,
// and a fixed id would collide with a log an earlier run left there.
sessionId: SessionId(`preset-skills-standard-${randomUUID()}`),
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 minimal agent the global layer but no loader tool', async () => {
const handle = await ctx.agents.create({
sessionId: SessionId(`preset-skills-minimal-${randomUUID()}`),
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'minimal').then(() => undefined),
})
try {
// Layer visibility is the registry's; whether an agent can USE skills
// stays the preset's choice — minimal 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