F-3 refinements:
- internal/inbound: replace fixed 3s reconnect wait with exponential
backoff (1s → 60s, ×2, reset when prior session lasted >30s); proxy
for "healthy" vs "flapping" and avoids hot reconnect loops when the
server is sick
F-4 agent tool surface (port of openclaw plugin's tools.ts):
- internal/tools/tools.go (~370 LOC): Registry binds Deps {Client,
Tokens, Identities} and exposes 8 agent-facing tools:
fabric-send-message post a normal message to any channel
fabric-send-sys-msg post a kind=sys message (bypasses turn engine)
fabric-channel-list list channels visible in a guild
fabric-guild-list list guilds the agent is in
fabric-message-history paginate channel messages by seq
fabric-channel-set-purpose PATCH the channel's purpose
fabric-channel fetch metadata + members for one channel
fabric-canvas get/share/update/remove channel canvas
- internal/tools/contracts.go: static ToolContract list — kept in sync
with install.sh's manifest emitter
- Every agent-scoped tool requires agent_id in input args (Plexum SDK
doesn't propagate calling agent id through CallTool today)
- guild_node_id defaults to agent's first guild for fabric-send-message
internal/fabric/client.go: new REST methods needed by tools —
PostSystemMessage, CreateChannel, CloseChannel, JoinChannel,
LeaveChannel, SetChannelPurpose, GetCanvas, ShareCanvas, UpdateCanvas,
RemoveCanvas, SyncCommands.
cmd/plexum-fabric-channel-plugin/main.go:
- Manifest declares the tool surface via tools.New(...).Contracts()
- CallTool dispatches "send" to handleSend (outbound for channel
manager), everything else to tools.Registry.Handler(name)
scripts/install.sh:
- Manifest tools[] now lists all 9 tools with schemas — matches what
internal/tools/contracts.go advertises
Live verified against running Fabric stack:
$ plexum plugin-call fabric-guild-list '{"agent_id":"fabrictester"}'
→ "guilds for agent fabrictester (1): test-guild2 @ http://localhost:7003"
$ plexum plugin-call fabric-channel-list '{...,"guild_node_id":"test-guild2"}'
→ 2 channels listed
$ plexum plugin-call fabric-message-history '{...,"limit":5}'
→ 5 messages with timestamps + authors
F-5+ deferred:
- create-{chat,work,report,discussion}-channel (batch 2)
- sub-discussion family (state store + 3 tools)
- presence-sync + command-sync
- attachments
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
140 lines
4.4 KiB
Go
140 lines
4.4 KiB
Go
package tools
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
plugin "git.hangman-lab.top/hzhang/Plexum-sdk-go/plugin"
|
|
)
|
|
|
|
// allContracts is the static manifest list of all agent-facing tools.
|
|
// install.sh reads this via a Go-build introspection step? No — we'd
|
|
// need a separate emit-manifest binary. For now, install.sh has the
|
|
// same JSON inline (keep in sync; trivially small). Future refinement
|
|
// could auto-emit.
|
|
var allContracts = []plugin.ToolContract{
|
|
{
|
|
Name: "send",
|
|
Description: "Outbound channel reply — host calls this after an inbound dispatch (agent-side calls usually prefer `fabric-send-message`).",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"channel_name": {"type": "string"},
|
|
"session_id": {"type": "string"},
|
|
"message": {"type": "string"}
|
|
},
|
|
"required": ["channel_name", "message"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "fabric-send-message",
|
|
Description: "Post a normal message to a Fabric channel as the bound agent. guild_node_id defaults to the agent's first guild when omitted.",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"channel_id": {"type": "string"},
|
|
"content": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "channel_id", "content"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "fabric-send-sys-msg",
|
|
Description: "Post a system-kind message to a channel (not subject to turn engine / wakeup).",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"channel_id": {"type": "string"},
|
|
"content": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "guild_node_id", "channel_id", "content"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "fabric-channel-list",
|
|
Description: "List channels visible to the agent in a guild (public + member channels).",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "guild_node_id"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "fabric-guild-list",
|
|
Description: "List the guilds the agent belongs to (nodes + endpoints).",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"}
|
|
},
|
|
"required": ["agent_id"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "fabric-message-history",
|
|
Description: "Paginate channel messages by seq. seq_from/seq_to/limit are optional (server caps limit at 200).",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"channel_id": {"type": "string"},
|
|
"seq_from": {"type": "integer"},
|
|
"seq_to": {"type": "integer"},
|
|
"limit": {"type": "integer"}
|
|
},
|
|
"required": ["agent_id", "guild_node_id", "channel_id"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "fabric-channel-set-purpose",
|
|
Description: "Update the channel's free-form purpose text.",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"channel_id": {"type": "string"},
|
|
"purpose": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "guild_node_id", "channel_id", "purpose"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "fabric-channel",
|
|
Description: "Fetch metadata + member list for a specific channel.",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"channel_id": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "guild_node_id", "channel_id"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "fabric-canvas",
|
|
Description: "Read or write the channel's pinned canvas doc. op: get|share|update|remove. share/update need title+format+source.",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"channel_id": {"type": "string"},
|
|
"op": {"type": "string", "enum": ["get","share","update","remove"]},
|
|
"title": {"type": "string"},
|
|
"format": {"type": "string", "enum": ["md","html","text"]},
|
|
"source": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "guild_node_id", "channel_id", "op"]
|
|
}`),
|
|
},
|
|
}
|