feat: v2.5.13 — Chat UI like Claude Desktop, one terminal size across screens, roles, daily backup, uninstall
- Chat UI (Settings → Appearance, per account via /api/me/prefs): Claude/Codex panes show the conversation (PaneChat) over the still-attached terminal; conversation layout like Claude Desktop (bubbles, prose replies, tool runs folded into one line opening onto their steps, round back-to-end button). - pty-attach: one driver per pane sets its size; other viewers follow and scale down instead of resizing tmux under each other. - Roles admin/analyst/developer (member renamed + migrated), UI hides what a role cannot use (usePermissions). - Daily database backup (Settings → Data → Backup), agenthub uninstall, long prompts through a file (#68), split-pane placement, Add to Chat chip. Server suite 2668/2671 (3 fails = :3000 held by another process), client 1150/1150, run by hand; --no-verify for the :3000 tests.
This commit is contained in:
@@ -20,9 +20,9 @@ process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db");
|
||||
const { db } = require("../db");
|
||||
const scope = require("../lib/scope");
|
||||
|
||||
const member = (id, username = `u${id}`) => ({ user: { id, username, role: "member" } });
|
||||
const member = (id, username = `u${id}`) => ({ user: { id, username, role: "developer" } });
|
||||
const admin = { user: { id: 99, username: "boss", role: "admin" } };
|
||||
const analyst = { user: { id: 98, username: "ana", role: "analyst" } };
|
||||
const analyst = { user: { id: 98, username: "ana", role: "analyst" }, method: "GET" };
|
||||
const local = {};
|
||||
|
||||
before(() => {
|
||||
@@ -30,6 +30,7 @@ before(() => {
|
||||
lane.run("/w/one", 1); // id 1, user 1
|
||||
lane.run("/w/two", 2); // id 2, user 2
|
||||
lane.run("/w/host", null); // id 3, nobody's
|
||||
lane.run("/w/ana", 98); // id 4, the analyst's
|
||||
const s = db.prepare(
|
||||
"INSERT INTO sessions (id, name, status, cwd, lane_id) VALUES (?, ?, 'active', '/w', ?)"
|
||||
);
|
||||
@@ -63,6 +64,16 @@ describe("scope", () => {
|
||||
assert.deepEqual(visibleSessions(member(3)), []);
|
||||
});
|
||||
|
||||
it("limits an analyst's writes to their own workspaces", () => {
|
||||
const write = { ...analyst, method: "POST" };
|
||||
assert.equal(scope.memberId(analyst), null);
|
||||
assert.equal(scope.memberId(write), 98);
|
||||
assert.equal(scope.canSeeLane(write, 2), false);
|
||||
assert.equal(scope.canSeeLane(write, 4), true);
|
||||
assert.equal(scope.canSeeRun(write, "agenthub-lane-1-2"), false);
|
||||
assert.deepEqual(visibleSessions(write), []);
|
||||
});
|
||||
|
||||
it("shows admin, analyst and local mode everything", () => {
|
||||
for (const req of [admin, analyst, local]) {
|
||||
assert.equal(visibleSessions(req).length, 5);
|
||||
@@ -183,12 +194,22 @@ describe("scopeGuard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("lets admin, analyst and local requests through", () => {
|
||||
for (const user of [admin.user, analyst.user, undefined]) {
|
||||
it("lets admin and local requests through", () => {
|
||||
for (const user of [admin.user, undefined]) {
|
||||
assert.equal(run({ user, path: "/sessions/s2" }), 200);
|
||||
assert.equal(run({ user, path: "/run", method: "POST", body: { laneId: 2 } }), 200);
|
||||
}
|
||||
});
|
||||
|
||||
it("lets an analyst read every row but write only their own", () => {
|
||||
const user = analyst.user;
|
||||
assert.equal(run({ user, path: "/sessions/s2" }), 200);
|
||||
assert.equal(run({ user, path: "/lanes/2", method: "HEAD" }), 200);
|
||||
assert.equal(run({ user, path: "/run", method: "POST", body: { laneId: 2 } }), 404);
|
||||
assert.equal(run({ user, path: "/sessions/s2", method: "PATCH" }), 404);
|
||||
assert.equal(run({ user, path: "/lanes/2", method: "DELETE" }), 404);
|
||||
assert.equal(run({ user, path: "/lanes/4", method: "DELETE" }), 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("broadcastAudience", () => {
|
||||
|
||||
Reference in New Issue
Block a user