64173a5428
Only the process that starts a pane writes it into its own cached tmux list (noteSession); a second dashboard on the same database (a dev server) holds a member list up to 15 s old, so its heal beat ended a Codex pane seconds after it started and the proxy gateway answered 401. reconcileOrphans and healRunId skip a run younger than 20 s (dashboard-runs.isYoungRun). Tests: dashboard-runs (young vs old).
69 lines
2.5 KiB
JavaScript
69 lines
2.5 KiB
JavaScript
/**
|
|
* @file s3-unseen-member-runs.test.js
|
|
* @description A dashboard WITHOUT S3 (local mode, e.g. a dev server) sharing
|
|
* the database of an S3 server must never end a member's run or release a
|
|
* member's workspace: their panes live on the member's own tmux server, which
|
|
* it cannot ask. Before the fix its heal beat marked every member run
|
|
* `interrupted` within 10 s, and the proxy gateway then refused the pane.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const { describe, it, after } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
|
|
const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "s3-unseen-"));
|
|
const USERS = path.join(ROOT, "users");
|
|
process.env.DASHBOARD_DB_PATH = path.join(ROOT, "t.db");
|
|
process.env.AGENTHUB_SERVER_CONF = path.join(ROOT, "server.conf");
|
|
delete process.env.AGENTHUB_MODE; // local mode: this server has no S3
|
|
fs.writeFileSync(
|
|
process.env.AGENTHUB_SERVER_CONF,
|
|
`HOST_USER=host\nHOST_UID=1001\nUSERS_ROOT=${USERS}\n`
|
|
);
|
|
|
|
const runs = require("../lib/dashboard-runs");
|
|
const { db } = require("../db");
|
|
const { healAllRunIds } = require("../routes/lanes");
|
|
|
|
after(() => fs.rmSync(ROOT, { recursive: true, force: true }));
|
|
|
|
const start = (id, laneId, cwd) =>
|
|
runs.recordRun({
|
|
id,
|
|
mode: "interactive",
|
|
cwd,
|
|
status: "running",
|
|
laneId,
|
|
sessionId: `s-${id}`,
|
|
startedAt: Date.parse("2026-01-01T00:00:00Z"),
|
|
});
|
|
|
|
describe("a server without S3 on an S3 machine's database", () => {
|
|
it("never ends a member's run, and still ends its own", () => {
|
|
start("agenthub-lane-1", 1, path.join(USERS, "mia", "proj"));
|
|
start("agenthub-lane-2", 2, "/home/host/proj");
|
|
runs.reconcileOrphans(() => false);
|
|
assert.equal(runs.getRun("agenthub-lane-1").status, "running");
|
|
assert.equal(runs.getRun("agenthub-lane-2").status, "interrupted");
|
|
});
|
|
|
|
it("never releases a member's workspace", () => {
|
|
const lane = db.prepare(
|
|
"INSERT INTO lanes (title, cwd, run_id, status) VALUES (?, ?, ?, 'running')"
|
|
);
|
|
const member = lane.run(
|
|
"m",
|
|
path.join(USERS, "mia", "proj"),
|
|
"agenthub-lane-1"
|
|
).lastInsertRowid;
|
|
const host = lane.run("h", "/home/host/proj", "agenthub-lane-2").lastInsertRowid;
|
|
healAllRunIds(new Set());
|
|
const runId = (id) => db.prepare("SELECT run_id FROM lanes WHERE id = ?").get(id).run_id;
|
|
assert.equal(runId(member), "agenthub-lane-1");
|
|
assert.equal(runId(host), null);
|
|
});
|
|
});
|