Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/frontend/packages/swarm-ui/test/subagent-list.test.mjs
iris e9bc28fe2f swarm: list subagents from their published run state
The subagents list reads each subagent's status from the subagent-status
bucket and reports its state, when it began and the exit status. A subagent
with no status keeps the newest-row lookup, so an older daemon lists as before.
The swarm UI uses the state when there is one and the row-age guess otherwise.
2026-11-15 17:17:10 +01:00

136 lines
4.4 KiB
JavaScript

// What counts as a running subagent, and the menu badge's wording. Run by
// `npm test`, which Node runs with its built-in test runner and type
// stripping, so the `.ts` module is imported as it is.
import assert from "node:assert/strict";
import { test } from "node:test";
import {
STALE_S,
byRecency,
isDimmed,
isRunning,
runLabel,
runningLabel,
splitRunning,
stateLabel,
stillListed,
} from "../src/agent/terminal/subagent-list.ts";
const NOW = 1_000_000;
const sub = (name, ago) => ({
name,
last_row_unix: ago === null ? null : NOW - ago,
model: null,
effort: null,
});
test("a subagent with a recent row is running, a quiet one is not", () => {
assert.equal(isRunning(sub("a", 5), NOW), true);
assert.equal(isRunning(sub("a", STALE_S), NOW), true);
assert.equal(isRunning(sub("a", STALE_S + 1), NOW), false);
});
test("a subagent whose time could not be read is not counted as running", () => {
assert.equal(isRunning(sub("a", null), NOW), false);
});
test("newest first, unreadable last, ties by name", () => {
const list = [sub("c", 50), sub("z", null), sub("b", 10), sub("a", 10)];
assert.deepEqual(
[...list].sort(byRecency).map((s) => s.name),
["a", "b", "c", "z"],
);
});
test("the list splits into running and quiet, each newest first", () => {
const { running, quiet } = splitRunning(
[
sub("old", STALE_S + 100),
sub("new", 1),
sub("mid", 60),
sub("none", null),
],
NOW,
);
assert.deepEqual(
running.map((s) => s.name),
["new", "mid"],
);
assert.deepEqual(
quiet.map((s) => s.name),
["old", "none"],
);
});
test("the badge counts in words", () => {
assert.equal(runningLabel(1), "1 running subagent");
assert.equal(runningLabel(4), "4 running subagents");
});
test("a pick holds only while the refreshed list still has it", () => {
const subs = [sub("a", 10), sub("b", 20)];
assert.equal(stillListed("a", subs), "a");
assert.equal(stillListed("gone", subs), null);
assert.equal(stillListed("a", []), null);
assert.equal(stillListed("a", null), null);
assert.equal(stillListed(null, subs), null);
});
const withState = (name, state, sinceAgo, rowAgo = null) => ({
...sub(name, rowAgo),
state,
since_unix: NOW - sinceAgo,
exit: state === "finished" ? 0 : null,
});
test("a published state decides, whatever the newest row's age says", () => {
// Quiet for an hour but the daemon says running: thinking, not stopped.
assert.equal(isRunning(withState("a", "running", 3600, 3600), NOW), true);
// A row a second ago but the daemon says finished.
assert.equal(isRunning(withState("a", "finished", 5, 1), NOW), false);
assert.equal(isRunning(withState("a", "stale", 900), NOW), false);
});
test("only a subagent without a status falls back to the row's age", () => {
assert.equal(isDimmed(sub("a", STALE_S + 1), NOW), true);
assert.equal(isDimmed(sub("a", 5), NOW), false);
assert.equal(isDimmed(sub("a", null), NOW), false);
assert.equal(isDimmed(withState("a", "running", 3600, 3600), NOW), false);
assert.equal(isDimmed(withState("a", "finished", 5, 1), NOW), true);
assert.equal(isDimmed(withState("a", "stale", 5), NOW), true);
});
test("the state word names finished and stale subagents only", () => {
assert.equal(stateLabel(withState("a", "finished", 5)), "finished");
assert.equal(stateLabel(withState("a", "stale", 5)), "stopped reporting");
assert.equal(stateLabel(withState("a", "running", 5)), "");
assert.equal(stateLabel(sub("a", 5)), "");
});
test("a subagent with only a status sorts by when its state began", () => {
const list = [
withState("old", "finished", 500),
sub("row", 100),
withState("new", "running", 5),
];
assert.deepEqual(
[...list].sort(byRecency).map((s) => s.name),
["new", "row", "old"],
);
const { running, quiet } = splitRunning(list, NOW);
assert.deepEqual(
running.map((s) => s.name),
["new", "row"],
);
assert.deepEqual(
quiet.map((s) => s.name),
["old"],
);
});
test("the run label names the model and effort, whichever are known", () => {
assert.equal(runLabel({ model: "opus", effort: "high" }), "opus · high");
assert.equal(runLabel({ model: "opus", effort: null }), "opus");
assert.equal(runLabel({ model: null, effort: "medium" }), "medium");
assert.equal(runLabel({ model: null, effort: null }), "");
assert.equal(runLabel({ model: "", effort: "" }), "");
});