Files
deepseek-harness/packages/ui/user-interaction
creatixchu 89d30b0ef7 feat(user-interaction): declare a plan-review presentation intent on questions
A question may now carry `intent`, a tagged declaration that it IS a decision
of a known shape, so a UI that recognises the tag can present it as such
instead of as a generic option list. The one member is
`{ kind: 'plan-review', approve }`, which plan-mode sets on the exit_plan_mode
review.

An intent shapes presentation only: a UI honouring it answers with the same
option labels a generic UI would send, so the tool reads one answer shape
either way, and a UI that does not know the tag renders the generic flow.
`approve` names the affirmative option rather than relying on option order;
since no type can tie that label to the question's own option list, `ask()`
rejects a mismatch as BAD_INTENT, and the wire schema rejects an unknown tag
outright rather than silently rendering generic.

plan-mode also stops reporting a dismissed review as "the user cancelled
ask_user_question" — a tool it never called. A dismissal now tells the model
the user took the turn back to speak, and to stay in plan mode and wait; every
other ask failure keeps its own message.
2026-07-30 19:09:19 +08:00
..

@deepseek-ai/dsh-user-interaction

English | 中文

Abstract user-interaction seam. It owns ctx.userInteraction, the service a model-facing tool or permission plugin uses when it needs to pause work and ask the human for a decision.

Service: UserInteractionService (ctx key: userInteraction)

Public API

  • ctx.userInteraction.registerProvider(provider): () => void Register the UI-side provider. Only one provider may be active in a context; disposal unregisters it.
  • ctx.userInteraction.ask(request): Promise<AskUserQuestionAnswer> Ask the active provider and wait for the answer.

Key Types

  • AskUserQuestionRequest{ questions: [{ id, question, detail?, header?, options?, multiSelect?, intent? }], agent?, signal? }; detail supplies supporting text that providers render with the question without turning it into an option label.
  • AskUserQuestionOption{ label, description? }.
  • AskUserQuestionIntent{ kind: 'plan-review', approve }; the tagged presentation intent below.
  • AskUserQuestionAnswer{ answers: [{ id, selected, custom? }] }.
  • UserInteractionProvider — UI implementation with ask(request).
  • UserInteractionErrorHarnessError subclass with codes such as EMPTY_QUESTIONS, BAD_INTENT, NO_PROVIDER, DUPLICATE_PROVIDER, and ASK_ABORTED.

When an answer includes custom, selected is empty; custom text is an override rather than a supplement to selected choices. A UI may preserve a skipped item as { id, selected: [] }, keeping the existing answer shape while retaining other answers in the batch.

Presentation intent

intent declares that a question IS a decision of a known shape, so a UI that recognises the tag may present it as such — plan-review says detail is a plan under review, and dsh-plan-mode sets it on the exit_plan_mode question. An intent shapes presentation only: a UI honouring it answers with the same option labels a generic UI would send, and a UI that does not know the tag renders the generic option list, so callers read one answer shape either way. approve names the label that approves rather than relying on option order; ask() rejects an intent whose approve names none of that question's own options with BAD_INTENT, since no type can tie the two together.

Role

This is the interface package. Model-facing consumers such as @deepseek-ai/dsh-tool-ask-user depend on this seam; dsh-tui and the host runtime provide interactive implementations. The loop stays unchanged: a tool call awaits a promise, and the tool result resumes the normal agent loop.

Model Experience

Indirectly, through dsh-tool-ask-user, which retains a successful provider answer as compact JSON or one of these failures: Error: ask_user_question was aborted before the user answered, Error: ask_user_question requires at least one question, Error: no user-interaction provider is registered, or Error: <message>. Waiting for the human adds no tokens.

KV Cache effect

No direct invalidation; the named consumer owns any request-prefix changes.

Known Limitations and Deferred Work

  • One provider per context — there is no routing or fan-out to multiple UIs; a second registration throws DUPLICATE_PROVIDER, and with none registered ask() throws NO_PROVIDER rather than degrading.
  • The vocabulary is the question-form shape only — selectable options plus optional custom text; richer interaction shapes (file pickers, diff-preview confirmations) have no seam vocabulary yet.