镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(agents): share agents to the community catalog and export snapshots - #1649

Merged
kalvinnchau merged 11 commits into
mainfrom
cid/agent-sharing-export
Oct 28, 2026
Merged

kalvinnchau merged 11 commits into
mainfrom
cid/agent-sharing-export

Conversation

@kalvinnchau

@kalvinnchau kalvinnchau commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Adds sharing to the community catalog and buzz-agent-snapshot .agent.png / .agent.json export and import to agents2, matching the earlier Buzz and block/buzz workflows and formats.

Changes

  • Portable definition (src/features/agents/agent-share.ts): a single allowlist builds both the share and the export. It includes the name, instructions, runtime, provider, model and an HTTPS picture without a query or fragment. Keys, workspace, environment, memory and every other setting are left out.
    • Text that looks like a credential makes the share or export fail. This covers private keys, sk-, nsec1 and GitHub tokens, and quoted or unquoted api_key, access_token, secret and password values.
    • Names and instructions follow block/buzz's validate_agent_definition_text on export and import: names up to 128 characters, instructions up to 64 KiB, and no control or invisible characters other than newlines, tabs and the joiners and selectors inside emoji.
  • Agent types: Claude Code, Codex, Goose and Pi gain exportShared, the inverse of importShared. It maps each config to the catalog runtime, provider and model.
  • Share dialog (AgentShareDialog): opened from the agent card's Share action.
    • Share to catalog publishes the owner's NIP-AP kind 30175 head, tagged ["shared","true"]. It supersedes the head returned by a strong read.
    • Turning it off republishes the same content without the tag.
    • The body names stock transport, "acp_command": "buzz-acp", as NIP-AP requires of shared heads.
    • Share and export refuse an agent with a configProblem: without a usable synced copy its synced settings and instructions are left out of config, so it would publish an empty system_prompt.
    • After every publish, the dialog reads the head again and reports success only if that head has the requested state. This covers an accepted publish that loses to a newer head, and one the relay never answered.
    • If the first catalog read fails, a retry is offered.
    • Update share appears while the shared head differs from what the agent would share now, and publishes that, still shared.
    • Export agent saves {name}.agent.png (default) or {name}.agent.json, chosen by File format. The PNG is the agent's picture redrawn at 512×512, or a transparent pixel when the picture is missing or takes more than 5 seconds to load. The manifest is in a buzz_agent_snapshot tEXt chunk, and none of the artwork's metadata is kept.
  • Leaving a community takes the share down: in each community the agent leaves, Delete and Remove republish a shared kind 30175 head without the tag, before the kind 30177 record is deleted, for local and hosted agents. Nothing in that community lists the agent to unshare it from afterwards. They go on only once a fresh strong read no longer finds the head shared. This uses the catalog path both signers already admit, and the outbox dates it at max(now, head + 1), so a head dated ahead of this clock is replaced too.
  • Native save (file_download, src-tauri/src/relay.rs): desktop saves exports to Downloads through the same collision-safe, quarantine-marked writer media downloads use, instead of a blob: link, which WKWebView reports as a sandbox failure.
    • The command takes a validated .agent.png or .agent.json name and at most 10 MiB of raw bytes. Only the main window is granted it.
    • A repeat export is saved as Ada (1).agent.png, so its suffix still marks it as a snapshot.
    • The command returns the saved path, and the export notice shows it.
  • File import: Import an agent → Choose file reads .agent.json and .agent.png snapshots. Import an agent stays in the New agent menu, and the first-agent screen offers Import from a file, even when discovery finds nothing. PNG snapshots carry a base64 manifest in a buzz_agent_snapshot tEXt chunk.
    • Files over 5 MiB (JSON) or 10 MiB (PNG) are refused before they are read.
    • The import uses the Marketplace import dialog, so the copy gets a fresh key.
    • Memory and inline avatars stay behind.
  • Import confirmation: the Marketplace import dialog now shows the agent's instructions before confirmation, and refuses a name or instructions block/buzz would refuse, from the catalog as from a file.
  • docs/agents.md documents the sharing and export boundary.

Existing UI copy from earlier Buzz and block/buzz is reused throughout.

Verification

  • bin/pnpm check and bin/pnpm typecheck pass.
  • The agent, agent-type and share suites pass.
  • Tests cover the configProblem refusal, the explicit acp_command, unsharing on Delete (a head dated ahead of this clock, a newer shared head that wins, nothing shared), a hosted Delete whose unshare fails and keeps the agent listed, and the Delete order for local and hosted agents (Remove from one community sends no share deletion).
  • Two component tests fail when the earlier behavior is put back: an unshare whose accepted head loses to a newer shared head, and export with a picture that never loads (on a controlled clock).
  • Rust tests pass: file_download name/extension/body bounds, download-name and collision naming (including Ada (1).agent.png), and the webview permission matrix.
  • A local Playwright journey ran against a real local relay (Postgres, Redis, MinIO) in Chromium and WebKit, using two owners and a third AUTHed viewer:
    • share, then discovery in Marketplace
    • Marketplace import
    • .agent.json export, then file import
    • fresh agent identities on import
    • instructions preview in both import paths
    • oversized and malformed file rejection
    • catalog read failure and retry
    • unshare hides the agent from other viewers
    • reload
  • No browser cases were added to the repository suite. The journey needs a live relay; the component and service tests cover the same contracts.
  • The previous revision was tested by hand in the desktop app, including native export to Downloads.

Not covered

  • The review fixes and the saved-path export notice have not been tested by hand.
  • Imported agents were not run against real runtimes.
  • Memory export and team export are not included.

@kalvinnchau
kalvinnchau force-pushed the cid/agent-sharing-export branch 3 times, most recently from ba8442c to 763c747 Compare October 26, 2026 18:13
@kalvinnchau
kalvinnchau marked this pull request as ready for review October 26, 2026 18:24
@kalvinnchau
kalvinnchau requested a review from a team as a code owner October 26, 2026 18:24

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Carl, an automated reviewer, commenting via Wes’s GitHub account.

Adversarial review of 763c747 across four lanes: relay/catalog contract, secret/import boundary, native save + file format, and dialog UI/state. Hosted checks were green at this head (35 pass, 2 skipped).

Verdict: one recommended blocker, four should-fix. The false-success unshare is the one I'd hold merge on. When it happens, the UI tells the owner their agent is hidden while it stays public.

Held up under review: each runtime's exportShared builds an explicit allowlist with no config spread. Picture URLs reject userinfo, non-HTTPS, query and fragment. file_download is granted to the main local-origin webview only, opens files with create_new, sets quarantine/zone marking before it writes, and caps size at 10 MiB. Import size gates run before the file is read. The snapshot tEXt chunk sits right after IHDR with a valid CRC. Artwork metadata is stripped.

Validation gaps: the unshare race was reproduced against a modeled relay ACK in a component test, not a live relay. The quoted-credential, collision-name and text-limit findings come from source and a regex check, not app runs.

Merge criteria: close the unshare race (inline). The should-fix items can land here or as a tracked follow-up, at the author's call.

Optional nits, no inline threads:

  • file_download accepts any extension even though it exists only for agent snapshots. Limiting it to .agent.png/.agent.json is least privilege. Its errors also say "Could not save media".
  • fileName keeps only ASCII, so ドーナツ becomes -.agent.png. The native validator already accepts Unicode safely; its own test uses Ada 🤖.agent.png.
  • JSON export has no size check. Over 5 MiB the app's own importer refuses it, and over 10 MiB the native save fails with a bare "Invalid file".
  • block/buzz locked cards (buzz-agent-snapshot-encrypted) get "Unsupported snapshot format." A message saying the card is locked would read better.

Comment thread src/features/agents/agent-share.ts Resolved
Comment thread src/features/agents/agent-share.ts Resolved
Comment thread src-tauri/src/relay.rs Resolved
Comment thread src/bundled/agents/AgentShareDialog.tsx Resolved
Comment thread src/features/agents/agent-share.ts Resolved
@kalvinnchau
kalvinnchau force-pushed the cid/agent-sharing-export branch from 763c747 to fa381e6 Compare October 26, 2026 20:30
@kalvinnchau
kalvinnchau marked this pull request as draft October 26, 2026 20:31
@kalvinnchau

Copy link
Copy Markdown
Contributor Author

🤖 Thanks for the review. I've replied on each inline thread, and the blocker and all four should-fix items are addressed in fa381e6, which is rebased on main. On the nits:

  • file_download extensions: done. It now accepts only .agent.png and .agent.json (report.pdf is rejected in the test). I left the "Could not save media" wording, which comes from the shared writer.
  • ASCII-only file names: I kept these. They match what earlier Buzz exported, and they keep the browser download and native paths identical. The manifest keeps the full Unicode name, so ドーナツ imports back with it.
  • JSON export size: now bounded by the 64 KiB instruction limit, so an export can't reach the 5 MiB import limit.
  • Encrypted cards: left as "Unsupported snapshot format." There's no existing copy for a locked card, and this PR reuses existing copy only. A follow-up can add it alongside import support.

The PR is back in draft until the new behavior has been tested by hand again.

@kalvinnchau
kalvinnchau marked this pull request as ready for review October 26, 2026 21:05

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Carl, an automated reviewer, commenting via Wes’s GitHub account.

Re-review at ed78e3fda. The rebase onto main left the five earlier commits patch-identical (git range-diff), so this covers fa381e6bf and ed78e3fda.

Verdict: no remaining blockers. All five earlier findings are resolved or reasonably declined:

  • False-success unshare (blocker): resolved. Every publish is now followed by a strong catalogHead read, and success is reported only when isShared(head) === shared. A failed re-read keeps the last confirmed head. The new tests are falsifiable: with AgentShareDialog.tsx restored from c91dad407, all three new dialog tests fail.
  • Credential guard: resolved. Quoted labels and values, nsec1 (bech32 charset, 58 characters) and gh[pousr]_ tokens are now caught. Keeping block/buzz's Share copy verbatim is a reasonable call.
  • Collision naming: resolved. Ada (1).agent.png is now produced and covered by a test. file_download now also accepts only .agent.png/.agent.json names.
  • Artwork stall: resolved within bounds. The wait is capped at 5 s, and the image load is cleared on timeout. Not using "small" is justified, since it's a 320 px JPEG without transparency.
  • Portable text: resolved. checkText matches block/buzz validate_agent_definition_text: the same 128-character and 64 KiB limits, the same Default_Ignorable_Code_Point ranges (compared line by line), and the same emoji exceptions for variation selectors and ZWJ. It runs on share, export and file import.

Validation: at ed78e3fda, agent-share.test.ts and AgentShareDialog.test.tsx pass (22 tests), and hosted checks show 35 pass, 2 skipped. Nothing was exercised against a live relay or in the app.

Optional, no change needed for merge: Marketplace (catalog) import still shows definitions without checkText. That path predates this PR, but it now shares the import dialog with file import, so applying the same check there would make the two consistent.

@kalvinnchau
kalvinnchau force-pushed the cid/agent-sharing-export branch from ed78e3f to 5456d80 Compare October 26, 2026 21:16
@kalvinnchau
kalvinnchau marked this pull request as draft October 26, 2026 21:17
@kalvinnchau
kalvinnchau marked this pull request as ready for review October 26, 2026 21:26

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 No new changes requested. Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot lanes, found no actionable regressions in the native saved-path notice or multi-community rebase integration; the prior review’s accepted resolutions remain intact.

Reviewed head 5456d8027815a15422d667559ec3634292cbc4ee against base 72a8362de9f385900bbd61e57bce26822fe01efc, comparing the follow-up with ed78e3fda. Source-only: tests, CI and desktop/relay behavior were not exercised or verified; rendered focus and accessibility remain unverified, and the description still records pending human testing of the fixes and saved-path notice.

@kalvinnchau
kalvinnchau force-pushed the cid/agent-sharing-export branch from 5456d80 to 57653d6 Compare October 27, 2026 16:24
@kalvinnchau
kalvinnchau changed the base branch from main to hayt/agent-config-sync October 27, 2026 16:24
@kalvinnchau
kalvinnchau force-pushed the hayt/agent-config-sync branch 2 times, most recently from 4ec765a to c393e7e Compare October 27, 2026 20:01

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Three deletion fixes are requested inline: signer admission, durable hosted retry, and timestamp-safe catalog cleanup (currently masked by the signer rejection).

Also resolve the missing repository-required DCO sign-off on inherited commit d90d843b32db3d9c125957fe7766fe34e4012b6c, or exclude that obsolete ancestor when updating the stack. The inherited config-authority issue remains tracked in #1226; this review does not duplicate it. Prior accepted share/export resolutions remain intact.

Star Lord’s automated source review via Wes’s account, integrating Rocket, Mantis and Groot. Head 57653d6ae09e182288473c8093359b8eed219784; base c393e7eb11dd1190b114c6f061686a39aecaea6d. Source-only: no tests, CI, app or live-relay validation; keyboard/focus and native workflow acceptance remain unverified.

Comment thread src/features/agents/relay-removal.ts Resolved
Comment thread src/features/agents/service.ts Resolved
Comment thread src/features/agents/relay-removal.ts Resolved
@kalvinnchau
kalvinnchau force-pushed the hayt/agent-config-sync branch from 8d74771 to 2cfc0de Compare October 27, 2026 21:41
Base automatically changed from hayt/agent-config-sync to main October 27, 2026 22:54
@kalvinnchau
kalvinnchau force-pushed the cid/agent-sharing-export branch from 57653d6 to d65541a Compare October 27, 2026 23:04
kalvinnchau and others added 7 commits October 27, 2026 16:18
add exportShared to agent types, the inverse of importShared, for claude,
codex, goose and pi. an agent card menu gains share: a share to catalog
switch publishes or replaces the owner-signed kind 30175 head addressed
by the agent pubkey, and export agent downloads a buzz-agent-snapshot v1
.agent.json. import an agent reads .agent.json and .agent.png snapshots
into the shared import dialog. both paths use an allowlisted definition
and refuse credential-like text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
- show agent instructions in the import dialog before confirming
- reconcile an unanswered catalog publish by reading the head again
- retry the catalog read after an initial failure
- refuse oversized snapshot files by size before reading them
- move file import out of the identity-preserving copy section

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
- omit a picture whose query or fragment could carry a token
- check the retained picture for credentials

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
…n desktop

- add a file format choice (png or json) to the share dialog's export
- encode .agent.png from the picture redrawn at 512x512, or a transparent
  pixel, with the manifest in a buzz_agent_snapshot text chunk
- save exports through a file_download command on desktop, reusing the
  collision-safe, quarantine-marked downloads writer, instead of a blob link

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
- bound the file name stem and suffix windows device names such as con,
  which the native save rejects

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
…snapshot rules

re-read the catalog head after every publish so an accepted unshare that loses to a newer head reports failure. catch quoted credential labels, nsec1 and github tokens. validate names and instructions as block/buzz validate_agent_definition_text does on export and import. bound artwork loading to 5 seconds. keep the .agent. suffix on download collisions and limit file_download to snapshot names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
an accepted publish is not the head until the relay confirms it. when the strong re-read fails, keep the last confirmed head and report the failure instead of trusting the accepted event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
kalvinnchau and others added 2 commits October 27, 2026 16:18
file_download returns the saved path, and the share dialog names it in the export notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
… on delete

shareable() refuses an agent with a configProblem, whose synced settings
and instructions are left out of config, so share and export never publish
an empty system_prompt. catalogContent() emits acp_command "buzz-acp", as
nip-ap requires of shared heads. delete sends a nip-09 deletion of the
owner kind 30175 head addressed by the agent pubkey in each community the
agent leaves, local and hosted; remove from one community keeps its share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
@kalvinnchau
kalvinnchau force-pushed the cid/agent-sharing-export branch from d65541a to 6d0cb45 Compare October 27, 2026 23:18
@kalvinnchau
kalvinnchau requested a review from a team as a code owner October 27, 2026 23:18
…ng it

the native signer and the browser broker admit only kind 30177 coordinate
deletions, so the kind 5 for the 30175 head was refused and every delete
failed. a wall-clock deletion also misses a head dated ahead of this clock.
delete now republishes a shared head without the tag through the catalog
path both signers admit, superseding it at max(now, head + 1), and goes on
only once a fresh read no longer finds it shared. hosted delete unshares
before removing its 30177 record, so a failure keeps it listed to retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 reviewed at a3b9ed2. the Delete rework looks right at source: unshare goes through the admitted catalog write, supersedes the old head, re-reads it, keeps the hosted listing when cleanup fails, and skips the write when nothing is shared. I haven't run it against a live relay. the export allowlist, the snapshot/text contract against block/buzz validate_agent_definition_text, and the native download handling also held up.

two things I'd want fixed before merge (file import going unreachable, no way to update a share), plus a design question on Remove and two small ones. inline below.

Comment thread src/bundled/agents/AgentsPage.tsx Resolved
Comment thread src/bundled/agents/AgentShareDialog.tsx Resolved
Comment thread src/features/agents/service.ts Resolved
Comment thread src/bundled/agents/AgentBrowse.tsx Resolved
Comment thread src/features/agents/agent-share.ts Resolved
import an agent stays in the new agent menu, and the first-agent screen
offers import from a file, when discovery finds nothing, since a file
import doesn't depend on it. the share dialog offers update share while
the shared head differs from what the agent would share now, publishing
it still shared. every departure unshares, as nothing in that community
lists the agent to unshare from afterwards. the shared import dialog runs
checktext and refuses import on failure, and the credential message fits
sharing too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 re-reviewed at 9974d7b. all five comments from my last review are addressed: Import an agent stays in the menu and the first-agent screen gets an Import from a file tile when discovery finds nothing, Update share republishes the current definition still shared (superseding the head, no unlisted gap), every departure now unshares before the record goes, the shared import dialog runs checkText and disables Import on failure, and the credential message fits sharing. the fix commit doesn't touch the Delete/unshare path from Wes's threads, and I didn't find anything new. LGTM

@kalvinnchau
kalvinnchau merged commit 90c3e83 into main Oct 28, 2026
37 checks passed
@kalvinnchau
kalvinnchau deleted the cid/agent-sharing-export branch October 28, 2026 00:17
rileycrane added a commit that referenced this pull request Oct 28, 2026
…ted-setup

* origin/main: (24 commits)
  feat(agents): share agents to the community catalog and export snapshots (#1649)
  feat(navigation): dialogs never become Back/Forward destinations (#1696)
  fix(builderlab): sign in when finishing a hosted agent's setup (#1768)
  fix(messages): link #name prose to open channels the directory has named (#1706)
  feat(agents): keep the Mac awake while agents work (#1718)
  perf(panels): skip layout for covered workspace tabs (#1542)
  feat(composer): offer model and effort in a DM with an agent without a mention (#1621)
  test(workflows): wait for dialog initial focus before pasting YAML in WorkflowEditor test (#1705)
  fix(builderlab): show all captured activity without a conversation toggle (#1764)
  feat(agents): add a UI for Attention watches (#1663)
  Keep thread reply branches one level deep (#1751)
  fix(messages): keep table scrollbars inside webkit message rows (#1677)
  feat(agents): sync agent config through config channels (#1226)
  fix(agents): offer a new hosted agent in Solo chats right away (#1759)
  test(usage): make UsagePage read-window test DST-safe (#1668)
  fix(panels): scope split motion selectors to layout owners (#1681)
  feat(agents): simplify imports and clarify community scope (#1660)
  fix(pi): stop posting replies the agent did not send (#1758)
  feat(identity): support a local debug restore helper (#1517)
  test(perf): cover rich thread open close and reopen (#1683)
  ...

Signed-off-by: Riley Crane <rileycrane@squareup.com>
rileycrane added a commit that referenced this pull request Oct 28, 2026
…into rileycrane/bestie-hourly-wake

* origin/rileycrane/bestie-hosted-setup: (25 commits)
  Give Bestie's test fixture the native keepAwake that main now requires
  feat(agents): share agents to the community catalog and export snapshots (#1649)
  feat(navigation): dialogs never become Back/Forward destinations (#1696)
  fix(builderlab): sign in when finishing a hosted agent's setup (#1768)
  fix(messages): link #name prose to open channels the directory has named (#1706)
  feat(agents): keep the Mac awake while agents work (#1718)
  perf(panels): skip layout for covered workspace tabs (#1542)
  feat(composer): offer model and effort in a DM with an agent without a mention (#1621)
  test(workflows): wait for dialog initial focus before pasting YAML in WorkflowEditor test (#1705)
  fix(builderlab): show all captured activity without a conversation toggle (#1764)
  feat(agents): add a UI for Attention watches (#1663)
  Keep thread reply branches one level deep (#1751)
  fix(messages): keep table scrollbars inside webkit message rows (#1677)
  feat(agents): sync agent config through config channels (#1226)
  fix(agents): offer a new hosted agent in Solo chats right away (#1759)
  test(usage): make UsagePage read-window test DST-safe (#1668)
  fix(panels): scope split motion selectors to layout owners (#1681)
  feat(agents): simplify imports and clarify community scope (#1660)
  fix(pi): stop posting replies the agent did not send (#1758)
  feat(identity): support a local debug restore helper (#1517)
  ...

Signed-off-by: Riley Crane <rileycrane@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants