Merge remote-tracking branch 'origin/main' into pr48275-rebase

# Conflicts:
#	cron/scheduler.py
This commit is contained in:
teknium1 2026-06-19 07:40:29 -07:00
commit a58287afcb
No known key found for this signature in database
162 changed files with 8521 additions and 634 deletions

View file

@ -743,7 +743,7 @@ Upload a debug report (system info + recent logs) to a paste service and get a s
| `--expire <days>` | Paste expiry in days (default: 7). |
| `--local` | Print the report locally instead of uploading. |
The report includes system info (OS, Python version, Hermes version), recent agent and gateway logs (512 KB limit per file), and redacted API key status. Keys are always redacted — no secrets are uploaded.
The report includes system info (OS, Python version, Hermes version), recent agent, gateway, GUI/dashboard, and desktop logs (512 KB limit per file), and redacted API key status. Keys are always redacted — no secrets are uploaded.
Paste services tried in order: paste.rs, dpaste.com.

View file

@ -119,6 +119,8 @@ The **Chat** tab embeds the full Hermes TUI (the same interface you get from `he
**Resume an existing session:** from the **Sessions** tab, click the play icon (▶) next to any session. That jumps to `/chat?resume=<id>` and launches the TUI with `--resume`, loading the full history.
**Session switcher (right rail):** the Chat tab carries its own ChatGPT-style conversation list in a thin right rail beside the terminal, so you can swap conversations without leaving the page. The rail stacks the model picker on top and the session list directly below it; the terminal takes up most of the screen. The list shows your most recent sessions for the active profile — title (falling back to a message preview), relative last-active time, message count, and the source channel for non-CLI sessions. Click any row to resume it in place (the terminal respawns with that conversation's history); the active session is highlighted. **New chat** starts a fresh session, and a refresh control re-pulls the list. The rail is read-only for switching — delete, rename, export, and bulk cleanup still live on the **Sessions** tab. On narrow screens it folds into a slide-over panel.
**Prerequisites:**
- Node.js (same requirement as `hermes --tui`; the TUI bundle is built on first launch)

View file

@ -56,6 +56,139 @@ research gateway start
That's it — three independent agents, each on its own process, restarting
automatically on crash and on user login.
## Alternative: one gateway for all profiles (multiplexing)
The model above runs **one process per profile**. That is the default and is
the right choice for most setups. But on a host with many profiles — or a
container deployment where one process per profile is operationally heavy — you
can instead run a **single multiplexing gateway**: the default profile's gateway
becomes the sole inbound process and serves messages for *every* profile on the
box.
This is **opt-in** and **off by default**. When it's off, nothing on this page
changes — every behavior below is inert.
### When to prefer multiplexing
- A container/VPS deployment where N supervisor units, N ports, and N PID files
are a burden.
- Many low-traffic profiles that don't each justify a full process.
- You want a single thing to start, monitor, and restart.
Stick with one-process-per-profile when you want hard process-level isolation
between profiles (separate memory footprints, independent crash domains, the
ability to restart one profile without touching the others).
### How to opt in
Set the flag on the **default profile** (it owns the multiplexer) and restart
its gateway:
```bash
hermes config set gateway.multiplex_profiles true
hermes gateway restart
```
Equivalently, in the default profile's `~/.hermes/config.yaml`:
```yaml
gateway:
multiplex_profiles: true
```
(The flag is also accepted as a top-level `multiplex_profiles: true` for
convenience.) On the next start the default gateway enumerates every profile,
brings up each profile's enabled platforms under that profile's own
credentials, and routes each inbound message to the profile it belongs to. Each
turn resolves the routed profile's config, skills, memory, SOUL, **and provider
keys** — credentials are never shared across profiles.
You do **not** run `hermes gateway start` for the secondary profiles — the
default gateway serves them. See the contract changes below.
### What changes when multiplexing is on
Enabling the flag changes how a few things behave. All of these revert the
moment the flag is off.
#### 1. Secondary profiles must not start their own gateway
With a multiplexer running, a named-profile `hermes gateway start` / `run` is a
**hard error**, pointing you back at the multiplexer:
```
The default gateway is running as a profile multiplexer and already serves
profile 'coder'. ...
```
The multiplexer is the single inbound process; a second profile gateway would
double-bind that profile's platforms. Pass `--force` only if you deliberately
want a separate process for that profile (not recommended while the multiplexer
is running). The cross-profile lifecycle wrapper script earlier on this page is
therefore **not** used in multiplex mode — you only manage the default gateway.
#### 2. HTTP-inbound platforms are reached via a `/p/<profile>/` URL prefix
Webhook (and other HTTP-inbound) traffic for a secondary profile arrives on the
default listener under a profile prefix, **not** a second port:
```
# default profile
POST http://host:8644/webhooks/<route>
# the "coder" profile, same listener
POST http://host:8644/p/coder/webhooks/<route>
```
An unknown or unconfigured profile in the prefix returns `404`. Because the one
shared listener already serves every profile this way, a **secondary profile
must not enable a port-binding platform itself** — doing so is a config error
and the gateway refuses to start, naming the profile and platform:
```
Profile 'coder' enables the port-binding platform 'webhook', but
gateway.multiplex_profiles is on. ... Remove platforms.webhook from profile
'coder's config.yaml (configure it only on the default profile).
```
Port-binding platforms covered by this rule: `webhook`, `api_server`,
`msgraph_webhook`, `feishu`, `wecom_callback`, `bluebubbles`, `sms`. Configure
any of these **only on the default profile**; every profile is reachable through
its `/p/<profile>/` prefix.
#### 3. Per-credential platforms still need their own token per profile
Polling/connection platforms (Telegram, Discord, Slack, Matrix, Signal, …) work
fine multiplexed, but each profile that enables one must supply its **own** bot
token — the same token cannot be polled by two profiles at once. If two profiles
configure the same `(platform, token)`, startup fails fast naming both profiles
(see [Token-conflict safety](#token-conflict-safety) — the rule is unchanged,
it's just enforced inside the one process now).
#### 4. Session keys are namespaced by profile
Each profile's sessions live under an `agent:<profile>:…` namespace so two
profiles on the same platform/chat never collide in the shared session store.
The **default** profile keeps the historical `agent:main:…` namespace
byte-for-byte, so existing default-profile sessions are unaffected — no
migration, no orphaned history.
#### 5. One PID/lock and one status surface
There is a single process-level PID and lock (the multiplexer, under the default
home). `hermes status` reports the multiplexer and the profiles it serves;
`hermes status -p <name>` slices to one profile. Each profile still writes its
own `runtime_status.json` under its own home, so existing per-profile readers
keep working.
#### What does **not** change
Per-profile `.env` credential isolation is preserved and, if anything,
stricter: a profile's keys are resolved from its own scope and are never unioned
into a shared environment (this also means subprocesses like MCP servers and
Kanban workers only ever see their own profile's secrets). Kanban,
profile-scoped skills/memory/SOUL, and model routing all behave per-profile
exactly as they do with separate gateways.
## Start, stop, or restart all gateways at once
The CLI ships with single-profile lifecycle commands. To act across every