mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
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>
This commit is contained in:
parent
bc592e102d
commit
0cfc6e7407
@@ -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<String>,
|
||||
/// 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
|
||||
|
||||
@@ -70,7 +70,7 @@ export function AgentSnapshotImportDialog({
|
||||
<Dialog onOpenChange={onOpenChange} open={open}>
|
||||
<DialogContent
|
||||
aria-describedby={undefined}
|
||||
className="max-w-md"
|
||||
className="max-h-[85vh] max-w-2xl overflow-y-auto"
|
||||
data-testid="agent-snapshot-import-dialog"
|
||||
showCloseButton={false}
|
||||
>
|
||||
@@ -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 */}
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium">{preview.displayName}</p>
|
||||
{preview.systemPrompt ? (
|
||||
<p className="line-clamp-3 text-xs text-muted-foreground">
|
||||
{preview.systemPrompt}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Portable behavior — never hide executable configuration behind a summary. */}
|
||||
<section
|
||||
className="space-y-2 rounded-md border border-border p-3"
|
||||
data-testid="agent-snapshot-import-behavior"
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm font-medium">Agent instructions</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Review the instructions this agent will follow after import.
|
||||
</p>
|
||||
</div>
|
||||
<pre
|
||||
className="max-h-48 overflow-auto whitespace-pre-wrap break-words rounded bg-muted/60 p-3 text-xs"
|
||||
data-testid="agent-snapshot-import-system-prompt"
|
||||
>
|
||||
{preview.systemPrompt || "No system prompt included."}
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<p className="text-sm text-muted-foreground">
|
||||
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"})
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
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.
|
||||
</p>
|
||||
<ul
|
||||
className="max-h-28 space-y-1 overflow-y-auto rounded bg-muted/60 p-2 font-mono text-xs"
|
||||
data-testid="agent-snapshot-import-allowlist-values"
|
||||
>
|
||||
{preview.sourceAllowlist.map((pubkey) => (
|
||||
<li className="break-all" key={pubkey}>
|
||||
{pubkey}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
@@ -234,6 +258,22 @@ function PreviewBody({
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<details
|
||||
className="rounded-md border border-border p-3"
|
||||
data-testid="agent-snapshot-import-manifest"
|
||||
>
|
||||
<summary className="cursor-pointer text-sm font-medium">
|
||||
Full embedded manifest
|
||||
</summary>
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
This is the complete portable payload decoded from the file. Secrets,
|
||||
credentials, and source identity are not part of the snapshot format.
|
||||
</p>
|
||||
<pre className="mt-2 max-h-64 overflow-auto whitespace-pre-wrap break-words rounded bg-muted/60 p-3 text-xs">
|
||||
{preview.manifestJson}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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`. */
|
||||
|
||||
Reference in New Issue
Block a user