From 74d14f68bb87ee39ba346f6335556a056791d773 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:11:49 +0800 Subject: [PATCH 1/4] fix(acp): accept baseline resource links and release a failed prompt slot ACP v1 requires every agent to accept text AND resource_link prompt content; the automation rewrite dropped the resource_link half of that baseline. Restore the old bracketed-reference flattening in the codec, reject only beyond-baseline blocks, and update the package contract and Agent Note. Also release the per-session prompt slot when agent.send() throws synchronously (an agent disposed outside the bridge would otherwise wedge the session into permanent 'already in flight' rejections), drop the tautological version-negotiation branch, prove the scenario env layer reaches the snapshot subprocess, pin bridge-side fail-closed permission errors, and correct two overpromising test names. --- ...-23-acp-automation-only-protocol.i18n.yaml | 4 +-- ...2026-07-23-acp-automation-only-protocol.md | 2 +- ...6-07-23-acp-automation-only-protocol.zh.md | 2 +- packages/acp/acp/README.md | 8 +++--- packages/acp/acp/src/codec.ts | 26 +++++++++++++----- packages/acp/acp/src/index.ts | 27 ++++++++++++++----- packages/acp/acp/tests/approval.spec.ts | 7 +++++ packages/acp/acp/tests/bridge.spec.ts | 23 +++++++++++++--- packages/acp/acp/tests/codec.spec.ts | 17 +++++++++--- packages/acp/acp/tests/edges.spec.ts | 6 ++++- packages/acp/acp/tests/harness.ts | 5 +++- packages/acp/acp/tests/turns.spec.ts | 13 +++++++++ .../tests/fixtures/fake-acp-agent.ts | 2 ++ .../support/acp-snapshot/tests/suite.spec.ts | 2 ++ 14 files changed, 114 insertions(+), 30 deletions(-) diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml index 6d477ec53a..2901a3b813 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-23-acp-automation-only-protocol.md: 5faeae49d8730c12995c80057437350af68cf962 -2026-07-23-acp-automation-only-protocol.zh.md: e464ca023a56eb5cb5b997c12a14cafb1f93ac23 +2026-07-23-acp-automation-only-protocol.md: 2a92f306065b348764f35e1e63f0d7750a636372 +2026-07-23-acp-automation-only-protocol.zh.md: 5889d668310e3bd63f3934a39d4e5250f83f063d diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md index 5faeae49d8..2a92f30606 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md @@ -14,7 +14,7 @@ The snapshot suite complicates removal. Most ACP scenarios exercise the assemble ## Decision -`@deepseek-ai/dsh-acp` is an automation transport under [`packages/acp/acp`](../../../../packages/acp/acp/README.md), outside the `ui` package group. Its public protocol is intentionally small: version negotiation, fresh text sessions with one in-flight prompt each, committed assistant text updates, per-session cancellation, concurrent sessions, and connection-owned teardown. The bridge rejects additional directories, MCP servers, non-text prompts, empty prompts, unknown sessions, and overlapping prompts. +`@deepseek-ai/dsh-acp` is an automation transport under [`packages/acp/acp`](../../../../packages/acp/acp/README.md), outside the `ui` package group. Its public protocol is intentionally small: version negotiation, fresh text sessions with one in-flight prompt each, committed assistant text updates, per-session cancellation, concurrent sessions, and connection-owned teardown. Prompts carry the spec-required baseline only — text plus resource links flattened to bracketed textual references; the bridge rejects additional directories, MCP servers, beyond-baseline prompt content (image, audio, embedded resources), empty prompts, unknown sessions, and overlapping prompts. The bridge emits only committed `assistant/message` text. Reasoning, raw chunks, tool activity, todos, plans, titles, retry markers, terminal metadata, diffs, locations, and resource links remain in the durable session log or in UI-specific transports. It does not provide session load/list/delete, commands, modes, configuration selectors, model switching, plan review, or human elicitation. diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md index e464ca023a..5889d66831 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md @@ -14,7 +14,7 @@ ACP 仍有一个有用的职责:另一个 agent(智能体)或自动化控 ## 决策 -`@deepseek-ai/dsh-acp` 是位于 [`packages/acp/acp`](../../../../packages/acp/acp/README.md) 下、独立于 `ui` 包组的自动化传输层。其公开协议特意保持精简:版本协商、全新文本会话(每个会话最多允许一个进行中的提示词)、已提交的助手文本更新、按会话取消、并发会话,以及由连接负责的资源清理。桥接层会拒绝附加目录、MCP 服务器、非文本提示词、空提示词、未知会话和重叠提示词。 +`@deepseek-ai/dsh-acp` 是位于 [`packages/acp/acp`](../../../../packages/acp/acp/README.md) 下、独立于 `ui` 包组的自动化传输层。其公开协议特意保持精简:版本协商、全新文本会话(每个会话最多允许一个进行中的提示词)、已提交的助手文本更新、按会话取消、并发会话,以及由连接负责的资源清理。提示词只承载规范要求的基线内容——文本,加上被展平为方括号文本引用的资源链接;桥接层会拒绝附加目录、MCP 服务器、超出基线的提示词内容(图片、音频、内嵌资源)、空提示词、未知会话和重叠提示词。 桥接层只发出已提交的 `assistant/message` 文本。推理、原始分片、工具活动、待办事项、计划、标题、重试标记、终端元数据、diff、位置和资源链接仍保留在持久会话日志或 UI 专用传输层中。它不提供会话加载、列出与删除、命令、模式、配置选择器、模型切换、plan 评审或面向人类的询问。 diff --git a/packages/acp/acp/README.md b/packages/acp/acp/README.md index 6a2db4e6ba..20b1ecbefe 100644 --- a/packages/acp/acp/README.md +++ b/packages/acp/acp/README.md @@ -19,10 +19,10 @@ Both fields are optional so another agent/request listener may supply the target | Method | Behavior | |---|---| -| `initialize` | Negotiates the supported version and advertises text-only prompts. No session, editor, terminal, filesystem, or MCP capability is advertised. | +| `initialize` | Negotiates the supported version and advertises baseline-only prompts (no image, audio, or embedded-context capability). No session, editor, terminal, filesystem, or MCP capability is advertised. | | `authenticate` | No-op because the server advertises no authentication methods. | | `session/new` | Creates a fresh agent with an absolute primary `cwd`; empty `additionalDirectories` and `mcpServers` are accepted, non-empty values reject. | -| `session/prompt` | Concatenates text blocks, rejects empty or non-text input, permits one in-flight request per session, and settles from that request's owning durable `turn/end`. | +| `session/prompt` | Concatenates text blocks, renders baseline resource links as bracketed textual references, rejects empty or beyond-baseline input, permits one in-flight request per session, and settles from that request's owning durable `turn/end`. | | `session/cancel` | Cancels only the addressed agent and settles its pending prompt as `cancelled`; unknown ids are no-ops. | | `session/update` | Emits one `agent_message_chunk` per non-empty text block in a committed `assistant/message`. Raw deltas and non-message events are omitted. | | `session/request_permission` | Offers one-shot allow/reject choices for bridge-owned approval requests carrying a tool call id. Clients may answer automatically. | @@ -45,7 +45,7 @@ Client disconnect and Cordis disposal share one memoized teardown. The bridge fi #### What the model sees -`session/prompt` text blocks are concatenated verbatim into one user message. Protocol metadata, client capabilities, permission choices, and session ids never enter the model request. +`session/prompt` text blocks are concatenated verbatim into one user message; a baseline resource link appears in that message as a bracketed `[resource_link name=… uri=…]` reference the model may open with its own tools. Protocol metadata, client capabilities, permission choices, and session ids never enter the model request. #### Token effect @@ -72,6 +72,6 @@ Append-only through the owning tool result. ## Known Limitations and Deferred Work - **Fresh sessions only** — load, list, resume, delete, and fork are unsupported. -- **Text and one workspace only** — resource links, images, audio, embedded resources, non-empty additional directories, and MCP servers reject. +- **Baseline prompts and one workspace only** — images, audio, embedded resources, non-empty additional directories, and MCP servers reject; resource links flatten to textual references rather than fetched content. - **Committed answers only** — live progress, reasoning, tool activity, plans, titles, and usage stay off the wire. - **Connection-owned lifetime** — one connection releases all of its sessions; per-session close is not implemented. diff --git a/packages/acp/acp/src/codec.ts b/packages/acp/acp/src/codec.ts index 53ba437425..2a88af1184 100644 --- a/packages/acp/acp/src/codec.ts +++ b/packages/acp/acp/src/codec.ts @@ -31,19 +31,33 @@ export function turnEndToStopReason(reason: TurnEndReason): StopReason { } /** - * Concatenate an ACP prompt's text blocks. + * Flatten an ACP prompt's baseline blocks to text. Text blocks concatenate + * verbatim; resource links become explicit textual references so a baseline + * client can point at files without the bridge silently dropping that context. * @param prompt - supported ACP prompt blocks. - * @returns text in wire order. + * @returns text in wire order, with resource links rendered as bracketed references. */ export function acpPromptToText(prompt: readonly AcpContentBlock[]): string { - return prompt.flatMap(block => block.type === 'text' ? [block.text] : []).join('') + return prompt.flatMap((block): string[] => { + switch (block.type) { + case 'text': + return [block.text] + case 'resource_link': + return [`\n[resource_link name=${JSON.stringify(block.name)} uri=${JSON.stringify(block.uri)}]\n`] + default: + return [] + } + }).join('') } /** - * Whether a prompt asks the automation bridge to carry non-text content. + * Whether a prompt carries content beyond the ACP baseline. The spec requires + * every agent to accept `text` and `resource_link`; richer inline payloads + * (image, audio, embedded resource) are optional capabilities this bridge does + * not advertise, so they are rejected rather than silently dropped. * @param prompt - ACP prompt blocks to inspect. - * @returns `true` when any block is not text. + * @returns `true` when any block is neither `text` nor `resource_link`. */ export function promptHasUnsupportedContent(prompt: readonly AcpContentBlock[]): boolean { - return prompt.some(block => block.type !== 'text') + return prompt.some(block => block.type !== 'text' && block.type !== 'resource_link') } diff --git a/packages/acp/acp/src/index.ts b/packages/acp/acp/src/index.ts index 59ed499b00..c4973a9e1d 100644 --- a/packages/acp/acp/src/index.ts +++ b/packages/acp/acp/src/index.ts @@ -192,12 +192,11 @@ export function apply(ctx: Context, config: AcpConfig): void { const makeAgent = (connection: AgentSideConnection): AcpAgent => { conn = connection return { - initialize(params: InitializeRequest): Promise { - const protocolVersion = params.protocolVersion === PROTOCOL_VERSION - ? params.protocolVersion - : PROTOCOL_VERSION + initialize(_params: InitializeRequest): Promise { + // Single-version agent: per spec, answer the latest version this + // server supports regardless of the client's requested version. return Promise.resolve({ - protocolVersion, + protocolVersion: PROTOCOL_VERSION, agentInfo: { name: 'deepseek-harness-acp', version: '0.0.1' }, agentCapabilities: { promptCapabilities: { image: false, audio: false, embeddedContext: false }, @@ -239,14 +238,28 @@ export function apply(ctx: Context, config: AcpConfig): void { throw invalidParams('a prompt is already in flight for this session') } if (promptHasUnsupportedContent(params.prompt)) { - throw invalidParams('only text prompt content is supported') + throw invalidParams('only text and resource_link prompt content is supported') } const text = acpPromptToText(params.prompt) if (text.trim().length === 0) throw invalidParams('empty prompt') const stopReason = await new Promise((resolve, reject) => { + // Arm the slot before send() so a listener-driven synchronous turn + // cannot slip past correlation; a synchronous send() failure (an + // agent disposed outside the bridge, e.g. an agent-loop-only reload) + // must free the slot again or the session would reject every later + // prompt as already in flight. record.inflight = { resolve, reject, turn: undefined } - record.agent.send([{ type: 'text', text }]) + try { + record.agent.send([{ type: 'text', text }]) + } catch (error: unknown) { + record.inflight = undefined + // send() throws only Errors (disposed agent / invalid input); the + // String arm is a defensive fallback for a non-Error throw. + /* v8 ignore next */ + const detail = error instanceof Error ? error.message : String(error) + throw internalError(`prompt was not queued: ${detail}`) + } }) return { stopReason } }, diff --git a/packages/acp/acp/tests/approval.spec.ts b/packages/acp/acp/tests/approval.spec.ts index 648f597010..01bcd83249 100644 --- a/packages/acp/acp/tests/approval.spec.ts +++ b/packages/acp/acp/tests/approval.spec.ts @@ -50,6 +50,13 @@ describe('ACP machine permission policy', () => { await expect(harness.ctx.approval.request(request)).resolves.toBe('rejected') }) + it('fails closed when the client errors the permission request', async () => { + harness = await makeBridgeHarness() + const request = await ownedRequest() + harness.onPermission = () => { throw new Error('client gone') } + await expect(harness.ctx.approval.request(request)).resolves.toBe('unavailable') + }) + it('delegates a same-id foreign agent', async () => { harness = await makeBridgeHarness() const request = await ownedRequest() diff --git a/packages/acp/acp/tests/bridge.spec.ts b/packages/acp/acp/tests/bridge.spec.ts index a462506038..619a628ea1 100644 --- a/packages/acp/acp/tests/bridge.spec.ts +++ b/packages/acp/acp/tests/bridge.spec.ts @@ -107,7 +107,7 @@ describe('automation-only ACP bridge', () => { })).resolves.toHaveProperty('sessionId') }) - it('rejects empty and non-text prompts before a turn starts', async () => { + it('rejects empty and beyond-baseline prompts before a turn starts', async () => { harness = await makeBridgeHarness() await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] }) @@ -116,11 +116,28 @@ describe('automation-only ACP bridge', () => { .rejects.toThrow(/empty prompt/) await expect(harness.client.prompt({ sessionId, - prompt: [{ type: 'resource_link', name: 'file', uri: 'file:///tmp/a' }], - })).rejects.toThrow(/only text/) + prompt: [{ type: 'image', data: '', mimeType: 'image/png' }], + })).rejects.toThrow(/only text and resource_link/) expect(harness.ctx.agents.get(SessionId(sessionId))?.session.events.some(event => event.type === 'turn/start')).toBe(false) }) + it('renders baseline resource links as textual references in the user message', async () => { + harness = await makeBridgeHarness({ script: [textResponse('done')] }) + await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) + const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] }) + await harness.client.prompt({ + sessionId, + prompt: [ + { type: 'text', text: 'summarize' }, + { type: 'resource_link', name: 'notes.txt', uri: 'file:///tmp/notes.txt' }, + ], + }) + expect(harness.adapter.requests[0]?.messages.at(-1)?.content).toEqual([{ + type: 'text', + text: 'summarize\n[resource_link name="notes.txt" uri="file:///tmp/notes.txt"]\n', + }]) + }) + it('rejects prompts for unknown sessions and ignores unknown cancellation', async () => { harness = await makeBridgeHarness() await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) diff --git a/packages/acp/acp/tests/codec.spec.ts b/packages/acp/acp/tests/codec.spec.ts index 4f8eaecd56..2fdf544500 100644 --- a/packages/acp/acp/tests/codec.spec.ts +++ b/packages/acp/acp/tests/codec.spec.ts @@ -20,10 +20,19 @@ describe('ACP automation codec', () => { expect(turnEndToStopReason({ kind: 'future' } as unknown as TurnEndReason)).toBe('end_turn') }) - it('concatenates text and rejects every non-text block', () => { + it('flattens baseline blocks and rejects everything richer', () => { expect(acpPromptToText([{ type: 'text', text: 'a' }, { type: 'text', text: 'b' }])).toBe('ab') - expect(acpPromptToText([{ type: 'resource_link', name: 'x', uri: 'file:///x' }])).toBe('') - expect(promptHasUnsupportedContent([{ type: 'text', text: 'ok' }])).toBe(false) - expect(promptHasUnsupportedContent([{ type: 'resource_link', name: 'x', uri: 'file:///x' }])).toBe(true) + expect(acpPromptToText([ + { type: 'text', text: 'see' }, + { type: 'resource_link', name: 'x', uri: 'file:///x' }, + ])).toBe('see\n[resource_link name="x" uri="file:///x"]\n') + expect(acpPromptToText([{ type: 'image', data: '', mimeType: 'image/png' }])).toBe('') + expect(promptHasUnsupportedContent([ + { type: 'text', text: 'ok' }, + { type: 'resource_link', name: 'x', uri: 'file:///x' }, + ])).toBe(false) + expect(promptHasUnsupportedContent([ + { type: 'image', data: '', mimeType: 'image/png' }, + ])).toBe(true) }) }) diff --git a/packages/acp/acp/tests/edges.spec.ts b/packages/acp/acp/tests/edges.spec.ts index ab70859939..68e78fb256 100644 --- a/packages/acp/acp/tests/edges.spec.ts +++ b/packages/acp/acp/tests/edges.spec.ts @@ -54,7 +54,11 @@ describe('ACP automation output boundary', () => { expect(harness.updates).toHaveLength(0) }) - it('contains client update failures without changing prompt settlement', async () => { + // `session/update` is a JSON-RPC notification, so a client-side handler + // failure never reaches the bridge; this pins that the prompt still settles + // normally with such a client. The bridge's own write-failure guard is + // transport-level and documented untestable at `notify`. + it('settles the prompt normally when the client rejects update notifications', async () => { harness = await makeBridgeHarness({ script: [textResponse('answer')] }) await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] }) diff --git a/packages/acp/acp/tests/harness.ts b/packages/acp/acp/tests/harness.ts index 35d3c79947..aa3564ea8a 100644 --- a/packages/acp/acp/tests/harness.ts +++ b/packages/acp/acp/tests/harness.ts @@ -101,6 +101,8 @@ export interface BridgeHarness { closeClientTransport: () => Promise abortClientTransport: () => Promise acpFiber: Awaited> + /** The AgentLoop fiber, so a test can reload the loop out from under the bridge. */ + loopFiber: Awaited> dispose: () => Promise } @@ -115,7 +117,7 @@ export async function makeBridgeHarness(options: { const adapter = new MockAdapter(options.script ?? []) const ctx = new Context() await mountAgentLoopTestDependencies(ctx, { systemPrompt: { persona: options.persona ?? '' } }) - await ctx.plugin(AgentLoop, { agents: [] }) + const loopFiber = await ctx.plugin(AgentLoop, { agents: [] }) ctx.llm.registerAdapter(['mock'], adapter) const agentToClient = new TransformStream() @@ -140,6 +142,7 @@ export async function makeBridgeHarness(options: { onSessionUpdateError: undefined, client: undefined as unknown as ClientSideConnection, acpFiber: undefined as unknown as BridgeHarness['acpFiber'], + loopFiber, closeClientTransport: async () => { await clientToAgentWriter.close() }, abortClientTransport: async () => { await clientToAgentWriter.abort(new Error('client transport failed')) }, dispose: async () => { await ctx.fiber.dispose() }, diff --git a/packages/acp/acp/tests/turns.spec.ts b/packages/acp/acp/tests/turns.spec.ts index e4123efe60..8a6ab09404 100644 --- a/packages/acp/acp/tests/turns.spec.ts +++ b/packages/acp/acp/tests/turns.spec.ts @@ -109,6 +109,19 @@ describe('ACP prompt lifecycle', () => { await expect(prompt).resolves.toEqual({ stopReason: 'cancelled' }) }) + it('frees the prompt slot when the agent rejects the send synchronously', async () => { + harness = await makeBridgeHarness({ script: [] }) + const sessionId = await newSession(harness) + // Reload the loop out from under the bridge: its agents dispose while the + // bridge record survives, so the next send() throws synchronously. + await harness.loopFiber.dispose() + await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'one' }] })) + .rejects.toThrow(/prompt was not queued/) + // The failed prompt must not wedge the session's single prompt slot. + await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'two' }] })) + .rejects.toThrow(/prompt was not queued/) + }) + it('permits only one in-flight prompt per session', async () => { harness = await makeBridgeHarness({ script: ['hang'] }) const sessionId = await newSession(harness) diff --git a/packages/support/acp-snapshot/tests/fixtures/fake-acp-agent.ts b/packages/support/acp-snapshot/tests/fixtures/fake-acp-agent.ts index 00e7c8756a..9be693e7ad 100644 --- a/packages/support/acp-snapshot/tests/fixtures/fake-acp-agent.ts +++ b/packages/support/acp-snapshot/tests/fixtures/fake-acp-agent.ts @@ -149,6 +149,8 @@ async function handlePrompt(id: number | string): Promise { override: process.env.DSH_SNAPSHOT_OVERRIDE ?? null, childFiles: process.env.DSH_SNAPSHOT_CHILD_FILES ?? null, spillRoot: process.env.DSH_SNAPSHOT_SPILL_ROOT ?? null, + // Scenario-supplied deployment env (the `Scenario.env` layering seam). + permissionMode: process.env.DSH_PERMISSION_MODE ?? null, })}`) } if (behavior.echoWorkspace === true) { diff --git a/packages/support/acp-snapshot/tests/suite.spec.ts b/packages/support/acp-snapshot/tests/suite.spec.ts index 15838480c7..e021bc31d9 100644 --- a/packages/support/acp-snapshot/tests/suite.spec.ts +++ b/packages/support/acp-snapshot/tests/suite.spec.ts @@ -131,6 +131,8 @@ describe('defineAcpSnapshotSuite: refresh write-back', () => { expect(stdout).not.toContain('stale stdout') expect(stdout).toContain('env:{\\"mode\\":\\"replay\\"') expect(stdout).not.toContain('\\"mode\\":\\"refresh\\"') + // The scenario's own env layer reached the subprocess. + expect(stdout).toContain('\\"permissionMode\\":\\"never\\"') const blocked = readFileSync(join(refreshDir, 'blocked-log', 'session.jsonl'), 'utf8') expect(blocked).toContain('"decision":"block"') From 5569f3f8ac9ac5798376bc690eca15be79967a80 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:12:23 +0800 Subject: [PATCH 2/4] docs: repair rewritten rationale and stale claims from the ACP reduction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The automation-only rewrite edited many implemented Agent Notes; several edits replaced still-live or historical rationale instead of reframing: - llm-model-catalog: restore the prompt/request consistency section and selection-ownership alternatives — installAgentLlmTarget and the TUI /model selector still ship that design; only the ACP wire is gone. - plan-specific-collaboration-state, acp-multi-session, todo-write, ask-user-question: link the superseding automation-only note instead of silently rewriting the original decision or motivation; drop a paragraph duplicating the Web-provider facts stated two paragraphs up. - sandbox: stop claiming unit coverage for turn-enclosed config writes (that mechanism left with the bridge) and retitle the commit-boundary paragraph accordingly. - Fix the missing blank line before '## Consequences' in the plugin-command-registration pair, the JSON-RPC/Web render-intent consumer misattribution (the second consumer is the host/client runtime), stale bash_output/bash_kill names, and 'optional goals' in architecture.md. - examples/acp-agent/README.md: point at the package contract instead of restating it; packages/ui/permission and plan-mode READMEs record the consumer-less preset service and the exit_plan_mode coverage gap under Known Limitations. - 2026-06-19-acp-snapshot-tests: the new note defers the corpus migration rather than committing to it; say so. Re-record the touched bilingual pairs. --- .../2026-07-02-tool-render-intent-union.md | 2 +- ...-model-catalog-and-acp-selection.i18n.yaml | 4 ++-- ...-15-llm-model-catalog-and-acp-selection.md | 21 ++++++++++++++++--- ...-llm-model-catalog-and-acp-selection.zh.md | 21 ++++++++++++++++--- .../2026-06-14-acp-agent-client-protocol.md | 2 +- .../feature/2026-06-14-acp-multi-session.md | 6 ++++-- .../feature/2026-06-15-code-mode.md | 2 +- .../feature/2026-06-25-ask-user-question.md | 2 +- .../feature/2026-06-29-todo-write-tool.md | 4 ++-- .../implemented/feature/2026-07-06-sandbox.md | 4 ++-- ...7-19-plugin-command-registration.i18n.yaml | 4 ++-- .../2026-07-19-plugin-command-registration.md | 1 + ...26-07-19-plugin-command-registration.zh.md | 1 + ...lan-specific-collaboration-state.i18n.yaml | 4 ++-- ...07-22-plan-specific-collaboration-state.md | 2 +- ...22-plan-specific-collaboration-state.zh.md | 2 +- .../testing/2026-06-19-acp-snapshot-tests.md | 2 +- docs/architecture.i18n.yaml | 4 ++-- docs/architecture.md | 2 +- docs/architecture.zh.md | 2 +- examples/acp-agent/README.md | 9 +------- packages/plan/plan-mode/README.md | 1 + packages/ui/permission/README.md | 1 + 23 files changed, 66 insertions(+), 37 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md index 9837f27b82..5dba85cdfc 100644 --- a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md +++ b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md @@ -12,7 +12,7 @@ A tool declares how its calls render in a UI (an editor's tool-call card) throug - Which combinations are *valid* is unwritten: a `terminal` call that also sets `content` means "description above the card"; a generic call that sets `terminal` is meaningless but representable. The type permits nonsense. - There is no way to express the one file-tool affordance an editor most wants — a **diff card** (`{path, oldText, newText}`, which Zed renders as an inline diff / new-file preview). `ToolCallPresentation.content` is the *LLM* `ContentBlock[]` vocabulary (text/image), so a tool literally cannot ask for a diff. -The existing `FIXME(tool-presentation)` in `packages/core/tools/src/index.ts` named the fix: "redesign the type so a tool declares its render INTENT once (e.g. a tagged union over card kinds) rather than a bag of optional fields the bridge stitches together." The rejected Agent Note [Collapse tool-owned UI presentation](../../rejected/simplification/2026-06-20-generic-tool-rendering.md) deferred it explicitly: rich rendering "should return later as a tagged render-intent union after there are at least two real tools and two real consumers to validate the vocabulary." That bar is met by multiple producer families plus the TUI and JSON-RPC/Web consumers. +The existing `FIXME(tool-presentation)` in `packages/core/tools/src/index.ts` named the fix: "redesign the type so a tool declares its render INTENT once (e.g. a tagged union over card kinds) rather than a bag of optional fields the bridge stitches together." The rejected Agent Note [Collapse tool-owned UI presentation](../../rejected/simplification/2026-06-20-generic-tool-rendering.md) deferred it explicitly: rich rendering "should return later as a tagged render-intent union after there are at least two real tools and two real consumers to validate the vocabulary." That bar is met by multiple producer families plus the TUI and host/client-runtime (Web) consumers. ## Decision diff --git a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml index edec1d5969..2a9e4a5d95 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-15-llm-model-catalog-and-acp-selection.md: 88a86bfd3d2a190f756f091d9734f85f50312b2e -2026-07-15-llm-model-catalog-and-acp-selection.zh.md: c9cc9c633864943b4395dea520cfcbf7b228a606 +2026-07-15-llm-model-catalog-and-acp-selection.md: 689eab30eed8b22295f8757f83d458e41df7c2f3 +2026-07-15-llm-model-catalog-and-acp-selection.zh.md: e5766dec70f30b8fc9b5c5ddca59f594c7ad598b diff --git a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md index 88a86bfd3d..689eab30ee 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md +++ b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md @@ -26,22 +26,37 @@ Catalog membership is advisory. It drives selectors and diagnostics but never ch `dsh-llm-pi-ai` maps the configured provider's installed `getModels(provider)` entries into the neutral catalog. Its existing request-time catalog lookup remains authoritative and still rejects unknown models with `UNKNOWN_MODEL`. `dsh-llm-deepseek` accepts an optional `models` config containing display entries, defaulting to `deepseek-v4-flash` and `deepseek-v4-pro`. An explicit list replaces those defaults and an empty list disables discovery. The entries improve selector UX for known public or private models, while every unlisted model id continues to pass through unchanged. -### ACP transport boundary +### Per-session selection in the front door -The ACP automation transport is not a catalog consumer. Its deployment config supplies one optional provider/model target for newly created agents, and it advertises no model selector or configuration-option interface. TUI, Web, SDK hosts, and other human-facing consumers may use the advisory catalog through their own interaction contracts. +A selection is owned by the front door that offers it (today the TUI `/model` selector), never by `LlmService` or `AgentOptions`: those are deployment-wide or creation-wide objects, and mutating them would couple concurrent sessions. Each opaque choice carries the full provider/model pair, because the same model id may appear under multiple routes. + +The ACP automation transport is not a catalog consumer. Its deployment config supplies one optional provider/model target for newly created agents, and it advertises no model selector or configuration-option interface. + +### Prompt/request consistency and durability + +`installAgentLlmTarget` (in `dsh-agent`) installs scoped `system-prompt/assemble` and `agent/request` listeners for a front-door-owned target. Prompt assembly snapshots the selected pair once per step, overwrites the assembled `provider` and `model` variables after downstream prompt listeners, and the request listener applies that same snapshot after downstream request listeners. A selection during asynchronous assembly therefore starts on the next step rather than splitting prompt text from routing. Other call-config fields remain untouched. + +The request header remains the durable source of truth. When a selected target is actually used, the existing full `request/header` snapshot records it, and a front door initializes its selection from the folded last request header before falling back to creation options. A selection that is never used by a request is intentionally in-memory only because it never became model-visible state. ## Alternatives considered +**Return model strings only.** A model-only value loses the provider route and becomes ambiguous as soon as two providers expose the same id. + **Make catalogs mandatory whitelists.** This conflicts with the hand-written adapter's arbitrary model pass-through and private deployments. The selected adapter already owns authoritative request validation. +**Store selection in `AgentOptions` or `LlmService`.** Those are creation-wide or deployment-wide objects. Mutating them would couple concurrent sessions and bypass the logged `agent/request` replacement path. + +**Persist a new model-selection session event immediately.** An unused UI selection has not affected a model request. Recording the existing request header when the target is consumed preserves the model-visible-if-and-only-if-logged rule without adding a second source of truth. + ## Consequences - Any adapter can expose a dynamic model list without leaking provider-library types into the core seam. - Catalog consumers must treat absence as “not advertised,” never “invalid request.” - pi-ai adapters expose their installed provider catalogs; hand-written DeepSeek deployments list known choices explicitly and retain arbitrary model support. - Human-facing catalog consumers own their selection interaction. ACP uses its fixed deployment target and does not widen the protocol with model discovery. +- Request headers remain compatible with the provider-routed session shape; no new JSONL event or format version is required. - A catalog read can be asynchronous, and every caller receives detached values. ## Testing -Unit coverage validates catalog detachment and malformed metadata plus pi-ai and DeepSeek catalog projection. ACP transport tests validate fixed provider/model forwarding independently of catalog discovery. +Unit coverage validates catalog detachment and malformed metadata, pi-ai and DeepSeek catalog projection, provider/model request routing, prompt-variable alignment, and per-agent target isolation. ACP transport tests validate fixed provider/model forwarding independently of catalog discovery; the TUI suite covers selector interaction and header-based restoration. diff --git a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md index c9cc9c6338..e5766dec70 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md @@ -26,22 +26,37 @@ ACP 选择还必须保留提供方维度。同一个模型 ID 可能存在于多 `dsh-llm-pi-ai` 将已配置提供方的安装目录 `getModels(provider)` 映射为中立目录。其现有请求时目录查询仍是权威依据,未知模型仍以 `UNKNOWN_MODEL` 失败。`dsh-llm-deepseek` 接受可选的 `models` 配置作为展示条目,默认包含 `deepseek-v4-flash` 和 `deepseek-v4-pro`。显式列表会替换这些默认值,空列表则关闭发现。这些条目改善已知公开或私有模型的选择体验,而所有未列出的模型 ID 仍会原样透传。 -### ACP 传输边界 +### 前门内的会话级选择 -ACP 自动化传输层不是目录消费方。它通过部署配置为新创建的 agent 提供一个可选的提供方/模型目标,不展示模型选择器或配置选项接口。TUI、Web、SDK host 和其他面向人类的消费方可以通过各自的交互契约使用该建议性目录。 +选择由提供它的前门拥有(今天是 TUI 的 `/model` 选择器),而不由 `LlmService` 或 `AgentOptions` 拥有:它们是部署级或创建级对象,改动它们会把并发会话耦合在一起。每个不透明选项都携带完整的提供方/模型对,因为同一模型 ID 可能出现在多个路由下。 + +ACP 自动化传输层不是目录消费方。它通过部署配置为新创建的 agent 提供一个可选的提供方/模型目标,不展示模型选择器或配置选项接口。 + +### Prompt/请求一致性与持久化 + +`installAgentLlmTarget`(位于 `dsh-agent`)为前门拥有的目标安装 agent 作用域的 `system-prompt/assemble` 与 `agent/request` 监听器。Prompt 组装在每个 step 对所选组合做一次快照,在下游 prompt 监听器之后覆写组装出的 `provider` 与 `model` 变量;请求监听器在下游请求监听器之后应用同一快照。因此,发生在异步组装期间的选择会从下一个 step 生效,而不会让 prompt 文本与路由分裂。其他调用配置字段保持不变。 + +请求头仍是持久化的事实来源。当所选目标真正被使用时,现有的完整 `request/header` 快照会记录它;前门先从折叠后的最后一个请求头初始化其选择,然后才回退到创建选项。从未被请求使用的选择有意只保留在内存中,因为它从未成为模型可见状态。 ## 考虑过的替代方案 +**只返回模型字符串。** 只有模型的值会丢失提供方路由,一旦两个提供方暴露相同 ID 就会产生歧义。 + **将目录设为强制白名单。** 这与手写适配器的任意模型透传和私有部署冲突。请求的权威校验本就属于被选中的适配器。 +**把选择存进 `AgentOptions` 或 `LlmService`。** 它们是创建级或部署级对象。改动它们会把并发会话耦合在一起,并绕过有日志记录的 `agent/request` 替换路径。 + +**立即持久化一个新的模型选择会话事件。** 未被使用的 UI 选择尚未影响任何模型请求。在目标被消费时记录现有请求头,既保持“模型可见当且仅当有日志”的规则,又不会引入第二个事实来源。 + ## 结果 - 任意适配器都能暴露动态模型列表,无需把提供方库类型泄漏到核心接缝。 - 目录消费者必须把缺失理解为“未展示”,而不是“请求无效”。 - pi-ai 适配器会暴露其已安装的提供方目录;手写 DeepSeek 部署显式列出已知选项,同时保留对任意模型的支持。 - 面向人类的目录消费方拥有各自的选择交互。ACP 使用固定部署目标,不会为模型发现扩大协议范围。 +- 请求头与基于提供方路由的会话形态保持兼容;不需要新的 JSONL 事件或格式版本。 - 目录读取可以是异步的,且每个调用方都会收到分离后的值。 ## 测试 -单元测试覆盖目录分离与错误元数据,以及 pi-ai 和 DeepSeek 目录投影。ACP 传输测试独立验证固定提供方/模型的转发行为。 +单元测试覆盖目录分离与错误元数据、pi-ai 和 DeepSeek 目录投影、提供方/模型请求路由、prompt 变量对齐,以及按 agent 隔离的目标。ACP 传输测试独立验证固定提供方/模型的转发行为;TUI 套件覆盖选择器交互与基于请求头的恢复。 diff --git a/.agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md b/.agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md index 908274532f..0579d9c279 100644 --- a/.agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md +++ b/.agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md @@ -12,7 +12,7 @@ The bridge must preserve the harness's existing ownership boundaries. It cannot ## Decision -`@deepseek-ai/dsh-acp` was a UI/client-driver plugin in the former UI package group. It uses `@agentclientprotocol/sdk`'s `AgentSideConnection` over stdin/stdout and programs only interface services: the agent create/resume factory, session persistence, tool registry, user interaction, and optional approval/bash capabilities. It does not change the agent loop and is not a capability-seam implementation. +`@deepseek-ai/dsh-acp` was a UI/client-driver plugin in the `ui` package group (it now lives in `acp`). It used `@agentclientprotocol/sdk`'s `AgentSideConnection` over stdin/stdout and programmed only interface services: the agent create/resume factory, session persistence, tool registry, user interaction, and optional approval/bash capabilities. It did not change the agent loop and was not a capability-seam implementation. The bridge implements the following stable session path: diff --git a/.agents/notes/implemented/feature/2026-06-14-acp-multi-session.md b/.agents/notes/implemented/feature/2026-06-14-acp-multi-session.md index 00b06f1e86..96974220d1 100644 --- a/.agents/notes/implemented/feature/2026-06-14-acp-multi-session.md +++ b/.agents/notes/implemented/feature/2026-06-14-acp-multi-session.md @@ -2,6 +2,8 @@ Status: implemented +> Written when ACP was an editor bridge, motivated by Zed's multi-session client model. [ACP as an automation-only protocol](../simplification/2026-07-23-acp-automation-only-protocol.md) removed the editor surfaces; the multiplexing decision itself is unchanged and this note now states it against the automation contract. + ## Problem An ACP automation client can keep several conversations alive over one agent subprocess. A single-active-session bridge would force extra processes and prevent one parent controller from driving independent children over one connection. Multiplexing introduces isolation risks: committed answers, prompt completion, cancellation, permission requests, and predictable background-task ids must never cross session boundaries. @@ -14,7 +16,7 @@ Every `session/event` callback resolves the owning record before sending or sett Permission ownership uses the same exact-agent check against the forward map. The ACP `approval/request` answerer sends a one-shot machine-policy request only for the session that owns the requesting agent and delegates foreign or call-less requests. The bridge has no elicitation, config-selection, or other human-interaction state. -Background bash tasks carry an opaque owner token equal to the owning session id. `bash_output` and `bash_kill` compare the caller's token with the executor's task ownership before reading or killing; a predictable task id alone grants no access. Ownership is stored with the executor task, so a tool plugin reload does not erase it. +Background bash tasks carry an opaque owner token equal to the owning session id. `task_output` and `task_kill` compare the caller's token with the executor's task ownership before reading or killing; a predictable task id alone grants no access. Ownership is stored with the executor task, so a tool plugin reload does not erase it. Connection teardown clears the live map, settles each pending prompt as cancelled, and disposes all `AgentHandle`s in parallel. Each handle stops and awaits its loop, flushes the session while attached, unregisters the agent, and removes the session. Teardown is memoized and shared by client disconnect and plugin disposal. @@ -42,4 +44,4 @@ The bridge exposes no protocol method to close one live session independently. R ## Verification -The multi-session suite drives concurrent sessions through routed committed answers, independent in-flight prompts, targeted cancellation, permission routing, exact-agent rejection, and shared teardown. Tool-bash tests prove one session cannot read or kill another session's background task. +The multi-session suite drives concurrent sessions through routed committed answers, independent in-flight prompts, targeted cancellation, and shared teardown; the approval and output-boundary suites cover permission routing and exact-agent rejection. Tool-bash tests prove one session cannot read or kill another session's background task. diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.md b/.agents/notes/implemented/feature/2026-06-15-code-mode.md index 7193e63e80..1b191686c3 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.md +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.md @@ -48,7 +48,7 @@ Under `'code'` and `'both'` the registry owns `run_code` as a reserved presentat **Concurrency is serialized.** Each run owns a dispatch queue, so even `Promise.all` executes tool calls in submission order. Settlement abandons queued calls that have not started. Parallelism requires per-tool concurrency-safety metadata. -**Presentation.** `run_code`'s render intent is decided here per the [render-intent Agent Note](../architecture/2026-07-02-tool-render-intent-union.md): `presentCall` creates a `generic` card with `kind: 'execute'`, the program text as its title, and the same program text as `rawInput`; `run_code` intentionally declares no `presentResult`, so TUI and JSON-RPC/Web complete that card through their generic raw-content fallback using the final durable `tool/result.content`, including captured logs plus the returned value, failure, or post-policy spill preview. This is not a `terminal` card: that card's semantics are "a shell command in a working directory", which a program is not. See the [result-card completeness note](../bug-fix/2026-07-20-code-mode-result-card-completeness.md). +**Presentation.** `run_code`'s render intent is decided here per the [render-intent Agent Note](../architecture/2026-07-02-tool-render-intent-union.md): `presentCall` creates a `generic` card with `kind: 'execute'`, the program text as its title, and the same program text as `rawInput`; `run_code` intentionally declares no `presentResult`, so the TUI and host/client runtime (Web) complete that card through their generic raw-content fallback using the final durable `tool/result.content`, including captured logs plus the returned value, failure, or post-policy spill preview. This is not a `terminal` card: that card's semantics are "a shell command in a working directory", which a program is not. See the [result-card completeness note](../bug-fix/2026-07-20-code-mode-result-card-completeness.md). ### Observability: `tool/code-dispatch` diff --git a/.agents/notes/implemented/feature/2026-06-25-ask-user-question.md b/.agents/notes/implemented/feature/2026-06-25-ask-user-question.md index 4f6dc3e2d1..94ee8c426b 100644 --- a/.agents/notes/implemented/feature/2026-06-25-ask-user-question.md +++ b/.agents/notes/implemented/feature/2026-06-25-ask-user-question.md @@ -26,7 +26,7 @@ The Web composer shows one question at a time while retaining every request in t `dsh-tui` renders each question as a keyboard overlay, shows option descriptions, supports single- and multi-select choices plus free-form custom answers, and rejects pending questions on abort, provider disposal, or terminal shutdown. Batched and simultaneous requests are queued so one overlay owns keyboard focus at a time. -The Web host exposes the same seam through its selected question provider. Stable request ids, whole-request cancellation, owner abort, per-item skips, and structured batch settlement keep browser interaction behind the provider-neutral service. +An ACP elicitation mapping existed while the bridge was an editor UI; [ACP as an automation-only protocol](../simplification/2026-07-23-acp-automation-only-protocol.md) removed that third mapping. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-06-29-todo-write-tool.md b/.agents/notes/implemented/feature/2026-06-29-todo-write-tool.md index ba482809e2..6a6a4c9ae8 100644 --- a/.agents/notes/implemented/feature/2026-06-29-todo-write-tool.md +++ b/.agents/notes/implemented/feature/2026-06-29-todo-write-tool.md @@ -12,7 +12,7 @@ Add a model-facing `todo_write(todos: [{ content, status }])` tool whose whole-l ### Whole-list replace, three-state status -The model sends the entire list every call; the new list replaces the old (last-write-wins on replay). This is the shape claude-code V1, opencode, and codex `update_plan` all use, and the shape the model is most trained on — no per-item ids, no delta protocol. `status` is exactly `pending | in_progress | completed`, the same triple as codex `update_plan`. +The model sends the entire list every call; the new list replaces the old (last-write-wins on replay). This is the shape claude-code V1, opencode, and codex `update_plan` all use, and the shape the model is most trained on — no per-item ids, no delta protocol. `status` is exactly `pending | in_progress | completed`, the same triple as codex `update_plan`; it also matched the ACP `PlanEntryStatus` 1:1 while the bridge projected todo lists as `plan` updates, a mapping retired with the [automation-only ACP contract](../simplification/2026-07-23-acp-automation-only-protocol.md). ### State on the session log, not a service @@ -24,7 +24,7 @@ The list is appended as a `todo/write` event carrying the full `{ todos }` snaps ### Dropped vs claude-code V1: `activeForm`, id, priority -claude-code V1's item is `{ content, status, activeForm }`; later (V2) it grew ids, dependencies, and ownership — but only to support agent *swarms* (disk-backed, lock-guarded, per-item mutation). This tool keeps the item at the minimum: `{ content, status }`. No `activeForm` (the present-continuous label) — the UI shows `content`; no id — whole-list replace needs no stable identity; no priority — ordering is the only ranking the model controls. Each dropped field is one less thing the model must produce on every call. +claude-code V1's item is `{ content, status, activeForm }`; later (V2) it grew ids, dependencies, and ownership — but only to support agent *swarms* (disk-backed, lock-guarded, per-item mutation). This tool keeps the item at the minimum: `{ content, status }`. No `activeForm` (the present-continuous label) — the UI shows `content`; no id — whole-list replace needs no stable identity; no priority — that was only ever an ACP `PlanEntry` wire requirement, synthesized as a constant at the bridge boundary rather than modeled, and it left with that projection. Each dropped field is one less thing the model must produce on every call. ### Single owner — no swarm machinery (YAGNI) diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.md index b05ac1b5c2..47e4225da5 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.md @@ -107,7 +107,7 @@ Sandbox mode is not narrated in the prompt; denial results report the mode when **The optional UI surface** is `PermissionService`: a deployment-defined preset table whose entries bundle one sandbox mode with one approval policy. The shipped `workspace-write` and `danger-full-access` presets write through to both domain setters; a knob combination outside the table is reported as `custom`. UI adapters may expose that table as a selector. The automation-only ACP transport advertises no configuration selector and mounts no permission-preset service. -**Turn enclosure is the commit boundary.** A runtime switch records its preset and changed knob events on the target session, and every later capability resolution folds the last values. Adapters must use a valid session append boundary; the ACP transport has no runtime switch path. +**The committed event is the commit boundary.** A runtime switch records its preset and changed knob events on the target session, and every later capability resolution folds the last values. Adapters own choosing a valid session append boundary; the ACP transport has no runtime switch path. (The former ACP idle-switch anchoring — holding a pending idle selection until the next prompt submission — left with that bridge.) #### In-process tools @@ -115,7 +115,7 @@ fs/web/todo execute in-process, so their sandbox semantics are policy at their s ### Testing -- **Unit:** pin platform selection and profiles, fail-closed runner classification, per-call mode/root resolution, per-process facts, escalation validation and outcomes, permission preset folding and write-through, narrator coalescing, and turn-enclosed config writes. +- **Unit:** pin platform selection and profiles, fail-closed runner classification, per-call mode/root resolution, per-process facts, escalation validation and outcomes, permission preset folding and write-through, and narrator coalescing. - **Keyless real-runner:** exercise bwrap, Landlock, and Seatbelt against real filesystem effects at provider and bash-consumer layers; one real Cordis context concurrently drives two project sessions through shipped bash and fs tools, proving own-root success and sibling-root denial. Packed-install coverage proves the registry launcher remains executable. CI rejects a silent all-skip. - **With-key:** start the real ACP composition in read-only mode, let a model-driven bash write hit the runner's denial marker, then drive the bridge answerer and disk effect through granted and rejected workspace-write retries; unavailable credentials or runners self-skip. - **Snapshot:** pin prompt deltas and notices plus both scripted approval branches. A real ACP example scenario places its session under the user home while the deployment fallback points at `/tmp`, then pins a successful deployment-selected workspace-write mutation; this distinguishes session-root resolution from the process fallback without depending on runner-specific denial text. Other snapshots start unconfined so unrelated fixtures remain platform-independent. diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml index cc3aca9788..8b2ddb5ac1 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-19-plugin-command-registration.md: 119b1e6e545dca8ea8cb425407723c8d11c806ad -2026-07-19-plugin-command-registration.zh.md: fed14ca6e3bdb0cd40dabac7641a359f4bc3b3f2 +2026-07-19-plugin-command-registration.md: 343cb5d946dba9fb881adf12c197961dfd6a359b +2026-07-19-plugin-command-registration.zh.md: 27757f05afe04d7cbd4ceaf9380b6f73441cc4b9 diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md index 119b1e6e54..343cb5d946 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md @@ -55,6 +55,7 @@ TUI tests exercise all migrated built-ins, live plugin discovery, help/autocompl - **Attach adapter masks to each definition** — rejected because support is a composition fact, not command-domain state. Every composed adapter exposes a registered command; an incompatible plugin omits registration in that deployment. - **Send unknown slash input to the model** — rejected because typoed or unavailable direct actions must fail predictably rather than change execution planes. - **Persist generic command input and output** — rejected because adapter notices are not model-visible state. A handler that changes durable behavior calls the owning domain API, which records its own events. + ## Consequences - Command producers are ordinary removable plugins, and TUI consumes their validated catalog and dispatch contract. diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md index fed14ca6e3..27757f05af 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md @@ -55,6 +55,7 @@ TUI 测试覆盖全部迁移后的内置命令、实时插件发现、帮助与 - **为每个定义附加适配器掩码**——不予采纳,因为支持能力是组合事实,而不是命令领域状态。每个已组合适配器都暴露已注册命令;不兼容插件不会在该部署中注册。 - **把未知斜杠输入发送给模型**——不予采纳,因为输入错误或不可用的直接操作必须可预测地失败,而不能改变执行平面。 - **持久化通用命令输入与输出**——不予采纳,因为适配器提示不是模型可见状态。改变持久行为的处理器会调用拥有该状态的领域 API,由后者记录自己的事件。 + ## 后果 - 命令生产者是普通的可移除插件,TUI 消费其经过校验的目录与分派契约。 diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml index 01bae69974..d0c69e6c43 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-22-plan-specific-collaboration-state.md: 768fc45c1ec483662c2561269b164835bb235452 -2026-07-22-plan-specific-collaboration-state.zh.md: 174745c55fde5b0d30e72315ba472283c1b7654d +2026-07-22-plan-specific-collaboration-state.md: d6b606d2235b5dbcb7e1882dd34e8965799c1199 +2026-07-22-plan-specific-collaboration-state.zh.md: c0dc22f6ec296a293681b78cebe7cc05f49f774b diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md index 768fc45c1e..d6b606d223 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md @@ -16,7 +16,7 @@ Plan mode owns a plan-specific product package: `@deepseek-ai/dsh-plan-mode` at Configuration is exactly `{ section: string }`. The package registers the fixed `plan:policy` section, `/plan [message]`, the exact `/plan off` direct-exit form, and `exit_plan_mode` itself. Bare `/plan` selects active; another non-empty argument selects it first and then sends the trimmed text through `agent.steer()`, making the text an ordinary logged user message in the affected step. `/plan off` selects inactive without model input and can cancel an entry that is still pending at the boundary. The exit tool remains registered while plan mode is inactive so the request tool catalog stays stable. -Human-facing compositions own plan selection and review. The ACP automation composition mounts neither plan mode nor a mode-selection protocol, so its transport does not widen this product-specific vocabulary. +Human-facing compositions own plan selection and review. This note originally kept ACP's protocol-level `default`/`plan` picker as an adapter over the boolean service; [ACP as an automation-only protocol](2026-07-23-acp-automation-only-protocol.md) supersedes that wire projection, so the ACP composition now mounts neither plan mode nor a mode-selection protocol. Sandbox mode and approval policy remain separate enforcement axes. Plan mode neither reads nor writes them, and the simplification introduces no shared base type, registry, or preset abstraction across those concepts. diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md index 174745c55f..c0dc22f6ec 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md @@ -16,7 +16,7 @@ Plan mode 拥有一个 plan 专用产品包:位于 `packages/plan/plan-mode/` 配置严格为 `{ section: string }`。该包自行注册固定的 `plan:policy` 段、`/plan [message]`、精确匹配的 `/plan off` 主动退出形式,以及 `exit_plan_mode`。不带参数的 `/plan` 选择激活;其他非空参数则先选择激活,再通过 `agent.steer()` 发送去除首尾空白后的文本,使该文本在受影响的步骤中成为一条记录到日志的普通用户消息。`/plan off` 选择未激活,不产生模型输入,并可取消仍待在边界生效的进入选择。即使 plan mode 未激活,退出工具仍保持注册,以确保请求工具目录稳定。 -面向人类的组合拥有 plan 选择与评审。ACP 自动化组合既不挂载 plan mode,也不提供模式选择协议,因此其传输层不会扩大这套产品专用词汇。 +面向人类的组合拥有 plan 选择与评审。本笔记最初把 ACP 协议级的 `default`/`plan` 选择器保留为布尔服务之上的适配器;[ACP 作为仅面向自动化的协议](2026-07-23-acp-automation-only-protocol.md)取代了那个线上投影,因此 ACP 组合现在既不挂载 plan mode,也不提供模式选择协议。 沙箱模式与审批策略仍是彼此独立的强制约束轴。Plan mode 既不读取也不写入二者;此次简化也没有为这些概念引入共享基类型、注册表或预设抽象。 diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md index 9ab3ee7a08..e5f637ba0b 100644 --- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md +++ b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md @@ -77,6 +77,6 @@ Tool determinism comes from a generated cwd, scrubbed environment, fresh non-log ## Consequences -The tier adds reviewed per-scenario input, session, stdout, optional override, and optional workspace fixtures. Workspace seeds are copied into the generated cwd for both record and replay. In return the tier provides deterministic keyless coverage through the real Loader and tool composition. Most retained scenarios exercise the assembled backend rather than ACP; the [automation-only ACP decision](../simplification/2026-07-23-acp-automation-only-protocol.md#snapshot-boundary) records the explicit follow-up to move that corpus to a transport-neutral headless suite. +The tier adds reviewed per-scenario input, session, stdout, optional override, and optional workspace fixtures. Workspace seeds are copied into the generated cwd for both record and replay. In return the tier provides deterministic keyless coverage through the real Loader and tool composition. Most retained scenarios exercise the assembled backend rather than ACP; the [automation-only ACP decision](../simplification/2026-07-23-acp-automation-only-protocol.md#snapshot-boundary) keeps that corpus here and defers any move to a transport-neutral headless suite as an independent testing change (the suite-level FIXME marks it). This Agent Note relates to but does not supersede the [proposed determinism Agent Note](../../proposed/testing/2026-06-11-deterministic-and-stress-testing.md): that proposal's "universal replay fixture" re-derives session *message history* after every test (an internal-consistency invariant), whereas these snapshots pin assembled behavior plus the external automation output. They are complementary until the backend corpus moves off ACP. diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 3ef65b4ac9..b8f3487a65 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -architecture.md: a2e8338399d797055a9432a32e73720f283a50f4 -architecture.zh.md: e282b35097b6f27b56f8e7a48ea2b6a553e8333e +architecture.md: 78a0ab26c7e5c38b497204459cce5656c0aa4500 +architecture.zh.md: 7e6294330f43538f6421f0e3044e066fa5581e2f diff --git a/docs/architecture.md b/docs/architecture.md index a2e8338399..78a0ab26c7 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -167,7 +167,7 @@ Exceptions combine layers: LLM interface/consumer; filesystem policy; web regist ### Bundles And Apps -`dsh-agent-spine-demo` bundles a spine and goals. App packages own TUI, CLI, ACP automation, and JSON-RPC front doors ([README](../packages/examples/agent-spine-demo/README.md), [acp/](../packages/acp/README.md), [ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK supplies a default only without explicit config ([Python SDK](../python/README.md)). Thin deployments use swappable backends and optional tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)). +`dsh-agent-spine-demo` bundles a spine and optional goals. App packages own TUI, CLI, ACP automation, and JSON-RPC front doors ([README](../packages/examples/agent-spine-demo/README.md), [acp/](../packages/acp/README.md), [ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK supplies a default only without explicit config ([Python SDK](../python/README.md)). Thin deployments use swappable backends and optional tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)). ### Where New Behavior Goes diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index e282b35097..7e6294330f 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -167,7 +167,7 @@ forever: ### 组合包与应用 -`dsh-agent-spine-demo` 组合一套主干和目标。应用包负责 TUI、CLI(命令行界面)、ACP 自动化入口和 JSON-RPC 入口([README](../packages/examples/agent-spine-demo/README.md)、[acp/](../packages/acp/README.md)、[ui/](../packages/ui/README.md))。`dsh-jsonrpc-agent` 启动外部 `cordis.yml`;Python SDK 仅在没有显式配置时提供默认项([Python SDK](../python/README.md))。轻量部署使用可替换后端和可选工具([examples/](../examples/AGENTS.md)、[可运行接线](cookbook/extension-cookbook.md#runnable-wirings)、[图谱](graph-atlas.md))。 +`dsh-agent-spine-demo` 组合一套主干和可选的目标。应用包负责 TUI、CLI(命令行界面)、ACP 自动化入口和 JSON-RPC 入口([README](../packages/examples/agent-spine-demo/README.md)、[acp/](../packages/acp/README.md)、[ui/](../packages/ui/README.md))。`dsh-jsonrpc-agent` 启动外部 `cordis.yml`;Python SDK 仅在没有显式配置时提供默认项([Python SDK](../python/README.md))。轻量部署使用可替换后端和可选工具([examples/](../examples/AGENTS.md)、[可运行接线](cookbook/extension-cookbook.md#runnable-wirings)、[图谱](graph-atlas.md))。 ### 新行为的归属位置 diff --git a/examples/acp-agent/README.md b/examples/acp-agent/README.md index bd4ca4d6ff..589bde267d 100644 --- a/examples/acp-agent/README.md +++ b/examples/acp-agent/README.md @@ -13,7 +13,7 @@ The leaf loads the ACP app, DeepSeek adapter, sandboxed bash and filesystem stac Stdout carries only newline-delimited ACP JSON-RPC. `@deepseek-ai/dsh-acp-demo` installs no stdout logger; leaf additions must use stderr for diagnostics. -The server accepts initialization/authentication, fresh text sessions, one in-flight prompt per session, cancellation, and one-shot permission decisions. It emits only committed assistant text. Session navigation, commands, modes, configuration pickers, elicitation, titles, plans, reasoning, tool cards, and terminal output are intentionally absent; the full contract lives in [`@deepseek-ai/dsh-acp`](../../packages/acp/acp/README.md). +The automation contract — supported methods, baseline prompt content, committed-text output, and the intentionally absent UI surfaces — lives in [`@deepseek-ai/dsh-acp`](../../packages/acp/acp/README.md). ## Session workspaces and permissions @@ -26,10 +26,3 @@ Under `workspace-write`, a model retry requesting wider sandbox access triggers This example owns the ACP snapshot suite. It boots the real automation server, replays committed model streams through `dsh-llm-replay`, and compares both normalized protocol output and re-persisted session logs. Recording uses the real model; refresh reuses committed replay input. Overrides cover throw/hang behavior, and optional `workspace/` fixtures seed world-state checks. Most scenarios pin backend behavior rather than ACP-specific behavior; the [automation-only ACP decision](../../.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md#snapshot-boundary) owns why that coverage remains transport-coupled. - -## Protocol limitations - -- Sessions are fresh and connection-owned; load, list, resume, close, delete, and fork are unsupported. -- Prompts accept text only; resource links, images, audio, embedded resources, non-empty additional directories, and MCP servers reject. -- Output is committed assistant text, not live progress, tool activity, reasoning, plans, titles, or usage. -- All sessions close with the connection; there is no per-session close method. diff --git a/packages/plan/plan-mode/README.md b/packages/plan/plan-mode/README.md index a38251826d..3f34441877 100644 --- a/packages/plan/plan-mode/README.md +++ b/packages/plan/plan-mode/README.md @@ -86,3 +86,4 @@ Mode transitions do not change the tool catalog; plan arguments and review resul - Plan mode guides rather than enforces; deployments needing a hard boundary must combine independent sandbox and approval controls. - A pending selection made while idle is lost if the process exits before the next boundary, so the UI must reapply it. - Forked agents inherit logged plan state, while newly spawned agents begin inactive; there is no creation-time plan option. +- The `exit_plan_mode` review arc (submit → human review → approved flip or rejected feedback) is covered by package tests only; its assembled-application snapshot left with the retired ACP UI scenarios ([automation-only ACP](../../../.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md)) and the TUI keyless scenarios exercise only `/plan` entry and `/plan off` exit. diff --git a/packages/ui/permission/README.md b/packages/ui/permission/README.md index 67eb1c5164..cdf6e0f435 100644 --- a/packages/ui/permission/README.md +++ b/packages/ui/permission/README.md @@ -16,6 +16,7 @@ No direct invalidation; the named consumer owns any request-prefix changes. ## Known Limitations and Deferred Work +- **No shipped composition currently mounts the service** — the ACP bridge was its only selector before [ACP became automation-only](../../../.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md); the preset table is kept for the interactive front door that next exposes a runtime policy switch. - **Only two mechanism knobs are bundled** — presets select sandbox mode and approval policy; an agent/profile choice is not part of `PresetSpec` yet. - **`custom` is derived-only** — callers can switch away from an unmatched knob combination but cannot target or persist a named custom preset through this service. - **The preset table is process-level** — configuration is fixed for the plugin lifetime; changing available presets requires reloading the plugin. From 90057b25f2b50fc3a4d8c242580f42695d14c38c Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:19:10 +0800 Subject: [PATCH 3/4] docs(acp): state both arms of the spec's version-negotiation rule --- packages/acp/acp/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/acp/acp/src/index.ts b/packages/acp/acp/src/index.ts index c4973a9e1d..b0d9e2792e 100644 --- a/packages/acp/acp/src/index.ts +++ b/packages/acp/acp/src/index.ts @@ -193,8 +193,8 @@ export function apply(ctx: Context, config: AcpConfig): void { conn = connection return { initialize(_params: InitializeRequest): Promise { - // Single-version agent: per spec, answer the latest version this - // server supports regardless of the client's requested version. + // Single-version agent: the spec's "same version if supported, else + // the latest supported" both resolve to this server's one version. return Promise.resolve({ protocolVersion: PROTOCOL_VERSION, agentInfo: { name: 'deepseek-harness-acp', version: '0.0.1' }, From 88f393332752fdd93f5b7f0b0e70a2113614280d Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:36:06 +0800 Subject: [PATCH 4/4] fix(docs): address Codex round-1 findings on the review-fix stack - gen-doc-graphs still summarized the ACP demo as 'text-only'; say baseline-prompt and regenerate composition.md. - The llm-catalog note claimed unit coverage for per-agent target isolation that no test pins directly; state the actual basis (agent-scoped listener installation) in both languages and re-record. --- .../2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml | 4 ++-- .../2026-07-15-llm-model-catalog-and-acp-selection.md | 2 +- .../2026-07-15-llm-model-catalog-and-acp-selection.zh.md | 2 +- examples/acp-agent/composition.md | 2 +- scripts/gen-doc-graphs.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml index 2a9e4a5d95..52ec59ccbf 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-15-llm-model-catalog-and-acp-selection.md: 689eab30eed8b22295f8757f83d458e41df7c2f3 -2026-07-15-llm-model-catalog-and-acp-selection.zh.md: e5766dec70f30b8fc9b5c5ddca59f594c7ad598b +2026-07-15-llm-model-catalog-and-acp-selection.md: adbd8671f0ea5cd2e0c049c32616453882329396 +2026-07-15-llm-model-catalog-and-acp-selection.zh.md: dfcb581e43149281cd18b28f1411ea98944983ad diff --git a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md index 689eab30ee..adbd8671f0 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md +++ b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.md @@ -59,4 +59,4 @@ The request header remains the durable source of truth. When a selected target i ## Testing -Unit coverage validates catalog detachment and malformed metadata, pi-ai and DeepSeek catalog projection, provider/model request routing, prompt-variable alignment, and per-agent target isolation. ACP transport tests validate fixed provider/model forwarding independently of catalog discovery; the TUI suite covers selector interaction and header-based restoration. +Unit coverage validates catalog detachment and malformed metadata, pi-ai and DeepSeek catalog projection, provider/model request routing, and prompt-variable alignment; per-agent isolation follows from installing the listeners on the agent-scoped context. ACP transport tests validate fixed provider/model forwarding independently of catalog discovery; the TUI suite covers selector interaction and header-based restoration. diff --git a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md index e5766dec70..dfcb581e43 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-15-llm-model-catalog-and-acp-selection.zh.md @@ -59,4 +59,4 @@ ACP 自动化传输层不是目录消费方。它通过部署配置为新创建 ## 测试 -单元测试覆盖目录分离与错误元数据、pi-ai 和 DeepSeek 目录投影、提供方/模型请求路由、prompt 变量对齐,以及按 agent 隔离的目标。ACP 传输测试独立验证固定提供方/模型的转发行为;TUI 套件覆盖选择器交互与基于请求头的恢复。 +单元测试覆盖目录分离与错误元数据、pi-ai 和 DeepSeek 目录投影、提供方/模型请求路由,以及 prompt 变量对齐;按 agent 的隔离来自监听器安装在 agent 作用域上下文这一事实。ACP 传输测试独立验证固定提供方/模型的转发行为;TUI 套件覆盖选择器交互与基于请求头的恢复。 diff --git a/examples/acp-agent/composition.md b/examples/acp-agent/composition.md index de7639e913..8d112f2910 100644 --- a/examples/acp-agent/composition.md +++ b/examples/acp-agent/composition.md @@ -3,7 +3,7 @@ # ACP Automation App Composition -The ACP demo exposes fresh text-only agent sessions to programmatic clients over JSON-RPC stdio, with no stdout logger, human UI, or pre-created agent. +The ACP demo exposes fresh baseline-prompt agent sessions to programmatic clients over JSON-RPC stdio, with no stdout logger, human UI, or pre-created agent. ```mermaid flowchart LR diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index c99023339e..3fc14ac84b 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -533,7 +533,7 @@ const APP_EXAMPLES = [ title: 'ACP Automation App Composition', label: 'examples/acp-agent', config: 'examples/acp-agent/cordis.yml', - summary: 'The ACP demo exposes fresh text-only agent sessions to programmatic clients over JSON-RPC stdio, with no stdout logger, human UI, or pre-created agent.', + summary: 'The ACP demo exposes fresh baseline-prompt agent sessions to programmatic clients over JSON-RPC stdio, with no stdout logger, human UI, or pre-created agent.', }, ]