OpenClaw plugin-sdk's registerTool execute signature is:
execute: async (_id: string, params) => { ... }
Fabric tools were calling it as `(p) => { ... }`, so `p` held the
call id (a string) and the real args were silently dropped onto the
floor. Every tool that read a required field from `p` failed with
the field surfacing as undefined.
fabric-guild-list (just added) appeared to work because all its
properties are optional — `p.nameFilter` and `p.purposeFilter`
both being undefined produced empty filter needles, which let the
unfiltered guild list through. The real bug surfaced the moment
fabric-channel-list (required: guildNodeId) was invoked: the
ctxGuild helper saw `undefined` and reported `agent not a member
of guild undefined`.
Compare dialectic plugin's tools.ts which has always used the
correct `async (_id: string, params) => {...}` shape and worked
end-to-end. Aligning the fabric signature to match.
Verified end-to-end on sim:
- fabric-guild-list returns 1 guild with the purpose set via the
new `cli node set-purpose`
- fabric-channel-list returns 3 channels including a now-populated
`purpose` field on each row
- fabric-channel-set-purpose successfully patches a channel and
the subsequent fabric-channel-list shows the new purpose
Adds two agent-facing tools that close the discoverability loop:
- fabric-guild-list — enumerates guilds the agent belongs to with
name + purpose + status (no api calls beyond the existing agentLogin
response). Optional nameFilter/purposeFilter for narrowing.
- fabric-channel-set-purpose — PATCH /api/channels/:id { purpose }
so agents can backfill or update an existing channel's purpose.
Extends existing tools:
- fabric-channel-list now returns purpose on each row.
- create-{chat,work,report,discussion}-channel accept optional purpose.
FabricClient + FabricSession type changes carry the new field through.
Manifest contracts.tools updated (jiti loader needs both manifest entry
and onStartup activation to register).
Lets workflows that previously needed hardcoded channel ids instead say
'find a guild whose purpose mentions debate, then a channel of x_type
announce whose purpose covers public debate broadcasts.'
One tool, three actions backed by FabricClient channelMembers (GET
/channels/:id/members -> [{userId,bypass}]), joinChannel, and new
leaveChannel (POST /channels/:id/leave).
Verified: client-level smoke against the running guild — members
initial=[tester], after join echo2 present, after leave echo2 gone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- bin/fabric-register.mjs: only AGENT_ID is read from the environment;
--api-key is flag-only (no FABRIC_API_KEY); dropped FABRIC_CENTER_API_BASE
/ FABRIC_IDENTITY_FILE / OPENCLAW_PATH env fallbacks (flags + sensible
defaults; --center still falls back to openclaw.json).
- New fabric-canvas tool (one tool, four actions): read / share / update /
close the channel's single pinned canvas. Backed by FabricClient
get/share/update/removeCanvas (GET/PUT/PATCH/DELETE; empty 2xx body ->
null). update/close are sharer-only server-side.
- README updated.
Verified: client-level smoke against the running guild —
read(empty→null) → share(v1) → read → update(v2) → close(→null) all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Binding an agent's Fabric API key was an OpenClaw tool; make it a
self-contained Node script installed to ~/.openclaw/bin/fabric-register
instead.
- bin/fabric-register.mjs: no plugin deps; AGENT_ID env wins, else
--agent-id required; --api-key validated via POST /auth/agent/login;
on success upserts ~/.openclaw/fabric-identity.json (format matches
IdentityRegistry). Flags/env for center, identity-file, openclaw-path.
- install.mjs: copy the script to ~/.openclaw/bin (chmod 0755) on
install, remove on uninstall; Next-steps updated.
- tools.ts: drop the fabric-register tool; ctxGuild error now points to
the script / static accounts config.
- README updated.
Verified: missing-id -> exit 2; --agent-id and AGENT_ID both bind and
write a valid identity file; bad key -> 401, no write.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Real channel-turn dispatch (resolveAgentRoute + finalizeInboundContext +
dispatchInboundReplyWithBase), wakeup->drop/dispatch, messaging target
grammar (fabric:<id>) + outbound.sendText, tools use execute/parameters.
Verified live: human msg in Fabric -> wakeup -> openclaw agent runs ->
reply posted back into the Fabric channel as the agent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>