zames_pro 2.75.5 → 2.75.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +10 -4
  3. package/dist/agent-loop.js +61 -1
  4. package/dist/browser.js +40 -8
  5. package/dist/commands.js +0 -1
  6. package/dist/config.js +8 -3
  7. package/dist/deepseek-ui.js +1 -3
  8. package/dist/i18n/common.js +13 -16
  9. package/dist/i18n/config.js +2 -2
  10. package/dist/i18n/help.js +2 -6
  11. package/dist/i18n/spinner.js +7 -1
  12. package/dist/index.js +0 -49
  13. package/dist/net-capture.js +43 -11
  14. package/dist/provider-handoff.js +0 -4
  15. package/dist/qwen-ui.js +0 -8
  16. package/dist/run-task.js +24 -1
  17. package/dist/shell.js +0 -6
  18. package/dist/system-prompt.js +52 -2
  19. package/dist/tools.js +33 -0
  20. package/dist/tui/AskQuestion.js +108 -0
  21. package/dist/tui/FullScreenApp.js +2 -1
  22. package/dist/tui/InputLine.js +0 -3
  23. package/dist/tui/StatusBar.js +0 -9
  24. package/dist/tui/TuiEditor.js +0 -43
  25. package/dist/tui/keys.js +0 -2
  26. package/dist/tui/run-ask.js +22 -0
  27. package/dist/tui/store.js +0 -2
  28. package/dist/tui/widgets.js +3 -9
  29. package/package.json +2 -2
  30. package/scripts/build-readme.mts +0 -101
  31. package/scripts/changelog-draft.mts +0 -93
  32. package/scripts/clean-dist.mts +0 -17
  33. package/scripts/coverage-gate.mts +0 -112
  34. package/scripts/ctd-live-driver.mts +0 -51
  35. package/scripts/ctd-live.tsx +0 -33
  36. package/scripts/escd-live-driver.mts +0 -37
  37. package/scripts/eslint-ts6.mts +0 -58
  38. package/scripts/inline-status-driver.mts +0 -31
  39. package/scripts/inline-status.tsx +0 -33
  40. package/scripts/mouse-churn.mts +0 -34
  41. package/scripts/qwen-models-smoke.mts +0 -117
  42. package/scripts/qwen-smoke.mts +0 -221
  43. package/scripts/release.mts +0 -199
  44. package/scripts/render-demo.mts +0 -154
  45. package/scripts/render-how-it-works.mts +0 -229
  46. package/scripts/ru-shot.mts +0 -20
  47. package/scripts/sched-smoke.mts +0 -150
  48. package/scripts/self-smoke.mts +0 -309
  49. package/scripts/tui-comp-shot.mts +0 -63
  50. package/scripts/tui-components.tsx +0 -62
  51. package/scripts/tui-config-driver.mts +0 -82
  52. package/scripts/tui-config-probe.tsx +0 -74
  53. package/scripts/tui-edit-driver.mts +0 -100
  54. package/scripts/tui-edit-probe.tsx +0 -38
  55. package/scripts/tui-editor-probe.tsx +0 -24
  56. package/scripts/tui-input-driver.mts +0 -54
  57. package/scripts/tui-input-shot.tsx +0 -59
  58. package/scripts/tui-log-driver.mts +0 -35
  59. package/scripts/tui-log-shot.tsx +0 -48
  60. package/scripts/tui-longpaste-driver.mts +0 -56
  61. package/scripts/tui-longpaste.tsx +0 -35
  62. package/scripts/tui-manyselect-shot.mts +0 -34
  63. package/scripts/tui-manyselect.tsx +0 -17
  64. package/scripts/tui-mouse-churn-probe.tsx +0 -28
  65. package/scripts/tui-paste-driver.mts +0 -88
  66. package/scripts/tui-paste-kinds.tsx +0 -76
  67. package/scripts/tui-pastetest.mts +0 -38
  68. package/scripts/tui-probe.tsx +0 -62
  69. package/scripts/tui-repro.tsx +0 -64
  70. package/scripts/tui-resize.mts +0 -70
  71. package/scripts/tui-scroll-driver.mts +0 -53
  72. package/scripts/tui-scroll-probe.tsx +0 -46
  73. package/scripts/tui-shot.mts +0 -106
  74. package/scripts/tui-status-driver.mts +0 -24
  75. package/scripts/tui-status-shot.tsx +0 -27
  76. package/scripts/tui-submit.tsx +0 -57
  77. package/scripts/tui-wheel-driver.mts +0 -61
  78. package/scripts/tui-wheel-probe.tsx +0 -48
  79. package/scripts/tui-widgets-driver.mts +0 -20
  80. package/scripts/tui-widgets.tsx +0 -44
@@ -1,62 +0,0 @@
1
- // A tiny Ink harness that mounts the REAL FullScreenApp with a scripted store,
2
- // so a pty run can assert the on-screen geometry (input box height, cursor
3
- // row) instead of grepping escape bytes. Run under `script -qec` and feed keys
4
- // on stdin; the terminal output is replayed by the VT100 emulator in the test.
5
- //
6
- // NOT shipped: a dev probe only (lives in scripts/, not in `files`).
7
-
8
- import React from 'react'
9
- import fs from 'node:fs'
10
- import { render } from 'ink'
11
- import { TuiStore } from '../src/tui/store.js'
12
- import { FullScreenApp } from '../src/tui/FullScreenApp.js'
13
- import { TuiApp } from '../src/tui/TuiApp.js'
14
- import { initialEditState } from '../src/tui/edit.js'
15
-
16
- const store = new TuiStore()
17
- store.set({
18
- title: 'probe',
19
- version: '0.0.0',
20
- workdir: '/tmp',
21
- prompt: '> ',
22
- locale: 'en',
23
- commands: [],
24
- edit: { ...initialEditState(), buf: process.argv[2] ?? '' },
25
- // A copyable code block, so a pty run can press Ctrl+Y and assert the copy
26
- // callback fired (the raw block, not the highlighted render).
27
- codeBlocks: [{ lang: 'ts', text: 'const a = 1', index: 1 }],
28
- })
29
-
30
- // A few log rows so the viewport is not empty.
31
- store.pushLog('system', 'log line one')
32
- store.pushLog('system', 'log line two')
33
-
34
- const COPY_LOG = process.env.PROBE_COPY_LOG || ''
35
- const INLINE = process.env.PROBE_INLINE === '1'
36
-
37
- const inputProps = {
38
- onSubmit: () => {},
39
- onEscape: () => {},
40
- onCtrlC: () => {},
41
- onCopyCode: () => {
42
- if (COPY_LOG) fs.appendFileSync(COPY_LOG, 'copy' + String.fromCharCode(10))
43
- },
44
- onAttach: () => null,
45
- onClipboard: () => Promise.resolve(null),
46
- onHistoryChange: () => {},
47
- onClearScreen: () => {},
48
- attachments: [],
49
- onPastesChange: () => {},
50
- }
51
-
52
- render(
53
- INLINE ? (
54
- <TuiApp store={store} inputProps={inputProps} />
55
- ) : (
56
- <FullScreenApp store={store} inputProps={inputProps} />
57
- ),
58
- { exitOnCtrlC: false, patchConsole: false, alternateScreen: !INLINE },
59
- )
60
-
61
- // Keep the app alive until killed.
62
- setInterval(() => {}, 1 << 30)
@@ -1,64 +0,0 @@
1
- // Reproduce the LIVE TUI scenario for the input-box geometry bug.
2
- //
3
- // The stock probe uses a bare `> ` prompt, an empty command list and no header, which is why the gated pty test stays green while the live agent still overprints the second input line. This probe mounts the same FullScreenApp with a realistic prompt, a non-empty slash command list and a long title.
4
- //
5
- // NOT shipped: a dev probe only.
6
-
7
- import React from 'react'
8
- import { render } from 'ink'
9
- import { TuiStore } from '../src/tui/store.js'
10
- import { FullScreenApp } from '../src/tui/FullScreenApp.js'
11
- import { TuiApp } from '../src/tui/TuiApp.js'
12
- import { initialEditState } from '../src/tui/edit.js'
13
-
14
- const store = new TuiStore()
15
- const commands = [
16
- { name: '/help', description: 'show help' },
17
- { name: '/history', description: 'input history' },
18
- { name: '/new', description: 'new chat' },
19
- { name: '/resume', description: 'open a chat' },
20
- { name: '/config', description: 'settings' },
21
- {
22
- name: '/self-review',
23
- description:
24
- 'snapshot src/ and review the current working tree for bugs and risks',
25
- },
26
- ]
27
- store.set({
28
- title: 'zames_pro',
29
- version: '2.34.0',
30
- workdir: '/workspaces/zames/zames_pro',
31
- prompt: '❯ › ',
32
- locale: process.env.PROBE_LOCALE === 'ru' ? 'ru' : 'en',
33
- commands,
34
- edit: { ...initialEditState(), buf: process.argv[2] ?? '' },
35
- })
36
-
37
- store.pushLog('system', 'log line one')
38
- store.pushLog('system', 'log line two')
39
-
40
- const INLINE = process.env.PROBE_INLINE === '1'
41
-
42
- const inputProps = {
43
- onSubmit: () => {},
44
- onEscape: () => {},
45
- onCtrlC: () => {},
46
- onCopyCode: () => {},
47
- onAttach: () => null,
48
- onClipboard: () => Promise.resolve(null),
49
- onHistoryChange: () => {},
50
- onClearScreen: () => {},
51
- attachments: [],
52
- onPastesChange: () => {},
53
- }
54
-
55
- render(
56
- INLINE ? (
57
- <TuiApp store={store} inputProps={inputProps} />
58
- ) : (
59
- <FullScreenApp store={store} inputProps={inputProps} />
60
- ),
61
- { exitOnCtrlC: false, patchConsole: false, alternateScreen: !INLINE },
62
- )
63
-
64
- setInterval(() => {}, 1 << 30)
@@ -1,70 +0,0 @@
1
- // Resize a REAL TuiEditor under a pty and screenshot the final grid. The pty
2
- // WINSIZE is changed mid-run so the kernel delivers SIGWINCH, which exercises
3
- // TuiEditor.installResizeHandler (unmount + remount), not just Ink's erase.
4
- //
5
- // Usage: npx tsx scripts/tui-resize.mts [sc sr nc nr text]
6
- import { spawnSync } from 'node:child_process'
7
- import { Term } from '../test/vt100.mts'
8
-
9
- const NL = String.fromCharCode(10)
10
- const sc = Number(process.argv[2] || 80)
11
- const sr = Number(process.argv[3] || 24)
12
- const nc = Number(process.argv[4] || 100)
13
- const nr = Number(process.argv[5] || 30)
14
- const text = process.argv[6] || ''
15
-
16
- // Inline python pty harness (no external file).
17
- const py = [
18
- 'import os,pty,sys,time,fcntl,termios,struct,signal,select',
19
- 'sc,sr,nc,nr=' + sc + ',' + sr + ',' + nc + ',' + nr,
20
- 'text=' + JSON.stringify(text),
21
- 'pid,fd=pty.fork()',
22
- 'if pid==0:',
23
- " os.environ['COLUMNS']=str(sc); os.environ['LINES']=str(sr)",
24
- " os.execvp('npx',['npx','tsx','scripts/tui-editor-probe.tsx'])",
25
- 'else:',
26
- " def sz(c,r): fcntl.ioctl(fd,termios.TIOCSWINSZ,struct.pack('HHHH',r,c,0,0))",
27
- ' sz(sc,sr)',
28
- " buf=b''; start=time.time(); sent=False; resized=False",
29
- ' while time.time()-start<14:',
30
- ' t=time.time()-start',
31
- ' if not sent and t>5:',
32
- ' for ch in text:',
33
- ' os.write(fd,ch.encode()); time.sleep(0.12)',
34
- ' sent=True',
35
- ' if not resized and t>9:',
36
- ' sz(nc,nr); os.kill(pid,signal.SIGWINCH); resized=True',
37
- ' r,_,_=select.select([fd],[],[],0.2)',
38
- ' if r:',
39
- ' try:',
40
- ' d=os.read(fd,65536)',
41
- ' if not d: break',
42
- ' buf+=d',
43
- ' except OSError: break',
44
- ' time.sleep(0.5)',
45
- ' try:',
46
- ' while True:',
47
- ' r,_,_=select.select([fd],[],[],0.2)',
48
- ' if not r: break',
49
- ' d=os.read(fd,65536)',
50
- ' if not d: break',
51
- ' buf+=d',
52
- ' except OSError: pass',
53
- ' try: os.kill(pid,signal.SIGKILL)',
54
- ' except ProcessLookupError: pass',
55
- ' sys.stdout.buffer.write(buf)',
56
- ].join(NL)
57
-
58
- const out = spawnSync('python3', ['-c', py], {
59
- encoding: 'buffer',
60
- maxBuffer: 64 * 1024 * 1024,
61
- })
62
- const term = new Term(Math.max(sr, nr), Math.max(sc, nc))
63
- term.write(out.stdout.toString('utf-8'))
64
- process.stdout.write(
65
- term
66
- .render()
67
- .split(NL)
68
- .map((l, n) => String(n).padStart(2) + '| ' + l)
69
- .join(NL) + NL,
70
- )
@@ -1,53 +0,0 @@
1
- // Press keys on the scroll probe and print the scroll values it saw. Accepts
2
- // several End/Home encodings so a terminal that sends a non-xterm form can be
3
- // diagnosed.
4
- import { spawn } from 'node:child_process'
5
- import fs from 'node:fs'
6
- import path from 'node:path'
7
- const ESC = String.fromCharCode(27)
8
- const LOG = '/tmp/scroll-probe.log'
9
- try {
10
- fs.unlinkSync(LOG)
11
- } catch {
12
- /* ignore */
13
- }
14
- const map: Record<string, string> = {
15
- PGUP: ESC + '[5~',
16
- PGDN: ESC + '[6~',
17
- HOME: ESC + '[H',
18
- END: ESC + '[F',
19
- END4: ESC + '[4~',
20
- HOME1: ESC + '[1~',
21
- CEND: ESC + '[1;5F',
22
- CHOME: ESC + '[1;5H',
23
- SPGDN: ESC + '[6;2~',
24
- SPGUP: ESC + '[5;2~',
25
- }
26
- const keys = (process.argv[2] || 'PGUP').split(',').map((k) => map[k] ?? k)
27
- const child = spawn(
28
- 'script',
29
- ['-qec', 'npx tsx scripts/tui-scroll-probe.tsx', '/dev/null'],
30
- {
31
- cwd: path.resolve('.'),
32
- env: { ...process.env, COLUMNS: '80', LINES: '24', PROBE_SCROLL_LOG: LOG },
33
- },
34
- )
35
- child.stdout.on('data', () => {})
36
- child.stderr.on('data', () => {})
37
- let i = 0
38
- const tick = (): void => {
39
- if (i < keys.length) {
40
- child.stdin.write(keys[i])
41
- i++
42
- setTimeout(tick, 250)
43
- } else setTimeout(() => child.kill('SIGTERM'), 400)
44
- }
45
- setTimeout(tick, 5000)
46
- child.on('close', () => {
47
- const lines = fs
48
- .readFileSync(LOG, 'utf-8')
49
- .trim()
50
- .split(String.fromCharCode(10))
51
- console.log('SCROLL=' + JSON.stringify(lines.map(Number)))
52
- process.exit(0)
53
- })
@@ -1,46 +0,0 @@
1
- // Mount the REAL FullScreenApp with a LONG log and log every scroll change,
2
- // so a pty can press PgUp/PgDn/Home/End and prove the viewport moves.
3
- import React from 'react'
4
- import fs from 'node:fs'
5
- import { render } from 'ink'
6
- import { TuiStore } from '../src/tui/store.js'
7
- import { FullScreenApp } from '../src/tui/FullScreenApp.js'
8
- import { initialEditState } from '../src/tui/edit.js'
9
-
10
- const LOG = process.env.PROBE_SCROLL_LOG || '/tmp/scroll-probe.log'
11
- fs.writeFileSync(LOG, '')
12
- const store = new TuiStore()
13
- store.set({
14
- title: 'probe',
15
- version: '0',
16
- workdir: '/tmp',
17
- prompt: '\u2794 probe \u203a ',
18
- locale: 'en',
19
- commands: [],
20
- edit: { ...initialEditState(), buf: '' },
21
- })
22
- for (let i = 0; i < 100; i++) store.pushLog('system', 'log line ' + i)
23
- const inputProps = {
24
- onSubmit: () => {},
25
- onEscape: () => {},
26
- onCtrlC: () => {},
27
- onCopyCode: () => {},
28
- onAttach: () => null,
29
- onClipboard: () => Promise.resolve(null),
30
- onHistoryChange: () => {},
31
- onClearScreen: () => {},
32
- attachments: [],
33
- onPastesChange: () => {},
34
- }
35
- store.subscribe(() => {
36
- fs.appendFileSync(
37
- LOG,
38
- String(store.getSnapshot().scroll) + String.fromCharCode(10),
39
- )
40
- })
41
- render(React.createElement(FullScreenApp, { store, inputProps }), {
42
- exitOnCtrlC: false,
43
- patchConsole: false,
44
- alternateScreen: true,
45
- })
46
- setInterval(() => {}, 1 << 30)
@@ -1,106 +0,0 @@
1
- // Take a VT100 "screenshot" of the TUI under a pty and print the grid.
2
- //
3
- // Usage: npx tsx scripts/tui-shot.mts [--inline] [--probe=tui-repro|tui-probe]
4
- // [--buf=<initial buffer>] [--keys=a,LF,b,ENTER] [--cols=80] [--rows=24]
5
- //
6
- // Keys are sent ONE CHUNK AT A TIME. Tokens: LF (Ctrl+J newline), CR (Enter),
7
- // ESC, TAB, or any literal character. The screen is snapshotted WHILE the app
8
- // is still alive, so the real terminal-cursor cell is included.
9
-
10
- import { spawn } from 'node:child_process'
11
- import path from 'node:path'
12
- import { Term } from '../test/vt100.mts'
13
-
14
- const ESC = String.fromCharCode(27)
15
- const LF = String.fromCharCode(10)
16
- const CR = String.fromCharCode(13)
17
- const TAB = String.fromCharCode(9)
18
-
19
- function arg(name: string, def = ''): string {
20
- const hit = process.argv.find((a) => a.startsWith('--' + name + '='))
21
- return hit ? hit.slice(name.length + 3) : def
22
- }
23
- const hasFlag = (name: string): boolean => process.argv.includes('--' + name)
24
-
25
- const inline = hasFlag('inline')
26
- const probe = arg('probe', 'tui-repro')
27
- const buf = arg('buf', '')
28
- const keys = arg('keys', '')
29
- .split(',')
30
- .filter((s) => s.length > 0)
31
- .map((s) => {
32
- if (s === 'LF') return LF
33
- if (s === 'CR' || s === 'ENTER') return CR
34
- if (s === 'ESC') return ESC
35
- if (s === 'TAB') return TAB
36
- // Alt+Enter and Shift+Enter must arrive as ONE chunk, like a real terminal
37
- // sends them; splitting them into separate chunks makes Ink read a bare
38
- // Escape key instead of the modified Enter.
39
- if (s === 'ALTENTER') return ESC + CR
40
- if (s === 'SHIFTENTER') return ESC + '[13;2u'
41
- if (s === 'UP') return ESC + '[A'
42
- if (s === 'DOWN') return ESC + '[B'
43
- if (s === 'RIGHT') return ESC + '[C'
44
- if (s === 'LEFT') return ESC + '[D'
45
- // Editing keys, so the word-delete bindings can be exercised on a real pty.
46
- if (s === 'CTDL') return ESC + '[3;5~' // Ctrl+Delete (xterm)
47
- if (s === 'CTDLU') return ESC + '[3;5u' // Ctrl+Delete (kitty CSI-u)
48
- if (s === 'CTRLBS') return ESC + String.fromCharCode(127) // Ctrl+Backspace
49
- if (s === 'CTB08') return String.fromCharCode(8) // Ctrl+Backspace (0x08)
50
- if (s === 'DEL') return ESC + '[3~' // Delete
51
- if (s === 'CTRLW') return String.fromCharCode(23)
52
- if (s === 'CTRLU') return String.fromCharCode(21)
53
- if (s === 'CTRLK') return String.fromCharCode(11)
54
- if (s === 'SPACE') return ' '
55
- return s
56
- })
57
- const cols = Number(arg('cols', '80'))
58
- const rows = Number(arg('rows', '24'))
59
- const hold = Number(arg('hold', '500'))
60
-
61
- const scriptPath = 'npx tsx scripts/' + probe + '.tsx ' + JSON.stringify(buf)
62
-
63
- const term = new Term(rows, cols)
64
- const child = spawn('script', ['-qec', scriptPath, '/dev/null'], {
65
- cwd: path.resolve('.'),
66
- env: {
67
- ...process.env,
68
- COLUMNS: String(cols),
69
- LINES: String(rows),
70
- ...(inline ? { PROBE_INLINE: '1' } : {}),
71
- },
72
- })
73
- child.stdout.on('data', (d) => term.write(d.toString('utf-8')))
74
- child.stderr.on('data', (d) => term.write(d.toString('utf-8')))
75
-
76
- let i = 0
77
- const tick = (): void => {
78
- if (i < keys.length) {
79
- child.stdin.write(keys[i])
80
- i++
81
- setTimeout(tick, 250)
82
- } else {
83
- setTimeout(() => {
84
- const cr = term.r
85
- const cc = term.c
86
- const lines = term.render().split(LF)
87
- if (lines[cr])
88
- lines[cr] =
89
- lines[cr].slice(0, cc) +
90
- String.fromCharCode(9673) +
91
- lines[cr].slice(cc + 1)
92
- process.stdout.write(
93
- lines.map((l, n) => String(n).padStart(2) + '| ' + l).join(LF) +
94
- LF +
95
- 'cursor: row ' +
96
- cr +
97
- ', col ' +
98
- cc +
99
- LF,
100
- )
101
- child.kill('SIGTERM')
102
- }, hold)
103
- }
104
- }
105
- setTimeout(tick, 4500)
106
- child.on('close', () => process.exit(0))
@@ -1,24 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
- import path from 'node:path'
3
- import { Term } from '../test/vt100.mts'
4
- const NL = String.fromCharCode(10)
5
- const cols = Number(
6
- (process.argv.find((a) => a.startsWith('--cols=')) || '--cols=80').slice(7),
7
- )
8
- const rows = 8
9
- const term = new Term(rows, cols)
10
- const child = spawn(
11
- 'script',
12
- ['-qec', 'npx tsx scripts/tui-status-shot.tsx', '/dev/null'],
13
- {
14
- cwd: path.resolve('.'),
15
- env: { ...process.env, COLUMNS: String(cols), LINES: String(rows) },
16
- },
17
- )
18
- child.stdout.on('data', (d) => term.write(d.toString('utf-8')))
19
- child.stderr.on('data', (d) => term.write(d.toString('utf-8')))
20
- setTimeout(() => {
21
- process.stdout.write(term.render() + NL)
22
- child.kill('SIGTERM')
23
- }, 5000)
24
- child.on('close', () => process.exit(0))
@@ -1,27 +0,0 @@
1
- // Mount the REAL StatusBar with all badges set, to check the width math at
2
- // narrow terminals (truncation must not clip the badges).
3
- import React from 'react'
4
- import { render } from 'ink'
5
- import { StatusBar } from '../src/tui/StatusBar.js'
6
- import { TuiStore } from '../src/tui/store.js'
7
- const store = new TuiStore()
8
- store.set({
9
- locale: 'ru',
10
- busy: true,
11
- animBase: 'Думаю',
12
- animStart: Date.now() - 45000,
13
- provider: 'DeepSeek',
14
- queueLength: 2,
15
- tasks: 'задачи: 2/5',
16
- thinkingEnabled: true,
17
- searchEnabled: true,
18
- contextText: 'ctx: 302k · 30%',
19
- contextLevel: 'warn',
20
- statusLine: 'git:master',
21
- sendState: 'generating',
22
- })
23
- render(React.createElement(StatusBar, { state: store.getSnapshot() }), {
24
- exitOnCtrlC: false,
25
- patchConsole: false,
26
- })
27
- setInterval(() => {}, 1 << 30)
@@ -1,57 +0,0 @@
1
- // Minimal probe: mount the REAL FullScreenApp with a store whose submit
2
- // callback logs what it receives. Used to verify the bracketed-paste ->
3
- // [Pasted lines#N] -> expandPastes path end to end.
4
-
5
- import React from 'react'
6
- import fs from 'node:fs'
7
- import { render } from 'ink'
8
- import { TuiStore } from '../src/tui/store.js'
9
- import { FullScreenApp } from '../src/tui/FullScreenApp.js'
10
- import { initialEditState } from '../src/tui/edit.js'
11
- import { expandPastes } from '../src/input/layout.js'
12
-
13
- const LOG = process.env.PROBE_SUBMIT_LOG || '/tmp/paste-submit.log'
14
- const store = new TuiStore()
15
- store.set({
16
- title: 'probe',
17
- version: '0',
18
- workdir: '/tmp',
19
- prompt: '> ',
20
- locale: 'en',
21
- commands: [],
22
- edit: { ...initialEditState(), buf: '' },
23
- })
24
- const inputProps = {
25
- onSubmit: (text: string) => {
26
- // Mirrors TuiEditor.handleSubmit: expand the paste markers from the
27
- // CURRENT store snapshot, which is what the agent actually receives.
28
- const expanded = expandPastes(store.getSnapshot().pastes, text)
29
- fs.appendFileSync(
30
- LOG,
31
- 'SUBMIT=' + JSON.stringify(expanded) + String.fromCharCode(10),
32
- )
33
- },
34
- onEscape: () => {},
35
- onCtrlC: () => {},
36
- onCopyCode: () => {},
37
- onAttach: () => null,
38
- onClipboard: () => Promise.resolve(null),
39
- onHistoryChange: () => {},
40
- onClearScreen: () => {},
41
- attachments: [],
42
- onPastesChange: (p: unknown) => {
43
- // Mirrors TuiEditor.buildInputProps: the adapter writes the paste list
44
- // BACK into the store, which is what handleSubmit reads to expand markers.
45
- store.set({ pastes: p as never })
46
- fs.appendFileSync(
47
- LOG,
48
- 'PASTES=' + JSON.stringify(p) + String.fromCharCode(10),
49
- )
50
- },
51
- }
52
- render(<FullScreenApp store={store} inputProps={inputProps} />, {
53
- exitOnCtrlC: false,
54
- patchConsole: false,
55
- alternateScreen: true,
56
- })
57
- setInterval(() => {}, 1 << 30)
@@ -1,61 +0,0 @@
1
- // Drive scripts/tui-wheel-probe.tsx under a pty and send REAL SGR wheel events
2
- // (mouse reporting must be on for these to arrive; the probe sets nothing, so
3
- // this also proves the SCROLL mode sequences are emitted). Prints the buffer
4
- // and scroll transitions the probe logged: the buffer must NEVER change and
5
- // the scroll must INCREASE on wheel-up.
6
- import { spawn } from 'node:child_process'
7
- import fs from 'node:fs'
8
- import path from 'node:path'
9
-
10
- const ESC = String.fromCharCode(27)
11
- const LOG = '/tmp/wheel-probe.log'
12
- const SCROLL = process.argv.includes('--scroll')
13
- // SGR wheel-up = Cb 64, wheel-down = 65: ESC [ < 64 ; col ; row M
14
- try {
15
- fs.unlinkSync(LOG)
16
- } catch {
17
- /* ignore */
18
- }
19
-
20
- const child = spawn(
21
- 'script',
22
- ['-qec', 'npx tsx scripts/tui-wheel-probe.tsx', '/dev/null'],
23
- {
24
- cwd: path.resolve('.'),
25
- env: {
26
- ...process.env,
27
- COLUMNS: '80',
28
- LINES: '24',
29
- PROBE_WHEEL_LOG: LOG,
30
- ...(SCROLL ? { PROBE_MOUSE_SCROLL: '1' } : {}),
31
- },
32
- },
33
- )
34
- child.stdout.on('data', () => {})
35
- child.stderr.on('data', () => {})
36
-
37
- setTimeout(() => {
38
- // Five wheel-up notches.
39
- for (let i = 0; i < 5; i++) {
40
- child.stdin.write(ESC + '[<64;10;10M')
41
- setTimeout(() => {}, 50)
42
- }
43
- setTimeout(() => child.kill('SIGTERM'), 800)
44
- }, 6000)
45
-
46
- child.on('close', () => {
47
- let lines: string[] = []
48
- try {
49
- lines = fs.readFileSync(LOG, 'utf-8').trim().split(String.fromCharCode(10))
50
- } catch {
51
- /* ignore */
52
- }
53
- const parsed = lines.map(
54
- (l) => JSON.parse(l) as { buf: string; scroll: number },
55
- )
56
- const bufs = [...new Set(parsed.map((p) => p.buf))]
57
- const scrolls = parsed.map((p) => p.scroll)
58
- console.log('BUFS=' + JSON.stringify(bufs))
59
- console.log('SCROLLS=' + JSON.stringify(scrolls))
60
- process.exit(0)
61
- })
@@ -1,48 +0,0 @@
1
- // Mount the REAL TuiEditor with a NON-EMPTY input history and a long log, so a
2
- // pty can send real SGR wheel events and the probe can log whether the INPUT
3
- // BUFFER or the LOG scroll offset changed. This distinguishes the two failure
4
- // modes the operator kept hitting: wheel must scroll the LOG (SCROLL mode) and
5
- // must NEVER touch the input history.
6
-
7
- import fs from 'node:fs'
8
- import { TuiEditor } from '../src/tui/TuiEditor.js'
9
-
10
- const LOG = process.env.PROBE_WHEEL_LOG || '/tmp/wheel-probe.log'
11
- fs.writeFileSync(LOG, '')
12
-
13
- const ed = new TuiEditor({
14
- prompt: '> ',
15
- commands: [],
16
- locale: 'en',
17
- alternateScreen: true,
18
- })
19
- ed.onSubmit = () => {}
20
- ed.onAttach = () => null
21
- ed.onClipboard = () => Promise.resolve(null)
22
- // Mouse-wheel scroll of the log is opt-in (config.ui.mouseScroll). Set it from
23
- // the env so the driver can exercise BOTH modes on a real pty.
24
- ed.setHeader?.({ mouseScroll: process.env.PROBE_MOUSE_SCROLL === '1' })
25
-
26
- // A non-empty history: a wheel-to-history bug would replace the empty buffer
27
- // with one of these lines.
28
- const snap = ed.store.getSnapshot()
29
- ed.store.set({
30
- edit: { ...snap.edit, history: ['OLD_COMMAND_ONE', 'OLD_COMMAND_TWO'] },
31
- scroll: 0,
32
- })
33
- // A tall log so there is something to scroll.
34
- for (let i = 0; i < 40; i++) ed.printAbove('log line ' + i)
35
-
36
- const report = (): void => {
37
- const s = ed.store.getSnapshot()
38
- fs.appendFileSync(
39
- LOG,
40
- JSON.stringify({ buf: s.edit.buf, scroll: s.scroll }) +
41
- String.fromCharCode(10),
42
- )
43
- }
44
- ed.store.subscribe(report)
45
- report()
46
-
47
- ed.start()
48
- setInterval(() => {}, 1 << 30)
@@ -1,20 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
- import path from 'node:path'
3
- import { Term } from '../test/vt100.mts'
4
- const NL = String.fromCharCode(10)
5
- const term = new Term(16, 60)
6
- const child = spawn(
7
- 'script',
8
- ['-qec', 'npx tsx scripts/tui-widgets.tsx', '/dev/null'],
9
- {
10
- cwd: path.resolve('.'),
11
- env: { ...process.env, COLUMNS: '60', LINES: '16' },
12
- },
13
- )
14
- child.stdout.on('data', (d) => term.write(d.toString('utf-8')))
15
- child.stderr.on('data', (d) => term.write(d.toString('utf-8')))
16
- setTimeout(() => {
17
- process.stdout.write(term.render() + NL)
18
- child.kill('SIGTERM')
19
- }, 5000)
20
- child.on('close', () => process.exit(0))