refactor(acp): reduce bridge to automation protocol

This commit is contained in:
Tianyi Cui
2026-07-24 01:40:25 +08:00
parent b06bcfd21c
commit e819a586b0
406 changed files with 3880 additions and 21325 deletions

View File

@@ -168,7 +168,7 @@ const PROJECT_QUESTION_STEPS: readonly WizardStep<ProjectAnswerState>[] = [
id: 'interface',
message: 'Run interface',
options: [
{ value: 'acp', label: 'ACP server' },
{ value: 'acp', label: 'ACP automation server' },
{ value: 'tui', label: 'Terminal TUI' },
{ value: 'embed', label: 'Embedded context' },
],

View File

@@ -183,7 +183,7 @@ describe.skipIf(process.platform === 'win32')('create-sdk terminal contract', ()
"kind": "select",
"message": "Run interface",
"options": [
"ACP server",
"ACP automation server",
"Terminal TUI",
"Embedded context",
],
@@ -287,12 +287,6 @@ describe.skipIf(process.platform === 'win32')('create-sdk terminal contract', ()
"label": "Tool timeout policy",
"required": false,
},
{
"choices": undefined,
"default": false,
"label": "Ask the user from the model loop",
"required": false,
},
],
},
{

View File

@@ -6,7 +6,7 @@ The package owns the builtin typed-spec catalog, provider/app behavior entities,
All business and document validation completes before commit writes any affected file. Commit detects external edits made after the session opened, but deliberately provides no cross-file rollback after writing starts.
Builtin features are provider, bash, app, persistence, HMR, filesystem, todo, skill, web, subagent, workflow, compaction, hooks, repeat-tool guard, timeout policy, and ask-user. The catalog owns feature options, required and non-default Cordis plugin config, feature requirements, resource contribution, and round-trip markers; create and config use the same registry and configurator. The ACP app option contributes the human-command and user-interaction services before the bridge.
Builtin features are provider, bash, app, persistence, HMR, filesystem, todo, skill, web, subagent, workflow, compaction, hooks, repeat-tool guard, timeout policy, and ask-user. The catalog owns feature options, required and non-default Cordis plugin config, feature requirements, resource contribution, and round-trip markers; create and config use the same registry and configurator. The ACP app option contributes only the automation bridge; interactive services belong to TUI or Web compositions.
`SdkProject.open()` requires only readable root `package.json` and `cordis.yml`. A Cordis config entry anchors feature installation; a package present only through a linked NPM dependency closure leaves the feature absent. Once an owned Cordis config entry exists, an incomplete resource shape is `inconsistent` and cannot be modified automatically.

View File

@@ -73,14 +73,6 @@ class AppOption extends FeatureOption {
case 'acp':
return new ProjectContribution([
...appProjectResources(profile, this.id),
...npmCordisConfigEntry(ID, {
id: 'commands',
name: '@deepseek-ai/dsh-commands',
}),
...npmCordisConfigEntry(ID, {
id: 'user-interaction',
name: '@deepseek-ai/dsh-user-interaction',
}),
...npmCordisConfigEntry(ID, {
id: 'acp',
name: '@deepseek-ai/dsh-acp',
@@ -119,7 +111,7 @@ export class AppFeature extends ExclusiveOptionFeature {
override readonly required = true
override readonly requires = [featureId('spine')]
override readonly options = [
new AppOption('acp', 'ACP server'),
new AppOption('acp', 'ACP automation server'),
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', 'tui'],
supportedInterfaces: ['tui'],
options: [{
id: 'default',
label: 'ask_user_question tool',

View File

@@ -5,9 +5,9 @@
Built with the DeepSeek Harness SDK using the {{model}} model.
{{#if isAcp}}
## Run as an ACP server
## Run as an ACP automation server
Run `{{packageManager}} start` and configure your ACP client to launch this project. Standard output is reserved for ACP JSON-RPC.
Run `{{packageManager}} start` and configure a programmatic ACP client to launch this project. Standard output is reserved for ACP JSON-RPC.
{{else}}
{{#if isTui}}
## Run in a terminal

View File

@@ -289,12 +289,13 @@ describe('SdkProject and ProjectEditSession', () => {
edit.configureFeature(registry.get(featureId('app')), selection('app', ['acp']))
const acp = (await edit.commit()).project
expect(acp.profile.runInterface).toBe('acp')
expect(acp.cordis.entry('commands')).toMatchObject({ name: '@deepseek-ai/dsh-commands' })
expect(acp.cordis.entry('commands')).toBeUndefined()
expect(acp.cordis.entry('user-interaction')).toBeUndefined()
expect(acp.packageManifest().scripts).toMatchObject({
dev: 'dsh-sdk dev index.ts',
start: 'dsh-sdk start index.js',
})
expect(await readFile(join(acp.root, 'README.md'), 'utf8')).toContain('Run as an ACP server')
expect(await readFile(join(acp.root, 'README.md'), 'utf8')).toContain('Run as an ACP automation server')
expect(await readFile(join(acp.root, 'index.ts'), 'utf8')).not.toContain('agents.create')
const acpRegistry = createBuiltinRegistry(acp.profile)
@@ -324,12 +325,16 @@ describe('SdkProject and ProjectEditSession', () => {
.toContain('missing package.json script dev')
})
it('rejects enabled features that do not apply to the target app interface', async () => {
it('rejects ask-user on non-interactive app interfaces', async () => {
const project = await createCommitted([selection('ask-user', ['default'])])
const registry = createBuiltinRegistry(project.profile)
const edit = project.edit(registry)
edit.configureFeature(registry.get(featureId('app')), selection('app', ['embed']))
await expect(edit.commit()).rejects.toThrow('feature ask-user is not available for embed')
const embed = project.edit(registry)
embed.configureFeature(registry.get(featureId('app')), selection('app', ['embed']))
await expect(embed.commit()).rejects.toThrow('feature ask-user is not available for embed')
const acp = project.edit(registry)
acp.configureFeature(registry.get(featureId('app')), selection('app', ['acp']))
await expect(acp.commit()).rejects.toThrow('feature ask-user is not available for acp')
})
it('supports disabled feature reconfiguration and rejects invalid state operations', async () => {

View File

@@ -85,7 +85,7 @@ Change file: package.json
"choices": [
{
"default": false,
"label": "ACP server",
"label": "ACP automation server",
"value": "acp",
},
{

View File

@@ -542,23 +542,23 @@ describe('ConfigWorkflow', () => {
expect(result.commit?.project.cordis.entry('agent-core')).toBeUndefined()
})
it('disables ask-user when switching its app interface to embed', async () => {
it('disables ask-user when switching its app interface to ACP', async () => {
const project = await committedProject([
{ id: featureId('ask-user'), options: ['default'] },
], [], 'acp')
], [], 'tui')
const registry = createBuiltinRegistry(project.profile)
const output = outputBuffer()
const workflow = new ConfigWorkflow(new QueuePort([
[
{ value: 'feature:provider', choices: ['deepseek'] },
{ value: 'feature:app', choices: ['embed'] },
{ value: 'feature:app', choices: ['acp'] },
{ value: 'feature:persistence', choices: ['jsonl'] },
{ value: 'feature:ask-user', choices: ['default'] },
],
true,
]), output.stream, async () => {})
const result = await workflow.run(project, registry)
expect(result.commit?.project.profile.runInterface).toBe('embed')
expect(result.commit?.project.profile.runInterface).toBe('acp')
expect(result.commit?.project.cordis.entry('tool-ask-user')?.disabled).toBe(true)
expect(output.read()).toContain('Disable feature: ask-user')
})