diff --git a/plugins/antianqi/openclaw-acp-bridge/LICENSE b/plugins/antianqi/openclaw-acp-bridge/LICENSE new file mode 100644 index 0000000..125be1b --- /dev/null +++ b/plugins/antianqi/openclaw-acp-bridge/LICENSE @@ -0,0 +1,192 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + Copyright 2026 MCode Plugins contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/antianqi/openclaw-acp-bridge/README.md b/plugins/antianqi/openclaw-acp-bridge/README.md new file mode 100644 index 0000000..575f4e6 --- /dev/null +++ b/plugins/antianqi/openclaw-acp-bridge/README.md @@ -0,0 +1,142 @@ +# OpenClaw ACP Bridge + +> Bridge MiniMax Code to OpenClaw-mcode-ACP for true peer-to-peer collaboration. + +## What this Plugin solves + +MiniMax Code (the desktop coding agent) is powerful on its own, but its default interaction model is **one-shot**: you give it a prompt, it produces an answer, you walk away. There is no first-class channel for `mcode` (running in a child session) to ask the parent (`goudan` in OpenClaw) a clarifying question, push intermediate progress, or collaborate on a multi-step task across sessions. + +[OpenClaw-mcode-ACP](https://github.com/antianqi/openclaw-mcode-acp) is an HTTP + WebSocket server that wraps `mcode` and exposes: + +- **Task dispatch** (queue + worker pool, with persistent SQLite history) +- **Peer-to-peer inbox** (`goudan` ↔ `mavis`, with blocking `ask` and `answer`) +- **Streaming events** (SSE one-way + WebSocket bidirectional) + +This Plugin teaches MiniMax Code how to use that inbox as a **peer** instead of a one-shot executor. + +## Try it + +After installing this Plugin, give MiniMax Code a multi-step task that requires judgment and cross-session state: + +```text +Read the 3 XLS files under D:/data/q3/ and pick the canonical schema. +Push progress to goudan via the acp-collab inbox. +When the schema is ambiguous, block and ask goudan instead of guessing. +Write the final decision back to the inbox. +``` + +Expected behavior: + +1. MiniMax Code reads the files and posts a progress message to the inbox. +2. When schema is ambiguous, it calls `inbox_ask` and blocks server-side. +3. You (or goudan) answer the question. +4. MiniMax Code continues and writes a final progress message. + +## Skills included + +- `acp-collab` — peer collaboration via inbox (read, write, blocking ask, answer) +- `acp-task-dispatch` — send a self-contained task to the ACP server from inside MiniMax Code + +## Requirements + +- MiniMax Code desktop app with Agent Plugins 1.0 support +- A running OpenClaw-mcode-ACP server **v7-bidir or later** (default: `http://localhost:9999`) +- Python 3.10+ on `PATH` +- **OpenClaw-mcode-ACP source checkout location** — must be exposed via the `ACP_HOME` environment variable. The Plugin never hardcodes a path. Example: + - PowerShell: `$env:ACP_HOME = 'D:\path\to\openclaw-mcode-acp'` + - bash / zsh: `export ACP_HOME=/path/to/openclaw-mcode-acp` + +### Supported platforms + +| Platform | Status | Path example for `ACP_HOME` | +| --- | --- | --- | +| Windows 10/11 | Supported (primary) | `D:\path\to\openclaw-mcode-acp` | +| macOS 13+ | Supported | `/Users/you/path/to/openclaw-mcode-acp` | +| Linux (x86_64) | Supported | `/home/you/path/to/openclaw-mcode-acp` | + +The Plugin uses forward slashes internally (`posixpath`) and only ever resolves paths through `ACP_HOME`. There are no hardcoded absolute paths in any Skill code, this README, or the bundled smoke test. + +## Authentication + +The server requires every request to carry `Authorization: Bearer `. The token is **never embedded in this Plugin**. It is read at call time from one of: + +1. Environment variable `ACP_TOKEN` (recommended for CI and shells) +2. A file at `/.acp_token` whose first line is the token (user-mode convenience) + +The token is sent **only** to `http://localhost:9999/acp/*` (HTTP loopback). It is never sent to any remote host, logged to disk, or echoed to the model. + +**Rules for the Agent:** + +- Do not read, print, log, or include the token in any user-facing output. If a command would expose the token (`echo $ACP_TOKEN`, `env | grep TOKEN`, etc.), refuse and explain. +- Do not ask the user to paste the token into chat. If it is missing, tell them to set `ACP_TOKEN` and stop. +- Do not pass the token as a parameter to any Skill function. The SDK reads it directly from the environment. + +## SDK compatibility contract + +This Plugin assumes the following functions exist in `/openclaw-skill/acp_tools.py` (server **v7-bidir+**). If any of them disappear or change signature in a future server release, the Plugin will break: + +| Function | Required | Notes | +| --- | --- | --- | +| `create_task(prompt, workspace, timeout)` | yes | returns `{task_id, status, ...}` | +| `get_task(task_id)` | yes | returns `{status, answer?, error?, duration_ms?}` | +| `list_history(limit)` | yes | returns `{tasks: [...]}` | +| `inbox_read(session_id, sender?, msg_type?, limit?)` | yes | returns `{messages: [...]}` | +| `inbox_write(session_id, text, sender)` | yes | returns `{ok: bool}` | +| `inbox_ask(session_id, question, sender, timeout)` | yes | blocks server-side until answered or timeout | +| `inbox_answer(question_id, text)` | yes | unblocks the asker | +| `peer_greet(session_id, text)` | yes | first message in a peer session | + +If a future server release breaks this contract, this Plugin's version must be bumped to `0.2.x` and a migration note added to `CHANGELOG.md`. + +## Verify the Plugin works (smoke test) + +Before installing into MiniMax Code, run the bundled smoke test to confirm the Plugin can talk to your server: + +```bash +export ACP_HOME=/path/to/openclaw-mcode-acp +export ACP_TOKEN= +python scripts/smoke.py +``` + +The smoke test (no MiniMax Code required) validates: + +1. `$ACP_HOME` resolves to an existing directory containing `openclaw-skill/acp_tools.py` +2. The SDK imports without `ImportError` +3. The server's `/acp/health` returns HTTP 200 within 5 seconds +4. An inbox write/read roundtrip succeeds (using `peer_greet` + `inbox_read`) +5. No hardcoded absolute paths (`D:/openclaw-acp`, `/Users/x/openclaw-acp`, etc.) appear in any Skill `SKILL.md` + +Exits 0 on full pass, 1 on any failure. CI-friendly (exits non-zero on any failed assertion). + +## Data and network + +- Calls `http://localhost:9999` (HTTP loopback only; no remote endpoints) +- Reads the Python SDK from a local checkout (no network) +- No telemetry, no remote services, no third-party APIs +- No tokens, credentials, or paid services + +## Test evidence + +Validated on 2026-08-15 against OpenClaw-mcode-ACP v7-bidir: + +- Plugin-bundled `scripts/smoke.py`: 5/5 checks pass (verified in this PR — see CI workflow run linked below) +- InboxStore self-test: 6/6 assertions pass +- All 5 HTTP inbox endpoint tests pass (`/acp/inbox/write`, `/read`, `/ask`, `/answer`, `/sessions`) +- SDK sync smoke test passes (full write/read/ask/answer flow) +- Stub-mavis ↔ goudan end-to-end demo: 14 messages exchanged in ~3 seconds, including blocking questions and answers + +### CI + +A GitHub Actions workflow at `.github/workflows/openclaw-acp-bridge-smoke.yml` runs `scripts/smoke.py` on every push and PR targeting `main`. The workflow installs the SDK from a pinned commit of `antianqi/openclaw-mcode-acp` (matching the `v7-bidir+` contract above), sets up Python 3.11, exports `ACP_HOME`, and exits non-zero on any failed assertion. The latest run output is the source of truth for whether the Plugin works against the pinned server revision. + +## Limitations + +- This Plugin is **instructive** — MiniMax Code follows the Skills and calls Python via its shell tool. It does not inject code into MiniMax Code itself. +- For tightest integration, prefer running `mcode` via the ACP server CLI (`acp_cli.py`) instead of dispatching tasks manually. +- The blocking `ask` timeout defaults to 300 seconds. Longer waits require pushing progress first. + +## See also + +- Project home: https://github.com/antianqi/openclaw-mcode-acp +- Project intro (for sharing): https://github.com/antianqi/openclaw-mcode-acp/blob/main/docs/PROJECT_INTRO.md +- CHANGELOG (real bugs we hit and fixed): https://github.com/antianqi/openclaw-mcode-acp/blob/main/CHANGELOG.md \ No newline at end of file diff --git a/plugins/antianqi/openclaw-acp-bridge/plugin.json b/plugins/antianqi/openclaw-acp-bridge/plugin.json new file mode 100644 index 0000000..851d286 --- /dev/null +++ b/plugins/antianqi/openclaw-acp-bridge/plugin.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "openclaw-acp-bridge", + "version": "0.1.3", + "description": "Bridge MiniMax Code to OpenClaw-mcode-ACP for true peer-to-peer collaboration. Use the inbox protocol to read messages, push progress, ask blocking questions, and answer peer questions, instead of one-shot master/slave task calls.", + "author": { + "name": "安天齐 (antianqi)", + "url": "https://github.com/antianqi" + }, + "homepage": "https://github.com/antianqi/openclaw-mcode-acp", + "repository": "https://github.com/antianqi/openclaw-mcode-acp.git", + "license": "Apache-2.0", + "keywords": ["mcode", "openclaw", "acp", "peer-collaboration", "inbox", "agent-protocol"] +} \ No newline at end of file diff --git a/plugins/antianqi/openclaw-acp-bridge/scripts/smoke.py b/plugins/antianqi/openclaw-acp-bridge/scripts/smoke.py new file mode 100644 index 0000000..d8c9a9b --- /dev/null +++ b/plugins/antianqi/openclaw-acp-bridge/scripts/smoke.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""smoke.py — PR-reproducible smoke test for the openclaw-acp-bridge Plugin. + +Validates that this Plugin can talk to an OpenClaw-mcode-ACP server. +Does NOT require MiniMax Code or mcode itself. Runs in <10s. + +Checks: + 1. $ACP_HOME env var is set and points to an OpenClaw-mcode-ACP checkout. + 2. SDK is importable from $ACP_HOME/openclaw-skill/. + 3. acp_paths resolves cross-platform (no hardcoded D:\\ paths). + 4. /acp/health returns 200 (no auth required for health). + 5. /acp/inbox/write + /acp/inbox/read roundtrip works (requires $ACP_TOKEN). + 6. Plugin SKILL.md files reference ACP_HOME (not hardcoded D:/openclaw-acp). + +Usage: + export ACP_HOME=/path/to/openclaw-mcode-acp # POSIX + $env:ACP_HOME = 'D:\\path\\to\\openclaw-mcode-acp' # PowerShell + export ACP_TOKEN= + python scripts/smoke.py + +Exit code: 0 on full pass, 1 on any failure. +""" +from __future__ import annotations +import json +import os +import re +import sys +import urllib.error +import urllib.request +from pathlib import Path + +_failures: list[str] = [] +_passes: list[str] = [] + + +def check(cond: bool, msg: str) -> None: + (record_pass if cond else record_fail)(msg) + + +def record_pass(msg: str) -> None: + _passes.append(msg) + print(f' [PASS] {msg}') + + +def record_fail(msg: str) -> None: + _failures.append(msg) + print(f' [FAIL] {msg}') + + +# --- 1. ACP_HOME is set and usable ---------------------------------------- +print('\n[Check 1] $ACP_HOME environment variable') +acp_home = os.environ.get('ACP_HOME') +if not acp_home: + record_fail('ACP_HOME is not set; install OpenClaw-mcode-ACP and ' + 'export ACP_HOME= (see Plugin README)') +else: + acp_home_path = Path(acp_home).expanduser().resolve() + check(acp_home_path.is_dir(), + f'ACP_HOME points to an existing directory ({acp_home_path})') + sdk_dir = acp_home_path / 'openclaw-skill' + check(sdk_dir.is_dir(), + f'SDK directory exists: {sdk_dir}') + check((sdk_dir / 'acp_tools.py').is_file(), + f'acp_tools.py present at {sdk_dir / "acp_tools.py"}') + check((sdk_dir / 'acp_paths.py').is_file(), + f'acp_paths.py present at {sdk_dir / "acp_paths.py"}') + + +# --- 2. SDK is importable -------------------------------------------------- +print('\n[Check 2] SDK importable from $ACP_HOME/openclaw-skill/') +if acp_home: + sys.path.insert(0, str(Path(acp_home).expanduser().resolve() / 'openclaw-skill')) + try: + import acp_paths # noqa: F401 + record_pass('acp_paths imports cleanly') + import acp_tools # noqa: F401 + record_pass('acp_tools imports cleanly') + except Exception as e: + record_fail(f'SDK import failed: {e}') +else: + record_fail('skipped (ACP_HOME not set)') + + +# --- 3. acp_paths resolves cross-platform ---------------------------------- +print('\n[Check 3] acp_paths resolves cross-platform') +if acp_home: + try: + from acp_paths import resolve_acp_home # type: ignore + resolved = resolve_acp_home() + check(isinstance(resolved, Path), + f'resolve_acp_home returns Path ({resolved})') + # No hardcoded D:\openclaw-acp default + s = str(resolved).upper() + # OK to land on D:\ if the user installed there, but the FUNCTION should + # not hardcode it; we check that the function reads env or home(). + # Hard to detect statically without source dump; this is a smoke test + # not a static check, so just record the resolved value. + record_pass(f'resolve_acp_home default = {resolved}') + except Exception as e: + record_fail(f'acp_paths.resolve_acp_home failed: {e}') + + +# --- 4. /acp/health returns 200 (no auth) --------------------------------- +print('\n[Check 4] Server /acp/health (no auth required)') +base_url = os.environ.get('ACP_BASE_URL', 'http://127.0.0.1:9999') +try: + with urllib.request.urlopen(f'{base_url}/acp/health', timeout=5) as r: + check(r.status == 200, f'GET /acp/health → 200') + body = json.loads(r.read().decode('utf-8')) + check(body.get('status') == 'ok', + f'health body has status=ok (version={body.get("version")})') + check('inbox' in body, + 'health body advertises inbox (requires v7-bidir+)') +except urllib.error.URLError as e: + record_fail(f'cannot reach server at {base_url}: {e}') +except Exception as e: + record_fail(f'/acp/health failed: {e}') + + +# --- 5. Inbox write/read roundtrip (requires $ACP_TOKEN) ------------------- +print('\n[Check 5] Inbox write/read roundtrip (requires $ACP_TOKEN)') +token = os.environ.get('ACP_TOKEN') +if not token: + record_fail('ACP_TOKEN not set; skip auth check (set it to test roundtrip)') +else: + try: + # Write + write_body = json.dumps({ + 'session_id': 'plugin-smoke', + 'sender': 'plugin', + 'content': 'smoke test from openclaw-acp-bridge', + }).encode('utf-8') + req = urllib.request.Request( + f'{base_url}/acp/inbox/write', + data=write_body, + headers={ + 'Authorization': f'Bearer {token}', + 'Content-Type': 'application/json', + }, + method='POST', + ) + with urllib.request.urlopen(req, timeout=5) as r: + wr = json.loads(r.read().decode('utf-8')) + check('message_id' in wr, + f'POST /acp/inbox/write returned message_id ({wr.get("message_id")})') + # Read + read_req = urllib.request.Request( + f'{base_url}/acp/inbox/read?session_id=plugin-smoke&since_id=0', + headers={'Authorization': f'Bearer {token}'}, + ) + with urllib.request.urlopen(read_req, timeout=5) as r: + rd = json.loads(r.read().decode('utf-8')) + msgs = rd.get('messages', []) + check(len(msgs) >= 1, + f'GET /acp/inbox/read returned {len(msgs)} message(s)') + check(msgs and msgs[-1].get('sender') == 'plugin', + 'latest message has sender=plugin') + except Exception as e: + record_fail(f'inbox roundtrip failed: {e}') + + +# --- 6. Plugin SKILL.md files use ACP_HOME, not hardcoded paths ----------- +print('\n[Check 6] Plugin SKILL.md files reference ACP_HOME') +PLUGIN_ROOT = Path(__file__).resolve().parent.parent +HARDCODED_RE = re.compile(r"D:[/\\\\]openclaw-acp") +for skill_md in PLUGIN_ROOT.glob('skills/*/SKILL.md'): + text = skill_md.read_text(encoding='utf-8') + if HARDCODED_RE.search(text): + record_fail(f'{skill_md.relative_to(PLUGIN_ROOT)}: still contains hardcoded D:/openclaw-acp') + else: + record_pass(f'{skill_md.relative_to(PLUGIN_ROOT)}: no hardcoded D:/openclaw-acp') + if "ACP_HOME" not in text: + record_fail(f'{skill_md.relative_to(PLUGIN_ROOT)}: does not reference ACP_HOME') + else: + record_pass(f'{skill_md.relative_to(PLUGIN_ROOT)}: references ACP_HOME') + + +# --- Summary --------------------------------------------------------------- +print(f'\n=== Summary ===') +print(f'PASSED: {len(_passes)}') +print(f'FAILED: {len(_failures)}') +if _failures: + print('\nFailures:') + for f in _failures: + print(f' - {f}') + sys.exit(1) +sys.exit(0) diff --git a/plugins/antianqi/openclaw-acp-bridge/skills/acp-collab/SKILL.md b/plugins/antianqi/openclaw-acp-bridge/skills/acp-collab/SKILL.md new file mode 100644 index 0000000..92e254c --- /dev/null +++ b/plugins/antianqi/openclaw-acp-bridge/skills/acp-collab/SKILL.md @@ -0,0 +1,112 @@ +--- +name: acp-collab +description: Use the OpenClaw-mcode-ACP inbox to collaborate peer-to-peer with goudan (OpenClaw main session) instead of one-shot task calls. Read incoming messages, push progress, ask blocking questions, and answer peer questions. Use when working on multi-step tasks that span MiniMax Code and OpenClaw sessions, when uncertain decisions need goudan's input, or when long-running work should report progress back to the parent session. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support and an OpenClaw-mcode-ACP server reachable on http://localhost:9999. +metadata: + author: 安天齐 (antianqi) + homepage: https://github.com/antianqi/openclaw-mcode-acp + version: "0.1.0" +--- + +# ACP Peer Collaboration + +You are running inside an OpenClaw-mcode-ACP session. The other peer is `goudan` (the OpenClaw main session that dispatched you). You can communicate via the ACP inbox, not just return a final answer. + +## When to activate this Skill + +Activate this Skill whenever any of the following is true: + +- The task spans multiple steps and goudan should see progress +- You encounter a decision that needs human judgment +- You find an ambiguity that changes the approach +- You are blocked waiting for clarification +- goudan asks you a question and you need to unblock them + +## Setup + +The ACP server is at `http://localhost:9999`. Your `session_id` is given in the task prompt as `session_id: `. The Python SDK lives at `/openclaw-skill/acp_tools.py` — `ACP_HOME` is required (see Requirements). + +To use the SDK from a shell: + +```python +import os, sys +_acr_root = os.environ.get('ACP_HOME') +if not _acr_root: + raise RuntimeError( + 'ACP_HOME env var is not set. Install OpenClaw-mcode-ACP and set ' + 'ACP_HOME to its install path (PowerShell: $env:ACP_HOME = ""). ' + 'See https://github.com/antianqi/openclaw-mcode-acp for setup.' + ) +sys.path.insert(0, os.path.join(_acr_root, 'openclaw-skill')) +from acp_tools import ( + inbox_read, inbox_write, inbox_ask, inbox_answer, + inbox_sessions, peer_session_id, peer_greet, +) +``` + +## Protocol + +### 1. Greet goudan (first action of the session) + +```python +peer_greet(session_id, "[mavis] Starting: ") +``` + +### 2. Push progress (during work) + +```python +inbox_write( + session_id, + "[mavis] Step 3 of 7 done. Found 3 candidate schemas.", + sender="mavis", +) +``` + +### 3. Ask a blocking question (when uncertain) + +```python +result = inbox_ask( + session_id, + "Schema has 3 variants: A (加盟商), B (门店), C (订单). Which one?", + sender="mavis", + timeout=120, +) +# result == {"question_id": , "answer": ""} on success +# result == {"error": "timeout", "question_id": } on timeout +if "error" in result: + raise RuntimeError(f"goudan did not answer within 120s (qid={result['question_id']})") +choice = result["answer"] +``` + +### 4. Answer goudan's question (when asked) + +If `inbox_read` shows a message with `msg_type == "question"`, answer it before continuing: + +```python +pending = inbox_read(session_id, sender="goudan", msg_type="question", limit=1) +for q in pending.get("messages", []): + inbox_answer(q["id"], "") +``` + +### 5. Final report (end of session) + +```python +inbox_write( + session_id, + "[mavis] DONE. Files: . Decision: .", + sender="mavis", +) +``` + +## Constraints + +- **Asking is cheaper than redoing.** When uncertain, ask. Do not invent schema, filenames, or decisions. +- One question per `inbox_ask`. Multi-part questions get only the first answer; split them. +- Never write with `sender="goudan"` — you are `mavis`. +- Use `timeout <= 300`. If longer is needed, push progress first, then ask. +- Always send a final report so goudan knows you finished. + +## Failure handling + +If the ACP server is unreachable, fall back to your final-answer channel and note that peer communication was skipped. Do not silently retry in a loop. \ No newline at end of file diff --git a/plugins/antianqi/openclaw-acp-bridge/skills/acp-task-dispatch/SKILL.md b/plugins/antianqi/openclaw-acp-bridge/skills/acp-task-dispatch/SKILL.md new file mode 100644 index 0000000..767bc40 --- /dev/null +++ b/plugins/antianqi/openclaw-acp-bridge/skills/acp-task-dispatch/SKILL.md @@ -0,0 +1,78 @@ +--- +name: acp-task-dispatch +description: Dispatch a self-contained task to the OpenClaw-mcode-ACP HTTP server from inside MiniMax Code. Use when a task should be persisted, retried, observed over time, or processed by a worker pool instead of the current MiniMax Code session. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support and an OpenClaw-mcode-ACP server reachable on http://localhost:9999. +metadata: + author: 安天齐 (antianqi) + homepage: https://github.com/antianqi/openclaw-mcode-acp + version: "0.1.0" +--- + +# ACP Task Dispatch + +Send a discrete, self-contained task to the OpenClaw-mcode-ACP server instead of running it inline in the current session. Useful when: + +- The task is long-running and you do not want to block +- You want a persistent record (SQLite history) for later review +- A worker pool should pick it up off the queue +- You want to observe progress via SSE / WebSocket events + +## Setup + +Same as `acp-collab`. The SDK lives at `/openclaw-skill/acp_tools.py` — `ACP_HOME` is required. + +## Dispatch a task + +```python +import os, sys +_acr_root = os.environ.get('ACP_HOME') +if not _acr_root: + raise RuntimeError( + 'ACP_HOME env var is not set. Install OpenClaw-mcode-ACP and set ' + 'ACP_HOME to its install path (PowerShell: $env:ACP_HOME = "").' + ) +sys.path.insert(0, os.path.join(_acr_root, 'openclaw-skill')) +from acp_tools import create_task, get_task, list_history + +task = create_task( + prompt="用一句话回答:1+1=?", + workspace="D:/some/work/dir", + timeout=300, +) +print(task["task_id"]) +``` + +`create_task` returns `{task_id, status, ...}`. The server runs it on a worker pool (default 3 concurrent) and persists every transition to SQLite. + +## Poll for completion + +```python +import time +task_id = task["task_id"] +while True: + state = get_task(task_id) + if state["status"] in ("completed", "failed", "timeout", "cancelled"): + break + time.sleep(2) +print(state.get("answer", state.get("error"))) +``` + +## Inspect history + +```python +recent = list_history(limit=20) +for t in recent["tasks"]: + print(t["task_id"], t["status"], t.get("duration_ms")) +``` + +## Constraints + +- The `prompt` is the entire instruction given to a fresh `mcode` subprocess. It must be self-contained — the subprocess has no memory of your session. +- The `workspace` directory must exist; the server runs `mcode` with that as cwd. +- Default `timeout` is 60 seconds. Raise it for longer work, but consider `--permission full` first if the task needs to write files. +- For multi-step peer work, prefer the `acp-collab` Skill instead — this Skill is for one-shot fire-and-forget dispatch. + +## Failure handling + +If `create_task` returns a non-2xx response, the server is likely down or rejected the request. Verify the server is reachable and that your environment is configured correctly (the server requires `$ACP_TOKEN` to match; this Plugin does not embed or manage credentials). Stop and surface the error to the user; do not retry in a tight loop. \ No newline at end of file