Architecture
The pieces of the app and how they reach each machine
A call goes from the views to HostStore (one per open session), to
HerdrClient (the Multiplexer), to the machine's Exec: ProcessExec on
This Mac, SSHExec and the relay on a remote machine. Requests and the event
stream go to herdr's API socket (on a remote machine through a socket the SSH
connection forwards); each streamed pane runs a terminal session.
Pieces
| Piece | Job |
|---|---|
HerdrKit (Swift package) | Exec, the Multiplexer interface and its herdr implementation: call(), the event stream, snapshot decoding, the split-tree rebuild, the remote command envelope and the relay; Files and Git, the plugin cards' file and git data over Exec; PluginManifest, PluginCalls (the plugin API's calls on a machine) and PluginPackages (plugin sources and installs); AgentRequest, the ovvi command's request and its mailbox runs |
ProcessExec | Process with pipes, for This Mac |
SSHExec | a remote machine: its ControlMaster, the askpass prompts and the relay that carries every run |
HostStore | the app's live model of one open session: snapshot, events, streams, seen state, git state, and which window controls it. One per session, shared by every window that shows it |
| Window state | one per window: its machine, its open pages and its selection (page, space, tabs, floating panes, sidebar and its width), restored with @SceneStorage; the keyboard card and zoom stay in memory |
PaneViewRegistry | owns terminal, browser and plugin views by terminal id, so scrolling never destroys them |
| Plugin cards | Registry (the browser's manifest and every package's), Plugin and its WebCard (one web view and the ovvi-plugin:// scheme), Bridge (the plugin API, one per card), Header, and the installs, builds, defaults and confirmations; the API's runtime and types in web/ovvi/, the default plugins and the template in plugins/builtin/, the extra ones (not in the app) in plugins/extra/ (Plugins) |
| Agent commands | AgentCommands in Ovvi/Agent/: the one parser and every command that the ovvi command asks for, run against the asking session's HostStore; the script itself, ovvi.sh (Agent control) |
| Views | title row, sidebar pages (space and agent rows), page dots and Archived Sessions, tab bar, tab page, cards, overview, floating panes, panels and dialogs |
Exec and Multiplexer are HerdrKit's only protocols; the app adds one,
ChatAdapter, for the chat view's agents. HerdrKit is a separate
package, so its single test target runs with swift test.
The Multiplexer interface
Multiplexer is the Swift
protocol that every multiplexer implements (D49). herdr is the first and only
implementation. Rex (superlogical.com) and tmux
are candidates (Future work). The views use only this
interface and the app's own model types, never herdr's JSON or argv.
| Part | What it gives the app | herdr implementation |
|---|---|---|
| Machine connection | one session on one machine, through that machine's Exec | herdr on each machine, Checks on connect, Health check |
| Snapshot | every space, tab, pane, label and split tree, in the app's model types | session.snapshot, Reconcile, Layout |
| Events | "the state changed, read the snapshot again" | events.subscribe, Events |
| Terminal streams | one pane's screen as ANSI frames; keys, resize, scroll and mouse back; control or watch | terminal session control/observe, Terminal plane |
| Actions | create, split, move, swap, close, rename, resize; read a pane's text | Writes are herdr calls |
| Agent status | per agent pane: working, needs you, idle, and a finish counter | agent_status, completion_seq, Status |
Agent status comes only from the interface, never from escape sequences in the app's terminal surfaces (D55, Status).
Multiplexer lives in HerdrKit/Multiplexer.swift next to HerdrClient,
its only conformer, and has only the methods the app uses. The machine
connection is not a method: HostStore makes the HerdrClient (the one place
that names herdr) with the path from the machine's Exec.herdr()
(herdr on each machine). The
model types (Snapshot, Session, HerdrError, ...) stay in HerdrKit until
a second multiplexer needs its own.
Simpler choice: the interface has the shape of herdr's features. A second implementation reshapes it where that multiplexer differs.