Keep daemon connections lean across the app, CLI, and plugins - #4470
Conversation
Protocol capabilities were partly app-owned, leaving SDK and plugin clients on legacy global streams. Own the supported defaults, timeline demand, and provider reference decoding in the client, and require explicit event demand on capable connections.
|
| Filename | Overview |
|---|---|
| packages/client/src/connection/index.ts | Owns shared timeline and event demand, acknowledgement readiness, unsubscribe behavior, and reconnect restoration. |
| packages/client/src/daemon-client.ts | Integrates capability defaults, selective subscriptions, provider-reference resolution, and lifecycle restoration into the client. |
| packages/client/src/provider-snapshots/index.ts | Coalesces provider announcements and safely invalidates old in-flight fetches while preserving reconnect work. |
| packages/server/src/server/session.ts | Tracks per-source event membership and filters project, provider, permission, and attention delivery. |
| packages/protocol/src/messages.ts | Adds backward-compatible schemas for explicit session-event subscriptions and daemon feature negotiation. |
| packages/client/src/connection.test.ts | Covers capability, membership, readiness, reconnect, and provider behavior but violates the required domain-level test shape. |
| packages/server/src/server/plugins/connection-demand.e2e.test.ts | Verifies idle and legacy plugin traffic behavior while directly parsing protocol frames in the test fixture. |
Sequence Diagram
sequenceDiagram
participant Consumer as App / CLI / Plugin
participant Client as Shared client
participant Daemon as Daemon session
participant Agent as Agent/provider services
Consumer->>Client: subscribe to timeline/events
Client->>Daemon: set explicit membership
Daemon-->>Client: correlated acknowledgement
Client-->>Consumer: ready resolves
Agent-->>Daemon: timeline/catalog update
Daemon-->>Client: deliver only requested stream
Client-->>Consumer: typed callback
Consumer->>Client: unsubscribe
Client->>Daemon: release membership
Reviews (2): Last reviewed commit: "fix(client): acknowledge timeline demand..." | Re-trigger Greptile
Move supported protocol capabilities into client defaults and make session traffic subscription-driven. Preserve timeline readiness and reconnect recovery across SDK, CLI, and plugin consumers.
Linked issue
Refs #4151. This addresses the unwanted traffic reported there; it does not eliminate the separate memory cost of importing the SDK.
Type of change
Reasoning
Plugin and SDK connections fell back to global timeline broadcasts because optimized protocol capabilities were declared by the app. A real RPC-only plugin received all 3,000 timeline messages in the reproduction (about 188 MiB), despite never using its Paseo API.
The client now owns exhaustive protocol defaults and accepts caller overrides. Timeline subscriptions establish and release shared network demand, expose initial acknowledgement through
unsubscribe.ready, restore demand after reconnect, and expose history replacement events. Provider references resolve only for interested consumers, retaining unresolved announcements across reconnect. The daemon sends project, provider, permission, and attention events only to capable clients that request them, and skips agent hydration for idle sessions. CLI log-following and attach use the same subscription interface.Goals
Non-goals
QA
logs --followandattachas real CLI subprocesses against an isolated daemon; each received a single event emitted immediately after its readiness banner, three times per command with default capabilities.npm run build:client,npm run typecheck,npm run lint,npm run format, and SDK example typecheck passed.Risk surface: the SDK timeline callback now includes a documented replacement event. Internal raw-stream consumers have been migrated to explicit subscriptions. Live-provider and voice journeys were not run locally; the client and daemon checks used test providers. No UI layout changes.
Protocol compatibility: new hello/server-info fields are optional. The new subscription RPC is sent only when advertised by the daemon. Clients without the new capability retain legacy broadcasts; existing wire fields and variants remain accepted.
Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatpasses