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
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: