feat(#2050): set matrix avatar in the daemon over the live client

This commit is contained in:
damocles 2026-06-27 19:35:21 +02:00 committed by mara
commit c99fa714d9
8 changed files with 118 additions and 210 deletions

View file

@ -165,9 +165,8 @@ Tree-shape version:
`systemd.globalEnvironment` in `meta.rs` for every container
(manager included), so all token/state paths resolve through
`$HYPERHIVE_STATE_DIR`. The harness-base shell scripts
(tea-login, forge-avatar-sync, matrix-avatar-sync) simplified
from glob+for loops to a direct `$HYPERHIVE_STATE_DIR/<token>`
read.
(tea-login, forge-avatar-sync) simplified from glob+for loops to a
direct `$HYPERHIVE_STATE_DIR/<token>` read.
### E — prompt + tools

View file

@ -474,10 +474,10 @@ the state dir at provisioning time:
- `forge-avatar-sync` — uploads `hyperhive.icon` SVG to the
agent's Forgejo profile, so the icon shows up on commits / PRs /
issue comments.
- `matrix-avatar-sync` — same idea for matrix profile avatars
(two-step `media upload``set avatar_url` dance — see
`docs/persistence.md::matrix-avatar-sync` for the protocol
detail).
(The matrix profile avatar is **not** a oneshot — `hive-matrix-daemon`
sets it over its live authenticated Client; see
`docs/persistence.md::matrix avatar`.)
Shape contract — every one of these:
@ -498,7 +498,7 @@ Shape contract — every one of these:
5. **Re-runnable**: a second invocation produces the same final
state (idempotent uploads, idempotent config rewrites). Used
by the `.path` watchers that re-fire on token appearance (see
`docs/persistence.md::matrix-avatar-sync`).
`docs/persistence.md::Matrix per-agent daemon`).
The artefact lives under the agent user's home where applicable
(`~/.config/tea/config.yml`) and is chown'd to that user, but the

View file

@ -396,39 +396,35 @@ agent containers come up. Without the path-trigger sibling
first time it ran and the MCP would have no backend until the next
restart. The `.path` unit makes the appearance of the token re-fire
the service so the daemon comes alive in the same boot cycle as
provisioning. `matrix-avatar-sync.path` uses the same pattern for
the icon-upload oneshot.
provisioning. The same token watcher also drives avatar setting: on a
restart the daemon re-runs each account's bring-up, which sets the
avatar (see below).
### matrix-avatar-sync (two-step media + profile dance)
### matrix avatar (set by the daemon over the live Client)
Mirrors the forge-avatar oneshot's shape (`docs/conventions.md::
Best-effort oneshot services`) but differs in protocol: matrix
avatars are a two-step `POST /media/r0/upload` → `PUT
/profile/<user_id>/avatar_url` dance, both authenticated by the
`access_token` written by `hive-c0re::matrix::ensure_user_for` to
`<state>/matrix-token`.
The agent icon (`hyperhive.icon`, an SVG) is published as each matrix
account's profile avatar by `hive-matrix-daemon` itself
(`hive-matrix-mcp::client::sync_avatar`), not a separate oneshot. After
the daemon builds + restores an account's `Client` (authenticated,
pointed at that account's resolved homeserver), it calls matrix-sdk's
`account().upload_avatar()` — one call that uploads the media and sets
`avatar_url`. Because it reuses the live Client, there is no hardcoded
homeserver URL, no token re-read, and no token-file globbing: the daemon
already iterates every configured + dashboard-discovered account in its
bring-up loop, so the avatar is set for **every** account.
The oneshot syncs the avatar for **every** account, not just the
hive-internal one: a `sync_one` shell function runs the upload + set
dance for the bare `matrix-token` (`main`, against `localhost:8008`)
and then for each dashboard-provisioned `matrix-token-<name>`, reading
that account's homeserver from its `matrix-account-<name>.json` sidecar
(mirrors `accounts::discover_token_accounts`). Idempotency is
**per-account**: a `matrix-avatar-icon-hash[-<name>]` file each, because
the `mxc://` URI is homeserver-scoped so every account uploads to its own
homeserver independently.
Nix rasterizes the SVG to a 512x512 PNG at build time (`iconPng`, via
librsvg) and forwards its store path as `HIVE_ICON_PNG` on the daemon
unit, gated on `hyperhive.icon != null`. No icon configured → the env is
unset → `sync_avatar` returns early and no avatar is set.
Triggered by EITHER boot (`wantedBy = multi-user.target`) OR the
sibling `matrix-avatar-sync.path` firing on token appearance — its glob
covers both `matrix-token` and `matrix-token-*`, so an account logged in
after boot gets its avatar without a restart. Both paths re-run the
oneshot idempotently — running the avatar set twice is harmless.
Critically: **`RemainAfterExit = false`** (not the more common
`true` for oneshots). systemd treats `RemainAfterExit = true`
oneshots as "still running" after the first exit, so the second
trigger from the `.path` watcher becomes a no-op. Setting it to
`false` lets re-fires actually re-execute. The trade-off is the
service unit shows `inactive (dead)` between fires — visible in
`journalctl` but harmless; the `.path` unit drives the lifecycle.
Idempotency is **per-account**: an `avatar-icon-hash` file in each
account's matrix-sdk `state_dir`. The daemon hashes the PNG bytes and
skips the upload when unchanged, because every upload mints a fresh
`mxc://` URI that emits a profile state event in every joined room —
re-uploading identical bytes is timeline spam. A dashboard-provisioned
account gets its avatar when the `systemd.paths.hive-matrix-daemon` token
watcher restarts the daemon (which re-runs the per-account bring-up), so
no separate avatar trigger is needed. Avatar failures are swallowed
(logged, non-fatal) so they never break account bring-up or sync.

View file

@ -317,9 +317,9 @@ hyperhive.icon = ./icon.svg; # default: null (falls back to shared hyperhive lo
Path to an SVG file used as this agent's visual identity — shown in
the per-agent page header, as the page favicon, and uploaded to the
agent's Forgejo profile avatar (via the `forge-avatar-sync` boot
unit) and Matrix profile avatar (via `matrix-avatar-sync`). Commit
the SVG next to `agent.nix` in the config repo and reference it as a
relative path.
unit) and Matrix profile avatar (set by `hive-matrix-daemon` over its
live Client). Commit the SVG next to `agent.nix` in the config repo
and reference it as a relative path.
When `null` (the default), the agent falls back to the shared
hyperhive branding mark. The harness serves whichever icon is active