用户交互
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 会发送的那些选项标签,因此调用方两种情况下读到的回答字段相同。approve 指名肯定选项,而不依赖选项顺序。ask() 会拒绝两种无法由类型系统表达的情况:approve 未指向该问题自身的任何选项,以及为没有 detail 的问题指定意图。
/** * A caller-declared presentation intent: the question IS this kind of * decision, 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 changes 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 携带辅助文本;提供方会将其随问题渲染,但不会放入可选选项标签。
/** One question in a user-questions 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 是跨包请求。questions 是数组,这样 UI 可以在一个流程中呈现相关提示,同时保持每个回答有稳定的 id。如提供 agent,它必须与存活调用方是同一实例;只有当当前注册表将该实例识别为运行时根时,交互 seam 才会接纳该 agent。
/** Request for a human answer. */interface AskUserQuestionRequest { /** Questions to display. */ questions: AskUserQuestionItem[] /** Exact live 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 UserQuestionProvider { ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>}UserQuestionError 继承 HarnessError,因此 ctx.tools.execute() 会保留 { name, code },用于面向模型的工具失败,如 EMPTY_QUESTIONS、NO_PROVIDER、ASK_ABORTED 或 UI 侧取消。
/** Stable error taxonomy for user-questions failures. */class UserQuestionError extends HarnessError { constructor(message: string, code: string, options?: ErrorOptions) { super(message, code, options) this.name = 'UserQuestionError' }}Cordis API
Section titled “Cordis API”Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — this section is byte-identical in both language sides of the page. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.
ctx.userQuestions — UserQuestionService
Section titled “ctx.userQuestions — UserQuestionService”ctx.userQuestions: one active UI provider plus an ask() API.
/** * Register the UI provider. Only one provider may be active in a context. * * @param provider UI-side implementation that collects answers. * @returns Disposer that unregisters this provider. */registerProvider(provider: UserQuestionProvider): () => void
/** * Ask the active UI provider and wait for the user's answer. * * When a caller supplies an agent, human interaction is valid only for the * exact live runtime root. Runtime ownership, not durable session lineage, * decides this boundary: an owned child has no human answerer and would * block forever, while a lineage-bearing session resumed as a new runtime * root may ask normally. * * @param request Questions, owner agent, and abort signal. * @returns The answer chosen or typed by the human. * @throws {UserQuestionError} code `CALLER_NOT_LIVE` when a supplied * agent is not the registry's exact live instance, or `DELEGATED_CALLER` * when that live agent is owned by another agent. */async ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>