From 6fc10fe3eb1a71fbb5f7e35a87ecba73ed64c87d Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Thu, 23 Jul 2026 12:27:56 -0400 Subject: [PATCH 1/8] fix(desktop): explain open agent access Signed-off-by: David Hamilton --- desktop/playwright.config.ts | 1 + desktop/src/features/agents/AGENTS.md | 9 + .../agents/ui/AgentInstanceEditDialog.tsx | 2 +- .../src/features/agents/ui/RespondToField.tsx | 59 ++++--- .../agents/ui/respondToFieldContract.test.mjs | 47 +++++ .../channels/ui/EditRespondToDialog.tsx | 6 +- .../channels/ui/MembersSidebarMemberCard.tsx | 6 +- .../src/features/messages/ui/MessageRow.tsx | 30 ++-- .../profile/ui/UserProfilePanelFields.tsx | 12 +- .../tests/e2e/agent-access-warning.spec.ts | 161 ++++++++++++++++++ desktop/tests/e2e/profile.spec.ts | 2 +- 11 files changed, 292 insertions(+), 43 deletions(-) create mode 100644 desktop/src/features/agents/ui/respondToFieldContract.test.mjs create mode 100644 desktop/tests/e2e/agent-access-warning.spec.ts diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts index 459fa75743..c2eec84a83 100644 --- a/desktop/playwright.config.ts +++ b/desktop/playwright.config.ts @@ -121,6 +121,7 @@ export default defineConfig({ "**/signout-confirmation.spec.ts", "**/agent-provider-dropdowns.spec.ts", "**/agent-lifecycle-feedback.spec.ts", + "**/agent-access-warning.spec.ts", "**/inbox-live-update.spec.ts", "**/mesh-compute.spec.ts", "**/observer-archive-policy.spec.ts", diff --git a/desktop/src/features/agents/AGENTS.md b/desktop/src/features/agents/AGENTS.md index 35ad4a63af..7c0cf041f4 100644 --- a/desktop/src/features/agents/AGENTS.md +++ b/desktop/src/features/agents/AGENTS.md @@ -114,6 +114,13 @@ with a TypeScript lookup table or an id comparison in a component. published or removed. A queued update must stay visibly queued, and the catalog itself must render only relay-confirmed publications — never an optimistic local persona. +11. **Open agent access names the consequence where it is selected.** The shared + respond-to field shows a persistent warning whenever `anyone` is selected, + including persona-backed create and edit surfaces. Keep that disclosure in + the shared field instead of adding surface-specific flags. Describe the + audience and their ability to use the agent to access the computer or server + where it runs in plain language; don't expose `respond-to`, `allowlist`, + Nostr, or harness jargon in primary UI copy. ## The tests that enforce this @@ -128,6 +135,8 @@ with a TypeScript lookup table or an id comparison in a component. `isCacheableDiscoveryResponse`, `deriveModelDiscoveryPending`, `isSuccessfulEmptyDiscovery`. If the "reopen to retry" copy becomes inert again, these tests will catch it. +- `ui/respondToFieldContract.test.mjs` — plain-language mode labels and the + persistent warning contract for open agent access. - `desktop/tests/e2e/onboarding-agent-defaults.spec.ts` — onboarding behavior acceptance coverage for readiness, failure states, defaults, navigation, successful-empty vs failed optional-model discovery, and persistence races. diff --git a/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx b/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx index f3c410e2ff..79d1e9a790 100644 --- a/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx +++ b/desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx @@ -936,7 +936,7 @@ export function AgentInstanceEditDialog({ - {/* Who can talk to this agent */} + {/* Who can send instructions */} - Who can talk to this agent + Who can send instructions {isPersonaVariant ? ( onModeChange(e.target.value as RespondToMode)} value={mode} > - - - + {RESPOND_TO_OPTIONS.map((option) => ( + + ))} )} - {!isPersonaVariant ? ( + {mode === "anyone" ? ( +
+
+ ) : (

- Controls which Nostr authors the agent listens to (@mentions, DMs, - thread replies). The agent's owner can always shut it down with - !shutdown. + {mode === "allowlist" + ? "Only you and the people you choose can send instructions." + : "Only you can send instructions."}

- ) : null} + )} {mode === "allowlist" ? ( {!isPersona ? (
- Allowed pubkeys + Selected people {allowlist.length} selected @@ -277,13 +294,13 @@ function AllowlistPicker({ ) : null} {!isPersona && ownerPubkey ? (

- Owner ( - ) is always implicitly allowed by the - harness — no need to add it here. + You ( + ) can always use this agent. You + don't need to add yourself.

) : !isPersona ? (

- The agent's owner is always implicitly allowed. + You can always use this agent.

) : null}
@@ -452,7 +469,7 @@ function AllowlistPicker({ onClick={onAddFromPaste} type="button" > - Add to allowlist + Add people
diff --git a/desktop/src/features/agents/ui/respondToFieldContract.test.mjs b/desktop/src/features/agents/ui/respondToFieldContract.test.mjs new file mode 100644 index 0000000000..e57133f896 --- /dev/null +++ b/desktop/src/features/agents/ui/respondToFieldContract.test.mjs @@ -0,0 +1,47 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; + +const respondToFieldSource = await readFile( + new URL("./RespondToField.tsx", import.meta.url), + "utf8", +); + +for (const label of ["Only me (default)", "Selected people", "Anyone"]) { + test(`respond-to control uses the plain-language label: ${label}`, () => { + assert.ok(respondToFieldSource.includes(`label: "${label}"`)); + }); +} + +test("native and persona controls share one option list", () => { + assert.match( + respondToFieldSource, + / \([\s\S]*