mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
git mv per the regrouping RFC: the five human-collaboration seams and tui join packages/interaction/, app-boot becomes packages/boot/, and jsonrpc joins the renamed scaffold/ (formerly sdk/) as its server half beside client/protocol/create-sdk/helper/scripts/telemetry, whose folders drop the legacy sdk- prefix. Three new group README triplets replace the ui/ and sdk/ ones; tsconfig references/paths/globs, knip keys, vitest globs, gate scripts, catalogs, docs, and the lockfile follow. Adds the four settled FIXME rename markers (dsh-sdk-server, dsh-sdk-telemetry, dsh-sdk-helper, dsh-sdk-scripts). The scaffold folders diverge from their npm names until those renames land, so tsconfig.base.json maps the three affected names explicitly beside the group wildcard. Also repairs two pre-existing stale-path classes the strengthened sweep surfaced: docs/web-styling.md's retired web-ui host package and type-model spec fixture-literal joins. app-boot's three Loader-composition specs time out at the default 5s under full-suite parallel load on this filesystem (pre-existing; pass isolated with --testTimeout=30000); interaction/scaffold/boot suites otherwise green (687 passed).
129 lines
4.4 KiB
TypeScript
129 lines
4.4 KiB
TypeScript
import { mkdtemp, rm } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { Writable } from 'node:stream'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import {
|
|
NpmPackageManager,
|
|
SdkProject,
|
|
featureId,
|
|
createBuiltinRegistry,
|
|
type NestedMultiSelectValue,
|
|
type PromptPort,
|
|
} from '@deepseek-ai/dsh-helper'
|
|
import type {
|
|
ConfirmPromptRequest,
|
|
MultiSelectPromptRequest,
|
|
NestedMultiSelectRequest,
|
|
PromptOutcome,
|
|
SecretPromptRequest,
|
|
SelectPromptRequest,
|
|
TextPromptRequest,
|
|
} from '../../helper/src/questions/prompt-port.ts'
|
|
import { ConfigWorkflow } from '../src/config/config-workflow.ts'
|
|
|
|
class RecordingPort implements PromptPort {
|
|
readonly transcript: unknown[] = []
|
|
readonly #answers: unknown[]
|
|
|
|
constructor(answers: unknown[]) { this.#answers = [...answers] }
|
|
|
|
answer<T>(record: unknown): Promise<PromptOutcome<T>> {
|
|
this.transcript.push(record)
|
|
return Promise.resolve({ status: 'answered', value: this.#answers.shift() as T })
|
|
}
|
|
|
|
text(request: TextPromptRequest): Promise<PromptOutcome<string>> {
|
|
return this.answer({ kind: 'text', message: request.message })
|
|
}
|
|
secret(request: SecretPromptRequest): Promise<PromptOutcome<string>> {
|
|
return this.answer({ kind: 'secret', message: request.message })
|
|
}
|
|
select<T>(request: SelectPromptRequest<T>): Promise<PromptOutcome<T>> {
|
|
return this.answer({
|
|
kind: 'select', message: request.message,
|
|
options: request.options.map(option => ({ value: option.value, label: option.label })),
|
|
})
|
|
}
|
|
multiselect<T>(request: MultiSelectPromptRequest<T>): Promise<PromptOutcome<readonly T[]>> {
|
|
return this.answer({ kind: 'multiselect', message: request.message })
|
|
}
|
|
confirm(request: ConfirmPromptRequest): Promise<PromptOutcome<boolean>> {
|
|
return this.answer({ kind: 'confirm', message: request.message, initialValue: request.initialValue })
|
|
}
|
|
nestedMultiselect<TValue, TChoice>(
|
|
request: NestedMultiSelectRequest<TValue, TChoice>,
|
|
): Promise<PromptOutcome<readonly NestedMultiSelectValue<TValue, TChoice>[]>> {
|
|
return this.answer({
|
|
kind: 'nested-multiselect',
|
|
message: request.message,
|
|
showChanges: request.showChanges,
|
|
options: request.options.map(option => ({
|
|
value: option.value,
|
|
label: option.label,
|
|
required: option.required,
|
|
default: option.default,
|
|
disabled: option.disabled,
|
|
warning: option.warning,
|
|
choiceMode: option.choiceMode,
|
|
choices: option.choices?.map(choice => ({
|
|
value: choice.value,
|
|
label: choice.label,
|
|
default: choice.default,
|
|
})),
|
|
})),
|
|
})
|
|
}
|
|
}
|
|
|
|
const temporary: string[] = []
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(temporary.splice(0).map(path => rm(path, { recursive: true, force: true })))
|
|
})
|
|
|
|
async function baseProject(): Promise<SdkProject> {
|
|
const root = await mkdtemp(join(tmpdir(), 'dsh-config-snapshot-'))
|
|
temporary.push(root)
|
|
const request = {
|
|
name: 'snapshot-agent',
|
|
description: 'snapshot',
|
|
runtime: { model: 'deepseek-v4-flash' },
|
|
packageManager: new NpmPackageManager('10.0.0'),
|
|
releaseVersion: '0.0.1',
|
|
features: [
|
|
{ id: featureId('provider'), options: ['deepseek-official'], secrets: { apiKey: 'key' } },
|
|
{ id: featureId('bash'), options: ['local'] },
|
|
{ id: featureId('app'), options: ['acp'] },
|
|
{ id: featureId('persistence'), options: ['jsonl'] },
|
|
],
|
|
localPlugins: [],
|
|
}
|
|
const project = SdkProject.create(root, request)
|
|
const registry = createBuiltinRegistry(project.profile)
|
|
const edit = project.edit(registry)
|
|
for (const item of request.features) edit.installFeature(registry.get(item.id), item)
|
|
return (await edit.commit()).project
|
|
}
|
|
|
|
describe('dsh-sdk config terminal contract', () => {
|
|
it('pins the feature tree and Review & Apply output', async () => {
|
|
const project = await baseProject()
|
|
const registry = createBuiltinRegistry(project.profile)
|
|
const port = new RecordingPort([
|
|
[{ value: 'feature:todo', choices: [] }],
|
|
true,
|
|
])
|
|
let output = ''
|
|
const stream = new Writable({ write(chunk, _encoding, callback) { output += String(chunk); callback() } })
|
|
let installs = 0
|
|
const result = await new ConfigWorkflow(port, stream, async () => { installs += 1 }).run(project, registry)
|
|
expect({
|
|
transcript: port.transcript,
|
|
review: output,
|
|
installs,
|
|
committed: result.commit?.changes,
|
|
}).toMatchSnapshot()
|
|
})
|
|
})
|