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
+12
View File
@@ -623,6 +623,18 @@ describe("skills from files or written", () => {
["s/.env", "K=1"],
]);
assert.equal(secret.body.error.code, "ESECRET");
const word = await upload("/api/artifacts/skill/upload", [
["w/SKILL.md", "---\ndescription: W\n---\n"],
["w/api-token.json", "{}"],
]);
assert.equal(word.body.error.code, "ESECRET");
// The word alone in a doc or a script is skill content, not a key.
const docs = await upload("/api/artifacts/skill/upload", [
["tokens/SKILL.md", "---\ndescription: Design tokens\n---\n"],
["tokens/design-tokens.md", "# tokens"],
["tokens/scripts/count_tokens.py", "print(1)"],
]);
assert.equal(docs.status, 201, JSON.stringify(docs.body));
const out = await upload("/api/artifacts/skill/upload", [["../SKILL.md", "x"]], {
description: "d",
});
@@ -0,0 +1,194 @@
/**
* @file pool-account-choice.test.js
* @description A user's own choice of pool account (account-pins.js
* `setChoice`/`preferredFor`, `/api/me/pool-accounts`), fake accounts only:
* the balancer runs the user's new panes on the chosen account; an admin's pin
* wins and locks the choice (ELOCKED); only accounts the user's group allows
* may be chosen (ENOACCOUNT), and one later taken from the group falls back
* to the balancer; a removed account drops its choices; the routes list no
* token, need a login cookie and refuse in local mode.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const { describe, it, before, after, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const path = require("node:path");
const fs = require("node:fs");
const os = require("node:os");
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "pool-choice-test-"));
process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db");
process.env.CLAUDE_HOME = path.join(TMP, ".claude");
process.env.DASHBOARD_LIVENESS_PROBE = "0";
const { createApp, startServer } = require("../index");
const { db } = require("../db");
const auth = require("../lib/auth");
const groups = require("../lib/pool-groups");
const cursorPool = require("../lib/cursor-pool");
const claudePool = require("../lib/claude-pool");
const pins = require("../lib/account-pins");
require("../lib/cc-usage").fetchUsage = async () => ({});
const KEY = (c) => `key_${c.repeat(40)}`;
const TOKEN = (c) => `sk-ant-oat01-${c.repeat(40)}`;
let server;
let BASE;
let MIA;
let LEO;
async function call(method, p, { cookie, body } = {}) {
const res = await fetch(BASE + p, {
method,
headers: {
"Content-Type": "application/json",
Origin: BASE,
...(cookie ? { cookie } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
return { status: res.status, body: await res.json().catch(() => null) };
}
async function login(username, password) {
const res = await fetch(BASE + "/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
return res.headers.get("set-cookie").split(";")[0];
}
before(async () => {
process.env.AGENTHUB_MODE = "server";
server = await startServer(createApp(), 0);
BASE = `http://127.0.0.1:${server.address().port}`;
await auth.createUser("boss", "admin-pass-1", "admin");
MIA = (await auth.createUser("mia", "member-pass-1", "developer")).id;
LEO = (await auth.createUser("leo", "member-pass-2", "developer")).id;
});
after(() => {
delete process.env.AGENTHUB_MODE;
server?.close();
fs.rmSync(TMP, { recursive: true, force: true });
});
beforeEach(() => {
db.prepare("DELETE FROM pool_groups").run();
db.prepare("DELETE FROM account_pins").run();
db.prepare("DELETE FROM account_choices").run();
for (const dir of [cursorPool.poolDir(), claudePool.poolDir()]) {
fs.rmSync(dir, { recursive: true, force: true });
}
for (const c of ["a", "b", "c"]) cursorPool.addAccount(c, { apiKey: KEY(c) });
});
describe("pool account choice: the balancer", () => {
it("runs the user's new panes on the account they chose, others unchanged", () => {
assert.equal(cursorPool.pickAccount(MIA).name, "a");
pins.setChoice(MIA, "cursor", "c");
assert.equal(cursorPool.pickAccount(MIA).name, "c");
assert.equal(cursorPool.pickAccount(LEO).name, "a");
claudePool.addAccount("one", TOKEN("o"));
claudePool.addAccount("two", TOKEN("t"));
pins.setChoice(MIA, "claude", "two");
assert.equal(claudePool.pickAccount(null, MIA).name, "two");
pins.setChoice(MIA, "cursor", null);
assert.equal(cursorPool.pickAccount(MIA).name, "a", "automatic again");
});
it("lets an admin's pin win, and refuses a choice while pinned", () => {
pins.setChoice(MIA, "cursor", "b");
pins.setPin(MIA, "cursor", "c");
assert.equal(cursorPool.pickAccount(MIA).name, "c");
assert.throws(() => pins.setChoice(MIA, "cursor", "a"), { code: "ELOCKED" });
pins.setPin(MIA, "cursor", null);
assert.equal(cursorPool.pickAccount(MIA).name, "b", "the choice comes back");
});
it("allows only the group's accounts, and falls back once one leaves it", () => {
const g = groups.createGroup("A");
groups.setGroupUsers(g.id, [MIA]);
groups.setGroupAccounts(g.id, "cursor", ["b", "c"]);
assert.throws(() => pins.setChoice(MIA, "cursor", "a"), { code: "ENOACCOUNT" });
assert.throws(() => pins.setChoice(MIA, "cursor", "zz"), { code: "ENOACCOUNT" });
pins.setChoice(MIA, "cursor", "c");
assert.equal(cursorPool.pickAccount(MIA).name, "c");
groups.setGroupAccounts(g.id, "cursor", ["b"]);
assert.equal(pins.preferredFor(MIA, "cursor"), null);
assert.equal(cursorPool.pickAccount(MIA).name, "b");
});
it("drops a removed account's choices", () => {
pins.setChoice(MIA, "cursor", "b");
cursorPool.removeAccount("b");
assert.equal(pins.choiceFor(MIA, "cursor"), null);
});
});
describe("pool account choice: /api/me/pool-accounts", () => {
it("lists only the tools and accounts the user may use, never a token", async () => {
const g = groups.createGroup("A");
groups.setGroupUsers(g.id, [MIA]);
groups.setGroupAccounts(g.id, "cursor", ["b"]);
const cookie = await login("mia", "member-pass-1");
const r = await call("GET", "/api/me/pool-accounts", { cookie });
assert.equal(r.status, 200);
assert.deepEqual(
r.body.tools.map((t) => t.tool),
["cursor"]
);
assert.deepEqual(
r.body.tools[0].accounts.map((a) => a.name),
["b"]
);
assert.ok(!JSON.stringify(r.body).includes(KEY("b")));
});
it("chooses, goes back to automatic, refuses while pinned, and is audited", async () => {
const cookie = await login("leo", "member-pass-2");
let r = await call("PUT", "/api/me/pool-accounts", {
cookie,
body: { tool: "cursor", account: "c" },
});
assert.equal(r.status, 200);
assert.equal(r.body.choice, "c");
assert.equal(r.body.switched, "next-session");
assert.equal(cursorPool.pickAccount(LEO).name, "c");
r = await call("PUT", "/api/me/pool-accounts", {
cookie,
body: { tool: "cursor", account: "nope" },
});
assert.equal(r.status, 404);
assert.equal(r.body.error.code, "ENOACCOUNT");
r = await call("PUT", "/api/me/pool-accounts", { cookie, body: { tool: "x", account: "c" } });
assert.equal(r.status, 400);
pins.setPin(LEO, "cursor", "a");
r = await call("PUT", "/api/me/pool-accounts", {
cookie,
body: { tool: "cursor", account: "b" },
});
assert.equal(r.status, 409);
assert.equal(r.body.error.code, "ELOCKED");
pins.setPin(LEO, "cursor", null);
r = await call("PUT", "/api/me/pool-accounts", {
cookie,
body: { tool: "cursor", account: null },
});
assert.equal(r.status, 200);
assert.equal(r.body.choice, null);
const audited = db
.prepare("SELECT COUNT(*) AS n FROM audit_log WHERE path LIKE '%pool account%'")
.get().n;
assert.ok(audited >= 2);
});
it("needs a login", async () => {
const r = await call("PUT", "/api/me/pool-accounts", {
body: { tool: "cursor", account: "a" },
});
assert.equal(r.status, 401);
});
});
+20 -1
View File
@@ -782,6 +782,25 @@ describe("pty-run", () => {
})
);
assert.deepEqual(paneArgv(last), [at("agy"), "--continue", "--dangerously-skip-permissions"]);
// A named conversation: agy's `--conversation <id>`, not `--continue`.
tmux.__reset();
const named = captureNewSession(() =>
pty.spawnRun({
laneId: 14,
cwd: "/tmp/repo",
tool: "antigravity",
resumeSessionId: "70785283-bcc4-43a4-a1ea-02d844d5df5b",
initialPrompt: "go on",
})
);
assert.deepEqual(paneArgv(named), [
at("agy"),
"--conversation",
"70785283-bcc4-43a4-a1ea-02d844d5df5b",
"-i",
"go on",
]);
});
// A catalog tool has no "resume THIS session" argv; starting it fresh under
@@ -792,7 +811,7 @@ describe("pty-run", () => {
pty.spawnRun({
laneId: 15,
cwd: "/tmp/repo",
tool: "antigravity",
tool: "septool",
resumeSessionId: "abc12345",
}),
{ code: "EUNSUPPORTED" }
+41
View File
@@ -137,6 +137,47 @@ describe("server mode", () => {
assert.equal(a.status, 404);
});
it("lets any user press Update all AI tools and Update 9router, nothing else there", async () => {
// The guard alone, called directly: a real request would start the job.
const member = await loginAs("mia", "member-pass-1");
const guard = (method, p) => {
let passed = false;
let status = null;
const req = { method, path: p, headers: { cookie: member }, socket: {} };
const res = {
status(s) {
status = s;
return this;
},
json() {
return this;
},
};
auth.authGuard(req, res, () => {
passed = true;
});
return passed ? "next" : status;
};
for (const p of [
"/ai-tools/update-all",
"/AI-Tools/update-all/",
"/ai-proxies/9router/update",
"/ai-routers/9router/update",
]) {
assert.equal(guard("POST", p), "next", p);
}
for (const [method, p] of [
["POST", "/ai-tools/claude/update"],
["POST", "/ai-tools/claude/install"],
["PUT", "/ai-tools/claude"],
["PUT", "/ai-tools/omp-sharing"],
["DELETE", "/ai-tools/update-all"],
["POST", "/ai-proxies/9router/update/x"],
]) {
assert.equal(guard(method, p), 403, `${method} ${p}`);
}
});
it("takes hooks from this machine only", async () => {
const remote = await call("POST", "/api/hooks/event", {
body: {},