First chunk of #589 v0 phase A: plumbing the hive-qualified
'name@hive' form through the per-agent surfaces that the harness
itself owns. Broker from/to + dashboard rendering + container_view
follow in subsequent PRs once damocles ships the HYPERHIVE_HIVE_DOMAIN
env var in harness-base.nix.
- new hive_ag3nt::identity module: label() / hive_domain() /
qualified_label() / qualify(label). Reads HYPERHIVE_HIVE_DOMAIN
(set by hive-c0re.nix module from hyperhive.domain) — when unset
or empty, qualified_label degrades to just the short label so
existing single-hive deployments are unchanged. Six unit tests
cover the set / unset / empty / arbitrary-label paths.
- prompt::render gains {qualified_label} substitution alongside
the existing {label}. system.md template uses both: the agent
intro now reads 'You are hyperhive agent iris (qualified:
iris@darkest.space) in a multi-agent system. ... When you're
talking to or about a peer on a different hive, use the
qualified form (name@hive) so the operator + the manager can
disambiguate'. Manager flavor gets the same treatment.
- /api/state gains qualified_label: String. Always present, equals
label when no domain is configured.
- frontend setHeader takes the qualified_label, drives the browser
tab title (so two tabs from different hives are
distinguishable in the tab bar) while the glyphic #title stays
short for the cinematic header.
Gated on env var presence — no behaviour change for single-hive
deployments. Pairs with damocles's upcoming harness-base.nix
HYPERHIVE_HIVE_DOMAIN ship; safe to land in either order.
Cuts every `include_bytes!`/`include_str!` of a non-rust path in
the workspace over to runtime file loads from `$HIVE_ASSETS_DIR`
(the `hyperhive-assets` derivation introduced in the previous
commit). After this commit the rust derivation has no compile-time
dependency on `branding/*` or `hive-ag3nt/prompts/*` anymore.
Call-site flips:
- `hive-c0re/src/forge.rs::CORE_AVATAR_PNG` /
`CONFIG_ORG_AVATAR_PNG`: were `include_bytes!` of
`branding/hyperhive.png` and `$OUT_DIR/agent-configs.png`. Now
`ensure_core_avatar` / `ensure_config_org_avatar` `tokio::fs::read`
via `hive_sh4re::assets::{core_avatar_png, config_org_avatar_png}`
at startup. The `agent-configs.png` is now rendered by the
`hyperhive-assets` derivation's rsvg-convert step (was
`hive-c0re/build.rs` + librsvg on the rust derivation's
nativeBuildInputs — both gone in the next commit).
- `hive-ag3nt/src/prompt.rs::TEMPLATE`: `render` now takes the
template as an argument; `write_system_prompt` reads it once from
`$HIVE_ASSETS_DIR/prompts/system.md` before calling render. The
test module still `include_str!`s the production template so
`cargo test --workspace` doesn't need `HIVE_ASSETS_DIR` set —
this is the only remaining compile-time reference to the file
from the rust workspace, gated to `#[cfg(test)]`.
- `hive-ag3nt/src/turn.rs::CLAUDE_SETTINGS`: was `include_str!`'d
and written via `tokio::fs::write`; now `tokio::fs::copy` from
`$HIVE_ASSETS_DIR/prompts/claude-settings.json` into the
per-agent socket dir.
- `hive-ag3nt/src/web_ui.rs::DEFAULT_ICON`: was `include_str!`'d;
now read on-demand from `$HIVE_ASSETS_DIR/branding/hyperhive.svg`
inside `serve_icon`. Falls back to an empty body if missing so
the endpoint never panics on a misconfigured container (matches
the existing "per-agent icon.svg override" fallthrough).
`HIVE_ASSETS_DIR` wiring:
- Inside containers: `nix/templates/harness-base.nix`
`environment.variables` sets it to
`${pkgs.hyperhive-assets}/share/hyperhive` (resolved through
the default overlay applied in `mkContainer`). Verified by
building `agent-base-toplevel` and grepping the resulting
`/etc/set-environment`.
- Host-side: `nix/modules/hive-c0re.nix` adds an `assets` option
defaulting to `hyperhive.packages.${system}.assets`, threaded
in from the flake's nixosModules wiring, and sets the same env
var on the `hive-c0re` systemd unit so the daemon's
`forge::ensure_*_avatar` startup hooks find the PNGs.
`hive-c0re/build.rs` deleted entirely; `[package].build` removed
from `hive-c0re/Cargo.toml`; rsvg-convert dependency lives in the
assets derivation only.
Validated: `nix build .#default .#checks.x86_64-linux.clippy
.#agent-base-toplevel .#manager-toplevel --fallback` all succeed.
`/etc/set-environment` in the toplevel shows
`HIVE_ASSETS_DIR="/nix/store/.../hyperhive-assets-0.1.0/share/hyperhive"`.
Phase 4 of #273 — the actual switch. Both axum routers now serve
their static surface via `tower_http::services::ServeDir` mounted
as a fallback service, reading the dist path from `HIVE_STATIC_DIR`
(set by Phase 3's NixOS module wiring).
Deletes:
- `hive-c0re/assets/{index.html, app.js, dashboard.css}`
- `hive-ag3nt/assets/{index.html, app.js, agent.css, stats.html,
stats.js, screen.html}`
- The whole `hive-fr0nt/` crate (workspace member dropped, both
hive-c0re and hive-ag3nt drop their `hive-fr0nt.workspace = true`
dep). Its contents now live as `@hive/shared` under
`frontend/packages/shared/`.
Rust changes:
- `hive-c0re/src/dashboard.rs`: remove `serve_index`, `serve_css`,
`serve_app_js`, `serve_shared_js`, `serve_marked_js`,
`serve_favicon` (all six `include_str!` handlers); replace their
routes with a single `.fallback_service(ServeDir::new(static_dir))`
on the router. Fail closed (anyhow::bail) if `HIVE_STATIC_DIR` is
unset or not a directory at startup.
- `hive-ag3nt/src/web_ui.rs`: remove `serve_index`, `serve_css`,
`serve_app_js`, `serve_shared_js`, `serve_marked_js`,
`serve_stats`, `serve_stats_js`, `serve_screen`; same
`fallback_service` pattern. `serve_icon` stays (consumes
`/etc/hyperhive/icon.svg` + `branding/hyperhive.svg` fallback,
neither of which lives under the frontend dist).
- `AgentLink` URLs for stats/screen switched from `/stats` / `/screen`
to `/stats.html` / `/screen.html` since ServeDir doesn't auto-
append the extension and the on-disk filename is the natural URL
post-cutover.
- `Cargo.toml` (workspace): drop `hive-fr0nt` member + workspace
dep, add `tower-http = { version = "0.6", features = ["fs"] }`.
- `hive-c0re/Cargo.toml` + `hive-ag3nt/Cargo.toml`: drop the
`hive-fr0nt.workspace = true` dep, add `tower-http.workspace =
true`.
Docs updated:
- `CLAUDE.md`: file map reflects `frontend/` (was `hive-fr0nt/` +
`assets/`) and the ServeDir/HIVE_STATIC_DIR shape.
- `docs/web-ui.md` 'Shape (shared by both)' section: describes the
ServeDir fallback + bundled-by-esbuild surface, no more
`include_str!` references.
- `docs/terminal-rendering.md`: src paths point at
`frontend/packages/{agent,shared}/src/`; marked is the npm dep,
not vendored UMD.
Validation:
- `cargo check --workspace` — clean (5 warnings, all pre-existing
in `rebuild_queue.rs`, none on changed files).
- `cargo clippy --workspace --all-targets` — clean (11 warnings,
same pre-existing source).
- `cd frontend && npm run build` from the prior commit's lockfile
produces the dist directories the new routers consume:
dashboard: `dist/{index.html, static/{app.js, dashboard.css}}`
agent: `dist/{index.html, stats.html, screen.html,
static/{app.js, stats.js, agent.css}}`
(favicon.svg lands in dashboard/ during the nix build —
`nix/frontend.nix` install phase copies `branding/hyperhive.svg`
there, since it's outside the npm tree.)
Refs #273.
The retry was capped at 12 attempts (~20s of exponential backoff
capped at 2s). Two back-to-back nspawn restarts in #324 left the
previous socket holding the port longer than that budget; once the
cap fired, the web-UI task returned an error and silently died for
the rest of the process lifetime — the agent kept running fine
otherwise (MCP, turn loop), but the operator's dashboard click
hit nothing.
Genuine port collisions are preflighted host-side
(lifecycle::{spawn,rebuild}) and surfaced as a port-conflict banner,
so at this layer a persistent AddrInUse always reflects a
recoverable stale socket. Drop the cap, keep retrying forever with
the same 2s-capped backoff. WARN for the first dozen attempts so a
normal restart-race is visible; INFO after that to avoid spamming
the journal during a long stale-socket hold. Logs a one-line INFO
on success when we did have to retry, so post-mortems can find the
attempt count.
Closes#324.
The previous take put a shared NavLink wire type in hive-sh4re and
duplicated the link-building logic across crates. Per @mara on #326:
that doesn't fit the eventual frontend/backend split goal (#273).
The agent backend is the natural source of truth for what links its
own page exposes; hive-c0re just passes the list through to the
dashboard.
* hive-ag3nt/src/web_ui.rs: agent_links now also serves the
config-repo link + reads agent-declared dashboardLinks extras
from {state_dir}/hyperhive-dashboard-links.json. AgentLink gains a
kind enum (Container | Forge | External) so the frontend can build
the right href no matter which surface is rendering. The host
header is no longer used — URLs are paths for Container/Forge,
absolute for External.
* hive-c0re/src/dashboard.rs: new GET /api/agent/{name}/links route,
a same-origin proxy that fetches the agent's /api/state and
forwards just the links field. No shared wire type — hive-c0re
treats the payload as opaque JSON (serde_json::Value). All failure
modes degrade to an empty list so the dashboard still renders.
* hive-c0re/assets/app.js: container card head row gets an async-
populated icon-only nav strip from the proxy. The hardcoded stats
link, the standalone config-repo trigger, and the extras block are
gone. The deployed:<sha> chip stays — the agent harness can't know
its own deployed sha, so this chip is how the operator sees what's
live alongside the agent's (root-only) config link.
* hive-ag3nt/assets/app.js: agent page meta-links rendered via
el() / textContent (DOM build) so agent-declared icon / label / url
strings never reach innerHTML. kind-based href resolution mirrors
the dashboard side.
* docs/web-ui.md: dashboard + per-agent sections updated for the new
architecture.
Closes#262.
the stream-json result event carries modelUsage.<model>.contextWindow
which is the actual per-inference active window the model enforces.
for claude-sonnet-4-6 this is 200k even though the full prompt cache
can hold millions of tokens via accumulated cache reads.
with the nix-configured sonnet = 1000000 the proactive compact watermark
sat at 750k and was never reached. agents grew context until prompt_too_long
at ~170k — reactive compact, no checkpoint turn.
changes:
- bus gains api_context_window field seeded from modelUsage.*.contextWindow
in each turn's result event. authoritative; falls back to env var, then 200k.
- new effective_context_window(bus) helper used by both watermark functions
- compact_watermark (75%) and auto_reset_watermark (50%) call effective_context_window
- context_tokens() docstring clarified: all three token fields (input +
cache_read + cache_creation) count against the per-inference contextWindow
limit. the large cache_read values seen in the result event are cumulative
across all inferences in a turn, not per-inference.
- /api/state context_window_tokens now reflects the calibrated window
closes#129
Per review: build the full forge profile URL in the harness instead
of the client. /api/state now returns forge_url: Option<String>
(assembled from the request Host header — resolves against whatever
host the operator reached the page on), replacing the forge_present
bool. The JS just links forge_url when present — no client-side URL
construction.
Add a '⬡ forge ↗' link to the per-agent page's meta row, next to
the stats + screen links. It opens the agent's Forgejo profile
(http://<host>:3000/<label> — the per-agent forge user is named
after the agent) in a new tab.
- web_ui.rs: StateSnapshot gains forge_present, true when the
agent's forge-token file exists in the state dir (same signal
that tells the agent it has a forge account).
- index.html / app.js: hidden link, shown + href-filled when
forge_present, mirroring the existing gui_enabled/screen-link
pattern. Host comes from window.location so it works off
whatever host the page is served from.
closes#185
Foundation for the per-agent icon feature (#137).
- harness-base.nix: new hyperhive.icon option (nullable path to an
SVG). An agent commits an SVG into its config repo and references
it as ./icon.svg; when set it lands at /etc/hyperhive/icon.svg.
- web_ui.rs: GET /icon serves the configured SVG, falling back to the
bundled hyperhive logo when none is set — so it always returns an
image and consumers can hit it unconditionally.
Closes#139
CountPendingReminders and ReminderRollup were hardcoded to
MANAGER_AGENT. Both now take agent: Option<String> — None keeps the
current behavior (manager's own), Some(name) returns that agent's
reminder stats. The broker functions already take an agent name, so
this is a thin wire-protocol change. Callers (web UI stats page,
post-turn counts) pass None.
Closes#122
GetLooseEnds now takes agent: Option<String>:
- None = manager's own loose ends (default; the bug fix)
- Some("*") = hive-wide view (every approval/question/reminder)
- Some("name") = that agent's loose ends
The get_loose_ends MCP tool exposes this as an optional agent arg, so
the manager can still scan the whole swarm on demand. The web UI and
post-turn counts pass None (manager's own).
Adds Hour (5-min buckets), FourHour (15-min buckets), and ThreeDay
(hourly buckets) to the Window enum, plus the matching tab buttons
in stats.html. Simplifies web_ui.rs to use Window::span_secs()
instead of a duplicate match.
Closes#25
- add rate_limited: Arc<AtomicBool> to Bus; set/cleared by emit_status
- write/remove sentinel file hyperhive-rate-limited in state dir so host-side
dashboard can detect it without a live socket call
- api_state returns status=rate_limited when flag is set (cold-load accurate)
- ALIVE_LABELS gains rate_limited entry (⊘ red chip) on per-agent page
- ContainerView gains rate_limited: bool read from sentinel file
- dashboard container row shows ⊘ rate limited badge (red) ahead of needs_login
Closes#24
Reads /etc/hyperhive/gui.json at startup to get the VNC port written
by the weston-vnc ExecStart script (issue #50). Adds:
- gui_vnc_port: Option<u16> on AppState
- gui_enabled: bool on StateSnapshot (for issue #52 screen link)
- GET /screen: serves a minimal RFB-over-WebSocket viewer (screen.html)
- GET /screen/ws: upgrades to WebSocket and byte-pumps to 127.0.0.1:<vnc_port>
The relay is a pure two-task byte pump (WS→TCP and TCP→WS), transparent
to any RFB variant including VeNCrypt. Returns 404 when gui is not
enabled.
screen.html is a self-contained RFB client: handshake, FramebufferUpdate
(Raw encoding), pointer and keyboard forwarding — enough to display the
desktop and interact with it. noVNC assets (issue #52) replace this.
Closes#51
Add fetch_reminder_stats() helper to query ReminderRollup from broker,
and update api_stats endpoint to include reminder stats in snapshot.
Reminder activity metrics (scheduled, delivered, pending) are now
available to the stats page UI for display.
new hive-ag3nt::stats module reads turn_stats.sqlite read-only and
aggregates over 24h/7d/30d windows (hourly/daily buckets) — turn
rate, p50/p95/avg duration, ctx tokens (avg/max), cost token
components, top tools, wake mix, result mix. served by the agent
itself so per-MCP extensions can register more providers without
the host knowing their schemas.
/stats route + /api/stats?window=... on the per-agent web ui.
chart.js v4.4.4 pulled from jsdelivr (SRI hash deferred). nav
links: 📊 chip on the dashboard container row + 📊 stats → on
the per-agent header.
todo housekeeping: softened damocles-area note at the top,
new reverse-proxy + deferred reminder-rollup items, removed
the two telemetry-ui items absorbed by this page.
the existing ctx badge was misnamed: it summed `result.usage`, which is
the cumulative tokens billed across every inference in the turn. for
tool-heavy turns that easily exceeds the model's context window (a 600k
cached prefix × 15 sub-calls = 9M cache_read), making it useless as a
"should i compact?" signal.
now two separate badges:
ctx · N last inference's prompt size = actual context window in
use right now. parsed from each `assistant` event's
`.message.usage`; the harness tracks the most recent one
across the stream and snapshots it when the `result`
event lands.
cost · M cumulative tokens billed across the whole turn (the
previous behaviour, now correctly labelled).
both update via a single `TokenUsageChanged { ctx, cost }` SSE event at
turn-end. turn_stats grows four columns (`last_input_tokens`,
`last_output_tokens`, `last_cache_read_input_tokens`,
`last_cache_creation_input_tokens`) so the cold-load seed can paint both
badges on page load. migrations run try-and-ignore ALTERs so existing
agent dbs catch up; pre-migration rows have last-inference zeros and
yield no `ctx` seed (badge stays empty until next turn) rather than a
misleading 0.
layout
- unified prefix-column for every row kind: padding-left + negative
text-indent so the glyph (→ ← · ◆ ✓ ✗ ⌁ !) sits in the same column
whether the row is flat or a <details>. wraps hang under the body,
not under the glyph.
- expandable rows drop the directional glyph from their summary text;
the ▸/▾ disclosure marker from CSS sits in the prefix column instead,
and the row's colour still carries cyan = outbound, muted = inbound.
- turn-start / turn-end de-weighted: bold/margin/tint dropped, the
coloured left rule alone marks the boundary.
note classification
- stderr lines render orange with a `!` glyph (was muted `·`)
- operator-initiated notes (cancel/compact/model/new-session) render
mauve italic (was muted `·` indistinguishable from harness chatter)
- catch-all .sys row escalates to orange `!` so unrecognised stream-json
shapes surface for follow-up instead of hiding in muted noise
message-bearing rows
- send / ask / answer tool_use rich renderers default-open with the
body inline; new ask + answer renderers (previously fell through to
the generic JSON dump). recv tool_result also default-open, keyed by
tracking tool_use_id → name across the stream so we know which
result came from which tool.
- assistant text rows render markdown.
- bodies use vendored marked v4.0.2 (hive-fr0nt::MARKED_JS); falls
back to plain text when the asset doesn't load.
extra-mcp tool pretty-print
- generic args formatter replaces the raw JSON dump for unknown tools
(single-string field → `name k: "v"`; single dict / multi-field →
trimmed `k: v · k: v …` summary)
dashboard .live .msgrow gets a text-indent: 0 reset so the new
hanging-indent metrics from TERMINAL_CSS don't leak into the flex-grid
broker rows.
new /api/open-threads endpoint on hive-ag3nt proxies the agent's
own GetOpenThreads RPC (manager flavour proxies the hive-wide
ManagerRequest::GetOpenThreads). same data the
mcp__hyperhive__get_open_threads tool sees from inside claude.
frontend renders a collapsible <details> section above the
terminal, listing each pending row (approval / question) with
asker → target, age, and free-form body. auto-expands on the
first appearance of any open thread; sticky after that.
refreshed on cold load + after every turn_end (turns are when
threads land or resolve).
new LiveEvent variants on the per-agent bus —
status_changed / model_changed / token_usage_changed /
turn_state_changed — replace the per-agent web UI's
/api/state polling for the badge row.
emit sites:
- Bus::set_model → model_changed
- Bus::record_usage → token_usage_changed
- Bus::set_state → turn_state_changed
- turn::wait_for_login → status_changed("online") on creds detect
- post_login_start / post_login_cancel → status_changed("needs_login_*")
per-agent endpoints (post_set_model / post_compact / post_new_session
/ post_cancel_turn / post_login_*) now all return 200; client
drops the post-submit refetch except on login transitions, which
still need /api/state to render the OAuth form + session stream.
client adds dispatch on the four new event kinds, threads
`currentLabel` through so the composer re-enables on a live
status flip, and no longer fires refreshState() from turn_end or
postModel — the events carry the same signal faster.
closes the per-agent half of the dashboard event-channel
refactor; TODO entry dropped.
new TurnFiles bundle (mcp_config + settings + system_prompt +
flavor) materialised once per harness boot, passed to drive_turn
and compact_session alike. operator-initiated /compact now uses
the exact same session shape as a normal turn — same MCP
surface, same allowed tools, same role prompt — only the stdin
payload differs (/compact vs the wake-up body). web_ui's
AppState carries the TurnFiles instead of (label + socket +
flavor + ad-hoc file writes per click). bin/hive-ag3nt and
bin/hive-m1nd prepare TurnFiles before spawning the web UI and
pass them to both surfaces. web_ui::Flavor folds into a type
alias for mcp::Flavor — no two-stage enum mapping.
removes ClaudeMode + the run_claude variant fork (system prompt
was Option, mcp args were skipped on Compact). dead 'mode'
plumbing gone.
bus carries a one-shot AtomicBool armed by POST
/api/new-session (or the /new-session slash command). next
turn drops --continue, starting a fresh claude session; the
flag clears automatically so subsequent turns resume normal
behavior. /compact still always uses --continue — compacting
a non-existent session is a no-op anyway.
per-agent page grows an ↻ new session button next to the
cancel-turn one (always visible, amber, confirms before
posting since dropping --continue context isn't reversible).
slash-command surface picks up /new-session for parity with
the button. note row emitted on the live feed both at arm-
time and again when the turn actually consumes the flag, so
the operator can confirm it landed.
- runtime model override: Bus::{model,set_model} + POST /api/model
(form-encoded {model: name}). turn.rs reads bus.model() per turn
so a flip lands on the next claude invocation. /api/state grows
a model field; agent page shows a 'model · <name>' chip in the
state row. '/model <name>' slash command POSTs to the endpoint
and refreshes state.
- port regression fix: agent_web_port no longer probes forward for
*existing* agents (the previous fix shifted ports for any agent
without a port file, including legacy ones whose container was
already bound to the bare hashed port — dashboard rendered the
new port, container was still on the old one, conn errors). new
rule: port file exists → use it; absent + applied flake present
→ legacy, persist port_hash without probing; absent + no applied
flake → fresh spawn, probe forward.
- SO_REUSEADDR on both the dashboard and per-agent web UI binds
via tokio::net::TcpSocket. operator hit 12 retries failing on
manager :8000 — REUSEADDR handles the TIME_WAIT case cleanly
without a new dep; retry still covers the genuine
process-still-alive overlap.
todo: drops the model-override entry (shipped); adds two new
items — model persistence (optional, future), and custom
per-agent MCP tools (groundwork for moving bitburner-agent into
hyperhive).
new TurnState { Idle, Thinking, Compacting } on hive_ag3nt::events::Bus
with set_state + state_snapshot. the turn loops in hive-ag3nt and
hive-m1nd flip Thinking before drive_turn and Idle after; the
web_ui's /api/compact handler flips Compacting around compact_session.
per-agent /api/state grows turn_state + turn_state_since (unix
seconds). frontend prefers the server-reported state over the
client-derived one — setStateAbs takes the absolute since-time so
the 'last turn' chip reads the actual server-side duration instead
of the client's perceived gap between SSE events. SSE turn_start /
turn_end still drive state instantly between renders; /api/state
re-anchors on each turn_end refresh.
new compacting state gets its own purple badge with pulse
animation (mirrors thinking's amber). napping will slot in the
same way once the nap tool lands.
operator hit 'Address already in use (os error 98)' on a harness
restart — the new harness raced the old socket's release. add a
bind_with_retry helper that backs off (250ms doubling, capped at
2s, 12 tries ≈ 22s total) on AddrInUse before giving up. applied
to both the per-agent web UI and the hive-c0re dashboard.
proper fix would be SO_REUSEADDR via socket2 but retry covers the
TIME_WAIT case fine and keeps the dep count down. Other bind errors
still fail immediately (port permission, fd exhaustion).
new wire request AgentRequest::Recent { limit } / ManagerRequest::Recent
(plus matching responses with Vec<InboxRow>). InboxRow moved to
hive-sh4re so it lives on both surfaces without an internal-to-wire
conversion. host-side dispatch in agent_server / manager_server
calls broker.recent_for(name, limit).
per-agent web_ui /api/state grew an inbox: Vec<InboxRow> populated
via the same per-agent socket (best-effort; transport failure
returns empty). frontend renders as a collapsible <details> section
between the state row and the terminal — fmt timestamp / from /
body in a tight grid, capped at 16em scrollable. only visible when
there are rows.
new POST /api/compact on the per-agent web UI: spawns
turn::compact_session in the background so the http handler returns
immediately. claude runs '/compact' over the persistent --continue
session; output streams into the live panel like any other turn.
slash command /compact wired to the new endpoint. SLASH_COMMANDS list
now lists all four (/help /clear /cancel /compact). postCancelTurn +
postCompact share a postSimple() helper.
deliberately not gated against an in-flight turn — claude's own
session lock will reject a concurrent compact and the failure
surfaces as a Note in the live panel.
new POST /api/cancel on the per-agent web UI: shells out
pkill -INT claude (procps added to harness-base.nix). emits a Note
on the bus so the operator sees the cancel landed; state goes back
to idle when run_claude wakes and emits TurnEnd as usual.
frontend:
- /cancel slash command in the terminal input
- ■ cancel turn button in the state row, visible only while
state === 'thinking' (driven from the same SSE-based state
machine). disabled briefly during the POST.
claude gets SIGINT (not TERM) so it flushes anything in-flight and
emits a final result row before exiting.
agent page restructure:
- send form moves into the terminal panel as a prompt-style row beneath
the live tail (status line stays above so it still reads as a header).
- live panel + prompt share a single bordered 'terminal-wrap' box.
- harness-alive / login-state status lines drop their decorative ascii
bookends; just a leading dot/glyph remains.
- banner gradient is now a real css gradient with a shimmer animation
toggled by an .active class. turn_start adds it, turn_end removes it.
dashboard side mirrors this: each broker sse event nudges a 4s
shimmer window.
- dashboard container rows drop their static ▓█▓▒░ / ▒░▒░░ glyph
prefixes; the role chips already disambiguate m1nd vs ag3nt.
- empty-state placeholders drop the ▓ bookends.
terminal pre-fill: hive-ag3nt::events::Bus grows a 500-event ring
buffer; new GET /events/history endpoint returns it. The agent JS
fetches history before opening the SSE stream so opening the page mid-
turn shows the last N events instead of a blank panel. The replay
walks turn_start/turn_end pairs to seed the banner-active state
correctly if a turn was still open.