Files
deepseek-harness/docs/core-data-structures/user-interaction.zh.md
T
Yichen Jiang 109d5b5e1c Merge remote-tracking branch 'origin/master' into worktree/fix-multi-select-custom-answer
# Conflicts:
#	apps/web/tests/snapshots/question-composer/answered.expected.md
#	apps/web/tests/snapshots/question-composer/session.jsonl
#	docs/core-data-structures/user-interaction.i18n.yaml
#	packages/client/ui-question/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/README.md
#	packages/host/apiproxy/README.zh.md
#	packages/ui/tui/README.i18n.yaml
#	packages/ui/user-interaction/README.i18n.yaml
2026-08-03 16:09:17 +08:00

5.7 KiB
Raw Blame History

用户交互

English | 中文

dsh-user-interaction 的用户交互 seam。它是工具或权限插件需要人类回答后 agent(智能体)才能继续时所使用的、提供方无关的词汇。UI surface 提供活跃的 UserInteractionProviderdsh-tui 使用键盘驱动的 overlay,host 运行时把请求转发给它连接的客户端。

源码:packages/ui/user-interaction/src/index.ts

问题选项

AskUserQuestionOption 是可选择项的形状。label 是面向用户的选项文字,同时也是面向模型的选中值;description 是可选的 UI 帮助文本。

/** One selectable answer offered to the user. */
interface AskUserQuestionOption {
  /** User-facing label. */
  label: string
  /** Optional extra context rendered by capable UIs. */
  description?: string
}

呈现意图

AskUserQuestionIntent 是一项可选声明:某个问题本身就是一次已知形状的决定。它按 kind 打标签,因此意图可以扩充;不认识某个标签的 UI 渲染通用选项列表。意图只塑造呈现 —— 遵循它的 UI 回答的仍是通用 UI 会发送的那些 option label,因此调用方两种情况下读到的都是同一种回答形态。approve 指名肯定选项,而不依赖选项顺序。有两项断言是任何类型都承载不了的,ask() 会拒绝它们:approve 未命中该问题自身的任一选项,以及意图落在没有 detail 的问题上。

/**
 * A caller-declared presentation intent: the question IS a decision of this
 * shape, so a UI that recognises the tag may present it as such instead of as a
 * generic option list. Tagged so further intents can be added; a UI that does
 * not know a tag renders the generic flow, and the answer encoding is identical
 * either way — an intent shapes presentation only, never the protocol.
 */
type AskUserQuestionIntent = {
  /** A plan submitted for review: `detail` is the plan markdown `ask()` requires, and the decision approves or declines it. */
  kind: 'plan-review'
  /**
   * The option label that approves the plan; every other option declines it.
   * Named rather than positional so no UI infers the verdict from option order.
   * An `approve` naming no option of its own question is rejected at `ask()`.
   */
  approve: string
}

问题条目

AskUserQuestionItem 是请求中的一个问题。调用方提供稳定的 id,它会随答案原样返回,使批量问题仍可路由。可选的 detail 携带辅助文本;提供方会将其随问题渲染,但不会放入可选 option label。

/** 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
  /** Optional presentation intent for capable UIs; absent asks for the generic option list. */
  intent?: AskUserQuestionIntent
}

提问请求

AskUserQuestionRequest 是跨包(package)的请求。questions 是数组,这样 UI 可以在一个流程中呈现相关提示,同时保持每个回答有稳定的 id。

/** 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 为空。对于多选题,custom 可以补充 selected 中的标签。UI 也可以使用 selected 为空且不含 custom 的回答项,在其余问题均已完成的批次中保留被跳过的问题。

/** Answer to one question. */
interface AskUserQuestionAnswerItem {
  /** The answered question id. */
  id: string
  /** Selected option labels. May accompany custom text for a multi-select question. */
  selected: string[]
  /** Optional free-text "Other" answer. */
  custom?: string
}
/** The human's answer. */
interface AskUserQuestionAnswer {
  /** Structured answers keyed by question id. */
  answers: AskUserQuestionAnswerItem[]
}

提供方

同一上下文中只能有一个活跃的提供方。提供方注册绑定到 effect,因此 HMR(热模块替换)或 dispose(资源释放)会移除当前活跃的 UI。

/** UI-side provider for user questions. */
interface UserInteractionProvider {
  ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>
}

错误

UserInteractionError 继承 HarnessError,因此 ctx.tools.execute() 会保留 { name, code },用于面向模型的工具失败,如 EMPTY_QUESTIONSNO_PROVIDERASK_ABORTED 或 UI 侧取消。

/** 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'
  }
}