b7fea83ed6
Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
141 lines
5.1 KiB
Markdown
141 lines
5.1 KiB
Markdown
# Tauri Pi Client Architecture
|
||
|
||
## Purpose
|
||
|
||
Replace the Noctalia panel with a testable, status-bar-agnostic desktop Pi
|
||
client. Noctalia becomes an optional status indicator and launcher only.
|
||
|
||
## Decision
|
||
|
||
Use a **Tauri v2 desktop application** with a web frontend and a small Rust
|
||
host. The bridge remains the only owner of Pi processes, sessions, recovery,
|
||
and the owner-only Unix socket.
|
||
|
||
Tauri is appropriate because its frontend talks to a native host through typed
|
||
asynchronous IPC, and its WebDriver support gives the client a real E2E test
|
||
surface. Reference: <https://v2.tauri.app/concept/architecture/> and
|
||
<https://v2.tauri.app/develop/tests/webdriver/>.
|
||
|
||
## Boundaries
|
||
|
||
```text
|
||
Pi Status Bridge systemd service
|
||
└─ Unix JSONL socket: state, events, commands
|
||
└─ Tauri Rust host
|
||
├─ validates/serializes bridge requests
|
||
├─ owns subscribe connections and emits UI events
|
||
└─ exposes typed Tauri commands
|
||
└─ React frontend: desktop UI
|
||
|
||
Optional Noctalia compatibility shim
|
||
├─ reads compact state through existing client/bridge protocol
|
||
└─ launches or focuses `pi-status-ui --toggle`
|
||
```
|
||
|
||
### Security
|
||
|
||
- The frontend cannot run shell commands or open sockets.
|
||
- Only the Tauri Rust host reads the per-user Unix socket.
|
||
- The UI never receives or stores bridge lock tokens.
|
||
- No TCP listener is introduced.
|
||
- Pi remains authoritative for model, thinking, queues, tool approvals, and
|
||
extension request IDs.
|
||
|
||
## Tauri host API
|
||
|
||
The host maps the existing JSONL protocol into typed commands. It must reject
|
||
unknown operations and preserve agent scoping.
|
||
|
||
| Command | Bridge operation | Result |
|
||
| --- | --- | --- |
|
||
| `listAgents` | `list_agents` | current agent summaries |
|
||
| `selectWorktree` | `select_agent` | explicitly selected agent |
|
||
| `loadAgent` | state, transcript, commands, models | one snapshot |
|
||
| `submitPrompt` | `submit_prompt` | Pi-owned prompt/follow-up queueing |
|
||
| `abort`, `retry`, `restart` | same | command response |
|
||
| `setModel`, `setThinkingLevel` | same | command response |
|
||
| `respondToExtension` | `extension_response` | command response |
|
||
| `subscribeAgent` | `subscribe` | emits `bridge-event` to this window |
|
||
|
||
The Rust host owns reconnection and cursor replay. Its view-model event stream
|
||
is normalized to:
|
||
|
||
```ts
|
||
type BridgeEvent = {
|
||
agentId: string;
|
||
seq: number;
|
||
kind: "state" | "message" | "tool" | "queue" | "extension" | "recovery";
|
||
payload: unknown;
|
||
};
|
||
```
|
||
|
||
## Frontend
|
||
|
||
### Window behavior
|
||
|
||
- One persistent window; close hides it, quit is explicit.
|
||
- `--show`, `--hide`, and `--toggle` are routed to the existing instance using
|
||
Tauri’s single-instance capability.
|
||
- The optional Noctalia widget runs `pi-status-ui --toggle`; it never renders
|
||
the primary UI or handles bridge commands.
|
||
|
||
### Screens
|
||
|
||
1. **Conversation** (default): selected agent, streaming/queue/tool status,
|
||
transcript, extension dialog, and one composer.
|
||
2. **Agents**: Home plus remembered explicit folders. Selecting a remembered
|
||
folder invokes bridge `select_agent`; no folder starts merely because it is
|
||
remembered.
|
||
3. **Session settings**: model, thinking level, session metadata, retry, and
|
||
restart.
|
||
|
||
### Composer invariant
|
||
|
||
The frontend has one normal submit action. It invokes `submit_prompt`; it does
|
||
not decide between prompt, steering, or follow-up. Slash-command selection
|
||
inserts text only.
|
||
|
||
### Extension invariant
|
||
|
||
Render Pi `select`, `confirm`, `input`, and `editor` requests. Present any
|
||
other method as unsupported without sending an approval.
|
||
|
||
## Persistence
|
||
|
||
| Data | Owner |
|
||
| --- | --- |
|
||
| Pi session/recovery | bridge |
|
||
| remembered worktree paths + last selected path | UI app data |
|
||
| bridge agent IDs | memory only; invalid after bridge restart |
|
||
| compact launcher state | Noctalia only, disposable |
|
||
|
||
At startup the UI resolves its stored path through `select_agent`; it never
|
||
persists or trusts a bridge agent ID.
|
||
|
||
## Implementation sequence
|
||
|
||
1. Add `ui/` (React/Vite) and `src-tauri/`; leave current Node bridge intact.
|
||
2. Implement Rust JSONL client plus typed command/snapshot contract with unit
|
||
tests against a temporary Unix socket server.
|
||
3. Implement the conversation store and pure reducer tests for event replay.
|
||
4. Build the conversation screen; add WebDriver E2E for initial load,
|
||
composer submission, and busy follow-up behavior.
|
||
5. Build agent/settings/extension screens and tests.
|
||
6. Replace Noctalia panel entry with a small launcher compatibility shim; retain
|
||
its compact state indicator.
|
||
7. Remove the primary Luau panel only after client E2E and manual desktop checks
|
||
pass.
|
||
|
||
## Acceptance checks
|
||
|
||
- Launching `pi-status-ui --toggle` opens/focuses one window.
|
||
- Restarting the bridge recovers to Home or a saved worktree path; it never
|
||
issues a command to a stale agent ID.
|
||
- A normal submit, busy submit, model change, and extension response route
|
||
through typed host commands only.
|
||
- A custom extension UI cannot be approved from the client.
|
||
- Noctalia can be disabled/uninstalled while the desktop client remains fully
|
||
usable.
|
||
- Rust host tests, frontend unit tests, WebDriver E2E, bridge tests, and a
|
||
manual desktop run pass.
|