diff --git a/desktop/scripts/check-file-sizes.mjs b/desktop/scripts/check-file-sizes.mjs index 5578f9f9e2..285a8610af 100644 --- a/desktop/scripts/check-file-sizes.mjs +++ b/desktop/scripts/check-file-sizes.mjs @@ -35,8 +35,8 @@ const overrides = new Map([ ["src/app/AppShell.tsx", 860], // message edit state + handlers + ChannelPane edit prop threading + scrollback pagination + workflows view + memory-leak safeguards ["src/features/channels/hooks.ts", 550], // canvas query + mutation hooks + DM hide mutation ["src/features/channels/ui/ChannelManagementSheet.tsx", 800], - ["src/features/channels/ui/ChannelPane.tsx", 520], // composer/timeline/sidebar orchestration + anchored agent activity footers - ["src/features/channels/ui/ChannelScreen.tsx", 550], // profile panel state + mutual exclusion wiring + ProfilePanelProvider context + agent typing classification + ["src/features/channels/ui/ChannelPane.tsx", 525], // composer/timeline/sidebar orchestration + anchored agent activity footers + ["src/features/channels/ui/ChannelScreen.tsx", 610], // profile panel state + mutual exclusion wiring + ProfilePanelProvider context + relay-aware agent typing classification ["src/features/messages/hooks.ts", 500], // message query/mutation hooks + optimistic updates ["src/features/messages/ui/MessageComposer.tsx", 700], // media upload handlers (paste, drop, dialog) + channelId reset effect + edit mode (pre-fill, save, cancel, escape) ["src/features/settings/ui/SettingsView.tsx", 600], diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 08abcdfa5d..5f861ea343 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; -use tauri::AppHandle; +use tauri::{AppHandle, Manager}; use crate::{ + app_state::AppState, managed_agents::{ append_log_marker, login_shell_path, managed_agent_log_path, missing_command_message, normalize_agent_args, open_log_file, resolve_command, ManagedAgentProcess, @@ -561,6 +562,11 @@ pub fn spawn_agent_child( } else { command.env_remove("SPROUT_AUTH_TAG"); } + if let Some(state) = app.try_state::() { + if let Ok(keys) = state.keys.lock() { + command.env("SPROUT_ACP_AGENT_OWNER", keys.public_key().to_hex()); + } + } command.env("SPROUT_ACP_RELAY_OBSERVER", "true"); diff --git a/desktop/src/features/channels/ui/ChannelScreen.tsx b/desktop/src/features/channels/ui/ChannelScreen.tsx index b85a826ce4..a8c5b2ea30 100644 --- a/desktop/src/features/channels/ui/ChannelScreen.tsx +++ b/desktop/src/features/channels/ui/ChannelScreen.tsx @@ -16,6 +16,7 @@ import { MembersSidebar } from "@/features/channels/ui/MembersSidebar"; import { useManagedAgentsQuery, usePersonasQuery, + useRelayAgentsQuery, } from "@/features/agents/hooks"; import { useManagedAgentObserverBridge } from "@/features/agents/observerRelayStore"; import { @@ -52,6 +53,7 @@ import { AgentSessionProvider } from "@/shared/context/AgentSessionContext"; import { ProfilePanelProvider } from "@/shared/context/ProfilePanelContext"; import { useChannelAgentSessions } from "./useChannelAgentSessions"; import { useChannelProfilePanel } from "./useChannelProfilePanel"; +import type { BotActivityAgent } from "./BotActivityBar"; type ChannelScreenProps = { activeChannel: Channel | null; currentIdentity?: Identity; @@ -165,46 +167,66 @@ export function ChannelScreen({ enabled: messageProfilePubkeys.length > 0, }); const managedAgentsQuery = useManagedAgentsQuery(); + const relayAgentsQuery = useRelayAgentsQuery(); useManagedAgentObserverBridge(managedAgentsQuery.data ?? []); const { botTypingEntries, humanTypingPubkeys } = React.useMemo<{ botTypingEntries: TypingIndicatorEntry[]; humanTypingPubkeys: string[]; }>(() => { - const localAgentSet = new Set( - (managedAgentsQuery.data ?? []) - .filter((agent) => agent.backend.type === "local") - .map((agent) => agent.pubkey.toLowerCase()), + const managedAgentSet = new Set( + (managedAgentsQuery.data ?? []).map((agent) => + agent.pubkey.toLowerCase(), + ), + ); + const relayAgentSet = new Set( + (relayAgentsQuery.data ?? []).map((agent) => agent.pubkey.toLowerCase()), ); const channelTypingEntries = typingEntries.filter( (entry) => entry.threadHeadId === null, ); - const agentTypingEntries = typingEntries.filter((entry) => - localAgentSet.has(entry.pubkey.toLowerCase()), + const agentTypingEntries = typingEntries.filter( + (entry) => + managedAgentSet.has(entry.pubkey.toLowerCase()) || + relayAgentSet.has(entry.pubkey.toLowerCase()), ); return { botTypingEntries: agentTypingEntries, humanTypingPubkeys: channelTypingEntries - .filter((entry) => !localAgentSet.has(entry.pubkey.toLowerCase())) + .filter( + (entry) => + !managedAgentSet.has(entry.pubkey.toLowerCase()) && + !relayAgentSet.has(entry.pubkey.toLowerCase()), + ) .map((entry) => entry.pubkey), }; - }, [managedAgentsQuery.data, typingEntries]); + }, [managedAgentsQuery.data, relayAgentsQuery.data, typingEntries]); const messageProfiles = React.useMemo(() => { const base = mergeCurrentProfileIntoLookup( messageProfilesQuery.data?.profiles, currentProfile, ) ?? {}; - // Merge managed agent names so system messages resolve instantly + // Merge agent names so system messages and activity pills resolve instantly // (without waiting for the relay profile batch query). - const agents = managedAgentsQuery.data ?? []; const merged = { ...base }; - for (const agent of agents) { + for (const agent of managedAgentsQuery.data ?? []) { const key = agent.pubkey.toLowerCase(); if (!merged[key]?.displayName) { merged[key] = { ...merged[key], displayName: agent.name, - avatarUrl: null, + avatarUrl: merged[key]?.avatarUrl ?? null, + nip05Handle: null, + }; + } + } + for (const agent of relayAgentsQuery.data ?? []) { + const key = agent.pubkey.toLowerCase(); + if (!merged[key]?.displayName) { + merged[key] = { + ...merged[key], + displayName: agent.name, + avatarUrl: merged[key]?.avatarUrl ?? null, nip05Handle: null, }; } @@ -214,9 +236,43 @@ export function ChannelScreen({ currentProfile, managedAgentsQuery.data, messageProfilesQuery.data?.profiles, + relayAgentsQuery.data, ]); const channelMembersQuery = useChannelMembersQuery(activeChannel?.id ?? null); const channelMembers = channelMembersQuery.data; + const activityAgents = React.useMemo(() => { + const byPubkey = new Map(); + for (const agent of managedAgentsQuery.data ?? []) { + byPubkey.set(agent.pubkey.toLowerCase(), { + name: agent.name, + pubkey: agent.pubkey, + }); + } + for (const agent of relayAgentsQuery.data ?? []) { + const key = agent.pubkey.toLowerCase(); + if (!byPubkey.has(key)) { + byPubkey.set(key, { name: agent.name, pubkey: agent.pubkey }); + } + } + for (const member of channelMembers ?? []) { + const key = member.pubkey.toLowerCase(); + if (member.role === "bot" && !byPubkey.has(key)) { + byPubkey.set(key, { + name: + member.displayName ?? + messageProfiles[key]?.displayName ?? + `Agent ${member.pubkey.slice(0, 8)}`, + pubkey: member.pubkey, + }); + } + } + return [...byPubkey.values()]; + }, [ + channelMembers, + managedAgentsQuery.data, + messageProfiles, + relayAgentsQuery.data, + ]); const personasQuery = usePersonasQuery(); const personaLookup = React.useMemo(() => { const agents = managedAgentsQuery.data ?? []; @@ -470,6 +526,7 @@ export function ChannelScreen({ }> normalizePubkey(member.pubkey)), ); - return managedAgents.filter( - (agent) => - agent.backend.type === "local" && - memberPubkeys.has(normalizePubkey(agent.pubkey)), + return managedAgents.filter((agent) => + memberPubkeys.has(normalizePubkey(agent.pubkey)), ); }, [channelMembers, managedAgents]);