matrix: make the hive-internal main account an ordinary matrixAccounts entry
`matrixAccounts` is meant to be the agent's full account list, but the hive-internal `main` account was outside it: the nix module emitted only the extras and `hive-matrix-daemon` prepended a `main` it synthesized from the per-agent paths, with the option schema forbidding the name outright. nix/agent-modules/matrix.nix now declares `main` itself, as an ordinary entry under `matrix.enable`, from the state-dir paths the module already used for its token path-watcher (now a shared `stateDir` binding) plus `matrix.url`. The whole set, `main` included, is serialized to HIVE_MATRIX_ACCOUNTS. accounts::configured therefore synthesizes `main` only when the parsed list carries none, and otherwise takes the declared one verbatim — hoisting it to index 0, since the daemon reads index 0 as the primary and nix serializes an attrset, so `main` sorts wherever its key falls. Declared xor synthesized: an agent whose harness predates this entry keeps working, a current one gets its own, and there is no arrangement where `main` is duplicated or missing. The reserved-name assertion is replaced rather than dropped: the name must now be legal (the module uses it), but `main`'s tokenFile stays pinned to `<state>/matrix-token`, since hive-c0re provisions the hive-internal token there and nowhere else — a retarget would evaluate fine and then never restore. The other two fields are mkDefault and free to override. Refs #4475
This commit is contained in:
parent
c7f6c33321
commit
c74249f371
4 changed files with 220 additions and 80 deletions
|
|
@ -11,6 +11,11 @@
|
|||
}:
|
||||
let
|
||||
userName = config.services.hyperhive.agent.user.name;
|
||||
# This agent's own state dir, where hive-c0re provisions the
|
||||
# hive-internal account's token (`matrix-token`) and the daemon keeps
|
||||
# its matrix-sdk store. Shared by the `main` account entry below and
|
||||
# the path-watcher glob at the bottom of this file.
|
||||
stateDir = "/agents/${userName}/state";
|
||||
# Rasterize the operator-set agent icon (`services.hyperhive.agent.icon`, an SVG) to a
|
||||
# 512x512 PNG so the matrix daemon can upload it as each account's avatar
|
||||
# over the live authenticated Client (see hive-matrix-mcp::client::sync_avatar).
|
||||
|
|
@ -92,9 +97,11 @@ in
|
|||
description = ''
|
||||
Path to this account's bearer-token file. The daemon reads
|
||||
the token from here to restore the matrix session; how the
|
||||
file gets populated is the provisioner's concern (an
|
||||
operator-supplied secret for an external account). The
|
||||
daemon skips an extra account whose token file is absent.
|
||||
file gets populated is the provisioner's concern
|
||||
(hive-c0re for the hive-internal `main` account, an
|
||||
operator-supplied secret for an external one). The daemon
|
||||
skips an extra account whose token file is absent, and
|
||||
exits cleanly to wait on the path watcher when `main`'s is.
|
||||
'';
|
||||
};
|
||||
sessionDir = lib.mkOption {
|
||||
|
|
@ -131,24 +138,29 @@ in
|
|||
}
|
||||
'';
|
||||
description = ''
|
||||
Declare *additional* matrix accounts served by the single
|
||||
`hive-matrix-daemon` (one matrix-sdk Client + sync loop each),
|
||||
beyond the agent's built-in hive-internal account. The
|
||||
attribute name keys each account (unique by construction) and is
|
||||
the handle the matrix MCP tools target via their `account`
|
||||
argument.
|
||||
Every matrix account served by the single `hive-matrix-daemon`
|
||||
(one matrix-sdk Client + sync loop each). The attribute name keys
|
||||
each account (unique by construction) and is the handle the matrix
|
||||
MCP tools target via their `account` argument.
|
||||
|
||||
The **hive-internal account is always present and is the primary**:
|
||||
it is named `main`, synthesized by the daemon from
|
||||
it is named `main`, and this module declares it for you (when
|
||||
`services.hyperhive.agent.matrix.enable` is set) from
|
||||
`services.hyperhive.agent.matrix.url` + `<state>/matrix-token` +
|
||||
`<state>/matrix-sdk-state`, and is the account a tool call acts as
|
||||
when it omits `account`. You never declare it here --- this option
|
||||
is only for the extras (e.g. an external public-matrix account).
|
||||
`<state>/matrix-sdk-state`. It is the account a tool call acts as
|
||||
when it omits `account`. It is an ordinary entry of this option
|
||||
like any other, so it shows up in the account list --- what you
|
||||
add here are the *further* accounts (e.g. an external
|
||||
public-matrix account). Its `tokenFile` stays pinned to
|
||||
`<state>/matrix-token` (an assertion; that is the one path
|
||||
hive-c0re provisions the hive-internal token to), and the
|
||||
dashboard's link-account route refuses to create an account named
|
||||
`main` --- the entry belongs to the module, not to a provisioner.
|
||||
|
||||
Leave empty (the default) for the common single-account case: the
|
||||
agent then has only `main`. When non-empty, the extras are
|
||||
serialized to the daemon's `HIVE_MATRIX_ACCOUNTS` environment
|
||||
variable and the daemon appends them after `main`. Requires
|
||||
Leave it alone (the default) for the common single-account case:
|
||||
the agent then has only `main`. The whole set is serialized to the
|
||||
daemon's `HIVE_MATRIX_ACCOUNTS` environment variable, `main`
|
||||
first. Declaring extras requires
|
||||
`services.hyperhive.agent.matrix.enable` (there is no `main` to extend otherwise).
|
||||
'';
|
||||
};
|
||||
|
|
@ -185,13 +197,22 @@ in
|
|||
+ "(the extras extend the hive-internal `main` account, which only "
|
||||
+ "exists when matrix is enabled).";
|
||||
}
|
||||
# `main` is reserved for the synthesized hive-internal account; a
|
||||
# declared extra by that name would silently collide with it.
|
||||
# `main` is no longer a forbidden key --- this module declares it
|
||||
# itself (see the `matrixAccounts.main` definition below), so the
|
||||
# name must be allowed. What stays rejected is retargeting *its
|
||||
# token file*: hive-c0re writes the hive-internal account's token
|
||||
# to `<state>/matrix-token` and nowhere else, so an override there
|
||||
# is an account that evaluates fine and then never restores. The
|
||||
# other two fields are free to override (a `mkDefault` each).
|
||||
{
|
||||
assertion = !builtins.hasAttr "main" config.services.hyperhive.agent.matrixAccounts;
|
||||
assertion =
|
||||
!config.services.hyperhive.agent.matrix.enable
|
||||
|| config.services.hyperhive.agent.matrixAccounts.main.tokenFile == "${stateDir}/matrix-token";
|
||||
message =
|
||||
"services.hyperhive.agent.matrixAccounts cannot contain a key named \"main\" "
|
||||
+ "--- that name is reserved for the hive-internal account.";
|
||||
"services.hyperhive.agent.matrixAccounts.main.tokenFile must stay "
|
||||
+ "\"${stateDir}/matrix-token\" --- that is where hive-c0re provisions the "
|
||||
+ "hive-internal account's token. Declare a separate account instead of "
|
||||
+ "pointing `main` elsewhere.";
|
||||
}
|
||||
# Token files must land at the `matrix-token*` name the daemon
|
||||
# path-watcher globs (`matrix-token*` inside this agent's own state
|
||||
|
|
@ -218,6 +239,21 @@ in
|
|||
}
|
||||
];
|
||||
|
||||
# The hive-internal account as an ordinary `matrixAccounts` entry,
|
||||
# rather than something the daemon conjures behind the option's
|
||||
# back: `matrixAccounts` is the list of *all* this agent's accounts,
|
||||
# so the one it always has belongs in it. `mkDefault` per field so an
|
||||
# operator can retarget e.g. the homeserver without a
|
||||
# conflicting-definition error (the token file is pinned by an
|
||||
# assertion above, since hive-c0re owns that path).
|
||||
services.hyperhive.agent.matrixAccounts = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
main = {
|
||||
tokenFile = lib.mkDefault "${stateDir}/matrix-token";
|
||||
sessionDir = lib.mkDefault "${stateDir}/matrix-sdk-state";
|
||||
homeserver = lib.mkDefault config.services.hyperhive.agent.matrix.url;
|
||||
};
|
||||
};
|
||||
|
||||
# Auto-inject the matrix MCP entry alongside the bash entry from
|
||||
# ./mcp.nix. `lib.mkDefault` so the operator's own agent.nix can
|
||||
# override it. Points at the daemon's own persistent
|
||||
|
|
@ -260,13 +296,14 @@ in
|
|||
// lib.optionalAttrs (config.services.hyperhive.agent.matrix.url != null) {
|
||||
HIVE_MATRIX_URL = config.services.hyperhive.agent.matrix.url;
|
||||
}
|
||||
# Multi-account: serialize the *extra* accounts to the JSON the
|
||||
# daemon parses (`accounts::configured`). Only set when extras are
|
||||
# declared; the daemon always synthesizes the primary `main`
|
||||
# (hive-internal) account itself from the per-agent paths and
|
||||
# prepends it, so we emit extras only. Each entry is in the
|
||||
# daemon's `AccountCfg` serde shape: name (the attr key) /
|
||||
# token_file / state_dir / optional homeserver.
|
||||
# Serialize the whole account set --- `main` included --- to the
|
||||
# JSON the daemon parses (`accounts::configured`). Each entry is in
|
||||
# the daemon's `AccountCfg` serde shape: name (the attr key) /
|
||||
# token_file / state_dir / optional homeserver. The daemon hoists
|
||||
# `main` to primary wherever the attr key sorted, and falls back to
|
||||
# synthesizing it from the per-agent paths only when this JSON
|
||||
# carries no `main` --- which is how an agent whose harness
|
||||
# predates this entry keeps working.
|
||||
// lib.optionalAttrs (config.services.hyperhive.agent.matrixAccounts != { }) {
|
||||
HIVE_MATRIX_ACCOUNTS = builtins.toJSON (
|
||||
lib.mapAttrsToList (
|
||||
|
|
@ -325,7 +362,7 @@ in
|
|||
# sibling's token still matches, and it fires again until the start
|
||||
# limit stops it. Scoped to this agent, the condition is false exactly
|
||||
# when the daemon would have nothing to do.
|
||||
pathConfig.PathExistsGlob = "/agents/${userName}/state/matrix-token*";
|
||||
pathConfig.PathExistsGlob = "${stateDir}/matrix-token*";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue