openvibers/openvibe.chat Commits
AGENTS.md: the context an agent starts from (#539)
Changes
nnew file mode 100644@@@ -0,0 +1,166 @@++# AGENTS.md — OpenVibe.Chat++++## Project overview++++The communication authority of the OpenVibe network: rooms, messages, DMs, group DMs, voice channels++and calls, TTS/sounds, moderation, presence, and both of Live's chat paths. It was moved out of Live++behind a service boundary (Wave 6) and has been the authority for `openvibe.live` chat in every mode++since 2026-09-23; Live runs no chat of its own. Node.js/Express + `ws`, PostgreSQL, CommonJS, **no++build step**.++++- Origin `https://openvibe.chat`; also served on **Live's origin**: nginx routes `/ws/chat`,++ `/api/chat/`, `/api/dm/`, `/api/tts/`, `/api/sounds`, `/api/emotes` (and, after the calls cutover,++ `/ws/call` and the call routes under `/api/streams`) to Chat.++- Port `4400`, unit `openvibe-chat.service`, release root `/opt/openvibe.chat`; `openvibe.party` is++ Chat's Watch Together host.++++## What it owns++++- Rooms: global, stream, channel, community, DM/group DM, call, system; servers (groups of rooms as++ text and voice channels, with roles and invites).++- Messages, replies/rich payloads, edit history, membership/roles, unread/read state, presence and++ typing (ephemeral).++- Moderation state: bans, timeouts, delete/purge, filters, slow/member-only modes, the moderation++ audit and the moderation read API Live uses (`GET /internal/moderation/*`).++- TTS/audio/soundboard/channel-sound queues with skip/clear/failure; the call signalling metadata and++ the `pending/ringing/active/ended/missed/declined/failed` lifecycle.++- The tables moved from Live: `chat_messages`, the DM tables, `tts_voice_overrides`,++ `channel_sounds`, `relay_users`, `hidden_relay_users`, `pending_ip_messages`,++ `stream_first_chats`, `moderation_actions`, plus `channel_moderators`,++ `channel_moderation_settings`, `emotes`, `user_tags`, `chat_ai_summaries`,++ `chat_timeline_events`. Live keeps no copy and reads through the internal API.++- Rooms, Vibe (the assistant), support inboxes, the website widget, feeds, `openvibe.party`, agent++ rooms and chat apps.++++**Stays in Live:** `media_requests` and `media_request_settings`. Do not grow Live's chat tables back.++++## Who calls it, and whom it calls++++- Callers: browsers on `openvibe.chat` and Live's origin; Live (internal reads, bridge and event++ consumer); Bot (chat-app bridge); Actor (asks); other services with a Network service token++ audience `openvibe.chat` and a `chat.*` capability.++- Depends on: Network (identity, SSO, service tokens, the `chat.preferences` module, VIP badges),++ Live (channel/stream context through `server/live-context.js` — the only module that talks to Live),++ Events (outbox and subscriptions), Media (emote image bytes, namespace `chat`), AI++ (`server/ai/`, off unless enabled), VIP (optional; without it nobody has a badge), Tools/Search++ where the assistant needs them.++++## Repository map++++- **Entry:** [server/index.js](server/index.js) — database, first Live sync, chat and call WebSocket++ servers, HTTP app with the Events consumer and `POST /internal/events`, background loops,++ subscription check, graceful stop (tells clients the chat is restarting).++- **App:** [server/app.js](server/app.js) — Live's guards for these routes: credentialed CORS for an++ exact origin list, `/api` rate limit, IP/network bans, `ov_banned` cookie, WS origin+IP checks.++- **Config:** [server/config.js](server/config.js); database [server/db/database.js](server/db/database.js)++ (`openDb`, migrations via `DATABASE_DIRECT_URL`).++- **Chat core:** [server/chat/](server/chat/) — `chat-server.js`, `dm.js`, `dm-routes.js`,++ `history-store.js`, `moderation-utils.js`, `word-filter.js`, `deploy-notice.js`, the T3 APIs.++- **Calls:** [server/calls/](server/calls/) — call server, lifecycle, REST routes, Live stream hooks.++- **Rooms/servers:** [server/rooms/](server/rooms/) — `rooms.js`, `servers.js`, `vibe.js`,++ `workspace.js`, parties, memory, feeds.++- **Events:** [server/events/](server/events/) — `service-outbox.js`, `consumer.js`, `subscriptions.js`.++- **Auth/net:** [server/auth/](server/auth/), [server/net/](server/net/) (service tokens, client IP).++- **Browser:** [public/web/](public/web/) — `chat.js`, `chat-shell.js`, `call.js`, `vibe.js`,++ `widget.js`, `embed.js`, `party.js`, `feed-view.js`, plus their CSS.++- **Deploy:** [deploy/systemd/openvibe-chat.service](deploy/systemd/openvibe-chat.service),++ [deploy/nginx/openvibe.chat.conf](deploy/nginx/openvibe.chat.conf),++ [deploy/nginx/openvibe.live-chat.locations.conf](deploy/nginx/openvibe.live-chat.locations.conf),++ [deploy/nginx/calls/openvibe.live-calls.locations.conf](deploy/nginx/calls/openvibe.live-calls.locations.conf).++++## Commands++++```bash++npm ci++npm run dev # NODE_ENV=development node server/index.js++npm start # node server/index.js, http://127.0.0.1:4400++npm test # test/run.js: every file in its own process, PGlite per process++npm run test:pg # same on PostgreSQL + PgBouncer + Valkey (containers)++npm test -- ws import # filter by file name; --strict makes a skip fail++npm run n-1:record # record the N-1 fixtures after a deploy++ov test # the harness's sharded run of what the change reaches++```++++## Database and migrations++++Chat's own PostgreSQL database on the host's data role (`add-service.sh chat`); development and++tests use embedded PGlite, PostgreSQL in production through PgBouncer. Migrations are++`migrations/NNNN_*.sql`, applied at boot with the owner connection (`DATABASE_DIRECT_URL`), then the++app serves through the pooled runtime role (`DATABASE_URL`, DML only). Never edit an applied++migration. The moved tables keep Live's ids and columns plus Network subject columns++(`subject_id`, `sender_subject_id`, …). `docs/parity.md` records the parity method.++++## Auth and the WebSocket rules++++- People sign in with a Network session JWT, verified against the Network key; tokens issued before a++ subject's `token_valid_after` are refused. `hbt_` API tokens are resolved through Live.++- A WebSocket upgrade authenticates with an `Authorization: Bearer` header only (bots); a browser's++ socket starts anonymous and presents its token in the first message (`join`, `join_room`,++ `auth-update`). **Neither the `ov_token` cookie nor `?token=` in the URL signs a socket in** (removed++ 2026-10-10); a token in the URL gets a `system` frame saying to use the header or `join`.++- The `ov_token` cookie only exempts staff from a network/IP ban at upgrade.++- Services use client-credentials tokens for audience `openvibe.chat`, checked per capability++ (`chat.live_bridge.write`, `chat.message.send`, `chat.presence.read`, `chat.moderation.read`, …).++- DMs go only to participants, private rooms only to members; network blocks count like DM blocks; a++ staff member reading someone else's chat is recorded as a moderation action.++++## Frontend loading model++++Browsers load plain, no-build scripts from [public/web/](public/web/) on Chat's own site; Live's++origin keeps Live's existing chat JavaScript and talks to the same sockets/APIs (the Wave 6 rule was++"move behind a service boundary before changing behaviour"). There is no SPA, bundler or transpiler.++`openvibe-shared` supplies the Frame and `/shared/*` from the pinned node_modules. The widget is a++shadow-root bubble under 3 KB compressed; embeds and `openvibe.party` are separate pages/scripts.++++## Deploy++++`sudo ovhost deploy chat` (git-checkout: fetch, fast-forward `/opt/openvibe.chat`, install on a++lockfile change, restart, wait for **`/ready`**, not `/api/ready`). Unit `openvibe-chat.service` on++`127.0.0.1:4400`, env `/etc/openvibe/chat.env`; vhosts++[deploy/nginx/openvibe.chat.conf](deploy/nginx/openvibe.chat.conf) and the Live locations include++(inside Live's `server { … }`). After a deploy, record the N-1 fixtures (`npm run n-1:record`).++Rollback is a Chat release rollback (`sudo ovhost rollback chat --to <sha>`), never a switch back to++Live. Migrations only add tables and columns.++++## Tests++++`npm test` runs each `test/*.test.js` in its own process (60 s) against one migrated PGlite database++per process (`test/helpers/pg-preload.mjs`); `npm run test:pg` uses the PostgreSQL/PgBouncer/Valkey++containers. The suite covers the WS gates and commands, DMs and privacy, history latency, moderation++and staff audit, calls and capture-on-load, TTS/audio queues, rooms/servers/Vibe, the widget/embeds,++feeds, chat apps, Events consumption, account export/deletion and the N-1 fixtures. `ov test` runs the++same.++++## Conventions++++- CommonJS; 4-space indent in the newer modules, Live's original style preserved in the moved files.++- `server/live-context.js` is the **only** module that talks to Live; do not call Live from routes.++- Errors are problem+json or the chat frames' own stable codes; never leak a DM, a staff log or a++ token into a response or a log.++- CORS and WS origins are exact origins; no wildcard for credentialed requests.++++## Pitfalls++++- **A token in the socket URL or a cookie must not sign the socket in** (C-05/C-06). Keep the++ first-message auth and the `Authorization` header for bots.++- **Chat is required in every mode.** Live's chat routes are retired; do not add a fallback to Live.++- **The CORS/WS origin list is exact.** `*.openvibe.tools` must not be honoured: a credentialed grant++ would let it read signed-in DMs.++- **`/internal/*` and `/metrics` must stay unreachable through nginx** (404 there); nginx sets the++ client address from the connection only.++- **DM privacy:** participants only, block-aware, and staff reads are audited moderation actions.++- **Bans/mutes are Chat's**, and Live reads moderation through the internal API; `media_requests`++ remains Live's.++- **Readiness is `/ready`**, not `/api/ready` — the standard deploy probe assumes the latter.++- **Do not "fix" a message by editing the store**: the Live bridge mirrors changes; use the service++ paths so events, the bridge and Search stay consistent.++- **N-1 fixtures** are part of shipping: record them after the deploy.++++## Documentation++++- [README.md](README.md) — the product, cutover, data, events, rooms, Vibe, inboxes, feeds, deploy.++- [docs/platform.md](docs/platform.md) — agent rooms and the digest Code/Chat/Actor use.++- [docs/chat-ingress.md](docs/chat-ingress.md) — `/internal/chat/*` tickets and reads.++- [docs/calls-cutover.md](docs/calls-cutover.md), [docs/parity.md](docs/parity.md),++ [docs/chat-apps.md](docs/chat-apps.md), [docs/party-pool.md](docs/party-pool.md).++- `openvibe-contracts` holds `chat.*` shapes and capabilities.nnew file mode 100644@@@ -0,0 +1 @@
new file mode 100644
new file mode 100644
@@ -0,0 +1,166 @@
@@ -0,0 +1,166 @@
+# AGENTS.md — OpenVibe.Chat
+
+## Project overview
+
+The communication authority of the OpenVibe network: rooms, messages, DMs, group DMs, voice channels
+and calls, TTS/sounds, moderation, presence, and both of Live's chat paths. It was moved out of Live
+behind a service boundary (Wave 6) and has been the authority for `openvibe.live` chat in every mode
+since 2026-09-23; Live runs no chat of its own. Node.js/Express + `ws`, PostgreSQL, CommonJS, **no
+build step**.
+
+- Origin `https://openvibe.chat`; also served on **Live's origin**: nginx routes `/ws/chat`,
+ `/api/chat/`, `/api/dm/`, `/api/tts/`, `/api/sounds`, `/api/emotes` (and, after the calls cutover,
+ `/ws/call` and the call routes under `/api/streams`) to Chat.
+- Port `4400`, unit `openvibe-chat.service`, release root `/opt/openvibe.chat`; `openvibe.party` is
+ Chat's Watch Together host.
+
+## What it owns
+
+- Rooms: global, stream, channel, community, DM/group DM, call, system; servers (groups of rooms as
+ text and voice channels, with roles and invites).
+- Messages, replies/rich payloads, edit history, membership/roles, unread/read state, presence and
+ typing (ephemeral).
+- Moderation state: bans, timeouts, delete/purge, filters, slow/member-only modes, the moderation
+ audit and the moderation read API Live uses (`GET /internal/moderation/*`).
+- TTS/audio/soundboard/channel-sound queues with skip/clear/failure; the call signalling metadata and
+ the `pending/ringing/active/ended/missed/declined/failed` lifecycle.
+- The tables moved from Live: `chat_messages`, the DM tables, `tts_voice_overrides`,
+ `channel_sounds`, `relay_users`, `hidden_relay_users`, `pending_ip_messages`,
+ `stream_first_chats`, `moderation_actions`, plus `channel_moderators`,
+ `channel_moderation_settings`, `emotes`, `user_tags`, `chat_ai_summaries`,
+ `chat_timeline_events`. Live keeps no copy and reads through the internal API.
+- Rooms, Vibe (the assistant), support inboxes, the website widget, feeds, `openvibe.party`, agent
+ rooms and chat apps.
+
+**Stays in Live:** `media_requests` and `media_request_settings`. Do not grow Live's chat tables back.
+
+## Who calls it, and whom it calls
+
+- Callers: browsers on `openvibe.chat` and Live's origin; Live (internal reads, bridge and event
+ consumer); Bot (chat-app bridge); Actor (asks); other services with a Network service token
+ audience `openvibe.chat` and a `chat.*` capability.
+- Depends on: Network (identity, SSO, service tokens, the `chat.preferences` module, VIP badges),
+ Live (channel/stream context through `server/live-context.js` — the only module that talks to Live),
+ Events (outbox and subscriptions), Media (emote image bytes, namespace `chat`), AI
+ (`server/ai/`, off unless enabled), VIP (optional; without it nobody has a badge), Tools/Search
+ where the assistant needs them.
+
+## Repository map
+
+- **Entry:** [server/index.js](server/index.js) — database, first Live sync, chat and call WebSocket
+ servers, HTTP app with the Events consumer and `POST /internal/events`, background loops,
+ subscription check, graceful stop (tells clients the chat is restarting).
+- **App:** [server/app.js](server/app.js) — Live's guards for these routes: credentialed CORS for an
+ exact origin list, `/api` rate limit, IP/network bans, `ov_banned` cookie, WS origin+IP checks.
+- **Config:** [server/config.js](server/config.js); database [server/db/database.js](server/db/database.js)
+ (`openDb`, migrations via `DATABASE_DIRECT_URL`).
+- **Chat core:** [server/chat/](server/chat/) — `chat-server.js`, `dm.js`, `dm-routes.js`,
+ `history-store.js`, `moderation-utils.js`, `word-filter.js`, `deploy-notice.js`, the T3 APIs.
+- **Calls:** [server/calls/](server/calls/) — call server, lifecycle, REST routes, Live stream hooks.
+- **Rooms/servers:** [server/rooms/](server/rooms/) — `rooms.js`, `servers.js`, `vibe.js`,
+ `workspace.js`, parties, memory, feeds.
+- **Events:** [server/events/](server/events/) — `service-outbox.js`, `consumer.js`, `subscriptions.js`.
+- **Auth/net:** [server/auth/](server/auth/), [server/net/](server/net/) (service tokens, client IP).
+- **Browser:** [public/web/](public/web/) — `chat.js`, `chat-shell.js`, `call.js`, `vibe.js`,
+ `widget.js`, `embed.js`, `party.js`, `feed-view.js`, plus their CSS.
+- **Deploy:** [deploy/systemd/openvibe-chat.service](deploy/systemd/openvibe-chat.service),
+ [deploy/nginx/openvibe.chat.conf](deploy/nginx/openvibe.chat.conf),
+ [deploy/nginx/openvibe.live-chat.locations.conf](deploy/nginx/openvibe.live-chat.locations.conf),
+ [deploy/nginx/calls/openvibe.live-calls.locations.conf](deploy/nginx/calls/openvibe.live-calls.locations.conf).
+
+## Commands
+
+```bash
+npm ci
+npm run dev # NODE_ENV=development node server/index.js
+npm start # node server/index.js, http://127.0.0.1:4400
+npm test # test/run.js: every file in its own process, PGlite per process
+npm run test:pg # same on PostgreSQL + PgBouncer + Valkey (containers)
+npm test -- ws import # filter by file name; --strict makes a skip fail
+npm run n-1:record # record the N-1 fixtures after a deploy
+ov test # the harness's sharded run of what the change reaches
+```
+
+## Database and migrations
+
+Chat's own PostgreSQL database on the host's data role (`add-service.sh chat`); development and
+tests use embedded PGlite, PostgreSQL in production through PgBouncer. Migrations are
+`migrations/NNNN_*.sql`, applied at boot with the owner connection (`DATABASE_DIRECT_URL`), then the
+app serves through the pooled runtime role (`DATABASE_URL`, DML only). Never edit an applied
+migration. The moved tables keep Live's ids and columns plus Network subject columns
+(`subject_id`, `sender_subject_id`, …). `docs/parity.md` records the parity method.
+
+## Auth and the WebSocket rules
+
+- People sign in with a Network session JWT, verified against the Network key; tokens issued before a
+ subject's `token_valid_after` are refused. `hbt_` API tokens are resolved through Live.
+- A WebSocket upgrade authenticates with an `Authorization: Bearer` header only (bots); a browser's
+ socket starts anonymous and presents its token in the first message (`join`, `join_room`,
+ `auth-update`). **Neither the `ov_token` cookie nor `?token=` in the URL signs a socket in** (removed
+ 2026-10-10); a token in the URL gets a `system` frame saying to use the header or `join`.
+- The `ov_token` cookie only exempts staff from a network/IP ban at upgrade.
+- Services use client-credentials tokens for audience `openvibe.chat`, checked per capability
+ (`chat.live_bridge.write`, `chat.message.send`, `chat.presence.read`, `chat.moderation.read`, …).
+- DMs go only to participants, private rooms only to members; network blocks count like DM blocks; a
+ staff member reading someone else's chat is recorded as a moderation action.
+
+## Frontend loading model
+
+Browsers load plain, no-build scripts from [public/web/](public/web/) on Chat's own site; Live's
+origin keeps Live's existing chat JavaScript and talks to the same sockets/APIs (the Wave 6 rule was
+"move behind a service boundary before changing behaviour"). There is no SPA, bundler or transpiler.
+`openvibe-shared` supplies the Frame and `/shared/*` from the pinned node_modules. The widget is a
+shadow-root bubble under 3 KB compressed; embeds and `openvibe.party` are separate pages/scripts.
+
+## Deploy
+
+`sudo ovhost deploy chat` (git-checkout: fetch, fast-forward `/opt/openvibe.chat`, install on a
+lockfile change, restart, wait for **`/ready`**, not `/api/ready`). Unit `openvibe-chat.service` on
+`127.0.0.1:4400`, env `/etc/openvibe/chat.env`; vhosts
+[deploy/nginx/openvibe.chat.conf](deploy/nginx/openvibe.chat.conf) and the Live locations include
+(inside Live's `server { … }`). After a deploy, record the N-1 fixtures (`npm run n-1:record`).
+Rollback is a Chat release rollback (`sudo ovhost rollback chat --to <sha>`), never a switch back to
+Live. Migrations only add tables and columns.
+
+## Tests
+
+`npm test` runs each `test/*.test.js` in its own process (60 s) against one migrated PGlite database
+per process (`test/helpers/pg-preload.mjs`); `npm run test:pg` uses the PostgreSQL/PgBouncer/Valkey
+containers. The suite covers the WS gates and commands, DMs and privacy, history latency, moderation
+and staff audit, calls and capture-on-load, TTS/audio queues, rooms/servers/Vibe, the widget/embeds,
+feeds, chat apps, Events consumption, account export/deletion and the N-1 fixtures. `ov test` runs the
+same.
+
+## Conventions
+
+- CommonJS; 4-space indent in the newer modules, Live's original style preserved in the moved files.
+- `server/live-context.js` is the **only** module that talks to Live; do not call Live from routes.
+- Errors are problem+json or the chat frames' own stable codes; never leak a DM, a staff log or a
+ token into a response or a log.
+- CORS and WS origins are exact origins; no wildcard for credentialed requests.
+
+## Pitfalls
+
+- **A token in the socket URL or a cookie must not sign the socket in** (C-05/C-06). Keep the
+ first-message auth and the `Authorization` header for bots.
+- **Chat is required in every mode.** Live's chat routes are retired; do not add a fallback to Live.
+- **The CORS/WS origin list is exact.** `*.openvibe.tools` must not be honoured: a credentialed grant
+ would let it read signed-in DMs.
+- **`/internal/*` and `/metrics` must stay unreachable through nginx** (404 there); nginx sets the
+ client address from the connection only.
+- **DM privacy:** participants only, block-aware, and staff reads are audited moderation actions.
+- **Bans/mutes are Chat's**, and Live reads moderation through the internal API; `media_requests`
+ remains Live's.
+- **Readiness is `/ready`**, not `/api/ready` — the standard deploy probe assumes the latter.
+- **Do not "fix" a message by editing the store**: the Live bridge mirrors changes; use the service
+ paths so events, the bridge and Search stay consistent.
+- **N-1 fixtures** are part of shipping: record them after the deploy.
+
+## Documentation
+
+- [README.md](README.md) — the product, cutover, data, events, rooms, Vibe, inboxes, feeds, deploy.
+- [docs/platform.md](docs/platform.md) — agent rooms and the digest Code/Chat/Actor use.
+- [docs/chat-ingress.md](docs/chat-ingress.md) — `/internal/chat/*` tickets and reads.
+- [docs/calls-cutover.md](docs/calls-cutover.md), [docs/parity.md](docs/parity.md),
+ [docs/chat-apps.md](docs/chat-apps.md), [docs/party-pool.md](docs/party-pool.md).
+- `openvibe-contracts` holds `chat.*` shapes and capabilities.
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@