feat(bridge): establish local pi status bridge
Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# Full Pi Panel Specification
|
||||
|
||||
## Reason for existence
|
||||
|
||||
Replace the compact Noctalia status widget with a feature-equivalent local Pi
|
||||
client panel. The bar remains a compact launcher and status indicator; the
|
||||
panel is the single interaction surface.
|
||||
|
||||
## User flow
|
||||
|
||||
1. The user session starts one bridge-managed Home agent for `$HOME`.
|
||||
2. Clicking the bar opens the **Output** view for the selected agent. Home is
|
||||
selected by default.
|
||||
3. Output shows transcript/history, live streaming/tool/queue state, extension
|
||||
requests, and one composer anchored at the bottom.
|
||||
4. The composer mirrors the confirmed TUI workflow: submit a normal prompt;
|
||||
when Pi is busy, Pi queues it as a follow-up. The panel never asks the user
|
||||
to choose prompt versus steer versus follow-up for ordinary submission.
|
||||
5. **Settings** is the only location for folder management. Adding an absolute
|
||||
folder explicitly creates/resumes its own agent, persists it, and selects it.
|
||||
6. The user may switch among remembered agents from Settings. Previously added
|
||||
folders do not start at login unless selected; Home is always running.
|
||||
|
||||
## Views
|
||||
|
||||
### Output (default)
|
||||
|
||||
- Header: selected folder, agent health, streaming/queue/recovery status.
|
||||
- Scrollable transcript: user, assistant, tool result, and visible custom
|
||||
messages. Thinking content follows Pi visibility policy; it is not exposed by
|
||||
default merely because the panel exists.
|
||||
- Tool activity: current tool name/progress while active and tool results in
|
||||
history after completion.
|
||||
- Composer: one input and one submit action. It accepts normal text, Pi command
|
||||
syntax, prompt templates, and skills exactly as Pi RPC `prompt` accepts them.
|
||||
- Suggestions: when composer input begins with `/`, show Pi-provided
|
||||
`get_commands` entries. Selecting one inserts it; it does not execute until
|
||||
submitted.
|
||||
- Extension requests: select, confirm, input, and editor map to native panel
|
||||
controls and return the exact request ID/response shape. Unsupported custom
|
||||
TUI UI renders an explicit unsupported state and never approves anything.
|
||||
|
||||
### Settings
|
||||
|
||||
- Agent list: Home plus remembered explicit folders, each with state.
|
||||
- Folder input: requires an absolute path. Adding it invokes `select_agent`;
|
||||
bridge canonicalization remains authoritative.
|
||||
- Session controls: current session metadata, retry/restart, and agent health.
|
||||
- Pi controls: available models, current model, thinking level, and queue modes
|
||||
are read from Pi and changed only through Pi proxy operations. No independent
|
||||
bridge defaults or persistence are allowed.
|
||||
|
||||
## Bridge/API contract
|
||||
|
||||
The panel speaks only through `pi-status-bridge-client` over the owner-only
|
||||
Unix socket. Required operations are:
|
||||
|
||||
| Purpose | Operation |
|
||||
| --- | --- |
|
||||
| agents | `list_agents`, `select_agent` |
|
||||
| live state | `get_state`, `subscribe` |
|
||||
| history | `get_transcript` |
|
||||
| composer | `submit_prompt` |
|
||||
| recovery | `retry`, `restart` |
|
||||
| Pi controls | `get_available_models`, `set_model`, `set_thinking_level` |
|
||||
| suggestions | `get_commands` |
|
||||
| extension UI | `extension_response` |
|
||||
|
||||
`submit_prompt` is the only default composer operation. The bridge dispatches
|
||||
it to Pi `prompt` when idle and Pi `follow_up` while streaming; Pi retains
|
||||
queue semantics. Direct `steer` and `follow_up` may exist only as explicit
|
||||
advanced/TUI-parity shortcuts after their exact behavior is verified; they are
|
||||
not ordinary UI buttons.
|
||||
|
||||
## State and persistence
|
||||
|
||||
- Bridge persists the Pi session reference per canonical worktree.
|
||||
- Noctalia persists the remembered folder list and last selected agent in its
|
||||
plugin data directory, not the plugin installation directory.
|
||||
- The bridge starts Home; Noctalia never spawns Pi or owns a socket lock.
|
||||
- Agent IDs are bridge-issued. The panel never invents or maps IDs to another
|
||||
worktree.
|
||||
|
||||
## Safety invariants
|
||||
|
||||
- Never expose TCP.
|
||||
- Never silently select a non-Home folder.
|
||||
- Never auto-approve extension/tool requests.
|
||||
- Never replace Pi model, thinking, or queue policy with panel defaults.
|
||||
- Never treat unsupported custom extension UI as approval.
|
||||
- Never execute an arbitrary folder path through an unquoted shell command.
|
||||
|
||||
## Acceptance scenarios
|
||||
|
||||
- **Home startup:** After user login, Home appears as a ready/working agent
|
||||
without opening the panel.
|
||||
- **Folder add:** Entering an absolute folder in Settings creates/selects its
|
||||
distinct agent; Home transcript and prompts remain isolated.
|
||||
- **Normal prompt:** Submitting the one composer while idle sends one Pi prompt.
|
||||
- **Busy prompt:** Submitting while Pi is active follows Pi's follow-up queue
|
||||
behavior without an extra panel decision.
|
||||
- **History:** Reopening Output replays existing transcript, then receives live
|
||||
updates without duplicate cursor entries.
|
||||
- **Command suggestion:** Typing `/` presents `get_commands` results; selection
|
||||
inserts, not executes, a command.
|
||||
- **Extension safety:** Confirm/select/input/editor responses preserve request
|
||||
IDs and values; custom UI shows unsupported.
|
||||
- **Recovery:** Failed-recovery state is visible with explicit retry/restart.
|
||||
|
||||
## Verification
|
||||
|
||||
```sh
|
||||
npm run check && npm test
|
||||
/usr/bin/noctalia plugins lint ~/.local/share/noctalia/plugins/pi-status-bridge
|
||||
/usr/bin/noctalia config validate
|
||||
```
|
||||
|
||||
Manual verify: open the panel, select Home, add a folder from Settings, submit a
|
||||
prompt, close/reopen the panel, and confirm history plus status remain correct.
|
||||
@@ -0,0 +1,140 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,27 @@
|
||||
feature_name: tauri-pi-desktop-client
|
||||
problem_statement: >
|
||||
The current Noctalia panel is not reliably interactive or testable, while
|
||||
users need a feature-equivalent Pi desktop client that works independently
|
||||
of any status bar.
|
||||
constraints:
|
||||
- The bridge remains the only Pi process/session/recovery owner.
|
||||
- The Tauri host uses only the local Unix bridge client and never opens TCP.
|
||||
- Noctalia is an optional compact launcher/status compatibility layer.
|
||||
- Folder selection is explicit and canonicalized by the bridge.
|
||||
- Pi owns model, thinking, queue behavior, approvals, and extension policy.
|
||||
- TUI-only custom extension UI must show unsupported and never auto-approve.
|
||||
out_of_scope:
|
||||
- Pixel-identical terminal TUI rendering.
|
||||
- Arbitrary custom TUI extension component rendering.
|
||||
- Auto-starting every remembered non-Home folder at login.
|
||||
key_decisions:
|
||||
- decision: One default composer
|
||||
rationale: Mirror the confirmed TUI workflow without bridge-owned prompt/steer decisions.
|
||||
- decision: Tauri owns the primary UI
|
||||
rationale: Native host IPC plus frontend/unit/E2E testing is more reliable than a declarative status-bar panel.
|
||||
- decision: Noctalia is a compatibility shim
|
||||
rationale: It displays compact status and opens/focuses the standalone client without owning UI behavior.
|
||||
- decision: Persist remembered folders and last selection
|
||||
rationale: Remove manual ID/folder setup while preserving explicit selection.
|
||||
- decision: Home agent is always running
|
||||
rationale: Provide an immediately available default Pi instance.
|
||||
Reference in New Issue
Block a user