diff --git a/server/modules/providers/services/tmux-interactive-prompt.service.ts b/server/modules/providers/services/tmux-interactive-prompt.service.ts index af4a58c..1c126cc 100644 --- a/server/modules/providers/services/tmux-interactive-prompt.service.ts +++ b/server/modules/providers/services/tmux-interactive-prompt.service.ts @@ -443,11 +443,24 @@ function parseCodexAsyncQuestion(screen: string): ParsedPrompt | null { }); } +/** + * The current question frame's own options: the run of numbered rows that ends + * at the last row above the hint and counts back down to `1.`. A numbered line + * above that `1.` (a list in the assistant's reply, for example) belongs to + * something else and never counts as an option. + */ +function currentClaudeRun(rows: NumberedRow[]): NumberedRow[] { + let start = rows.length - 1; + if (start < 0) return []; + while (start > 0 && rows[start].number > 1 && rows[start - 1].number === rows[start].number - 1) start -= 1; + return rows[start].number === 1 ? rows.slice(start) : []; +} + function parseClaudeQuestion(screen: string): ParsedPrompt | null { const lines = screen.replace(ANSI_RE, '').split(/\r?\n/); const hintIndex = findLastIndex(lines, (line) => CLAUDE_ASK_HINT_RE.test(line)); if (hintIndex < 0) return null; - const rows = parseNumberedRows(lines, Math.max(0, hintIndex - 64), hintIndex); + const rows = currentClaudeRun(parseNumberedRows(lines, Math.max(0, hintIndex - 64), hintIndex)); if (!sequentialRows(rows) || rows.filter((row) => row.selected).length !== 1) return null; const chatIndex = rows.findIndex((row) => row.label === 'Chat about this'); const customIndex = rows.findIndex((row) => /^Type something\.?$/i.test(row.label)); diff --git a/server/modules/providers/tests/claude-question-numbered-list.test.ts b/server/modules/providers/tests/claude-question-numbered-list.test.ts new file mode 100644 index 0000000..c26219b --- /dev/null +++ b/server/modules/providers/tests/claude-question-numbered-list.test.ts @@ -0,0 +1,148 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +import type { TmuxRunner } from '@/modules/providers/services/builtin-relay.service.js'; +import { parseClaudeAskSelectionScreen } from '@/modules/providers/services/tmux-ask-selection.service.js'; +import { createVerifiedTmuxActionTarget } from '@/modules/providers/services/tmux-fresh-verifier.service.js'; +import { + getTmuxInteractivePrompt, + parseTmuxInteractivePrompt, +} from '@/modules/providers/services/tmux-interactive-prompt.service.js'; + +// A numbered list in the assistant's reply, above a short Claude question, must +// not hide the question. Fixtures are real Claude Code TUI captures in a 78x73 +// tmux pane (`capture-pane -p -e -N -S -80`, and the visible screen alone): +// - `s1`: fullscreen renderer, a 40-item list right above the question frame; +// - `s2`: default renderer, the same shape; +// - `list40`: default renderer, re-captured with colour; +// - `list3`: default renderer, an earlier 40-item list in scrollback and a +// 3-item list numbered 1.-3. directly above the frame. +const FIXTURE_DIR = new URL('./fixtures/claude-question-numbered-list/', import.meta.url); +const read = (name: string) => readFileSync(new URL(name, FIXTURE_DIR), 'utf8'); +type Shot = { joined: string; screen: string }; +const shot = (name: string, ext: 'txt' | 'ansi'): Shot => ({ + joined: read(`${name}.joined.${ext}`), + screen: read(`${name}.screen.${ext}`), +}); +const ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g; + +let generation = 0; +const targetFor = () => createVerifiedTmuxActionTarget( + { socketPath: '/tmp/chatmux-numbered-list-test.sock', sessionId: '$7', windowId: '@8', paneId: '%9' }, + { pid: 42, startedAtMs: (generation += 1) }, + 'claude', 'test', 'claude-session', +); + +/** A pane whose `capture-pane` returns the given text, trimmed as runTmux trims. */ +function paneOf(text: string): TmuxRunner { + return async (args) => { + if (args.includes('capture-pane')) return { code: 0, output: text.trim() }; + if (args.includes('display-message')) return { code: 0, output: '$7\t@8\t%9\n' }; + return { code: 0, output: '' }; + }; +} + +/** Numbered lines above the frame's header chip: the list that used to hide the card. */ +function numberedAboveFrame(text: string): number { + const lines = text.replace(ANSI_RE, '').split('\n'); + const top = lines.findIndex((line) => /^\s*☐\s+\S/.test(line)); + assert.ok(top > 0, 'fixture assumption broken: no header chip'); + return lines.slice(0, top).filter((line) => /^\s*(?:●\s*)?\d+\.\s/.test(line)).length; +} + +const PACK = 'Pack the green fig and golden-yellow peaches together?'; +const SHORT_ASKS = [ + ['s1-78x73', 'txt', PACK, ['Ripe avocados', 'Big red plum']], + ['s2-78x73', 'txt', PACK, ['Ripe avocados', 'Big red plum']], + ['list40-78x73', 'ansi', PACK, ['Ripe avocados', 'Big red plum']], + ['list3-78x73', 'ansi', 'Which step goes first?', ['Apple', 'Banana', 'Cherry']], +] as const; + +for (const [name, ext, question, labels] of SHORT_ASKS) { + test(`${name}: a short Claude question under a numbered list still gets its card`, async () => { + const { joined, screen } = shot(name, ext); + assert.ok(numberedAboveFrame(joined) >= 2, 'fixture assumption broken: no list above the frame'); + for (const [capture, prompt] of [ + ['joined', await getTmuxInteractivePrompt(targetFor(), paneOf(joined))], + ['screen', parseTmuxInteractivePrompt('claude', screen)], + ] as const) { + assert.ok(prompt, `${capture}: the card reads the question`); + assert.equal(prompt.kind, 'question'); + assert.equal(prompt.question, question); + assert.deepEqual(prompt.options.map((option) => option.label), [...labels]); + assert.equal(prompt.multiSelect, false); + assert.equal(prompt.customOptionNumber, labels.length + 1); + } + }); +} + +/** s2's question frame, under a list numbered 1.-3. whose items repeat the frame's own rows. */ +function listDirectlyAbove(keepDivider: boolean): string { + const lines = read('s2-78x73.joined.txt').split('\n'); + const chip = lines.findIndex((line) => line.includes('☐ Pick one')); + assert.ok(chip > 0, 'fixture assumption broken: no header chip'); + const frameLines = lines.slice(keepDivider ? chip - 1 : chip); + return [ + '● Here is the plan:', + '', + ' 1. Ripe avocados', + ' 2. Big red plum', + ' 3. Type something.', + ...frameLines, + ].join('\n'); +} + +test('a list numbered 1.-3. directly above the frame yields the real options only', () => { + for (const keepDivider of [true, false]) { + const text = listDirectlyAbove(keepDivider); + assert.equal((text.match(/^[ ❯] 1\. /gm) ?? []).length, 2, 'fixture assumption broken: two runs start at 1.'); + const prompt = parseTmuxInteractivePrompt('claude', text); + assert.ok(prompt, `divider ${keepDivider}: the card reads the question`); + assert.equal(prompt.question, PACK); + assert.deepEqual(prompt.options.map((option) => option.label), ['Ripe avocados', 'Big red plum']); + assert.equal(prompt.customOptionNumber, 3); + } +}); + +test('control: a short Claude question with no list above parses as before', () => { + const text = listDirectlyAbove(true).split('\n').slice(5).join('\n'); + assert.equal(numberedAboveFrame(text), 0, 'fixture assumption broken: a list is still above the frame'); + const prompt = parseTmuxInteractivePrompt('claude', text); + assert.ok(prompt); + assert.equal(prompt.question, PACK); + assert.deepEqual(prompt.options.map((option) => option.label), ['Ripe avocados', 'Big red plum']); + assert.deepEqual(prompt.options.map((option) => option.description), ['Soft green, very ripe', 'Deep red skin']); + assert.equal(prompt.customOptionNumber, 3); +}); + +// The transcript-ask path matches screen rows against the transcript's own +// labels, in a window that must number 1..N and end in "Type something." and +// "Chat about this". A list above the frame cannot complete such a window, so +// it already finds the real rows; these cases pin that. +test('transcript asks: a numbered list above the frame never hides or replaces the real rows', () => { + const question = { question: PACK, options: [{ label: 'Ripe avocados' }, { label: 'Big red plum' }] }; + const fruit = { question: 'Which step goes first?', options: [{ label: 'Apple' }, { label: 'Banana' }, { label: 'Cherry' }] }; + const screens = [ + [shot('s1-78x73', 'txt').joined, question], + [shot('s2-78x73', 'txt').joined, question], + [shot('list40-78x73', 'ansi').joined, question], + [shot('list3-78x73', 'ansi').joined, fruit], + [listDirectlyAbove(true), question], + [listDirectlyAbove(false), question], + ] as const; + for (const [screen, ask] of screens) { + const last = ask.options.length - 1; + // Every capture has row 1 focused, so moving to the last option is `last` steps down. + assert.deepEqual(parseClaudeAskSelectionScreen(screen, ask, last), { + action: 'option', + delta: last, + label: ask.options[last].label, + }); + assert.deepEqual(parseClaudeAskSelectionScreen(screen, ask, ask.options.length), { + action: 'other', + delta: ask.options.length, + label: 'Direct input', + }); + } +}); diff --git a/server/modules/providers/tests/fixtures/claude-question-numbered-list/list3-78x73.joined.ansi b/server/modules/providers/tests/fixtures/claude-question-numbered-list/list3-78x73.joined.ansi new file mode 100644 index 0000000..3b80bd4 --- /dev/null +++ b/server/modules/providers/tests/fixtures/claude-question-numbered-list/list3-78x73.joined.ansi @@ -0,0 +1,95 @@ +user@devbox:/tmp/demo-list-proj$ /tmp/demo-list-work/launch.sh + ▐▛███▛█ Claude Code v2.1.291 + ▐▛███▛█ Claude Code v2.1.291 +▝▜██████▀ Haiku 4.5 · Claude API + ▝▝ ▝▝ /tmp/demo-list-proj + + +❯ /tui default  +  ⎿ Already using the default renderer. + +❯ First write a numbered list of 40 short tmux tips, one line each, numbered  + 1. to 40. Then, without any other text, use the AskUserQuestion tool  + exactly once: header "Pick one", question "Pack the green fig and  + golden-yellow peaches together?", options "Ripe avocados" (description  + "Soft green, very ripe") and "Big red plum" (description "Deep red skin").  + Do nothing else. + +● 1. Start a new session: tmux new-session -s name + 2. List all sessions: tmux list-sessions + 3. Attach to a session: tmux attach -t name + 4. Detach from current session: Ctrl+B then D + 5. Kill a session: tmux kill-session -t name + 6. Rename a session: Ctrl+B then $ + 7. Create a new window: Ctrl+B then C + 8. List windows: Ctrl+B then W + 9. Next window: Ctrl+B then N + 10. Previous window: Ctrl+B then P + 11. Jump to window 0-9: Ctrl+B then number + 12. Kill current window: Ctrl+B then & + 13. Rename window: Ctrl+B then , + 14. Split pane vertically: Ctrl+B then % + 15. Split pane horizontally: Ctrl+B then " + 16. Navigate panes: Ctrl+B then arrow keys + 17. Close pane: Ctrl+B then X + 18. Maximize pane: Ctrl+B then Z + 19. Resize pane: Ctrl+B then Ctrl+Arrow + 20. Show pane numbers: Ctrl+B then Q + 21. Enter copy mode: Ctrl+B then [ + 22. Paste from buffer: Ctrl+B then ] + 23. List buffers: Ctrl+B then # + 24. Choose buffer: Ctrl+B then = + 25. Synchronize panes: Ctrl+B then : then setw synchronize-panes on + 26. Break pane to new window: Ctrl+B then ! + 27. Join pane to window: Ctrl+B then : then join-pane -t target + 28. Send command to pane: tmux send-keys -t session:pane "command" Enter + 29. List key bindings: Ctrl+B then ? + 30. Reload config: Ctrl+B then : then source-file ~/.tmux.conf + 31. Scroll in pane: Ctrl+B then [ then arrow keys + 32. Set custom prefix: set -g prefix C-a in config + 33. Bind custom key: bind-key -n C-x command in config + 34. New session with command: tmux new-session -d -s name "command" + 35. Split window vertically (alt): tmux split-window -h + 36. Split window horizontally (alt): tmux split-window -v + 37. Cycle layouts: Ctrl+B then Space + 38. Show status bar time: set -g status-right "%H:%M %d-%m-%Y" + 39. Enable mouse: set -g mouse on in config + 40. Last active window: Ctrl+B then L + +● User answered Claude's questions: +  ⎿  · Pack the green fig and golden-yellow peaches together? → Big red plum + +● Got it. You've selected "Big red plum" for the green fig. + + I'm ready to help you pack the green fig. What's the plan, or would you like + me to look at the fruit in the basket to see which of the plums need to be + eaten? + +✻ Brewed for 19s · done 7:14 AM + +❯ Write a numbered list of exactly 3 short steps, numbered 1. to 3., with no  + text after the list. Then immediately use the AskUserQuestion tool exactly  + once: header "Order", question "Which step goes first?", options "Apple"  + (description "First fruit"), "Banana" (description "Second fruit") and  + "Cherry" (description "Third fruit"). Do nothing else. + +● 1. Identify the plums in a box + 2. Cut the figs + 3. Serve the plates +────────────────────────────────────────────────────────────────────────────── + ☐ Order  + +Which step goes first? + +❯ 1. Apple + First fruit + 2. Banana + Second fruit + 3. Cherry + Third fruit + 4. Type something. +────────────────────────────────────────────────────────────────────────────── + 5. Chat about this + +Enter to select · ↑/↓ to navigate · Esc to cancel + diff --git a/server/modules/providers/tests/fixtures/claude-question-numbered-list/list3-78x73.screen.ansi b/server/modules/providers/tests/fixtures/claude-question-numbered-list/list3-78x73.screen.ansi new file mode 100644 index 0000000..78ad133 --- /dev/null +++ b/server/modules/providers/tests/fixtures/claude-question-numbered-list/list3-78x73.screen.ansi @@ -0,0 +1,73 @@ + 6. Rename a session: Ctrl+B then $ + 7. Create a new window: Ctrl+B then C + 8. List windows: Ctrl+B then W + 9. Next window: Ctrl+B then N + 10. Previous window: Ctrl+B then P + 11. Jump to window 0-9: Ctrl+B then number + 12. Kill current window: Ctrl+B then & + 13. Rename window: Ctrl+B then , + 14. Split pane vertically: Ctrl+B then % + 15. Split pane horizontally: Ctrl+B then " + 16. Navigate panes: Ctrl+B then arrow keys + 17. Close pane: Ctrl+B then X + 18. Maximize pane: Ctrl+B then Z + 19. Resize pane: Ctrl+B then Ctrl+Arrow + 20. Show pane numbers: Ctrl+B then Q + 21. Enter copy mode: Ctrl+B then [ + 22. Paste from buffer: Ctrl+B then ] + 23. List buffers: Ctrl+B then # + 24. Choose buffer: Ctrl+B then = + 25. Synchronize panes: Ctrl+B then : then setw synchronize-panes on + 26. Break pane to new window: Ctrl+B then ! + 27. Join pane to window: Ctrl+B then : then join-pane -t target + 28. Send command to pane: tmux send-keys -t session:pane "command" Enter + 29. List key bindings: Ctrl+B then ? + 30. Reload config: Ctrl+B then : then source-file ~/.tmux.conf + 31. Scroll in pane: Ctrl+B then [ then arrow keys + 32. Set custom prefix: set -g prefix C-a in config + 33. Bind custom key: bind-key -n C-x command in config + 34. New session with command: tmux new-session -d -s name "command" + 35. Split window vertically (alt): tmux split-window -h + 36. Split window horizontally (alt): tmux split-window -v + 37. Cycle layouts: Ctrl+B then Space + 38. Show status bar time: set -g status-right "%H:%M %d-%m-%Y" + 39. Enable mouse: set -g mouse on in config + 40. Last active window: Ctrl+B then L + +● User answered Claude's questions: +  ⎿  · Pack the green fig and golden-yellow peaches together? → Big red plum + +● Got it. You've selected "Big red plum" for the green fig. + + I'm ready to help you pack the green fig. What's the plan, or would you like + me to look at the fruit in the basket to see which of the plums need to be + eaten? + +✻ Brewed for 19s · done 7:14 AM + +❯ Write a numbered list of exactly 3 short steps, numbered 1. to 3., with no  + text after the list. Then immediately use the AskUserQuestion tool exactly  + once: header "Order", question "Which step goes first?", options "Apple"  + (description "First fruit"), "Banana" (description "Second fruit") and  + "Cherry" (description "Third fruit"). Do nothing else. + +● 1. Identify the plums in a box + 2. Cut the figs + 3. Serve the plates +────────────────────────────────────────────────────────────────────────────── + ☐ Order  + +Which step goes first? + +❯ 1. Apple + First fruit + 2. Banana + Second fruit + 3. Cherry + Third fruit + 4. Type something. +────────────────────────────────────────────────────────────────────────────── + 5. Chat about this + +Enter to select · ↑/↓ to navigate · Esc to cancel + diff --git a/server/modules/providers/tests/fixtures/claude-question-numbered-list/list40-78x73.joined.ansi b/server/modules/providers/tests/fixtures/claude-question-numbered-list/list40-78x73.joined.ansi new file mode 100644 index 0000000..864c8da --- /dev/null +++ b/server/modules/providers/tests/fixtures/claude-question-numbered-list/list40-78x73.joined.ansi @@ -0,0 +1,73 @@ +user@devbox:/tmp/demo-list-proj$ /tmp/demo-list-work/launch.sh + ▐▛███▛█ Claude Code v2.1.291 +▝▜██████▀ Haiku 4.5 · Claude API + ▝▝ ▝▝ /tmp/demo-list-proj + + +❯ /tui default  + ⎿ Already using the default renderer. + +❯ First write a numbered list of 40 short tmux tips, one line each, numbered  + 1. to 40. Then, without any other text, use the AskUserQuestion tool  + exactly once: header "Pick one", question "Pack the green fig and  + golden-yellow peaches together?", options "Ripe avocados" (description  + "Soft green, very ripe") and "Big red plum" (description "Deep red skin").  + Do nothing else. + +● 1. Start a new session: tmux new-session -s name + 2. List all sessions: tmux list-sessions + 3. Attach to a session: tmux attach -t name + 4. Detach from current session: Ctrl+B then D + 5. Kill a session: tmux kill-session -t name + 6. Rename a session: Ctrl+B then $ + 7. Create a new window: Ctrl+B then C + 8. List windows: Ctrl+B then W + 9. Next window: Ctrl+B then N + 10. Previous window: Ctrl+B then P + 11. Jump to window 0-9: Ctrl+B then number + 12. Kill current window: Ctrl+B then & + 13. Rename window: Ctrl+B then , + 14. Split pane vertically: Ctrl+B then % + 15. Split pane horizontally: Ctrl+B then " + 16. Navigate panes: Ctrl+B then arrow keys + 17. Close pane: Ctrl+B then X + 18. Maximize pane: Ctrl+B then Z + 19. Resize pane: Ctrl+B then Ctrl+Arrow + 20. Show pane numbers: Ctrl+B then Q + 21. Enter copy mode: Ctrl+B then [ + 22. Paste from buffer: Ctrl+B then ] + 23. List buffers: Ctrl+B then # + 24. Choose buffer: Ctrl+B then = + 25. Synchronize panes: Ctrl+B then : then setw synchronize-panes on + 26. Break pane to new window: Ctrl+B then ! + 27. Join pane to window: Ctrl+B then : then join-pane -t target + 28. Send command to pane: tmux send-keys -t session:pane "command" Enter + 29. List key bindings: Ctrl+B then ? + 30. Reload config: Ctrl+B then : then source-file ~/.tmux.conf + 31. Scroll in pane: Ctrl+B then [ then arrow keys + 32. Set custom prefix: set -g prefix C-a in config + 33. Bind custom key: bind-key -n C-x command in config + 34. New session with command: tmux new-session -d -s name "command" + 35. Split window vertically (alt): tmux split-window -h + 36. Split window horizontally (alt): tmux split-window -v + 37. Cycle layouts: Ctrl+B then Space + 38. Show status bar time: set -g status-right "%H:%M %d-%m-%Y" + 39. Enable mouse: set -g mouse on in config + 40. Last active window: Ctrl+B then L +────────────────────────────────────────────────────────────────────────────── + ☐ Pick one  + +Pack the green fig and golden-yellow peaches together? + +❯ 1. Ripe avocados + Soft green, very ripe + 2. Big red plum + Deep red skin + 3. Type something. +────────────────────────────────────────────────────────────────────────────── + 4. Chat about this + +Enter to select · ↑/↓ to navigate · Esc to cancel + + + diff --git a/server/modules/providers/tests/fixtures/claude-question-numbered-list/list40-78x73.screen.ansi b/server/modules/providers/tests/fixtures/claude-question-numbered-list/list40-78x73.screen.ansi new file mode 100644 index 0000000..864c8da --- /dev/null +++ b/server/modules/providers/tests/fixtures/claude-question-numbered-list/list40-78x73.screen.ansi @@ -0,0 +1,73 @@ +user@devbox:/tmp/demo-list-proj$ /tmp/demo-list-work/launch.sh + ▐▛███▛█ Claude Code v2.1.291 +▝▜██████▀ Haiku 4.5 · Claude API + ▝▝ ▝▝ /tmp/demo-list-proj + + +❯ /tui default  + ⎿ Already using the default renderer. + +❯ First write a numbered list of 40 short tmux tips, one line each, numbered  + 1. to 40. Then, without any other text, use the AskUserQuestion tool  + exactly once: header "Pick one", question "Pack the green fig and  + golden-yellow peaches together?", options "Ripe avocados" (description  + "Soft green, very ripe") and "Big red plum" (description "Deep red skin").  + Do nothing else. + +● 1. Start a new session: tmux new-session -s name + 2. List all sessions: tmux list-sessions + 3. Attach to a session: tmux attach -t name + 4. Detach from current session: Ctrl+B then D + 5. Kill a session: tmux kill-session -t name + 6. Rename a session: Ctrl+B then $ + 7. Create a new window: Ctrl+B then C + 8. List windows: Ctrl+B then W + 9. Next window: Ctrl+B then N + 10. Previous window: Ctrl+B then P + 11. Jump to window 0-9: Ctrl+B then number + 12. Kill current window: Ctrl+B then & + 13. Rename window: Ctrl+B then , + 14. Split pane vertically: Ctrl+B then % + 15. Split pane horizontally: Ctrl+B then " + 16. Navigate panes: Ctrl+B then arrow keys + 17. Close pane: Ctrl+B then X + 18. Maximize pane: Ctrl+B then Z + 19. Resize pane: Ctrl+B then Ctrl+Arrow + 20. Show pane numbers: Ctrl+B then Q + 21. Enter copy mode: Ctrl+B then [ + 22. Paste from buffer: Ctrl+B then ] + 23. List buffers: Ctrl+B then # + 24. Choose buffer: Ctrl+B then = + 25. Synchronize panes: Ctrl+B then : then setw synchronize-panes on + 26. Break pane to new window: Ctrl+B then ! + 27. Join pane to window: Ctrl+B then : then join-pane -t target + 28. Send command to pane: tmux send-keys -t session:pane "command" Enter + 29. List key bindings: Ctrl+B then ? + 30. Reload config: Ctrl+B then : then source-file ~/.tmux.conf + 31. Scroll in pane: Ctrl+B then [ then arrow keys + 32. Set custom prefix: set -g prefix C-a in config + 33. Bind custom key: bind-key -n C-x command in config + 34. New session with command: tmux new-session -d -s name "command" + 35. Split window vertically (alt): tmux split-window -h + 36. Split window horizontally (alt): tmux split-window -v + 37. Cycle layouts: Ctrl+B then Space + 38. Show status bar time: set -g status-right "%H:%M %d-%m-%Y" + 39. Enable mouse: set -g mouse on in config + 40. Last active window: Ctrl+B then L +────────────────────────────────────────────────────────────────────────────── + ☐ Pick one  + +Pack the green fig and golden-yellow peaches together? + +❯ 1. Ripe avocados + Soft green, very ripe + 2. Big red plum + Deep red skin + 3. Type something. +────────────────────────────────────────────────────────────────────────────── + 4. Chat about this + +Enter to select · ↑/↓ to navigate · Esc to cancel + + + diff --git a/server/modules/providers/tests/fixtures/claude-question-numbered-list/s1-78x73.joined.txt b/server/modules/providers/tests/fixtures/claude-question-numbered-list/s1-78x73.joined.txt new file mode 100644 index 0000000..43b5882 --- /dev/null +++ b/server/modules/providers/tests/fixtures/claude-question-numbered-list/s1-78x73.joined.txt @@ -0,0 +1,73 @@ + + ▐▛███▛█ Claude Code v2.1.291 +▝▜██████▀ Haiku 4.5 · Claude API + ▝▝ ▝▝ /tmp/demo-list-proj + + Get to finished work sooner with Opus 5.5. Switch anytime with /model. + +❯ First write a plain numbered list of 40 short lines about tmux tips (no + tools). Then use the AskUserQuestion tool exactly once with a single + question: question "Pack the green fig and golden-yellow peaches + together?", header "Pick one", multiSelect false, two options: label "Ripe + avocados" description "Soft green, very ripe"; label "Big red plum" + description "Deep red skin". + +● 1. tmux list-sessions shows all active tmux sessions + 2. tmux new-session -s name creates a new session with a specific name + 3. tmux attach -t name connects to an existing session + 4. C-b is the default prefix key for all tmux commands + 5. C-b c creates a new window in the current session + 6. C-b , renames the current window + 7. C-b & kills the current window + 8. C-b n moves to the next window + 9. C-b p moves to the previous window + 10. C-b 0-9 jumps directly to a numbered window + 11. C-b % splits the current pane vertically + 12. C-b " splits the current pane horizontally + 13. C-b o moves to the next pane + 14. C-b ; toggles between the last two panes + 15. C-b x closes the current pane + 16. C-b d detaches from the current session + 17. tmux kill-session -t name ends a specific session + 18. tmux kill-server terminates all sessions (use with caution) + 19. C-b [ enters copy mode for scrolling and selection + 20. C-b ] pastes the most recent copied text + 21. C-b = shows all paste buffers + 22. C-b - deletes the most recent paste buffer + 23. C-b : enters command mode for typing tmux commands + 24. C-b ? displays all key bindings + 25. C-b t shows a clock in the current pane + 26. C-b { moves the current pane left + 27. C-b } moves the current pane right + 28. C-b ! breaks the current pane into a new window + 29. C-b $ renames the current session + 30. C-b s displays all sessions and allows selection + 31. tmux send-keys -t session "command" Enter executes a command in a + session + 32. C-b z toggles zoom on the current pane + 33. set -g mouse on enables mouse support in tmux.conf + 34. set -g prefix C-a changes the prefix key (example: Ctrl-A) + 35. bind-key -n C-l send-keys -R clears scrollback history with a shortcut + 36. C-b ( moves to the previous session + 37. C-b ) moves to the next session + 38. C-b | custom binding for vertical split (if configured) + 39. C-b - custom binding for horizontal split (if configured) + 40. tmux capture-pane -p -t session outputs the current pane content to + stdout + + Now for the question: +────────────────────────────────────────────────────────────────────────────── + ☐ Pick one + +Pack the green fig and golden-yellow peaches together? + +❯ 1. Ripe avocados + Soft green, very ripe + 2. Big red plum + Deep red skin + 3. Type something. +────────────────────────────────────────────────────────────────────────────── + 4. Chat about this + +Enter to select · ↑/↓ to navigate · Esc to cancel + diff --git a/server/modules/providers/tests/fixtures/claude-question-numbered-list/s1-78x73.screen.txt b/server/modules/providers/tests/fixtures/claude-question-numbered-list/s1-78x73.screen.txt new file mode 100644 index 0000000..43b5882 --- /dev/null +++ b/server/modules/providers/tests/fixtures/claude-question-numbered-list/s1-78x73.screen.txt @@ -0,0 +1,73 @@ + + ▐▛███▛█ Claude Code v2.1.291 +▝▜██████▀ Haiku 4.5 · Claude API + ▝▝ ▝▝ /tmp/demo-list-proj + + Get to finished work sooner with Opus 5.5. Switch anytime with /model. + +❯ First write a plain numbered list of 40 short lines about tmux tips (no + tools). Then use the AskUserQuestion tool exactly once with a single + question: question "Pack the green fig and golden-yellow peaches + together?", header "Pick one", multiSelect false, two options: label "Ripe + avocados" description "Soft green, very ripe"; label "Big red plum" + description "Deep red skin". + +● 1. tmux list-sessions shows all active tmux sessions + 2. tmux new-session -s name creates a new session with a specific name + 3. tmux attach -t name connects to an existing session + 4. C-b is the default prefix key for all tmux commands + 5. C-b c creates a new window in the current session + 6. C-b , renames the current window + 7. C-b & kills the current window + 8. C-b n moves to the next window + 9. C-b p moves to the previous window + 10. C-b 0-9 jumps directly to a numbered window + 11. C-b % splits the current pane vertically + 12. C-b " splits the current pane horizontally + 13. C-b o moves to the next pane + 14. C-b ; toggles between the last two panes + 15. C-b x closes the current pane + 16. C-b d detaches from the current session + 17. tmux kill-session -t name ends a specific session + 18. tmux kill-server terminates all sessions (use with caution) + 19. C-b [ enters copy mode for scrolling and selection + 20. C-b ] pastes the most recent copied text + 21. C-b = shows all paste buffers + 22. C-b - deletes the most recent paste buffer + 23. C-b : enters command mode for typing tmux commands + 24. C-b ? displays all key bindings + 25. C-b t shows a clock in the current pane + 26. C-b { moves the current pane left + 27. C-b } moves the current pane right + 28. C-b ! breaks the current pane into a new window + 29. C-b $ renames the current session + 30. C-b s displays all sessions and allows selection + 31. tmux send-keys -t session "command" Enter executes a command in a + session + 32. C-b z toggles zoom on the current pane + 33. set -g mouse on enables mouse support in tmux.conf + 34. set -g prefix C-a changes the prefix key (example: Ctrl-A) + 35. bind-key -n C-l send-keys -R clears scrollback history with a shortcut + 36. C-b ( moves to the previous session + 37. C-b ) moves to the next session + 38. C-b | custom binding for vertical split (if configured) + 39. C-b - custom binding for horizontal split (if configured) + 40. tmux capture-pane -p -t session outputs the current pane content to + stdout + + Now for the question: +────────────────────────────────────────────────────────────────────────────── + ☐ Pick one + +Pack the green fig and golden-yellow peaches together? + +❯ 1. Ripe avocados + Soft green, very ripe + 2. Big red plum + Deep red skin + 3. Type something. +────────────────────────────────────────────────────────────────────────────── + 4. Chat about this + +Enter to select · ↑/↓ to navigate · Esc to cancel + diff --git a/server/modules/providers/tests/fixtures/claude-question-numbered-list/s2-78x73.joined.txt b/server/modules/providers/tests/fixtures/claude-question-numbered-list/s2-78x73.joined.txt new file mode 100644 index 0000000..29a550b --- /dev/null +++ b/server/modules/providers/tests/fixtures/claude-question-numbered-list/s2-78x73.joined.txt @@ -0,0 +1,76 @@ +user@devbox:/tmp/demo-list-proj$ env -u CLAUDECODE -u CLAUDE_CODE_ENTRYPOINT H +OME=/tmp/demo-list-home CLAUDE_CONFIG_DIR=/tmp/demo-list-claude CLAUDE_CODE_OA +UTH_TOKEN="$(cat ~/.config/demo/oauth-token)" claude --model haiku + ▐▛███▛█ Claude Code v2.1.291 +▝▜██████▀ Haiku 4.5 · Claude API + ▝▝ ▝▝ /tmp/demo-list-proj + +▎ Your voice can help guide AI +▎ Take 15 min to share your experiences with Anthropic Interviewer. Start now + +❯ First write a plain numbered list of 40 short lines about tmux tips (no + tools). Then use the AskUserQuestion tool exactly once with a single + question: question "Pack the green fig and golden-yellow peaches + together?", header "Pick one", multiSelect false, two options: label "Ripe + avocados" description "Soft green, very ripe"; label "Big red plum" + description "Deep red skin". + +● Here are 40 tmux tips: + + 1. Start a new session with tmux new-session -s name + 2. Attach to session with tmux attach-session -t name + 3. List all sessions with tmux list-sessions + 4. Detach from session with Ctrl-b d + 5. Kill a session with tmux kill-session -t name + 6. Create a new window with Ctrl-b c + 7. Switch windows with Ctrl-b n (next) or Ctrl-b p (previous) + 8. Jump to window N with Ctrl-b N (where N is 0-9) + 9. Split horizontally with Ctrl-b " + 10. Split vertically with Ctrl-b % + 11. Navigate panes with Ctrl-b arrow-keys + 12. Resize panes with Ctrl-b :resize-pane -D 5 (and U/L/R) + 13. Zoom a pane with Ctrl-b z + 14. Kill a pane with Ctrl-b x + 15. Rename window with Ctrl-b , + 16. Rename session with tmux rename-session -t old new + 17. Choose and switch sessions with Ctrl-b s + 18. Choose and switch windows with Ctrl-b w + 19. Show time with Ctrl-b t + 20. Enter copy mode with Ctrl-b [ + 21. Exit copy mode with q + 22. Search in copy mode with / then n for next + 23. Paste buffer with Ctrl-b ] + 24. List buffers with tmux list-buffers + 25. Save buffer to file with tmux save-buffer -b 0 filename + 26. Load config with tmux source-file ~/.tmux.conf + 27. Set prefix to Ctrl-a in config with set -g prefix C-a + 28. Enable mouse with set -g mouse on + 29. Increase history with set -g history-limit 10000 + 30. Use vi keybindings with setw -g mode-keys vi + 31. Set status bar position with set -g status-position top + 32. Customize status bar with set -g status-left "#S" + 33. Show pane numbers with Ctrl-b q + 34. Select specific pane with Ctrl-b q then number + 35. Break pane into new window with Ctrl-b ! + 36. Join pane from another window with tmux join-pane -t target + 37. Display pane info with Ctrl-b i + 38. Swap panes with Ctrl-b { or } + 39. Swap windows with tmux swap-window -t windows + 40. Run command without attaching with tmux new-session -d -s name command + + Now, here's my question for you: +────────────────────────────────────────────────────────────────────────────── + ☐ Pick one + +Pack the green fig and golden-yellow peaches together? + +❯ 1. Ripe avocados + Soft green, very ripe + 2. Big red plum + Deep red skin + 3. Type something. +────────────────────────────────────────────────────────────────────────────── + 4. Chat about this + +Enter to select · ↑/↓ to navigate · Esc to cancel + diff --git a/server/modules/providers/tests/fixtures/claude-question-numbered-list/s2-78x73.screen.txt b/server/modules/providers/tests/fixtures/claude-question-numbered-list/s2-78x73.screen.txt new file mode 100644 index 0000000..31c219d --- /dev/null +++ b/server/modules/providers/tests/fixtures/claude-question-numbered-list/s2-78x73.screen.txt @@ -0,0 +1,73 @@ + ▐▛███▛█ Claude Code v2.1.291 +▝▜██████▀ Haiku 4.5 · Claude API + ▝▝ ▝▝ /tmp/demo-list-proj + +▎ Your voice can help guide AI +▎ Take 15 min to share your experiences with Anthropic Interviewer. Start now + +❯ First write a plain numbered list of 40 short lines about tmux tips (no + tools). Then use the AskUserQuestion tool exactly once with a single + question: question "Pack the green fig and golden-yellow peaches + together?", header "Pick one", multiSelect false, two options: label "Ripe + avocados" description "Soft green, very ripe"; label "Big red plum" + description "Deep red skin". + +● Here are 40 tmux tips: + + 1. Start a new session with tmux new-session -s name + 2. Attach to session with tmux attach-session -t name + 3. List all sessions with tmux list-sessions + 4. Detach from session with Ctrl-b d + 5. Kill a session with tmux kill-session -t name + 6. Create a new window with Ctrl-b c + 7. Switch windows with Ctrl-b n (next) or Ctrl-b p (previous) + 8. Jump to window N with Ctrl-b N (where N is 0-9) + 9. Split horizontally with Ctrl-b " + 10. Split vertically with Ctrl-b % + 11. Navigate panes with Ctrl-b arrow-keys + 12. Resize panes with Ctrl-b :resize-pane -D 5 (and U/L/R) + 13. Zoom a pane with Ctrl-b z + 14. Kill a pane with Ctrl-b x + 15. Rename window with Ctrl-b , + 16. Rename session with tmux rename-session -t old new + 17. Choose and switch sessions with Ctrl-b s + 18. Choose and switch windows with Ctrl-b w + 19. Show time with Ctrl-b t + 20. Enter copy mode with Ctrl-b [ + 21. Exit copy mode with q + 22. Search in copy mode with / then n for next + 23. Paste buffer with Ctrl-b ] + 24. List buffers with tmux list-buffers + 25. Save buffer to file with tmux save-buffer -b 0 filename + 26. Load config with tmux source-file ~/.tmux.conf + 27. Set prefix to Ctrl-a in config with set -g prefix C-a + 28. Enable mouse with set -g mouse on + 29. Increase history with set -g history-limit 10000 + 30. Use vi keybindings with setw -g mode-keys vi + 31. Set status bar position with set -g status-position top + 32. Customize status bar with set -g status-left "#S" + 33. Show pane numbers with Ctrl-b q + 34. Select specific pane with Ctrl-b q then number + 35. Break pane into new window with Ctrl-b ! + 36. Join pane from another window with tmux join-pane -t target + 37. Display pane info with Ctrl-b i + 38. Swap panes with Ctrl-b { or } + 39. Swap windows with tmux swap-window -t windows + 40. Run command without attaching with tmux new-session -d -s name command + + Now, here's my question for you: +────────────────────────────────────────────────────────────────────────────── + ☐ Pick one + +Pack the green fig and golden-yellow peaches together? + +❯ 1. Ripe avocados + Soft green, very ripe + 2. Big red plum + Deep red skin + 3. Type something. +────────────────────────────────────────────────────────────────────────────── + 4. Chat about this + +Enter to select · ↑/↓ to navigate · Esc to cancel +