From 0cfc6e7407b95dfe6446ae480e3c069798702dec Mon Sep 17 00:00:00 2001 From: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Date: Mon, 27 Jul 2026 22:24:00 -0400 Subject: [PATCH] feat(desktop): disclose agent snapshot payload before import Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> --- .../src/commands/personas/snapshot/import.rs | 16 ++++- .../agents/ui/AgentSnapshotImportDialog.tsx | 58 ++++++++++++++++--- .../ui/agentSnapshotImportDialog.test.mjs | 44 +++++++++++++- desktop/src/shared/api/tauriPersonas.ts | 4 ++ 4 files changed, 110 insertions(+), 12 deletions(-) diff --git a/desktop/src-tauri/src/commands/personas/snapshot/import.rs b/desktop/src-tauri/src/commands/personas/snapshot/import.rs index ac5c0eace..4b49dd7ee 100644 --- a/desktop/src-tauri/src/commands/personas/snapshot/import.rs +++ b/desktop/src-tauri/src/commands/personas/snapshot/import.rs @@ -65,6 +65,12 @@ pub struct AgentSnapshotImportPreview { pub has_source_allowlist: bool, /// Number of source allowlist entries. pub source_allowlist_count: usize, + /// Full source allowlist entries, surfaced before import so hidden access + /// configuration is never reduced to a count. + pub source_allowlist: Vec, + /// Pretty-printed, validated manifest exactly as decoded from the file. + /// The UI makes this available before confirmation for full payload review. + pub manifest_json: String, } /// The confirmation request sent from the UI after the user reviews the preview. @@ -269,6 +275,10 @@ pub async fn preview_agent_snapshot_import( } .to_string(); + let manifest_json = serde_json::to_string_pretty(&snapshot) + .map_err(|e| format!("failed to render snapshot manifest: {e}"))?; + let source_allowlist = snapshot.definition.respond_to_allowlist.clone(); + Ok(AgentSnapshotImportPreview { display_name: snapshot.profile.display_name.clone(), system_prompt: snapshot.definition.system_prompt.clone(), @@ -280,8 +290,10 @@ pub async fn preview_agent_snapshot_import( .or_else(|| snapshot.profile.avatar_url.clone()), memory_level, memory_entry_count: snapshot.memory.entries.len(), - source_allowlist_count: snapshot.definition.respond_to_allowlist.len(), - has_source_allowlist: !snapshot.definition.respond_to_allowlist.is_empty(), + source_allowlist_count: source_allowlist.len(), + has_source_allowlist: !source_allowlist.is_empty(), + source_allowlist, + manifest_json, }) }) .await diff --git a/desktop/src/features/agents/ui/AgentSnapshotImportDialog.tsx b/desktop/src/features/agents/ui/AgentSnapshotImportDialog.tsx index ad1219310..5ff618cf4 100644 --- a/desktop/src/features/agents/ui/AgentSnapshotImportDialog.tsx +++ b/desktop/src/features/agents/ui/AgentSnapshotImportDialog.tsx @@ -70,7 +70,7 @@ export function AgentSnapshotImportDialog({ @@ -139,7 +139,7 @@ export function AgentSnapshotImportDialog({ // ── Preview body ────────────────────────────────────────────────────────────── -function PreviewBody({ +export function PreviewBody({ preview, hasMemory, memoryLevelLabel, @@ -157,13 +157,27 @@ function PreviewBody({ {/* Agent identity */}

{preview.displayName}

- {preview.systemPrompt ? ( -

- {preview.systemPrompt} -

- ) : null}
+ {/* Portable behavior — never hide executable configuration behind a summary. */} +
+
+

Agent instructions

+

+ Review the instructions this agent will follow after import. +

+
+
+          {preview.systemPrompt || "No system prompt included."}
+        
+
+

A new agent will be created with a fresh keypair. The imported agent is independent of the source — identity never travels. @@ -203,9 +217,19 @@ function PreviewBody({ {preview.sourceAllowlistCount === 1 ? "y" : "ies"})

- This snapshot includes a source-environment pubkey allowlist. Those - identities are not meaningful on your relay. + This snapshot includes source-environment pubkeys. Review every key + before choosing Keep; Clear is safer when you do not recognize them.

+
    + {preview.sourceAllowlist.map((pubkey) => ( +
  • + {pubkey} +
  • + ))} +
) : null} + +
+ + Full embedded manifest + +

+ This is the complete portable payload decoded from the file. Secrets, + credentials, and source identity are not part of the snapshot format. +

+
+          {preview.manifestJson}
+        
+
); } diff --git a/desktop/src/features/agents/ui/agentSnapshotImportDialog.test.mjs b/desktop/src/features/agents/ui/agentSnapshotImportDialog.test.mjs index 3ddaf1972..cc9dcaa5a 100644 --- a/desktop/src/features/agents/ui/agentSnapshotImportDialog.test.mjs +++ b/desktop/src/features/agents/ui/agentSnapshotImportDialog.test.mjs @@ -9,7 +9,7 @@ import test from "node:test"; // memoryErrors strings are carried through to a bounded list element with the // correct data-testid. No DOM or test renderer is needed. -import { ResultBody } from "./AgentSnapshotImportDialog.tsx"; +import { PreviewBody, ResultBody } from "./AgentSnapshotImportDialog.tsx"; /** * Walk a React element tree (breadth-first) and collect all elements that @@ -71,6 +71,48 @@ function makeResult(overrides = {}) { }; } +function makePreview(overrides = {}) { + return { + displayName: "TestBot", + systemPrompt: "Inspect every boundary before changing code.", + avatarUrl: null, + memoryLevel: "none", + memoryEntryCount: 0, + hasSourceAllowlist: true, + sourceAllowlistCount: 2, + sourceAllowlist: ["a".repeat(64), "b".repeat(64)], + manifestJson: '{\n "format": "buzz-agent-snapshot"\n}', + ...overrides, + }; +} + +// ── preview transparency ────────────────────────────────────────────────────── + +test("preview_body_discloses_prompt_allowlist_and_full_manifest", () => { + const preview = makePreview(); + const element = PreviewBody({ + preview, + hasMemory: false, + memoryLevelLabel: "none", + keepAllowlist: false, + onKeepAllowlistChange: () => {}, + }); + const allText = collectText(element).join(" "); + + assert.ok(allText.includes(preview.systemPrompt)); + for (const pubkey of preview.sourceAllowlist) { + assert.ok(allText.includes(pubkey), `missing allowlist pubkey ${pubkey}`); + } + assert.ok(allText.includes(preview.manifestJson)); + assert.equal( + findAll( + element, + (n) => n.props?.["data-testid"] === "agent-snapshot-import-manifest", + ).length, + 1, + ); +}); + // ── memory errors detail list ───────────────────────────────────────────────── test("result_body_renders_memory_errors_list_with_test_id", () => { diff --git a/desktop/src/shared/api/tauriPersonas.ts b/desktop/src/shared/api/tauriPersonas.ts index bd3be9240..afff28ab5 100644 --- a/desktop/src/shared/api/tauriPersonas.ts +++ b/desktop/src/shared/api/tauriPersonas.ts @@ -181,6 +181,10 @@ export type AgentSnapshotImportPreview = { /** True when the snapshot's respond_to_allowlist is non-empty. */ hasSourceAllowlist: boolean; sourceAllowlistCount: number; + /** Full source pubkeys shown before import, not only their count. */ + sourceAllowlist: string[]; + /** Validated, pretty-printed manifest for full payload disclosure. */ + manifestJson: string; }; /** Confirmation sent to `confirm_agent_snapshot_import`. */