Bundles the remaining "agent does things in Fabric" surface, skipping
presence-sync (Plexum's state machine isn't 1:1 with HF status semantics)
and attachments (Plexum has no media pipeline yet) — both deferred.
internal/subdisc/ (~140 LOC + 5 tests): persistent KV at
<profile>/state/plugin-kv/fabric-sub-discussions.json
- Entry: SubChannelID, HostAgentID, HostUserID, GuestUserIDs,
HostGuide, GuestGuide, CallbackGuildNodeID, CallbackChannelID, CreatedAt
- Open / Lookup / LookupForHost (host-enforcement) / Add / Remove / All
- atomic tmp+rename persistence; corrupt file = start empty
internal/tools/tools.go — 7 new tools:
- create-chat-channel (xType=general)
- create-work-channel (xType=work)
- create-report-channel (xType=report)
- create-discussion-channel (xType=discuss)
- discussion-complete (post summary + close channel)
- create-sub-discussion (create + invite + KV store + greeting)
- close-sub-discussion (host-only; posts callback to parent + closes)
internal/fabric/client.go: CreateChannel / CloseChannel /
JoinChannel / LeaveChannel / SetChannelPurpose / Canvas CRUD /
SyncCommands (added in earlier F-4 commit; reused here)
cmd/plexum-fabric-channel-plugin/main.go:
- subdisc.Store opened from DefaultPath at init; passed into tools.Deps
- HostConfig adds commands_sync_key + sync_commands (defaults
["new","stop"]) — F-5 command-sync to every (agent,guild) pair at
init when key is set; silently skips when omitted
- syncCommandsToGuilds: best-effort PUT per guild, logs per-guild
outcome
scripts/install.sh: manifest tools[] expanded to 16 entries
(9 from F-4 + 7 new). create-channel variants share a schema shape.
Live verified:
$ plexum plugin-call create-chat-channel \
'{"agent_id":"fabrictester","guild_node_id":"test-guild2",
"name":"plexum-f7-smoke","is_public":true}'
→ "created general channel plexum-f7-smoke (id=6315e636-...)"
$ plexum plugin-call fabric-channel-list ...
→ 3 channels listed including the new one
F-6 (attachments) + F-5b (presence-sync) + F-8 (coalesce) deliberately
deferred — see DEV-NOTES.
Tests: 5 new in internal/subdisc (27 total in this repo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
213 lines
7.3 KiB
Go
213 lines
7.3 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"]
|
|
}`),
|
|
},
|
|
// ---- channel creation (4 x-types share the same shape) ----
|
|
createChannelContract("create-chat-channel", "Create a chat channel (xType=general)."),
|
|
createChannelContract("create-work-channel", "Create a work channel (xType=work)."),
|
|
createChannelContract("create-report-channel", "Create a report channel (xType=report)."),
|
|
createChannelContract("create-discussion-channel", "Create a discussion channel (xType=discuss)."),
|
|
{
|
|
Name: "discussion-complete",
|
|
Description: "Post a summary then close the channel — concludes a discussion.",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"channel_id": {"type": "string"},
|
|
"summary": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "guild_node_id", "channel_id", "summary"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "create-sub-discussion",
|
|
Description: "Spawn a new sub-discussion channel with the calling agent as host + invited guests; remembered in plugin KV so close-sub-discussion can post the callback to the parent channel.",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"name": {"type": "string"},
|
|
"guest_user_ids": {"type": "array", "items": {"type": "string"}},
|
|
"host_guide": {"type": "string"},
|
|
"guest_guide": {"type": "string"},
|
|
"parent_channel_id":{"type": "string"},
|
|
"greeting": {"type": "string"},
|
|
"x_type": {"type": "string", "enum": ["discuss","work","report","general"]}
|
|
},
|
|
"required": ["agent_id", "guild_node_id", "name", "parent_channel_id"]
|
|
}`),
|
|
},
|
|
{
|
|
Name: "close-sub-discussion",
|
|
Description: "Close a sub-discussion the calling agent originally created; posts a callback message to the parent channel.",
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"sub_channel_id":{"type": "string"},
|
|
"summary": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "sub_channel_id"]
|
|
}`),
|
|
},
|
|
}
|
|
|
|
// createChannelContract returns the shared schema for the four
|
|
// create-*-channel tool variants.
|
|
func createChannelContract(name, description string) plugin.ToolContract {
|
|
return plugin.ToolContract{
|
|
Name: name, Description: description,
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {
|
|
"agent_id": {"type": "string"},
|
|
"guild_node_id": {"type": "string"},
|
|
"name": {"type": "string"},
|
|
"is_public": {"type": "boolean"},
|
|
"member_user_ids": {"type": "array", "items": {"type": "string"}},
|
|
"listeners": {"type": "array", "items": {"type": "string"}},
|
|
"on_duty": {"type": "string"},
|
|
"purpose": {"type": "string"}
|
|
},
|
|
"required": ["agent_id", "guild_node_id", "name"]
|
|
}`),
|
|
}
|
|
}
|