The human-readable matrix MCP reference was missing three behaviours
documented only in the MCP server instructions string:
- send_file / download_file tools (issues 1829, 1830)
- [file:]/[image:]/[audio:]/[video:] attachment markers in read_room (issue 1830)
- unread guard: send_message, send_dm, send_file, send_reply all
reject if the room has unread messages (issue 1828)
- send_redact (omitted from the tool list entirely)
Add all four to docs/tools/matrix.md to match the MCP server
instructions added in the previous commit.
Part of the #1802 hive-forge audit: the read verbs (view / issue / pr /
comments / comment-show / timeline / diff / pr-status / pr-reviews) look
overlapping but each has a distinct output shape. Add a selection table +
rule-of-thumb so agents pick the right one (esp. 'view first' to clear the
read-before-comment guard) instead of guessing. Zero blast radius — pure
docs, no verb changes (the audit concluded the CLI is structurally clean
and renames/removals aren't worth the caller breakage).
ci-log drove only the live run-view streamer (POST .../runs/<n>/jobs/<j>
with logCursors), which reads the live act_runner task record. Forgejo
prunes that record once a run completes, so the streamer 500s with
'task ... resource does not exist' on quick or older runs even though
the web UI still shows the logs — the reader (argus, atlas) then had to
ask the operator to relay the error.
Add a fallback: when the streamer errors or returns no lines, download
the persisted whole-job log via the same web route the run page's view-
raw-logs link uses, .../runs/<n>/jobs/<job>/attempt/<a>/logs, keyed by
the per-repo run number with the attempt segment. It survives the task
prune. Live and recent runs keep the rich per-step streamed view; only
the pruned case takes the flat fallback (where --step can't apply). New
--attempt flag (default 1) selects the run attempt for re-runs.
Verified against a real pruned run whose streamer 500'd: the fallback
prints the full persisted log; a completed short job ends cleanly at
'Job succeeded', confirming the route returns complete logs.
hive-forge had close but no reopen, so reopening required the non-obvious
workaround 'issue edit <n> --state open'. Add a reopen verb mirroring close
(PATCH state=open), wired into both 'pr reopen' (kind-checked PR) and
'issue reopen' (kind-checked issue). Updates docs/forge.md + the subcommand
enumerations.
The previous implementation POSTed Forgejo's run-page rerun web route,
which is CSRF-gated and answers a bare token POST with 404 — so the verb
never actually re-ran anything against the agent token.
Rework it to dispatch a fresh run of the workflow via the
GitHub-compatible workflow-dispatch API
(POST /repos/<o>/<r>/actions/workflows/<workflow>/dispatches {"ref":<branch>}),
which accepts a plain agent token (verified end-to-end on Forgejo 15.0.3).
A dispatched run is equivalent to the old empty-commit retrigger, minus
the commit.
The branch is resolved from exactly one of --pr (the PR head branch),
--run (branch + workflow looked up from that run in the Actions runs
list), or --branch (directly); --workflow picks the workflow file for
--pr/--branch (default ci.yml). Dispatch re-runs the whole workflow, so
the old --job single-job variant is dropped.
Also add workflow_dispatch to .forgejo/workflows/ci.yml for explicitness
(Forgejo 15.0.3 dispatches the pull_request workflow without it, but the
trigger makes the API path intent-clear and cross-version robust), remove
the now-unused Client::post_web_no_content, and update docs/tools/forge.md.
When a CI run fails for a transient reason (remote-builder flap, cold-daemon
window, act_runner hiccup) the only retrigger path was an empty commit, which
litters PR history and forces a force-push to clean up. This verb POSTs the
rerun action directly.
- `ci-rerun --run <n>` re-runs all jobs of a run (run number = the `runs/<n>`
the UI shows, same value ci-log / artifact-get take, surfaced as a CI
context's target_url by pr-status).
- `--pr <n>` resolves the run from the PR head sha's CI status target_url.
- `--job <i>` re-runs a single job.
Forgejo exposes no REST endpoint for rerunning a run, so this rides the run
page's web routes (`<base>/<owner>/<repo>/actions/runs/<n>[/jobs/<i>]/rerun`)
via a new `Client::post_web_no_content` (web base like post_json_web, tolerates
the redirect/empty response the rerun handler returns). Mirrors ci-log's
web-route approach + auth path.
docs/tools/forge.md updated with the verb.
Per operator review (#1899): the hive's own domain should never be
typed by hand. Remove the --domain flag from peer-config and wg init
entirely; both now resolve it from the daemon (HostRequest::HiveDomain).
peer-config errors with a clear hint if the daemon can't answer; wg init
prints the peer block best-effort (skips it, still enables the mesh, if
unresolved). Regenerated docs/tools/hivectl-cli.md.
Per operator review: hivectl shouldn't make the operator retype this
hive's own domain. Add a HostRequest::HiveDomain admin-socket query
(c0re answers from HYPERHIVE_HIVE_DOMAIN, already in its service env) and
a domain field on HostResponse.
hivectl peer-config now resolves the domain as: --domain override (kept
for offline/scripted use), else the daemon query; errors with a clear
hint when neither resolves. wg init resolves it best-effort too, so it
prints the hand-over peer-config block without --domain (skipped, not
failed, when the daemon is unreachable).
Regenerated docs/tools/hivectl-cli.md.
Add `hivectl peer-config --domain <this-hive-domain>`: prints the
`services.hyperhive.swarm.peers."<domain>"` nix block a peer operator
pastes to federate with this hive. Emits `caCert = ./<hive>-ca.pem` plus
a `cp /var/lib/hive-tls/ca.pem ./<hive>-ca.pem` line when this hive
serves a self-signed CA (the cert file exists); omits caCert for
ACME/public-CA hives (trusted by the default bundle). Includes the
WireGuard public key when the mesh key exists, and the
wireguardAddress/Endpoint passed via flags.
`wg init` gains an optional --domain; when set it calls peer-config at
the end, so a fresh mesh setup prints the hand-over block in one command.
Pure output — reads local state (TLS CA cert presence, wg key), never
mutates. Regenerated docs/tools/hivectl-cli.md.
Closes#1897.
Group issue/PR operations under `pr` and `issue` parent commands
(`hive-forge pr close 42`, `issue create …`, `pr status --pr 42`) per the
operator decision — kind-namespaced verbs replace the flat surface.
- new `verbs::pr_cmd` / `verbs::issue_cmd` parent commands wrap the existing
per-verb modules (reuse their Args + run fns) under `#[command(subcommand)]`.
- kind-validation (the win over the old generic verbs): the generics that work
on both (view/comment/comments/close/labels/assign/timeline) call
`assert_kind` first, so `pr close <issue>` / `issue close <pr>` are rejected
with a 'use the other command' message. PR-only / issue-only verbs are
kind-correct by construction. `number` exposed `pub(crate)` on the shared
verbs so the wrappers can probe it.
- every flat kind verb (`close`, `pr-create`, `pr-status`, `issue-edit`, …)
kept as a `#[command(hide = true)]` back-compat alias — still parses, dropped
from --help; removed in a later sweep once usage migrates. (`pr`/`issue`
bare-show become `pr show` / `issue show` — the names are now parents.)
- docs/tools/forge.md documents the new surface + the deprecated aliases.
cargo build/clippy/fmt clean, 54 tests pass; --help surface + alias parsing
smoke-tested.
New agents get a btrfs subvolume state root automatically when the host
FS is btrfs, but agents that predate that migration are left on plain
dirs and miss the subvolume feature set (snapshots, per-subvol
usage/quota, send/receive migration). Add an opt-in operator verb to
convert an existing plain-dir agent in place.
btrfs cannot promote a directory to a subvolume in place, so the new
privileged op stages a sibling subvolume mirroring the dir (create +
`cp -a --reflink=auto` preserving ownership/permissions/xattrs + match
the root's owner and mode), then atomically renames the original aside
and the subvolume into place, then removes the original. Any failure
before the swap leaves the original untouched; idempotent (no-op if
already a subvolume) and btrfs-gated.
The `hivectl subvol upgrade <agent> --yes` verb composes it client-side
like `restart`: stop the agent so its state bind-mount is released, run
the migration via hive-priv, then restart it — the restart is attempted
regardless of the migration outcome so a failed migration never leaves
the agent down.
- hive-sh4re: UpgradeAgentSubvolume priv request variant.
- hive-priv: the migration handler plus stage/cleanup helpers.
- hive-c0re: priv_client wrapper and the hivectl verb; regen CLI docs.
Follow-up to the btrfs-subvolume migration. Operator-opt-in disk
accounting + quotas on agent state subvolumes via btrfs qgroups:
- three privileged ops (qgroup ops need root): EnsureBtrfsQuota
(btrfs quota enable on the agent-state filesystem — statfs-gated,
idempotent, no-op off btrfs), ReadSubvolumeUsage (btrfs qgroup show
-f --raw for one agent), SetSubvolumeQuota (btrfs qgroup limit, or
clear). Reuses the is_on_btrfs helper from the subvolume work.
- priv_client wrappers, incl parse_qgroup_usage -> (referenced,
exclusive) bytes.
- hivectl 'quota' subcommand: enable / show [agent] / limit <agent>
<size|none>, with a K/M/G/T size parser + human-readable output.
Quota is deliberately NOT auto-enabled: btrfs quota enable triggers a
full rescan that is I/O-heavy on a large filesystem, and the operator
should choose when to pay that. 'quota show' on a plain-dir agent (no
subvolume) reports no qgroup data rather than erroring.
One-time-setup convenience for the inter-hive WireGuard mesh
(services.hyperhive.swarm) so nobody has to remember the wg dance:
- hivectl wg init [--address X] — generate (if absent) the hive's
private key at /etc/wireguard/hive.key (0400, never clobbered),
derive + print the public key, and print the swarm.wireguard nix
snippet to enable the mesh.
- hivectl wg peer <domain> --pubkey --address [--endpoint] — print the
swarm.peers.<domain> nix snippet to add a remote hive.
- hivectl wg status — wrap wg show wg-hive.
Hybrid model per the design: the verb owns the imperative state (the
key file), the operator pastes the printed nix into host config (kept
in git) — nothing mutates declarative config behind their back.
hivectl-only (root host ops, like the gateway htpasswd verbs); no
priv/wire/c0re changes.
flake: wrap hivectl with wireguard-tools on PATH so wg resolves even
before the mesh config (which would otherwise pull it in) exists —
wg init is the first setup step. Add clippy.toml doc-valid-idents for
the WireGuard proper noun. Regenerate hivectl-cli.md.
Add a 'hivectl completions <shell>' subcommand (clap_complete) that
prints a completion script for bash/zsh/fish/elvish/powershell, generated
from hivectl's own clap command tree so it never drifts from the real
verbs/flags. The package build installs the bash/zsh/fish scripts via
installShellFiles, so an operator gets working completion automatically
once hivectl is on their profile with shell completion enabled.
Regenerated docs/tools/hivectl-cli.md for the new verb.
Drop the enumeration of what the flag does not apply to, per operator
review on the sibling graceful-stop change. Regenerate the CLI doc so
the markdown-docs self-diff check stays in sync.
`hivectl restart [scope]` cycles the scoped containers — composes the
existing stop + start daemon ops client-side (reusing the merged Stop/Start
wire ops + global --socket), so no new wire/c0re surface. Same scope model
as stop/start (--agents/--agent/--ci/--forge/--gateway/--matrix), and
--graceful on the stop half. If the stop phase reports a failure the start
phase is skipped so a half-stopped hive isn't blindly started over.
Regenerated docs/tools/hivectl-cli.md.
The host admin socket path was duplicated on every daemon-assisted verb
(agents restart/restart-all, stop, start). Hoist it to a single global arg
on the top-level Cli (`--socket`, default DEFAULT_HOST_SOCKET, accepted
before or after the subcommand) and thread cli.socket through dispatch.
Verbs that don't talk to the daemon ignore it. Pure CLI-ergonomics change;
no wire/behaviour change. Regenerated docs/tools/hivectl-cli.md.
The hivectl-docs flake check regenerates docs/tools/hivectl-cli.md from the
clap command tree and asserts it's committed up to date. Adding the stop/start
verbs changed the CLI, so the doc was stale and the check failed. Regenerate it.
Forgejo 15 has no REST endpoint to download an Actions artifact — the
only path is the web UI download route, which is keyed by the run's
global id rather than the per-repo run number shown in run-page URLs.
The REST artifacts list route keys off the run number instead, so the
two can't be chained directly.
artifact-get takes the run number (what pr-status surfaces as a CI
context target_url), translates it to the global run id via the REST
runs list by matching each run's html_url tail, then GETs the web
download route with the agent's forge token. Saves the artifact zip to
a path (default /tmp/forge-artifact-<name>.zip) or streams to stdout
with -o -.
The artifact name is percent-encoded into the path. The encoder that
list already used for query-string filters is promoted to a shared
verbs::pct_encode helper so both call sites stay in sync.
Lets an agent pull a CI-built artifact (e.g. a paper PDF) into /shared
without host access.
Adds `hive-forge pr-merge <n> [--method merge|rebase] [--keep-branch]
[--force]` wrapping POST /repos/{owner}/{repo}/pulls/{n}/merge, so agents on
the peer-review-and-merge workflow have a CLI path instead of the raw API.
- Methods: merge (default) | rebase. Squash is intentionally not offered.
- Deletes the head branch after merge unless --keep-branch.
- Safe by default: refuses unless the PR is mergeable, CI is not red, and no
review's current verdict requests changes (latest-per-reviewer wins, so a
later approval clears an earlier request-changes). --force overrides and
also sets Forgejo's force_merge.
- New client helper post_no_content for the 200-empty-body merge response.
The daemon does not call join_room_by_id. It fires a wake and writes
to mcp-loose-ends/matrix.json; the agent calls list_invites + join_room.
Replace the inaccurate 'Auto-accept invites' paragraph with 'Invite
wakes' describing the actual flow. Merge the pending-invites paragraph
into a single accurate block.