feat(server-mode): pane presence and typing audit (sharing phase 4)

A pane's tab shows the other people who have it open (pane-presence.js,
in memory, fed by the /ws-pty attach; GET /api/lanes/:id/presence and the
pane_presence broadcast to the owner and full collaborators) and says
"Billed to <name>" for a pane a collaborator opened (startedBy on the run).
The first real keystroke of each attach into a shared workspace's pane by
anyone but its owner is audited as `type into <run id>` (mouse, focus and
terminal replies to tmux do not count). The pty socket now pings, so a
vanished browser is ended at the next re-check and leaves the presence list.
This commit is contained in:
2026-11-06 11:32:25 +07:00
parent 879a6bb02d
commit 18e574a765
21 changed files with 568 additions and 8 deletions
+11
View File
@@ -2126,6 +2126,17 @@ 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/presence → { panes: { "<run id>": [{id, name}] } }
```
Who has which pane open (server mode; local mode `{panes: {}}`), in memory;
changes come as the WebSocket message `pane_presence` `{laneId, runId, users}`
to the workspace's owner and full collaborators. A run handle
(`GET /api/run`) carries `startedBy: {id, name}` when a collaborator opened
it — they pay for it. The first keystroke of a terminal attach into a shared
workspace's pane by anyone but its owner is audited (`type into <run id>`).
```http
GET /api/lanes/:id/sessions/:sid/shares → { shares, canManage }
PUT /api/lanes/:id/sessions/:sid/shares {"userIds": [3]} → { shares }
+11
View File
@@ -311,6 +311,17 @@ 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.
**Who is here, and who pays.** A pane's tab shows the other people who have
it open (`pane_presence`, `GET /api/lanes/:id/presence`, in memory — never
the viewer themself), and its tooltip says "Billed to <name>" for a pane a
collaborator opened (`startedBy` on the run). The first keystroke of each
terminal attach into a SHARED workspace's pane (whole or one session of it)
by anyone but its owner is written to the audit log as `type into <run id>` —
who typed where, never what (mouse, focus and the terminal's own answers to
tmux do not count); the Chat composer types through the same attach, so its
prompts are covered too. A terminal that stops answering pings (a sleeping
laptop) is ended at the next re-check, and its presence with it.
#### Sharing one session
The owner (or an admin) can also share ONE session: right-click it in the
+1 -1
View File
@@ -1,6 +1,6 @@
# Workspace & session sharing — plan
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.
Status: **phases 1–4 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`; pane presence + typing audit; R1 decided as option **(a)** — accept and warn hard).
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