From 1ab28315a7f7d07125d77501f9d3e62c01ecf1f4 Mon Sep 17 00:00:00 2001 From: Luke Norland <217919286+Luke-Norland@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:18:07 +0000 Subject: [PATCH] fix(claude): keep a numbered list above a question from hiding its card The Claude question parser scanned up to 64 lines above the "Enter to select" hint and required every numbered row in that window to run 1..N. When the assistant's reply above the question ended in a numbered list, those items were read as rows too (37., 38., ... ahead of the real 1.), the check failed, and a short question that fits the screen got no card. Take only the current run of rows: the contiguous run that ends at the last row above the hint and counts back down to 1. Numbered lines above that 1. are ignored. The question text and the answer path are unchanged. The transcript-ask parser already finds the real rows: it slides a window that must number 1..N, match the transcript's labels in order and end in "Type something." and "Chat about this", so a list above the frame cannot complete a match. A test pins that. Co-Authored-By: Claude Opus 5.5 --- .../tmux-interactive-prompt.service.ts | 15 +- .../claude-question-numbered-list.test.ts | 148 ++++++++++++++++++ .../list3-78x73.joined.ansi | 95 +++++++++++ .../list3-78x73.screen.ansi | 73 +++++++++ .../list40-78x73.joined.ansi | 73 +++++++++ .../list40-78x73.screen.ansi | 73 +++++++++ .../s1-78x73.joined.txt | 73 +++++++++ .../s1-78x73.screen.txt | 73 +++++++++ .../s2-78x73.joined.txt | 76 +++++++++ .../s2-78x73.screen.txt | 73 +++++++++ 10 files changed, 771 insertions(+), 1 deletion(-) create mode 100644 server/modules/providers/tests/claude-question-numbered-list.test.ts create mode 100644 server/modules/providers/tests/fixtures/claude-question-numbered-list/list3-78x73.joined.ansi create mode 100644 server/modules/providers/tests/fixtures/claude-question-numbered-list/list3-78x73.screen.ansi create mode 100644 server/modules/providers/tests/fixtures/claude-question-numbered-list/list40-78x73.joined.ansi create mode 100644 server/modules/providers/tests/fixtures/claude-question-numbered-list/list40-78x73.screen.ansi create mode 100644 server/modules/providers/tests/fixtures/claude-question-numbered-list/s1-78x73.joined.txt create mode 100644 server/modules/providers/tests/fixtures/claude-question-numbered-list/s1-78x73.screen.txt create mode 100644 server/modules/providers/tests/fixtures/claude-question-numbered-list/s2-78x73.joined.txt create mode 100644 server/modules/providers/tests/fixtures/claude-question-numbered-list/s2-78x73.screen.txt 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 +