diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 20f71eca..9ea39dec 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -8,12 +8,6 @@ jobs: check: name: nix flake check runs-on: [hive-ci] - # Bound the job so a wedged build fails in minutes instead of - # hanging until the runner's 3h cap (or, when the runner itself - # deadlocks, never). 30 min is well above a cold-cache rebuild - # (~15 min observed) and well under the 3h hard cap — tune if a - # legit cold build ever trips it. - timeout-minutes: 30 steps: - uses: actions/checkout@v3 - name: check @@ -21,19 +15,3 @@ jobs: # cargo clippy, and module evaluation. No --no-build: the checks # derivations are the canonical source of truth. run: nix flake check - - tracker-tags: - name: tracker-tag lint - runs-on: [hive-ci] - # Pure git+grep — seconds normally; a few minutes is already a hang. - timeout-minutes: 5 - steps: - - uses: actions/checkout@v3 - - name: lint - # Flags hash-number tracker tags in source (hive convention is - # prose, not tags — /knowledge/hive-rules.md). Runs as its own - # job, kept out of the required checks while the legacy backlog - # is cleaned up: a hit fails this check (red) without blocking - # merge. Promote to a required check once the tree is clean. - # See scripts/check-issue-refs.sh. - run: sh scripts/check-issue-refs.sh diff --git a/docs/turn-loop.md b/docs/turn-loop.md index f8ece2d3..42524014 100644 --- a/docs/turn-loop.md +++ b/docs/turn-loop.md @@ -82,7 +82,7 @@ zero-sized impl (`AgentSurface`) wrapping: - One async method per wire op: `ack_turn`, `requeue_inflight`, `inbox_unread`, `post_turn_counts`, `send_to_parent`, - `recv_next`, `wake_external`. + `self_wake`, `recv_next`, `wake_external`. `main()` calls `serve_main::` for all roles. The turn loop (`serve_loop` / `handle_turn` / `wake`) has no per-role branches. @@ -117,19 +117,9 @@ and the manager fall through to operator). After the outcome handler, the stats sink records a row and the `hyperhive-continue` sentinel (dropped by the `request_next_turn` -MCP tool) is consumed if present. `handle_turn` reports the result -to `serve_loop` via `TurnControl { auth_failed, continue_requested, -pending }`. When a continue was requested, the turn did not -auth-fail, and the inbox is empty (`pending == 0`), `serve_loop` -drives the next turn in-process with a synthetic -`{ from: "self", body: "continue" }` message (`synthetic_continue`) -— it never goes through the broker, so the self-continue doesn't -persist to sqlite or show up as a recv'able inbox message. If real -messages are already pending the continue is dropped: those messages -drive the next turn(s) via `recv_next`, so an explicit self-wake -isn't needed (this is the `request_next_turn` contract — "no effect -if a new inbox message arrives before this turn ends"). The -`should_self_continue` predicate encodes exactly that decision. +MCP tool) is consumed if present, firing `self_wake` so the next +turn starts with `{ from: "self", body: "continue" }` even if the +inbox is empty. ## The claude invocation diff --git a/flake.nix b/flake.nix index 4b39a3fd..51048152 100644 --- a/flake.nix +++ b/flake.nix @@ -4,7 +4,7 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - # Crane (replaces the former naersk-based build). Stateless — no nixpkgs input to + # Crane (replaces naersk #538). Stateless — no nixpkgs input to # follow; `crane.mkLib pkgs` returns the lib at whatever pkgs we # pass it (we use the project's pinned nixpkgs). crane.url = "github:ipetkov/crane"; @@ -51,7 +51,7 @@ # cares about" filter (Cargo.toml/Cargo.lock + *.rs). All # non-rust runtime assets — branding + the claude system # prompt template + claude-settings.json — live in the - # separate `hyperhive-assets` derivation and are + # separate `hyperhive-assets` derivation (#555) and are # loaded by the binaries at runtime from `$HIVE_ASSETS_DIR`. # The unit tests in `hive-ag3nt::prompt` read the same # `prompts/system.md` directly from the workspace tree at @@ -91,10 +91,10 @@ # sandbox. # `librsvg` used to live here for `hive-c0re/build.rs`'s # rsvg-convert call — that whole codepath moved into the - # `hyperhive-assets` derivation, so the rust + # `hyperhive-assets` derivation in #555, so the rust # derivation no longer needs the dependency. # `sqlite` required by matrix-sdk's `sqlite` feature - # (`hive-matrix-mcp` workspace member) — the + # (`hive-matrix-mcp` workspace member, #548 phase 3) — the # matrix-sdk-sqlite + rusqlite stack links against system # libsqlite3 by default. nativeBuildInputs = [ @@ -129,7 +129,7 @@ # prompt::tests` reads the production prompt template at test # runtime through `$HIVE_ASSETS_DIR`, so wiring the env var # into the build phase here would make the prompt's hash a - # build input of `default` (defeats the asset-split cache goal: a + # build input of `default` (defeats #555's cache goal: a # prompt edit would still bust the binary derivation, even # though no .rs file changed). Keeping tests in a separate # check derivation localises the asset-rebuild blast radius @@ -144,12 +144,12 @@ }; # Bundled browser assets — see ./nix/frontend.nix. Output is # $out/{dashboard,agent}/ which the Rust binaries serve via - # tower_http::ServeDir. + # tower_http::ServeDir (wired up in Phase 4 of #273). frontend = pkgs.callPackage ./nix/frontend.nix { branding-svg = ./branding/hyperhive.svg; }; # Static runtime assets the rust binaries read via - # `hive_sh4re::assets::*`: branding/* + prompts/*, + # `hive_sh4re::assets::*` (#555): branding/* + prompts/*, # plus the rendered agent-configs.png. Split out of the # rust derivation so a tweak to e.g. system.md doesn't bust # the cargo cache. Build input of the `cargo-test` check but @@ -164,6 +164,7 @@ # spawn dramatically because the heavy lifting (nixpkgs + # claude-code + hive-ag3nt binary) is already in the store # when the meta evaluator goes to build the container. + # Closes #97. # # nixosConfigurations are pinned to x86_64-linux (nixos- # containers only run native arch), so these toplevels are @@ -174,7 +175,7 @@ agent-base-toplevel = self.nixosConfigurations.agent-base.config.system.build.toplevel; ruth-toplevel = self.nixosConfigurations.ruth.config.system.build.toplevel; - # Auto-generated nix options reference for hyperhive. + # Auto-generated nix options reference for hyperhive (#616). # `docs` bundles host + agent pages into one tree; the split # outputs are useful when consumers only want one surface. # All three are pure markdown — no rust or frontend deps in @@ -195,7 +196,7 @@ # is applied (manager + agent containers both apply it via # `mkContainer` further down). hyperhive-frontend = self.packages.${prev.stdenv.hostPlatform.system}.frontend; - # Static runtime assets. Exposed alongside the binary + # Static runtime assets (#555). Exposed alongside the binary # so the harness module can wire $HIVE_ASSETS_DIR straight # to `${pkgs.hyperhive-assets}/share/hyperhive`. hyperhive-assets = self.packages.${prev.stdenv.hostPlatform.system}.assets; @@ -242,7 +243,7 @@ hyperhiveNixpkgsUnstable = "path:${nixpkgs-unstable}"; # Per-container toplevels — wired into `system.extraDependencies` # when `services.hyperhive.c0re.preBuildAgentTemplates` is on so the - # host system closure pre-fetches the heavy build inputs. + # host system closure pre-fetches the heavy build inputs (#97). # Defined only for x86_64-linux because nixosConfigurations are # hardcoded to that system; the option's default keeps the # extra deps gated so aarch64 hosts don't accidentally pull @@ -291,7 +292,7 @@ packages = with pkgs; [ cargo clippy - librsvg # rsvg-convert — hive-c0re/build.rs invokes it + librsvg # rsvg-convert — hive-c0re/build.rs invokes it (#424) pkg-config rust-analyzer rustc @@ -330,7 +331,7 @@ # group from that gate: pedantic is the "extra, opinionated" # group the clippy team grows freely, so denying it means # every toolchain bump that adds a new pedantic lint breaks CI - # with zero code changes. The `pedantic = warn` + # with zero code changes (#1368). The `pedantic = warn` # workspace lint (Cargo.toml) keeps it as advisory signal in # local `cargo clippy` — it just no longer blocks the build. # (`-A` rather than `-W` here: `-W clippy::pedantic` would @@ -360,7 +361,7 @@ cargoTestExtraArgs = "--workspace"; HIVE_ASSETS_DIR = "${self.packages.${system}.assets}/share/hyperhive"; }; - # Nix options docs evaluation. Cheap: pulls in + # Nix options docs evaluation (#616). Cheap: pulls in # `nixosOptionsDoc` + the host module's stub eval, no rust or # frontend deps. CI fails fast if a module change breaks # option declarations or the doc rendering. Reuses the diff --git a/frontend/packages/dashboard/src/common.css b/frontend/packages/dashboard/src/common.css index 47ec4c26..305d68d5 100644 --- a/frontend/packages/dashboard/src/common.css +++ b/frontend/packages/dashboard/src/common.css @@ -3,7 +3,6 @@ @import "@hive/shared/base.css"; @import "@hive/shared/terminal.css"; @import "@hive/shared/tabs.css"; -@import "@hive/shared/chrome.css"; /* ─── global typography ───────────────────────────────────────────── Element-level rules shared across all three pages (index, flow, @@ -221,9 +220,38 @@ code { color: var(--fg); } -/* page chrome (.page-header / .page-back / .page-title — the standalone - pages' sticky back-link bar) moved to @hive/shared/chrome.css, imported - at the top of this file. */ +/* ─── page chrome: header + back link (flow + logs pages) ─────────── + .logs-header / .logs-back / .logs-title are used by both + /flow.html and /logs.html — the same frosted sticky bar pattern. */ +.logs-header { + position: sticky; + top: 0; + z-index: 25; + background: color-mix(in srgb, var(--bg) 92%, transparent); + -webkit-backdrop-filter: blur(8px) saturate(120%); + backdrop-filter: blur(8px) saturate(120%); + border-bottom: 1px solid var(--purple-dim); + display: flex; + align-items: center; + gap: 1.5em; + padding: 0.5em 1.5em; +} + +.logs-back { + color: var(--purple); + text-decoration: none; + font-size: 0.88em; + white-space: nowrap; + flex: none; +} +.logs-back:hover { text-decoration: underline; } + +.logs-title { + color: var(--subtext0); + font-size: 0.85em; + letter-spacing: 0.05em; + flex: none; +} /* ─── operator inbox + message rows ──────────────────────────────── .inbox and .msg-* are rendered by common.js (renderInbox) and @@ -525,17 +553,14 @@ body.side-panel-resizing * { cursor: ew-resize !important; } font-weight: bold; letter-spacing: 0.02em; } -/* Tint composited over the opaque elevated-surface grey (not transparent): - the bar is `position: sticky`, so a see-through background would let - page content scroll through behind it. */ .server-warn-warn { color: var(--amber); - background: color-mix(in srgb, var(--amber) 16%, var(--bg-elev)); + background: color-mix(in srgb, var(--amber) 16%, transparent); border-bottom: 1px solid var(--amber); } .server-warn-crit { color: var(--red); - background: color-mix(in srgb, var(--red) 18%, var(--bg-elev)); + background: color-mix(in srgb, var(--red) 18%, transparent); border-bottom: 1px solid var(--red); } diff --git a/frontend/packages/dashboard/src/common.js b/frontend/packages/dashboard/src/common.js index 8a05f6ee..cd60f399 100644 --- a/frontend/packages/dashboard/src/common.js +++ b/frontend/packages/dashboard/src/common.js @@ -46,11 +46,10 @@ export const form = (action, btnClass, btnLabel, confirmMsg, extra = {}, opts = return f; }; -// `truncate`, `fmtAgo`, `fmtElapsed`, `fmtDuration` + the `paintAtomic` -// render helper live in the dashboard-internal `./util.js`, not here — -// their phrasing ("X running", "X ago") is dashboard-specific, so they -// stay out of the cross-page `common.js` but are shared across the -// dashboard's own tab modules. +// `truncate`, `fmtAgo`, `fmtElapsed`, `fmtDuration` stay in tabs.js +// for now — each has display-specific phrasing ("X running", "X ago") +// tied to its caller, so they don't generalise cleanly. We can lift +// them when a second consumer needs the same shape. // ─── shared-worker SSE pipe ───────────────────────────────────────────── // Returns an EventSource-shaped facade backed by a SharedWorker that diff --git a/frontend/packages/dashboard/src/dashboard.css b/frontend/packages/dashboard/src/dashboard.css index a0d0e04c..dfd2eeb0 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -21,12 +21,6 @@ body.dashboard-shell { } .dashboard-chrome { - /* Home back-link + tab strip share one row (flex). The chrome owns the - horizontal gutter and the single full-width bottom divider; its - children drop their own side padding / border so they sit inline. */ - display: flex; - align-items: center; - gap: 0.75em; position: sticky; top: 0; z-index: 25; @@ -34,19 +28,19 @@ body.dashboard-shell { -webkit-backdrop-filter: blur(8px) saturate(120%); backdrop-filter: blur(8px) saturate(120%); border-bottom: 1px solid var(--purple-dim); - padding: 0.3em 1em; + padding: 0.4em 0 0; margin: 0 0 1em; } /* ← home back-link to the H0M3 hub. Small purple link top-left of the - chrome, mirroring the .page-back treatment the standalone pages use - (now shared from @hive/shared/chrome.css) so the navigation reads - consistently. The dashboard keeps this richer sticky chrome of its - own rather than folding into the simple page header. */ + chrome, mirroring the .logs-back treatment the sub-pages use so the + navigation reads consistently. Full chrome unification (a shared, + reusable tab/back-link component) is a later step. */ .dash-home-back { - flex: none; + display: inline-block; color: var(--purple); text-decoration: none; font-size: 0.82em; + padding: 0.1em 1em 0; white-space: nowrap; } .dash-home-back:hover { text-decoration: underline; } @@ -67,36 +61,38 @@ body.dashboard-shell { flex-wrap: nowrap; align-items: center; gap: 0.2em; - /* On one row with the home link now; the chrome owns the gutter + - the bottom divider, so no outer padding / border here. */ + padding: 0.5em 1em 0; + border-bottom: 1px solid var(--purple-dim); } -/* Shares the flat-pill aesthetic of the shared `.hive-tab` (sub-tabs on - the standalone pages) so the chrome reads as one family. Kept a touch - weightier than bare `.hive-tab` — larger font, bold labels, count - pills — since this is the dashboard's primary nav, not a sub-strip. */ .tabbar .tab { display: inline-flex; align-items: center; gap: 0.5em; flex-shrink: 0; - padding: 0.4em 0.95em; + padding: 0.55em 1em 0.45em; + margin-bottom: -1px; /* overlap the tabbar bottom border */ color: var(--muted); font-family: inherit; font-size: 0.92em; - letter-spacing: 0.06em; + letter-spacing: 0.08em; text-decoration: none; - border: 0; - border-radius: 4px; + border: 1px solid transparent; + border-bottom: 0; + border-radius: 4px 4px 0 0; cursor: pointer; - transition: color 0.15s ease, background 0.15s ease; + transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease; } .tabbar .tab:hover { - color: var(--fg); - background: var(--border); + color: var(--purple); + background: color-mix(in srgb, var(--purple) 6%, transparent); } .tabbar .tab.hive-tab--active { color: var(--purple); - background: var(--border); + border-color: var(--purple-dim); + background: var(--bg); + /* Lift the active tab visually — the bottom border of the tabbar + yields under it via the -1px margin above. */ + box-shadow: 0 -2px 12px -4px color-mix(in srgb, var(--purple) 40%, transparent); } .tab-label { font-weight: bold; white-space: nowrap; } .tab-count { diff --git a/frontend/packages/dashboard/src/flow.html b/frontend/packages/dashboard/src/flow.html index ee4e037c..6b3d0c7e 100644 --- a/frontend/packages/dashboard/src/flow.html +++ b/frontend/packages/dashboard/src/flow.html @@ -16,9 +16,9 @@ No full tabbar — the flow page is a dedicated full-viewport terminal surface; navigating back to the menu is the only chrome needed. Pages link back to H0M3, not the dashboard. --> -