Files
orca/src/shared/zcode-missing-tui.ts
T
Neil 69839c253e feat(zcode): explain a ZCode build that has no terminal UI (#22730)
* feat(zcode): explain a ZCode build that has no terminal UI

ZCode ships one agent runtime behind two front ends. The desktop app bundles it
without `@zcode/tui`, because it draws its own window in Electron. Put that
bundle on PATH as `zcode` and it answers `--version`, runs `-p` headlessly, and
passes `zcode doctor` — so Orca detects it, launches it, and installs hooks
against it, all successfully. Only the interactive session fails, leaving a bare
Node stack trace in the pane that reads as a broken Orca integration.

Watch a freshly launched ZCode pane's first output and replace that with an
explanation: Orca's hooks are fine, this `zcode` just cannot open a session,
install one that ships the TUI.

The rule keys on Node's own module-resolution error rather than on the healthy
build's "TUI requires an interactive terminal." message, because ZCode localizes
the latter (`TUI 需要交互式终端。` in zh-CN) and matching it would miss every
non-English user. Node's error is not translated and names the package.

Scoped so it costs a healthy pane nothing: it runs only for a pane Orca launched
as `zcode`, and only over the first 8 KiB, because a module-resolution failure
happens before the runtime renders anything.

Evidence: `src/main/runtime/__fixtures__/zcode-missing-tui.txt`, a recorded PTY
capture of the desktop bundle refusing to start, per
docs/reference/agent-pty-transcript-capture.md.

Reported-by: JWu527

* refactor(zcode): ask the CLI if it can open a session instead of watching for the failure

The stream watcher this replaces never fired. Before/after screenshots were
identical and instrumentation showed the hook never ran, so the sidecar was
both misplaced and racing a failure that lands ~440ms after spawn.

Replace it with a direct question, answered once per run and cached.

Reading zai-org/ZCode shows why running it is the only way to ask, and why the
answer is unambiguous. `--version` and `doctor` are byte-identical in shape
between a build that has the terminal UI and one that does not, because the TUI
is only ever touched on the `tui` command path. There, `runTuiCommand` calls
`loadTuiRuntime()` before anything else, and `runTui` checks for a TTY only
after that module is already loaded. So with stdin at EOF:

  - no TUI  -> fails in the loader  -> Node's module-resolution error
  - has TUI -> loads, then declines -> "TUI requires an interactive terminal."

The module error is therefore present exactly when the terminal UI is absent.
All three shipping shapes land correctly: an npm/node-bundle install resolves
`@zcode/tui` as a real package (esbuild marks it external, so it is never
inlined), a SEA build always carries it as embedded assets, and the desktop
app's bundled runtime carries neither.

Verified against both real builds on this machine rather than a mock: the
desktop bundle answers `missing-tui`, a CLI built from source answers
`interactive`, and a command that does not exist answers `unknown` — the probe
fails open so an unrelated spawn failure never accuses a working CLI.

* feat(zcode): warn at launch when the installed zcode cannot open a session

Wires the capability probe to the one place a ZCode launch is first known:
terminal tab creation, which runs before the pane connects, so the explanation
reaches the screen alongside the failure rather than after it.

- main exposes the cached probe over `preflight:zcodeInteractiveCapability`,
  beside the other "what can the installed CLIs do" answers
- the web preload stub answers `unknown`, because a paired client has no
  business deciding anything about the host's CLI install
- the renderer notice is advisory: a probe that cannot run never blocks a launch

Verified in the running app against the real desktop bundle: creating a ZCode
workspace now shows "This ZCode build has no terminal UI" next to the stack
trace, where before the trace stood alone.
2026-09-25 02:28:40 -07:00

40 lines
2.0 KiB
TypeScript

/**
* Recognizing a `zcode` build that cannot open a session.
*
* ZCode ships one agent runtime behind two front ends. The desktop app bundles the runtime
* without `@zcode/tui`, because it draws its own window in Electron and would never call a
* terminal renderer. Put that bundle on PATH as `zcode` and it answers `--version`, runs
* `-p` headlessly, and passes `zcode doctor` — but dies the moment Orca asks it for an
* interactive session.
*
* That combination is why this is worth detecting rather than documenting alone: Orca's own
* auto-setup reports success (the hooks really are installed correctly), so the only visible
* failure is a Node stack trace inside the pane, and it reads as a broken Orca integration.
*
* Evidence: `src/main/runtime/__fixtures__/zcode-missing-tui.txt`, a recorded PTY capture of
* the desktop bundle refusing to start.
*/
// Why the module-resolution error and not the success path: a build that HAS the TUI but no
// TTY prints "TUI requires an interactive terminal.", which ZCode localizes (`TUI 需要交互式终端。`
// in zh-CN), so matching it would miss every non-English user. Node's own resolution failure
// is not localized and names the package directly.
//
// Both spellings are covered because the ESM loader reports `Cannot find package` while a CJS
// require path reports `Cannot find module`, and which one a given build hits depends on how
// it was bundled.
const ZCODE_MISSING_TUI_RE = /Cannot find (?:package|module) ['"]@zcode\/tui['"]/
/**
* What a `zcode` build can do when asked for a session.
*
* `unknown` is a real answer, not a failure: a probe that could not run says nothing about
* the build, and callers must not treat it as broken.
*/
export type ZCodeInteractiveCapability = 'interactive' | 'missing-tui' | 'unknown'
/** True when this output is ZCode reporting that its terminal UI is not installed. */
export function isZCodeMissingTuiOutput(output: string): boolean {
return ZCODE_MISSING_TUI_RE.test(output)
}