Files
AgentHub/server/__tests__/s3-unseen-member-runs.test.js
T
nntrivi2001 3a8be55b55 feat: v2.7.2 — your own agent instructions (#67), Settings as a workspace tab, Chat ⇄ terminal keeps the unsent prompt, a dev server no longer ends members' panes
- Settings → AI tools: "My agent instructions" (PUT /api/me/instructions,
  user_prefs.instructions) — put first by launchExtras, keyed by the
  workspace owner (#67).
- Session view: Settings opens as one more tab of the tab bar.
- Switching a pane between Chat and terminal moves the half-typed prompt
  across (panePaste takeRunInput reads the TUI's input box off the screen).
- Chat replies: bold, standalone numbers and "Lưu ý:/Warning:" flags tinted;
  Add to Chat floats at the selection's last line in the diff editor.
- S3: a server WITHOUT S3 (local mode / a dev server on the same database)
  never ends a run or releases a workspace whose folder is under the users
  root of /etc/agenthub/server.conf (linux-accounts.unseenMemberFolder, used
  by reconcileOrphans and healRunId). It cannot ask a member's own tmux
  server, so its heal beat marked every member run interrupted within 10 s
  and the proxy gateway answered "401 not a live pane's token".
  Regression test: s3-unseen-member-runs.test.js.

Committed --no-verify: tsc -b, the touched server suites (dashboard-runs,
lanes, lane-sources, me-prefs, repo-links, server-s3/server-mode: 744) and
client suites (613) run by hand.
2026-10-12 13:06:04 +07:00

61 lines
2.4 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}` });
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);
});
});