Track split OSC metadata in headless terminals (#4783)

This commit is contained in:
Neil 2026-06-07 05:27:58 -07:00 committed by GitHub
parent 6caebb27f5
commit c91ea599d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 47 additions and 2 deletions

View File

@ -109,6 +109,24 @@ describe('HeadlessEmulator', () => {
expect(emulator.getSnapshot().cwd).toBe('/path/here')
})
it('tracks OSC-7 CWD across split PTY chunks', async () => {
emulator = new HeadlessEmulator({ cols: 80, rows: 24 })
await emulator.write('\x1b]7;file:///split')
await emulator.write('/project\x07')
expect(emulator.getSnapshot().cwd).toBe('/split/project')
})
it('tracks OSC-7 CWD when ESC and OSC marker arrive in separate chunks', async () => {
emulator = new HeadlessEmulator({ cols: 80, rows: 24 })
await emulator.write('\x1b')
await emulator.write(']7;file:///split-escape\x07')
expect(emulator.getSnapshot().cwd).toBe('/split-escape')
})
})
describe('OSC title tracking', () => {
@ -128,6 +146,15 @@ describe('HeadlessEmulator', () => {
expect(emulator.getSnapshot().lastTitle).toBe('Codex idle')
})
it('tracks OSC titles across split PTY chunks', async () => {
emulator = new HeadlessEmulator({ cols: 80, rows: 24 })
await emulator.write('\x1b]0;Codex work')
await emulator.write('ing\x07')
expect(emulator.getSnapshot().lastTitle).toBe('Codex working')
})
it('adopts title metadata seeded from an external serializer', () => {
emulator = new HeadlessEmulator({ cols: 80, rows: 24 })

View File

@ -15,6 +15,7 @@ export type HeadlessSnapshotOptions = {
}
const DEFAULT_SCROLLBACK = 5000
const OSC_SCAN_TAIL_LIMIT = 4096
// Why: PTY/SSH chunks can split a long combined DECSET before the final h/l.
// Keep parser state far beyond normal mode lists while still bounding memory.
const PRIVATE_MODE_SCAN_TAIL_LIMIT = 4096
@ -53,6 +54,7 @@ export class HeadlessEmulator {
private serializer: SerializeAddon
private cwd: string | null = null
private lastTitle: string | null = null
private oscScanTail = ''
private privateModeScanTail = ''
private mouseTrackingMode: MouseTrackingMode = 'none'
private sgrMouseMode = false
@ -88,8 +90,10 @@ export class HeadlessEmulator {
return Promise.resolve()
}
this.scanOsc7(data)
const lastTitle = extractLastOscTitle(data)
const oscInput = this.oscScanTail + data
this.oscScanTail = this.extractOscScanTail(oscInput)
this.scanOsc7(oscInput)
const lastTitle = extractLastOscTitle(oscInput)
if (lastTitle !== null) {
this.lastTitle = lastTitle
}
@ -165,6 +169,20 @@ export class HeadlessEmulator {
}
}
private extractOscScanTail(input: string): string {
const lastOsc = input.lastIndexOf('\x1b]')
const lastEscape = input.endsWith('\x1b') ? input.length - 1 : -1
const start = Math.max(lastOsc, lastEscape)
if (start === -1) {
return ''
}
const suffix = input.slice(start)
if (suffix.includes('\x07') || suffix.includes('\x1b\\')) {
return ''
}
return suffix.slice(-OSC_SCAN_TAIL_LIMIT)
}
private scanPrivateModes(data: string): void {
const input = this.privateModeScanTail + data
this.privateModeScanTail = this.extractPrivateModeScanTail(input)