fix(mode): stabilize plan-mode model experience
This commit is contained in:
@@ -145,11 +145,14 @@ function elicitationForQuestion(
|
||||
options: AskUserQuestionOption[],
|
||||
): CreateElicitationRequest {
|
||||
const title = question.header ?? 'Question'
|
||||
const message = question.detail === undefined
|
||||
? question.question
|
||||
: `${question.question}\n\n${question.detail}`
|
||||
if (options.length === 0) {
|
||||
return {
|
||||
sessionId,
|
||||
mode: 'form',
|
||||
message: question.question,
|
||||
message,
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
title,
|
||||
@@ -183,7 +186,7 @@ function elicitationForQuestion(
|
||||
return {
|
||||
sessionId,
|
||||
mode: 'form',
|
||||
message: question.question,
|
||||
message,
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
title,
|
||||
@@ -504,7 +507,7 @@ export function apply(ctx: Context, config: AcpConfig): void {
|
||||
// --- Stream the harness event taxonomy to ACP session/update --------------
|
||||
|
||||
// --- Session modes (dsh-mode, opportunistic) ------------------------------
|
||||
// The mode PICKER is dsh-mode's ACP surface (the plan-mode RFC): advertised
|
||||
// The mode PICKER is dsh-mode's ACP surface (the plan-mode Agent Note): advertised
|
||||
// as `modes` on session/new + session/load, switched via session/set_mode —
|
||||
// optimistic `current_mode_update` (the pending mode IS the user's
|
||||
// selection; the logged `mode/set` follows at the turn boundary) — and
|
||||
|
||||
@@ -143,15 +143,18 @@ describe('acp bridge', () => {
|
||||
questions: [{
|
||||
id: 'language',
|
||||
question: 'Which language?',
|
||||
detail: 'Choose the implementation language for this project.',
|
||||
options: [{ label: 'TypeScript' }],
|
||||
}],
|
||||
})
|
||||
|
||||
expect(result).toEqual({ answers: [{ id: 'language', selected: [], custom: 'Use Zig' }] })
|
||||
expect(harness.elicitationRequests[0]).toMatchObject({
|
||||
message: 'Which language?\n\nChoose the implementation language for this project.',
|
||||
requestedSchema: {
|
||||
properties: {
|
||||
choice: {
|
||||
title: 'Which language?',
|
||||
description: 'Choose one option, or fill a custom answer below.',
|
||||
oneOf: [{ const: 'TypeScript', title: 'TypeScript' }],
|
||||
},
|
||||
|
||||
@@ -227,7 +227,7 @@ export async function makeBridgeHarness(options: {
|
||||
await ctx.plugin(ToolTodo)
|
||||
}
|
||||
if (options.withModes) {
|
||||
await ctx.plugin(ModesService)
|
||||
await ctx.plugin(ModesService, { modes: { plan: { section: 'Test plan mode instructions.' } } })
|
||||
}
|
||||
if (options.withFs) {
|
||||
await ctx.plugin(LocalFileSystem, { cwd: options.fsCwd ?? options.storageDir })
|
||||
|
||||
@@ -751,6 +751,10 @@ class QuestionDialog implements Component, Focusable {
|
||||
lines.push(`${this.palette.accent('│')} ${clipped}${' '.repeat(Math.max(0, innerWidth - visibleWidth(clipped)))} ${this.palette.accent('│')}`)
|
||||
}
|
||||
for (const line of wrapTextWithAnsi(this.palette.bold(displayText(this.question.question)), innerWidth)) push(line)
|
||||
if (this.question.detail !== undefined) {
|
||||
push('')
|
||||
for (const line of wrapTextWithAnsi(displayText(this.question.detail), innerWidth)) push(line)
|
||||
}
|
||||
push('')
|
||||
if (this.mode === 'custom') {
|
||||
for (const line of this.input.render(innerWidth)) push(line)
|
||||
|
||||
@@ -731,12 +731,13 @@ describe('TUI user-interaction dialogs', () => {
|
||||
|
||||
const single = result.ctx.userInteraction.ask({
|
||||
questions: [{
|
||||
id: 'mode', header: 'Mode', question: 'Choose a mode',
|
||||
id: 'mode', header: 'Mode', question: 'Choose a mode', detail: 'This choice controls the next turn.',
|
||||
options: [{ label: 'Safe', description: 'Use checks' }, { label: 'Fast' }],
|
||||
}],
|
||||
})
|
||||
await tick()
|
||||
expect(result.terminal.output).toContain('Choose a mode')
|
||||
expect(result.terminal.output).toContain('This choice controls the next turn.')
|
||||
expect(result.terminal.output).toContain('1/2')
|
||||
result.terminal.send('\x1b[B')
|
||||
result.terminal.send('\r')
|
||||
|
||||
@@ -11,7 +11,7 @@ Abstract user-interaction seam. It owns `ctx.userInteraction`, the service a mod
|
||||
|
||||
### Key Types
|
||||
|
||||
- `AskUserQuestionRequest` — `{ questions: [{ id, question, header?, options?, multiSelect? }], agent?, signal? }`.
|
||||
- `AskUserQuestionRequest` — `{ questions: [{ id, question, detail?, header?, options?, multiSelect? }], agent?, signal? }`; `detail` supplies supporting text that providers render with the question without turning it into an option label.
|
||||
- `AskUserQuestionOption` — `{ label, description? }`.
|
||||
- `AskUserQuestionAnswer` — `{ answers: [{ id, selected, custom? }] }`.
|
||||
- `UserInteractionProvider` — UI implementation with `ask(request)`.
|
||||
|
||||
@@ -31,6 +31,8 @@ export interface AskUserQuestionItem {
|
||||
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. */
|
||||
|
||||
Reference in New Issue
Block a user