Watch
0
0
Fork
You've already forked hyperhive
0
Commit graph hyperhive/frontend/packages
Author SHA1 Message Date
atlas
efbfec6d01 config PRs: remove the hive's config-PR webhook, poll and core merge
An operator's merge on the forge deploys a config PR through
swarm-controller's DeployRequest{rev}. The hive-side path that queued a
MergeConfigPr approval and merged the PR as `core` goes:

- the `/webhook/config-pr` receiver, its HMAC secret, the WebhookRegister
  boot node and the org-hook registration; the hive vhost's `/webhook/`
  location
- the 5-minute config-PR poll
- ApprovalKind::MergeConfigPr, its dashboard card, and the deploy DAG it
  drove (DeployWindow, MergeVerify, DeployApply, FinalizeDeploy,
  DeployTail), with verify_commit, the two-phase meta deploy, rollback
  refs, the PR-failure comment and forge/pr_merge.rs
- `fetched_sha`, `sha_short`/`pr_number` on approval events, and
  `sha`/`tag` on HelperEvent::ApprovalResolved: only the merge path set
  them

`config_repo`, `merged_pr_for_commit` and `post_pr_comment` move to
forge/pr_comment.rs for the merged-rev deploy's refusal comment.
Approvals v5 drops stored `merge_config_pr` rows; a test reopens a v4
database holding them.

Closes #4850
2026-10-02 23:13:03 +02:00
atlas
9224c0bd15 swarm UI: fetch linked accounts only for the opened agent
The detail panel makes one request for the agent it shows,
GET /api/hives/{hive}/agents/{agent}/linked-accounts, which returns every
matrix, forge and github account of that agent as names and hosts. The
all-agents route and the table's matrix-column rows are removed, so the
table makes no linked-accounts request. The panel stays keyed by
hive/agent. The bao grant is unchanged.

Refs #4855
2026-10-02 22:36:11 +02:00
atlas
7ccde4647b swarm UI: one request for every agent's linked accounts
GET /api/agents/linked-accounts returns one entry per agent that
/api/agents/status has a row for, as {hive, agent, accounts}, from one
store login. The agents page fetches it once (and again when a link dialog
closes) and hands each table row and the detail panel its agent's slice,
so the page makes no per-agent request. The per-agent route had no caller
left and is removed. The bao grant is unchanged: the same list on each
agent's matrix and forge metadata directories.

Refs #4855
2026-10-02 21:20:40 +02:00
atlas
4fd1380a5f swarm UI: key LinkedAccounts by hive and agent
Selecting another agent kept the previous agent's rows on screen until
the new fetch landed. Keying both mounts by hive/agent remounts the
component on an agent change; a version bump still refetches in place.

Refs #4855
2026-10-02 21:20:40 +02:00
atlas
3380c1915f swarm UI: show the accounts linked to each agent
GET /api/hives/{hive}/agents/{agent}/linked-accounts returns one row per
account linked to the agent, as kind, name and host: each matrix account
under swarm/agents/<agent>/matrix (with its homeserver, and the agent's own
`main` marked reserved), each forge label under swarm/agents/<agent>/forge
(with its url), and github when swarm/agents/<agent>/github-token exists
(host github.com, which is not stored). No credential field is in the
response type.

Listing those two directories needs a new controller grant: `list` on
secret/metadata/swarm/agents/+/matrix and .../+/forge only, pinned in
bao-grants.nix as the only metadata stanzas under agents/ beside the queue
revocation. Checked against a dev OpenBao 2.6.3: the grant lists those two
directories and is refused on agents/, agents/<agent>/, and a leaf.

The swarm UI agent detail panel shows all rows under "accounts"; the table
view's matrix column shows the matrix rows. The link badges stay.

Refs #4855
2026-10-02 21:20:40 +02:00
atlas
f8a8acae93 github swarm bao: address argus review on #4892
- docs/web-ui/README.md: drop the removed Credentials tile from the
  H0M3 hub list.
- api-error.ts, hive-warn.js: rewrite comments pointing at
  dashboard/src/credentials.js and credentials.html, now deleted, to
  state what the code does instead.
- swarm-secret-client/src/github.rs: correct the Credential.value doc
  to the actual read command (bao kv get -format=json | jq
  .data.data.value), keeping the load-bearing-field-name point.
- github-token.nix, agent-github-bao.nix, LinkGithubAccountForm.tsx:
  restate added comments as current behaviour instead of changelog
  wording ("has always had", "holds the token now").

Refs #4347
2026-10-02 17:54:45 +02:00
atlas
8e23feb01b github: PATs live in swarm bao; the agent fetches them itself
An operator links an agent's GitHub personal access token in the swarm UI
(LinkGithubAccountForm, "link github account" on /agents). swarm-controller's
PUT /api/hives/{hive}/agents/{agent}/github-account stores it at
swarm/agents/<agent>/github-token (swarm_secret_client::github), a flat leaf
under the agent's prefix that the agent's existing read grant already covers:
no policy change, and no list grant, since there is one token per agent.

In the agent, hive-agent-github-token (oneshot + 2-minute timer, as the agent
user, under its own store certificate, ordered before hive-github-notify)
reads that path and writes <state>/github-token, 0600 and agent-owned, the
file the gh wrapper, git credential helper and hive-github-notify already
read. It replaces the file by rename only when the bytes changed and never
deletes it: a hive-written github-token stays until a token is linked in the
swarm UI. It is installed only with a store address and
services.hyperhive.agent.github.enable.

Removed: the dashboard's CR3D3NTIALS page (credentials.html/js/css, its
build entries and H0M3 tile; GITHUB was its only tab), hive-c0re's
dashboard/matrix_accounts.rs with GET/POST /api/github-account,
priv_client::write_agent_github_token, the host socket's
SetAgentGithubToken and `hivectl github set-token`, and hive-priv's
WriteAgentGithubToken with write_agent_state_file, its only caller gone.

Docs: integrations/github.md and swarm/ui.md describe the swarm path,
swarm/credentials.md gains the store-path row, and the hive UI docs,
hivectl docs and security.md's hive-priv table drop the removed pieces.

Closes #4347
2026-10-02 17:48:27 +02:00
atlas
46f1f3cbdb docs: point matrix GUI comments at the swarm UI, drop the M4TR1X tab
The previous commit removed dashboard.md's M4TR1X section. These
comments and the `deploy.matrix.gui.enable` option description still
described a hive-dashboard M4TR1X tab or cited that section. They now
state what the option does: it serves the client on the gateway vhost
and adds the swarm UI's Matrix quick link (docs/swarm/ui.md::Quick links).

Refs #3902
2026-10-02 14:44:34 +02:00
atlas
2c7e586f47 forge: external forge accounts live in swarm bao; the agent fetches them itself
An operator now links an agent's external forge account (label, base URL,
token) in the swarm UI. swarm-controller stores it at
swarm/agents/<agent>/forge/<label>. There is no index: the store's
listing of the agent's forge/ directory is the set of accounts.

In the agent, hive-agent-forge-accounts (oneshot + 2-minute timer, as
the agent user, under its own store certificate) lists
swarm/agents/<agent>/forge/ with the `list` #4866 grants an agent on its
own metadata subtree, reads each account, and writes
<state>/forge-<label>-token and forge-<label>.json in the names and shape
hive-forge -f already reads. An empty listing (a 404, which `bao kv list
-format=json` answers with `{}` and an empty stderr) is zero accounts; a
denial or an unreachable store fails the unit. It never deletes: files
for labels not listed, including ones the hive wrote, stay as they are.

Removed: the dashboard FORGES tab (credentials.js/html section and its
CSS), hive-c0re's extra_forges.rs and its routes, priv_client's
extra-forge calls, and hive-priv's WriteAgentExtraForgeAccount /
DeleteAgentExtraForgeAccount with their helpers. The GITHUB tab and
WriteAgentGithubToken stay.

Also: persistence.md's matrix avatar note names the exit-75 restart on a
changed account listing, not the dashboard, as what brings a linked
account up.

Refs #4348
2026-10-01 18:05:33 +02:00
atlas
97fb76ce99 matrix: the agent's daemon pulls its linked accounts from bao itself
hive-matrix-daemon now learns which external matrix accounts it has from
the swarm secret store, under the agent's own certificate, and the hive
push chain for matrix is gone.

The daemon lists swarm/agents/<agent>/matrix/ (the `list` its policy
grants on its own metadata subtree), reads each account's homeserver
from its credential, and brings the accounts up with their tokens from
the store. Every two minutes it lists again and exits with 75 when the
set of linked accounts changed; the unit restarts on 75 without counting
a failure. A listed name whose credential reads as absent is skipped and
logged once. At start it removes the matrix-token-<a> /
matrix-account-<a>.json pairs a hive delivered (a sidecar marks a pair
as delivered; a declared tokenFile keeps its token).

Removed: CredentialNotice and the $SWARM.credential.* subject and NATS
grant, the controller's publish and its queue precondition on the PUT
route, hive-c0re's credential subscription arm and workers/credential.rs,
priv_client::write_agent_matrix_token, hive-priv's WriteAgentMatrixToken
and its helpers, and the daemon's state-dir account discovery.

Kept: WriteAgentGithubToken and the external-forge path
(WriteAgentExtraForgeAccount, extra_forges.rs) are untouched, and a
declared matrixAccounts tokenFile is still read when the store has no
token for that account.

Refs #4348
2026-10-01 17:43:28 +02:00
atlas
5785c0024c Make agent creation swarm-only and refuse a name placed on another hive
swarm-controller's POST /api/agents now refuses (409) a name the swarm
has already placed on a different hive: a non-Destroyed declaration in
that hive's wanted state, or a SetAgentWanted node still queued for it.
The same name on the same hive is that agent being re-created and goes
through. A wanted state that cannot be read refuses (503/500) instead of
reading as "placed nowhere". Creations are serialised from that read to
the graph insert so two concurrent creations of one name cannot both
pass.

Hive-level creation is removed: hivectl `agent create` / `request-create`,
HostRequest::Spawn / RequestSpawn, the dashboard POST /api/request-spawn
route, and ApprovalKind::Spawn with its approve/resolve arms and the
approval-carrying `templates::spawn`. The swarm path (deploy request or
wanted-state sweep -> queue_first_deploy -> templates::first_deploy) used
none of them. Old `spawn` approval rows are skipped by collect_lenient,
as `init_config` rows were in a3b672d1.

policy.rs's comment on agent_object_name stated swarm-wide name
uniqueness as a fact; it now says where it is enforced and what that
check cannot see.

Refs #4396
2026-09-29 15:47:40 +02:00
atlas
6a1d85c24f hive-dashboard: remove the MATRIX credentials tab and its login route
The CR3D3NTIALS page's MATRIX tab was the only caller of
`POST /api/matrix-account-login` (provision/log in an external matrix
account through the hive) and `GET /api/matrix-accounts` (its account
list). External matrix accounts are linked from the swarm UI now
(`LinkMatrixAccountForm` -> swarm-controller), so the hive-side UI and
both routes go. `priv_client::restart_matrix_daemon` had no other caller
and goes with them.

Already-provisioned credentials keep working: the `matrix-token-<name>`
files and `matrix-account-<name>.json` sidecars the old route wrote are
still discovered by hive-matrix-mcp (`accounts::configured` ->
`discover_token_accounts`), the `matrix-token*` path unit still re-fires
the daemon, and `WriteAgentMatrixToken` stays for the swarm credential
worker. Removing that usage waits on moving the existing creds to
swarm level.

The GITHUB tab is the credentials page's default tab now.

Refs #4348
2026-09-29 13:54:18 +02:00
iris
88c386c96c swarm-ui: move dynamic agent-term tabs into the top nav bar
mara, PR review: "the dynamic tab should be in the top bar, not a new
one below". Moves the .shell-tabs group from its own sticky row under
the header into .shell-nav itself, right after the nav indicator.

Also adds a third re-measure effect for the sliding nav indicator,
keyed on tabs.length: with tabs inline in the same flex row the
indicator measures, closing a background tab (no navigation) can
shrink the row without the hop effect's own re-measure ever firing.
Same reflow-not-navigation reasoning as the existing resize-listener
effect.
2026-09-28 23:17:44 +02:00
iris
7bf4cfbc4c swarm-ui: fix specificity claim in AgentTermPreview.css comment
argus's review on PR #4784 caught a wrong technical claim: the
comment said the .ui-agent-term-preview-full override rules relied
on source order because they had equal specificity to the
un-modified rules above. They don't — each override selector adds
one more class (the .ui-agent-term-preview-full prefix) than what it
overrides, so they're strictly more specific and win regardless of
file order. Corrected the comment to say so.
2026-09-28 23:17:44 +02:00
iris
c460e91bd1 swarm-ui: full-tab agent terminal (#4506)
Adds a full, non-capped agent terminal reachable from a new expand
trigger on the embedded AgentTermPreview (the detail-panel preview on
AgentsPage stays as-is, just gains the trigger). Opens
/agents/:name/terminal in a new dynamic tab in Shell's header, next to
the static nav row — tabs persist across a reload via useDynamicTabs,
a small localStorage-backed hook built on @hive/shared's existing
settings-storage primitive.

AgentTermPreview gains two new props to support both mounts from one
component: fullHeight (drops the 12em preview cap, fills its page)
and showHeaderBadges (default true — lets a future caller that
already shows turn_state/model/ctx/cost elsewhere suppress this
cluster; AgentsPage doesn't use it, see below).

Deviation from the originally posted plan (issue comment 80596): that
plan proposed AgentsPage's embedded preview pass showHeaderBadges as
false, reasoning the detail panel already duplicates that info.
Checked the actual code before implementing — it doesn't; AgentRow/
AgentTypes.ts carry none of turn_state/model/ctx/cost, and
AgentTermPreview's own floating badges are the only place swarm-ui
shows them. Left the badges visible there instead of shipping a
regression the plan's own stated justification didn't hold up to.

Also fixed a same-tab pub/sub race found by actually rendering a cold
load of /agents/:name/terminal (headless chromium, not just reasoning
about the code): useLocalSetting subscribes inside a useEffect, and
mount effects fire children-before-parents, so a descendant's
mount-time write (AgentTerminalPage registering its own tab) can beat
an ancestor's (Shell's) subscription into existence, leaving Shell's
tab row silently empty on a direct/reload load. Fixed by having
useDynamicTabs re-sync from storage on every location change, not
just on notify() — the fix lives in the new hook itself, not in the
shared settings-storage primitive theme/motion overrides also use.
2026-09-28 23:17:44 +02:00
iris
9a444c27fa agent ui: add a clickable new-session menu entry
Same gap #4611 fixed for logout: the Preact rewrite's StatusChips menu
never picked up new-session as a click path, only /new-session typed
twice into the terminal. postNewSession already existed in
termActions.ts. Wire it into the status menu with the same arm-then-
confirm click pattern logout/cancel-turn already use.

Closes #4612
2026-09-28 13:53:58 +02:00
atlas
3b8df27dcf swarm-ui: show each agent's icon on its card
Each agent card now leads with the agent's icon, loaded as an `<img>`
from `GET /api/agents/<name>/icon`: the same 5em square, background and
fallback as the hive dashboard's container row. An agent with no icon
(the route's 404), or any other failed load, shows the dimmed hyperhive
mark (`/favicon.svg`) instead of a broken image.

Only ever an `<img>`, never inline markup: the body is an agent-authored
SVG, and an image load does not run its script.
2026-09-28 13:47:37 +02:00
iris
d9beb965a5 docs/web-ui: drop the redundant Topology tree section
C0NTAINERS already states the flat/alphabetical/no-nesting fact;
the section repeated it with zero new operator info. Fix the two
dangling references (README.md reading-path index, swarm.js
comment) rather than leaving them pointing at a removed heading.
2026-09-28 13:18:12 +02:00
iris
6b5c8cc51a dashboard: flatten the agent list, drop dead topology-tree machinery (#4638)
The backend dropped the agent hierarchy's parent field, so every
container is a root and buildAgentTree/treePrefixDom could only ever
produce a single-level flat list — the .tree-prefix CSS lane rules
already matched nothing. Replaced with sortedContainerRows, a plain
alphabetical sort, and dropped the now-dead .tree-prefix/.tree-lane/
data-depth CSS and the depth/isLast/ancestorIsLast fields from the row
fingerprint and buildContainerLi's signature. No visible behavior
change - the rendered list was already flat, just via dead machinery.

Docs updated to describe the simpler implementation directly instead
of narrating the removal (kept the heading name since swarm.js still
points a comment at it).
2026-09-28 13:18:12 +02:00
atlas
179f873722 docs: retire the agent hierarchy from every page that described it
The topology doc keeps its filename and its second half (manager
special-casing, harness unit shape) — both are cross-referenced from
other pages and neither is about the parent field. Its first half is
rewritten: what topology.json is now, and a table of what the removal
took with it, so a reader who finds `<parent>` or `set-parent` in an old
issue thread learns it went away rather than moved.

The dashboard's tree-rendering section is marked dormant rather than
deleted: the walk is still in swarm.js and retiring it is the frontend
owner's call.
2026-09-21 22:08:47 +02:00
atlas
d94bc2188d topology: drop the parent field and the hierarchy it fed
`topology.json` was a map of `name -> parent | null`, and that value fed
the whole agent hierarchy: `<parent>` / `<children>` recipient sentinels,
the reparenting API (CLI verb, wire verb, dashboard endpoints, DAG node),
the dashboard tree, the rebuild depth sort, and an unconditional
bind-mount grant giving every agent RW on its direct children's state.

Per the operator's ruling the field goes, and with it all of the above.
The file survives as what remains once the value is gone: the roster of
agent names, which is the set `ManageRootAgent` grants mounts over. It is
now a JSON array; `read` still accepts the old map shape and keeps its
keys, so a hive that upgrades across this does not blank its roster (and
so no capability holder loses its mounts for the length of that window).

Two sites kept their behaviour under a different recipient rather than
losing it. Both addressed `<parent>`, which the broker already resolved to
`operator` for a root agent, and every agent is now what that fallback
called a root:

- the harness's turn-failure / plugin-failure notification
  (`Surface::send_to_parent` -> `send_to_operator`), and
- the send allow-list's always-permitted escape hatch, so an agent with a
  restrictive allow-list still has a way to say it is stuck.

What is NOT preserved, deliberately: an agent with no capability no longer
sees any other agent's dirs. `ManageRootAgent`'s own grant is unchanged --
still every agent in the roster, still state RW + config RO, still no
`harness`.

The dashboard's reparenting control (the M0V3 picker) is deleted with its
CSS. The tree rendering that reads `ContainerView.parent` is left for the
frontend owner -- it degrades to a flat list with the field gone.
2026-09-21 22:08:47 +02:00
iris
8d257e9362 agent ui: restore a clickable logout affordance to the status menu
mara: 'hive ui agent term: logout button gone? must have dropped with
one of the redesigns, pls bring it back or tell me where to find it'

It did drop, silently. The old app.js overflow menu had an
unconditional 🔓 logout item (dashboard-link + rebuild + new-session +
logout); the Preact rewrite's Root.tsx comment explicitly documents
dashboard-link moving to MetaNav and rebuild being removed outright,
but says nothing about logout — it just didn't come along. `/logout`
still works (TermInput's slash-command list, type-twice-to-confirm),
but nothing in the UI points at it or offers a click path anymore.

Fix: add a 'logout' entry to the status badge's dropdown (StatusChips'
StatusMenu) — the same menu that already hosts pause/resume and
cancel-turn, reusing its exact click-then-confirm-click pattern
(danger: true, armed/confirm state reset on close). Unlike cancel-turn
it's not gated on `thinking` — always offered, matching the old
overflow menu's unconditional entry. Wired through onLogout ->
termActions.ts's existing postLogout (already used by the slash
command, so the only new code is the menu entry + prop threading).

Scoped to just what was asked: /new-session has the identical
gone-from-any-menu gap (same old overflow item, no restored click
path), left alone here and flagged separately rather than folded in.

tsc --noEmit clean, nix fmt clean.
2026-09-21 17:18:36 +02:00
iris
24673fac96 dashboard: pre-fill resource-limit inputs so editing one doesn't clear the other
mara: 'filling just one of the fields should not touch the other.'

The cpu/mem override form always posts both fields, and the server
correctly treats an empty field as 'clear this override' (documented,
tested behavior in agent_config/resource_limits.rs). The bug was on the
client: the inputs only ever showed the current effective value as a
placeholder, never as the actual value — so a field left untouched
looked filled to the eye but posted empty, silently clearing whatever
override (or lack of one) was already in effect for that field.

Fix: set .value to the current effective value (already fetched and
displayed correctly in the read-only cap columns next to this form) so
leaving a field alone round-trips it unchanged. One resulting nuance
worth flagging: an agent with no per-agent override on a field, that's
purely tracking the hive-wide default, now pins that field to today's
default the first time *any* field on the form is edited, rather than
continuing to silently follow future hive-default changes. Fixing that
fully would need the server to expose whether each field is a real
per-agent override or just the resolved default, which is more than
this bug report asked for.
2026-09-21 15:55:05 +02:00
iris
d9c8b7ca8f swarm-ui: floating turn-state/model/ctx/cost badges on the agent term preview
Header-bar parity for the swarm-level agent terminal, per mara's ruling
"dont make it a header though - make it floating elements on top of the
terminal". Adds useSwarmAgentStateStream (SSE consumer for
swarm-controller's GET /api/agents/{name}/state/stream) and renders a
small read-only Badge cluster absolutely positioned in
AgentTermPreview's terminal box corner, reusing the same anchor pattern
@hive/shared/terminal.css's .tail-pill already uses.

Shows turn_state always, agent_state only when it isn't the boring "up"
case, model (with resolved-model tooltip), and ctx/cost usage. No badge
renders until the first header event lands - the swarm queue's
agent-state subject is transition-only with no seed/replay, and no
swarm-level endpoint today can seed turn_state/model/ctx/cost
synchronously.

Vocabulary (state tones, token-total/format helpers) ported from
@hive/agent's Root.tsx/lib/format.ts rather than imported, matching the
existing "small helper, not worth a cross-package hop" call
useSwarmTermStream's own appendRow already made.

Verified by rendering the real component tree against a stubbed
EventSource and screenshotting the result - the badge cluster initially
overlapped the terminal's first row of text, fixed by giving this
preview's own .live.terminal extra top padding.
2026-09-18 17:30:59 +02:00
atlas
a3b672d1d5 refactor(hive-c0re): drop the request_init_config tool and InitConfig approval
swarm-controller's `InitAgentConfigRepo` node already covers config-repo
creation, so this deletes a duplicate rather than a capability; old
`init_config` rows are skipped by `collect_lenient` with no migration, by
operator decision.

Refs #4398
2026-09-14 19:03:44 +02:00
iris
cab6910cbb swarm-ui: make the shell header sticky and frosted glass 2026-09-13 22:13:07 +02:00
iris
08aee4b704 swarm-ui: fix agent term preview box-sizing causing content to bleed past its wrap 2026-09-13 22:01:40 +02:00
iris
8db1e61cd3 swarm-ui: fix agent term preview placeholder wrapping one char per line 2026-09-13 21:47:23 +02:00
iris
e18619329d swarm-ui: fix treefmt formatting drift on the agent term files 2026-09-13 20:23:51 +02:00
iris
727bfb775b swarm-ui: fold the agent terminal into AgentsPage's detail panel, not a route
mara's steer on the issue, right as the previous commit's plan was
posted: the swarm-level term isn't a separate page, it's part of the
same pwa -- selecting an agent should show a small preview (no header,
no input) below the existing detail fields. That's the whole MVP for
this issue; the full terminal (+ sending input back to the agent) is
explicit follow-up scope.

AgentTermPage -> AgentTermPreview: drops the Panel wrapper, the
"terminal" nav badge + wouter route, and the back-link -- just the
inline preview, height-capped at 12em (@hive/shared/terminal.css's own
default is a much taller 72vh/60em, sized for a dedicated page rather
than a peek).
2026-09-13 20:23:51 +02:00
iris
593923375c swarm-ui: read-only agent terminal page consuming the swarm term stream
Moves the TermMsg rendering pipeline (Row.tsx, termMsg.ts, linkify.tsx,
markdown.ts) from @hive/agent into @hive/shared, so swarm-ui becomes a
second consumer of it instead of forking a copy -- CSS was already
shared (@hive/shared/terminal.css). marked+dompurify move from
@hive/agent's deps to @hive/shared's; swarm-ui picks them up
transitively, no new direct dep there.

New swarm-ui route /agents/:name/term (AgentTermPage), linked from
AgentsPage's detail panel via a "terminal" badge next to "link matrix
account". Consumes GET /api/agents/{name}/term/stream: unlike
@hive/agent's own useLiveStream (TermEnvelope-wrapped, history/backfill
dance), the swarm relay forwards one bare TermMsg per SSE event with no
envelope and no history endpoint -- useSwarmTermStream is a much
smaller hook for that shape (EventSource -> parse -> coalesce, nothing
to buffer/dedupe/backfill against).

Verified against a live SSE mock (screenshots in /agents/iris/state/screenshots/
3801-agents-detail-panel-terminal-badge.png and
3801-agent-term-page-live-rows.png -- real rows rendering through the
shared Row component, not just a build/typecheck pass).
2026-09-13 20:23:51 +02:00
iris
74fb07c052 agent: fix non-interactive badges losing their pill shape in the status row 2026-09-13 19:33:55 +02:00
iris
e1858b277a swarm-ui: extend neon glow to the swarm brand name 2026-09-13 19:19:26 +02:00
iris
fefeaecbbf swarm-ui: neon glow on panel titles + the active nav tab
Ports the subtle text-shadow glow @hive/agent's and the dashboard's own
headline rule already use (both independently converged on
`text-shadow: 0 0 Npx color-mix(color, transparent)`), not their whole
uppercase+letter-spacing headline typography -- that's those UIs' own
look, swarm-ui never adopted it and this issue only asked for the glow.

- Panel titles: `currentColor`-based, since a panel title has no one
  fixed accent (some are dynamic, e.g. an agent's own name) -- a fixed
  --purple glow on plain --fg text would read as a mismatched halo.
- Active nav tab: each item's own `accent` (NAV_ITEMS), the same value
  its underline already uses, so the glow and the underline read as
  one accent rather than two that happen to agree.
2026-09-13 19:19:26 +02:00
iris
9f8df505c6 swarm-ui: widen the agents page's shell body cap, same as issue report
AgentsPage's table view (and its list+detail split) hits the same
60em readable-line-length cap the issue report's wide table already
opts out of via shell-body-wide. Adds /agents to WIDE_BODY_ROUTES --
the allowlist's own comment already anticipated this, a future wide
page reaches for the same class rather than inventing its own cap.
2026-09-13 18:44:59 +02:00
atlas
053340128b term_msg: carry the source event's time on every row
A terminal row published on `$SWARM.term.<hive>.<agent>` goes out bare,
with no envelope around it and no server-side stamp, so a subscriber had
nothing to place the row in time with beyond its own receipt clock —
wrong by the queue's latency and meaningless for anything read later
than live.

`TermMsg` gains `ts`, ISO 8601 UTC. `classify` takes the event's own
unix-seconds stamp and applies it to every row that event expands into,
so a row replayed out of sqlite says when it happened rather than when
it was read, and a row that sat in a lagging subscriber's buffer does
not lie about its time. The oversize degrade keeps it; only the body is
ever spent.

`TermEnvelope` stops duplicating `ts` and keeps `seq`: the dedup counter
is a real transport concern, the event's time is not, now that it rides
on the row. Nothing in the frontend read `envelope.ts` — only the type
declared it.

Refs #4321
2026-09-13 14:23:11 +02:00
iris
8a4c613e4e swarm-ui: drop the redundant outer dialog card, move close into the panel header
Dialog and Panel both drew their own bordered/backgrounded card in the
same --bg-elev, so a Panel-based dialog (create-agent, link-matrix-account)
rendered as two concentric cards with a floating close button on the
outer one and no purpose to it.

Give Dialog a "plain" mode that drops its own card chrome (border,
background, padding) and floating close button, and give Panel an
optional onClose that renders a close button at the end of its own
header row instead. AgentsPage's two Panel-backed dialogs now use
plain + Panel's onClose, so the Panel is the dialog's only visible
card. ConfirmDialog (no Panel of its own) is unaffected — plain
defaults to false, unchanged card + floating close button.

Added a ComponentsPage sample demonstrating the plain + onClose
pairing. Verified both dialog modes via a real headless-chromium
screenshot (plain dialog: single card, close button in the header
bar; default dialog: unchanged floating close button).
2026-09-13 00:58:53 +02:00
iris
3c6c2ed272 ci: add .mjs to check-comment-blocks.sh + check-issue-refs.sh's scope
mara: "add mjs to the lint and fix it in this pr". Both lints scope their
git ls-files scan to a fixed extension list that never included .mjs, so
all three frontend build.mjs files (swarm-ui, dashboard, agent) were
completely invisible to both -- not merely under the limits, outside the
scan entirely.

check-comment-blocks.sh: added '*.mjs' to the ls-files glob and to the
mode_of regex (same slash-comment treatment .js/.ts/.tsx already get --
.mjs is the same comment syntax, argus's exact proposed fix). check-issue-refs.sh:
added '*.mjs' to its glob list, no regex change needed.

Fixing the scope immediately surfaced real, pre-existing violations in
the other two build.mjs files (swarm-ui's own is already handled by this
PR's earlier commits):
- dashboard/build.mjs: 49-line comment block (trimmed to 15 -- redundant
  per-file output-layout table cut, the code below already names every
  output path) plus three bare tracker tags (#448 x2, #453) in the
  stream-worker comment, reworded to prose
- agent/build.mjs: two bare tracker tags (hyperhive#3685 x2), reworded

No logic changes anywhere -- comment-only edits plus the two lint
scripts. Closes #4300 and (independently filed, consolidated into that
one) #4299.
2026-09-12 13:04:01 +02:00
iris
1cc4e4625e swarm-ui: trim build.mjs's top comment under the 30-line convention
mara flagged (PR #4297) that the comment-block lint doesn't catch this
file at all -- check-comment-blocks.sh (and check-issue-refs.sh) scope
to a fixed git ls-files extension list that omits .mjs. That's a real
gap (dashboard/build.mjs already has a 49-line block, pre-existing),
tracked separately -- but the fix here is to just respect the 30-line
convention regardless of whether the lint enforces it on this
extension. Cut the redundant per-file output-layout table (the code
below already names every output path) and kept only the genuinely
non-obvious decisions -- no content lost, verified against a plain awk
run of the lint's own line-counting logic.
2026-09-12 13:04:01 +02:00
iris
6b3840b6ac swarm-ui: content-hash main/theme/swarm-ui-css filenames for real cache-busting
Every redeploy currently changes the nix store path serving swarm-ui's
JS/CSS but never the URL the browser requested (main.js, main.css,
theme.css, swarm-ui.css are all fixed filenames) -- so a browser can get
stuck serving yesterday's bundle after a deploy until someone clears the
cache by hand.

build.mjs now hashes main.tsx's JS bundle + its companion CSS output,
plus theme.css and swarm-ui.css, via esbuild's own metafile (not a
guessed hash algorithm), and rewrites the real URLs into index.html and
sw.js after the build.

colors.css deliberately stays unhashed: nix/host-modules/swarm-ui.nix's
stylix theming swaps that exact fixed path via an `= /static/colors.css`
nginx location override with no npm/esbuild rebuild involved. Hashing it
would silently break that swap on a themed host.

sw.js's CACHE_VERSION is now derived from the real hashed URLs instead of
a hand-bumped literal, so any shell-asset change gets a fresh cache name
and the SW's own activate-time sweep evicts the previous one in full --
fulfilling a promise its own prior comment already made.

Scope: swarm-ui only, per mara's call on hyperhive#4283 ("this is about
swarm ui - everything else will eventually migrate over"). dashboard and
agent are untouched.
2026-09-12 13:04:01 +02:00
iris
1257fadd9b swarm-ui: run sw.js through nix fmt
The response.ok fix from the earlier review round was never reformatted
after editing. nix flake check has no --keep-going, so this one real
treefmt-check failure was aborting the whole check run -- and whatever
other derivation happened to be mid-build in the same parallel batch got
killed and logged with an empty error message, misread as an
infrastructure flake across many CI attempts. No logic change.
2026-09-12 11:30:20 +02:00
iris
1353bea7e1 swarm-ui: PWA follow-ups from review
- manifest.webmanifest: mara — "name should be hyperhive", was
  "hyperhive swarm".
- sw.js: argus — cache-write path didn't check response.ok before
  caching; an error response would get served back as if it were the
  real asset on the next offline/failed fetch.
2026-09-12 11:30:20 +02:00
iris
9ad700a1a1 swarm-ui: make it installable as a PWA
Closes #4282. mara: "scope looks good" — approving the plan posted
there (manifest + icons + minimal shell-only service worker + iOS meta
tags) and both explicit questions (network-first-with-offline-fallback,
never cache /api/*).

docs/web-ui/design-guide.md's "Layout & viewport" section already
asserted swarm-ui is installable as a PWA — this is what actually backs
it.

- manifest.webmanifest: name/icons/start_url/standalone display, theme
  #cba6f7 / background #1e1e2e matching the mocha --purple/--bg values.
- sw.js: plain JS, not TypeScript — the DOM lib swarm-ui's own tsconfig
  uses and the WebWorker lib a service worker's globals need are
  mutually exclusive in one tsc program, not worth a second tsconfig for
  a self-contained ~100-line file. Scoped to the app shell only, never
  touches /api/* at all, network-first with offline-fallback-to-cache
  (not cache-first) since main.js/main.css are unhashed filenames and a
  cache-first SW would risk wedging an operator on stale JS after a
  deploy.
- index.html: manifest link, theme-color meta, iOS
  apple-mobile-web-app-* tags (Safari ignores the manifest spec).
- main.tsx: feature-detected SW registration.
- branding/hyperhive-maskable.svg: hyperhive.svg's own artwork already
  fills nearly its whole canvas, so a maskable icon needs a padded
  variant or an OS mask crops the outer ring/corner brackets — embeds
  the original via a scaled <image> ref rather than duplicating markup.
- nix/packages/swarm-ui.nix: rasterizes icon-192/512/512-maskable.png
  from the branding SVGs at build time via librsvg, rather than
  checking in static PNGs.

Verified for real: typecheck+build green, and a real headless-chromium
tab driven over CDP confirms the service worker registers and becomes
the active controller, and a simulated-offline reload still serves the
full cached shell rather than a browser error page. nix build .#swarm-ui
also verified green, including the rasterized icon output.
2026-09-12 11:30:20 +02:00
damocles
b4dc09ff93 remove operator as target for scheduled prompts 2026-09-11 23:32:45 +02:00
iris
f263f82047 shared/badge: give interactive badges a real pill radius, not a roundrect
mara: "make clickable badges a proper pill not a roundrect" (#4276).

.ui-badge's base border-radius: 1em is a genuine pill at its own
compact display-only height, but .ui-badge-interactive's min-height:
2.75em (the WCAG 2.5.5 touch-target floor) makes an interactive badge
tall enough that the fixed 1em radius no longer reaches half the box's
height -- the corners round without the sides ever meeting the same
curve, i.e. a rounded rectangle. border-radius: 999px is the standard
stadium-shape trick (the browser clamps it to exactly half the box's
height whenever it exceeds it), same value .hive-pill/.hive-pill-sm
already use. Shared component -- fixes every interactive Badge caller
(swarm-ui's WantedMenu/AgentCard/model-effort pickers, the per-agent
page's StatusChips/MetaNav/HeaderPill) in one place.

Verified with a real headless-chromium screenshot of the /components
Badge section before/after: the interactive "model sonnet"/"pause"
badges were visibly rectangular with rounded corners before this,
matching the non-interactive display badges' pill shape after.
2026-09-11 22:45:36 +02:00
iris
1176de08bd swarm-ui/agents: move the split-out agent-page files into pages/agents/
mara, PR review: "make agentspage a subdir now that its split into
sub components". AgentsPage.tsx/.css, AgentCard.tsx/.css,
AgentTypes.ts, and WantedMenu.tsx move as a family into their own
pages/agents/ directory; CreateAgentForm and LinkMatrixAccountForm
stay in pages/ since they aren't part of this split (CreateAgentForm
is still rendered inside AgentsPage's own dialog but is a standalone,
independently-named form, not one of the pieces carved out of the
page itself).

Pure move: relative imports within the new pages/agents/ family are
unchanged (they were always siblings), only the ones reaching back
out to ui/ and the two forms above gained one more '../', plus
App.tsx's route import.
2026-09-11 22:35:44 +02:00
iris
601068f1eb swarm-ui/agents: split the page, extract FilterableView per mara's follow-up
Three more asks from the same review thread:

- "agentspage is now giant and deserves a split" - AgentsPage.tsx was
  1047 lines. Split into AgentTypes.ts (AgentRow and friends),
  WantedMenu.tsx, AgentCard.tsx (+ its own CSS), leaving AgentsPage.tsx
  as state/actions/columns/the render tree - 649 lines, and every piece
  it composes is now independently readable.

- "what about the component that represents filtered data ... that the
  card view and table can both use?" - extracted FilterableView
  (ui/filterable-view/): takes columns/rows/rowKey/storageKey/view/
  renderCard, builds its filter bar from *every* filterable column (not
  a hand-picked subset - the old AgentFilterBar only showed 4 of the
  agent columns' 6 filterable fields, an accidental gap the table's own
  popovers didn't have), and renders either the card list or Table.
  AgentsPage now just tells it which view to show; the view toggle
  itself stays page-side since it's Panel-header chrome, not filtering.
  Disclosed side effect: card view's filter bar now also covers
  message/config-PR (text filters), matching table view exactly instead
  of a narrower subset.

- CSS audit: AgentsPage.css now holds only what's genuinely page-specific
  (the view toggle, the detail-panel field grid) - everything else moved
  to its owning component's own colocated CSS.

FilterableView gets a /components demo (view toggle + filter bar + both
render modes, same day per the design guide). Verified: AgentsPage
still renders the same (real screenshot), and the demo's own table
toggle produces a real Table with the same rows.
2026-09-11 22:30:41 +02:00
iris
3b66f0c8d6 swarm-ui/agents: fix detail panel going stale after refresh or an action
argus, PR review: detailTarget stored the whole AgentRow object at
selection time, so it never picked up a later refresh() or a
declareState patch - the card list updated live, the panel next to it
kept showing whatever was true the moment it was opened. Concrete
repro: open an agent's detail panel, wait for the next refresh or
destroy it from inside the panel itself, watch the panel not update.

Fix: store only the selected agent's name (detailTargetName) and
re-derive the actual row from rows on every render
(rows?.find(r => r.name === detailTargetName)). The panel can't drift
from what the list is showing since it's reading the same array.

Verified with a real refresh cycle against a mock server that returns
different data on the second call: before, both card and panel show
"idle"; after a live refresh, both show the new value in sync.
2026-09-11 22:30:41 +02:00
iris
7003560569 swarm-ui: extract Card/MultiselectFilter/SplitView per component-first design
Mara: "result looks like the shape i am looking for, but the code does
not. you did not follow component first principle" - the card's
clickable/selectable mechanics, the card-view filter trigger+popover,
and the list+detail split layout were all one-off page-local JSX in
AgentsPage.tsx instead of docs/web-ui/design-guide.md's "Component-first
design" primitives. Three new ui/ components, each with a same-day
/components demo section per that doc's own rule:

- ui/card/Card.tsx - the role=button/keyboard-activation/selected
  mechanics AgentCard now wraps agent-specific content around, instead
  of owning them itself.
- ui/multiselect-filter/MultiselectFilter.tsx - the checkbox-list
  trigger+popover control. This was also a straight duplicate of
  Table's own inline popover content once AgentsPage's filter toolbar
  needed the identical thing; Table now renders the same
  MultiselectFilterOptions piece too (keeping its own th-anchored
  trigger and fixed+portal positioning, which are genuinely
  table-specific), not a second copy.
- ui/split-view/SplitView.tsx - the list+detail flex-wrap layout, no
  opinion on what's inside either pane.

AgentsPage.tsx's own CSS shrinks to just the agent-specific content
inside these primitives (card line/message layout, detail-panel field
grid, the name-search input) - the container/positioning rules moved
to each component's own colocated CSS.

No behavior change for any other Table caller (HivesPage,
IssueReportPage, the components demo's own Table samples) - the
popover's visual output is identical, just sourced from the shared
component instead of inline JSX.

Verified: typecheck/build clean, real screenshots of both AgentsPage
(pixel-identical to before) and the three new /components sections,
plus a live click confirming MultiselectFilter's popover opens
correctly on the demo page too.
2026-09-11 22:30:41 +02:00
iris
60d7e8ce84 swarm-ui/agents: shared filters across views, real second panel for detail
Two more asks from mara's live review:

- "i want the same filters for the cards tho, thats why i suggested
  separating data and filter from view" - extracted Table's filter
  *state* (not its popover UI, which stays table-shaped) into a new
  exported useTableFilters hook. Table calls it internally, unchanged
  behavior for every existing caller. AgentsPage now calls the same hook
  with the same storageKey, so card view and table view read/write one
  shared filter state instead of each having their own (or cards having
  none at all). Card view gets its own toolbar (AgentFilterBar) - a
  name search input plus one FilterMultiselect per multiselect column,
  same checkbox-list markup Table's own popover uses, driving the same
  state. Switching the view toggle no longer loses or hides whatever's
  filtered.

- "why no separate panel? i mean a second panel on agent page" - replaced
  the modal Dialog with a real second Panel, always mounted (empty state
  when nothing's selected, so selecting an agent never shifts the page's
  own layout). Panel gained an optional `class` prop so the two panels
  can flex-size themselves in a row. List/detail panels sit side by side
  in a flex-wrap row that stacks on a narrow viewport - content-driven,
  same approach the shell's own nav uses, not a second hardcoded
  breakpoint. Selected card gets a highlight so it's clear which one the
  detail panel is showing.

Verified with real CDP clicks: split layout with nothing selected,
selecting a card highlights it and populates the detail panel, opening
a card-view filter and checking a value narrows both the card list AND
(after switching the toggle) the table to the identical row set.
2026-09-11 22:30:41 +02:00