3a8be55b55
- 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.
61 lines
2.4 KiB
JavaScript
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);
|
|
});
|
|
});
|