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:
2026-10-08 23:04:57 +07:00
parent a9c71cd905
commit b7f3735b29
147 changed files with 5644 additions and 2561 deletions
+39
View File
@@ -42,6 +42,7 @@ describe("pty-attach", () => {
beforeEach(() => {
fakePty = makeFakePty();
ptyAttach.__setSpawnImpl(() => fakePty);
ptyAttach.__resetGroups();
tmux.__setExecImpl(() => "");
});
afterEach(() => {
@@ -164,6 +165,44 @@ describe("pty-attach", () => {
assert.equal(closed, true);
});
it("holds a second viewer at the first one's size until it takes the lead", () => {
// Two browsers of different sizes on one pane: tmux's `window-size latest`
// resized the window under whichever was not typing. One drives; the other
// renders the driver's grid.
const ptys = [];
ptyAttach.__setSpawnImpl((_f, _a, opts) => {
const p = makeFakePty();
p.opts = opts;
ptys.push(p);
return p;
});
const resize = (ws, cols, rows, drive) =>
ws.__emitter.emit(
"message",
Buffer.from(JSON.stringify({ type: "resize", cols, rows, drive })),
{
binary: false,
}
);
const a = makeFakeWs();
const b = makeFakeWs();
ptyAttach.attach(a, "agenthub-lane-1", { cols: 200, rows: 50 });
ptyAttach.attach(b, "agenthub-lane-1", { cols: 100, rows: 30 });
assert.deepEqual([ptys[1].opts.cols, ptys[1].opts.rows], [200, 50]);
assert.deepEqual(JSON.parse(b.__sent[0].data), { type: "size", cols: 200, rows: 50 });
resize(b, 100, 30); // its own fit, not a takeover: nothing moves
assert.equal(ptys[1].__writes.length, 0);
resize(b, 100, 30, true); // a keystroke in b
assert.deepEqual(ptys[1].__writes.at(-1), { resize: [100, 30] });
assert.deepEqual(ptys[0].__writes.at(-1), { resize: [100, 30] });
assert.deepEqual(JSON.parse(a.__sent.at(-1).data), { type: "size", cols: 100, rows: 30 });
b.__emitter.emit("close"); // the driver leaves: the rest size themselves
assert.deepEqual(JSON.parse(a.__sent.at(-1).data), { type: "lead" });
});
it("kills the PTY attach process when the WS connection closes", () => {
const ws = makeFakeWs();
ptyAttach.attach(ws, "agenthub-lane-1", { cols: 80, rows: 24 });