feat(terminal): add first-class Herdr runtime#9440
Conversation
472e22e to
f63197b
Compare
5898a52 to
3806903
Compare
📝 WalkthroughWalkthroughThe change adds managed Herdr sidecar packaging and validation, CLI and SSH transports, runtime reconciliation, event watching, and Herdr-backed PTY providers. Terminal backend settings, project activation state, persistence migrations, IPC schemas, and renderer controls are added. Terminal layout snapshots now flow through PTY spawning and headless split handling. Runtime output identifies the active backend, and daemon and SSH relay lifecycles route providers through Herdr. Unit, integration, persistence, UI, packaging, and end-to-end tests cover the new behavior. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ba2bebe9-43b4-48b5-8eba-cd2c6b745d07
📒 Files selected for processing (79)
config/electron-builder.config.cjsconfig/scripts/electron-builder-config.test.mjsresources/herdr/README.mdresources/herdr/darwin-arm64/.gitkeepresources/herdr/darwin-x64/.gitkeepresources/herdr/linux-arm64/.gitkeepresources/herdr/linux-x64/.gitkeepresources/herdr/win32-arm64/.gitkeepresources/herdr/win32-x64/.gitkeepsrc/cli/format.test.tssrc/cli/terminal-format.tssrc/main/codex-accounts/runtime-home-service.test.tssrc/main/codex-accounts/service.test.tssrc/main/daemon/daemon-init.tssrc/main/herdr/herdr-binary-source.test.tssrc/main/herdr/herdr-binary-source.tssrc/main/herdr/herdr-cli-host-transport.tssrc/main/herdr/herdr-created-root-bindings.tssrc/main/herdr/herdr-event-subscription.test.tssrc/main/herdr/herdr-event-subscription.tssrc/main/herdr/herdr-existing-tab-root.tssrc/main/herdr/herdr-managed-ssh-provisioner.tssrc/main/herdr/herdr-project-pty-target.test.tssrc/main/herdr/herdr-project-pty-target.tssrc/main/herdr/herdr-provider-factory.test.tssrc/main/herdr/herdr-provider-factory.tssrc/main/herdr/herdr-pty-codec.tssrc/main/herdr/herdr-pty-provider-base.tssrc/main/herdr/herdr-pty-provider.test.tssrc/main/herdr/herdr-pty-provider.tssrc/main/herdr/herdr-pty-queries.tssrc/main/herdr/herdr-pty-types.tssrc/main/herdr/herdr-real-runtime.integration.test.tssrc/main/herdr/herdr-reconcile-index.tssrc/main/herdr/herdr-runtime-contract.test.tssrc/main/herdr/herdr-runtime-contract.tssrc/main/herdr/herdr-runtime-graph.tssrc/main/herdr/herdr-runtime-manager.test.tssrc/main/herdr/herdr-runtime-manager.tssrc/main/herdr/herdr-session-watcher.tssrc/main/herdr/herdr-ssh-host-transport.tssrc/main/herdr/herdr-worktree-descriptor.tssrc/main/index.tssrc/main/ipc/pty.tssrc/main/ipc/repos.tssrc/main/persistence.test.tssrc/main/persistence.tssrc/main/providers/types.tssrc/main/runtime/claude-agent-teams-service.test.tssrc/main/runtime/headless-terminal-split-layout.test.tssrc/main/runtime/headless-terminal-split-layout.tssrc/main/runtime/orca-runtime.test.tssrc/main/runtime/orca-runtime.tssrc/main/runtime/rpc/methods/project-runtime-rpc-methods.tssrc/main/ssh/ssh-relay-session-terminal-error.test.tssrc/main/ssh/ssh-relay-session.test.tssrc/main/ssh/ssh-relay-session.tssrc/preload/api-types.tssrc/renderer/src/components/settings/ProjectTerminalBackendSetting.test.tsxsrc/renderer/src/components/settings/ProjectTerminalBackendSetting.tsxsrc/renderer/src/components/settings/RepositoryPane.tsxsrc/renderer/src/components/settings/TerminalBackendSection.test.tsxsrc/renderer/src/components/settings/TerminalBackendSection.tsxsrc/renderer/src/components/settings/TerminalPane.tsxsrc/renderer/src/components/settings/terminal-search.tssrc/renderer/src/components/terminal-pane/pty-connection.tssrc/renderer/src/components/terminal-pane/pty-transport-types.tssrc/renderer/src/components/terminal-pane/pty-transport.test.tssrc/renderer/src/components/terminal-pane/pty-transport.tssrc/shared/constants.tssrc/shared/herdr-session-identity.test.tssrc/shared/herdr-session-identity.tssrc/shared/host-setting-overrides.tssrc/shared/runtime-types.tssrc/shared/terminal-backend.test.tssrc/shared/terminal-backend.tssrc/shared/types.tstests/e2e/helpers/orca-restart.tstests/e2e/herdr-terminal-runtime.spec.ts
3806903 to
ef19540
Compare
|
Addressed the complete CodeRabbit review in The PR description now follows the repository template with Validation after the final rebase:
The complete |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/main/herdr/herdr-project-pty-target.test.ts (2)
22-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract a shared store-mock factory to remove duplication.
The
{ getSettings, getProjects, getWorkspaceSession }object is re-built nearly identically in six of the seven tests (also at lines 69-76, 108-115, 130-134, 148-152), varying only interminalBackendDefaultandterminalLayoutsByTabId. A small helper would reduce repetition and make future store-contract changes a one-line update.♻️ Proposed helper
+function createFloatingStore( + backend: 'orca' | 'herdr', + terminalLayoutsByTabId: Record<string, unknown> = {} +) { + return { + getSettings: () => ({ terminalBackendDefault: backend }), + getProjects: () => [], + getWorkspaceSession: () => ({ tabsByWorktree: {}, terminalLayoutsByTabId }) + } as unknown as Store +} + describe('Herdr PTY target resolution', () => { it('leaves floating terminals on Orca when Herdr is not selected', async () => { - const store = { - getSettings: () => ({ terminalBackendDefault: 'orca' }), - getProjects: () => [], - getWorkspaceSession: () => ({ tabsByWorktree: {}, terminalLayoutsByTabId: {} }) - } as unknown as Store + const store = createFloatingStore('orca')
51-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a short inline comment on the layout-merge rationale.
Tests 3 and 4 encode a "prefer the fuller/more-complete layout graph" heuristic between renderer-supplied and persisted
terminalLayout. This isn't obvious from the code itself (only from theittitle); a one-line comment near the assertion on why the renderer vs. persisted layout wins would help future readers.As per coding guidelines, "For non-obvious TypeScript code, add a concise comment explaining why the constraint exists, not how the code works."
Source: Coding guidelines
src/main/runtime/orca-runtime.ts (1)
4830-4830: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMake the fallback explicit for missing layouts.
Spreading
undefinedinside an object literal evaluates to an empty object without crashing, but explicitly providing a fallback clarifies the intent and protects against strict TypeScript configurations ifterminalLayoutsByTabIdis strictly optional.♻️ Proposed refactor
- const terminalLayoutsByTabId = { ...session.terminalLayoutsByTabId } + const terminalLayoutsByTabId = { ...(session.terminalLayoutsByTabId ?? {}) }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 7a5912ee-45d8-4068-a46c-2fb9f4a9f382
📒 Files selected for processing (88)
config/electron-builder.config.cjsconfig/localization-coverage-allowlist.jsonconfig/scripts/electron-builder-config.test.mjsresources/herdr/README.mdresources/herdr/darwin-arm64/.gitkeepresources/herdr/darwin-x64/.gitkeepresources/herdr/linux-arm64/.gitkeepresources/herdr/linux-x64/.gitkeepresources/herdr/win32-arm64/.gitkeepresources/herdr/win32-x64/.gitkeepsrc/cli/format.test.tssrc/cli/terminal-format.tssrc/main/codex-accounts/runtime-home-service.test.tssrc/main/codex-accounts/service.test.tssrc/main/daemon/daemon-init.tssrc/main/herdr/herdr-binary-source.test.tssrc/main/herdr/herdr-binary-source.tssrc/main/herdr/herdr-cli-host-transport.tssrc/main/herdr/herdr-created-root-bindings.tssrc/main/herdr/herdr-event-subscription.test.tssrc/main/herdr/herdr-event-subscription.tssrc/main/herdr/herdr-existing-tab-root.tssrc/main/herdr/herdr-host-transport.test.tssrc/main/herdr/herdr-managed-ssh-provisioner.test.tssrc/main/herdr/herdr-managed-ssh-provisioner.tssrc/main/herdr/herdr-project-pty-target.test.tssrc/main/herdr/herdr-project-pty-target.tssrc/main/herdr/herdr-provider-factory.test.tssrc/main/herdr/herdr-provider-factory.tssrc/main/herdr/herdr-pty-codec.tssrc/main/herdr/herdr-pty-provider-base.tssrc/main/herdr/herdr-pty-provider.test.tssrc/main/herdr/herdr-pty-provider.tssrc/main/herdr/herdr-pty-queries.tssrc/main/herdr/herdr-pty-types.tssrc/main/herdr/herdr-real-runtime.integration.test.tssrc/main/herdr/herdr-reconcile-index.tssrc/main/herdr/herdr-runtime-contract.test.tssrc/main/herdr/herdr-runtime-contract.tssrc/main/herdr/herdr-runtime-graph.tssrc/main/herdr/herdr-runtime-manager.test.tssrc/main/herdr/herdr-runtime-manager.tssrc/main/herdr/herdr-session-watcher.tssrc/main/herdr/herdr-ssh-host-transport.tssrc/main/herdr/herdr-worktree-descriptor.tssrc/main/index.tssrc/main/ipc/pty.tssrc/main/ipc/repos.tssrc/main/persistence.test.tssrc/main/persistence.tssrc/main/providers/types.tssrc/main/runtime/claude-agent-teams-service.test.tssrc/main/runtime/headless-terminal-split-layout.test.tssrc/main/runtime/headless-terminal-split-layout.tssrc/main/runtime/orca-runtime.test.tssrc/main/runtime/orca-runtime.tssrc/main/runtime/rpc/methods/project-runtime-rpc-methods.tssrc/main/ssh/ssh-relay-session-data-delivery.test.tssrc/main/ssh/ssh-relay-session-terminal-error.test.tssrc/main/ssh/ssh-relay-session.test.tssrc/main/ssh/ssh-relay-session.tssrc/preload/api-types.tssrc/renderer/src/components/settings/ProjectTerminalBackendSetting.test.tsxsrc/renderer/src/components/settings/ProjectTerminalBackendSetting.tsxsrc/renderer/src/components/settings/RepositoryPane.tsxsrc/renderer/src/components/settings/TerminalBackendSection.test.tsxsrc/renderer/src/components/settings/TerminalBackendSection.tsxsrc/renderer/src/components/settings/TerminalPane.tsxsrc/renderer/src/components/settings/terminal-search.tssrc/renderer/src/components/terminal-pane/pty-connection.tssrc/renderer/src/components/terminal-pane/pty-transport-types.tssrc/renderer/src/components/terminal-pane/pty-transport.test.tssrc/renderer/src/components/terminal-pane/pty-transport.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/shared/constants.tssrc/shared/herdr-session-identity.test.tssrc/shared/herdr-session-identity.tssrc/shared/host-setting-overrides.tssrc/shared/runtime-types.tssrc/shared/terminal-backend.test.tssrc/shared/terminal-backend.tssrc/shared/types.tstests/e2e/helpers/orca-restart.tstests/e2e/herdr-terminal-runtime.spec.ts
🚧 Files skipped from review as they are similar to previous changes (63)
- resources/herdr/linux-arm64/.gitkeep
- src/shared/runtime-types.ts
- resources/herdr/linux-x64/.gitkeep
- src/main/herdr/herdr-worktree-descriptor.ts
- src/main/herdr/herdr-runtime-contract.test.ts
- resources/herdr/darwin-x64/.gitkeep
- resources/herdr/win32-x64/.gitkeep
- src/main/herdr/herdr-runtime-graph.ts
- src/main/herdr/herdr-event-subscription.test.ts
- src/main/ssh/ssh-relay-session.test.ts
- config/scripts/electron-builder-config.test.mjs
- src/main/runtime/orca-runtime.test.ts
- src/renderer/src/components/settings/terminal-search.ts
- resources/herdr/darwin-arm64/.gitkeep
- src/main/herdr/herdr-pty-queries.ts
- src/shared/host-setting-overrides.ts
- src/main/herdr/herdr-created-root-bindings.ts
- src/renderer/src/components/settings/ProjectTerminalBackendSetting.tsx
- src/renderer/src/components/terminal-pane/pty-connection.ts
- src/renderer/src/components/terminal-pane/pty-transport-types.ts
- src/cli/terminal-format.ts
- src/main/index.ts
- src/main/codex-accounts/runtime-home-service.test.ts
- src/main/runtime/headless-terminal-split-layout.test.ts
- src/renderer/src/components/settings/TerminalPane.tsx
- config/electron-builder.config.cjs
- src/shared/herdr-session-identity.test.ts
- src/main/herdr/herdr-runtime-manager.test.ts
- src/main/ssh/ssh-relay-session-terminal-error.test.ts
- src/main/herdr/herdr-existing-tab-root.ts
- src/main/herdr/herdr-reconcile-index.ts
- src/main/providers/types.ts
- src/main/codex-accounts/service.test.ts
- src/main/herdr/herdr-pty-provider.test.ts
- src/renderer/src/components/terminal-pane/pty-transport.test.ts
- src/shared/constants.ts
- src/main/herdr/herdr-session-watcher.ts
- src/main/herdr/herdr-binary-source.test.ts
- src/main/runtime/rpc/methods/project-runtime-rpc-methods.ts
- src/main/herdr/herdr-pty-codec.ts
- src/shared/types.ts
- src/shared/terminal-backend.ts
- src/renderer/src/components/settings/RepositoryPane.tsx
- src/renderer/src/components/settings/TerminalBackendSection.test.tsx
- src/main/herdr/herdr-event-subscription.ts
- src/main/ipc/repos.ts
- src/main/herdr/herdr-binary-source.ts
- src/renderer/src/components/settings/ProjectTerminalBackendSetting.test.tsx
- src/cli/format.test.ts
- src/main/herdr/herdr-provider-factory.ts
- tests/e2e/helpers/orca-restart.ts
- src/main/herdr/herdr-runtime-contract.ts
- src/main/herdr/herdr-runtime-manager.ts
- src/main/herdr/herdr-pty-types.ts
- src/main/herdr/herdr-pty-provider-base.ts
- src/renderer/src/components/terminal-pane/pty-transport.ts
- src/shared/herdr-session-identity.ts
- src/main/ipc/pty.ts
- src/shared/terminal-backend.test.ts
- src/main/persistence.test.ts
- src/main/herdr/herdr-project-pty-target.ts
- src/main/persistence.ts
- src/main/herdr/herdr-pty-provider.ts
| const leafId = randomUUID() | ||
| const preAllocatedHandle = this.createPreAllocatedTerminalHandle() | ||
| const paneKey = makePaneKey(parentTabId, leafId) | ||
| const existingLayout = this.store?.getWorkspaceSession?.().terminalLayoutsByTabId?.[parentTabId] |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Fix missing optional chaining to prevent runtime crashes.
If getWorkspaceSession() returns undefined, accessing .terminalLayoutsByTabId without optional chaining will throw a TypeError: Cannot read properties of undefined.
🐛 Proposed fix
- const existingLayout = this.store?.getWorkspaceSession?.().terminalLayoutsByTabId?.[parentTabId]
+ const existingLayout = this.store?.getWorkspaceSession?.()?.terminalLayoutsByTabId?.[parentTabId]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const existingLayout = this.store?.getWorkspaceSession?.().terminalLayoutsByTabId?.[parentTabId] | |
| const existingLayout = this.store?.getWorkspaceSession?.()?.terminalLayoutsByTabId?.[parentTabId] |
Summary
Motivation
Today Orca's PTY daemon owns the lifetime of project terminals. That works while Orca is the only runtime involved, but it couples terminal processes to Orca's lifecycle: closing or restarting the desktop app cannot behave like detaching from a terminal multiplexer and later reattaching to the same live processes, layout, and scrollback.
Herdr already provides the durable session and terminal-control primitives needed to solve that problem. The goal of this integration is therefore deeper than launching Herdr inside an Orca terminal. Herdr becomes an optional terminal runtime behind Orca's existing terminal surfaces, so users keep the Orca UI while gaining multiplexer semantics:
Closing Orca then releases its terminal controllers without destroying the Herdr graph or its processes. Reopening the project reconciles the persisted Orca presentation state with the same Herdr resources and reattaches to them.
This is deliberately opt-in. Existing users and projects continue to use Orca's PTY runtime unless they select Herdr globally or for a specific project.
Design rationale
Keep Orca as the product surface and Herdr as terminal authority
Orca continues to own project/worktree metadata, unified-tab placement, and presentation-only state. Herdr owns the terminal graph and runtime state: workspace/tab/pane identity, PTY lifetime, layout, cwd, title, history, process information, and writable-controller ownership.
This boundary avoids two competing sources of truth. Terminal mutations initiated by Orca are routed through Herdr, while editor, diff, browser, simulator, and other non-terminal Orca tabs remain unchanged.
Use durable external identities instead of Herdr's internal IDs
Orca assigns every managed Herdr resource an
external_refwithowner: "orca"and a stable project/worktree/tab/pane identity. Reconciliation uses those references to find, create, or safely adopt resources.Herdr's internal numeric IDs can therefore change without breaking Orca persistence. Reordering tabs, restarting Orca, or reconnecting after an event gap does not create duplicate terminal graphs.
Integrate at the PTY provider boundary
The new
HerdrPtyProviderwraps Orca's existing provider and selects the backend from persisted project/host activation state. Non-Herdr targets continue through the existing provider. Herdr targets expose the same Orca PTY operations, including spawn/attach, input, resize, snapshots, clear, process inspection, signals, detach, and destructive close.This keeps renderer and CLI behavior consistent. Runtime terminal responses also expose
backend: "orca" | "herdr", making the active backend observable without relying on encoded PTY IDs.Treat detach and close as different lifecycle operations
That distinction is the core user-facing reason for the integration.
Fail explicitly instead of silently changing runtime
When a project is configured for Herdr, missing binaries, incompatible protocol capabilities, invalid managed manifests, checksum failures, transport timeouts, and SSH provisioning failures are surfaced as Herdr errors. Orca does not silently fall back to its legacy PTY provider, because doing so would split one project's terminal graph across two authorities and make persistence behavior unpredictable.
What this PR changes
Backend selection and compatibility
Orca/Herdrglobal terminal-backend setting.Inherit/Orca/Herdroverrides.orcaactivation, preserving current behavior after upgrade.Herdr graph reconciliation
Terminal control and persistence
herdr:PTY identities during workspace-session recovery.Local, packaged, and SSH execution
Users can select one of three Herdr binary sources:
Managed: a platform/architecture-specific sidecar shipped with Orca;System:herdrresolved from the execution host'sPATH;Custom: an explicit executable path.Managed distributions are verified before launch using their manifest identity, protocol version, required capabilities, license presence, and SHA-256. SSH hosts receive the matching verified distribution in a content-addressed directory. Host-level source overrides take precedence over the global setting.
This PR adds the packaging layout and verification path, but does not commit Herdr binaries. Release automation must stage the matching binary,
LICENSE, andmanifest.json. Shipping the managed sidecar remains subject to the Herdr AGPL/commercial distribution decision.End-to-end behavior covered
The real-runtime Playwright scenario builds on the corresponding Herdr branch and verifies the complete lifecycle:
backend: "herdr".Focused tests also cover backend resolution and migration blocking, legacy persistence backfill, project/worktree/tab/pane identity, graph reconciliation and adoption, split translation, event replay gaps and retries, provider routing and lifecycle, binary-source validation, SSH transport/provisioning, CLI backend reporting, and settings behavior.
Screenshots
No screenshot attached. The visual change is limited to terminal-backend controls in global Terminal settings and per-project settings. It uses the existing
SettingsRow,SettingsSegmentedControl, andInputprimitives without introducing a new layout or interaction pattern.Testing
pnpm lint(the complete command reaches the pre-existingsrc/main/ssh/ssh-relay-session.test.tsmax-lines violation: 803 lines against the 800-line limit)pnpm typecheckpnpm test(the full repository suite was not run)pnpm run build:desktopAdditional verification:
oxlint: no findingsoxfmt --check: passed for all changed filesgit diff --check upstream/main...HEADThe build runs under Node 22 in this checkout while the repository requests Node 24. It completed successfully. The development CLI installer also reported that it could not create
/usr/local/bin/orca-dev; that optional machine-level symlink did not fail the build.AI Review Report
The review covered every unresolved CodeRabbit thread and the integration's highest-risk boundaries:
terminal.closedevents instead of uncaught exceptions;nullwithout relying on structured-clone behavior forundefined;Cross-platform behavior was checked for macOS, Linux, and Windows. Path selection remains behind
path.joinand platform descriptors, managed binary names preserveherdr.exeon Windows, SSH provisioning uses the existing remote-platform command/path helpers, and no keyboard shortcut or platform-label behavior changed.Security Audit
null.spawn(file, args)rather than shell interpolation. SSH commands use Orca's existing escaping and platform-specific command builders.Follow-up remains necessary before distributing a managed Herdr binary: publish a capability-compatible upstream build, stage signed manifests/checksums, and complete the AGPL/commercial sidecar distribution review.
Notes
CodeRabbit's generic docstring-coverage warning is intentionally not addressed with boilerplate comments. Orca's repository guidance requires concise comments for non-obvious rationale, and the new modules already document those constraints where they matter.
The Herdr branch is currently awaiting that repository's first-time-contributor approval path before it can be proposed upstream.