mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
fix(tui): harden ansi sanitizers for dangling CSI
Strip incomplete CSI prefixes before rendering, remove carriage returns from sanitized output, and add regression tests to prevent escape-sequence recomposition across message boundaries.
This commit is contained in:
parent
9b2d58159c
commit
7e1788db5d
2 changed files with 24 additions and 2 deletions
|
|
@ -97,12 +97,24 @@ describe('ANSI sanitizers', () => {
|
||||||
expect(stripAnsi(sample)).toBe('ABCD')
|
expect(stripAnsi(sample)).toBe('ABCD')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('strips incomplete CSI prefixes and carriage returns', () => {
|
||||||
|
const sample = `A${ESC}[31mB${ESC}[12;${ESC}[CD\rE`
|
||||||
|
|
||||||
|
expect(stripAnsi(sample)).toBe('ABDE')
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps SGR color spans but removes cursor controls for Ansi rendering', () => {
|
it('keeps SGR color spans but removes cursor controls for Ansi rendering', () => {
|
||||||
const sample = `A${ESC}[31mB${ESC}[39m${ESC}[2J${ESC}]0;title${BEL}${ESC}[?25lC`
|
const sample = `A${ESC}[31mB${ESC}[39m${ESC}[2J${ESC}]0;title${BEL}${ESC}[?25lC`
|
||||||
|
|
||||||
expect(sanitizeAnsiForRender(sample)).toBe(`A${ESC}[31mB${ESC}[39mC`)
|
expect(sanitizeAnsiForRender(sample)).toBe(`A${ESC}[31mB${ESC}[39mC`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps valid SGR while removing dangling CSI and carriage returns', () => {
|
||||||
|
const sample = `A${ESC}[31mB${ESC}[12;${ESC}[39mC\rD`
|
||||||
|
|
||||||
|
expect(sanitizeAnsiForRender(sample)).toBe(`A${ESC}[31mB${ESC}[39mCD`)
|
||||||
|
})
|
||||||
|
|
||||||
it('detects non-CSI escape prefixes too', () => {
|
it('detects non-CSI escape prefixes too', () => {
|
||||||
expect(hasAnsi(`ok${ESC}Ppayload${ESC}\\`)).toBe(true)
|
expect(hasAnsi(`ok${ESC}Ppayload${ESC}\\`)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,30 @@ const ESC = String.fromCharCode(27)
|
||||||
const BEL = String.fromCharCode(7)
|
const BEL = String.fromCharCode(7)
|
||||||
const ANSI_CSI_RE = new RegExp(`${ESC}\\[[0-?]*[ -/]*[@-~]`, 'g')
|
const ANSI_CSI_RE = new RegExp(`${ESC}\\[[0-?]*[ -/]*[@-~]`, 'g')
|
||||||
const ANSI_CSI_WITH_CMD_RE = new RegExp(`${ESC}\\[[0-?]*[ -/]*([@-~])`, 'g')
|
const ANSI_CSI_WITH_CMD_RE = new RegExp(`${ESC}\\[[0-?]*[ -/]*([@-~])`, 'g')
|
||||||
|
const ANSI_INCOMPLETE_CSI_RE = new RegExp(`${ESC}\\[[0-?]*[ -/]*(?=${ESC}|\\n|$)`, 'g')
|
||||||
const ANSI_OSC_RE = new RegExp(`${ESC}\\][\\s\\S]*?(?:${BEL}|${ESC}\\\\)`, 'g')
|
const ANSI_OSC_RE = new RegExp(`${ESC}\\][\\s\\S]*?(?:${BEL}|${ESC}\\\\)`, 'g')
|
||||||
const ANSI_STRING_RE = new RegExp(`${ESC}[PX^_][\\s\\S]*?(?:${BEL}|${ESC}\\\\)`, 'g')
|
const ANSI_STRING_RE = new RegExp(`${ESC}[PX^_][\\s\\S]*?(?:${BEL}|${ESC}\\\\)`, 'g')
|
||||||
const ANSI_STRAY_ESC_RE = new RegExp(`${ESC}(?!\\[)[\\s\\S]?`, 'g')
|
const ANSI_STRAY_ESC_RE = new RegExp(`${ESC}(?!\\[)[\\s\\S]?`, 'g')
|
||||||
const CONTROL_RE = /[\x00-\x08\x0B\x0C\x0E-\x1A\x1C-\x1F\x7F]/g
|
const CONTROL_RE = /[\x00-\x08\x0B\x0C\x0D\x0E-\x1A\x1C-\x1F\x7F]/g
|
||||||
const WS_RE = /\s+/g
|
const WS_RE = /\s+/g
|
||||||
|
|
||||||
export const stripAnsi = (s: string) =>
|
export const stripAnsi = (s: string) =>
|
||||||
s.replace(ANSI_OSC_RE, '').replace(ANSI_STRING_RE, '').replace(ANSI_CSI_RE, '').replace(ANSI_STRAY_ESC_RE, '').replace(CONTROL_RE, '')
|
s
|
||||||
|
.replace(ANSI_OSC_RE, '')
|
||||||
|
.replace(ANSI_STRING_RE, '')
|
||||||
|
.replace(ANSI_INCOMPLETE_CSI_RE, '')
|
||||||
|
.replace(ANSI_CSI_RE, '')
|
||||||
|
.replace(ANSI_INCOMPLETE_CSI_RE, '')
|
||||||
|
.replace(ANSI_STRAY_ESC_RE, '')
|
||||||
|
.replace(CONTROL_RE, '')
|
||||||
|
|
||||||
export const sanitizeAnsiForRender = (s: string) =>
|
export const sanitizeAnsiForRender = (s: string) =>
|
||||||
s
|
s
|
||||||
.replace(ANSI_OSC_RE, '')
|
.replace(ANSI_OSC_RE, '')
|
||||||
.replace(ANSI_STRING_RE, '')
|
.replace(ANSI_STRING_RE, '')
|
||||||
|
.replace(ANSI_INCOMPLETE_CSI_RE, '')
|
||||||
.replace(ANSI_CSI_WITH_CMD_RE, (seq, cmd: string) => (cmd === 'm' ? seq : ''))
|
.replace(ANSI_CSI_WITH_CMD_RE, (seq, cmd: string) => (cmd === 'm' ? seq : ''))
|
||||||
|
.replace(ANSI_INCOMPLETE_CSI_RE, '')
|
||||||
.replace(ANSI_STRAY_ESC_RE, '')
|
.replace(ANSI_STRAY_ESC_RE, '')
|
||||||
.replace(CONTROL_RE, '')
|
.replace(CONTROL_RE, '')
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue