fix(desktop): show NIP-OA owners in profile pane (#1198)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Wes
2026-06-23 09:07:50 -07:00
committed by GitHub
co-authored by Pinky
parent 29978b6f93
commit 40070a5855
16 changed files with 183 additions and 34 deletions
@@ -296,5 +296,6 @@ fn empty_profile_info(pubkey: &str) -> ProfileInfo {
avatar_url: None,
about: None,
nip05_handle: None,
owner_pubkey: None,
}
}
+3
View File
@@ -15,6 +15,7 @@ pub struct ProfileInfo {
pub avatar_url: Option<String>,
pub about: Option<String>,
pub nip05_handle: Option<String>,
pub owner_pubkey: Option<String>,
}
#[derive(Serialize, Deserialize)]
@@ -22,6 +23,7 @@ pub struct UserProfileSummaryInfo {
pub display_name: Option<String>,
pub avatar_url: Option<String>,
pub nip05_handle: Option<String>,
pub owner_pubkey: Option<String>,
#[serde(default)]
pub is_agent: bool,
}
@@ -38,6 +40,7 @@ pub struct UserSearchResultInfo {
pub display_name: Option<String>,
pub avatar_url: Option<String>,
pub nip05_handle: Option<String>,
pub owner_pubkey: Option<String>,
#[serde(default)]
pub is_agent: bool,
}
+32 -11
View File
@@ -55,16 +55,16 @@ fn tags_named<'a>(event: &'a Event, name: &'a str) -> impl Iterator<Item = &'a [
})
}
/// Return true when a kind:0 profile carries a valid NIP-OA owner tag.
/// Return the owner pubkey from a valid NIP-OA owner tag on a kind:0 profile.
///
/// NIP-OA marks an agent identity by having the owner sign an `auth` tag for
/// the agent pubkey. We verify the tag against the profile event author, not
/// against the owner, so a forged or stale marker does not turn a person into
/// an agent in mention search.
pub(crate) fn profile_has_valid_oa_owner(event: &Event) -> bool {
pub(crate) fn profile_valid_oa_owner_pubkey(event: &Event) -> Option<String> {
let target_hex = event.pubkey.to_hex();
let Ok(target_pubkey) = nostr::PublicKey::from_hex(&target_hex) else {
return false;
return None;
};
for tag in event.tags.iter() {
@@ -75,12 +75,16 @@ pub(crate) fn profile_has_valid_oa_owner(event: &Event) -> bool {
let Ok(json) = serde_json::to_string(slice) else {
continue;
};
if buzz_sdk_pkg::nip_oa::verify_auth_tag(&json, &target_pubkey).is_ok() {
return true;
if let Ok(owner_pubkey) = buzz_sdk_pkg::nip_oa::verify_auth_tag(&json, &target_pubkey) {
return Some(owner_pubkey.to_hex());
}
}
false
None
}
pub(crate) fn profile_has_valid_oa_owner(event: &Event) -> bool {
profile_valid_oa_owner_pubkey(event).is_some()
}
// ── kind:39000 / 39002 (NIP-29) ─────────────────────────────────────────────
@@ -299,6 +303,7 @@ pub fn profile_info_from_event(event: &Event) -> Result<ProfileInfo, String> {
avatar_url,
about,
nip05_handle,
owner_pubkey: profile_valid_oa_owner_pubkey(event),
})
}
@@ -326,6 +331,7 @@ pub fn users_batch_from_events(
let mut profiles = HashMap::new();
for (pk, ev) in &latest {
let v: Value = serde_json::from_str(&ev.content).unwrap_or(Value::Null);
let owner_pubkey = profile_valid_oa_owner_pubkey(ev);
let summary = UserProfileSummaryInfo {
display_name: v
.get("display_name")
@@ -334,7 +340,8 @@ pub fn users_batch_from_events(
.map(str::to_string),
avatar_url: v.get("picture").and_then(Value::as_str).map(str::to_string),
nip05_handle: v.get("nip05").and_then(Value::as_str).map(str::to_string),
is_agent: profile_has_valid_oa_owner(ev),
is_agent: owner_pubkey.is_some(),
owner_pubkey,
};
profiles.insert(pk.clone(), summary);
}
@@ -586,7 +593,7 @@ mod tests {
}
/// Build a kind:0 profile with a valid NIP-OA auth tag.
fn oa_profile_event(content: &str) -> Event {
fn oa_profile_event(content: &str) -> (Event, String) {
let agent_keys = Keys::generate();
let owner_keys = Keys::generate();
let agent_pubkey = agent_keys.public_key();
@@ -595,10 +602,11 @@ mod tests {
let tag_values: Vec<String> = serde_json::from_str(&tag_json).expect("parse auth tag json");
let auth_tag = Tag::parse(tag_values).expect("parse auth tag");
EventBuilder::new(Kind::Metadata, content)
let event = EventBuilder::new(Kind::Metadata, content)
.tags(vec![auth_tag])
.sign_with_keys(&agent_keys)
.expect("sign")
.expect("sign");
(event, owner_keys.public_key().to_hex())
}
#[test]
@@ -760,6 +768,15 @@ mod tests {
assert_eq!(p.about.as_deref(), Some("hi"));
assert_eq!(p.nip05_handle.as_deref(), Some("alice@x"));
assert_eq!(p.pubkey, e.pubkey.to_hex());
assert!(p.owner_pubkey.is_none());
}
#[test]
fn profile_info_extracts_valid_nip_oa_owner() {
let (event, owner_pubkey) = oa_profile_event(r#"{"display_name":"Mira"}"#);
let p = profile_info_from_event(&event).unwrap();
assert_eq!(p.owner_pubkey.as_deref(), Some(owner_pubkey.as_str()));
}
#[test]
@@ -803,12 +820,16 @@ mod tests {
#[test]
fn users_batch_marks_valid_nip_oa_profiles_as_agents() {
let agent = oa_profile_event(r#"{"display_name":"Mira"}"#);
let (agent, owner_pubkey) = oa_profile_event(r#"{"display_name":"Mira"}"#);
let pubkey = agent.pubkey.to_hex();
let resp =
users_batch_from_events(std::slice::from_ref(&agent), std::slice::from_ref(&pubkey));
assert!(resp.profiles[&pubkey].is_agent);
assert_eq!(
resp.profiles[&pubkey].owner_pubkey.as_deref(),
Some(owner_pubkey.as_str())
);
}
#[test]
@@ -5,11 +5,12 @@ use serde_json::Value;
use crate::models::{SearchUsersResponse, UserSearchResultInfo};
use super::profile_has_valid_oa_owner;
use super::profile_valid_oa_owner_pubkey;
/// Convert a single kind:0 event to a [`UserSearchResultInfo`].
pub fn user_search_result_from_event(ev: &Event) -> UserSearchResultInfo {
let v: Value = serde_json::from_str(&ev.content).unwrap_or(Value::Null);
let owner_pubkey = profile_valid_oa_owner_pubkey(ev);
UserSearchResultInfo {
pubkey: ev.pubkey.to_hex(),
display_name: v
@@ -19,7 +20,8 @@ pub fn user_search_result_from_event(ev: &Event) -> UserSearchResultInfo {
.map(str::to_string),
avatar_url: v.get("picture").and_then(Value::as_str).map(str::to_string),
nip05_handle: v.get("nip05").and_then(Value::as_str).map(str::to_string),
is_agent: profile_has_valid_oa_owner(ev),
is_agent: owner_pubkey.is_some(),
owner_pubkey,
}
}
@@ -224,9 +226,15 @@ mod tests {
#[test]
fn user_search_result_marks_valid_nip_oa_profile_as_agent() {
let event = oa_profile_event(r#"{"display_name":"Mira"}"#);
let owner_pubkey = event
.tags
.iter()
.find_map(|tag| tag.as_slice().get(1).cloned())
.expect("owner pubkey");
let result = user_search_result_from_event(&event);
assert_eq!(result.display_name.as_deref(), Some("Mira"));
assert_eq!(result.owner_pubkey.as_deref(), Some(owner_pubkey.as_str()));
assert!(result.is_agent);
}
@@ -948,6 +948,7 @@ export const ChannelPane = React.memo(function ChannelPane({
layout={useSplitAuxiliaryPane ? "split" : "standalone"}
onClose={onCloseProfilePanel}
onOpenDm={onOpenDm}
onOpenProfile={onOpenProfilePanel}
onViewChange={onProfilePanelViewChange}
pubkey={profilePanelPubkey}
splitPaneClamp
@@ -247,6 +247,7 @@ export function MembersSidebar({
? currentName
: (currentName ?? candidateName),
nip05Handle: current.nip05Handle ?? candidate.nip05Handle ?? null,
ownerPubkey: current.ownerPubkey ?? candidate.ownerPubkey ?? null,
isAgent: current.isAgent || candidate.isAgent,
});
};
@@ -265,6 +266,7 @@ export function MembersSidebar({
displayName: agent.name,
avatarUrl: null,
nip05Handle: null,
ownerPubkey: null,
isAgent: true,
});
}
@@ -275,6 +277,7 @@ export function MembersSidebar({
displayName: agent.name,
avatarUrl: null,
nip05Handle: null,
ownerPubkey: null,
isAgent: true,
});
}
+1
View File
@@ -104,6 +104,7 @@ export function useProfileQuery(enabled = true) {
avatarUrl: cached.avatarUrl,
about: null,
nip05Handle: null,
ownerPubkey: null,
} satisfies Profile)
: undefined,
[cached, pubkey],
@@ -36,6 +36,8 @@ export function mergeCurrentProfileIntoLookup(
avatarUrl: currentProfile.avatarUrl,
nip05Handle: currentProfile.nip05Handle,
isAgent: profiles?.[normalizePubkey(currentProfile.pubkey)]?.isAgent,
ownerPubkey:
profiles?.[normalizePubkey(currentProfile.pubkey)]?.ownerPubkey ?? null,
},
};
}
@@ -13,6 +13,7 @@ function makeUser(overrides = {}) {
displayName: null,
isAgent: false,
nip05Handle: null,
ownerPubkey: null,
pubkey: "abcdef1234567890",
...overrides,
};
@@ -56,6 +56,7 @@ type UserProfilePanelProps = {
layout?: "standalone" | "split";
onClose: () => void;
onOpenDm?: (pubkeys: string[]) => void;
onOpenProfile?: (pubkey: string) => void;
onResetWidth?: () => void;
onResizeStart?: (event: React.PointerEvent<HTMLButtonElement>) => void;
onViewChange: (
@@ -136,6 +137,7 @@ export function UserProfilePanel({
layout = "standalone",
onClose,
onOpenDm,
onOpenProfile,
onResetWidth,
onResizeStart,
onViewChange,
@@ -172,6 +174,8 @@ export function UserProfilePanel({
const { goChannel } = useAppNavigation();
const profile = profileQuery.data;
const ownerPubkey = profile?.ownerPubkey ?? null;
const ownerProfileQuery = useUserProfileQuery(ownerPubkey ?? undefined);
const pubkeyLower = pubkey.toLowerCase();
const presenceStatus = presenceQuery.data?.[pubkeyLower];
const userStatus = userStatusQuery.data?.[pubkeyLower];
@@ -252,7 +256,16 @@ export function UserProfilePanel({
const displayName = profile?.displayName ?? truncatePubkey(pubkey);
const ownerHandle = React.useMemo(() => {
if (currentPubkey === undefined) {
if (ownerPubkey) {
const ownerProfile = ownerProfileQuery.data;
return (
ownerProfile?.nip05Handle?.trim() ||
ownerProfile?.displayName?.trim() ||
truncatePubkey(ownerPubkey)
);
}
if (currentPubkey === undefined || isOwner !== true) {
return null;
}
@@ -262,8 +275,22 @@ export function UserProfilePanel({
currentProfile?.displayName?.trim() ||
truncatePubkey(currentPubkey)
);
}, [currentProfileQuery.data, currentPubkey]);
const ownerDisplayName = ownerHandle ? `${ownerHandle} (you)` : null;
}, [
currentProfileQuery.data,
currentPubkey,
isOwner,
ownerProfileQuery.data,
ownerPubkey,
]);
const isCurrentUserOwner =
currentPubkey !== undefined &&
ownerPubkey !== null &&
ownerPubkey.toLowerCase() === currentPubkey.toLowerCase();
const ownerDisplayName = ownerHandle
? isCurrentUserOwner || (!ownerPubkey && isOwner === true)
? `${ownerHandle} (you)`
: ownerHandle
: null;
const panelTitle = VIEW_TITLES[view];
const memoryCount = memoryQuery.data
? (memoryQuery.data.core ? 1 : 0) + memoryQuery.data.memories.length
@@ -334,8 +361,15 @@ export function UserProfilePanel({
memoriesLoading={memoryQuery.isLoading}
memoryCount={memoryCount}
ownerDisplayName={ownerDisplayName}
ownerAvatarUrl={ownerProfileQuery.data?.avatarUrl ?? null}
ownerHandle={ownerHandle}
ownerPubkey={ownerPubkey}
onOpenChannels={() => onViewChange("channels")}
onOpenOwner={
ownerPubkey && onOpenProfile
? () => onOpenProfile(ownerPubkey)
: undefined
}
onOpenMemories={() => onViewChange("memories")}
onOpenDm={onOpenDm}
presenceLoaded={presenceQuery.isSuccess}
@@ -42,6 +42,7 @@ import { useFeatureEnabled } from "@/shared/features";
import { cn } from "@/shared/lib/cn";
import { useNow } from "@/shared/lib/useNow";
import { Badge } from "@/shared/ui/badge";
import { UserAvatar } from "@/shared/ui/UserAvatar";
const RUNTIME_LABELS: Record<string, string> = {
goose: "Goose",
@@ -80,8 +81,11 @@ export type ProfileSummaryViewProps = {
memoriesLoading: boolean;
memoryCount: number | undefined;
ownerDisplayName: string | null;
ownerAvatarUrl: string | null;
ownerHandle: string | null;
ownerPubkey: string | null;
onOpenChannels: () => void;
onOpenOwner?: () => void;
onOpenMemories: () => void;
onOpenDm?: (pubkeys: string[]) => void;
presenceLoaded: boolean;
@@ -112,8 +116,11 @@ export function ProfileSummaryView({
memoriesLoading,
memoryCount,
ownerDisplayName,
ownerAvatarUrl,
ownerHandle,
ownerPubkey,
onOpenChannels,
onOpenOwner,
onOpenMemories,
onOpenDm,
presenceLoaded,
@@ -134,11 +141,15 @@ export function ProfileSummaryView({
relayAgent,
isBot,
}),
...(isOwner === true
...(ownerDisplayName || isOwner === true
? buildOwnerFields({
includeOperationalFields: isOwner === true,
managedAgent,
ownerDisplayName,
ownerAvatarUrl,
ownerHandle,
ownerPubkey,
onOpenOwner,
presenceLoaded,
presenceStatus,
relayAgent,
@@ -594,16 +605,24 @@ function buildPublicFields({
}
function buildOwnerFields({
includeOperationalFields,
managedAgent,
ownerDisplayName,
ownerAvatarUrl,
ownerHandle,
ownerPubkey,
onOpenOwner,
presenceLoaded,
presenceStatus,
relayAgent,
}: {
includeOperationalFields: boolean;
managedAgent: ManagedAgent | undefined;
ownerDisplayName: string | null;
ownerAvatarUrl: string | null;
ownerHandle: string | null;
ownerPubkey: string | null;
onOpenOwner?: () => void;
presenceLoaded: boolean;
presenceStatus: "online" | "away" | "offline" | undefined;
relayAgent: RelayAgent | undefined;
@@ -612,14 +631,40 @@ function buildOwnerFields({
if (ownerDisplayName) {
fields.push({
copyValue: ownerHandle ?? undefined,
copyValue: onOpenOwner
? undefined
: (ownerPubkey ?? ownerHandle ?? undefined),
displayValue: ownerDisplayName,
displayNode: onOpenOwner ? (
<button
className="inline-flex max-w-full items-center gap-2 rounded text-left text-sm text-muted-foreground transition-colors hover:text-foreground hover:underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
onClick={(event) => {
event.stopPropagation();
onOpenOwner();
}}
title={ownerDisplayName}
type="button"
>
<UserAvatar
avatarUrl={ownerAvatarUrl}
className="shrink-0"
displayName={ownerHandle ?? ownerDisplayName}
size="xs"
testId="user-profile-owner-avatar"
/>
<span className="truncate">{ownerDisplayName}</span>
</button>
) : undefined,
icon: UserRound,
label: "Owned by",
testId: "user-profile-owned-by",
});
}
if (!includeOperationalFields) {
return fields;
}
if (managedAgent?.agentCommand) {
fields.push({
copyValue: managedAgent.agentCommand,
@@ -59,6 +59,7 @@ export function PulseScreen() {
currentPubkey={identityQuery.data?.pubkey}
onClose={handleCloseProfilePanel}
onOpenDm={handleOpenDm}
onOpenProfile={handleOpenProfilePanel}
onResetWidth={threadPanelWidth.onResetWidth}
onResizeStart={threadPanelWidth.onResizeStart}
onViewChange={handleProfilePanelViewChange}
@@ -316,6 +316,7 @@ export function NewDirectMessageDialog({
? currentName
: (currentName ?? candidateName),
nip05Handle: current.nip05Handle ?? candidate.nip05Handle ?? null,
ownerPubkey: current.ownerPubkey ?? candidate.ownerPubkey ?? null,
isAgent: current.isAgent || candidate.isAgent,
});
};
@@ -334,6 +335,7 @@ export function NewDirectMessageDialog({
displayName: agent.name,
avatarUrl: null,
nip05Handle: null,
ownerPubkey: null,
isAgent: true,
});
}
@@ -344,6 +346,7 @@ export function NewDirectMessageDialog({
displayName: agent.name,
avatarUrl: null,
nip05Handle: null,
ownerPubkey: null,
isAgent: true,
});
}
+6 -11
View File
@@ -56,12 +56,10 @@ type RawProfile = {
avatar_url: string | null;
about: string | null;
nip05_handle: string | null;
owner_pubkey: string | null;
};
type RawUserProfileSummary = {
display_name: string | null;
avatar_url: string | null;
nip05_handle: string | null;
type RawUserProfileSummary = Omit<RawProfile, "pubkey" | "about"> & {
is_agent?: boolean;
};
@@ -70,13 +68,7 @@ type RawUsersBatchResponse = {
missing: string[];
};
type RawUserSearchResult = {
pubkey: string;
display_name: string | null;
avatar_url: string | null;
nip05_handle: string | null;
is_agent?: boolean;
};
type RawUserSearchResult = RawUserProfileSummary & { pubkey: string };
type RawSearchUsersResponse = {
users: RawUserSearchResult[];
@@ -426,6 +418,7 @@ function fromRawProfile(profile: RawProfile): Profile {
avatarUrl: profile.avatar_url,
about: profile.about,
nip05Handle: profile.nip05_handle,
ownerPubkey: profile.owner_pubkey,
};
}
@@ -436,6 +429,7 @@ function fromRawUserProfileSummary(
displayName: profile.display_name,
avatarUrl: profile.avatar_url,
nip05Handle: profile.nip05_handle,
ownerPubkey: profile.owner_pubkey,
isAgent: profile.is_agent ?? false,
};
}
@@ -446,6 +440,7 @@ function fromRawUserSearchResult(user: RawUserSearchResult): UserSearchResult {
displayName: user.display_name,
avatarUrl: user.avatar_url,
nip05Handle: user.nip05_handle,
ownerPubkey: user.owner_pubkey,
isAgent: user.is_agent ?? false,
};
}
+3
View File
@@ -114,12 +114,14 @@ export type Profile = {
avatarUrl: string | null;
about: string | null;
nip05Handle: string | null;
ownerPubkey: string | null;
};
export type UserProfileSummary = {
displayName: string | null;
avatarUrl: string | null;
nip05Handle: string | null;
ownerPubkey: string | null;
isAgent?: boolean;
};
@@ -133,6 +135,7 @@ export type UserSearchResult = {
displayName: string | null;
avatarUrl: string | null;
nip05Handle: string | null;
ownerPubkey: string | null;
isAgent: boolean;
};
+32 -5
View File
@@ -52,6 +52,7 @@ type MockSearchProfileSeed = {
avatarUrl?: string | null;
nip05Handle?: string | null;
about?: string | null;
ownerPubkey?: string | null;
isAgent?: boolean;
};
@@ -136,6 +137,7 @@ type RawProfile = {
avatar_url: string | null;
about: string | null;
nip05_handle: string | null;
owner_pubkey: string | null;
is_agent?: boolean;
};
@@ -143,6 +145,7 @@ type RawUserProfileSummary = {
display_name: string | null;
avatar_url: string | null;
nip05_handle: string | null;
owner_pubkey: string | null;
is_agent?: boolean;
};
@@ -156,6 +159,7 @@ type RawUserSearchResult = {
display_name: string | null;
avatar_url: string | null;
nip05_handle: string | null;
owner_pubkey: string | null;
is_agent?: boolean;
};
@@ -1118,6 +1122,7 @@ function seedMockSearchProfiles(config?: E2eConfig) {
avatar_url: seed.avatarUrl ?? null,
about: seed.about ?? null,
nip05_handle: seed.nip05Handle ?? null,
owner_pubkey: seed.ownerPubkey ?? null,
is_agent: seed.isAgent ?? false,
};
mockProfiles.set(pubkey, profile);
@@ -1145,6 +1150,7 @@ function getMockProfileByPubkey(pubkey: string): RawProfile | null {
avatar_url: null,
about: null,
nip05_handle: null,
owner_pubkey: null,
is_agent: mockAgentPubkeys.has(normalizedPubkey),
};
}
@@ -1811,6 +1817,7 @@ const mockProfiles = new Map<string, RawProfile>([
avatar_url: null,
about: null,
nip05_handle: null,
owner_pubkey: null,
is_agent: false,
},
],
@@ -1822,6 +1829,7 @@ const mockProfiles = new Map<string, RawProfile>([
avatar_url: null,
about: null,
nip05_handle: null,
owner_pubkey: MOCK_IDENTITY_PUBKEY,
is_agent: true,
},
],
@@ -1947,6 +1955,7 @@ function importMockIdentity(nsec: string) {
avatar_url: null,
about: null,
nip05_handle: null,
owner_pubkey: null,
});
}
@@ -1993,6 +2002,7 @@ function ensureMockProfile(config: E2eConfig | undefined): RawProfile {
avatar_url: null,
about: null,
nip05_handle: null,
owner_pubkey: null,
};
mockProfiles.set(pubkey, profile);
return profile;
@@ -3078,7 +3088,8 @@ async function handleGetProfile(config: E2eConfig | undefined) {
display_name: null,
about: null,
avatar_url: null,
nip05: null,
nip05_handle: null,
owner_pubkey: null,
};
}
const content = JSON.parse(events[0].content ?? "{}");
@@ -3087,7 +3098,8 @@ async function handleGetProfile(config: E2eConfig | undefined) {
display_name: content.display_name ?? content.name ?? null,
about: content.about ?? null,
avatar_url: content.picture ?? null,
nip05: content.nip05 ?? null,
nip05_handle: content.nip05 ?? null,
owner_pubkey: null,
};
}
@@ -3170,7 +3182,8 @@ async function handleUpdateProfile(
display_name: updated.display_name ?? null,
about: updated.about ?? null,
avatar_url: updated.picture ?? null,
nip05: updated.nip05 ?? null,
nip05_handle: updated.nip05 ?? null,
owner_pubkey: null,
};
}
@@ -3201,7 +3214,8 @@ async function handleGetUserProfile(
display_name: null,
about: null,
avatar_url: null,
nip05: null,
nip05_handle: null,
owner_pubkey: null,
};
}
const content = JSON.parse(events[0].content ?? "{}");
@@ -3210,7 +3224,8 @@ async function handleGetUserProfile(
display_name: content.display_name ?? content.name ?? null,
about: content.about ?? null,
avatar_url: content.picture ?? null,
nip05: content.nip05 ?? null,
nip05_handle: content.nip05 ?? null,
owner_pubkey: null,
};
}
@@ -3238,6 +3253,7 @@ async function handleGetUsersBatch(
display_name: profile.display_name,
avatar_url: profile.avatar_url,
nip05_handle: profile.nip05_handle,
owner_pubkey: profile.owner_pubkey,
is_agent: profile.is_agent ?? false,
};
}
@@ -3261,6 +3277,10 @@ async function handleGetUsersBatch(
display_name: content.display_name ?? content.name ?? null,
avatar_url: content.picture ?? null,
nip05_handle: content.nip05 ?? null,
owner_pubkey:
((ev.tags ?? []) as string[][]).find(
(tag) => Array.isArray(tag) && tag[0] === "auth" && tag.length === 4,
)?.[1] ?? null,
is_agent: Array.isArray(ev.tags)
? ev.tags.some(
(tag) =>
@@ -3285,6 +3305,7 @@ async function handleGetUsersBatch(
display_name: profile.display_name,
avatar_url: profile.avatar_url,
nip05_handle: profile.nip05_handle,
owner_pubkey: profile.owner_pubkey,
is_agent: profile.is_agent ?? false,
};
}
@@ -3337,6 +3358,7 @@ async function handleSearchUsers(
display_name: profile.display_name,
avatar_url: profile.avatar_url,
nip05_handle: profile.nip05_handle,
owner_pubkey: profile.owner_pubkey,
is_agent: profile.is_agent ?? false,
}));
@@ -3360,6 +3382,10 @@ async function handleSearchUsers(
display_name: content.display_name ?? content.name ?? null,
avatar_url: content.picture ?? null,
nip05_handle: content.nip05 ?? null,
owner_pubkey:
((ev.tags ?? []) as string[][]).find(
(tag) => Array.isArray(tag) && tag[0] === "auth" && tag.length === 4,
)?.[1] ?? null,
is_agent: Array.isArray(ev.tags)
? ev.tags.some(
(tag) =>
@@ -5053,6 +5079,7 @@ async function handleCreateManagedAgent(
avatar_url: avatarUrl,
about: args.input.systemPrompt?.trim() || null,
nip05_handle: null,
owner_pubkey: MOCK_IDENTITY_PUBKEY,
is_agent: true,
});
syncMockRelayAgentsFromManagedAgents();