feat: v2.6.0 — Chat UI composer (+ menu, project files, activity panel, permission mode, Stop), git on a workspace's extra folders (#70), long paste as a file (#68), S3 server mode (experimental)

Chat UI:
- Each Claude/Codex session keeps its own chat/terminal view; Settings only
  sets how new sessions open.
- Composer + menu: upload, project files and folders (#54, also dragged from
  the Files/Changes tree), context files, recent uploads, skills, plugins.
- Activity side panel (progress, outputs, files read, uploads, memory,
  skills, connectors) with a file preview; edited-file counts per turn.
- Permission-mode chip (Shift+Tab steps, read back from the pane:
  server/lib/pane-mode.js, GET|POST /api/run/:id/permission-mode).
- Stop (Esc) while the main agent works; the status is checked per agent
  (with session_id /api/agents ignores its status filter) and re-read every
  5 s while Stop shows.
- Dropped images shown in the conversation (GET /api/run/drop/:name, the
  requester's own drop dir), full-size viewer.
- A plain-text paste over 5,000 characters is attached as pasted-text.txt,
  so no prompt reaches tmux/argv limits (#68).

Changes panel: every /api/lanes/:id/git* route takes ?source=<lane_sources
id> (sourceFolder; ENOSOURCE otherwise) and a folder picker switches the
whole panel to a workspace's extra folder (#70).

Server mode S3 (one Linux user per account) — experimental, verified with
fake sudo plus a P6 managed-settings spike on a test account:
- member tmux listing runs in the background (cached, 15 s => unknown);
- transcript snapshot copy is async, chunked, temp file + rename;
- Express Layer patch only on Express 4;
- tmux capturePane/sendKey go to the member's own server.

Docs: the multi-user design moved in as docs/plans/server-mode/README.md
(the AgentHub-Server repo is gone); S3-PLAN P6 result; LANES/API/openapi.
This commit is contained in:
2026-10-09 15:51:19 +07:00
parent ce773a3f5a
commit fa156d6a59
114 changed files with 5831 additions and 885 deletions
+3 -3
View File
@@ -31,10 +31,10 @@ Asked 2026-10-08 (modelled on Orca's 3-step welcome). Decisions:
3. Pet: none or an installed pet, and its size.
4. Notifications: enable, sound and preview, send a test.
The footer has Skip, Back and Continue (Ctrl+Enter). The last step's button is "Add your first workspace", which starts the tour.
The footer has Skip, Back and Continue (Ctrl+Enter). The last step's button is "Add your first workspace", which starts the tour on the Dashboard (`/`).
- [x] `components/onboarding/Tour.tsx`: a spotlight on `data-tour` targets. Each step moves on when its condition holds, and the step reached is saved in prefs (`agenthub.tour`). The steps:
- Add workspace
- the folder field
- Add workspace: the sidebar's **+** (the rail's tree icon while the sidebar is folded)
- the folder field (its suggestions list included while it is open) — or the folder browser while it is open over it
- Create
- the prompt box
- sending the prompt
+175
View File
@@ -0,0 +1,175 @@
# Remote clients — work runs on each user's machine, the hub only manages
Status: **PLAN, not built.** Decided 2026-10-09. Branch `server-s3` (one Linux
user per account on the hub) stays as it is until this plan is proven or dropped.
## Why
Everyone logging into one machine (server mode, S3) does not scale past ~10
concurrent users: every pane, build and test runs on the hub. Here the compute
moves to each user's machine; the hub keeps identity, the shared accounts, and
every workspace/session/conversation/token record.
## Decisions (user, 2026-10-09)
1. Each user installs **AgentHub local** on their own machine.
2. The **full conversation** is uploaded to the hub — only for sessions started
through AgentHub.
3. Shared accounts (the Claude pool, shared AI Proxies) are usable **only through
AgentHub**. The company has at least as many seats as people; a seat may be
used by more than one person.
4. Use without AgentHub is **detected and warned about**, and **blocked when an
admin turns blocking on**.
5. The hub is reached over the company LAN at `http://10.1.13.7:4820` — **no
HTTPS**.
6. An admin sees, in one click from the Users page, which machines a user has,
which workspaces they used, and what each session did.
7. `server-s3` is kept until this architecture is settled.
## Shape
```
user machine — AgentHub LOCAL hub 10.1.13.7 — AgentHub SERVER mode
┌───────────────────────────────────┐ ┌──────────────────────────────────┐
│ pane: claude │─hooks +▶│ /api/remote/ingest (device token)│
│ ANTHROPIC_BASE_URL = hub gateway│ transcr.│ → sessions/events/tokens, owned │
│ ANTHROPIC_AUTH_TOKEN = ahdev_… │ │ by the device's user │
│ │─/v1/… ─▶│ /api/proxy-gw → pool token added │
└───────────────────────────────────┘ └──────────────────────────────────┘
```
No pool token or proxy key ever reaches a user's machine; it holds one device
token. Plain HTTP means anyone sniffing the LAN can read that token and the
conversations — accepted for an internal network. Moving to HTTPS later is a
reverse proxy in front of the hub (Caddy `tls internal`), no code change.
## What the hub can see (per tool, checked 2026-10-09)
Uploads cover every session the machine's AgentHub started, in ANY of its
workspaces — company repo or the user's own project alike. A session run
outside AgentHub on the user's own login is invisible; only pool use is gated.
| Tool | Conversation | Tokens / cost | Pool / gate | Reader today |
|---|---|---|---|---|
| Claude Code | yes | yes | yes (R3) | hooks + transcript |
| Codex | yes | yes | not yet (Codex pool = later) | `codex-usage.js` rollouts |
| Antigravity | no — needs a renderer for `transcript_full.jsonl` | yes | no (own login) | `antigravity-usage.js` |
| Oh My Pi, other catalog tools | no | no | no | none: only the pane run (workspace, time) |
| DeepSeek CLI / not in catalog | no | no | no | not launchable as a tool; a shell pane records nothing |
R2 therefore uploads Claude transcripts, Codex rollouts and agy conversation
files (`remote-sync.js` today mirrors only the first two). Any tool beyond that
needs its own reader first — one per tool, on demand.
## Gating
Verified 2026-10-09 against Claude Code 2.1.295 with a capture server: every
`/v1/messages` call carries `x-claude-code-session-id: <id>` (same id in
`metadata.user_id`), and `--session-id <uuid>` sets it — `spawnRun` already
starts Claude panes that way.
The gateway checks each request:
- bearer = a device token that is not revoked, of a user who is not disabled —
else **always 401**;
- `x-claude-code-session-id` = a session the hub received uploads for, from
THAT device, within `REMOTE_LIVE_SEC` (300 s; 60 s grace for a new session).
A request failing the second check is a session AgentHub is not uploading
(token copied out of AgentHub, or uploads switched off). Setting
`remote_gate` decides: `warn` (default) forwards it and records the session
as **unmonitored** with its token count from the response's `usage`, and
fires an alert (existing `fireAlert`); `block` refuses it with 403. Ceiling:
someone who also fakes the upload stream passes — detection, not proof.
Relay spike (2026-10-09, Claude Code 2.1.295, script kept in
`~/.cache/agenthub-spike/relay.js`): a client with NO login and
`ANTHROPIC_AUTH_TOKEN=<fake>` worked through a relay that swapped in a team
subscription's OAuth token (+ `anthropic-beta: oauth-2025-04-20`). Checked:
answers, `--resume`, a local Bash tool call, prompt cache reads on every turn
(18–26k tokens), `anthropic-ratelimit-unified-*` headers passed back. Three
findings for R3:
- Through a base URL Claude Code writes **5-minute** cache entries; set
`ENABLE_PROMPT_CACHING_1H=1` in the pane (verified: 1h entries) to keep the
subscription's 1-hour TTL.
- The cache is per ACCOUNT (a fresh client read 18k cached tokens on its first
call), so the gateway must keep a session on ONE pool account — switching
accounts mid-session re-writes the whole cache.
- A repo's `.claude/settings*.json` `env` overrides the pane's
`ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` (seen in this repo) — give the
gateway through `--settings=<file>`, the layer that outranks project settings,
as AI Proxy panes already do.
Not checked yet (interactive features that call claude.ai with the user's own
login, likely unavailable): `/usage` quota view, claude.ai MCP connectors,
Remote Control / web sessions, org plugins from claude.ai.
What is and is not guaranteed:
- **Guaranteed:** no hub account (or a disabled user / revoked device) → no
issued credential, because the credential never leaves the hub. This holds
only while the ADMIN alone holds each shared seat's login (setup-tokens
made by the admin). A seat whose login (email/SSO) an employee knows can
always be used directly at claude.ai or in a plain `claude` — AgentHub cannot
stop that.
- **Not guaranteed:** that the AgentHub APP was used. The device token sits on
a machine the user controls; `block` stops a plain `claude` using it, unless
the user also fakes the upload stream. Hard enforcement needs managed,
locked-down machines.
- **Closes the visibility gap (R3b):** the gateway sees every request body, so
it can record the conversation of every pool request ITSELF — new messages
per request by session id, plus the streamed answer and its `usage` — no
matter which client sent it. Then nothing that uses an issued credential is
invisible to the admin, AgentHub app or not.
- **Plain HTTP:** a LAN sniffer can take a device token and act as that user.
If that matters, HTTPS (reverse proxy + an internal CA every machine trusts,
`NODE_EXTRA_CA_CERTS` for Claude Code).
## Phases
Each one ships alone and changes nothing for a local-only install.
**R0 — user activity view (hub, useful today).** Users page row → a user
page: their devices (once R1 exists), workspaces, and sessions by day with
tokens/cost; a session opens the existing session page (conversation, tool
calls). Backend: an `owner=<user id>` filter for staff on `GET /api/sessions`
and `GET /api/lanes`, reusing `sessionWhere`/`laneWhere` for that user — no
new query logic. Admin reads are audited as they already are.
Verify: server test for the filter (developer cannot use it); client snapshot.
**R1 — devices (hub + CLI).** Table `devices` (id, user_id, name, token_hash,
created_at, last_seen_at, revoked_at). `agenthub remote login
http://10.1.13.7:4820` signs in with the user's hub password (throttled like
`/api/auth/login`), receives an `ahdev_` token once, stores it 0600 in the
runtime dir. The user page lists devices; an admin revokes one (re-auth +
`audit_log`).
Verify: mint/hash/revoke tests; a revoked token gets 401.
**R2 — upload (both sides).** An enrolled local AgentHub uploads each of ITS
sessions (`dashboard_runs`): hook events as they arrive, the session files of
the table above (Claude transcript, Codex rollout, agy conversation)
incrementally (byte offset, retry queue on disk, never blocking a hook). The
hub stores transcripts under `<data dir>/remote/<user id>/<device id>/projects/`
and imports them with the SAME `importFromDirectory` `remote-sync.js` uses, so
tokens and cost match local sessions. Sessions are owned by the device's user
(`source = device:<id>`); workspaces are mirrored as records (title, folder,
tool) — shown, not driven. The existing `ingest-batch` stays as it is.
Verify: two local instances → one hub; R0's page shows both users' work.
**R3 — remote gateway.** The local `spawnRun` points an enrolled machine's
Claude pane at the hub (`ANTHROPIC_BASE_URL`, device token as
`ANTHROPIC_AUTH_TOKEN`, through tmux `-e`). `proxy-gateway.js` (from
origin/main since 84e2a9f0) gains the remote path and checks above; for the pool it picks an
account with `pickAccount` and sends its OAuth token. **Spike first** on ONE
real pool account: which headers a setup-token needs through a relay.
Verify: a pane gets "pong"; a curl with the device token and a session id
nobody uploads is logged (`warn`) or 403 (`block`).
**R4 — shared skills/plugins.** No AgentHub code: the company marketplace (git)
plus a `managed-settings.json` template (`extraKnownMarketplaces`,
`enabledPlugins`) and an install script for user machines.
Codex comes after R3 works for Claude: ChatGPT-account auth differs from an API
key, and its session-id header still has to be checked.
## Open risks
- A hub outage stops every pool session; a machine can still use its own login.
- The hub stores every conversation of an AgentHub session; tell users.