hermes-agent/apps/desktop/src/lib/composer-input-sanitize.ts
ethernet 93808ca6a7 fix(desktop): resolve eslint errors in composer-input-sanitize.ts
The hoisted shared eslint config catches pre-existing no-useless-escape
and no-control-regex errors in apps/desktop that were only fixed in web/
in the previous commit.

- no-useless-escape: remove unnecessary \) escapes inside character classes
- no-control-regex: add eslint-disable-next-line comments for intentional
  \x1b terminal escape byte matching (same pattern as web/src/lib/pty-mobile-input.ts)
2026-07-16 01:42:02 +05:30

74 lines
2.2 KiB
TypeScript

/**
* Strip terminal bracketed-paste leaks and repeated artifact tails from composer
* text before it is shown in the UI or sent to the gateway.
*
* Mirrors hermes_cli/input_sanitize.py (CLI/TUI gateway defensive path).
*/
const BRACKETED_PASTE_BOUNDARY_START = /(^|[\s\n>:\])])\[200~/g
const BRACKETED_PASTE_BOUNDARY_END = /\[201~(?=$|[\s\n<[():;.,!?])/g
const BRACKETED_PASTE_DEGRADED_START = /(^|[\s\n>:\])])00~/g
const BRACKETED_PASTE_DEGRADED_END = /01~(?=$|[\s\n<[():;.,!?])/g
const DESKTOP_PASTE_ARTIFACT = '~[[e'
/** Strip leaked bracketed-paste wrapper markers from user-visible text. */
export function stripLeakedBracketedPasteWrappers(text: string): string {
if (!text) {
return text
}
let cleaned = text
// eslint-disable-next-line no-control-regex -- terminal data may contain control chars
.replace(/\x1b\[200~/g, '')
// eslint-disable-next-line no-control-regex -- terminal data may contain control chars
.replace(/\x1b\[201~/g, '')
.replace(/\^\[\[200~/g, '')
.replace(/\^\[\[201~/g, '')
cleaned = cleaned.replace(BRACKETED_PASTE_BOUNDARY_START, '$1')
cleaned = cleaned.replace(BRACKETED_PASTE_BOUNDARY_END, '')
cleaned = cleaned.replace(BRACKETED_PASTE_DEGRADED_START, '$1')
cleaned = cleaned.replace(BRACKETED_PASTE_DEGRADED_END, '')
return cleaned
}
/** Drop a trailing run of the desktop ~[[e corruption signature (#62557). */
export function collapseRepeatedInputArtifacts(text: string, minRepeats = 4): string {
if (!text) {
return text
}
const marker = DESKTOP_PASTE_ARTIFACT
let index = text.length
let repeatCount = 0
while (index >= marker.length && text.slice(index - marker.length, index) === marker) {
repeatCount += 1
index -= marker.length
}
if (repeatCount < minRepeats) {
return text
}
let start = index
if (start >= 2 && text.slice(start - 2, start) === '[e') {
start -= 2
} else if (start >= 1 && text[start - 1] === '[') {
start -= 1
}
return text.slice(0, start)
}
/** Normalize composer text before submit or draft persistence. */
export function sanitizeComposerInput(text: string): string {
if (!text) {
return text
}
return collapseRepeatedInputArtifacts(stripLeakedBracketedPasteWrappers(text))
}