Files
deepseek-harness/docs/core-data-structures/user-interaction.md
Yichen Jiang ff8d38a9a8 Merge remote-tracking branch 'origin/master' into codex/ask-user-question
# Conflicts:
#	docs/architecture.md
#	docs/cordis-catalog/events-and-services.md
#	docs/core-data-structures/core.md
#	docs/module-graph.md
#	docs/tool-catalog/tools.md
#	packages/README.md
#	packages/core/README.md
#	packages/core/tools/tests/gen-tool-catalog.spec.ts
#	packages/support/README.md
#	packages/support/ui-stdio/README.md
#	packages/ui/acp-agent/tests/built-bin.e2e.ts
#	packages/ui/acp/README.md
#	packages/ui/stdio-agent/README.md
#	packages/ui/stdio-agent/package.json
#	packages/ui/stdio-agent/src/index.ts
#	packages/ui/stdio-agent/src/stdio-chat.ts
#	packages/ui/stdio-agent/tests/built-bin.e2e.ts
#	packages/ui/stdio-agent/tests/readline.spec.ts
#	packages/ui/stdio-agent/tests/stdio-chat.spec.ts
#	packages/web/web/package.json
#	packages/web/web/tsconfig.json
#	pnpm-lock.yaml
#	scripts/gen-tool-catalog.ts
2026-07-05 17:05:33 +08:00

2.9 KiB

User Interaction

The user-interaction seam of dsh-user-interaction. It is the provider-neutral vocabulary a tool or permission plugin uses when it needs the human to answer before the agent can continue. UI surfaces provide the active UserInteractionProvider: dsh-stdio-agent renders questions in readline, and dsh-acp maps them to ACP form elicitations.

Source: packages/core/user-interaction/src/index.ts

Question options

AskUserQuestionOption is the selectable-choice shape. label is user-facing, while value is the model-facing answer returned when the option is selected; when omitted, providers use the label.

interface AskUserQuestionOption {
  /** User-facing label. */
  label: string
  /** Value returned to the model when selected. Defaults to `label`. */
  value?: string
  /** Optional extra context rendered by capable UIs. */
  description?: string
  /** Marks the recommended/default option. */
  recommended?: boolean
}

Ask request

AskUserQuestionRequest is the cross-package request. options being absent means free-form input; an optionless request remains free-form even when a caller sets allowCustom: false, because there is no selectable option to constrain the answer to.

interface AskUserQuestionRequest {
  /** The question to display. */
  question: string
  /** Optional short heading/group label. */
  header?: string
  /** Optional choices the UI can render as a menu. */
  options?: AskUserQuestionOption[]
  /** Whether free-form answers are accepted. Defaults to `true`. */
  allowCustom?: boolean
  /** Calling agent, when the request came from an agent tool call. */
  agent?: Agent
  /** Abort signal for the owning tool/step. */
  signal?: AbortSignal
}

Answer

Providers return the model-facing answer text and optionally echo the chosen option as metadata. Consumers should use answer; the option is for UI/session metadata and diagnostics.

interface AskUserQuestionAnswer {
  /** Model-facing answer text. */
  answer: string
  /** The selected option, when the answer came from `options`. */
  option?: AskUserQuestionOption
}

Provider

Only one provider may be active in a context. Provider registration is effect-bound so HMR/disposal removes the active UI.

interface UserInteractionProvider {
  ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>
}

Errors

UserInteractionError extends HarnessError, so ctx.tools.execute() preserves { name, code } for model-facing tool failures such as NO_PROVIDER, ASK_ABORTED, or ACP-side cancellation.

class UserInteractionError extends HarnessError {
  constructor(message: string, code: string, options?: ErrorOptions) {
    super(message, code, options)
    this.name = 'UserInteractionError'
  }
}