Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions desktop/src-tauri/src/managed_agents/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{collections::BTreeMap, path::PathBuf, process::Child};

use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, path::PathBuf, process::Child};

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
Expand Down Expand Up @@ -80,8 +79,9 @@ pub struct RelayAgentInfo {
pub status: String,
#[serde(default)]
pub respond_to: Option<RespondTo>,
#[serde(default)]
pub respond_to_allowlist: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManagedAgentRecord {
pub pubkey: String,
Expand Down
20 changes: 20 additions & 0 deletions desktop/src-tauri/src/nostr_convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,26 @@ mod tests {
);
}

#[test]
fn agents_preserves_allowlist_metadata_for_directory_parse() {
let e = ev(
10100,
r#"{"name":"Scout","respond_to":"allowlist","respond_to_allowlist":["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]}"#,
vec![],
);
let v = agents_from_events(std::slice::from_ref(&e));
let agents = v.get("agents").cloned().unwrap();
let parsed: Vec<crate::managed_agents::RelayAgentInfo> =
serde_json::from_value(agents).unwrap();

assert_eq!(parsed.len(), 1);
assert_eq!(
parsed[0].respond_to,
Some(crate::managed_agents::RespondTo::Allowlist)
);
assert_eq!(parsed[0].respond_to_allowlist, vec!["a".repeat(64)]);
}

#[test]
fn relay_members_dedupes_and_defaults_role() {
let pk1 = "a".repeat(64);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import assert from "node:assert/strict";
import test from "node:test";

import {
coalesceAgentAutocompleteCandidates,
getMentionableAgentPubkeys,
getSharedChannelIds,
relayAgentIsSharedWithUser,
} from "./agentAutocompleteEligibility.ts";

const CURRENT_PUBKEY = "a".repeat(64);
const OWNER_PUBKEY = "b".repeat(64);
const OTHER_OWNER_PUBKEY = "c".repeat(64);
const PUB_A = "1".repeat(64);
const PUB_B = "2".repeat(64);
const PUB_C = "3".repeat(64);
const PUB_D = "4".repeat(64);

function coalesce(candidates, options = {}) {
return coalesceAgentAutocompleteCandidates(candidates, {
currentPubkey: CURRENT_PUBKEY,
getLabel: (candidate) => candidate.displayName,
...options,
});
}

function makeAgent(overrides = {}) {
return {
pubkey: PUB_A,
displayName: "Pinky",
isAgent: true,
isMember: false,
...overrides,
};
}

test("getSharedChannelIds: includes only active joined channels", () => {
assert.deepEqual(
getSharedChannelIds([
{ id: "joined", isMember: true, archivedAt: null },
{ id: "not-joined", isMember: false, archivedAt: null },
{ id: "archived", isMember: true, archivedAt: "2026-01-01T00:00:00Z" },
]),
new Set(["joined"]),
);
});

test("relayAgentIsSharedWithUser: accepts shared anyone agents and rejects unshared ones", () => {
const sharedChannelIds = new Set(["general"]);

assert.equal(
relayAgentIsSharedWithUser(
{ respondTo: "anyone", respondToAllowlist: [], channelIds: ["general"] },
sharedChannelIds,
),
true,
);
assert.equal(
relayAgentIsSharedWithUser(
{
respondTo: "owner-only",
respondToAllowlist: [],
channelIds: ["general"],
},
sharedChannelIds,
),
false,
);
assert.equal(
relayAgentIsSharedWithUser(
{ respondTo: "anyone", respondToAllowlist: [], channelIds: ["other"] },
sharedChannelIds,
),
false,
);
});

test("relayAgentIsSharedWithUser: accepts allowlist agents for the current user", () => {
const sharedChannelIds = new Set(["general"]);

assert.equal(
relayAgentIsSharedWithUser(
{
respondTo: "allowlist",
respondToAllowlist: [OTHER_OWNER_PUBKEY, CURRENT_PUBKEY.toUpperCase()],
channelIds: ["other"],
},
sharedChannelIds,
CURRENT_PUBKEY,
),
true,
);
assert.equal(
relayAgentIsSharedWithUser(
{
respondTo: "allowlist",
respondToAllowlist: [OTHER_OWNER_PUBKEY],
channelIds: ["general"],
},
sharedChannelIds,
CURRENT_PUBKEY,
),
false,
);
});

test("getMentionableAgentPubkeys: keeps managed agents and shared relay agents", () => {
const result = getMentionableAgentPubkeys({
managedAgentPubkeys: [PUB_A],
currentPubkey: CURRENT_PUBKEY,
relayAgents: [
{
pubkey: PUB_B,
respondTo: "anyone",
respondToAllowlist: [],
channelIds: ["general"],
},
{
pubkey: PUB_C,
respondTo: "allowlist",
respondToAllowlist: [CURRENT_PUBKEY],
channelIds: ["other"],
},
{
pubkey: PUB_D,
respondTo: "anyone",
respondToAllowlist: [],
channelIds: ["other"],
},
],
sharedChannelIds: new Set(["general"]),
});

assert.deepEqual(result, new Set([PUB_A, PUB_B, PUB_C]));
});

test("coalesceAgentAutocompleteCandidates: merges agents with the same persona id", () => {
const first = makeAgent({ pubkey: PUB_A, personaId: "pinky" });
const second = makeAgent({
pubkey: PUB_B,
personaId: "pinky",
isMember: true,
});

assert.deepEqual(coalesce([first, second]), [second]);
});

test("coalesceAgentAutocompleteCandidates: merges agents with the same owner and name", () => {
const first = makeAgent({ pubkey: PUB_A, ownerPubkey: OWNER_PUBKEY });
const second = makeAgent({
pubkey: PUB_B,
ownerPubkey: OWNER_PUBKEY,
isMember: true,
});

assert.deepEqual(coalesce([first, second]), [second]);
});

test("coalesceAgentAutocompleteCandidates: keeps same-name agents with different owners distinct", () => {
const first = makeAgent({ pubkey: PUB_A, ownerPubkey: OWNER_PUBKEY });
const second = makeAgent({
pubkey: PUB_B,
ownerPubkey: OTHER_OWNER_PUBKEY,
});

assert.deepEqual(coalesce([first, second]), [first, second]);
});

test("coalesceAgentAutocompleteCandidates: keeps owner-less same-name agents distinct", () => {
const first = makeAgent({ pubkey: PUB_A });
const second = makeAgent({ pubkey: PUB_B });

assert.deepEqual(coalesce([first, second]), [first, second]);
});

test("coalesceAgentAutocompleteCandidates: keeps owner-less managed same-name agents distinct", () => {
const first = makeAgent({ pubkey: PUB_A, isManagedAgent: true });
const second = makeAgent({ pubkey: PUB_B, isManagedAgent: true });

assert.deepEqual(coalesce([first, second]), [first, second]);
});

test("coalesceAgentAutocompleteCandidates: merges current-owner same-name agents", () => {
const first = makeAgent({ pubkey: PUB_A, ownerPubkey: CURRENT_PUBKEY });
const second = makeAgent({
pubkey: PUB_B,
ownerPubkey: CURRENT_PUBKEY,
isManagedAgent: true,
});

assert.deepEqual(coalesce([first, second]), [second]);
});

test("coalesceAgentAutocompleteCandidates: leaves non-agents alone", () => {
const first = makeAgent({ pubkey: PUB_A, isAgent: false });
const second = makeAgent({ pubkey: PUB_B, isAgent: false });

assert.deepEqual(coalesce([first, second]), [first, second]);
});
Loading