mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
99 lines
2.2 KiB
JavaScript
99 lines
2.2 KiB
JavaScript
const STOP_KEY = 0x1d
|
|
|
|
export async function recordPtyTranscript({ spawn, sink, stdin, stdout, options }) {
|
|
let recording = true
|
|
let term
|
|
let stopping = false
|
|
const timers = []
|
|
const wasRaw = stdin.isTTY === true && stdin.isRaw === true
|
|
const stop = () => {
|
|
if (stopping) {
|
|
return
|
|
}
|
|
stopping = true
|
|
recording = false
|
|
try {
|
|
term.kill()
|
|
} catch {
|
|
// A concurrent physical exit can make the native handle unavailable.
|
|
}
|
|
}
|
|
const onInput = (chunk) => {
|
|
if (chunk.includes(STOP_KEY)) {
|
|
stop()
|
|
} else if (!stopping) {
|
|
term.write(chunk)
|
|
}
|
|
}
|
|
let sinkFailure
|
|
const onSinkError = (error) => {
|
|
sinkFailure = error
|
|
if (term) {
|
|
stop()
|
|
}
|
|
}
|
|
sink.on('error', onSinkError)
|
|
let exitCode
|
|
let captureFailure
|
|
try {
|
|
term = await spawn((bytes) => {
|
|
if (recording) {
|
|
sink.write(bytes)
|
|
}
|
|
stdout.write(bytes)
|
|
})
|
|
const exit = new Promise((resolve) => term.onExit(({ exitCode }) => resolve(exitCode ?? 0)))
|
|
await term.waitForSpawn?.()
|
|
if (sinkFailure) {
|
|
throw sinkFailure
|
|
}
|
|
if (stdin.isTTY) {
|
|
stdin.setRawMode(true)
|
|
}
|
|
stdin.on('data', onInput)
|
|
stdin.resume()
|
|
for (const send of options.sends) {
|
|
timers.push(
|
|
setTimeout(() => {
|
|
if (!stopping) {
|
|
term.write(send.text)
|
|
}
|
|
}, send.atMs)
|
|
)
|
|
}
|
|
if (options.duration !== null) {
|
|
timers.push(setTimeout(stop, options.duration * 1000))
|
|
}
|
|
exitCode = await exit
|
|
} catch (error) {
|
|
captureFailure = error
|
|
} finally {
|
|
recording = false
|
|
for (const timer of timers) {
|
|
clearTimeout(timer)
|
|
}
|
|
stdin.off('data', onInput)
|
|
if (stdin.isTTY) {
|
|
stdin.setRawMode(wasRaw)
|
|
}
|
|
stdin.pause()
|
|
try {
|
|
term?.destroy()
|
|
} catch (error) {
|
|
captureFailure ??= error
|
|
}
|
|
await new Promise((resolve) => {
|
|
if (sink.destroyed) {
|
|
return resolve()
|
|
}
|
|
sink.once('error', resolve)
|
|
sink.end(resolve)
|
|
})
|
|
sink.off('error', onSinkError)
|
|
}
|
|
if (captureFailure || sinkFailure) {
|
|
throw captureFailure ?? sinkFailure
|
|
}
|
|
return exitCode
|
|
}
|