Merge latest master into invariant service seam

# Conflicts:
#	.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md
#	docs/capability-seams.md
#	docs/event-producer-consumer.md
#	packages/examples/agent-spine-demo/README.md
#	packages/examples/stdio-demo/tests/built-bin.e2e.ts
#	scripts/gen-doc-graphs.ts
This commit is contained in:
Tianyi Cui
2026-07-20 20:58:46 +08:00
175 changed files with 1343 additions and 4271 deletions

View File

@@ -61,7 +61,7 @@ function createProgram(): Command {
.option('--base-url <url>')
.option('--api-key <key>')
.option('--model <name>')
.addOption(new Option('--interface <name>').choices(['acp', 'stdio', 'embed']))
.addOption(new Option('--interface <name>').choices(['acp', 'tui', 'embed']))
.addOption(new Option('--pm <name>').choices(['npm', 'pnpm', 'yarn']))
.addOption(new Option('--install').default(undefined))
.addOption(new Option('--no-install').default(undefined))

View File

@@ -169,10 +169,10 @@ const PROJECT_QUESTION_STEPS: readonly WizardStep<ProjectAnswerState>[] = [
message: 'Run interface',
options: [
{ value: 'acp', label: 'ACP server' },
{ value: 'stdio', label: 'Terminal REPL' },
{ value: 'tui', label: 'Terminal TUI' },
{ value: 'embed', label: 'Embedded context' },
],
initialValue: 'stdio',
initialValue: 'tui',
}),
prefilled: state => state.args.runInterface,
apply: (state, value) => { state.runInterface = value },

View File

@@ -6,7 +6,7 @@ Options:
--base-url <url>
--api-key <key>
--model <name>
--interface <acp|stdio|embed>
--interface <acp|tui|embed>
--pm <npm|pnpm|yarn>
--install / --no-install
--config <path>

View File

@@ -179,12 +179,12 @@ describe('create-sdk terminal contract', () => {
"message": "DeepSeek API key",
},
{
"initialValue": "stdio",
"initialValue": "tui",
"kind": "select",
"message": "Run interface",
"options": [
"ACP server",
"Terminal REPL",
"Terminal TUI",
"Embedded context",
],
},

View File

@@ -151,7 +151,7 @@ describe('create arguments', () => {
expect(() => parseCreateArgs(['--link-packages-workspace'])).toThrow("unknown option '--link-packages-workspace'")
expect(parseCreateArgs(['--provider=custom']).provider).toBe('custom')
expect(parseCreateArgs(['--help']).help).toBe(true)
expect(() => parseCreateArgs(['--interface=bad'])).toThrow('Allowed choices are acp, stdio, embed')
expect(() => parseCreateArgs(['--interface=bad'])).toThrow('Allowed choices are acp, tui, embed')
expect(() => parseCreateArgs(['--unknown'])).toThrow("unknown option '--unknown'")
expect(() => parseCreateArgs(['one', 'two'])).toThrow('too many arguments')
})
@@ -208,7 +208,7 @@ describe('CreateWizard and scaffolder', () => {
'--provider=deepseek',
'--api-key=deepseek-key',
'--model=deepseek-v4-flash',
'--interface=stdio',
'--interface=tui',
'--pm=npm',
'--no-install',
'--link-workspace',
@@ -247,7 +247,7 @@ describe('CreateWizard and scaffolder', () => {
const resolved = await new CreateWizard({
args: parseCreateArgs([
'my-agent', '--description=demo', '--provider=deepseek', '--api-key=deepseek-key',
'--model=deepseek-v4-flash', '--interface=stdio', '--pm=npm', '--no-install',
'--model=deepseek-v4-flash', '--interface=tui', '--pm=npm', '--no-install',
]),
port: new HeadlessPromptPort(),
cwd,
@@ -275,7 +275,7 @@ describe('CreateWizard and scaffolder', () => {
await expect(new CreateWizard({
args: parseCreateArgs([
'my-agent', '--description=demo', '--provider=deepseek', '--api-key=k',
'--model=m', '--interface=stdio', '--pm=npm', '--no-install',
'--model=m', '--interface=tui', '--pm=npm', '--no-install',
]),
port: new HeadlessPromptPort(),
cwd,

View File

@@ -29,7 +29,7 @@ const ID = featureId('app')
function appProjectResources(
profile: ProjectProfile,
runInterface: 'acp' | 'stdio' | 'embed',
runInterface: 'acp' | 'tui' | 'embed',
): readonly ProjectResource[] {
const context = createProjectTemplateContext(profile, runInterface)
const scripts = createAppPackageScripts(context)
@@ -43,10 +43,10 @@ function appProjectResources(
}
class AppOption extends FeatureOption {
override readonly id: 'acp' | 'stdio' | 'embed'
override readonly id: 'acp' | 'tui' | 'embed'
override readonly label: string
constructor(id: 'acp' | 'stdio' | 'embed', label: string) {
constructor(id: 'acp' | 'tui' | 'embed', label: string) {
super()
this.id = id
this.label = label
@@ -56,7 +56,7 @@ class AppOption extends FeatureOption {
override markerConfigEntries(): readonly { id: string; name: string }[] {
switch (this.id) {
case 'acp': return [{ id: 'acp', name: '@deepseek-ai/dsh-acp' }]
case 'stdio': return [{ id: 'stdio', name: '@deepseek-ai/dsh-stdio' }]
case 'tui': return [{ id: 'tui', name: '@deepseek-ai/dsh-tui' }]
case 'embed': return []
}
}
@@ -65,7 +65,7 @@ class AppOption extends FeatureOption {
override matchesConfigEntries(entries: readonly { id: string; name: string }[], profile: ProjectProfile): boolean {
if (this.id !== 'embed') return super.matchesConfigEntries(entries, profile)
return entries.some(entry => entry.id === 'agent-loop' && entry.name === '@deepseek-ai/dsh-agent-loop')
&& !entries.some(entry => entry.name === '@deepseek-ai/dsh-acp' || entry.name === '@deepseek-ai/dsh-stdio')
&& !entries.some(entry => entry.name === '@deepseek-ai/dsh-acp' || entry.name === '@deepseek-ai/dsh-tui')
}
override contribution(profile: ProjectProfile): ProjectContribution {
@@ -83,7 +83,7 @@ class AppOption extends FeatureOption {
config: { model: profile.runtime.model },
}, ['model'], config => requiredString(config, 'model')),
])
case 'stdio':
case 'tui':
return new ProjectContribution([
...appProjectResources(profile, this.id),
...npmCordisConfigEntry(ID, {
@@ -91,10 +91,10 @@ class AppOption extends FeatureOption {
name: '@deepseek-ai/dsh-user-interaction',
}),
...npmCordisConfigEntry(ID, {
id: 'stdio',
name: '@deepseek-ai/dsh-stdio',
id: 'tui',
name: '@deepseek-ai/dsh-tui',
config: {
welcome: 'agent REPL ready. Give it a coding task.',
welcome: 'TUI agent ready. Give it a coding task.',
sessionId: new JsExpression('process.env.DSH_SDK_SESSION_ID'),
},
}, ['welcome', 'sessionId'], config => [
@@ -108,7 +108,7 @@ class AppOption extends FeatureOption {
}
}
/** Required app selection represented by acp, stdio, or embed options. */
/** Required app selection represented by ACP, TUI, or embed options. */
export class AppFeature extends ExclusiveOptionFeature {
override readonly id = ID
override readonly summary = 'Run interface'
@@ -116,7 +116,7 @@ export class AppFeature extends ExclusiveOptionFeature {
override readonly requires = [featureId('spine')]
override readonly options = [
new AppOption('acp', 'ACP server'),
new AppOption('stdio', 'Terminal REPL'),
new AppOption('tui', 'Terminal TUI'),
new AppOption('embed', 'Embedded context'),
]

View File

@@ -347,7 +347,7 @@ config:
id: 'ask-user',
summary: 'Ask the user from the model loop',
mode: 'single',
supportedInterfaces: ['acp', 'stdio'],
supportedInterfaces: ['acp', 'tui'],
options: [{
id: 'default',
label: 'ask_user_question tool',

View File

@@ -250,7 +250,7 @@ class DefinedFeature extends Feature {
this.required = spec.required ?? false
this.requires = (spec.requires ?? []).map(requirement => featureId(requirement.id))
this.suggests = (spec.suggests ?? []).map(featureId)
this.supportedInterfaces = spec.supportedInterfaces ?? ['acp', 'stdio', 'embed']
this.supportedInterfaces = spec.supportedInterfaces ?? ['acp', 'tui', 'embed']
}
override defaultOptions(): readonly string[] {

View File

@@ -113,7 +113,7 @@ export abstract class Feature {
/** Features recommended during creation. */
readonly suggests: readonly FeatureId[] = []
/** Front doors under which this feature is meaningful. */
readonly supportedInterfaces: readonly RunInterface[] = ['acp', 'stdio', 'embed']
readonly supportedInterfaces: readonly RunInterface[] = ['acp', 'tui', 'embed']
/**
* Options selected when installation has no override.

View File

@@ -549,7 +549,7 @@ export class ProjectEditSession implements FeatureProjectView {
private finalProfile(): ProjectProfile {
const runInterface = this.states.get(featureId('app'))?.selection?.options[0]
if (runInterface !== 'acp' && runInterface !== 'stdio' && runInterface !== 'embed') return this.profile
if (runInterface !== 'acp' && runInterface !== 'tui' && runInterface !== 'embed') return this.profile
return { ...this.profile, runInterface }
}

View File

@@ -42,7 +42,7 @@ const OPTIONAL_DOCUMENTS = [
function runInterface(entries: readonly CordisConfigEntry[]): RunInterface {
if (entries.some(entry => entry.name === '@deepseek-ai/dsh-acp')) return 'acp'
if (entries.some(entry => entry.name === '@deepseek-ai/dsh-stdio')) return 'stdio'
if (entries.some(entry => entry.name === '@deepseek-ai/dsh-tui')) return 'tui'
return 'embed'
}
@@ -146,7 +146,7 @@ export class SdkProject {
static create(root: string, request: ProjectCreationRequest): SdkProject {
const app = request.features.find(selection => selection.id === 'app')
const selectedInterface = app?.options[0]
if (selectedInterface !== 'acp' && selectedInterface !== 'stdio' && selectedInterface !== 'embed') {
if (selectedInterface !== 'acp' && selectedInterface !== 'tui' && selectedInterface !== 'embed') {
throw new Error('project creation requires one app feature option')
}
const profile: ProjectProfile = {

View File

@@ -9,7 +9,7 @@ import type { LocalPluginBlueprint } from '../plugins/local-plugin-blueprint.ts'
import type { FeatureId } from '../ids.ts'
/** Runtime front door selected for a generated project. */
export type RunInterface = 'acp' | 'stdio' | 'embed'
export type RunInterface = 'acp' | 'tui' | 'embed'
/** Values shared by the required provider and app features. */
interface ProjectRuntimeOptions {

View File

@@ -9,7 +9,7 @@ Built with the DeepSeek Harness SDK using the {{model}} model.
Run `{{packageManager}} start` and configure your ACP client to launch this project. Standard output is reserved for ACP JSON-RPC.
{{else}}
{{#if isStdio}}
{{#if isTui}}
## Run in a terminal
Run `{{packageManager}} start` to start the interactive agent.

View File

@@ -8,18 +8,18 @@ import { startSDK, type SdkBootContext } from '@deepseek-ai/dsh-scripts'
/** Boot this project's cordis.yml when invoked by dsh-scripts. */
export async function main(boot: SdkBootContext) {
{{#if isStdio}}
{{#if isTui}}
const model = boot.args.model
if (typeof model !== 'string' || model.length === 0) throw new Error('stdio startup requires --model=<name>')
if (typeof model !== 'string' || model.length === 0) throw new Error('TUI startup requires --model=<name>')
const resume = boot.args.resume
if (resume !== undefined && (typeof resume !== 'string' || resume.length === 0)) {
throw new Error('stdio startup requires --resume=<session-id>')
throw new Error('TUI startup requires --resume=<session-id>')
}
const sessionId = SessionId(resume ?? `main-session-${randomUUID()}`)
process.env.DSH_SDK_SESSION_ID = sessionId
{{/if}}
const ctx = await startSDK(new URL('./cordis.yml', import.meta.url))
{{#if isStdio}}
{{#if isTui}}
try {
if (resume === undefined) {
await ctx.agents.create({
@@ -37,7 +37,7 @@ export async function main(boot: SdkBootContext) {
try {
await ctx.fiber.dispose()
} catch (disposeError) {
throw new AggregateError([error, disposeError], 'stdio startup and cleanup failed')
throw new AggregateError([error, disposeError], 'TUI startup and cleanup failed')
}
throw error
}

View File

@@ -20,7 +20,7 @@ export interface ProjectTemplateContext {
model: string
modelLiteral: string
isAcp: boolean
isStdio: boolean
isTui: boolean
isEmbed: boolean
packageManager: PackageManagerName
installArgs: string
@@ -60,7 +60,7 @@ export function createProjectTemplateContext(
model: profile.runtime.model,
modelLiteral: JSON.stringify(profile.runtime.model),
isAcp: runInterface === 'acp',
isStdio: runInterface === 'stdio',
isTui: runInterface === 'tui',
isEmbed: runInterface === 'embed',
packageManager: profile.packageManager.name,
installArgs: profile.packageManager.installCommand().join(' '),
@@ -105,7 +105,7 @@ export function createAppProjectArtifacts(
/** Build package scripts owned by the selected app feature option. */
export function createAppPackageScripts(context: ProjectTemplateContext): Readonly<Record<'dev' | 'start', string>> {
const modelArg = context.isStdio ? ` -- --model=${JSON.stringify(context.model)}` : ''
const modelArg = context.isTui ? ` -- --model=${JSON.stringify(context.model)}` : ''
return {
dev: `dsh-sdk dev index.ts${modelArg}`,
start: `dsh-sdk start index.js${modelArg}`,

View File

@@ -243,7 +243,7 @@ overrides:
expect(() => loadHelperTemplate('../bad.tpl')).toThrow('must not contain a directory')
expect(createBaselineProjectArtifacts({
name: 'demo', description: 'demo', releaseVersion: '0.0.1', model: 'model', modelLiteral: '"model"', packageManager: 'yarn',
isAcp: false, isStdio: false, isEmbed: true,
isAcp: false, isTui: false, isEmbed: true,
installArgs: 'install', buildArgs: 'build',
}).map(document => document.relativePath)).toContain('.yarnrc.yml')
expect(() => new LocalPluginBlueprint('---', 'plugin')).toThrow('invalid local plugin name')

View File

@@ -51,7 +51,7 @@ function selection(id: string, options: readonly string[], secrets?: Record<stri
function request(
extra: readonly FeatureSelection[] = [],
plugins: readonly LocalPluginBlueprint[] = [],
app: 'acp' | 'stdio' | 'embed' = 'stdio',
app: 'acp' | 'tui' | 'embed' = 'tui',
bash: 'local' | 'sandbox' = 'local',
): ProjectCreationRequest {
return {
@@ -115,16 +115,16 @@ describe('SdkProject and ProjectEditSession', () => {
expect(acp.readEnvironment('.env', 'KEY')).toBe('value')
expect(() => acp.readEnvironment('.env.example', 'KEY')).not.toThrow()
expect(acp.document('tsconfig.json')).toBeInstanceOf(TextProjectFile)
const stdio = await make('dsh-open-stdio', {}, `- id: provider
const tui = await make('dsh-open-tui', {}, `- id: provider
name: '@deepseek-ai/dsh-llm-deepseek'
config: { models: [provider-model] }
- id: stdio
name: '@deepseek-ai/dsh-stdio'
- id: tui
name: '@deepseek-ai/dsh-tui'
`, { 'yarn.lock': '' })
expect(stdio.profile.runInterface).toBe('stdio')
expect(stdio.profile.runtime.model).toBe('provider-model')
expect(stdio.profile.packageManager.name).toBe('yarn')
expect(stdio.profile.name).toBe(stdio.root.split('/').at(-1))
expect(tui.profile.runInterface).toBe('tui')
expect(tui.profile.runtime.model).toBe('provider-model')
expect(tui.profile.packageManager.name).toBe('yarn')
expect(tui.profile.name).toBe(tui.root.split('/').at(-1))
const pnpm = await make('dsh-open-pnpm', { name: 'pnpm' }, '[]\n', { 'pnpm-lock.yaml': '' })
expect(pnpm.profile.packageManager.name).toBe('pnpm')
const defaults = await make('dsh-open-default', { name: 'default', packageManager: 'npm@10.0.0' }, '[]\n')
@@ -134,8 +134,8 @@ describe('SdkProject and ProjectEditSession', () => {
expect(() => SdkProject.create(defaults.root, { ...request(), features: [] })).toThrow('requires one app')
await expect(make('dsh-open-invalid-manager', { name: 'bad', packageManager: 'bad' }, '[]\n'))
.rejects.toThrow('invalid packageManager field')
const providerFallback = await make('dsh-open-provider-fallback', { name: 'fallback' }, `- id: stdio
name: '@deepseek-ai/dsh-stdio'
const providerFallback = await make('dsh-open-provider-fallback', { name: 'fallback' }, `- id: tui
name: '@deepseek-ai/dsh-tui'
config: { model: '' }
- id: provider
name: '@deepseek-ai/dsh-llm-deepseek'
@@ -172,7 +172,7 @@ describe('SdkProject and ProjectEditSession', () => {
expect(index).toContain('process.env.DSH_SDK_SESSION_ID = sessionId')
expect(index).toContain('resumeSessionId: sessionId')
expect(index).toContain('await ctx.fiber.dispose()')
expect(index).toContain("new AggregateError([error, disposeError], 'stdio startup and cleanup failed')")
expect(index).toContain("new AggregateError([error, disposeError], 'TUI startup and cleanup failed')")
expect(project.packageManifest().scripts).toEqual({
dev: 'dsh-sdk dev index.ts -- --model="deepseek-v4-flash"',
build: 'dsh-sdk build',
@@ -181,12 +181,12 @@ describe('SdkProject and ProjectEditSession', () => {
config: 'dsh-sdk config',
})
expect(await readFile(join(project.root, '.env.example'), 'utf8')).toContain('EXA_API_KEY=')
expect(project.cordis.entry('stdio')?.config?.sessionId).toMatchObject({
expect(project.cordis.entry('tui')?.config?.sessionId).toMatchObject({
source: 'process.env.DSH_SDK_SESSION_ID',
})
expect(await readFile(join(project.root, 'cordis.yml'), 'utf8'))
.toContain('sessionId: !!js process.env.DSH_SDK_SESSION_ID')
expect(project.cordis.entry('stdio')?.config).not.toHaveProperty('model')
expect(project.cordis.entry('tui')?.config).not.toHaveProperty('model')
expect(project.cordis.entry('agent-loop')?.config).toEqual({ agents: [] })
expect(project.cordis.entry('session-invariant')?.name).toBe('@deepseek-ai/dsh-session/invariant')
expect(project.cordis.entry('agent-invariant')?.name).toBe('@deepseek-ai/dsh-agent/invariant')
@@ -217,13 +217,13 @@ describe('SdkProject and ProjectEditSession', () => {
expect(app.selection).toEqual(selection('app', ['embed']))
expect(committed.cordis.entry('agent-loop')?.config).toEqual({ agents: [] })
expect(committed.cordis.entry('acp')).toBeUndefined()
expect(committed.cordis.entry('stdio')).toBeUndefined()
expect(committed.cordis.entry('tui')).toBeUndefined()
})
it('emits the sandbox workspace-write example as inactive Cordis config', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-sandbox-bash-'))
temporary.push(root)
const creation = request([], [], 'stdio', 'sandbox')
const creation = request([], [], 'tui', 'sandbox')
const project = SdkProject.create(root, creation)
const registry = createBuiltinRegistry(project.profile)
const edit = project.edit(registry)
@@ -312,7 +312,7 @@ describe('SdkProject and ProjectEditSession', () => {
const modifiedRegistry = createBuiltinRegistry(modified.profile)
expect(() => { modified.edit(modifiedRegistry).configureFeature(
modifiedRegistry.get(featureId('app')),
selection('app', ['stdio']),
selection('app', ['tui']),
) }).toThrow('feature-owned file was modified: README.md')
const manifest = PackageJsonFile.parse(await readFile(join(embed.root, 'package.json'), 'utf8'))
@@ -355,7 +355,7 @@ describe('SdkProject and ProjectEditSession', () => {
const edit = project.edit(registry)
edit.setCustomPluginDisabled('sample', true)
expect(edit.cordisConfigEntries().find(entry => entry.id === 'sample')?.disabled).toBe(true)
expect(() => { edit.setCustomPluginDisabled('stdio', true) }).toThrow('builtin feature')
expect(() => { edit.setCustomPluginDisabled('tui', true) }).toThrow('builtin feature')
const next = (await edit.commit()).project
const enable = next.edit(createBuiltinRegistry(next.profile))
enable.setCustomPluginDisabled('sample', false)
@@ -450,8 +450,8 @@ describe('SdkProject and ProjectEditSession', () => {
}
const internals = edit as unknown as Internals
const collidingEntry: ProjectResource = {
kind: 'cordis-config-entry', key: resourceKey('cordis-config-entry:stdio'),
entry: { id: 'stdio', name: 'other-package' }, ownedConfigKeys: [],
kind: 'cordis-config-entry', key: resourceKey('cordis-config-entry:tui'),
entry: { id: 'tui', name: 'other-package' }, ownedConfigKeys: [],
}
expect(() => { internals.applyResource(collidingEntry, undefined) }).toThrow('is owned by')
const existingFile: ProjectResource = {
@@ -803,7 +803,7 @@ describe('extension points', () => {
})
expect(exclusive.defaultOptions(profile)).toEqual(['one'])
expect(exclusive.isApplicable(profile)).toBe(true)
expect(exclusive.isApplicable({ ...profile, runInterface: 'stdio' })).toBe(false)
expect(exclusive.isApplicable({ ...profile, runInterface: 'tui' })).toBe(false)
expect(exclusive.requirements(selection('defined', ['one']))).toEqual([
{ id: 'base' }, { id: 'option', options: ['required'] },
])
@@ -817,7 +817,7 @@ describe('extension points', () => {
expect(entry?.validateConfig?.({ nested: { value: 2 }, list: ['a', 'b'], nullable: null })).toEqual([])
expect(entry?.validateConfig?.({ nested: [], list: 'bad' })).toHaveLength(3)
expect(() => exclusive.normalizeSelection(selection('other', ['one']), profile)).toThrow('does not belong')
expect(() => exclusive.normalizeSelection(selection('defined', ['one']), { ...profile, runInterface: 'stdio' }))
expect(() => exclusive.normalizeSelection(selection('defined', ['one']), { ...profile, runInterface: 'tui' }))
.toThrow('not available')
expect(() => exclusive.normalizeSelection(selection('defined', ['missing']), profile)).toThrow('unknown')
expect(() => exclusive.normalizeSelection(selection('defined', ['one', 'two']), profile)).toThrow('exactly one')
@@ -825,7 +825,7 @@ describe('extension points', () => {
id: 'fixed', summary: 'Fixed', mode: 'single', options: [option],
}])).toHaveLength(2)
expect(() => new FeatureRegistry([], profile).get(featureId('missing'))).toThrow('unknown feature')
expect(new FeatureRegistry([exclusive], profile).ownerOfPackage('one-package', { ...profile, runInterface: 'stdio' }))
expect(new FeatureRegistry([exclusive], profile).ownerOfPackage('one-package', { ...profile, runInterface: 'tui' }))
.toBeUndefined()
class Unsupported extends FixedFeature {
override readonly id = featureId('unsupported')
@@ -908,10 +908,10 @@ describe('extension points', () => {
resource.kind === 'cordis-config-entry' && resource.entry.id === 'acp')
expect(acpEntry?.entry.id).toBe('acp')
expect(acpEntry?.validateConfig?.({ model: '' })).toHaveLength(1)
const stdioEntry = builtins.get(featureId('app')).contribution(selection('app', ['stdio']), profile).resources
const tuiEntry = builtins.get(featureId('app')).contribution(selection('app', ['tui']), profile).resources
.find((resource): resource is CordisConfigEntryResource =>
resource.kind === 'cordis-config-entry' && resource.entry.id === 'stdio')
expect(stdioEntry?.validateConfig?.({ welcome: 'ready', sessionId: 1 })).toEqual([
resource.kind === 'cordis-config-entry' && resource.entry.id === 'tui')
expect(tuiEntry?.validateConfig?.({ welcome: 'ready', sessionId: 1 })).toEqual([
'sessionId must be a non-empty string',
])
const embedOption = app.options.find(option => option.id === 'embed')
@@ -921,7 +921,7 @@ describe('extension points', () => {
])
expect(embedOption?.matchesConfigEntries([
{ id: 'agent-loop', name: '@deepseek-ai/dsh-agent-loop' },
{ id: 'stdio', name: '@deepseek-ai/dsh-stdio' },
{ id: 'tui', name: '@deepseek-ai/dsh-tui' },
], profile)).toBe(false)
const spineAgentLoop = builtins.get(featureId('spine')).contribution(selection('spine', ['default']), profile).resources
.find((resource): resource is CordisConfigEntryResource =>

View File

@@ -376,7 +376,7 @@ describe('feature configurator', () => {
name: 'demo',
description: 'demo',
runtime: { model: 'deepseek-v4-flash' },
runInterface: 'stdio',
runInterface: 'tui',
packageManager: new NpmPackageManager('10.0.0'),
releaseVersion: '0.0.1',
}

View File

@@ -56,7 +56,7 @@ function targetRunInterface(
desired: ReadonlyMap<string, NestedMultiSelectValue<string, string>>,
): RunInterface {
const selected = desired.get('feature:app')?.choices[0]
return selected === 'acp' || selected === 'stdio' || selected === 'embed' ? selected : current
return selected === 'acp' || selected === 'tui' || selected === 'embed' ? selected : current
}
/** Reconcile one tree selection into domain commands, then review and commit once. */

View File

@@ -90,8 +90,8 @@ Change file: package.json
},
{
"default": true,
"label": "Terminal REPL",
"value": "stdio",
"label": "Terminal TUI",
"value": "tui",
},
{
"default": false,

View File

@@ -94,7 +94,7 @@ async function baseProject(): Promise<SdkProject> {
features: [
{ id: featureId('provider'), options: ['deepseek'], secrets: { apiKey: 'key' } },
{ id: featureId('bash'), options: ['local'] },
{ id: featureId('app'), options: ['stdio'] },
{ id: featureId('app'), options: ['tui'] },
{ id: featureId('persistence'), options: ['jsonl'] },
],
localPlugins: [],

View File

@@ -85,7 +85,7 @@ function commandContext(cwd: string): DshSdkCommandContext & { readStdout: () =>
function creation(
extra: ProjectCreationRequest['features'] = [],
localPlugins: readonly LocalPluginBlueprint[] = [],
app: 'acp' | 'stdio' | 'embed' = 'embed',
app: 'acp' | 'tui' | 'embed' = 'embed',
): ProjectCreationRequest {
return {
name: 'config-agent',
@@ -107,7 +107,7 @@ function creation(
async function committedProject(
extra: ProjectCreationRequest['features'] = [],
localPlugins: readonly LocalPluginBlueprint[] = [],
app: 'acp' | 'stdio' | 'embed' = 'embed',
app: 'acp' | 'tui' | 'embed' = 'embed',
): Promise<SdkProject> {
const root = await mkdtemp(join(tmpdir(), 'dsh-config-workflow-'))
temporary.push(root)
@@ -525,7 +525,7 @@ describe('ConfigWorkflow', () => {
const workflow = new ConfigWorkflow(new QueuePort([
[
{ value: 'feature:provider', choices: ['custom'] },
{ value: 'feature:app', choices: ['stdio'] },
{ value: 'feature:app', choices: ['tui'] },
{ value: 'feature:persistence', choices: ['jsonl'] },
],
'https://provider.example/v1',
@@ -536,7 +536,7 @@ describe('ConfigWorkflow', () => {
const provider = result.commit?.project.cordis.entry('llm-pi-ai')
expect(provider?.config?.apiKey).toBeDefined()
expect(provider?.config?.baseURL).toBe('https://provider.example/v1')
expect(result.commit?.project.cordis.entry('stdio')).toBeDefined()
expect(result.commit?.project.cordis.entry('tui')).toBeDefined()
expect(result.commit?.project.cordis.entry('agent-loop')).toBeDefined()
expect(result.commit?.project.cordis.entry('agent-core')).toBeUndefined()
})