chore: v2.8.15 — Oh My Pi login sharing, omp --allow-home, Markdown Preview default, first-login password

- omp-share.js: an admin's switch (S3) runs omp's own auth broker + gateway as
  the host on loopback; a member's omp pane reaches it as a scoped client keyed
  by its pane token (clients file = live omp panes only), runs on
  --model agenthub/<model>, never holds a credential.
- omp catalog: --allow-home, so a workspace at the account's home keeps its
  sessions there (they were recorded under a temp dir and dropped).
- Settings → Appearance → Open Markdown as: Preview (default) or Raw, for
  Files/Changes, the config viewer and a chat's file panel.
- First-login password change and password rules, avatar URLs, Artifacts
  "Mine" includes installed shared skills/plugins, a member's own hook pages.
This commit is contained in:
2026-10-22 10:04:30 +07:00
parent 8a6dc6a29e
commit 750d74a2af
66 changed files with 3021 additions and 696 deletions
+69 -5
View File
@@ -155,6 +155,10 @@ Workspaces made before server mode, or by the host account, have none.
- An **admin** or **analyst** sees everything. Each time one of them opens a
session a developer owns (its page, transcript, events or cost), the read is
written to the audit log: `agenthub users audit`, or `GET /api/users/audit`.
Entries are kept `AGENTHUB_AUDIT_DAYS` days (default 180; `0` keeps them
all) — older ones are deleted as new ones are written, at most once an hour.
An account's last login is kept in `users.last_login_at`, so it still shows
after the login itself has ended.
- `GET /api/users` (admin/analyst) lists every account with its workspaces,
sessions, and the tokens and $ it used in the last 7 and 30 days — the
**Administration** page in the sidebar (`/admin`; the old `/users` link
@@ -264,7 +268,22 @@ settings file that lacks the variable falls back to `USERS_ROOT` in
- A login lasts 30 days in the httpOnly, `SameSite=Strict` cookie
`agenthub_sid`. Only the token's SHA-256 is stored. Logging out, a password
change and disabling the user each end it.
- Passwords are stored as scrypt hashes; any non-empty password is accepted (no minimum length).
- Passwords are stored as scrypt hashes. Two bars:
- A password an admin **gives** someone — **Add user**, a reset on the Edit
form, `agenthub users add` — may be anything non-empty (`123` included).
The account is then flagged `must_change_password`: after signing in it
sees only a "Choose your own password" page, the server answers every
other request with `403 EPWCHANGE` (only `/auth/*`, the `/me` reads and
`POST /api/me/password` pass) and no WebSocket opens. A pane token is not
held back — agents started before a reset keep running.
- A password an account **picks for itself** (that page, Settings → Account,
an admin's own on the Edit form) must pass every rule: at least 8
characters, a lower-case and an upper-case letter, a digit, a symbol, not
containing the username, and not the current password (`400
EWEAKPASSWORD`, with the failed `rules`). The form ticks each rule as it is
typed (`client/src/lib/passwordRules.ts` mirrors `passwordProblems` in
`server/lib/auth.js`). `agenthub users passwd` and the first admin of
`agenthub server setup` set no flag.
Every login attempt counts before its password is checked, so attempts sent
in parallel count too. After 10 failed logins from one address, or 30 for one
username, in 15 minutes, further logins answer `429 ETOOMANY` until the
@@ -307,7 +326,10 @@ everywhere". Every field is checked before any is written, so a bad email or
a workspace the user does not own changes nothing. Every change asks for the admin's OWN
password again — the server refuses without it (`EREAUTH`, and `ETOOMANY`
after 10 wrong tries in 15 minutes) — an admin can never demote or disable
themselves (`ESELF`), and each change is written to the audit log. In local
themselves (`ESELF`), no path (the CLI included) may demote, disable or
delete the last enabled admin (`ELASTADMIN`), and each change is written to
the audit log. A row says **New password due** while the account still has a
password an admin gave. In local
mode these routes refuse (`ELOCALMODE`): there is no login to check.
Wherever the dashboard names a person — the sidebar's sign-out tooltip, the
@@ -335,10 +357,10 @@ The same from the host's shell:
```bash
agenthub users list
agenthub users add <name> [--role admin|analyst|developer]
agenthub users add <name> [--role admin|analyst|developer] # they choose their own password at first login
agenthub users passwd <name> # ends that user's logins
agenthub users role <name> <role>
agenthub users disable <name> # ends that user's logins; users are never deleted
agenthub users role <name> <role> # never the last enabled admin (ELASTADMIN)
agenthub users disable <name> # ends that user's logins; never the last enabled admin
agenthub users enable <name>
agenthub users assign <workspace id> <name> # give a pre-server-mode workspace an owner
agenthub users audit [--limit N] # who read which developer's session
@@ -507,6 +529,48 @@ agenthub accounts pin team-key mia --tool codex # pins work as for Claude
> **Ceiling:** a pane can read its own account's key (`$AGENTHUB_POOL_KEY`) or
> `auth.json`, like the Claude pool's token.
## Oh My Pi login sharing
With one Linux user per account, a member's omp has its own `~/.omp` and no
login. An admin can let every member's omp run on the HOST account's omp
logins instead: **Settings → AI tools → Oh My Pi login sharing** (off by
default; `PUT /api/ai-tools/omp-sharing`, audited). It uses omp's own auth
broker and gateway (`server/lib/omp-share.js`):
```bash
# as the host account: the logins to share (omp's built-in providers only)
omp login # e.g. anthropic
```
- While it is on, the dashboard runs, as the host and on loopback only,
`omp auth-broker serve --bind=127.0.0.1:4826` over the host's `~/.omp` store
and `omp auth-gateway serve --bind=127.0.0.1:4827 --clients=<data dir>/omp-share/clients.json`
(ports: `AGENTHUB_OMP_BROKER_PORT`, `AGENTHUB_OMP_GATEWAY_PORT`; logs in
`<data dir>/omp-share/`). Both tokens stay in the host's 0700 home; the
broker's reaches only the gateway's env. A crashed daemon restarts with a
back-off; the server stops both on shutdown, and kills ones a previous
server left (by pid file and argv) before starting its own.
- A member's omp pane gets one provider `agenthub` in its
`~/.omp/agent/models.yml` (written as the member; the rest of the file is
kept, a file that is not a YAML mapping is left alone), pointing at the
gateway with `apiKey: AGENTHUB_PANE_TOKEN` — the NAME of the env var holding
its pane token — and `OMP_SKIP_SETUP=1`. It runs on `--model agenthub/<model>`:
the model picked in AgentHub, else the host's own `modelRoles.default`
(`~/.omp/agent/config.yml`) — left to itself omp would take the provider's
first listed model, a retired one the API answers 404 for. The clients file lists the SHA-256
of each LIVE omp pane's token (rewritten every 15 s; the gateway re-reads it
every 10 s), so a token stops working shortly after its pane ends, and
turning sharing off empties it at once.
- A scoped client token calls models only: it cannot read a credential, the
gateway's usage or credential health, and refresh tokens never leave the
broker. Host panes use the host's login directly and get nothing new.
- A custom provider in the host's own `models.yml` (a 9Router, say) is not
shared — the gateway serves omp's built-in providers.
> **Ceiling:** an agent can SPEND through the shared logins while its pane
> runs; it cannot carry a login away. A member who edits the `agenthub`
> provider out of their `models.yml` gets it back at their next omp pane.
## One Linux user per account
Every pane of a user, an agent's shell included, runs as that user's own Linux