feat(server-mode): collaborators run sessions on their own account (sharing phase 2)

A user a workspace is shared with may now start and resume AI sessions in
it. The run uses THEIR OWN account — a Claude pool setup-token, a Codex/Cursor
pool API key, or an AI Proxy of theirs — never the owner's login or a pool
login (409 ENOCOLLABACCOUNT otherwise). Their quota decides (EQUOTA, their own
flag file, the sweep goes by payer).

Money is per payer by UTC hour (session_payers, server/lib/session-payers.js):
a session nobody else ran is billed as before; one a collaborator started is
theirs; a resume changes hands from the next hour. Only Claude books by the
hour, so a cross-payer resume of any other tool and a collaborator's
resumeLast are refused (409 ECOLLABRESUME); a Terminal tab stays the owner's.
Users page, details, quotas, Today (mine=1) and developer budgets count by
payer. Docs: SERVER-MODE.md, API.md, CLAUDE.md, plan status.
This commit is contained in:
2026-11-06 11:10:05 +07:00
parent 54001585e2
commit 1a365a45bd
23 changed files with 834 additions and 136 deletions
+9 -4
View File
@@ -2110,10 +2110,15 @@ WHOLE list — at most 200 enabled users, never the owner (`400 EBADUSER`) — f
owner or an admin only (`403 ENOTOWNER`), same-origin, each change audited. Local
mode: `GET` answers an empty list, `PUT` `400 ELOCALMODE`. A user it is shared with
sees and works in it like its owner, but `PATCH {title}`, `reset`/`remove`/`purge`,
`DELETE` and `PUT …/shares` answer them `403 ENOTOWNER`, and a non-shell
`POST /api/lanes/:id/start` or any `POST /api/run` `403 ECOLLABSTART`. A Terminal
tab they open (`start {shell: true}`) records them in `dashboard_runs.started_by`:
its pane token acts as them, not the owner. A user disabled since stays on the
`DELETE` and `PUT …/shares` answer them `403 ENOTOWNER`. A session they start or
resume (`POST /api/lanes/:id/start`, `POST /api/run`) runs on THEIR OWN account —
a pool setup-token (Claude) or API key (Codex, Cursor) they may use, or an AI
Proxy of theirs — else `409 ENOCOLLABACCOUNT`; resuming a non-Claude session the other side pays
for, or a collaborator's `resumeLast`, is `409 ECOLLABRESUME`; their spend quota (`429 EQUOTA`)
decides, not the owner's. Every pane they open (a Terminal tab too) records them
in `dashboard_runs.started_by`: its pane token acts as them, and an AI pane's
session hours are billed to them (`session_payers`; a Terminal tab's stay the
owner's). A user disabled since stays on the
list (`disabled: true`) and may be kept or removed; only a new one must be enabled. Users taken off
get the WebSocket message `lane_access` `{laneId, userIds}` (sent to them only). See
`docs/SERVER-MODE.md` → Sharing a workspace.