From 07e14d25ad8d435fb1be7b0b3ca2ab69229570cd Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 16:47:41 -0700 Subject: [PATCH 1/9] fix(sidebar): gate working pill on NIP-OA ownership Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../lib/useActiveWorkingChannelsById.test.mjs | 91 +++++++++++++++++- .../lib/useActiveWorkingChannelsById.ts | 93 +++++++++++++++++-- 2 files changed, 175 insertions(+), 9 deletions(-) diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs index 5f022df53..5593b6a77 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs @@ -1,10 +1,19 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; -import { resolveActiveWorkingChannelNames } from "./useActiveWorkingChannelsById.ts"; +import { + getOwnedRelayWorkingAgents, + mergeWorkingAgents, + resolveActiveWorkingChannelNames, +} from "./useActiveWorkingChannelsById.ts"; + +const VIEWER_PUBKEY = + "80c5f18be5aafa62cf6198c6335963ba3306b595288117c8ea2f805fc9bdc94a"; +const OWNED_RELAY_AGENT_PUBKEY = + "a1b2c3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff00"; describe("resolveActiveWorkingChannelNames", () => { - it("resolves active agent pubkeys to managed agent names", () => { + it("resolves active agent pubkeys to working agent names", () => { const resolved = resolveActiveWorkingChannelNames( { channelId: "chan-1", @@ -34,4 +43,82 @@ describe("resolveActiveWorkingChannelNames", () => { assert.deepEqual(resolved.agentNames, ["Ned"]); }); + + it("resolves owned relay agent names", () => { + const resolved = resolveActiveWorkingChannelNames( + { + channelId: "chan-1", + anchorAt: 0, + agentCount: 1, + agentPubkeys: [OWNED_RELAY_AGENT_PUBKEY.toUpperCase()], + }, + [{ pubkey: OWNED_RELAY_AGENT_PUBKEY, name: "nadia" }], + ); + + assert.deepEqual(resolved.agentNames, ["nadia"]); + }); +}); + +describe("getOwnedRelayWorkingAgents", () => { + it("keeps relay agents whose NIP-OA owner is the current viewer", () => { + assert.deepEqual( + getOwnedRelayWorkingAgents( + [ + { pubkey: OWNED_RELAY_AGENT_PUBKEY, name: "nadia" }, + { pubkey: "other-agent", name: "nelson" }, + ], + { + [OWNED_RELAY_AGENT_PUBKEY]: { + displayName: "nadia", + avatarUrl: null, + nip05Handle: null, + ownerPubkey: VIEWER_PUBKEY.toUpperCase(), + isAgent: true, + }, + "other-agent": { + displayName: "nelson", + avatarUrl: null, + nip05Handle: null, + ownerPubkey: "someone-else", + isAgent: true, + }, + }, + VIEWER_PUBKEY, + ), + [{ pubkey: OWNED_RELAY_AGENT_PUBKEY, name: "nadia", status: "deployed" }], + ); + }); + + it("returns no relay agents without a current viewer", () => { + assert.deepEqual( + getOwnedRelayWorkingAgents( + [{ pubkey: OWNED_RELAY_AGENT_PUBKEY, name: "nadia" }], + {}, + undefined, + ), + [], + ); + }); +}); + +describe("mergeWorkingAgents", () => { + it("dedupes owned relay agents behind locally managed agents", () => { + assert.deepEqual( + mergeWorkingAgents( + [{ pubkey: "AAAA", name: "Ned", status: "running" }], + [ + { pubkey: "aaaa", name: "Relay Ned", status: "deployed" }, + { + pubkey: OWNED_RELAY_AGENT_PUBKEY, + name: "nadia", + status: "deployed", + }, + ], + ), + [ + { pubkey: "AAAA", name: "Ned", status: "running" }, + { pubkey: OWNED_RELAY_AGENT_PUBKEY, name: "nadia", status: "deployed" }, + ], + ); + }); }); diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts index 03f245f64..ec514c7c5 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts @@ -5,16 +5,32 @@ import { useActiveAgentTurnsBridge, useActiveAgentTurnsByChannel, } from "@/features/agents/activeAgentTurnsStore"; -import { useManagedAgentsQuery } from "@/features/agents/hooks"; +import { + useManagedAgentsQuery, + useRelayAgentsQuery, +} from "@/features/agents/hooks"; import { useManagedAgentObserverBridge } from "@/features/agents/observerRelayStore"; +import { useUsersBatchQuery } from "@/features/profile/hooks"; +import { useIdentityQuery } from "@/shared/api/hooks"; +import type { + ManagedAgent, + RelayAgent, + UserProfileSummary, +} from "@/shared/api/types"; import { normalizePubkey } from "@/shared/lib/pubkey"; +type WorkingAgentName = Pick; +type WorkingAgent = Pick; +type OwnedRelayWorkingAgent = Pick & { + status: "deployed"; +}; + export function resolveActiveWorkingChannelNames( summary: ActiveChannelTurnSummary, - managedAgents: readonly { pubkey: string; name: string }[], + workingAgents: readonly WorkingAgentName[], ): ActiveChannelTurnSummary { const namesByPubkey = new Map( - managedAgents.map((agent) => [normalizePubkey(agent.pubkey), agent.name]), + workingAgents.map((agent) => [normalizePubkey(agent.pubkey), agent.name]), ); return { @@ -26,18 +42,81 @@ export function resolveActiveWorkingChannelNames( }; } +export function getOwnedRelayWorkingAgents( + relayAgents: readonly Pick[], + profiles: Record | undefined, + currentPubkey: string | undefined, +): OwnedRelayWorkingAgent[] { + if (!currentPubkey) return []; + + const normalizedCurrentPubkey = normalizePubkey(currentPubkey); + return relayAgents.flatMap((agent) => { + const profile = profiles?.[normalizePubkey(agent.pubkey)]; + if (!profile?.ownerPubkey) return []; + if (normalizePubkey(profile.ownerPubkey) !== normalizedCurrentPubkey) { + return []; + } + + return [{ pubkey: agent.pubkey, name: agent.name, status: "deployed" }]; + }); +} + +export function mergeWorkingAgents( + managedAgents: readonly WorkingAgent[], + ownedRelayAgents: readonly WorkingAgent[], +): WorkingAgent[] { + const seenPubkeys = new Set(); + const merged: WorkingAgent[] = []; + + for (const agent of [...managedAgents, ...ownedRelayAgents]) { + const pubkey = normalizePubkey(agent.pubkey); + if (seenPubkeys.has(pubkey)) continue; + seenPubkeys.add(pubkey); + merged.push(agent); + } + + return merged; +} + export function useActiveWorkingChannelsById(): ReadonlyMap< string, ActiveChannelTurnSummary > { + const identityQuery = useIdentityQuery(); + const currentPubkey = identityQuery.data?.pubkey; const managedAgentsQuery = useManagedAgentsQuery(); const managedAgents = React.useMemo( () => managedAgentsQuery.data ?? [], [managedAgentsQuery.data], ); + const relayAgentsQuery = useRelayAgentsQuery(); + const relayAgents = React.useMemo( + () => relayAgentsQuery.data ?? [], + [relayAgentsQuery.data], + ); + const relayAgentPubkeys = React.useMemo( + () => relayAgents.map((agent) => agent.pubkey), + [relayAgents], + ); + const relayAgentProfilesQuery = useUsersBatchQuery(relayAgentPubkeys, { + enabled: relayAgentPubkeys.length > 0, + }); + const ownedRelayAgents = React.useMemo( + () => + getOwnedRelayWorkingAgents( + relayAgents, + relayAgentProfilesQuery.data?.profiles, + currentPubkey, + ), + [currentPubkey, relayAgentProfilesQuery.data?.profiles, relayAgents], + ); + const workingAgents = React.useMemo( + () => mergeWorkingAgents(managedAgents, ownedRelayAgents), + [managedAgents, ownedRelayAgents], + ); - useManagedAgentObserverBridge(managedAgents); - useActiveAgentTurnsBridge(managedAgents); + useManagedAgentObserverBridge(workingAgents); + useActiveAgentTurnsBridge(workingAgents); const activeWorkingChannels = useActiveAgentTurnsByChannel(); return React.useMemo( @@ -46,11 +125,11 @@ export function useActiveWorkingChannelsById(): ReadonlyMap< activeWorkingChannels.map((summary) => { const resolvedSummary = resolveActiveWorkingChannelNames( summary, - managedAgents, + workingAgents, ); return [resolvedSummary.channelId, resolvedSummary]; }), ), - [activeWorkingChannels, managedAgents], + [activeWorkingChannels, workingAgents], ); } From 4535a42323a0b2482fedb1ba7b13b2fbc92cc282 Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 16:55:42 -0700 Subject: [PATCH 2/9] test(sidebar): cover ownerless relay agents Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../lib/useActiveWorkingChannelsById.test.mjs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs index 5593b6a77..a13dc48d8 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs @@ -99,6 +99,28 @@ describe("getOwnedRelayWorkingAgents", () => { [], ); }); + + it("drops relay agents with missing profiles or null owners", () => { + assert.deepEqual( + getOwnedRelayWorkingAgents( + [ + { pubkey: OWNED_RELAY_AGENT_PUBKEY, name: "nadia" }, + { pubkey: "ownerless-agent", name: "ralph" }, + ], + { + "ownerless-agent": { + displayName: "ralph", + avatarUrl: null, + nip05Handle: null, + ownerPubkey: null, + isAgent: true, + }, + }, + VIEWER_PUBKEY, + ), + [], + ); + }); }); describe("mergeWorkingAgents", () => { From a8d150197931d63e56c5a61f6cb14ca4e13326af Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 17:01:29 -0700 Subject: [PATCH 3/9] fix(sidebar): use declared owner helper for working pill Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../src/features/sidebar/lib/useActiveWorkingChannelsById.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts index ec514c7c5..fd0102616 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts @@ -11,6 +11,7 @@ import { } from "@/features/agents/hooks"; import { useManagedAgentObserverBridge } from "@/features/agents/observerRelayStore"; import { useUsersBatchQuery } from "@/features/profile/hooks"; +import { ownsAuthorAgent } from "@/features/profile/lib/identity"; import { useIdentityQuery } from "@/shared/api/hooks"; import type { ManagedAgent, @@ -49,11 +50,9 @@ export function getOwnedRelayWorkingAgents( ): OwnedRelayWorkingAgent[] { if (!currentPubkey) return []; - const normalizedCurrentPubkey = normalizePubkey(currentPubkey); return relayAgents.flatMap((agent) => { const profile = profiles?.[normalizePubkey(agent.pubkey)]; - if (!profile?.ownerPubkey) return []; - if (normalizePubkey(profile.ownerPubkey) !== normalizedCurrentPubkey) { + if (!ownsAuthorAgent(profile, currentPubkey)) { return []; } From 2143fee81d4369d256d411a9085f960a9678c851 Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 20:08:57 -0700 Subject: [PATCH 4/9] chore(sidebar): temporary pill diagnosis logging Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../lib/useActiveWorkingChannelsById.ts | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts index fd0102616..021cbada7 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts @@ -22,6 +22,9 @@ import { normalizePubkey } from "@/shared/lib/pubkey"; type WorkingAgentName = Pick; type WorkingAgent = Pick; +type PillDiagProfile = UserProfileSummary & { + pubkey?: string; +}; type OwnedRelayWorkingAgent = Pick & { status: "deployed"; }; @@ -77,6 +80,86 @@ export function mergeWorkingAgents( return merged; } +function summarizeAgent(agent: WorkingAgentName & { status?: string }) { + return { + pubkey: normalizePubkey(agent.pubkey), + name: agent.name, + status: agent.status ?? null, + }; +} + +function summarizeRelayProfile( + pubkey: string, + profile: PillDiagProfile | undefined, + currentPubkey: string | undefined, +) { + return { + pubkey: normalizePubkey(pubkey), + profileKeyPubkey: profile?.pubkey ? normalizePubkey(profile.pubkey) : null, + displayName: profile?.displayName ?? null, + isAgent: profile?.isAgent ?? null, + ownerPubkey: profile?.ownerPubkey + ? normalizePubkey(profile.ownerPubkey) + : null, + ownsAuthorAgent: ownsAuthorAgent(profile, currentPubkey), + }; +} + +function logPillDiagnostics({ + currentPubkey, + managedAgents, + relayAgents, + profiles, + ownedRelayAgents, + workingAgents, + activeWorkingChannels, +}: { + currentPubkey: string | undefined; + managedAgents: readonly WorkingAgent[]; + relayAgents: readonly Pick[]; + profiles: Record | undefined; + ownedRelayAgents: readonly WorkingAgent[]; + workingAgents: readonly WorkingAgent[]; + activeWorkingChannels: readonly ActiveChannelTurnSummary[]; +}) { + const normalizedProfiles = Object.fromEntries( + relayAgents.map((agent) => { + const pubkey = normalizePubkey(agent.pubkey); + const profile = profiles?.[pubkey] as PillDiagProfile | undefined; + return [ + pubkey, + summarizeRelayProfile(agent.pubkey, profile, currentPubkey), + ]; + }), + ); + + console.groupCollapsed("[pill-diag] active working channels inputs", { + currentPubkey: currentPubkey ? normalizePubkey(currentPubkey) : null, + managedAgentCount: managedAgents.length, + relayAgentCount: relayAgents.length, + ownedRelayAgentCount: ownedRelayAgents.length, + workingAgentCount: workingAgents.length, + activeChannelCount: activeWorkingChannels.length, + }); + console.log("[pill-diag] currentPubkey", { + currentPubkey: currentPubkey ? normalizePubkey(currentPubkey) : null, + }); + console.table(managedAgents.map(summarizeAgent)); + console.log("[pill-diag] managedAgents", managedAgents.map(summarizeAgent)); + console.table(relayAgents.map(summarizeAgent)); + console.log("[pill-diag] relayAgents", relayAgents.map(summarizeAgent)); + console.log("[pill-diag] profilesByRelayAgent", normalizedProfiles); + console.table(ownedRelayAgents.map(summarizeAgent)); + console.log( + "[pill-diag] ownedRelayAgents", + ownedRelayAgents.map(summarizeAgent), + ); + console.table(workingAgents.map(summarizeAgent)); + console.log("[pill-diag] workingAgents", workingAgents.map(summarizeAgent)); + console.log("[pill-diag] activeWorkingChannels", activeWorkingChannels); + console.groupEnd(); +} + export function useActiveWorkingChannelsById(): ReadonlyMap< string, ActiveChannelTurnSummary @@ -118,6 +201,27 @@ export function useActiveWorkingChannelsById(): ReadonlyMap< useActiveAgentTurnsBridge(workingAgents); const activeWorkingChannels = useActiveAgentTurnsByChannel(); + + React.useEffect(() => { + logPillDiagnostics({ + currentPubkey, + managedAgents, + relayAgents, + profiles: relayAgentProfilesQuery.data?.profiles, + ownedRelayAgents, + workingAgents, + activeWorkingChannels, + }); + }, [ + activeWorkingChannels, + currentPubkey, + managedAgents, + ownedRelayAgents, + relayAgentProfilesQuery.data?.profiles, + relayAgents, + workingAgents, + ]); + return React.useMemo( () => new Map( From 8bbdd88a674d0581d2265d065d239a4b96675161 Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 20:25:40 -0700 Subject: [PATCH 5/9] chore(sidebar): earlier pill-diag load and render probes Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../sidebar/lib/useActiveWorkingChannelsById.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts index 021cbada7..d61658659 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts @@ -20,6 +20,12 @@ import type { } from "@/shared/api/types"; import { normalizePubkey } from "@/shared/lib/pubkey"; +console.log("[pill-diag] module loaded", { + loadedAt: new Date().toISOString(), +}); + +let pillDiagRenderCount = 0; + type WorkingAgentName = Pick; type WorkingAgent = Pick; type PillDiagProfile = UserProfileSummary & { @@ -164,6 +170,12 @@ export function useActiveWorkingChannelsById(): ReadonlyMap< string, ActiveChannelTurnSummary > { + pillDiagRenderCount += 1; + console.log("[pill-diag] hook render", { + renderCount: pillDiagRenderCount, + renderedAt: new Date().toISOString(), + }); + const identityQuery = useIdentityQuery(); const currentPubkey = identityQuery.data?.pubkey; const managedAgentsQuery = useManagedAgentsQuery(); From 4a1892a6512304896142efd4bdb0279b9959fe1b Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 20:42:26 -0700 Subject: [PATCH 6/9] chore(sidebar): trace observer relay timing Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../src/features/agents/observerRelayStore.ts | 122 +++++++++++++++++- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/desktop/src/features/agents/observerRelayStore.ts b/desktop/src/features/agents/observerRelayStore.ts index 6aa397edb..1ac54e7ef 100644 --- a/desktop/src/features/agents/observerRelayStore.ts +++ b/desktop/src/features/agents/observerRelayStore.ts @@ -84,17 +84,26 @@ function registerKnownAgents( subscriptionId: string, pubkeys: readonly string[], ) { - knownAgentsBySubscription.set( - subscriptionId, - new Set(pubkeys.map((pubkey) => normalizePubkey(pubkey))), - ); + const normalizedPubkeys = pubkeys.map((pubkey) => normalizePubkey(pubkey)); + knownAgentsBySubscription.set(subscriptionId, new Set(normalizedPubkeys)); recomputeKnownAgentPubkeys(); + pillDiagLog("registerKnownAgents", { + subscriptionId, + subscriptionPubkeys: pillDiagPubkeys(normalizedPubkeys), + }); } function unregisterKnownAgents(subscriptionId: string) { - if (knownAgentsBySubscription.delete(subscriptionId)) { + const removedPubkeys = knownAgentsBySubscription.get(subscriptionId); + const removed = knownAgentsBySubscription.delete(subscriptionId); + if (removed) { recomputeKnownAgentPubkeys(); } + pillDiagLog("unregisterKnownAgents", { + subscriptionId, + removed, + removedPubkeys: removedPubkeys ? pillDiagPubkeys(removedPubkeys) : [], + }); } let connectionState: ConnectionState = "idle"; @@ -104,6 +113,44 @@ let startPromise: Promise | null = null; let eventProcessingQueue: Promise = Promise.resolve(); let generation = 0; +function pillDiagPubkeys(pubkeys: Iterable) { + return [...pubkeys].map((pubkey) => normalizePubkey(pubkey)).sort(); +} + +function pillDiagState(extra: Record = {}) { + return { + generation, + connectionState, + hasRelaySubscription: Boolean(unsubscribeRelay), + hasStartPromise: Boolean(startPromise), + knownAgentCount: knownAgentPubkeys.size, + knownAgents: pillDiagPubkeys(knownAgentPubkeys), + subscriptionCount: knownAgentsBySubscription.size, + subscriptionSizes: Object.fromEntries( + [...knownAgentsBySubscription.entries()].map( + ([subscriptionId, pubkeys]) => [subscriptionId, pubkeys.size], + ), + ), + at: new Date().toISOString(), + ...extra, + }; +} + +function pillDiagLog(label: string, extra: Record = {}) { + console.log(`[pill-diag] observer ${label}`, pillDiagState(extra)); +} + +function pillDiagEvent(event: RelayEvent) { + const agentPubkey = observerTag(event, "agent"); + return { + eventId: event.id, + eventPubkey: normalizePubkey(event.pubkey), + agentPubkey: agentPubkey ? normalizePubkey(agentPubkey) : null, + frame: observerTag(event, "frame"), + createdAt: event.created_at, + }; +} + function notifyListeners() { for (const listener of listeners) { listener(); @@ -191,26 +238,52 @@ async function handleRelayObserverEvent( const agentPubkey = observerTag(event, "agent"); const frame = observerTag(event, "frame"); if (!agentPubkey || frame !== "telemetry") { + pillDiagLog("drop nonTelemetryFrame", { + activeGeneration, + event: pillDiagEvent(event), + }); return; } // Verify agent is known/trusted before decrypting. // Silently drop events from agents we are not managing. if (!knownAgentPubkeys.has(normalizePubkey(agentPubkey))) { + pillDiagLog("drop unknownAgent", { + activeGeneration, + event: pillDiagEvent(event), + }); return; } // Defense-in-depth: verify the event sender matches the claimed agent pubkey. // The relay gates on is_agent_owner, but a compromised relay could misroute. if (normalizePubkey(event.pubkey) !== normalizePubkey(agentPubkey)) { + pillDiagLog("drop senderMismatch", { + activeGeneration, + event: pillDiagEvent(event), + }); return; } try { const parsed = (await decryptObserverEvent(event)) as ObserverEvent; if (activeGeneration !== generation) { + pillDiagLog("drop staleGenerationAfterDecrypt", { + activeGeneration, + event: pillDiagEvent(event), + parsedKind: parsed.kind, + parsedSeq: parsed.seq, + parsedTimestamp: parsed.timestamp, + }); return; } + pillDiagLog("appendFrame", { + activeGeneration, + event: pillDiagEvent(event), + parsedKind: parsed.kind, + parsedSeq: parsed.seq, + parsedTimestamp: parsed.timestamp, + }); appendAgentEvent(agentPubkey, parsed); if (parsed.kind === "session_config_captured") { void putAgentSessionConfig(agentPubkey, parsed.payload); @@ -220,8 +293,18 @@ async function handleRelayObserverEvent( } } catch (error) { if (activeGeneration !== generation) { + pillDiagLog("drop staleGenerationAfterError", { + activeGeneration, + event: pillDiagEvent(event), + error: error instanceof Error ? error.message : String(error), + }); return; } + pillDiagLog("decryptError", { + activeGeneration, + event: pillDiagEvent(event), + error: error instanceof Error ? error.message : String(error), + }); setConnectionState( "error", error instanceof Error @@ -233,25 +316,44 @@ async function handleRelayObserverEvent( export function ensureRelayObserverSubscription() { if (unsubscribeRelay) { + pillDiagLog("ensure reuseOpenSubscription"); return Promise.resolve(); } if (startPromise) { + pillDiagLog("ensure reuseStartPromise"); return startPromise; } const activeGeneration = generation; + pillDiagLog("ensure start", { activeGeneration }); setConnectionState("connecting", null); startPromise = (async () => { const identity = await getIdentity(); + pillDiagLog("subscribeToAgentObserverFrames start", { + activeGeneration, + identityPubkey: normalizePubkey(identity.pubkey), + }); const unsubscribe = await subscribeToAgentObserverFrames( identity.pubkey, (event) => { + pillDiagLog("frame received", { + activeGeneration, + event: pillDiagEvent(event), + }); eventProcessingQueue = eventProcessingQueue .then(() => handleRelayObserverEvent(event, activeGeneration)) .catch((error) => { if (activeGeneration !== generation) { + pillDiagLog("drop staleGenerationInQueueCatch", { + activeGeneration, + error: error instanceof Error ? error.message : String(error), + }); return; } + pillDiagLog("eventHandlingError", { + activeGeneration, + error: error instanceof Error ? error.message : String(error), + }); setConnectionState( "error", error instanceof Error @@ -262,13 +364,19 @@ export function ensureRelayObserverSubscription() { }, ); if (activeGeneration !== generation) { + pillDiagLog("subscribe resolved staleGeneration", { activeGeneration }); await unsubscribe(); return; } unsubscribeRelay = unsubscribe; + pillDiagLog("subscribe open", { activeGeneration }); setConnectionState("open", null); })() .catch((error) => { + pillDiagLog("subscribe error", { + activeGeneration, + error: error instanceof Error ? error.message : String(error), + }); if (activeGeneration === generation) { setConnectionState( "error", @@ -279,6 +387,7 @@ export function ensureRelayObserverSubscription() { } }) .finally(() => { + pillDiagLog("ensure finally", { activeGeneration }); if (activeGeneration === generation) { startPromise = null; } @@ -406,6 +515,7 @@ export function useManagedAgentObserverBridge( }, [subscriptionId, agentPubkeys]); React.useEffect(() => { + pillDiagLog("bridge activeEffect", { hasActiveAgent }); if (!hasActiveAgent) { return; } @@ -425,6 +535,7 @@ export function useManagedAgentObserverBridge( } export function resetAgentObserverStore() { + pillDiagLog("reset start", { nextGeneration: generation + 1 }); generation += 1; const unsubscribe = unsubscribeRelay; unsubscribeRelay = null; @@ -439,5 +550,6 @@ export function resetAgentObserverStore() { connectionState = "idle"; errorMessage = null; notifyListeners(); + pillDiagLog("reset afterClear", { hadUnsubscribe: Boolean(unsubscribe) }); void unsubscribe?.(); } From f79514bd9350f75453311b16d08a6c86e80fe59a Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 21:04:13 -0700 Subject: [PATCH 7/9] chore(sidebar): trace observer bridge agent statuses Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../src/features/agents/observerRelayStore.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/desktop/src/features/agents/observerRelayStore.ts b/desktop/src/features/agents/observerRelayStore.ts index 1ac54e7ef..6d5d46f65 100644 --- a/desktop/src/features/agents/observerRelayStore.ts +++ b/desktop/src/features/agents/observerRelayStore.ts @@ -151,6 +151,16 @@ function pillDiagEvent(event: RelayEvent) { }; } +function pillDiagBridgeAgents( + agents: readonly Pick[], +) { + return agents.map((agent) => ({ + pubkey: normalizePubkey(agent.pubkey), + status: agent.status, + startsObserver: agent.status === "running" || agent.status === "deployed", + })); +} + function notifyListeners() { for (const listener of listeners) { listener(); @@ -499,6 +509,12 @@ export function useManagedAgentObserverBridge( [agents], ); + pillDiagLog("bridge render", { + subscriptionId, + hasActiveAgent, + agents: pillDiagBridgeAgents(agents), + }); + const agentPubkeys = React.useMemo( () => agents.map((agent) => agent.pubkey), [agents], From 96bd0adc8de77b496edcdaa12d1e82a700f0a884 Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 21:27:04 -0700 Subject: [PATCH 8/9] fix(sidebar): prefer owned relay status for working pill Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../src/features/agents/observerRelayStore.ts | 138 +----------------- .../lib/useActiveWorkingChannelsById.test.mjs | 24 ++- .../lib/useActiveWorkingChannelsById.ts | 137 ++--------------- 3 files changed, 43 insertions(+), 256 deletions(-) diff --git a/desktop/src/features/agents/observerRelayStore.ts b/desktop/src/features/agents/observerRelayStore.ts index 6d5d46f65..6aa397edb 100644 --- a/desktop/src/features/agents/observerRelayStore.ts +++ b/desktop/src/features/agents/observerRelayStore.ts @@ -84,26 +84,17 @@ function registerKnownAgents( subscriptionId: string, pubkeys: readonly string[], ) { - const normalizedPubkeys = pubkeys.map((pubkey) => normalizePubkey(pubkey)); - knownAgentsBySubscription.set(subscriptionId, new Set(normalizedPubkeys)); - recomputeKnownAgentPubkeys(); - pillDiagLog("registerKnownAgents", { + knownAgentsBySubscription.set( subscriptionId, - subscriptionPubkeys: pillDiagPubkeys(normalizedPubkeys), - }); + new Set(pubkeys.map((pubkey) => normalizePubkey(pubkey))), + ); + recomputeKnownAgentPubkeys(); } function unregisterKnownAgents(subscriptionId: string) { - const removedPubkeys = knownAgentsBySubscription.get(subscriptionId); - const removed = knownAgentsBySubscription.delete(subscriptionId); - if (removed) { + if (knownAgentsBySubscription.delete(subscriptionId)) { recomputeKnownAgentPubkeys(); } - pillDiagLog("unregisterKnownAgents", { - subscriptionId, - removed, - removedPubkeys: removedPubkeys ? pillDiagPubkeys(removedPubkeys) : [], - }); } let connectionState: ConnectionState = "idle"; @@ -113,54 +104,6 @@ let startPromise: Promise | null = null; let eventProcessingQueue: Promise = Promise.resolve(); let generation = 0; -function pillDiagPubkeys(pubkeys: Iterable) { - return [...pubkeys].map((pubkey) => normalizePubkey(pubkey)).sort(); -} - -function pillDiagState(extra: Record = {}) { - return { - generation, - connectionState, - hasRelaySubscription: Boolean(unsubscribeRelay), - hasStartPromise: Boolean(startPromise), - knownAgentCount: knownAgentPubkeys.size, - knownAgents: pillDiagPubkeys(knownAgentPubkeys), - subscriptionCount: knownAgentsBySubscription.size, - subscriptionSizes: Object.fromEntries( - [...knownAgentsBySubscription.entries()].map( - ([subscriptionId, pubkeys]) => [subscriptionId, pubkeys.size], - ), - ), - at: new Date().toISOString(), - ...extra, - }; -} - -function pillDiagLog(label: string, extra: Record = {}) { - console.log(`[pill-diag] observer ${label}`, pillDiagState(extra)); -} - -function pillDiagEvent(event: RelayEvent) { - const agentPubkey = observerTag(event, "agent"); - return { - eventId: event.id, - eventPubkey: normalizePubkey(event.pubkey), - agentPubkey: agentPubkey ? normalizePubkey(agentPubkey) : null, - frame: observerTag(event, "frame"), - createdAt: event.created_at, - }; -} - -function pillDiagBridgeAgents( - agents: readonly Pick[], -) { - return agents.map((agent) => ({ - pubkey: normalizePubkey(agent.pubkey), - status: agent.status, - startsObserver: agent.status === "running" || agent.status === "deployed", - })); -} - function notifyListeners() { for (const listener of listeners) { listener(); @@ -248,52 +191,26 @@ async function handleRelayObserverEvent( const agentPubkey = observerTag(event, "agent"); const frame = observerTag(event, "frame"); if (!agentPubkey || frame !== "telemetry") { - pillDiagLog("drop nonTelemetryFrame", { - activeGeneration, - event: pillDiagEvent(event), - }); return; } // Verify agent is known/trusted before decrypting. // Silently drop events from agents we are not managing. if (!knownAgentPubkeys.has(normalizePubkey(agentPubkey))) { - pillDiagLog("drop unknownAgent", { - activeGeneration, - event: pillDiagEvent(event), - }); return; } // Defense-in-depth: verify the event sender matches the claimed agent pubkey. // The relay gates on is_agent_owner, but a compromised relay could misroute. if (normalizePubkey(event.pubkey) !== normalizePubkey(agentPubkey)) { - pillDiagLog("drop senderMismatch", { - activeGeneration, - event: pillDiagEvent(event), - }); return; } try { const parsed = (await decryptObserverEvent(event)) as ObserverEvent; if (activeGeneration !== generation) { - pillDiagLog("drop staleGenerationAfterDecrypt", { - activeGeneration, - event: pillDiagEvent(event), - parsedKind: parsed.kind, - parsedSeq: parsed.seq, - parsedTimestamp: parsed.timestamp, - }); return; } - pillDiagLog("appendFrame", { - activeGeneration, - event: pillDiagEvent(event), - parsedKind: parsed.kind, - parsedSeq: parsed.seq, - parsedTimestamp: parsed.timestamp, - }); appendAgentEvent(agentPubkey, parsed); if (parsed.kind === "session_config_captured") { void putAgentSessionConfig(agentPubkey, parsed.payload); @@ -303,18 +220,8 @@ async function handleRelayObserverEvent( } } catch (error) { if (activeGeneration !== generation) { - pillDiagLog("drop staleGenerationAfterError", { - activeGeneration, - event: pillDiagEvent(event), - error: error instanceof Error ? error.message : String(error), - }); return; } - pillDiagLog("decryptError", { - activeGeneration, - event: pillDiagEvent(event), - error: error instanceof Error ? error.message : String(error), - }); setConnectionState( "error", error instanceof Error @@ -326,44 +233,25 @@ async function handleRelayObserverEvent( export function ensureRelayObserverSubscription() { if (unsubscribeRelay) { - pillDiagLog("ensure reuseOpenSubscription"); return Promise.resolve(); } if (startPromise) { - pillDiagLog("ensure reuseStartPromise"); return startPromise; } const activeGeneration = generation; - pillDiagLog("ensure start", { activeGeneration }); setConnectionState("connecting", null); startPromise = (async () => { const identity = await getIdentity(); - pillDiagLog("subscribeToAgentObserverFrames start", { - activeGeneration, - identityPubkey: normalizePubkey(identity.pubkey), - }); const unsubscribe = await subscribeToAgentObserverFrames( identity.pubkey, (event) => { - pillDiagLog("frame received", { - activeGeneration, - event: pillDiagEvent(event), - }); eventProcessingQueue = eventProcessingQueue .then(() => handleRelayObserverEvent(event, activeGeneration)) .catch((error) => { if (activeGeneration !== generation) { - pillDiagLog("drop staleGenerationInQueueCatch", { - activeGeneration, - error: error instanceof Error ? error.message : String(error), - }); return; } - pillDiagLog("eventHandlingError", { - activeGeneration, - error: error instanceof Error ? error.message : String(error), - }); setConnectionState( "error", error instanceof Error @@ -374,19 +262,13 @@ export function ensureRelayObserverSubscription() { }, ); if (activeGeneration !== generation) { - pillDiagLog("subscribe resolved staleGeneration", { activeGeneration }); await unsubscribe(); return; } unsubscribeRelay = unsubscribe; - pillDiagLog("subscribe open", { activeGeneration }); setConnectionState("open", null); })() .catch((error) => { - pillDiagLog("subscribe error", { - activeGeneration, - error: error instanceof Error ? error.message : String(error), - }); if (activeGeneration === generation) { setConnectionState( "error", @@ -397,7 +279,6 @@ export function ensureRelayObserverSubscription() { } }) .finally(() => { - pillDiagLog("ensure finally", { activeGeneration }); if (activeGeneration === generation) { startPromise = null; } @@ -509,12 +390,6 @@ export function useManagedAgentObserverBridge( [agents], ); - pillDiagLog("bridge render", { - subscriptionId, - hasActiveAgent, - agents: pillDiagBridgeAgents(agents), - }); - const agentPubkeys = React.useMemo( () => agents.map((agent) => agent.pubkey), [agents], @@ -531,7 +406,6 @@ export function useManagedAgentObserverBridge( }, [subscriptionId, agentPubkeys]); React.useEffect(() => { - pillDiagLog("bridge activeEffect", { hasActiveAgent }); if (!hasActiveAgent) { return; } @@ -551,7 +425,6 @@ export function useManagedAgentObserverBridge( } export function resetAgentObserverStore() { - pillDiagLog("reset start", { nextGeneration: generation + 1 }); generation += 1; const unsubscribe = unsubscribeRelay; unsubscribeRelay = null; @@ -566,6 +439,5 @@ export function resetAgentObserverStore() { connectionState = "idle"; errorMessage = null; notifyListeners(); - pillDiagLog("reset afterClear", { hadUnsubscribe: Boolean(unsubscribe) }); void unsubscribe?.(); } diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs index a13dc48d8..cbfc3718d 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.test.mjs @@ -124,7 +124,7 @@ describe("getOwnedRelayWorkingAgents", () => { }); describe("mergeWorkingAgents", () => { - it("dedupes owned relay agents behind locally managed agents", () => { + it("keeps active managed agents ahead of owned relay duplicates", () => { assert.deepEqual( mergeWorkingAgents( [{ pubkey: "AAAA", name: "Ned", status: "running" }], @@ -143,4 +143,26 @@ describe("mergeWorkingAgents", () => { ], ); }); + + it("uses owned relay status when a duplicate managed agent is stopped", () => { + assert.deepEqual( + mergeWorkingAgents( + [ + { + pubkey: OWNED_RELAY_AGENT_PUBKEY.toUpperCase(), + name: "Local Nadia", + status: "stopped", + }, + ], + [ + { + pubkey: OWNED_RELAY_AGENT_PUBKEY, + name: "nadia", + status: "deployed", + }, + ], + ), + [{ pubkey: OWNED_RELAY_AGENT_PUBKEY, name: "nadia", status: "deployed" }], + ); + }); }); diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts index d61658659..dc0de7f4d 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts @@ -20,17 +20,8 @@ import type { } from "@/shared/api/types"; import { normalizePubkey } from "@/shared/lib/pubkey"; -console.log("[pill-diag] module loaded", { - loadedAt: new Date().toISOString(), -}); - -let pillDiagRenderCount = 0; - type WorkingAgentName = Pick; type WorkingAgent = Pick; -type PillDiagProfile = UserProfileSummary & { - pubkey?: string; -}; type OwnedRelayWorkingAgent = Pick & { status: "deployed"; }; @@ -69,113 +60,35 @@ export function getOwnedRelayWorkingAgents( }); } +function agentCanStartObserver(agent: WorkingAgent) { + return agent.status === "running" || agent.status === "deployed"; +} + export function mergeWorkingAgents( managedAgents: readonly WorkingAgent[], ownedRelayAgents: readonly WorkingAgent[], ): WorkingAgent[] { - const seenPubkeys = new Set(); - const merged: WorkingAgent[] = []; + const mergedByPubkey = new Map(); - for (const agent of [...managedAgents, ...ownedRelayAgents]) { - const pubkey = normalizePubkey(agent.pubkey); - if (seenPubkeys.has(pubkey)) continue; - seenPubkeys.add(pubkey); - merged.push(agent); + for (const agent of managedAgents) { + mergedByPubkey.set(normalizePubkey(agent.pubkey), agent); } - return merged; -} - -function summarizeAgent(agent: WorkingAgentName & { status?: string }) { - return { - pubkey: normalizePubkey(agent.pubkey), - name: agent.name, - status: agent.status ?? null, - }; -} - -function summarizeRelayProfile( - pubkey: string, - profile: PillDiagProfile | undefined, - currentPubkey: string | undefined, -) { - return { - pubkey: normalizePubkey(pubkey), - profileKeyPubkey: profile?.pubkey ? normalizePubkey(profile.pubkey) : null, - displayName: profile?.displayName ?? null, - isAgent: profile?.isAgent ?? null, - ownerPubkey: profile?.ownerPubkey - ? normalizePubkey(profile.ownerPubkey) - : null, - ownsAuthorAgent: ownsAuthorAgent(profile, currentPubkey), - }; -} - -function logPillDiagnostics({ - currentPubkey, - managedAgents, - relayAgents, - profiles, - ownedRelayAgents, - workingAgents, - activeWorkingChannels, -}: { - currentPubkey: string | undefined; - managedAgents: readonly WorkingAgent[]; - relayAgents: readonly Pick[]; - profiles: Record | undefined; - ownedRelayAgents: readonly WorkingAgent[]; - workingAgents: readonly WorkingAgent[]; - activeWorkingChannels: readonly ActiveChannelTurnSummary[]; -}) { - const normalizedProfiles = Object.fromEntries( - relayAgents.map((agent) => { - const pubkey = normalizePubkey(agent.pubkey); - const profile = profiles?.[pubkey] as PillDiagProfile | undefined; - return [ - pubkey, - summarizeRelayProfile(agent.pubkey, profile, currentPubkey), - ]; - }), - ); + for (const agent of ownedRelayAgents) { + const pubkey = normalizePubkey(agent.pubkey); + const managedAgent = mergedByPubkey.get(pubkey); + if (!managedAgent || !agentCanStartObserver(managedAgent)) { + mergedByPubkey.set(pubkey, agent); + } + } - console.groupCollapsed("[pill-diag] active working channels inputs", { - currentPubkey: currentPubkey ? normalizePubkey(currentPubkey) : null, - managedAgentCount: managedAgents.length, - relayAgentCount: relayAgents.length, - ownedRelayAgentCount: ownedRelayAgents.length, - workingAgentCount: workingAgents.length, - activeChannelCount: activeWorkingChannels.length, - }); - console.log("[pill-diag] currentPubkey", { - currentPubkey: currentPubkey ? normalizePubkey(currentPubkey) : null, - }); - console.table(managedAgents.map(summarizeAgent)); - console.log("[pill-diag] managedAgents", managedAgents.map(summarizeAgent)); - console.table(relayAgents.map(summarizeAgent)); - console.log("[pill-diag] relayAgents", relayAgents.map(summarizeAgent)); - console.log("[pill-diag] profilesByRelayAgent", normalizedProfiles); - console.table(ownedRelayAgents.map(summarizeAgent)); - console.log( - "[pill-diag] ownedRelayAgents", - ownedRelayAgents.map(summarizeAgent), - ); - console.table(workingAgents.map(summarizeAgent)); - console.log("[pill-diag] workingAgents", workingAgents.map(summarizeAgent)); - console.log("[pill-diag] activeWorkingChannels", activeWorkingChannels); - console.groupEnd(); + return [...mergedByPubkey.values()]; } export function useActiveWorkingChannelsById(): ReadonlyMap< string, ActiveChannelTurnSummary > { - pillDiagRenderCount += 1; - console.log("[pill-diag] hook render", { - renderCount: pillDiagRenderCount, - renderedAt: new Date().toISOString(), - }); - const identityQuery = useIdentityQuery(); const currentPubkey = identityQuery.data?.pubkey; const managedAgentsQuery = useManagedAgentsQuery(); @@ -214,26 +127,6 @@ export function useActiveWorkingChannelsById(): ReadonlyMap< const activeWorkingChannels = useActiveAgentTurnsByChannel(); - React.useEffect(() => { - logPillDiagnostics({ - currentPubkey, - managedAgents, - relayAgents, - profiles: relayAgentProfilesQuery.data?.profiles, - ownedRelayAgents, - workingAgents, - activeWorkingChannels, - }); - }, [ - activeWorkingChannels, - currentPubkey, - managedAgents, - ownedRelayAgents, - relayAgentProfilesQuery.data?.profiles, - relayAgents, - workingAgents, - ]); - return React.useMemo( () => new Map( From a8d6be013d5b6b64fcf5b4615454c03379df9705 Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Tue, 30 Jun 2026 21:32:58 -0700 Subject: [PATCH 9/9] chore(sidebar): trace post-fix working pill state Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../src/features/agents/observerRelayStore.ts | 17 +++ .../lib/useActiveWorkingChannelsById.ts | 103 ++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/desktop/src/features/agents/observerRelayStore.ts b/desktop/src/features/agents/observerRelayStore.ts index 6aa397edb..213cefa19 100644 --- a/desktop/src/features/agents/observerRelayStore.ts +++ b/desktop/src/features/agents/observerRelayStore.ts @@ -128,6 +128,16 @@ function observerTag(event: RelayEvent, tagName: string) { return event.tags.find((tag) => tag[0] === tagName)?.[1] ?? null; } +function pillDiagBridgeAgents( + agents: readonly Pick[], +) { + return agents.map((agent) => ({ + pubkey: normalizePubkey(agent.pubkey), + status: agent.status, + startsObserver: agent.status === "running" || agent.status === "deployed", + })); +} + function appendAgentEvent(agentPubkey: string, event: ObserverEvent) { const key = normalizePubkey(agentPubkey); const current = eventsByAgent.get(key) ?? []; @@ -390,6 +400,13 @@ export function useManagedAgentObserverBridge( [agents], ); + console.log("[pill-diag] observer bridge render", { + subscriptionId, + hasActiveAgent, + agents: pillDiagBridgeAgents(agents), + at: new Date().toISOString(), + }); + const agentPubkeys = React.useMemo( () => agents.map((agent) => agent.pubkey), [agents], diff --git a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts index dc0de7f4d..81aa5b323 100644 --- a/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts +++ b/desktop/src/features/sidebar/lib/useActiveWorkingChannelsById.ts @@ -22,6 +22,9 @@ import { normalizePubkey } from "@/shared/lib/pubkey"; type WorkingAgentName = Pick; type WorkingAgent = Pick; +type PillDiagProfile = UserProfileSummary & { + pubkey?: string; +}; type OwnedRelayWorkingAgent = Pick & { status: "deployed"; }; @@ -85,6 +88,86 @@ export function mergeWorkingAgents( return [...mergedByPubkey.values()]; } +function summarizeAgent(agent: WorkingAgentName & { status?: string }) { + return { + pubkey: normalizePubkey(agent.pubkey), + name: agent.name, + status: agent.status ?? null, + }; +} + +function summarizeRelayProfile( + pubkey: string, + profile: PillDiagProfile | undefined, + currentPubkey: string | undefined, +) { + return { + pubkey: normalizePubkey(pubkey), + profileKeyPubkey: profile?.pubkey ? normalizePubkey(profile.pubkey) : null, + displayName: profile?.displayName ?? null, + isAgent: profile?.isAgent ?? null, + ownerPubkey: profile?.ownerPubkey + ? normalizePubkey(profile.ownerPubkey) + : null, + ownsAuthorAgent: ownsAuthorAgent(profile, currentPubkey), + }; +} + +function logPillDiagnostics({ + currentPubkey, + managedAgents, + relayAgents, + profiles, + ownedRelayAgents, + workingAgents, + activeWorkingChannels, +}: { + currentPubkey: string | undefined; + managedAgents: readonly WorkingAgent[]; + relayAgents: readonly Pick[]; + profiles: Record | undefined; + ownedRelayAgents: readonly WorkingAgent[]; + workingAgents: readonly WorkingAgent[]; + activeWorkingChannels: readonly ActiveChannelTurnSummary[]; +}) { + const normalizedProfiles = Object.fromEntries( + relayAgents.map((agent) => { + const pubkey = normalizePubkey(agent.pubkey); + const profile = profiles?.[pubkey] as PillDiagProfile | undefined; + return [ + pubkey, + summarizeRelayProfile(agent.pubkey, profile, currentPubkey), + ]; + }), + ); + + console.groupCollapsed("[pill-diag] active working channels inputs", { + currentPubkey: currentPubkey ? normalizePubkey(currentPubkey) : null, + managedAgentCount: managedAgents.length, + relayAgentCount: relayAgents.length, + ownedRelayAgentCount: ownedRelayAgents.length, + workingAgentCount: workingAgents.length, + activeChannelCount: activeWorkingChannels.length, + }); + console.log("[pill-diag] currentPubkey", { + currentPubkey: currentPubkey ? normalizePubkey(currentPubkey) : null, + }); + console.table(managedAgents.map(summarizeAgent)); + console.log("[pill-diag] managedAgents", managedAgents.map(summarizeAgent)); + console.table(relayAgents.map(summarizeAgent)); + console.log("[pill-diag] relayAgents", relayAgents.map(summarizeAgent)); + console.log("[pill-diag] profilesByRelayAgent", normalizedProfiles); + console.table(ownedRelayAgents.map(summarizeAgent)); + console.log( + "[pill-diag] ownedRelayAgents", + ownedRelayAgents.map(summarizeAgent), + ); + console.table(workingAgents.map(summarizeAgent)); + console.log("[pill-diag] workingAgents", workingAgents.map(summarizeAgent)); + console.log("[pill-diag] activeWorkingChannels", activeWorkingChannels); + console.groupEnd(); +} + export function useActiveWorkingChannelsById(): ReadonlyMap< string, ActiveChannelTurnSummary @@ -127,6 +210,26 @@ export function useActiveWorkingChannelsById(): ReadonlyMap< const activeWorkingChannels = useActiveAgentTurnsByChannel(); + React.useEffect(() => { + logPillDiagnostics({ + currentPubkey, + managedAgents, + relayAgents, + profiles: relayAgentProfilesQuery.data?.profiles, + ownedRelayAgents, + workingAgents, + activeWorkingChannels, + }); + }, [ + activeWorkingChannels, + currentPubkey, + managedAgents, + ownedRelayAgents, + relayAgentProfilesQuery.data?.profiles, + relayAgents, + workingAgents, + ]); + return React.useMemo( () => new Map(