mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
Root-cause follow-up to #15559, which stopped a CPR overtaking a deferred colour reply but left the deferral itself in place. Orca answers terminal queries by writing to the PTY master, which a line discipline in ECHO copies straight back out as junk on a cooked prompt (#12112). The guard was to withhold the write until an `stty` subprocess proved ECHO clear — and forking is what forced the decision to be async. Any deferral, however short, lets a reply written later in the same turn overtake this one, so the async probe was the bug's root cause. Read the bit synchronously instead. Linux and the BSDs redirect a master's mode ioctls to the slave, so a `tcgetattr` on the master fd node-pty already owns answers for the slave with no fork: measured 0.26us against 2403us for the subprocess. With a verdict available inline, a querying program that already cleared ECHO — every raw-mode prober, including the colour probe behind the `gh auth login` report — is answered in its own turn and can never be reordered. The deferral stays for the genuinely cooked case, and the ordering guarantee stays underneath it: hosts whose node-pty predates this patch get no sync probe and fall back to the deferred path, which mixed client/host versions make a live production path. Reply routing is all-or-nothing: a payload needing neither containment nor ordering stays on the host's own path, so a CPR answered during shell startup cannot pass the daemon's post-ready flush gate and splice into the buffered startup command. Native side is fail-safe: a kernel that did not redirect would answer from the master's own termios, whose ECHO defaults set, so the degraded verdict is "echoing" — never a false "quiet". The JS half ships in the pnpm patch while the binding needs a source build, so ORCA_REQUIRE_NODE_PTY_ECHO_STATE=1 makes CI fail rather than silently skip when it is handed an upstream prebuild. Co-authored-by: Brennan <brennanb2025@users.noreply.github.com>
46 lines
2.1 KiB
TypeScript
46 lines
2.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
nodePtyEchoStateRequirementViolation,
|
|
REQUIRE_NODE_PTY_ECHO_STATE_ENV_VAR,
|
|
resolveNodePtyEchoStateSupport
|
|
} from './node-pty-echo-state-requirement'
|
|
|
|
// The exports an upstream node-pty prebuild carries — the shape the guard exists to catch.
|
|
const PREBUILD_EXPORTS = { fork: () => {}, open: () => {}, resize: () => {}, process: () => {} }
|
|
const SOURCE_BUILD_EXPORTS = { ...PREBUILD_EXPORTS, echoState: () => 0 }
|
|
|
|
describe('node-pty echoState requirement', () => {
|
|
it('accepts a binding that actually exports echoState', () => {
|
|
const lookup = resolveNodePtyEchoStateSupport({ native: SOURCE_BUILD_EXPORTS }, 'darwin')
|
|
expect(lookup.available).toBe(true)
|
|
expect(lookup.nativeExports).toContain('echoState')
|
|
})
|
|
|
|
it('rejects an upstream prebuild whose JS patch would still hand back a probe', () => {
|
|
const lookup = resolveNodePtyEchoStateSupport({ native: PREBUILD_EXPORTS }, 'darwin')
|
|
expect(lookup.available).toBe(false)
|
|
expect(lookup.available ? '' : lookup.reason).toContain('fork, open, resize, process')
|
|
// The remedy must name the flag, because plain `pnpm rebuild node-pty` exits 0 on macOS.
|
|
expect(lookup.available ? '' : lookup.reason).toContain('npm_config_build_from_source=true')
|
|
})
|
|
|
|
it('rejects a module with no native binding at all', () => {
|
|
expect(resolveNodePtyEchoStateSupport({}, 'linux').available).toBe(false)
|
|
expect(resolveNodePtyEchoStateSupport(undefined, 'linux').available).toBe(false)
|
|
})
|
|
|
|
it('fails only when CI demanded the source build', () => {
|
|
const prebuild = resolveNodePtyEchoStateSupport({ native: PREBUILD_EXPORTS }, 'linux')
|
|
const sourceBuild = resolveNodePtyEchoStateSupport({ native: SOURCE_BUILD_EXPORTS }, 'linux')
|
|
expect(nodePtyEchoStateRequirementViolation(prebuild, {})).toBeNull()
|
|
expect(
|
|
nodePtyEchoStateRequirementViolation(prebuild, { [REQUIRE_NODE_PTY_ECHO_STATE_ENV_VAR]: '1' })
|
|
).toContain('no echoState')
|
|
expect(
|
|
nodePtyEchoStateRequirementViolation(sourceBuild, {
|
|
[REQUIRE_NODE_PTY_ECHO_STATE_ENV_VAR]: '1'
|
|
})
|
|
).toBeNull()
|
|
})
|
|
})
|