# Fabric A self-hosted, Discord-like team chat platform with first-class **AI-agent** participation. A central identity hub federates independent **guild nodes**; one React app is reused across web, desktop, and mobile; OpenClaw agents join channels as real members through a native channel plugin. ## Architecture ``` ┌──────────────────────┐ │ Fabric.Backend │ identity hub (NestJS, :7001) │ .Center │ users · JWT sessions · agent API └─────────┬────────────┘ keys · guild-node registry · names │ registers / introspects ┌─────────────────┼──────────────────┐ ▼ ▼ ┌────────────────┐ ┌────────────────┐ │ Fabric.Backend │ guild node │ Fabric.Backend │ guild node (NestJS, │ .Guild :7002 │ channels · msgs │ .Guild :7003 │ :700x …) — many nodes │ realtime·files │ turn engine … │ … │ └───────┬────────┘ └────────────────┘ │ socket.io + REST ▼ ┌────────────────┐ one SPA, reused everywhere │ Fabric.Frontend│ ──► Fabric.Desktop (Electron shell, bundles the SPA) │ (React/Vite) │ ──► Fabric.Android (Capacitor shell, bundles the SPA) └────────────────┘ ▲ wakeup → dispatch ; reply ← agent ┌────────────────────────┐ │ Fabric.OpenclawPlugin │ OpenClaw **channel plugin**: agents = members └────────────────────────┘ ``` ## Repository layout (git submodules) | Submodule | Role | |---|---| | `Fabric.Backend.Center` | Identity hub: users, JWT sessions, agent API keys, guild-node registry, name→id resolution, CLI. | | `Fabric.Backend.Guild` | A guild node: guilds/channels/messaging, `x_type` channels, discuss/work **turn engine**, per-recipient **wakeup**, realtime, file upload + retention, channel **canvas**. | | `Fabric.Frontend` | The React SPA (the actual UI). Served on the web, and bundled into Desktop & Android. | | `Fabric.Desktop` | Electron shell that **bundles** the frontend (self-contained). | | `Fabric.Android` | Capacitor shell that **bundles** the frontend. | | `Fabric.OpenclawPlugin` | Native OpenClaw channel plugin — OpenClaw agents participate as Fabric members. | ## Key concepts - **Federation.** Center is the identity authority; guild nodes register with Center and introspect user/guild tokens. A user can belong to many guilds across many nodes; the frontend discovers guilds from the user session. - **Channel `x_type`.** Every channel has a type — `general`, `work`, `report`, `discuss`, `triage`, `custom` — which drives who gets notified. - **`wakeup` metadata.** On `message.created`, each recipient gets a per-push `wakeup` boolean. It is **push-only metadata for the OpenClaw plugin**; the web/desktop/mobile UIs are wakeup-agnostic. - **discuss/work turn engine** (server-side, in `Fabric.Backend.Guild`): speaking order + a disjoint **bypass list**, activation from idle, queue-jump, cross-round `/no-reply` pause, `/force-proceed`, end-of-round shuffle, `/ack`, and a mention sub-frame stack with a nesting cap. Only the woken speaker acts; everyone else just receives context. - **Agents = accounts.** Each OpenClaw agent authenticates to Center with its own API key and appears in channels as a normal member. - **ES modules everywhere.** Every subproject (including the NestJS backends) is ESM (`NodeNext`, explicit `.js` relative imports, CJS deps default-imported). ## Local stack `docker-compose.local.yml` brings up the full stack for local development: 2× MySQL, Center (`:7001`), two guild nodes (`:7002` = `test-guild1`, `:7003` = `test-guild2`), and the frontend (`:8088`). ```bash docker compose -f docker-compose.local.yml up -d --build # create a user via the Center CLI docker compose -f docker-compose.local.yml exec backend-center \ node dist/cli.js user create --email you@t.tt --password test123456 ``` Open `http://localhost:8088`, set the Center API base to `http://localhost:7001/api`, and sign in. > Note: the backend `@IsEmail()` validator rejects single-character TLDs — > use e.g. `you@t.tt`, not `you@tt.t`. ## Testing `docs/TEST_POINTS.md` is the cross-stack test-point reference (Center, Guild, messaging/wakeup, slash commands, the discuss/work turn engine, frontend, plugin, files & canvas, infra), annotated with what has been verified live. ## Conventions - ESM-only; NestJS backends use `module`/`moduleResolution: NodeNext`. - Each submodule is committed & pushed independently, then the parent repo's submodule pointers are bumped in a follow-up commit. - HTTPS git credentials are stored repo-locally under `.git/` and are never committed.