Preserve managed agent mentions during relay errors (#6167)

## Summary

- preserve selected managed-agent `p` tags when fresh managed-directory
evidence succeeds but relay discovery or owner-profile lookup fails
- keep relay-only agents fail-closed unless fresh relay evidence and any
required owner proof are available
- cover selective admission with focused unit tests and a signed-event
Playwright regression

## Testing

- `node --import ./desktop/test-loader.mjs --experimental-strip-types
--test
desktop/src/features/messages/lib/agentMentionRevalidation.test.mjs` (7
passed)
- focused Playwright regression plus adjacent relay-revocation case (2
passed)
- pre-commit desktop Biome/file-size hook
- pre-push desktop check, TypeScript typecheck, and full desktop unit
suite (4,987 passed)

Fixes #6147

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
Wes
2026-08-17 16:27:37 -06:00
committed by GitHub
parent f7a01bda7b
commit 7f61cf431a
3 changed files with 119 additions and 12 deletions

View File

@@ -7,6 +7,7 @@ const CURRENT = "a".repeat(64);
const AGENT = "b".repeat(64);
const HUMAN = "c".repeat(64);
const OTHER_OWNER = "d".repeat(64);
const LOCAL_AGENT = "e".repeat(64);
function options(refetchOwnerProfiles) {
return {
@@ -49,6 +50,62 @@ test("owner-only revalidation admits an agent only from a fresh same-owner proof
assert.deepEqual(result, [HUMAN, AGENT]);
});
test("fresh managed evidence survives unrelated relay authorization errors", async () => {
const result = await revalidateAgentMentionPubkeys({
...options(async () => {
throw new Error("owner profiles unavailable");
}),
pubkeys: [HUMAN, LOCAL_AGENT],
agentPubkeys: new Set([LOCAL_AGENT]),
refetchManagedAgents: async () => ({
data: [{ pubkey: LOCAL_AGENT }],
error: null,
}),
refetchRelayAgents: async () => ({
data: undefined,
error: new Error("relay directory unavailable"),
}),
});
assert.deepEqual(result, [HUMAN, LOCAL_AGENT]);
});
test("relay-only agents still fail closed when relay discovery fails", async () => {
const result = await revalidateAgentMentionPubkeys({
...options(async () => ({
profiles: { [AGENT]: { ownerPubkey: CURRENT } },
missing: [],
})),
refetchRelayAgents: async () => ({
data: undefined,
error: new Error("relay directory unavailable"),
}),
});
assert.deepEqual(result, [HUMAN]);
});
test("mixed evidence preserves only fresh managed agents and humans", async () => {
const result = await revalidateAgentMentionPubkeys({
...options(async () => ({
profiles: { [AGENT]: { ownerPubkey: CURRENT } },
missing: [LOCAL_AGENT],
})),
pubkeys: [HUMAN, LOCAL_AGENT, AGENT],
agentPubkeys: new Set([LOCAL_AGENT, AGENT]),
refetchManagedAgents: async () => ({
data: [{ pubkey: LOCAL_AGENT }],
error: null,
}),
refetchRelayAgents: async () => ({
data: undefined,
error: new Error("relay directory unavailable"),
}),
});
assert.deepEqual(result, [HUMAN, LOCAL_AGENT]);
});
for (const [name, refetchOwnerProfiles] of [
["revoked owner proof", async () => ({ profiles: {}, missing: [AGENT] })],
[

View File

@@ -57,14 +57,13 @@ export async function revalidateAgentMentionPubkeys({
? refetchOwnerProfiles([...requestedAgentPubkeys]).catch(() => null)
: Promise.resolve(null),
]);
const relayDirectoryReady =
relayResult.error === null && relayResult.data !== undefined;
if (
managedResult.error !== null ||
relayResult.error !== null ||
managedResult.data === undefined ||
relayResult.data === undefined ||
ownerOnly === undefined ||
ownerPolicyError !== null ||
(ownerOnly && ownerProfiles === null)
managedResult.error !== null ||
managedResult.data === undefined
) {
return filterAdmittedMentionPubkeys(pubkeys, agentPubkeys, new Set());
}
@@ -76,23 +75,28 @@ export async function revalidateAgentMentionPubkeys({
currentPubkey,
eligibilityScope,
managedAgentPubkeys: managedPubkeys,
relayAgents: relayResult.data,
relayAgents: relayDirectoryReady ? relayResult.data : [],
sharedChannelIds,
});
const admittedPubkeys = new Set(
[...agentPubkeys].filter(
(pubkey) =>
[...agentPubkeys].filter((pubkey) => {
const isManagedAgent = managedPubkeys.has(normalizePubkey(pubkey));
const directoryReady =
isManagedAgent ||
(relayDirectoryReady && (!ownerOnly || ownerProfiles !== null));
return (
getAgentMentionAdmission({
isAgent: true,
isManagedAgent: managedPubkeys.has(pubkey),
isManagedAgent,
pubkey,
ownerPubkey: ownerProfiles?.profiles[pubkey]?.ownerPubkey,
currentPubkey,
mentionableAgentPubkeys: mentionablePubkeys,
directoryReady: true,
directoryReady,
ownerOnly,
}) === "allow",
),
}) === "allow"
);
}),
);
return filterAdmittedMentionPubkeys(pubkeys, agentPubkeys, admittedPubkeys);
}

View File

@@ -1214,6 +1214,52 @@ test("relay-only allowlisted agents emit a p tag when sent", async ({
.toContain(ALLOWLIST_RELAY_AGENT_PUBKEY);
});
test("managed agents keep their p tag when relay discovery fails before send", async ({
page,
}) => {
await installMockBridge(page, {
managedAgents: [
{
pubkey: ALLOWLIST_RELAY_AGENT_PUBKEY,
name: "quinn",
status: "running",
},
],
relayAgents: [
{
pubkey: ALLOWLIST_RELAY_AGENT_PUBKEY,
name: "quinn",
respondTo: "allowlist",
respondToAllowlist: [MOCK_VIEWER_PUBKEY],
channelNames: ["general"],
},
],
});
await page.goto("/");
await page.getByTestId("channel-general").click();
const input = page.getByTestId("message-input");
await input.fill("@quinn");
const quinnRow = autocomplete(page).locator("button", { hasText: "quinn" });
await expect(quinnRow).toBeVisible();
await quinnRow.click();
await expect(input).toHaveText("@quinn ");
await page.keyboard.type("hello");
await expect(input).toHaveText("@quinn hello");
await page.evaluate(() => {
window.__BUZZ_E2E__.mock ??= {};
window.__BUZZ_E2E__.mock.relayAgentListErrors = Array(5).fill(
"mock unrelated relay directory failure",
);
});
await page.getByTestId("send-message").click();
await expect
.poll(() => readOutgoingMentionPubkeys(page, "@quinn hello"))
.toContain(ALLOWLIST_RELAY_AGENT_PUBKEY);
});
test("selected relay agents revoked before send emit no p tag", async ({
page,
}) => {