diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 9ea39dec..20f71eca 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -8,6 +8,12 @@ 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 @@ -15,3 +21,19 @@ 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 42524014..f8ece2d3 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`, - `self_wake`, `recv_next`, `wake_external`. + `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,9 +117,19 @@ 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, firing `self_wake` so the next -turn starts with `{ from: "self", body: "continue" }` even if the -inbox is empty. +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. ## The claude invocation diff --git a/flake.nix b/flake.nix index 51048152..4b39a3fd 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 naersk #538). Stateless — no nixpkgs input to + # Crane (replaces the former naersk-based build). 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 (#555) and are + # separate `hyperhive-assets` derivation 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 in #555, so the rust + # `hyperhive-assets` derivation, so the rust # derivation no longer needs the dependency. # `sqlite` required by matrix-sdk's `sqlite` feature - # (`hive-matrix-mcp` workspace member, #548 phase 3) — the + # (`hive-matrix-mcp` workspace member) — 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 #555's cache goal: a + # build input of `default` (defeats the asset-split 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 (wired up in Phase 4 of #273). + # tower_http::ServeDir. frontend = pkgs.callPackage ./nix/frontend.nix { branding-svg = ./branding/hyperhive.svg; }; # Static runtime assets the rust binaries read via - # `hive_sh4re::assets::*` (#555): branding/* + prompts/*, + # `hive_sh4re::assets::*`: 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,7 +164,6 @@ # 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 @@ -175,7 +174,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 (#616). + # Auto-generated nix options reference for hyperhive. # `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 @@ -196,7 +195,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 (#555). Exposed alongside the binary + # Static runtime assets. 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; @@ -243,7 +242,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 (#97). + # host system closure pre-fetches the heavy build inputs. # 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 @@ -292,7 +291,7 @@ packages = with pkgs; [ cargo clippy - librsvg # rsvg-convert — hive-c0re/build.rs invokes it (#424) + librsvg # rsvg-convert — hive-c0re/build.rs invokes it pkg-config rust-analyzer rustc @@ -331,7 +330,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 (#1368). The `pedantic = warn` + # with zero code changes. 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 @@ -361,7 +360,7 @@ cargoTestExtraArgs = "--workspace"; HIVE_ASSETS_DIR = "${self.packages.${system}.assets}/share/hyperhive"; }; - # Nix options docs evaluation (#616). Cheap: pulls in + # Nix options docs evaluation. 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 305d68d5..47ec4c26 100644 --- a/frontend/packages/dashboard/src/common.css +++ b/frontend/packages/dashboard/src/common.css @@ -3,6 +3,7 @@ @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, @@ -220,38 +221,9 @@ code { color: var(--fg); } -/* ─── 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; -} +/* 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. */ /* ─── operator inbox + message rows ──────────────────────────────── .inbox and .msg-* are rendered by common.js (renderInbox) and @@ -553,14 +525,17 @@ 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%, transparent); + background: color-mix(in srgb, var(--amber) 16%, var(--bg-elev)); border-bottom: 1px solid var(--amber); } .server-warn-crit { color: var(--red); - background: color-mix(in srgb, var(--red) 18%, transparent); + background: color-mix(in srgb, var(--red) 18%, var(--bg-elev)); border-bottom: 1px solid var(--red); } diff --git a/frontend/packages/dashboard/src/common.js b/frontend/packages/dashboard/src/common.js index cd60f399..8a05f6ee 100644 --- a/frontend/packages/dashboard/src/common.js +++ b/frontend/packages/dashboard/src/common.js @@ -46,10 +46,11 @@ export const form = (action, btnClass, btnLabel, confirmMsg, extra = {}, opts = return f; }; -// `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. +// `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. // ─── 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 dfd2eeb0..a0d0e04c 100644 --- a/frontend/packages/dashboard/src/dashboard.css +++ b/frontend/packages/dashboard/src/dashboard.css @@ -21,6 +21,12 @@ 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; @@ -28,19 +34,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.4em 0 0; + padding: 0.3em 1em; margin: 0 0 1em; } /* ← home back-link to the H0M3 hub. Small purple link top-left of the - 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. */ + 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. */ .dash-home-back { - display: inline-block; + flex: none; 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; } @@ -61,38 +67,36 @@ body.dashboard-shell { flex-wrap: nowrap; align-items: center; gap: 0.2em; - padding: 0.5em 1em 0; - border-bottom: 1px solid var(--purple-dim); + /* On one row with the home link now; the chrome owns the gutter + + the bottom divider, so no outer padding / border here. */ } +/* 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.55em 1em 0.45em; - margin-bottom: -1px; /* overlap the tabbar bottom border */ + padding: 0.4em 0.95em; color: var(--muted); font-family: inherit; font-size: 0.92em; - letter-spacing: 0.08em; + letter-spacing: 0.06em; text-decoration: none; - border: 1px solid transparent; - border-bottom: 0; - border-radius: 4px 4px 0 0; + border: 0; + border-radius: 4px; cursor: pointer; - transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease; + transition: color 0.15s ease, background 0.15s ease; } .tabbar .tab:hover { - color: var(--purple); - background: color-mix(in srgb, var(--purple) 6%, transparent); + color: var(--fg); + background: var(--border); } .tabbar .tab.hive-tab--active { color: var(--purple); - 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); + background: var(--border); } .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 6b3d0c7e..ee4e037c 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. --> -
- ← home - FL0W +