# Conflicts: # .agents/notes/implemented/architecture/2026-06-14-session-persistence.md # .agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md # .agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md # .agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md # .agents/notes/implemented/feature/2026-06-14-acp-multi-session.md # .agents/notes/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md # .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md # .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md # .agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md # docs/architecture.i18n.yaml # docs/cookbook/extension-cookbook.i18n.yaml # docs/cookbook/extension-cookbook.md # docs/cookbook/extension-cookbook.zh.md # docs/core-data-structures/approval.md # docs/core-data-structures/user-interaction.md # docs/event-producer-consumer.md # docs/persistence-catalog.md # docs/testing.md # docs/tool-catalog.md # examples/acp-agent/tests/fixtures/live-mode-switching-2026-07-07.session.jsonl # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/permission-switching/session.jsonl # packages/goal/tool-goal/README.md # packages/ui/acp/README.md # packages/ui/acp/acp-feature-support.md # packages/ui/acp/src/index.ts # packages/ui/acp/tests/bridge.spec.ts # packages/ui/acp/tests/dispose.spec.ts # packages/ui/acp/tests/edges.spec.ts # packages/ui/acp/tests/turns.spec.ts
109 lines
4.0 KiB
Markdown
109 lines
4.0 KiB
Markdown
# 用户交互
|
||
|
||
[English](user-interaction.md) | 中文
|
||
|
||
[dsh-user-interaction](../../packages/ui/user-interaction) 的用户交互 seam。它是工具或权限插件需要人类回答后 agent(智能体)才能继续时所使用的、提供方无关的词汇。UI surface 提供活跃的 `UserInteractionProvider`;`dsh-tui` 使用键盘驱动的 overlay,host 运行时把请求转发给它连接的客户端。
|
||
|
||
源码:[`packages/ui/user-interaction/src/index.ts`](../../packages/ui/user-interaction/src/index.ts)
|
||
|
||
## 问题选项
|
||
|
||
`AskUserQuestionOption` 是可选择项的形状。`label` 是面向用户的选项文字,同时也是面向模型的选中值;`description` 是可选的 UI 帮助文本。
|
||
|
||
```ts type-equiv
|
||
/** One selectable answer offered to the user. */
|
||
interface AskUserQuestionOption {
|
||
/** User-facing label. */
|
||
label: string
|
||
/** Optional extra context rendered by capable UIs. */
|
||
description?: string
|
||
}
|
||
```
|
||
|
||
## 问题条目
|
||
|
||
`AskUserQuestionItem` 是请求中的一个问题。调用方提供稳定的 `id`,它会随答案原样返回,使批量问题仍可路由。可选的 `detail` 携带辅助文本;提供方会将其随问题渲染,但不会放入可选 option label。
|
||
|
||
```ts type-equiv
|
||
/** One question in a user-interaction request. */
|
||
interface AskUserQuestionItem {
|
||
/** Stable caller-provided question id, echoed in the answer. */
|
||
id: string
|
||
/** The question to display. */
|
||
question: string
|
||
/** Optional supporting detail rendered with the question but kept out of option labels. */
|
||
detail?: string
|
||
/** Optional short heading/group label. */
|
||
header?: string
|
||
/** Optional choices the UI can render as a menu. */
|
||
options?: AskUserQuestionOption[]
|
||
/** Whether more than one option may be selected. Defaults to single-select. */
|
||
multiSelect?: boolean
|
||
}
|
||
```
|
||
|
||
## 提问请求
|
||
|
||
`AskUserQuestionRequest` 是跨包(package)的请求。`questions` 是数组,这样 UI 可以在一个流程中呈现相关提示,同时保持每个回答有稳定的 id。
|
||
|
||
```ts type-equiv
|
||
/** Request for a human answer. */
|
||
interface AskUserQuestionRequest {
|
||
/** Questions to display. */
|
||
questions: AskUserQuestionItem[]
|
||
/** Calling agent, when the request came from an agent tool call. */
|
||
agent?: Agent
|
||
/** Abort signal for the owning tool/step. */
|
||
signal?: AbortSignal
|
||
}
|
||
```
|
||
|
||
## 回答
|
||
|
||
提供方为每个问题 id 返回一个回答项。`selected` 包含选中的选项标签,`custom` 在用户输入自由文本时携带「其他」回答。当 `custom` 存在时,`selected` 为空;自定义文本是对选中项的覆盖,而非补充。UI 也可以使用 `selected` 为空且不含 `custom` 的回答项,在其余问题均已完成的批次中保留被跳过的问题。
|
||
|
||
```ts type-equiv
|
||
/** Answer to one question. */
|
||
interface AskUserQuestionAnswerItem {
|
||
/** The answered question id. */
|
||
id: string
|
||
/** Selected option labels. Empty for custom or unanswered choices. */
|
||
selected: string[]
|
||
/** Optional free-text "Other" answer. */
|
||
custom?: string
|
||
}
|
||
```
|
||
|
||
```ts type-equiv
|
||
/** The human's answer. */
|
||
interface AskUserQuestionAnswer {
|
||
/** Structured answers keyed by question id. */
|
||
answers: AskUserQuestionAnswerItem[]
|
||
}
|
||
```
|
||
|
||
## 提供方
|
||
|
||
同一上下文中只能有一个活跃的提供方。提供方注册绑定到 effect,因此 HMR(热模块替换)或 dispose(资源释放)会移除当前活跃的 UI。
|
||
|
||
```ts type-equiv
|
||
/** UI-side provider for user questions. */
|
||
interface UserInteractionProvider {
|
||
ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>
|
||
}
|
||
```
|
||
|
||
## 错误
|
||
|
||
`UserInteractionError` 继承 `HarnessError`,因此 `ctx.tools.execute()` 会保留 `{ name, code }`,用于面向模型的工具失败,如 `EMPTY_QUESTIONS`、`NO_PROVIDER`、`ASK_ABORTED` 或 UI 侧取消。
|
||
|
||
```ts type-equiv
|
||
/** Stable error taxonomy for user-interaction failures. */
|
||
class UserInteractionError extends HarnessError {
|
||
constructor(message: string, code: string, options?: ErrorOptions) {
|
||
super(message, code, options)
|
||
this.name = 'UserInteractionError'
|
||
}
|
||
}
|
||
```
|