feat(registry): Postman-style agent editor with lifecycle transitions#387
feat(registry): Postman-style agent editor with lifecycle transitions#387toadkicker merged 21 commits intomainfrom
Conversation
|
| Filename | Overview |
|---|---|
| apps/papillon/src/commands/agents.rs | Adds sign_and_publish_local and unpublish_local Tauri commands plus lifecycle_from_published_to helper; ExecutionTarget is hardcoded to None instead of being derived from the endpoint in all conversion paths. |
| apps/papillon/frontend/src/components/registry/json_ld_panel.rs | New JSON-LD preview panel with lifecycle-contextual action button; on_action silently no-ops for Draft agents without a DID — the primary publish flow — with no user-visible error. |
| apps/papillon/frontend/src/components/registry/agent_editor.rs | New Postman-style tabbed editor; tab count badges are non-reactive (captured once at render) and will not update when switching agents in the sidebar. |
| crates/papillon-shared/src/types.rs | Adds ExecutionTarget enum with derive() factory and AgentLifecycle enum; both are well-typed with sensible defaults and clean serde attributes. |
| crates/papillon-shared/src/schema_phrase.rs | New schema_phrase() utility with 6 unit tests covering prefix strip, suffix strip, CamelCase conversion, and edge cases — clean and well-tested. |
| apps/papillon/frontend/src/components/registry/agent_sidebar.rs | New sidebar grouping agents by lifecycle with colored dots; uses agent name as selection key which is fragile but consistent with the rest of the state model. |
| apps/papillon/frontend/src/components/registry/peer_browser.rs | Renamed from browser.rs; inlines former AgentCard but still strips schema: with trim_start_matches rather than the new schema_phrase(), leaving Action suffix and CamelCase un-normalized. |
| apps/papillon/src/commands/registry.rs | Minimal change to populate new AgentInfo fields; execution_target also hardcoded to None here. |
| apps/registry/src/ui/pages/agents.rs | Applies schema_phrase() to capability, returns, and disclosure display strings — straightforward and correct. |
Sequence Diagram
sequenceDiagram
participant UI as JsonLdPanel (Frontend)
participant Bridge as Tauri Bridge
participant Cmd as commands/agents.rs
participant DB as Database
Note over UI: User clicks Sign & Publish (Draft/Unpublished)
UI->>UI: Check agent_did — returns early if None/empty (silent no-op)
UI->>Bridge: invoke(sign_and_publish_local, { agent_did })
Bridge->>Cmd: sign_and_publish_local(agent_did)
Cmd->>DB: load_all_agents()
DB-->>Cmd: Vec DynamicAgentDef
Cmd->>Cmd: retain: remove LOCAL_UNPUBLISHED_SENTINEL
Cmd->>Cmd: push: LOCAL_REGISTRY_URL
Cmd->>DB: update_agent(def)
Cmd-->>Bridge: Ok(AgentInfo lifecycle Published)
Bridge-->>UI: Ok(AgentInfo)
UI->>Bridge: invoke(list_local_agents)
Bridge->>Cmd: list_local_agents()
Cmd-->>Bridge: Vec AgentInfo
Bridge-->>UI: agents list
UI->>UI: registry.agents.set(agents) sidebar re-renders
Comments Outside Diff (3)
-
apps/papillon/src/commands/agents.rs, line 908-911 (link)ExecutionTargetalwaysNone— derive() never called in backenddef_to_agent_info(line 908) andlist_local_agents(line 917–920) both hard-codeexecution_target: ExecutionTarget::Noneeven thoughExecutionTarget::derive()is implemented and tested. The UI's "Execution Target" badge inEndpointTabwill therefore always render "None" regardless of the actual endpoint URL. The same pattern is repeated inapps/papillon/src/commands/registry.rsandapps/papillon/frontend/src/service/web_service.rs.Prompt To Fix With AI
This is a comment left during a code review. Path: apps/papillon/src/commands/agents.rs Line: 908-911 Comment: **`ExecutionTarget` always `None` — derive() never called in backend** `def_to_agent_info` (line 908) and `list_local_agents` (line 917–920) both hard-code `execution_target: ExecutionTarget::None` even though `ExecutionTarget::derive()` is implemented and tested. The UI's "Execution Target" badge in `EndpointTab` will therefore always render "None" regardless of the actual endpoint URL. The same pattern is repeated in `apps/papillon/src/commands/registry.rs` and `apps/papillon/frontend/src/service/web_service.rs`. How can I resolve this? If you propose a fix, please make it concise.
-
apps/papillon/frontend/src/components/registry/json_ld_panel.rs, line 638-643 (link)Silent no-op when
agent_didisNonefor Draft agentson_actionreturns early without any feedback whenagent_didisNoneor empty. New Draft agents — the primary use-case for "Sign & Publish" — won't have a DID yet, so clicking the button silently does nothing. There is no toast, error message, orregistry.errorsignal set to tell the user why the action failed. This also means the "Sign & Publish" button appears active but is functionally broken for any agent without a pre-existing DID.Prompt To Fix With AI
This is a comment left during a code review. Path: apps/papillon/frontend/src/components/registry/json_ld_panel.rs Line: 638-643 Comment: **Silent no-op when `agent_did` is `None` for Draft agents** `on_action` returns early without any feedback when `agent_did` is `None` or empty. New Draft agents — the primary use-case for "Sign & Publish" — won't have a DID yet, so clicking the button silently does nothing. There is no toast, error message, or `registry.error` signal set to tell the user why the action failed. This also means the "Sign & Publish" button appears active but is functionally broken for any agent without a pre-existing DID. How can I resolve this? If you propose a fix, please make it concise.
-
apps/papillon/frontend/src/components/registry/peer_browser.rs, line 724-728 (link)Inlined card still uses raw
trim_start_matches("schema:")instead ofschema_phrase()The inlined agent card in
PeerBrowserstrips theschema:prefix manually (.trim_start_matches("schema:")) but never strips theActionsuffix or converts CamelCase to Title Case — inconsistent with the rest of the PR's stated goal of hiding schema.org vocabulary viaschema_phrase(). Consider replacing with:(and adding
use papillon_shared::schema_phrase;at the top of the file.)Prompt To Fix With AI
This is a comment left during a code review. Path: apps/papillon/frontend/src/components/registry/peer_browser.rs Line: 724-728 Comment: **Inlined card still uses raw `trim_start_matches("schema:")` instead of `schema_phrase()`** The inlined agent card in `PeerBrowser` strips the `schema:` prefix manually (`.trim_start_matches("schema:")`) but never strips the `Action` suffix or converts CamelCase to Title Case — inconsistent with the rest of the PR's stated goal of hiding schema.org vocabulary via `schema_phrase()`. Consider replacing with: (and adding `use papillon_shared::schema_phrase;` at the top of the file.) How can I resolve this? If you propose a fix, please make it concise.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 4
apps/papillon/src/commands/agents.rs:908-911
**`ExecutionTarget` always `None` — derive() never called in backend**
`def_to_agent_info` (line 908) and `list_local_agents` (line 917–920) both hard-code `execution_target: ExecutionTarget::None` even though `ExecutionTarget::derive()` is implemented and tested. The UI's "Execution Target" badge in `EndpointTab` will therefore always render "None" regardless of the actual endpoint URL. The same pattern is repeated in `apps/papillon/src/commands/registry.rs` and `apps/papillon/frontend/src/service/web_service.rs`.
```suggestion
execution_target: papillon_shared::ExecutionTarget::derive(def.endpoint.as_deref()),
lifecycle: lifecycle_from_published_to(&def.published_to),
```
### Issue 2 of 4
apps/papillon/frontend/src/components/registry/json_ld_panel.rs:638-643
**Silent no-op when `agent_did` is `None` for Draft agents**
`on_action` returns early without any feedback when `agent_did` is `None` or empty. New Draft agents — the primary use-case for "Sign & Publish" — won't have a DID yet, so clicking the button silently does nothing. There is no toast, error message, or `registry.error` signal set to tell the user why the action failed. This also means the "Sign & Publish" button appears active but is functionally broken for any agent without a pre-existing DID.
### Issue 3 of 4
apps/papillon/frontend/src/components/registry/agent_editor.rs:250-254
**Tab count badges are non-reactive — captured once at render**
`input_count()`, `returns_count()`, and `disclosure_count()` are called immediately and passed as `Option<usize>` to `TabButton`. Since `TabButton` accepts a plain `usize` (not a signal), these values are snapshotted at the time the `AgentEditor` is first composed. Switching to a different agent in the sidebar will update the tab content but leave the count badges stale.
Consider passing the closures as signals or wrapping in `Signal::derive()` so they re-evaluate when the `agent` signal changes.
### Issue 4 of 4
apps/papillon/frontend/src/components/registry/peer_browser.rs:724-728
**Inlined card still uses raw `trim_start_matches("schema:")` instead of `schema_phrase()`**
The inlined agent card in `PeerBrowser` strips the `schema:` prefix manually (`.trim_start_matches("schema:")`) but never strips the `Action` suffix or converts CamelCase to Title Case — inconsistent with the rest of the PR's stated goal of hiding schema.org vocabulary via `schema_phrase()`. Consider replacing with:
```suggestion
.map(|c| papillon_shared::schema_phrase(c))
```
(and adding `use papillon_shared::schema_phrase;` at the top of the file.)
Reviews (1): Last reviewed commit: "The implementation is complete. 12 commi..." | Re-trigger Greptile
|
|
||
| #[component] | ||
| fn SettingsTab(agent: Signal<Option<AgentInfo>>) -> impl IntoView { | ||
| let provider = move || agent.get().map(|a| a.provider_name.clone()).unwrap_or_default(); | ||
| view! { |
There was a problem hiding this comment.
Tab count badges are non-reactive — captured once at render
input_count(), returns_count(), and disclosure_count() are called immediately and passed as Option<usize> to TabButton. Since TabButton accepts a plain usize (not a signal), these values are snapshotted at the time the AgentEditor is first composed. Switching to a different agent in the sidebar will update the tab content but leave the count badges stale.
Consider passing the closures as signals or wrapping in Signal::derive() so they re-evaluate when the agent signal changes.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/papillon/frontend/src/components/registry/agent_editor.rs
Line: 250-254
Comment:
**Tab count badges are non-reactive — captured once at render**
`input_count()`, `returns_count()`, and `disclosure_count()` are called immediately and passed as `Option<usize>` to `TabButton`. Since `TabButton` accepts a plain `usize` (not a signal), these values are snapshotted at the time the `AgentEditor` is first composed. Switching to a different agent in the sidebar will update the tab content but leave the count badges stale.
Consider passing the closures as signals or wrapping in `Signal::derive()` so they re-evaluate when the `agent` signal changes.
How can I resolve this? If you propose a fix, please make it concise.
Benchmark Regression ReportThreshold: 10% regression vs baseline from main |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…glish conversion Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tInfo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fo literal in web_service The AgentInfo struct gained two new fields (execution_target, lifecycle) but the ad_to_info helper in web_service.rs was not updated, causing a WASM-only compile error (frontend crate is excluded from workspace check). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r, and JSON-LD panel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ument dead signals Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s, and disclosure display Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ns via Tauri IPC Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rors Use pap://local:unpublished sentinel to distinguish Unpublished from Draft in published_to. sign_and_publish_local clears the sentinel on re-publish. Wire registry.error signal on IPC failure instead of silently dropping it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ests, fix error handling Extract duplicated lifecycle derivation into lifecycle_from_published_to(), add LOCAL_UNPUBLISHED_SENTINEL constant to remove magic strings, use LOCAL_REGISTRY_URL constant throughout, fix unnecessary heap allocations, add 5 unit tests for all lifecycle state transitions, surface list_local_agents failures via registry.error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ackend and WASM frontend compile clean. **What was built:** The Registry page is now a Postman-style agent authoring editor. Schema.org vocabulary is hidden from all surfaces via `schema_phrase()` (algorithmic conversion using `heck`). Execution target is derived from endpoint URL scheme (`https://` → Remote, `file://` → Local, `did:/pap://` → Sub-agent) — zero TOML changes to the 200+ catalog agents. The lifecycle state machine (Draft → Published → Unpublished → Published) is fully wired: `sign_and_publish_local` and `unpublish_local` Tauri commands persist state via sentinels in `published_to`, `lifecycle_from_published_to()` is the single derivation point (5 unit tests), and IPC errors surface via `registry.error` rather than being silently dropped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ep validation error
cargo-leptos's manifest validator rejects `{ workspace = true, default-features = false }`
on papillon-shared because the workspace entry has no default-features override.
Inline the 3-line schema_phrase helper directly in the registry crate with heck to
eliminate the problematic workspace dep entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**3 failures, all traced to one root cause:** `cargo-leptos`'s manifest validator rejects `{ workspace = true, default-features = false }` on `papillon-shared` — it can't find the dep in `workspace.dependencies` even though it's there, because the cargo-leptos validator has a known quirk with that combination.
**Fix:** Removed the `papillon-shared` workspace dep from `apps/registry/Cargo.toml` entirely, added `heck = "0.5"` directly, and inlined the 3-line `schema_phrase` function into `agents.rs`. Verified `cargo check -p pap-registry --features ssr` and `cargo fmt --all` both pass locally.
- **Build registry Docker image** — was the root cause; now fixed
- **Federation E2E Tests** — downstream of Docker build, will unblock automatically
- **Chrysalis Integration Tests** — also downstream of Docker build, will unblock automatically
CI is running now on the new push. All other checks (Test, WASM, Benchmark, Clippy, etc.) were green on the previous run.
… no-op, schema_phrase
- Wire ExecutionTarget::derive() in agents.rs (def_to_agent_info and
list_local_agents), registry.rs (ad_to_info), and web_service.rs so the
Execution Target badge reflects the actual endpoint URL scheme instead of
always showing None
- Surface a user-visible error via registry.error when Sign & Publish is
clicked on a Draft agent with no DID, instead of silently no-opping
- Replace raw trim_start_matches("schema:") in peer_browser.rs with
schema_phrase() so Action suffix and CamelCase conversion are applied
consistently throughout the registry UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TabButton now accepts Option<Signal<usize>> instead of Option<usize>. Call sites wrap the existing closures in Signal::derive() so badge counts re-evaluate when the active agent changes in the sidebar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e14deba to
a6214e2
Compare
Both jobs had no timeout and defaulted to GitHub's 6-hour limit. cargo build cold-compile on a fresh runner can take 20-40 minutes; without a bound these jobs run indefinitely when cache misses occur. Set 30-minute timeout so failures surface quickly rather than blocking PRs for hours. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… test registry-probe used shared-key "registry-ssr" but ran in parallel with test-registry instead of after it, causing both to compile the full SSR dep tree from scratch simultaneously. Add needs: [test-registry] so the probe reuses the warm cache written by the test job. web-unit-test used shared-key "wasm-test" (unique) so it always compiled from scratch even though web-build/web-check/web-clippy already populated the "wasm" cache. Switch to shared-key "wasm" to reuse that cache. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cargo test -p papillon-shared --features wasm has no OpenSSL dep — the wasm feature only pulls in wasm-bindgen, js-sys, web-sys, and pap-federation (with default-features=false), none of which link against libssl. The apt-get step was taking 30 minutes on runners where azure.archive.ubuntu.com is slow, hitting the timeout we added. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
schema_phrase()— algorithmic conversion usingheck(stripsschema:prefix,Actionsuffix, converts CamelCase to Title Case)ExecutionTarget(Remote/Local/SubAgent) from endpoint URL scheme at load time — zero TOML changes to 200+ catalog agentspublished_toWhat changed
papillon-sharedschema_phrase()for schema.org → plain-English conversion (6 tests)ExecutionTargetenum derived from endpoint URL schemeAgentLifecycleenum (Draft / Published / Unpublished) onAgentInfoPapillon frontend (
apps/papillon/frontend)/registryroute:RegistryPagewith sidebar + editor layoutAgentSidebar: agents grouped by lifecycle, colored state dots, "+ New" buttonAgentEditor: tabbed editor (Input / Returns / Disclosure / Endpoint / Settings), action selector showing plain-English verbs, lifecycle badgeJsonLdPanel: live JSON-LD advertisement preview, lifecycle-contextual action button wired to Tauri IPCPapillon backend (
apps/papillon)sign_and_publish_localcommand: transitions Draft/Unpublished → Publishedunpublish_localcommand: transitions Published → Unpublishedlifecycle_from_published_to()helper: single derivation point for lifecycle state (5 tests), usingpap://localandpap://local:unpublishedsentinelsregistry.errorsignalRegistry app (
apps/registry)schema_phrase()to capability, returns, and disclosure display stringsTest Plan
cargo test -p papillon-shared— 335 passedcargo test -p papillon— all lifecycle tests pass (5 new tests forlifecycle_from_published_to)cargo check --manifest-path apps/papillon/frontend/Cargo.toml --features wasm— cleanschema:strings visible🤖 Generated with Claude Code