From 9d0fa1d5c8462dfb455f45e07b2ffeb30f7d439c Mon Sep 17 00:00:00 2001 From: hzhang Date: Sat, 16 May 2026 12:53:25 +0100 Subject: [PATCH] docs: rewrite README to match current architecture Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 77 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 1707c1b..254215d 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,82 @@ # Fabric.OpenclawPlugin -An **OpenClaw channel plugin** that connects OpenClaw agents to a Fabric guild. +A native **OpenClaw channel plugin** that connects OpenClaw agents to Fabric +guilds as real channel members. Independent of OpenClaw's source — it only +uses the public plugin SDK. ## Model -- `kind: "channel"` plugin (like the bundled discord channel). OpenClaw **core - owns dispatch** (inbound → agent run) and the reply pipeline via the channel - turn kernel `runtime.channel.turn.run(...)`. -- Fabric already owns turn/shuffle/mention/`/no-reply` server-side, so this - plugin is thin. Fabric's per-recipient **`wakeup`** maps to channel-turn - **admission**: - - `wakeup === true` → `dispatch` (agent runs, may reply) - - otherwise → `{ kind: "drop", recordHistory: true }` (kept as context) -- **No sidecar, no fake no-reply model, no `before_model_resolve` gating.** +- `kind: "channel"` plugin (like the bundled discord channel). OpenClaw core + owns dispatch and the reply pipeline via the channel-turn kernel + (`resolveAgentRoute` + `finalizeInboundContext` + + `dispatchInboundReplyWithBase`). Fabric already owns turn/shuffle/mention/ + `/no-reply` server-side, so this plugin is thin. +- Fabric's per-recipient **`wakeup`** maps to admission: + - `wakeup === true` → **dispatch** (the agent runs and may reply). + - `wakeup !== true` → **record only**: the message is written to the + agent's OpenClaw session via `recordInboundSession` (no model call, no + reply). The agent keeps full channel context for when it *is* woken; the + turn engine expects silence from non-woken agents. +- Replies are forced to **automatic** delivery + (`replyOptions.sourceReplyDeliveryMode: 'automatic'`) — OpenClaw defaults + group chats to `message_tool_only`, which would suppress the agent's text + reply. Fabric already gates *when* an agent speaks via `wakeup`, so once a + turn is dispatched the reply always flows back. +- One Fabric socket per agent identity. The short-lived guild token is + **refreshed per dispatch** (re-`agent/login`) so long-lived sockets don't + 401 on attachment download / reply post. + +## Files to agents + +When an inbound message has `attachments[]`, the plugin downloads each file +(with the agent's guild token) to a temp dir and sets local +`MediaPaths`/`MediaTypes` on the inbound context so the agent receives the +files. `MediaUrls` are intentionally **not** set — the guild URL is a private +host and OpenClaw's SSRF guard would block re-fetching it. ## Auth Each agent has a Fabric Center **API key** (mint via Center CLI: -`node dist/cli.js user apikey --email `). The key is exchanged for -a user session (`POST /auth/agent/login`) used to receive (socket) and post -replies. Bind a key to an agent via the `fabric-register` tool, or pre-populate -the identity file. +`node dist/cli.js user apikey --email `). The key is exchanged +for a user session (`POST /auth/agent/login`). Bind a key to an agent with +the `fabric-register` tool, or via `channels.fabric.accounts`. ## Config - `channels.fabric.centerApiBase` — e.g. `http://localhost:7001/api` - `channels.fabric.accounts.` = `{ fabricApiKey, enabled }` - (agent = account; the account id is the openclaw agentId) + (**agent = account**; the account id is the OpenClaw agentId) - plugin `identityFilePath` — default `~/.openclaw/fabric-identity.json` - (`{ entries: [{ agentId, fabricApiKey }] }`) ### Required: route binding (account → agent) -openclaw routes a channel turn to an agent via `cfg.bindings`. Without a -Fabric binding it falls back to the default agent. Add one per account: +OpenClaw routes a channel turn via `cfg.bindings`; without a Fabric binding +it falls back to the default agent. One per account: ```json { "agentId": "", "match": { "channel": "fabric", "accountId": "" } } ``` -e.g. `openclaw config set bindings '[…, {"agentId":"echo","match":{"channel":"fabric","accountId":"echo"}}]' --json`, -then `openclaw gateway restart`. +Then `openclaw gateway restart`. ## Tools - `fabric-register` — bind this agent's Fabric API key - `create-chat-channel` (general) / `create-work-channel` (work) / `create-report-channel` (report) / `create-discussion-channel` (discuss) -- `discussion-complete` — post a summary then close the channel - (Fabric `POST /channels/:id/close`; closed → history readable, posts → 409) +- `discussion-complete` — post a summary, then close the channel + (closed → history readable; new posts → `409`) -## Transport (Phase 1 = B1) - -One Fabric socket per agent identity, in the plugin runtime. Firehose (B2) is -a later drop-in behind the same `dispatch()` seam. - -## Build +## Install / build ```bash npm install && npm run build +node install.mjs # build + copy to ~/.openclaw/plugins/fabric + configure ``` -> The plugin compiles against the host's OpenClaw SDK -> (`openclaw/plugin-sdk/*`); build inside the OpenClaw plugin environment. +`install.mjs` mirrors the PaddedCell-style installer (also `--uninstall`). +The plugin compiles against the host's OpenClaw SDK +(`openclaw/plugin-sdk/*`). + +> Transport is Phase 1 (one socket per agent). A firehose variant (B2) is a +> later drop-in behind the same `dispatch()` seam.