Skip to content

feat(desktop): owner-global observer ingestion at app level#1493

Draft
tellaho wants to merge 1 commit into
tho/active-turn-bridge-fixfrom
tho/observer-owner-global
Draft

feat(desktop): owner-global observer ingestion at app level#1493
tellaho wants to merge 1 commit into
tho/active-turn-bridge-fixfrom
tho/observer-owner-global

Conversation

@tellaho

@tellaho tellaho commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1492 (tho/active-turn-bridge-fix) — merge that first; this PR's base is set to it so the diff stays scoped.

Overview

Category: improvement
User Impact: Agent activity and working indicators (sidebar badges, profile badges, activity panels) now update reliably no matter which screen is open — including for remote agents you own but don't run locally.

Problem: Observer ingestion was mount-timing incidental: frames were only received, decrypted, and folded into derived liveness state while some surface happened to mount its own bridge with the right agent list. That made indicators brittle and inconsistent — a panel could show raw turn events while another surface's liveness stayed stale (the bug class fixed point-wise in #1492), and remote owned agents were only registered when specific panels were open.

Solution: Ingestion becomes a single app-level concern. A new useAgentObserverIngestion hook mounts once in AppShell, registers all locally managed agents plus relay agents the current identity declared-owns (NIP-OA ownerPubkey, resolved via one batched profile query), and feeds both the observer store and the derived active-turns store app-wide. All five per-surface bridge mounts are removed; surfaces now only read from the stores. This establishes the product invariant: if you own an agent, its turn activity is ingested app-wide.

File changes

desktop/src/features/agents/useAgentObserverIngestion.ts
New app-level ingestion hook plus pure combineObserverIngestionAgents(): managed agents keep their real status; declared-owned relay agents not managed locally are added as deployed. Non-owned agents are excluded — their frames are #p-addressed to their owner and never arrive on our subscription anyway.

desktop/src/features/agents/useAgentObserverIngestion.test.mjs
Unit coverage for the combination logic: status passthrough, owned-relay inclusion, foreign/ownerless exclusion, managed/relay dedupe, case-insensitive matching, unresolved-identity fallback.

desktop/src/app/AppShell.tsx
Mounts the ingestion hook alongside the other always-on workspace managers.

desktop/src/features/channels/ui/ChannelScreen.tsx
Removes the per-screen observer/turns bridge mounts and the synthetic observerBridgeAgents memo (profile-panel agents are covered by app-level owned-relay registration).

desktop/src/features/profile/ui/UserProfilePanel.tsx
Removes the panel-local bridge mounts and both synthetic agent-list memos — the owned-relay "deployed" seeding it did is now the app-level rule.

desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts
Sidebar working-badge hook becomes read-only over the derived store.

desktop/src/features/agents/ui/useManagedAgentActions.ts
Agents-page hook becomes read-only over the derived store.

desktop/src/features/agents/usePreventSleep.ts
Drops its own bridge mount; keeps reading observer snapshots for activity tracking.

Reproduction Steps

  1. Run the desktop app with a local managed agent, stay on the home screen (do not open any channel or the Agents page).
  2. Mention the agent in a channel from another client/build so it starts a turn.
  3. The sidebar working badge for that channel appears and clears with the turn — previously this depended on a surface with a bridge being mounted at the right time.
  4. For a remote agent you own (declared ownerPubkey, not managed locally): open its Activity from a profile anywhere — frames decrypt without needing the specific panels that used to seed the known-agents set.
  5. Automated: cd desktop && pnpm test (1558/1558, 7 new).

No visual/markup changes — this is ingestion plumbing consolidation.

Make agent observer ingestion (kind 24200 frame decryption + derived
active-turn liveness) a single app-level concern instead of a per-surface
bridge mount, so activity data no longer depends on which screen happens
to be open.

- Add useAgentObserverIngestion (features/agents/useAgentObserverIngestion.ts):
  mounted once in AppShell; registers all locally managed agents (real
  status) plus relay agents the current identity declared-owns via NIP-OA
  ownerPubkey (treated as deployed, resolved through one users-batch
  profile query), then mounts the observer + active-turns bridges on that
  combined list
- Extract pure combineObserverIngestionAgents() with unit coverage:
  managed status passthrough, owned-relay inclusion, foreign/ownerless
  exclusion, managed/relay dedupe, case-insensitive matching, unresolved
  identity fallback
- Remove now-redundant per-surface bridge mounts and their synthetic
  agent-list memos: ChannelScreen (observerBridgeAgents), UserProfilePanel
  (bridgeAgents/observerBridgeAgents), useManagedAgentActions,
  useActiveWorkingChannelsById (sidebar), usePreventSleep
- Consolidating to one useManagedAgentObserverBridge mount also removes
  the fragile last-mount-wins wiring of setSessionConfigCapturedCallback
  across co-mounted bridges

This closes the failure mode fixed point-wise in a8dada7 (a surface
receiving raw observer frames while derived liveness stays stale because
its bridge was not mounted) for every current and future surface: sidebar
working badges, profile badges, and activity panels all read from stores
that are now fed app-wide.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant