mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
fix(terminal): make startup scanning chunk-invariant
This commit is contained in:
@@ -11,6 +11,10 @@ export type ShellReadyScanResult = {
|
||||
postMarkerBytesObserved: boolean
|
||||
}
|
||||
|
||||
type ShellReadyBoundaryScanResult = ShellReadyScanResult & {
|
||||
postMarkerOutputIndex: number | null
|
||||
}
|
||||
|
||||
export function createShellReadyScanState(): ShellReadyScanState {
|
||||
return { matchPos: 0, heldBytes: '' }
|
||||
}
|
||||
@@ -22,7 +26,10 @@ export function drainShellReadyHeldBytes(state: ShellReadyScanState): string {
|
||||
return heldBytes
|
||||
}
|
||||
|
||||
export function scanForShellReady(state: ShellReadyScanState, data: string): ShellReadyScanResult {
|
||||
export function scanForShellReadyBoundary(
|
||||
state: ShellReadyScanState,
|
||||
data: string
|
||||
): ShellReadyBoundaryScanResult {
|
||||
let output = ''
|
||||
|
||||
for (let i = 0; i < data.length; i += 1) {
|
||||
@@ -49,7 +56,8 @@ export function scanForShellReady(state: ShellReadyScanState, data: string): She
|
||||
return {
|
||||
output: output + remaining,
|
||||
matched: true,
|
||||
postMarkerBytesObserved: remaining.length > 0
|
||||
postMarkerBytesObserved: remaining.length > 0,
|
||||
postMarkerOutputIndex: output.length
|
||||
}
|
||||
} else {
|
||||
output += state.heldBytes
|
||||
@@ -64,5 +72,15 @@ export function scanForShellReady(state: ShellReadyScanState, data: string): She
|
||||
}
|
||||
}
|
||||
|
||||
return { output, matched: false, postMarkerBytesObserved: false }
|
||||
return {
|
||||
output,
|
||||
matched: false,
|
||||
postMarkerBytesObserved: false,
|
||||
postMarkerOutputIndex: null
|
||||
}
|
||||
}
|
||||
|
||||
export function scanForShellReady(state: ShellReadyScanState, data: string): ShellReadyScanResult {
|
||||
const { postMarkerOutputIndex: _, ...result } = scanForShellReadyBoundary(state, data)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -6,6 +6,42 @@ import {
|
||||
} from './shell-startup-output-scanner'
|
||||
|
||||
const READY_MARKER = '\x1b]777;orca-shell-ready\x07'
|
||||
const IDENTITY_MARKER = '\x1b]777;orca-shell-start:12345\x07'
|
||||
|
||||
function scanChunks(chunks: string[]): {
|
||||
output: string
|
||||
shellPid: number | null
|
||||
ready: boolean
|
||||
postMarkerBytesObserved: boolean
|
||||
} {
|
||||
const state = createShellStartupOutputScanState()
|
||||
let output = ''
|
||||
let shellPid: number | null = null
|
||||
let ready = false
|
||||
let postMarkerBytesObserved = false
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const scanned = scanShellStartupOutput(state, chunk)
|
||||
output += scanned.output
|
||||
shellPid ??= scanned.shellPid
|
||||
ready ||= scanned.ready
|
||||
postMarkerBytesObserved ||= scanned.postMarkerBytesObserved
|
||||
}
|
||||
return { output, shellPid, ready, postMarkerBytesObserved }
|
||||
}
|
||||
|
||||
function expectEverySplitToProduce(
|
||||
input: string,
|
||||
expected: Omit<ReturnType<typeof scanChunks>, 'postMarkerBytesObserved'>
|
||||
): void {
|
||||
for (let split = 0; split <= input.length; split += 1) {
|
||||
const { postMarkerBytesObserved: _, ...scanned } = scanChunks([
|
||||
input.slice(0, split),
|
||||
input.slice(split)
|
||||
])
|
||||
expect(scanned, `split ${split}`).toEqual(expected)
|
||||
}
|
||||
}
|
||||
|
||||
describe('shell startup output scanner', () => {
|
||||
it.each([
|
||||
@@ -22,12 +58,30 @@ describe('shell startup output scanner', () => {
|
||||
expect(output).toBe('\x1b[?2004hfish> ')
|
||||
})
|
||||
|
||||
it('preserves every post-ready byte at every chunk boundary', () => {
|
||||
expectEverySplitToProduce(`${READY_MARKER}${IDENTITY_MARKER}prompt`, {
|
||||
output: `${IDENTITY_MARKER}prompt`,
|
||||
shellPid: null,
|
||||
ready: true
|
||||
})
|
||||
expectEverySplitToProduce(`${READY_MARKER}\x1b[?2004hfish> `, {
|
||||
output: '\x1b[?2004hfish> ',
|
||||
shellPid: null,
|
||||
ready: true
|
||||
})
|
||||
})
|
||||
|
||||
it('strips pre-ready markers at every chunk boundary', () => {
|
||||
expectEverySplitToProduce(`${IDENTITY_MARKER}${READY_MARKER}prompt`, {
|
||||
output: 'prompt',
|
||||
shellPid: 12345,
|
||||
ready: true
|
||||
})
|
||||
})
|
||||
|
||||
it('strips identity and readiness markers from one chunk', () => {
|
||||
const state = createShellStartupOutputScanState()
|
||||
const scanned = scanShellStartupOutput(
|
||||
state,
|
||||
`\x1b]777;orca-shell-start:12345\x07${READY_MARKER}prompt`
|
||||
)
|
||||
const scanned = scanShellStartupOutput(state, `${IDENTITY_MARKER}${READY_MARKER}prompt`)
|
||||
|
||||
expect(scanned).toEqual({
|
||||
output: 'prompt',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
createShellReadyScanState,
|
||||
drainShellReadyHeldBytes,
|
||||
scanForShellReady,
|
||||
scanForShellReadyBoundary,
|
||||
type ShellReadyScanState
|
||||
} from './shell-ready-marker-scanner'
|
||||
import {
|
||||
@@ -34,30 +34,30 @@ export function scanShellStartupOutput(
|
||||
state: ShellStartupOutputScanState,
|
||||
data: string
|
||||
): ShellStartupOutputScanResult {
|
||||
const readiness = scanForShellReadyBoundary(state.ready, data)
|
||||
const postMarkerOutputIndex = readiness.postMarkerOutputIndex ?? readiness.output.length
|
||||
const identityInput = readiness.output.slice(0, postMarkerOutputIndex)
|
||||
let shellPid: number | null = null
|
||||
let output = identityInput
|
||||
if (state.identity) {
|
||||
const identity = scanForShellStartupIdentity(state.identity, data)
|
||||
data = identity.output
|
||||
const identity = scanForShellStartupIdentity(state.identity, identityInput)
|
||||
output = identity.output
|
||||
shellPid = identity.shellPid
|
||||
if (shellPid) {
|
||||
state.identity = null
|
||||
}
|
||||
}
|
||||
|
||||
const readiness = scanForShellReady(state.ready, data)
|
||||
let output = readiness.output
|
||||
let postMarkerBytesObserved = readiness.postMarkerBytesObserved
|
||||
if (readiness.matched && state.identity) {
|
||||
const heldOutput = drainShellStartupIdentityHeldBytes(state.identity)
|
||||
output += heldOutput
|
||||
postMarkerBytesObserved ||= heldOutput.length > 0
|
||||
output += drainShellStartupIdentityHeldBytes(state.identity)
|
||||
state.identity = null
|
||||
}
|
||||
output += readiness.output.slice(postMarkerOutputIndex)
|
||||
return {
|
||||
output,
|
||||
shellPid,
|
||||
ready: readiness.matched,
|
||||
postMarkerBytesObserved
|
||||
postMarkerBytesObserved: readiness.postMarkerBytesObserved
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function drainShellStartupOutputScanState(state: ShellStartupOutputScanSt
|
||||
let output = state.identity ? drainShellStartupIdentityHeldBytes(state.identity) : ''
|
||||
state.identity = null
|
||||
if (output) {
|
||||
output = scanForShellReady(state.ready, output).output
|
||||
output = scanForShellReadyBoundary(state.ready, output).output
|
||||
}
|
||||
return output + drainShellReadyHeldBytes(state.ready)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user