Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ post/edit the reply in the thread (degraded to the surface's capabilities) → u
the changed keys), `dead-fields.js` (retired fields stripped on every write).
- `src/db/` — `index.js` (the one lazy `node:sqlite` connection: WAL, `busy_timeout`,
`foreign_keys`, migrations on open, the one-time legacy JSON import behind `_meta` flags),
`migrations.js` (versioned on `PRAGMA user_version`, currently 24 — append, never edit),
`migrations.js` (versioned on `PRAGMA user_version`, currently 25 — append, never edit),
`import-legacy.js`, `fts.js` (the optional FTS5 `channel_memory_fts` index; without FTS5 memory
search degrades to a scan).
- `src/gateway/run.js` — the run orchestrator: engine adapter selection and precedence (per-run
Expand Down Expand Up @@ -169,6 +169,11 @@ post/edit the reply in the thread (degraded to the surface's capabilities) → u
stays available to every operator): one durable transaction and lock, a detached built-ins-only
runner (preflight, smoke, snapshot, install → restart → verify, automatic rollback), results on
`/api/health`; `restart_gateway` drains turns, jobs, API runs and update transactions first.
- `src/gateway/system-health.js` + `system-health-collect.js` + `system-health-store.js` —
daemon-side read-only Linux metrics, five-second samples, minute SQLite resource aggregates
(30 days), storage history (186 days, hourly after 30 days), hardware snapshots/change events,
and a data-dependent capacity forecast. Admin routes in `src/web/routes/system-health.js`,
browser page in `public/admin-system-health.js`, isolated canary in `scripts/system-health-soak.mjs`.
- Smaller gateway modules: `sessions.js` + `thread-engine.js` (thread key ↔ engine session,
per-thread pins), `session-adopt.js` + `session-carry.js` (`/resume` of a local session whose
cwd is this channel; host → container carry-over), `stopped-turns.js`, `active-runs.js`
Expand Down Expand Up @@ -307,7 +312,8 @@ through the control MCP.
`skill_revision_files`, `skill_sources`, `skill_templates`, `skill_usage`, `skill_proposals`,
`skill_access_tokens`); Composio SDK (`composio_sessions`); licensing (`license_usage`);
dashboard data (`usage`, `usage_components`, `usage_requests`, `usage_repair_batches`,
`events` — typed, indexed columns for day/week/month/channel/user rollups); plus `_meta`
`events` — typed, indexed columns for day/week/month/channel/user rollups); system telemetry
(`system_health_resources`, `system_health_storage`, `system_health_hardware`, `system_health_changes`); plus `_meta`
(key/value, created at open) and the optional FTS5 `channel_memory_fts`. Config-shaped rows
(`channel_meta`, `users`, `bg_jobs`) keep their full record in a JSON `data` blob so every field
survives without a migration; `dead-fields.js` strips retired fields on write.
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog — ChannelGate

- Add System health to the admin UI with real CPU/RAM/load and storage metrics, historical
charts and peaks, storage warnings and capacity estimates, and refreshed hardware inventory.

- Fix MCP configuration selection when a plugin package supplies both engine manifests. Each
engine uses its own inline declarations or referenced files, avoiding wrong endpoints and
duplicate server errors.
Expand Down
17 changes: 17 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# ChannelGate — Features

## System health

The last admin navigation item, **System health** (`/system-health`), shows daemon-side Linux
CPU, RAM, swap, filesystem capacity and system load. The collector runs every five seconds
independently of open browsers; the page supports pause, refresh and Live/1h/24h/7d/30d ranges.
Minute aggregates retain resource averages and peaks for 30 days. Storage history lasts 186 days,
with older samples reduced to hourly aggregates. The capacity forecast reports insufficient
history until an observed trend supports it; no historical points are generated to fill gaps.
Storage warnings start at 85%, critical at 95%. Peaks and collection status precede hardware.

Hardware inventory refreshes at startup, every five minutes and on manual refresh, with a bounded
change log. Unavailable hardware fields stay unknown; temperatures, serial numbers, credentials,
and hardware-management writes are excluded. Metrics use the filesystem containing the gateway
runtime root, and the collector runs in the host daemon without widening any engine container's
mounts. All metrics endpoints require the existing admin session. See `docs/OPERATIONS.md` and
the engine-independent System health acceptance gates in `TEST-PLAN.md`.

## Plugin packages through skill sources

Git and folder skill sources recognize Claude/Codex plugin manifests and store a complete package
Expand Down
42 changes: 42 additions & 0 deletions TEST-PLAN.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
# ChannelGate — Test Plan

## System health — engine-independent acceptance

These cases exercise the daemon collector and authenticated browser, not an engine turn;
Claude/Codex selection cannot affect collection, authorization or rendering.
Automated regressions: `test/system-health.test.js`, `test/system-health-api.test.js`,
`test/admin-system-health.test.js`, and the admin navigation suite.

- **SYS-01 — live and historical metrics.** Start a disposable Linux admin instance with an empty
runtime root and an admin password. Open `/system-health` after login. CPU's first delta may be
unknown; within two samples require current CPU/RAM/load and filesystem capacity from that
instance. Compare RAM against `/proc/meminfo`, load against `/proc/loadavg`, and capacity against
the runtime root's filesystem. Wait across a minute boundary; reload and restart the fixture.
Require persisted history and real peaks, no invented pre-install points. Select every range,
pause/resume, manually refresh, leave the page and hide the tab. Network evidence must show
five-second polling only while active, visible and unpaused, without overlapping/stale rendering.
- **SYS-02 — history, retention and forecast.** Use the deterministic collector test fixtures
with data spanning at least 187 days and a capacity change. Require resources older than 30 days
purged, storage retained for 186 days with old minute samples downsampled, bounded chart responses
and preserved peaks. Empty/short history must not predict a date. A changed filesystem capacity
must not create a false growth forecast; missing samples must remain gaps.
- **SYS-03 — hardware, error and responsive states.** In a disposable fixture, change synthetic
memory/disk inventory, advance the five-minute clock, then manually refresh. Require new values
and one change event, no duplicate event for an unchanged snapshot. Missing DMI/device files
must appear unavailable. In Chromium at 1440px and 430px verify cards, keyboard chart tooltips,
ranges, warning/critical states and peaks/Collection before hardware, without horizontal page
overflow. Simulate a failed request: require a visible error/stale state and successful retry.
- **SYS-04 — boundary.** With a password absent, expect 403 for all metrics routes; with a password
configured but no session, expect 401, also for a valid run-API key. A logged-in admin can read
metrics; manual refresh requires the CSRF header and allowed Origin. Public `/api/health` must
not expose metrics/hardware. No endpoint accepts arbitrary filesystem paths or shell commands.
- **SYS-05 — 24-hour host canary (release/deployment gate).** Run the isolated soak procedure in
`docs/OPERATIONS.md` on the target Linux host for at least 24 hours. Record candidate commit,
actual elapsed time, collector errors, CPU, RSS, database plus WAL size, persisted sample count,
gaps and hardware visibility. The original design's CPU/RAM/I/O figures are estimates, not pass
evidence. Require no collection/persistence errors, retained samples across restart, bounded
memory/history, and metrics storage below the 100 MiB safety budget. Review measured overhead
before enabling on the served instance. After activation, verify authenticated page and new
samples on the real daemon. A shorter container smoke is not a host soak pass.

Private deployment QA records and the 24-hour host canary must be recorded separately; an
unexecuted or unavailable check is not passing evidence.

## Plugin source packages — acceptance gates

Automated coverage: `test/skills-plugin-import.test.js`, `test/plugin-runtime.test.js`,
Expand Down
51 changes: 51 additions & 0 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,57 @@ groups) so interrupted execution is recorded for reconciliation on the next boot
turn may be recorded as a plain error before orderly state persistence. Existing installs: add
`KillMode=mixed` under `[Service]` and `systemctl --user daemon-reload`.

## System health

Open **System health**, the last admin navigation item (`/system-health`). The host daemon reads
Linux `/proc` and `/sys` without elevated privileges or subprocesses. It samples every five
seconds even when no browser is open; browsers poll while visible and active. Pause affects only
the page. Refresh also rescans hardware. CPU uses counter deltas (the first sample is unknown),
RAM uses `MemAvailable`, and load is the Linux 1/5/15-minute load average. Storage measures the
filesystem containing the configured gateway runtime root, not the sum of disks in the hardware
inventory. Used bytes exclude free blocks; available bytes exclude filesystem-reserved blocks;
the usage percentage is used / (used + available), as for an unprivileged process.
References: [Linux proc documentation](https://www.kernel.org/doc/html/v6.2/filesystems/proc.html)
and [Node filesystem statistics](https://nodejs.org/docs/latest-v22.x/api/fs.html#class-fsstatfs).

Resource minute averages and peaks stay for 30 days. Storage stays for 186 days (at least six
calendar months); samples older than 30 days are reduced to hourly observations with preserved
peaks. Responses reduce long resource series to about 240 buckets and storage to daily points.
Empty history and missing metrics are reported without simulated values. Forecasts require at
least seven days of measured history and use only the segment since the last observed capacity
change. The estimate assumes continued growth at the fitted rate, not a guarantee of free space.

Hardware is read at startup, every five minutes and on refresh. The current snapshot and at most
100 change events are retained; no temperatures, serial numbers, MAC addresses or machine IDs are
collected. DIMM information and some device details may be unavailable without host interfaces;
the page reports unknowns. RAM/disks recognized by Linux appear at the next scan. Distribution
changes appear on rescan; a newly installed kernel appears only after booting it. Hardware details
remain behind the admin session and never appear on the public liveness endpoint.

Metrics use tables in the existing SQLite database. Retention applies only to these tables,
not audit/usage records. Do not impose a 100 MiB limit on the entire gateway database or force
global WAL truncation: other gateway records and readers share it. Measure incremental metrics
storage using an isolated canary, with 100 MiB for its database plus WAL as a safety budget.

Before enabling on a served host, run the canary **on that Linux host** from the tested checkout:

```sh
node scripts/system-health-soak.mjs --output /absolute/new-health-soak-report.json --storage-path /path/to/gateway-runtime-root
```

It runs in the foreground for 24 hours, reads the target filesystem, and writes metrics only to
a fresh temporary database. The JSON report updates every 30 seconds and records actual elapsed
time, errors, gaps, CPU, peak RSS, database plus WAL bytes, hardware visibility and persistence
after reopening. The report path must be new; the scratch database is removed at completion.
Use the service manager or a daemon-owned job to keep it alive when a session cannot remain open.
`--duration-seconds 65` is a quick smoke; it explicitly cannot pass the 24-hour gate. A report
with `containerDetected: true` describes the container's visible interfaces and is not evidence
of a host deployment. Even when false, independently verify where the process ran.

Review the measured overhead rather than treating the prototype estimates as guarantees. After
the host canary passes, use the normal serialized beta landing and safe restart procedure, log in,
and verify current samples, hardware visibility and history persistence on the actual daemon.

## Retention and log rotation

Run `npm run maintenance` daily from the service manager. `CG_RETENTION_DAYS` defaults to 30 and
Expand Down
1 change: 1 addition & 0 deletions public/admin-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const ADMIN_VIEWS = Object.freeze({
skills: Object.freeze({ path: "/skills", title: "Skills" }),
api: Object.freeze({ path: "/api-docs", title: "HTTP run API" }),
settings: Object.freeze({ path: "/settings", title: "Settings" }),
"system-health": Object.freeze({ path: "/system-health", title: "System health" }),
});

export const ADMIN_VIEW_PATHS = Object.freeze(Object.values(ADMIN_VIEWS).map(({ path }) => path));
Expand Down
Loading
Loading