feat(server-mode): share one session; scope collaborators' pane tokens (sharing phase 3)
A workspace's owner (or an admin) can share ONE session from its right-click menu (GET/PUT /api/lanes/:id/sessions/:sid/shares, table session_shares, audited). The user sees the workspace (access: "session") with only that session, joins the pane holding it, resumes it on their own account in a free slot, and uses the workspace's git and Files reads and git writes; everything else of the workspace answers 403 ESESSIONSHARE (scope.js sessionShareAllows in scopeGuard). Hook/runtime broadcasts stay with full collaborators. A collaborator's pane token (readable by the owner's Linux account) now acts as them inside that workspace only, and never starts or bills a session (auth.js paneLaneAllows, 403 EPANESCOPE); a Terminal tab the owner or an admin opens acts as the owner again. Docs say plainly that under S3 a session share hands over the owner's account inside the terminal.
This commit is contained in:
+17
-1
@@ -2121,7 +2121,23 @@ 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.
|
||||
`docs/SERVER-MODE.md` → Sharing a workspace. A collaborator's pane token
|
||||
answers only that workspace's reads, the workspace list (filtered to it), its
|
||||
stage / up / down / hook / feature / mcp-sync writes and `/api/health`
|
||||
(`403 EPANESCOPE` — never a start or resume).
|
||||
|
||||
```http
|
||||
GET /api/lanes/:id/sessions/:sid/shares → { shares, canManage }
|
||||
PUT /api/lanes/:id/sessions/:sid/shares {"userIds": [3]} → { shares }
|
||||
```
|
||||
|
||||
One session shared on its own (`session_shares`), same rules as the workspace
|
||||
list (owner or admin, enabled users, audited `share session <id>`, `404
|
||||
ENOSESSION` for a session of another workspace). The user gets the workspace
|
||||
in `GET /api/lanes` with `access: "session"`, only that session in its session
|
||||
lists, the pane holding it, a resume of it (`start {resumeSessionId}` — no
|
||||
fork, no new session, no shell), and the workspace's git/Files reads and git
|
||||
writes; any other call on the workspace answers `403 ESESSIONSHARE`.
|
||||
|
||||
#### Workspace context: instructions, folders, context files, memory
|
||||
|
||||
|
||||
@@ -301,6 +301,41 @@ enabled. Every share and unshare is audited (`share workspace <id>` /
|
||||
the next attach re-check. Deleting the workspace, or the user, drops the rows;
|
||||
giving the workspace to a user it was shared with drops theirs.
|
||||
|
||||
**A collaborator's pane token acts as them inside that workspace only.** The
|
||||
owner's account can read it (it sits in the owner's home), so it answers only
|
||||
reads of `/api/lanes/<that id>/…` (and `/api/workspaces/<id>/…`), the
|
||||
workspace list (which then holds that workspace only), `/api/health`, and its
|
||||
`stage`, `up`, `down`, `hook/<name>`, `features/activate` and `mcp/sync`
|
||||
writes (`paneLane`, `403 EPANESCOPE` for everything else) — never a start,
|
||||
resume or any other call that could bill them. The CLI's workspace commands
|
||||
work from their pane; the MCP server and the statusline's spend line do not.
|
||||
A pane the owner or an admin opens still acts as the owner.
|
||||
|
||||
#### Sharing one session
|
||||
|
||||
The owner (or an admin) can also share ONE session: right-click it in the
|
||||
sidebar → **Share…** (`GET/PUT /api/lanes/:id/sessions/:sid/shares`, table
|
||||
`session_shares`, audited as `share session <id>`). The user sees the
|
||||
workspace in their list (`access: "session"`) with only that session in it,
|
||||
attaches to the pane holding it, sends it messages (a resume, on their own
|
||||
account and billed to them, as above) and uses the workspace's Changes, git
|
||||
and Files reads and git writes. Everything else of the workspace — another
|
||||
session, a new session, a fork, a Terminal tab, `/api/run`, its instructions,
|
||||
context and settings — answers `403 ESESSIONSHARE` (`sessionShareAllows` in
|
||||
`scope.js`, checked in `scopeGuard`). Their run lists show only runs that held
|
||||
that session, and a pane switching to another session closes their terminal
|
||||
at the next attach re-check. Taking it back, deleting the workspace or the
|
||||
user drops the rows. A resume names a free slot (`slot: "auto"`, never pane
|
||||
0) and a session of THAT workspace; the session's name and archive state stay
|
||||
the owner's (`PATCH /api/sessions/<id>` is `ESESSIONSHARE`), and the
|
||||
workspace's extra folders (`?source=`), its files list for Artifacts and its
|
||||
stage history stay out of reach. **These limits hold for the dashboard's API
|
||||
only.** Under S3 the pane runs as the owner's Linux account: inside it the
|
||||
user can `/resume` any session of the folder, run shell commands and read the
|
||||
owner's home — a session share hands over as much as a workspace share
|
||||
there. The Share dialog says so; share only with people you would hand your
|
||||
keyboard to.
|
||||
|
||||
### AI Proxy profiles
|
||||
|
||||
Each user may keep **personal** AI Proxy profiles beside the **shared** ones:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Workspace & session sharing — plan
|
||||
|
||||
Status: **phases 1–2 built 2026-11-06** (workspace sharing; collaborators run on their own account, billed per payer by hour — `session_payers`; R1 decided as option **(a)** — accept and warn hard). Phases 3–4 not built.
|
||||
Status: **phases 1–3 built 2026-11-06** (workspace sharing; collaborators run on their own account, billed per payer by hour — `session_payers`; one session can be shared — `session_shares`; R1 decided as option **(a)** — accept and warn hard). Phase 4 not built.
|
||||
An independent review of phase 1 found and got fixed: a collaborator's Terminal
|
||||
tab acted as the owner through its pane token (now `dashboard_runs.started_by`),
|
||||
`POST /api/run {shell:true}` bypassed `ECOLLABSTART`, a disabled collaborator
|
||||
|
||||
Reference in New Issue
Block a user