fix(tell-agent): follow the agent directory so new sessions get a pill - #158
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Two critical and two moderate issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 2
Open (4)
What changed in this PR
Adds live agent-directory tracking so Tell Agent pills appear for newly created sessions while preserving Paseo 0.8 compatibility.
Changes:
- Adds observation-based paging, reconciliation, reconnect handling, and legacy fallback.
- Drives composer pills from directory updates.
- Adds coverage for paging, failures, reconnects, and compatibility.
| File | Reviewed changes and findings |
|---|---|
tell-agent/tests/agents.test.ts |
Adds coverage for directory following and compatibility behavior. |
tell-agent/client/message-agent.tsx |
Connects pill lifecycle to the new directory follower. |
tell-agent/client/agents.ts |
Implements directory following. Findings: unsupported @getpaseo/client type dependency (critical, 2 votes); stale continuation after observation failure (critical, 2 votes); unreleased subscription during cleanup (moderate, 3 votes); continuation reads do not use the lifetime signal (moderate, 2 votes). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Thanks for the careful directory-follower implementation and paging coverage. The core diagnosis is right. I narrowed the compatibility boundary to Paseo 0.9. The focused correction now requires Paseo 0.9, removes the legacy fallback, restores the supported plugin-client type boundary, invalidates a pending continuation after terminal observation failure, and propagates the lifetime signal to continuation reads. The added regression proves a pending page cannot apply stale directory state after the observation fails. This keeps #158 limited to Tell Agent. The analogous migrations belong in separate plugin PRs. |
Since Paseo 0.9 (getpaseo/paseo#4596), agents.subscribe(handler) only registers a local listener on that API instance's observations and does not request data from the daemon; the plugin also seeds once via loadAgents() at startup, so agents created afterwards only get the "Tell agent" pill after the plugin is reloaded. - client/agents.ts: add followAgentDirectory. It feature-detects via paseo.observeEvents (added in 0.9.0-beta.1 together with the new subscription model). On 0.9 clients it opens an observation with list({ sort: updated_at desc, page: 200, subscribe: {}, signal }); the snapshot (first connect and every reconnect) only covers the first page, so remaining pages are read with plain list() calls under the existing 200x10 cap, updates arriving meanwhile override stale page data, and a fresh snapshot after a reconnect invalidates in-flight continuation reads. Pills for agents missing from the fully-read snapshot are removed; on truncation or continuation failure only upsert is performed. When the observation is released by the client it is reopened with 2s to 60s backoff; on stop the signal is aborted and the subscription released. On 0.8 clients the subscribe option is not sent (the legacy connection keeps one subscription slot per kind and later sends overwrite earlier ones, which would clobber the host app's own agent subscription), so the original agents.subscribe() + loadAgents() path is kept. - message-agent.tsx: pills are now driven by followAgentDirectory, reusing syncAgent (no re-registration when the workspace is unchanged); the popover target list still loads on demand via useQuery and is unchanged. - tests/agents.test.ts: cover the 0.9 single-page, multi-page, update override, cap truncation, reconnect invalidation, and failure reopen paths, plus the 0.8 legacy path.
6dcac8d to
6a3b733
Compare


Problem
On Paseo 0.9.x, the "Tell agent" composer pill only appears on agent sessions that already existed when the plugin loaded. Start a new agent session after the plugin has loaded and it gets no pill; reload the plugin and the pill appears.
Cause
Since Paseo 0.9 (getpaseo/paseo#4596),
client.paseo.agents.subscribe(handler)only registers a local listener on observations opened by that same API instance. The v0.9.1 SDK docs (public-docs/sdk/events.md) state:contributeAgentMessagingrelied onagents.subscribe()plus a one-shotloadAgents()seed at plugin startup, so it never learned about agents created afterwards. Measured on a 0.9.1 daemon with two API instances side by side for 60 seconds: the one that only calledsubscribe()received 0agent_updatemessages, while the one usinglist({ subscribe: {} })received 20.The
context-modeplugin in this repo already follows its pills through alist({ subscribe: {} })observation; this applies the same approach to tell-agent.Fix
client/agents.ts: newfollowAgentDirectory(paseo, follower). On 0.9 clients it opens an observation withlist({ sort: updated_at desc, page: 200, subscribe: {}, signal })and drives the follower fromsubscription.subscribe({ snapshot, update }). The snapshot only covers the first page, so remaining pages are read with plainlist()calls under the existing 200 × 10 cap; updates that land while later pages are being read override what those pages say, and a fresh snapshot after a reconnect invalidates any in-flight continuation read. Once the directory is read completely, agents absent from it get their pill removed; if the read is truncated or fails midway, only upserts are applied. When the client releases a failed observation it is reopened with 2 s → 60 s backoff. On cleanup the abort signal is aborted and the subscription released.client/message-agent.tsx: pills are now driven byfollowAgentDirectory, reusing the existingsyncAgent(no re-registration while workspace is unchanged) andremovePill. The popover's target list still loads on demand viauseQuery+loadAgents()paging and is unchanged.Compatibility
The 0.9 observation path is gated on
typeof paseo.observeEvents === "function".observeEventswas added to the SDK in v0.9.0-beta.1 together with the new owned-subscription model, so it is a reliable presence check. This matters because the plugin manifest allows^0.8.0 || ^0.9.0-beta.1: a 0.8 client must not sendsubscribeat all — perdocs/protocol-compatibility.mdin Paseo v0.9.1, "Old clients keep their existing wire shapes and slot behavior", and the legacy connection keeps a single subscription slot per kind where a later send overwrites the earlier one. A plugin-initiatedsubscribeon such a connection would replace the host app's own agents subscription. WhenobserveEventsis absent, the originalagents.subscribe()+loadAgents()path is kept unchanged. (Thecontext-modeplugin uses the observation unconditionally; it may have the same issue on 0.8 clients, which this PR deliberately does not touch.)Relation to #153
#153 ("avoid unsupported client type import") also touches
tell-agent, but only the type-import header ofclient/agents.ts— it derivesPaseoAgentListResult/PaseoApitypes from@getpaseo/plugin/clientinstead of importing them from@getpaseo/client. This PR keeps the@getpaseo/clientimport and addsPaseoAgentUpdateto it, so the two branches conflict textually in that import block. The conflict is mechanical to resolve (apply #153's derivation and derive the update type the same way, e.g. fromPaseoApi["agents"]["subscribe"]); nothing else overlaps —message-agent.tsxandtests/agents.test.tsare untouched by #153.Testing
With bun 1.4.2 (
bun install --frozen-lockfileat the repo root), the CI jobs that apply to tell-agent:tests/agents.test.ts(new) covers the 0.9 single-page, multi-page, update-overrides-page, cap-truncation, reconnect-invalidates-paging, and failure-reopen paths, plus the 0.8 legacy path (nosubscribesent,agents.subscribe()+ one-shot list). tell-agent defines no build or plugin-validation scripts in CI, so none were run.