# `@zephyrcloudio/harness-server`

Runtime-neutral Fetch HTTP handling for Ze Harness.

`createServerHandler` exposes injected `SessionServices` through validated HTTP routes and SSE.

```ts
import { createServerHandler } from '@zephyrcloudio/harness-server';
```

The Server does not choose a storage implementation or import Node.js. Node config discovery, trusted
plugin loading, and loopback hosting live in `@zephyrcloudio/harness-server-node`.

Long-lived event streams stay observable while nothing changes. A caught-up Session stream repeats
its `session.cursor`, a server stream repeats its `server.cursor`, and a Turn stream sends
`turn.heartbeat` naming the revision it already sent, at least every 15 seconds. All come from an
idle re-read of activity, which also recovers a missed wake.

The SSE projections are exported for embedding hosts that serve them outside the Fetch handler:
`turnEventStream`, `sessionEventStream`, and `serverEventStream`. The server stream wraps
`SessionServices.observation.watchServerActivity` and projects Turn rows through the same
functions as the Session stream, with one running-Turn memory per Session. A Session's first event
on a stream is always `session.created`: a Session row is `session.created` the first time the
stream sees that Session and `session.title` afterwards (storage stamps Session rows only on
creation and title changes), and a Turn row for a Session the stream has not announced yet is
preceded by a `session.created` read from the current Session, stamped one cursor before the
Turn's so a resume between the two replays the Turn. A stream
resumed after an existing Session was retitled therefore repeats `session.created`; consumers
upsert by `session.id`.

`HarnessServerOptions` accepts an optional `providerAuthority` service to serve model discovery and
provider connection management routes over the versioned v1 protocol. The
`/v1/runtime/models/events` and `/v1/servers/:server/sessions/:sessionId/models/events` routes are single-shot
current-revision checks, not long-lived subscriptions: each emits one revision event and closes.
Clients re-fetch the model snapshot endpoints for updates.

The portable Streamable HTTP MCP factory in `./mcp-host` requires `McpHostBackgroundWork`. This host
port keeps an abort-ignoring fetch and any late response-body cleanup owned after the transport scope
closes. Node and Worker packages supply their platform-specific implementations.

MCP catalog pages validate their standard fields and accept additional server metadata.
A server may omit `resources/list`, `resources/templates/list`, or `prompts/list`:
a method-not-found response to the first page of one of those optional lists is
treated as an empty catalog for that list. Other request errors, `tools/list`
failures, and malformed catalog fields still fail discovery.

## Dashboard

`createServerHandler({ dashboard: { server } })` serves the dependency-free dashboard
(`src/dashboard.html`, inlined at build time by `build/dashboard-asset.mjs`) at exactly
`GET /dashboard`, before routing and without authentication. The Node daemon and
`WorkerHarnessHost` both serve this one asset. The page is a single self-contained HTML
document that does not assume its host:

- **Server identity.** The handler replaces `%ZE_HARNESS_SERVER%` in
  `<meta name="ze-harness-server">` with `dashboard.server`. A page served without a valid
  name shows an error instead of guessing.
- **Relative requests.** Every call is document-relative (`v1/servers/<server>/…`,
  `v1/logs`), so the page works under any path prefix; at `/dashboard` they resolve to `/v1/…`.
- **Login only when needed.** On load the page lists Sessions with the tab's stored bearer
  token, or none, and shows the token form only on `401`. A host that vouches for its caller
  never shows it, and the sign-out chip appears only while a token is in use. When storage is
  unavailable (an opaque-origin frame) the token is kept in memory only.
- **Optional logs.** The log panel hides once `v1/logs` answers `404` or `503`.
- **Headers.** `content-security-policy: default-src 'none'; style-src 'unsafe-inline';
script-src 'sha256-<page script>'; connect-src 'self'; base-uri 'none'; form-action 'none'`.
  The script hash is computed at build time, so an embedder that appends its own script
  hashes to `script-src` (CEF does for injected scripts) keeps the page's script running.
  There is no `frame-ancestors` or `X-Frame-Options`, so any parent may frame the page. The
  page's API calls must be same-origin with it (`connect-src 'self'`). An embedder serving it
  from a custom URL scheme (such as `tap-harness://localhost`) must register that scheme so
  its pages get a scheme/host/port origin; a page with an opaque origin has no `'self'` to
  match.

### Theme

Every color, font, type size, weight, radius, shadow, and spacing value is a `--zh-*` custom
property on `:root`; the defaults are the built-in dark theme. The page has one color scheme
(dark) and takes no scheme parameter: an embedder restyles it, including a light palette and
`--zh-color-scheme: light`, through tokens. An embedding parent window posts:

```ts
iframe.contentWindow.postMessage(
  { type: 'ze-harness:theme', tokens: { '--zh-bg': '#ffffff', '--zh-text': '#1f2328' } },
  '*',
);
```

The page applies only messages from its parent window, and only entries whose key is one of
the tokens below and whose value is a string; everything else is ignored. The message carries
theme, never data. Post it after the frame's `load` event; later messages override earlier
ones.

| Group     | Tokens                                                                                                                                                       |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Scheme    | `--zh-color-scheme` (`dark`)                                                                                                                                 |
| Colors    | `--zh-bg`, `--zh-panel`, `--zh-inset`, `--zh-border`, `--zh-text`, `--zh-dim`, `--zh-faint`, `--zh-accent`, `--zh-ok`, `--zh-warn`, `--zh-bad`, `--zh-steer` |
| Fonts     | `--zh-font-sans`, `--zh-font-mono`                                                                                                                           |
| Type size | `--zh-text-10`, `-10-5`, `-11`, `-11-5`, `-12`, `-12-5`, `-13`, `-14`, `-15`, `-16`, `-17`                                                                   |
| Type      | `--zh-weight-normal`, `--zh-weight-medium`, `--zh-weight-semibold`, `--zh-leading`, `--zh-leading-prose`, `--zh-tracking-label`                              |
| Radius    | `--zh-radius-2`, `-3`, `-4`, `-5`, `-6`, `-8`, `--zh-radius-full`                                                                                            |
| Shadow    | `--zh-shadow-card`, `--zh-shadow-focus`, `--zh-shadow-focus-thin`                                                                                            |
| Spacing   | `--zh-space-1`, `-2`, `-3`, `-4`, `-5`, `-6`, `-7`, `-8`, `-9`, `-10`, `-12`, `-14`, `-16`, `-18`, `-20`, `-24`, `-28`, `-32`, `-44`, `-80`                  |

Numbered type size, radius, and spacing tokens are named for their default pixel value
(`--zh-text-10-5` is `10.5px`). Tinted surfaces (selection, pills, error banners, focus ring)
mix the color tokens, so they follow a recolored palette.


## Public TypeScript exports

- `AddSessionPluginAdminInput`
- `bearerUnauthorizedResponse`
- `ClientWorkdirResolver`
- `createServerHandler`
- `DaemonLogServiceApi`
- `DashboardOptions`
- `HarnessServer`
- `HarnessServerOptions`
- `isDashboardRequest`
- `makeLazySessionPluginAdmin`
- `makeSessionPluginAdmin`
- `ProviderModelRefresher`
- `publicSessionPluginInventory`
- `RefreshSessionPluginAdminInput`
- `RuntimeAdmin`
- `RuntimeAdminUnavailable`
- `serverEventStream`
- `ServerEventStreamInput`
- `SessionCatalogAdoptionStore`
- `sessionEventStream`
- `SessionEventStreamInput`
- `SessionPluginActivationValue`
- `SessionPluginAdmin`
- `SessionPluginAdminConflict`
- `SessionPluginAdminFailure`
- `SessionPluginAdminInvalid`
- `SessionPluginAdminStorageFailure`
- `SessionPluginAdminUnavailable`
- `SessionPluginRevisionValue`
- `SessionPluginsStatusValue`
- `SessionProjectResolver`
- `turnEventStream`
- `TurnEventStreamInput`
- `verifyBearerRequest`

---

Generated from [`packages/server/README.md`](https://github.com/ZephyrCloudIO/ze-harness/blob/main/packages/server/README.md).
