Conversation
…ature Robrix can now act as the human-facing client for the hagency control plane (https://github.com/hagency-org/hagency), which runs Claude Code and Codex coding agents and exposes them in Matrix rooms through a bridge bot. All of it is behind the new `agent_chat` cargo feature; default builds only gain empty placeholder widgets (`src/agent_chat_dummy.rs`), mirroring `tsp`/`tsp_dummy`. * Owner approval cards: `com.agentchat.approval.request.v1` events render as a card with tool, command preview, expiry, and the 2-4 decision buttons the bridge offered (`approve_once`, `approve_task`, `approve_always`, `deny`). Clicking one sends `com.agentchat.approval.verdict.v1` echoing every binding field, after refreshing the bridge's device keys and rotating the outbound Megolm session. Binding fields are read only from the original event; malformed requests and status/verdict notices never show buttons. * Timeline event filter extended so the SDK does not drop those msgtypes. * Inviting `@ac_<team>_<role>` also invites `@agent-bridge-<team>` (and the legacy `@agent-bridge`), best-effort. * Agent messages get a role / message-kind badge after the sender name; the bridge's leading type emoji and trailing permalink line are stripped. * `/create-issue`, `/go`, `/review`, `/status` (coordinator present) and `/task`, `/thread` (any agent present) are offered in the `/` popup and sent as plain text, gated by a new Settings -> Preferences toggle. * 19 unit tests for the protocol, identity, presentation, and command logic; docs in docs/agent-chat.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
…ms list
Found by driving the real client against a real Palpo homeserver, which no unit
test could have surfaced: agent-chat approval events are custom msgtypes, so
they fell through ruma's `MessageType::_Custom` arm in `event_preview.rs` and
rendered as
soak_owner: [Custom message]: CustomMessageContent { msgtype: ...
in the rooms list. The bridge already writes a human-readable `body`; show that
instead, gated on the feature so default builds are untouched. The fields are
private, so this reads them through `MessageType`'s public accessors.
Also from the same soak:
* `src/agent_chat/testdata/` holds the exact request and verdict bytes a real
Palpo stored and returned, with a test asserting they parse into a card and
that the verdict this client builds matches the one the server accepted,
field for field.
* `tools/agentchat-probe/probe_approval.py` drives a running app through
makepad's `--remote` HTTP control surface, which injects input through the
same path a human click takes. It opens the room, asserts the card, clicks a
decision button, and confirms the resulting verdict event on the homeserver.
* docs record the verified results (17/17 wire checks, 15/15 UI checks) and the
upstream makepad bug that rules out the headless renderer on macOS:
`os/cx_shared.rs` calls `os::apple::metal::note_input_event()` under
`cfg(target_vendor = "apple")` while `os/mod.rs` gates `pub mod apple;` behind
`not(headless)`, so headless builds fail with E0433.
122 lib tests pass with the feature on, 102 with it off; clippy clean in both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
…just com.agentchat A real soak against the live HAFleet deployment on the remote mini found that its bridge (v1.2.0) emits `com.hafleet.approval.request.v1` with content key `com.hafleet.approval`, and accepts verdicts only in that namespace — there is no `com.agentchat.*` compatibility. So this client (and upstream robrix2, and the hagency master the port was based on), which parsed only `com.agentchat.*`, would render NOTHING against that deployment: the timeline filter drops the events and the parser never fires. Make the approval layer namespace-agnostic. A `Namespace` derives the event key and the request/status/verdict msgtypes from a base, and the client accepts `com.agentchat`, `com.hafleet` and `com.hagency`. A parsed request remembers its namespace, and `verdict_content` echoes it — the wire name is the bridge's to choose, and a verdict in the wrong namespace is ignored by the bridge. Verified end to end: * server side, against the live HAFleet backend: owner binding, forged-sender rejection, digest-tamper rejection, owner approval, single-use replay rejection, bridge-secret enforcement (6/6). * client side, driving the real UI over makepad's remote bridge with a real `com.hafleet.*` request on a real Palpo: card renders, Approve once emits a real `com.hafleet.approval.verdict.v1` from the owner (3/3). 122 lib tests pass with the feature (incl. a HAFleet-namespace test), 102 without; clippy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
The port targeted upstream Robrix, so its approval card and agent badge fell back to upstream's older visual language and three hardcoded hex colours, which robrix2's own rules forbid. Restyle just the agent-chat surfaces onto robrix2's design-token recipes without porting the whole design system: * `src/agent_chat/tokens.rs` defines only the RBX_* tokens these surfaces use, with the values copied from robrix2 under the same names, so a future port of the full token layer replaces them in place. Typography keeps robrix2's sizes, weights and line spacing on the theme fonts, because robrix2's RBX_FONT_* styles load custom font files upstream does not ship. * The card follows robrix2's AgentApprovalCard recipe (warning-tinted surface, RBX_RADIUS_SM, card-title colour, pill status badge, meta hint), and the buttons its Primary/Secondary/Danger recipes (RBX_CONTROL_H_MD, 1px semantic stroke, tinted surface). * The agent badge is sized like robrix2's bot badge (16px, 3px radius) and takes the accent pair for workflow roles and the neutral pair otherwise, applied at populate time since portal-list items are recycled. No hardcoded hex remains in the card. Verified visually through the remote bridge against a live request. 123 lib tests with the feature, 102 without; clippy clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
robrix2 opens a "Feishu-style" account menu from a dedicated button at the bottom-left of the desktop rail (robrix2 PR #265); upstream has no such menu, only the top avatar that opens Settings directly. Port the menu structure: * `src/home/account_menu.rs`: an anchored overlay card that opens upward from the rail — active-account header (avatar, display name, user ID, "Active"), a divider, "Account Settings" and a danger-styled "Log Out". Same scrim/anchor/clamp pattern as the room context menu; closes on backdrop click, Escape, back gesture, or a desktop/mobile layout change. * A new `AccountSwitcherButton` at the bottom of the desktop rail, built on upstream's own `NavigationTabButton` recipe so it shares the rail's hover animation. The top avatar keeps opening Settings, unchanged. * The App hosts and positions the overlay next to its other menus. robrix2's menu also lists other logged-in accounts and "Log Into More Accounts"; both depend on its multi-account layer (`account_manager`, `request_switch_account`), which upstream does not have — it holds exactly one session. Those rows are deliberately not rendered; the card keeps the same shape so they can slot back in if multi-account is ever ported. The RBX token slice moves from the feature-gated agent-chat module to `src/shared/rbx_tokens.rs` (always compiled) since this menu is not an agent-chat feature; three tokens it needs are added (scrim, divider, icon size). The rail button uses robrix2's "people" icon, copied into resources. Verified live through the remote bridge: the button opens the card with the logged-in account, Active marker, Account Settings and Log Out. 125 lib tests with the feature, 104 without; clippy clean; no DSL errors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
robrix2's Settings screen (its PR #211) is a header, a row of segmented category tabs, and a PageFlip with one page per category; upstream's was one long scrolling column. Port the structure onto upstream's existing sections: * Tabs: Account, Preferences, Privacy, About. robrix2 also has Devices and Labs; upstream has no content for either (no devices page, no agent registry), so they are not offered. * Each page wraps the same sub-widget upstream already had (AccountSettings, AppSettings, PrivacySettings, AboutSettings), so nothing behind the tabs changed; the TSP settings placeholder stays on the Account page. * Tab styling follows robrix2 exactly: solid RBX_ACCENT with white text when selected, ghost with secondary text otherwise, applied at runtime and re-applied after a ScriptReapply. Canvas background, page title and close button use the shared RBX token slice, which gains the six tokens this screen needs (canvas, on-accent fg, xs radius, page-title type, spacing). * Populate, ScriptReapply restore, close on X/Escape/back, and the TSP modals are unchanged; the page flip is not lazy so populate still reaches every sub-widget on open. Verified live: Account Settings from the account menu lands on the tabbed screen, and each tab switches its page. 125 lib tests with the feature, 104 without; clippy clean; no DSL errors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
Replaces the local rbx_tokens.rs slice with robrix2's design_tokens.rs (fonts sit on theme.font_* until the system-fonts branch lands), moves the primary/CTA colour and unread-badge fills to the teal accent palette, adds the robrix2 icon set, the 4px spacing grid, RADIUS_*, the settings colour constants and the 76pt navigation rail width. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
- ExpandArrow draws an open chevron stroke instead of a filled triangle. - CollapsibleHeader is a transparent, calm group header (secondary-grey label, tertiary chevron); only mentions surface in its badge. - UnreadBadge: robrix2 pill (30x20, token fills) with an "@" prefix on mention counts so the two badges differ by more than hue; keeps the last-drawn cache. - RoomsListHeader: regular-weight title, token-coloured sync icons, a search icon that emits RoomsListHeaderAction::OpenRoomFilterModal, and a hidden directory slot for a future directory screen. - RoomsListEntry: RBX text tokens, soft teal selected wash and faint hover wash (no solid fill, text keeps the same ink in every state), token link colour. - RoomsSideBar / RoomFilterInputBar / status label: surface, canvas and inset-field tokens; the mobile sidebar focuses its filter bar on the header's search action. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
Navigation rail: navy RBX_NAV_BG SolidView (flush to the window edge), transparent nav items with a navy pill on hover/active and a teal accent bar plus white icon when selected, icons at RBX_ICON_LG, nav-fg dividers, teal identity fallback for the own-avatar initial. Spaces bar entries get the translucent teal wash + accent bar and RBX_IDENTITY_TEAL text avatars; the spaces status label uses the nav foreground. The mobile bottom bar shares the navy palette so the same button templates read on both. Dock: active tab filled RBX_ACCENT with white text, inactive tabs on RBX_BG_SURFACE_SUBTLE with a flat 1px RBX_STROKE_STRONG bottom border, close button in secondary/danger colours. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
…button The room input bar is now a floating white card (RBX_BG_SURFACE on a soft stroke) with a toolbar row above the text field: attach (opens the file picker), @ (opens the member-mention popup), a quick emoji row toggle, / (opens the slash-command popup) and + (the existing upload/location popup menu). The text field is borderless on the card with a Markdown placeholder, and the send button is a rounded teal square with a white icon that greys out while the draft is empty; the lock badge still tracks encryption. MentionableTextInputRef gains insert_at_cursor(), which inserts text as an ordinary undo-able edit and re-detects the trigger, so the shortcuts behave exactly like typing. Matrix-link pills, inline code and quotes, and link preview titles move onto the RBX accent/link tokens. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
fill_keep leaves the pill in the sdf shape and box unions with it, so the bar fill covered the whole tile whenever border_size was zero. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXUgAs5yG46jKcgcPSJ2Ym
|
Thanks for the PR Yue! I'm not familiar with "hagency", and searching it on Google yields nothing. For example, I have no idea what any of this means:
Can you explain what that is? Perhaps it should be merged into the a2app fork of Robrix instead, if it's related to agentic workflows. Lots of the other stuff in this PR description sounds good too, like the various UI improvements. However this PR is simply way too large for me to review at once. Each of those UI changes can be split out into separate PRs, which would make it feasible for me to test and review them. Can you split each UI change into the smallest PR that it can reasonably be a part of? Thanks. |
|
Ok I see the hagency thing now https://github.com/hagency-org/hagency, so yeah I think Tha belongs in the a2app fork. |
|
Split out six focused UI PRs for individual review, each based directly on current upstream
Each changes one to four files and passes Clippy with all features/targets and warnings denied, spelling, and whitespace checks. The combined branches apply cleanly and pass all 104 library tests. A local fixture also verified desktop/mobile search focus, Settings tab switching with unsaved edits preserved, and the rendered badges/navigation/room-list styles. I left dock styling out because #1073 already reworks that area. The larger account-menu/composer changes and shared palette from this PR are not included in this batch; the original branch remains available as their source. |
Summary
Robrix currently drops Hagency approval message types and rejects its workflow slash commands. This PR adds optional Hagency support and ports the Robrix2 interface styling for the navigation, room list, composer, and settings.
Hagency support
agent_chatCargo feature, with native approval cards, expiry handling, and encrypted verdict delivery.com.agentchat,com.hagency, andcom.hafleetapproval events, preserving the request namespace and original binding fields in verdicts. Authorization remains enforced by the bridge.UI changes
The shared UI styling applies to all builds; the Hagency protocol and agent widgets are gated by
agent_chat.Validation
typos --config .github/typos.tomlandgit diff --checkpass.cargo test --locked --lib --features agent_chat: 127 passed on the final branch.cargo test --locked --offline --lib --no-default-features: 105 passed on the final branch.cargo clippy --locked --workspace --all-features --all-targets -- -D warnings: passed.Build with
cargo run --features agent_chat; enable workflow commands in Settings → Preferences.Scope
Long-reply folding, streaming animation, and
agent_opsclient sessions are not included. The documented live homeserver soak results are historical; no fresh live homeserver end-to-end test has been run for this PR.