From 887bc2daf8a0f601dc4b66b97c744644002cb5df Mon Sep 17 00:00:00 2001 From: Yehonal <147092+Yehonal@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:11:43 +0000 Subject: [PATCH 1/2] fix: consolidate OpenClaw mesh plugin Add OpenPack/AgentWheel metadata for selective OpenClaw plugin installation and guard mention correction against bot/controller-originated ping loops. --- agentwheel.json | 16 ++- openpack.json | 37 ++++++ packages/core/src/policy.js | 26 +++++ packages/core/test/policy.test.js | 41 +++++-- packages/runtime-wrapper/policy.js | 26 +++++ packages/runtime-wrapper/test/policy.test.js | 41 +++++-- plugins/openclaw-agent-mesh/README.md | 49 ++++++++ .../examples/openclaw.config.example.jsonc | 46 ++++++++ .../openclaw-agent-mesh/openclaw.plugin.json | 60 ++++++++++ plugins/openclaw-agent-mesh/package.json | 32 ++++++ plugins/openclaw-agent-mesh/src/index.js | 107 ++++++++++++++++++ 11 files changed, 463 insertions(+), 18 deletions(-) create mode 100644 openpack.json create mode 100644 plugins/openclaw-agent-mesh/README.md create mode 100644 plugins/openclaw-agent-mesh/examples/openclaw.config.example.jsonc create mode 100644 plugins/openclaw-agent-mesh/openclaw.plugin.json create mode 100644 plugins/openclaw-agent-mesh/package.json create mode 100644 plugins/openclaw-agent-mesh/src/index.js diff --git a/agentwheel.json b/agentwheel.json index be2ee31..32ff2f5 100644 --- a/agentwheel.json +++ b/agentwheel.json @@ -3,6 +3,10 @@ "name": "nestdev-mesh", "version": "0.9.0", "provides": [ + { + "type": "instructions", + "path": "AGENTS.md" + }, { "type": "skills", "path": "skills", @@ -10,16 +14,24 @@ { "from": "packages/tmux-bridge/bin", "into": "bin", - "include": ["*.sh"], + "include": [ + "*.sh" + ], "mode": "preserve" }, { "from": "packages/tmux-bridge/agents", "into": "agents", - "include": ["*.conf"], + "include": [ + "*.conf" + ], "mode": "preserve" } ] + }, + { + "type": "plugins", + "path": "plugins" } ] } diff --git a/openpack.json b/openpack.json new file mode 100644 index 0000000..88e0969 --- /dev/null +++ b/openpack.json @@ -0,0 +1,37 @@ +{ + "schemaVersion": 2, + "name": "NestDevLab/agent-mesh", + "version": "0.9.0", + "provides": [ + { + "type": "instructions", + "path": "AGENTS.md" + }, + { + "type": "skills", + "path": "skills", + "assets": [ + { + "from": "packages/tmux-bridge/bin", + "into": "bin", + "include": [ + "*.sh" + ], + "mode": "preserve" + }, + { + "from": "packages/tmux-bridge/agents", + "into": "agents", + "include": [ + "*.conf" + ], + "mode": "preserve" + } + ] + }, + { + "type": "plugins", + "path": "plugins" + } + ] +} diff --git a/packages/core/src/policy.js b/packages/core/src/policy.js index a7b1855..08a97cb 100644 --- a/packages/core/src/policy.js +++ b/packages/core/src/policy.js @@ -408,6 +408,17 @@ export function planDiscordMentionCorrection(config, action, inheritedBase) { const normalizedTurn = normalizeBridgeTurn(request); const text = normalizedTurn.text; + if (/^\s*(?:<@!?\d+>\s*)*Controller:/i.test(text)) { + return { + ...base, + accepted: true, + reason: "mention_correction_skipped_controller_message", + nextAction: "none", + sideEffectsAllowed: false, + normalizedTurn + }; + } + if (cfg.mode === "observe") { return { ...base, @@ -452,6 +463,21 @@ export function planDiscordMentionCorrection(config, action, inheritedBase) { }; } + // Anti ping-loop guard: the mention-correction bridge used to derive + // Discord pings from free-form bot text. In mesh mode the body is human + // content, not routing. Bot/controller output must never generate fresh + // Discord mentions; only a separately validated routing/envelope layer may. + if (sourceBotId) { + return { + ...base, + accepted: true, + reason: "mention_correction_skipped_bot_source", + nextAction: "none", + sideEffectsAllowed: false, + normalizedTurn + }; + } + const references = findUntaggedParticipantReferences(cfg.bridge.participants, text, sourceBotId); if (!references.length) { return { diff --git a/packages/core/test/policy.test.js b/packages/core/test/policy.test.js index 7918573..a22724d 100644 --- a/packages/core/test/policy.test.js +++ b/packages/core/test/policy.test.js @@ -258,7 +258,7 @@ test("address book includes labels, mentions, and aliases", () => { assert.match(formatAgentAddressBook(entries), /ControllerBot: <@222>/); }); -test("mention correction planner detects natural agent names without real Discord mention", () => { +test("mention correction skips bot-authored natural agent names without creating pings", () => { const cfg = { mode: "plan", discordAllowlist: [{ guildId: "g1", channelId: "c1" }], @@ -277,13 +277,13 @@ test("mention correction planner detects natural agent names without real Discor } }); assert.equal(plan.accepted, true); - assert.equal(plan.reason, "mention_correction_required"); - assert.equal(plan.nextAction, "send_correction_dry_run"); - assert.deepEqual(plan.references, [{ botId: "222", mention: "<@222>", label: "ControllerBot", matchedAlias: "ControllerBot" }]); - assert.match(plan.correctionMessage, /^<@222> Controller:/); + assert.equal(plan.reason, "mention_correction_skipped_bot_source"); + assert.equal(plan.nextAction, "none"); + assert.equal(plan.references, undefined); + assert.equal(plan.correctionMessage, undefined); }); -test("mention correction planner does not correct already valid mentions or self references", () => { +test("mention correction skips bot-authored mentions and self references", () => { const cfg = { mode: "plan", discordAllowlist: [{ guildId: "g1", channelId: "c1" }], @@ -301,7 +301,7 @@ test("mention correction planner does not correct already valid mentions or self messageText: "Passo a ControllerBot <@222>." } }); - assert.equal(alreadyMentioned.reason, "mention_correction_not_needed"); + assert.equal(alreadyMentioned.reason, "mention_correction_skipped_bot_source"); assert.equal(alreadyMentioned.nextAction, "none"); const selfReference = planDiscordMentionCorrection(cfg, { @@ -311,7 +311,32 @@ test("mention correction planner does not correct already valid mentions or self messageText: "WorkerAlpha can continue alone." } }); - assert.equal(selfReference.reason, "mention_correction_not_needed"); + assert.equal(selfReference.reason, "mention_correction_skipped_bot_source"); +}); + +test("mention correction skips controller service text without creating pings", () => { + const cfg = { + mode: "plan", + discordAllowlist: [{ guildId: "g1", channelId: "c1" }], + bridge: { + participants: [ + { botId: "111", mention: "<@111>", label: "Agent Alpha", aliases: ["Agent Alpha"] }, + { botId: "222", mention: "<@222>", label: "Agent Beta", aliases: ["Agent Beta"] } + ] + } + }; + + const plan = planDiscordMentionCorrection(cfg, { + request: { + target: { guildId: "g1", channelId: "c1" }, + messageText: "Controller: Agent Gamma named Agent Alpha, Agent Beta without a valid Discord tag. The controller is applying the canonical tag so the turn can continue." + } + }); + + assert.equal(plan.accepted, true); + assert.equal(plan.reason, "mention_correction_skipped_controller_message"); + assert.equal(plan.nextAction, "none"); + assert.equal(plan.references, undefined); }); test("mention correction skips structured event task messages", () => { diff --git a/packages/runtime-wrapper/policy.js b/packages/runtime-wrapper/policy.js index 86b7841..419454a 100644 --- a/packages/runtime-wrapper/policy.js +++ b/packages/runtime-wrapper/policy.js @@ -404,6 +404,17 @@ export function planDiscordMentionCorrection(config, action, inheritedBase) { const normalizedTurn = normalizeBridgeTurn(request); const text = normalizedTurn.text; + if (/^\s*(?:<@!?\d+>\s*)*Controller:/i.test(text)) { + return { + ...base, + accepted: true, + reason: "mention_correction_skipped_controller_message", + nextAction: "none", + sideEffectsAllowed: false, + normalizedTurn + }; + } + if (cfg.mode === "observe") { return { ...base, @@ -448,6 +459,21 @@ export function planDiscordMentionCorrection(config, action, inheritedBase) { }; } + // Anti ping-loop guard: the mention-correction bridge used to derive + // Discord pings from free-form bot text. In mesh mode the body is human + // content, not routing. Bot/controller output must never generate fresh + // Discord mentions; only a separately validated routing/envelope layer may. + if (sourceBotId) { + return { + ...base, + accepted: true, + reason: "mention_correction_skipped_bot_source", + nextAction: "none", + sideEffectsAllowed: false, + normalizedTurn + }; + } + const references = findUntaggedParticipantReferences(cfg.bridge.participants, text, sourceBotId); if (!references.length) { return { diff --git a/packages/runtime-wrapper/test/policy.test.js b/packages/runtime-wrapper/test/policy.test.js index da111a6..bd0a065 100644 --- a/packages/runtime-wrapper/test/policy.test.js +++ b/packages/runtime-wrapper/test/policy.test.js @@ -290,7 +290,7 @@ test("address book includes labels, mentions, and aliases", () => { assert.match(formatAgentAddressBook(entries), /Agent Beta: <@222>/); }); -test("mention correction planner detects natural agent names without real Discord mention", () => { +test("mention correction skips bot-authored natural agent names without creating pings", () => { const cfg = { mode: "plan", discordAllowlist: [{ guildId: "g1", channelId: "c1" }], @@ -309,13 +309,13 @@ test("mention correction planner detects natural agent names without real Discor } }); assert.equal(plan.accepted, true); - assert.equal(plan.reason, "mention_correction_required"); - assert.equal(plan.nextAction, "send_correction_dry_run"); - assert.deepEqual(plan.references, [{ botId: "222", mention: "<@222>", label: "Agent Beta", matchedAlias: "Agent Beta" }]); - assert.match(plan.correctionMessage, /^<@222> Controller:/); + assert.equal(plan.reason, "mention_correction_skipped_bot_source"); + assert.equal(plan.nextAction, "none"); + assert.equal(plan.references, undefined); + assert.equal(plan.correctionMessage, undefined); }); -test("mention correction planner does not correct already valid mentions or self references", () => { +test("mention correction skips bot-authored mentions and self references", () => { const cfg = { mode: "plan", discordAllowlist: [{ guildId: "g1", channelId: "c1" }], @@ -333,7 +333,7 @@ test("mention correction planner does not correct already valid mentions or self messageText: "Passo a Agent Beta <@222>." } }); - assert.equal(alreadyMentioned.reason, "mention_correction_not_needed"); + assert.equal(alreadyMentioned.reason, "mention_correction_skipped_bot_source"); assert.equal(alreadyMentioned.nextAction, "none"); const selfReference = planDiscordMentionCorrection(cfg, { @@ -343,7 +343,32 @@ test("mention correction planner does not correct already valid mentions or self messageText: "Agent Alpha può continuare da solo." } }); - assert.equal(selfReference.reason, "mention_correction_not_needed"); + assert.equal(selfReference.reason, "mention_correction_skipped_bot_source"); +}); + +test("mention correction skips controller service text without creating pings", () => { + const cfg = { + mode: "plan", + discordAllowlist: [{ guildId: "g1", channelId: "c1" }], + bridge: { + participants: [ + { botId: "111", mention: "<@111>", label: "Agent Alpha", aliases: ["Agent Alpha"] }, + { botId: "222", mention: "<@222>", label: "Agent Beta", aliases: ["Agent Beta"] } + ] + } + }; + + const plan = planDiscordMentionCorrection(cfg, { + request: { + target: { guildId: "g1", channelId: "c1" }, + messageText: "Controller: Agent Gamma named Agent Alpha, Agent Beta without a valid Discord tag. The controller is applying the canonical tag so the turn can continue." + } + }); + + assert.equal(plan.accepted, true); + assert.equal(plan.reason, "mention_correction_skipped_controller_message"); + assert.equal(plan.nextAction, "none"); + assert.equal(plan.references, undefined); }); test("mention correction skips structured event task messages", () => { diff --git a/plugins/openclaw-agent-mesh/README.md b/plugins/openclaw-agent-mesh/README.md new file mode 100644 index 0000000..491a200 --- /dev/null +++ b/plugins/openclaw-agent-mesh/README.md @@ -0,0 +1,49 @@ +# Agent Mesh OpenClaw Plugin + +OpenClaw adapter package for Agent Mesh planning tools. + +This package wires the runtime-agnostic core into OpenClaw plugin tools. It is intentionally thin: real deployment-specific participants, channel ids, guild ids, paths, and feature flags should live in private host configuration, not in this package. + +## Current boundary + +Implemented here: + +- OpenClaw plugin registration as `agent-mesh-wrapper`. +- Dry-run planning tools for runtime actions, Discord bridge turns, event-task turns, and Mesh v1 pre-dispatch handling. +- Audit JSONL writes for tool invocations when audit is enabled. +- Generic, config-driven OpenClaw example config. + +Not implemented here yet: + +- A live inbound Discord interception hook before OpenClaw agent dispatch. +- Durable cross-message partial/final state persistence owned by OpenClaw runtime hooks. +- Automatic pre-dispatch rewrite/injection of assembled Mesh v1 content. +- Live send/forward side effects from the plugin itself. + +Until those hooks are wired, treat this package as the safe OpenClaw planning/adapter layer, not as the full live runtime path. + +## Mesh v1 finality rule + +Complete peer handoffs must use `final=1` in compact `ccm:v1` envelopes. Use `final=0` only for partial context chunks that should be buffered and must not dispatch the receiving agent yet. + +The runtime-neutral hydrator defaults compact messages to `final=1`; pass `--final 0` only when deliberately sending a partial chunk: + +```bash +node ../../scripts/mesh-hydrate.mjs \ + --compact \ + --to next-peer \ + --from current-peer \ + --id smoke-run \ + --body "Complete handoff body." +``` + +## Scripts + +```bash +npm run build +``` + +## Example Config + +See `examples/openclaw.config.example.jsonc`. + diff --git a/plugins/openclaw-agent-mesh/examples/openclaw.config.example.jsonc b/plugins/openclaw-agent-mesh/examples/openclaw.config.example.jsonc new file mode 100644 index 0000000..68dc755 --- /dev/null +++ b/plugins/openclaw-agent-mesh/examples/openclaw.config.example.jsonc @@ -0,0 +1,46 @@ +{ + "plugins": { + "agent-mesh-wrapper": { + "enabled": true, + "mode": "observe", + "dryRun": true, + "killSwitch": false, + "paused": false, + "allowRealCasDispatch": false, + "allowRealDiscordSend": false, + "localParticipant": "external-bot-a", + "discordAllowlist": [ + { + "guildId": "YOUR_DISCORD_GUILD_ID", + "channelId": "YOUR_DISCORD_CHANNEL_ID", + "threadId": "OPTIONAL_DISCORD_THREAD_ID" + } + ], + "casAllowlist": [ + { + "tempOnly": true, + "workspacePrefix": "/tmp/openclaw-agent-mesh-" + } + ], + "bridge": { + "maxTurns": 6, + "participants": [ + { + "botId": "EXTERNAL_BOT_A_ID", + "mention": "<@EXTERNAL_BOT_A_ID>", + "label": "external-bot-a" + }, + { + "botId": "EXTERNAL_BOT_B_ID", + "mention": "<@EXTERNAL_BOT_B_ID>", + "label": "external-bot-b" + } + ] + }, + "audit": { + "enabled": true, + "path": "runtime/agent-mesh-wrapper/audit.jsonl" + } + } + } +} diff --git a/plugins/openclaw-agent-mesh/openclaw.plugin.json b/plugins/openclaw-agent-mesh/openclaw.plugin.json new file mode 100644 index 0000000..2350390 --- /dev/null +++ b/plugins/openclaw-agent-mesh/openclaw.plugin.json @@ -0,0 +1,60 @@ +{ + "id": "agent-mesh-wrapper", + "name": "Agent Mesh Wrapper", + "description": "Planning wrapper for Agent Mesh runtime actions.", + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean", "default": true }, + "mode": { "type": "string", "enum": ["observe", "plan", "enforce"], "default": "observe" }, + "statePath": { "type": "string", "default": "runtime/agent-mesh-wrapper" }, + "sidecarPath": { "type": "string", "default": "../openclaw-agent-mesh" }, + "killSwitch": { "type": "boolean", "default": false }, + "paused": { "type": "boolean", "default": false }, + "dryRun": { "type": "boolean", "default": true }, + "allowRealCasDispatch": { "type": "boolean", "default": false }, + "allowRealDiscordSend": { "type": "boolean", "default": false }, + "localParticipant": { "type": "string", "description": "Local Mesh v1 participant label used for cc-mesh-turn gating." }, + "discordAllowlist": { + "type": "array", + "default": [], + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "guildId": { "type": "string" }, + "channelId": { "type": "string" }, + "threadId": { "type": "string" }, + "categoryId": { "type": "string" } + }, + "anyOf": [ + { "required": ["channelId"] }, + { "required": ["categoryId"] } + ] + } + }, + "casAllowlist": { + "type": "array", + "default": [{ "tempOnly": true, "workspacePrefix": "/tmp/" }], + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "tempOnly": { "type": "boolean", "default": true }, + "workspacePrefix": { "type": "string" }, + "repoScope": { "type": "string" } + } + } + }, + "audit": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean", "default": true }, + "path": { "type": "string", "default": "runtime/agent-mesh-wrapper/audit.jsonl" } + } + } + } + } +} diff --git a/plugins/openclaw-agent-mesh/package.json b/plugins/openclaw-agent-mesh/package.json new file mode 100644 index 0000000..489bb6e --- /dev/null +++ b/plugins/openclaw-agent-mesh/package.json @@ -0,0 +1,32 @@ +{ + "name": "@nestdevlab/agent-mesh-openclaw-plugin", + "version": "0.9.0", + "private": true, + "type": "module", + "description": "AgentWheel-installable OpenClaw plugin adapter for Agent Mesh.", + "scripts": { + "build": "node --check src/index.js" + }, + "openclaw": { + "extensions": [ + "./src/index.js" + ] + }, + "peerDependencies": { + "openclaw": ">=2026.3.24-beta.2" + }, + "dependencies": { + "@openclaw-agent-mesh/core": "file:../../packages/core" + }, + "main": "./src/index.js", + "exports": { + ".": "./src/index.js" + }, + "files": [ + "src", + "examples", + "openclaw.plugin.json", + "README.md" + ], + "license": "UNLICENSED" +} diff --git a/plugins/openclaw-agent-mesh/src/index.js b/plugins/openclaw-agent-mesh/src/index.js new file mode 100644 index 0000000..4c6a47e --- /dev/null +++ b/plugins/openclaw-agent-mesh/src/index.js @@ -0,0 +1,107 @@ +import { mkdir, appendFile } from "node:fs/promises"; +import path from "node:path"; +import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; +import { normalizeConfig, planDiscordBridgeTurn, planDiscordEventTaskTurn, planMeshV1Dispatch, planRuntimeAction } from "@openclaw-agent-mesh/core/policy"; + +async function appendAudit(cfg, record) { + if (!cfg.audit.enabled) return; + const fullPath = path.resolve(process.cwd(), cfg.audit.path); + await mkdir(path.dirname(fullPath), { recursive: true }); + await appendFile(fullPath, `${JSON.stringify({ ...record, observedAt: new Date().toISOString() })}\n`, "utf8"); +} + +export default definePluginEntry({ + id: "agent-mesh-wrapper", + name: "Agent Mesh Wrapper", + description: "Planning wrapper for Agent Mesh runtime actions", + register(api) { + const cfg = normalizeConfig(api.pluginConfig || {}); + if (!cfg.enabled) { + api.logger.info("agent-mesh-wrapper: disabled"); + return; + } + + api.registerTool(() => ({ + name: "agent_mesh_plan_runtime_action", + label: "Agent Mesh Plan Runtime Action", + description: "Plan an Agent Mesh runtime action through the wrapper policy without executing side effects.", + parameters: { + type: "object", + additionalProperties: true, + properties: { + kind: { type: "string", enum: ["cas_dispatch", "discord_send", "discord_bridge_turn", "discord_mention_correction", "discord_event_task_turn", "mesh_v1_dispatch"] } + }, + required: ["kind"] + }, + displaySummary: "Plan an Agent Mesh action without side effects.", + async execute(_toolCallId, input = {}) { + const plan = planRuntimeAction(cfg, input); + await appendAudit(cfg, { type: "agent_mesh.plan_runtime_action", input, plan }); + return { content: [{ type: "text", text: JSON.stringify(plan, null, 2) }] }; + } + }), { name: "agent_mesh_plan_runtime_action" }); + + api.registerTool(() => ({ + name: "agent_mesh_plan_discord_bridge_turn", + label: "Agent Mesh Plan Discord Bridge Turn", + description: "Validate and plan one controller-mediated Discord bot handoff without forwarding messages or executing side effects.", + parameters: { + type: "object", + additionalProperties: true, + properties: { + request: { type: "object", additionalProperties: true } + } + }, + displaySummary: "Validate a Discord bridge handoff without side effects.", + async execute(_toolCallId, input = {}) { + const plan = planDiscordBridgeTurn(cfg, input); + await appendAudit(cfg, { type: "agent_mesh.plan_discord_bridge_turn", input, plan }); + return { content: [{ type: "text", text: JSON.stringify(plan, null, 2) }] }; + } + }), { name: "agent_mesh_plan_discord_bridge_turn" }); + + api.registerTool(() => ({ + name: "agent_mesh_plan_discord_event_task_turn", + label: "Agent Mesh Plan Discord Event Task Turn", + description: "Validate and plan one event-driven Discord task turn without forwarding messages or executing side effects.", + parameters: { + type: "object", + additionalProperties: true, + properties: { + request: { type: "object", additionalProperties: true }, + state: { type: "object", additionalProperties: true } + } + }, + displaySummary: "Validate an event-driven Discord task turn without side effects.", + async execute(_toolCallId, input = {}) { + const plan = planDiscordEventTaskTurn(cfg, input); + await appendAudit(cfg, { type: "agent_mesh.plan_discord_event_task_turn", input, plan }); + return { content: [{ type: "text", text: JSON.stringify(plan, null, 2) }] }; + } + }), { name: "agent_mesh_plan_discord_event_task_turn" }); + + api.registerTool(() => ({ + name: "agent_mesh_plan_mesh_v1_dispatch", + label: "Agent Mesh Plan Mesh v1 Dispatch", + description: "Parse Mesh v1 headers and plan pre-dispatch buffering or exactly-once final dispatch without executing side effects.", + parameters: { + type: "object", + additionalProperties: true, + properties: { + text: { type: "string" }, + messageId: { type: "string" }, + state: { type: "object", additionalProperties: true } + }, + required: ["text"] + }, + displaySummary: "Plan Mesh v1 pre-dispatch handling without side effects.", + async execute(_toolCallId, input = {}) { + const plan = planMeshV1Dispatch(cfg, input); + await appendAudit(cfg, { type: "agent_mesh.plan_mesh_v1_dispatch", input, plan }); + return { content: [{ type: "text", text: JSON.stringify(plan, null, 2) }] }; + } + }), { name: "agent_mesh_plan_mesh_v1_dispatch" }); + + api.logger.info(`agent-mesh-wrapper: loaded in ${cfg.mode} mode, dryRun=${cfg.dryRun}`); + } +}); From 1925e1ef293839a93d4be07b51c2e3aa259018f3 Mon Sep 17 00:00:00 2001 From: Yehonal <147092+Yehonal@users.noreply.github.com> Date: Fri, 12 Jun 2026 11:55:40 +0000 Subject: [PATCH 2/2] docs: document OpenPack install flow Remove the legacy AgentWheel manifest and document OpenPack as the canonical package manifest, including optional OpenClaw plugin selection. --- README.md | 34 ++++++++++++++++++++++++++-------- agentwheel.json | 37 ------------------------------------- 2 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 agentwheel.json diff --git a/README.md b/README.md index 5130f5f..e5d6fa1 100644 --- a/README.md +++ b/README.md @@ -14,21 +14,39 @@ It merges the previously separate policy/plugin package, Discord gateway sidecar - `@openclaw-agent-mesh/runtime-wrapper` — OpenClaw runtime wrapper/plugin integration for sidecar rollout and dry-run safety. - `@openclaw-agent-mesh/tmux-bridge` — agnostic tmux bridge for CLI-to-CLI agent intercommunication, surfaced in the gateway as the `tmux-transport` adapter (a peer of the Discord adapter). See `docs/transports.md`. -## Agentwheel install +## AgentWheel / OpenPack install -This repo can be installed as an agentwheel package: +This repo is an OpenPack package for AgentWheel. `openpack.json` is the +canonical package manifest; the older `agentwheel.json` manifest is not used. + +Install the shared runtime-neutral instructions and tmux skills: ```bash npm i -g agentwheel agentwheel registry update -agentwheel add nestdev-mesh --adapter openclaw -agentwheel sync --dry-run -agentwheel sync +agentwheel add nestdev-mesh --adapter codex +agentwheel install --dry-run +agentwheel install +``` + +OpenClaw runtimes that need the optional mesh plugin should select it +explicitly instead of installing it everywhere: + +```bash +agentwheel install nestdev-mesh \ + --adapter openclaw \ + --select plugins/openclaw-agent-mesh \ + --dry-run + +# after reviewing the plan: +agentwheel install nestdev-mesh \ + --adapter openclaw \ + --select plugins/openclaw-agent-mesh ``` -The package currently provides the `claude-tmux` and `codex-tmux` skills from -`skills/`. With agentwheel asset-includes, each installed skill also receives -the bridge scripts and agent configs it needs under its own `bin/` and `agents/` +The package provides the `claude-tmux` and `codex-tmux` skills from `skills/`. +With AgentWheel asset-includes, each installed skill also receives the bridge +scripts and agent configs it needs under its own `bin/` and `agents/` directories. The canonical script source remains `packages/tmux-bridge/bin` in this repo; no generated script copies are committed. diff --git a/agentwheel.json b/agentwheel.json deleted file mode 100644 index 32ff2f5..0000000 --- a/agentwheel.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "schemaVersion": 1, - "name": "nestdev-mesh", - "version": "0.9.0", - "provides": [ - { - "type": "instructions", - "path": "AGENTS.md" - }, - { - "type": "skills", - "path": "skills", - "assets": [ - { - "from": "packages/tmux-bridge/bin", - "into": "bin", - "include": [ - "*.sh" - ], - "mode": "preserve" - }, - { - "from": "packages/tmux-bridge/agents", - "into": "agents", - "include": [ - "*.conf" - ], - "mode": "preserve" - } - ] - }, - { - "type": "plugins", - "path": "plugins" - } - ] -}