openvibers/openvibe.chat AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Chat |
| 2 | |
| 3 | ## Project overview |
| 4 | |
| 5 | The communication authority of the OpenVibe network: rooms, messages, DMs, group DMs, voice channels |
| 6 | and calls, TTS/sounds, moderation, presence, and both of Live's chat paths. It was moved out of Live |
| 7 | behind a service boundary (Wave 6) and has been the authority for `openvibe.live` chat in every mode |
| 8 | since 2026-09-23; Live runs no chat of its own. Node.js/Express + `ws`, PostgreSQL, CommonJS, **no |
| 9 | build step**. |
| 10 | |
| 11 | - Origin `https://openvibe.chat`; also served on **Live's origin**: nginx routes `/ws/chat`, |
| 12 | `/api/chat/`, `/api/dm/`, `/api/tts/`, `/api/sounds`, `/api/emotes` (and, after the calls cutover, |
| 13 | `/ws/call` and the call routes under `/api/streams`) to Chat. |
| 14 | - Port `4400`, unit `openvibe-chat.service`, release root `/opt/openvibe.chat`; `openvibe.party` is |
| 15 | Chat's Watch Together host. |
| 16 | |
| 17 | ## What it owns |
| 18 | |
| 19 | - Rooms: global, stream, channel, community, DM/group DM, call, system; servers (groups of rooms as |
| 20 | text and voice channels, with roles and invites). |
| 21 | - Messages, replies/rich payloads, edit history, membership/roles, unread/read state, presence and |
| 22 | typing (ephemeral). |
| 23 | - Moderation state: bans, timeouts, delete/purge, filters, slow/member-only modes, the moderation |
| 24 | audit and the moderation read API Live uses (`GET /internal/moderation/*`). |
| 25 | - TTS/audio/soundboard/channel-sound queues with skip/clear/failure; the call signalling metadata and |
| 26 | the `pending/ringing/active/ended/missed/declined/failed` lifecycle. |
| 27 | - The tables moved from Live: `chat_messages`, the DM tables, `tts_voice_overrides`, |
| 28 | `channel_sounds`, `relay_users`, `hidden_relay_users`, `pending_ip_messages`, |
| 29 | `stream_first_chats`, `moderation_actions`, plus `channel_moderators`, |
| 30 | `channel_moderation_settings`, `emotes`, `user_tags`, `chat_ai_summaries`, |
| 31 | `chat_timeline_events`. Live keeps no copy and reads through the internal API. |
| 32 | - Rooms, Vibe (the assistant), support inboxes, the website widget, feeds, `openvibe.party`, agent |
| 33 | rooms and chat apps. |
| 34 | |
| 35 | **Stays in Live:** `media_requests` and `media_request_settings`. Do not grow Live's chat tables back. |
| 36 | |
| 37 | ## Who calls it, and whom it calls |
| 38 | |
| 39 | - Callers: browsers on `openvibe.chat` and Live's origin; Live (internal reads, bridge and event |
| 40 | consumer); Bot (chat-app bridge); Actor (asks); other services with a Network service token |
| 41 | audience `openvibe.chat` and a `chat.*` capability. |
| 42 | - Depends on: Network (identity, SSO, service tokens, the `chat.preferences` module, VIP badges), |
| 43 | Live (channel/stream context through `server/live-context.js` — the only module that talks to Live), |
| 44 | Events (outbox and subscriptions), Media (emote image bytes, namespace `chat`), AI |
| 45 | (`server/ai/`, off unless enabled), VIP (optional; without it nobody has a badge), Tools/Search |
| 46 | where the assistant needs them. |
| 47 | |
| 48 | ## Repository map |
| 49 | |
| 50 | - **Entry:** [server/index.js](server/index.js) — database, first Live sync, chat and call WebSocket |
| 51 | servers, HTTP app with the Events consumer and `POST /internal/events`, background loops, |
| 52 | subscription check, graceful stop (tells clients the chat is restarting). |
| 53 | - **App:** [server/app.js](server/app.js) — Live's guards for these routes: credentialed CORS for an |
| 54 | exact origin list, `/api` rate limit, IP/network bans, `ov_banned` cookie, WS origin+IP checks. |
| 55 | - **Config:** [server/config.js](server/config.js); database [server/db/database.js](server/db/database.js) |
| 56 | (`openDb`, migrations via `DATABASE_DIRECT_URL`). |
| 57 | - **Chat core:** [server/chat/](server/chat/) — `chat-server.js`, `dm.js`, `dm-routes.js`, |
| 58 | `history-store.js`, `moderation-utils.js`, `word-filter.js`, `deploy-notice.js`, the T3 APIs. |
| 59 | - **Calls:** [server/calls/](server/calls/) — call server, lifecycle, REST routes, Live stream hooks. |
| 60 | - **Rooms/servers:** [server/rooms/](server/rooms/) — `rooms.js`, `servers.js`, `vibe.js`, |
| 61 | `workspace.js`, parties, memory, feeds. |
| 62 | - **Events:** [server/events/](server/events/) — `service-outbox.js`, `consumer.js`, `subscriptions.js`. |
| 63 | - **Auth/net:** [server/auth/](server/auth/), [server/net/](server/net/) (service tokens, client IP). |
| 64 | - **Browser:** [public/web/](public/web/) — `chat.js`, `chat-shell.js`, `call.js`, `vibe.js`, |
| 65 | `widget.js`, `embed.js`, `party.js`, `feed-view.js`, plus their CSS. |
| 66 | - **Deploy:** [deploy/systemd/openvibe-chat.service](deploy/systemd/openvibe-chat.service), |
| 67 | [deploy/nginx/openvibe.chat.conf](deploy/nginx/openvibe.chat.conf), |
| 68 | [deploy/nginx/openvibe.live-chat.locations.conf](deploy/nginx/openvibe.live-chat.locations.conf), |
| 69 | [deploy/nginx/calls/openvibe.live-calls.locations.conf](deploy/nginx/calls/openvibe.live-calls.locations.conf). |
| 70 | |
| 71 | ## Commands |
| 72 | |
| 73 | ```bash |
| 74 | npm ci |
| 75 | npm run dev # NODE_ENV=development node server/index.js |
| 76 | npm start # node server/index.js, http://127.0.0.1:4400 |
| 77 | npm test # test/run.js: every file in its own process, PGlite per process |
| 78 | npm run test:pg # same on PostgreSQL + PgBouncer + Valkey (containers) |
| 79 | npm test -- ws import # filter by file name; --strict makes a skip fail |
| 80 | npm run n-1:record # record the N-1 fixtures after a deploy |
| 81 | ov test # the harness's sharded run of what the change reaches |
| 82 | ``` |
| 83 | |
| 84 | ## Database and migrations |
| 85 | |
| 86 | Chat's own PostgreSQL database on the host's data role (`add-service.sh chat`); development and |
| 87 | tests use embedded PGlite, PostgreSQL in production through PgBouncer. Migrations are |
| 88 | `migrations/NNNN_*.sql`, applied at boot with the owner connection (`DATABASE_DIRECT_URL`), then the |
| 89 | app serves through the pooled runtime role (`DATABASE_URL`, DML only). Never edit an applied |
| 90 | migration. The moved tables keep Live's ids and columns plus Network subject columns |
| 91 | (`subject_id`, `sender_subject_id`, …). `docs/parity.md` records the parity method. |
| 92 | |
| 93 | ## Auth and the WebSocket rules |
| 94 | |
| 95 | - People sign in with a Network session JWT, verified against the Network key; tokens issued before a |
| 96 | subject's `token_valid_after` are refused. `hbt_` API tokens are resolved through Live. |
| 97 | - A WebSocket upgrade authenticates with an `Authorization: Bearer` header only (bots); a browser's |
| 98 | socket starts anonymous and presents its token in the first message (`join`, `join_room`, |
| 99 | `auth-update`). **Neither the `ov_token` cookie nor `?token=` in the URL signs a socket in** (removed |
| 100 | 2026-10-10); a token in the URL gets a `system` frame saying to use the header or `join`. |
| 101 | - The `ov_token` cookie only exempts staff from a network/IP ban at upgrade. |
| 102 | - Services use client-credentials tokens for audience `openvibe.chat`, checked per capability |
| 103 | (`chat.live_bridge.write`, `chat.message.send`, `chat.presence.read`, `chat.moderation.read`, …). |
| 104 | - DMs go only to participants, private rooms only to members; network blocks count like DM blocks; a |
| 105 | staff member reading someone else's chat is recorded as a moderation action. |
| 106 | |
| 107 | ## Frontend loading model |
| 108 | |
| 109 | Browsers load plain, no-build scripts from [public/web/](public/web/) on Chat's own site; Live's |
| 110 | origin keeps Live's existing chat JavaScript and talks to the same sockets/APIs (the Wave 6 rule was |
| 111 | "move behind a service boundary before changing behaviour"). There is no SPA, bundler or transpiler. |
| 112 | `openvibe-shared` supplies the Frame and `/shared/*` from the pinned node_modules. The widget is a |
| 113 | shadow-root bubble under 3 KB compressed; embeds and `openvibe.party` are separate pages/scripts. |
| 114 | |
| 115 | ## Deploy |
| 116 | |
| 117 | `sudo ovhost deploy chat` (git-checkout: fetch, fast-forward `/opt/openvibe.chat`, install on a |
| 118 | lockfile change, restart, wait for **`/ready`**, not `/api/ready`). Unit `openvibe-chat.service` on |
| 119 | `127.0.0.1:4400`, env `/etc/openvibe/chat.env`; vhosts |
| 120 | [deploy/nginx/openvibe.chat.conf](deploy/nginx/openvibe.chat.conf) and the Live locations include |
| 121 | (inside Live's `server { … }`). After a deploy, record the N-1 fixtures (`npm run n-1:record`). |
| 122 | Rollback is a Chat release rollback (`sudo ovhost rollback chat --to <sha>`), never a switch back to |
| 123 | Live. Migrations only add tables and columns. |
| 124 | |
| 125 | ## Tests |
| 126 | |
| 127 | `npm test` runs each `test/*.test.js` in its own process (60 s) against one migrated PGlite database |
| 128 | per process (`test/helpers/pg-preload.mjs`); `npm run test:pg` uses the PostgreSQL/PgBouncer/Valkey |
| 129 | containers. The suite covers the WS gates and commands, DMs and privacy, history latency, moderation |
| 130 | and staff audit, calls and capture-on-load, TTS/audio queues, rooms/servers/Vibe, the widget/embeds, |
| 131 | feeds, chat apps, Events consumption, account export/deletion and the N-1 fixtures. `ov test` runs the |
| 132 | same. |
| 133 | |
| 134 | ## Conventions |
| 135 | |
| 136 | - CommonJS; 4-space indent in the newer modules, Live's original style preserved in the moved files. |
| 137 | - `server/live-context.js` is the **only** module that talks to Live; do not call Live from routes. |
| 138 | - Errors are problem+json or the chat frames' own stable codes; never leak a DM, a staff log or a |
| 139 | token into a response or a log. |
| 140 | - CORS and WS origins are exact origins; no wildcard for credentialed requests. |
| 141 | |
| 142 | ## Pitfalls |
| 143 | |
| 144 | - **A token in the socket URL or a cookie must not sign the socket in** (C-05/C-06). Keep the |
| 145 | first-message auth and the `Authorization` header for bots. |
| 146 | - **Chat is required in every mode.** Live's chat routes are retired; do not add a fallback to Live. |
| 147 | - **The CORS/WS origin list is exact.** `*.openvibe.tools` must not be honoured: a credentialed grant |
| 148 | would let it read signed-in DMs. |
| 149 | - **`/internal/*` and `/metrics` must stay unreachable through nginx** (404 there); nginx sets the |
| 150 | client address from the connection only. |
| 151 | - **DM privacy:** participants only, block-aware, and staff reads are audited moderation actions. |
| 152 | - **Bans/mutes are Chat's**, and Live reads moderation through the internal API; `media_requests` |
| 153 | remains Live's. |
| 154 | - **Readiness is `/ready`**, not `/api/ready` — the standard deploy probe assumes the latter. |
| 155 | - **Do not "fix" a message by editing the store**: the Live bridge mirrors changes; use the service |
| 156 | paths so events, the bridge and Search stay consistent. |
| 157 | - **N-1 fixtures** are part of shipping: record them after the deploy. |
| 158 | |
| 159 | ## Documentation |
| 160 | |
| 161 | - [README.md](README.md) — the product, cutover, data, events, rooms, Vibe, inboxes, feeds, deploy. |
| 162 | - [docs/platform.md](docs/platform.md) — agent rooms and the digest Code/Chat/Actor use. |
| 163 | - [docs/vibe-personas.md](docs/vibe-personas.md) — the persona shape, the topic check, models, spend cap, owner page and tests. |
| 164 | - [docs/chat-ingress.md](docs/chat-ingress.md) — `/internal/chat/*` tickets and reads. |
| 165 | - [docs/calls-cutover.md](docs/calls-cutover.md), [docs/parity.md](docs/parity.md), |
| 166 | [docs/chat-apps.md](docs/chat-apps.md), [docs/party-pool.md](docs/party-pool.md). |
| 167 | - `openvibe-contracts` holds `chat.*` shapes and capabilities. |
| 168 |