feat(pi): add ACP bridge for editors - #5797
eersnington wants to merge 16 commits into
Conversation
ReviewSolid addition: wires ACP editors (Zed, etc.) to Pi actors via a bridge, with unusually thorough test coverage of the tricky concurrency/reconnection edge cases. A couple of things worth addressing before merge, plus a few nits. Findings1. (Medium) ```ts `#open(..., true)` calls `options.actor(sessionId, true)` (typically `getOrCreate`) and then `handle.getSession()`. `getSession` is a `read()` action (`integrations/pi/src/actions.ts:118-121, 221`) that goes through `ensurePiSession()` (`runtime.ts`), which connects/provisions the actor's sandbox when a `SandboxProvider` is configured, unconditionally, before any credential check happens. Only after that does `newSession` look at whether a `model` config option exists to decide whether to throw `authRequired`. On the "no credentials yet" branch, `#close()` only disposes the local `PiAgentConnection` (`agent.ts:307-312`); there's no `destroy()` on `PiAgentHandle`/`PiAgentConnection`, so the actor itself (and any sandbox it provisioned) is never cleaned up. Editors can call `session/new` repeatedly before a user finishes logging in (app startup, "New Chat" clicks, etc.), so this can leave behind a permanently orphaned actor (with a live sandbox, when one is configured) per failed attempt. Worth checking model/credential availability before creating the full session, or destroying the actor on the auth-required path. 2. (Low-medium) If `withTimeout(handle.getSession(), timeoutMs)` throws, the function just throws a `RequestError` without touching `handle`. For the scoped-JWT pattern demonstrated in the PR's own test ("an editor that reaches each conversation with a scoped token..."), each `actor()` call builds a brand-new `createClient(...)`. If that client's `getSession()` never resolves (engine unreachable, bad token, etc.) and times out, the freshly created client is never disposed, its background reconnect loop (which, per the `openTimeoutMs` doc comment, retries forever) keeps running with no way for the caller to reclaim it. Worth exposing a way to dispose the handle on the failure path, or documenting that `options.actor()` implementations must handle their own cleanup on this path. Nits
Positive notes
|
66a2099 to
d9aba94
Compare
e5040f4 to
89a5bd7
Compare
0a12f50 to
3a6264d
Compare
89a5bd7 to
dc9d5b1
Compare
dc9d5b1 to
3e2a18d
Compare
3a6264d to
926f6e9
Compare
3f92a6f to
b2d96c4
Compare
04872eb to
8742b4b
Compare
Adds
rivet-pi acp, which connects ACP editors such as Zed to Pi actors, andserveAcp()from@rivet-dev/pi/acpfor apps that build their own bridge.[user, sessionId]. Reopening a session replays its messages. The editor's model picker usesgetAvailableModelsandsetModel.--credentials <actor>offers a login in the editor. The editor runs Pi's login in a terminal, and the command callssave(provider, credential)on that actor with key[user].Security:
rivet-pi acpusesRIVET_TOKEN, which reaches every actor. The docs say to use it only for yourself, and to build aserveAcp()command with actor-scoped JWTs for other users.This is part 3 of 3 in a stack: