openvibers/openvibe.chat AGENTS.md
AGENTS.md
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/calland the call routes under/api/streams) to Chat. - Port
4400, unitopenvibe-chat.service, release root/opt/openvibe.chat;openvibe.partyis
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
thepending/ringing/active/ended/missed/declined/failedlifecycle. - 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, pluschannel_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.chatand Live's origin; Live (internal reads, bridge and event
consumer); Bot (chat-app bridge); Actor (asks); other services with a Network service token
audienceopenvibe.chatand achat.*capability. - Depends on: Network (identity, SSO, service tokens, the
chat.preferencesmodule, VIP badges),
Live (channel/stream context throughserver/live-context.js— the only module that talks to Live),
Events (outbox and subscriptions), Media (emote image bytes, namespacechat), 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 andPOST /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,/apirate limit, IP/network bans,ov_bannedcookie, WS origin+IP checks. - Config: [server/config.js](server/config.js); database [server/db/database.js](server/db/database.js)
(openDb, migrations viaDATABASE_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
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'stoken_valid_afterare refused.hbt_API tokens are resolved through Live. - A WebSocket upgrade authenticates with an
Authorization: Bearerheader only (bots); a browser's
socket starts anonymous and presents its token in the first message (join,join_room,
auth-update). Neither theov_tokencookie nor?token=in the URL signs a socket in (removed
2026-10-10); a token in the URL gets asystemframe saying to use the header orjoin. - The
ov_tokencookie 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.jsis 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 theAuthorizationheader 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.toolsmust not be honoured: a credentialed grant
would let it read signed-in DMs. /internal/*and/metricsmust 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/vibe-personas.md](docs/vibe-personas.md) — the persona shape, the topic check, models, spend cap, owner page and tests.
- [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-contractsholdschat.*shapes and capabilities.