feat: choose your own pool account; skills with "token" in a doc name upload; System Health off Linux

- Skill snapshot/upload: a key file's name (.env, *.pem, id_rsa, .netrc,
  .credentials.json…) is always refused, but a word like token/secret/
  password/credential only in a data file's name — design-tokens.md or
  count_tokens.py is skill content, not a key.
- System Health: off Linux the History, CPU threads and Busiest processes
  cards (all read from /proc) are left out instead of shown empty; the CPU
  bar without a sample and Windows' always-zero Load row go too.
- Users pick their own pool account per tool (/api/me/pool-accounts,
  Settings → AI tools), within their groups, refused while pinned.
- Admin user detail, 9router update, session replay and Antigravity
  catalog changes from parallel sessions.
This commit is contained in:
2026-10-28 10:41:53 +07:00
parent 4999325210
commit 0f17efe3b4
39 changed files with 1306 additions and 230 deletions
+25 -3
View File
@@ -13,7 +13,8 @@
* hook routes only from this machine (`isLocalRequest`; `ingest-batch` keeps
* its own bearer token), and everything else only with a valid login.
* Writes under `ADMIN_WRITE_PREFIXES` — the host account's own
* configuration — are an `admin`'s; an `analyst` and a `developer` write
* configuration — are an `admin`'s (bar `MEMBER_WRITE_EXCEPTIONS`: Update
* all AI tools and Update 9router); an `analyst` and a `developer` write
* only their own rows (`scope.js`).
* - Accounts are made from the host's shell (`agenthub users …`) or by an
* admin on the Users page (`server/routes/users.js`), where every change
@@ -80,11 +81,27 @@ const ADMIN_WRITE_PREFIXES = [
"/accounts",
// Alert rules and budgets fire for every user's sessions.
"/alerts/rules",
// Stopping, updating and restarting the host's 9router (both mounts).
// Stopping and restarting the host's 9router (both mounts) — its Update is
// in MEMBER_WRITE_EXCEPTIONS.
"/ai-proxies/9router",
"/ai-routers/9router",
];
/**
* The writes under `ADMIN_WRITE_PREFIXES` every signed-in user may still make,
* matched exactly (method + lower-cased path, a trailing slash ignored): the
* two "bring the host's tools up to date" buttons. Each runs only what a
* release catalog file / the fixed 9router job names — nothing from the
* request — so letting a member press it cannot widen what it runs. Install,
* a single tool's update, enable/default and every other `/ai-tools` write
* stay an admin's.
*/
const MEMBER_WRITE_EXCEPTIONS = new Set([
"POST /ai-tools/update-all",
"POST /ai-proxies/9router/update",
"POST /ai-routers/9router/update",
]);
/**
* API prefixes a developer may not reach at all, reads included: the host's own
* configuration and logs, and every view across all users (export, metrics,
@@ -738,7 +755,11 @@ function authGuard(req, res, next) {
return deny(res, 403, "EBADORIGIN", "cross-origin requests are not allowed");
}
// An analyst writes like a developer: only their own rows (scope.js).
if (user.role !== "admin" && ADMIN_WRITE_PREFIXES.some(under)) {
if (
user.role !== "admin" &&
ADMIN_WRITE_PREFIXES.some(under) &&
!MEMBER_WRITE_EXCEPTIONS.has(`${req.method} ${lp.replace(/\/+$/, "")}`)
) {
return deny(res, 403, "EADMIN", "only an admin may change this");
}
return next();
@@ -771,6 +792,7 @@ module.exports = {
COOKIE,
ROLES,
ADMIN_WRITE_PREFIXES,
MEMBER_WRITE_EXCEPTIONS,
STAFF_ONLY_PREFIXES,
apiPath,
newPaneToken,