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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user