feat: account pool per tool with groups and a Cursor pool; update runs in the background

- The update dialog can be closed while an update applies: it keeps running,
  the page still reloads itself when the new server is up, and a failure
  re-opens the dialog with its error.
- Account pool card: one tab per AI tool (Claude Code, Codex, Cursor CLI,
  Oh My Pi, and every other installed tool, which says it has no pool),
  accounts as cards with 5-hour and weekly quota shown as what is LEFT plus
  their reset times, and the saved profiles of Settings → AI tools →
  Profiles as cards with "Share to pool".
- Cursor account pool (server/lib/cursor-pool.js): Cursor API keys handed to
  a cursor-agent pane only through tmux -e CURSOR_API_KEY;
  `agenthub accounts add <name> --tool cursor`.
- Pool groups (server/lib/pool-groups.js, /api/accounts/groups): a group's
  members run only on its accounts of each tool, a user in no group on the
  accounts no group lists, a pin still wins. account_pins loses its tool
  CHECK (rebuilt, pins kept); three new tables.
- Oh My Pi login sharing can be switched from its pool tab too.
This commit is contained in:
2026-10-22 17:02:08 +07:00
parent 44426faf16
commit 685eb748e8
30 changed files with 2891 additions and 299 deletions
@@ -298,12 +298,17 @@ describe("UpdateNotifier — closing vs skipping", () => {
expect(await screen.findByText(i18n.t("updates:title"))).toBeInTheDocument();
});
it("ignores Escape while an update is applying", async () => {
applyMock.mockReturnValue(new Promise(() => {}));
it("closes while an update applies, and re-opens if it then fails", async () => {
let fail: (e: Error) => void = () => {};
applyMock.mockReturnValue(new Promise((_, reject) => (fail = reject)));
render(<UpdateNotifier />);
await userEvent.click(await screen.findByText(i18n.t("updates:updateNow")));
await userEvent.keyboard("{Escape}");
expect(screen.getByText(i18n.t("updates:title"))).toBeInTheDocument();
await waitFor(() =>
expect(screen.queryByText(i18n.t("updates:title"))).not.toBeInTheDocument()
);
fail(new Error("Command failed: npm run build"));
expect(await screen.findByText("Command failed: npm run build")).toBeInTheDocument();
});
});