Files
deepseek-harness/examples/headless-agent/tests/keyless-smoke.e2e.ts
Tianyi Cui 0664b25cd9 fix(review): validate skill roots at mount and isolate provider default roots
ds-review-bot round 1 on the repository-plugin runtime:
- a manifest-declared skill root absent or non-directory in the installed
  package now fails the plugin load (skill-local treats a missing root as
  legitimately empty, which silently mounted a skill-less plugin)
- includeDefaultRoots: false no longer inherits $DSH_BUNDLED_SKILL_DIR, so
  isolated repository providers see only their explicit roots
- prepared wrapper baseUrl schema requires the file: scheme, failing hostile
  URLs at the declared validation boundary
- preparedPath reuses format.ts's isOutside; SERVER_NAME_PATTERN is exported
  and pinned equal to dsh-mcp-client's, with the restatement justified (the
  prepare bin keeps a zod-only module graph); the unexplained `as never`
  cast now carries its schemastery rationale
- the import-free wrapper assertion also rejects dynamic import(
- the headless fixture wrapper is regenerated by the real prepareDshPlugin
  and a drift test pins fixture == generator output
- prepareDshPlugin JSDoc states the non-atomic publish repair contract
2026-08-02 01:25:02 +08:00

89 lines
4.5 KiB
TypeScript

import { cp, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { zstdDecompress } from 'node:zlib'
import { promisify } from 'node:util'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
import { PREPARED_ENTRY_FILENAME, prepareDshPlugin } from '@deepseek-ai/dsh-repository-plugin'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
const configPath = fileURLToPath(new URL('./fixtures/cli.cordis.yml', import.meta.url))
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
const decompress = promisify(zstdDecompress)
describe('headless-agent keyless smoke', () => {
it('boots the real Loader tree, runs a real bash tool round trip, and persists the turn', async () => {
let persistedHeader: Record<string, unknown> | undefined
const { stdout, stderr } = await runLoaderSmoke({
label: 'headless-agent',
tempDirPrefix: 'headless-agent-smoke-',
binScript,
configPath,
binArgs: ['--config', configPath, '--output-format', 'stream-json', 'prove the tool path'],
tsconfigPath,
inspect: async (cwd) => {
const files = await readdir(cwd, { recursive: true })
const relativePath = files.find(file => file.endsWith('.jsonl.zstd'))
if (relativePath === undefined) return
const compressed = await readFile(join(cwd, relativePath))
expect(compressed.subarray(0, 4).toString('hex')).toBe('28b52ffd')
persistedHeader = JSON.parse((await decompress(compressed)).toString()) as Record<string, unknown>
},
})
const lines = stdout.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
const events = lines.slice(0, -1).map(line => line['event'] as SessionEvent)
const result = lines.at(-1)
expect(stderr).toBe('')
expect(events.some(event => event.type === 'tool/call' && event.data.name === 'bash')).toBe(true)
const catalogMessage = events.find(event => event.type === 'user/message'
&& event.data.source.kind === 'plugin'
&& event.data.source.plugin === 'dsh-tool-skill')
const catalog = catalogMessage?.type === 'user/message'
? catalogMessage.data.content.filter(block => block.type === 'text').map(block => block.text).join('\n')
: ''
expect(catalog.split('\n').find(line => line.includes('repository-fixture'))).toMatchInlineSnapshot(
`
"- \`repository-fixture\`: Repository fixture skill."
`,
)
const toolResult = events.find(event => event.type === 'tool/result')
expect(JSON.stringify(toolResult)).toContain('CLI_TOOL_ROUND_TRIP')
expect(result).toMatchObject({
type: 'result',
success: true,
turn: 1,
reason: { kind: 'completed' },
usage: { inputTokens: 18, outputTokens: 8, cacheReadTokens: 2, reasoningTokens: 1 },
})
expect(String(result?.['result'])).toContain('CLI_TOOL_ROUND_TRIP')
expect(persistedHeader).toMatchObject({ type: 'session' })
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('keeps the checked-in prepared wrapper identical to the generator output for its manifest', async () => {
// The fixture claims "Generated by dsh-plugin-prepare"; this pin makes the
// claim true — a wrapper-template change fails here until the fixture is
// regenerated, so the assembled smoke can never exercise a stale shape.
const fixture = fileURLToPath(new URL('./fixtures/repository-plugin/', import.meta.url))
const root = await mkdtemp(join(tmpdir(), 'dsh-fixture-drift-'))
try {
const plugin = join(root, '.dsh-plugin')
await mkdir(plugin, { recursive: true })
await cp(join(fixture, 'dsh-plugin-assets/skills/0'), join(root, 'skills'), { recursive: true })
await writeFile(join(plugin, 'package.json'), `${JSON.stringify({
name: 'headless-repository-fixture',
version: '0.0.0',
dsh: { skills: ['../skills'] },
}, undefined, 2)}\n`)
await prepareDshPlugin(plugin)
const generated = await readFile(join(plugin, PREPARED_ENTRY_FILENAME), 'utf8')
const checkedIn = await readFile(join(fixture, PREPARED_ENTRY_FILENAME), 'utf8')
expect(checkedIn).toBe(generated)
} finally {
await rm(root, { recursive: true, force: true })
}
})
})