Skip to content

feat: add remote-acpx ACP runtime backend for node dispatch#1

Open
aehrt55 wants to merge 3963 commits intomainfrom
marxbiotech/remote-acp
Open

feat: add remote-acpx ACP runtime backend for node dispatch#1
aehrt55 wants to merge 3963 commits intomainfrom
marxbiotech/remote-acp

Conversation

@aehrt55
Copy link
Copy Markdown

@aehrt55 aehrt55 commented Mar 16, 2026

Summary

  • Add pub/sub bridge (acp-node-event-bridge) using Symbol.for globalThis for cross-module-loader state sharing
  • Add Mac-side ACP handler (invoke-acp) that spawns acpx subprocess per turn, streaming ndjson back as events
  • Add plugin-sdk/remote-acpx surface for the remote-acpx extension
  • Add claude-node extension with Claude Code CLI dispatch skill
  • Wire acp.* events through node-host runner and gateway event handler
  • Fix /acp spawn to resolve agents.list entries with runtime.type=acp
  • Fix @marxbiotech/openclaw package name in version/update checks

Key changes

  • src/gateway/acp-node-event-bridge.ts — Singleton pub/sub bridge (gateway ↔ extension)
  • src/node-host/invoke-acp.ts — Mac-side acp.spawn/acp.turn/acp.kill handler
  • src/gateway/server-node-events.ts — Route acp.spawned/acp.message/acp.exited/acp.error to bridge
  • src/auto-reply/reply/commands-acp/shared.ts — Resolve agents.list entries for spawn

Test plan

  • Deployed to magata-shiki with @marxbiotech/openclaw@2026.3.12-2
  • Verified make node connects Mac to remote gateway
  • Verified /acp spawn claude dispatches to Mac node via remote-acpx
  • Verified acp.turn executes acpx claude on Mac and streams response back

🤖 Generated with Claude Code

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances OpenClaw's capability to integrate with external execution environments by introducing a remote ACP backend. It enables the system to offload AI agent tasks, specifically Claude Code CLI operations, to dedicated Mac nodes. This architectural change facilitates distributed processing and expands the range of tools and environments OpenClaw can interact with, improving flexibility and performance for complex development tasks.

Highlights

  • Remote ACP Backend: Introduced a new remote-acpx ACP runtime backend to enable dispatching tasks to remote Mac nodes.
  • Claude Code Extension: Added a claude-node extension that allows OpenClaw to dispatch Claude Code CLI tasks to a paired Mac node.
  • Cross-Module Communication: Implemented a pub/sub bridge (acp-node-event-bridge) using Symbol.for for robust cross-module-loader state sharing between the gateway and extensions.
  • Mac-side ACP Handler: Developed a Mac-side ACP handler (invoke-acp) responsible for spawning acpx subprocesses per turn and streaming NDJSON events back to the gateway.
  • Agent Resolution Enhancement: Fixed the /acp spawn command to correctly resolve agents.list entries where runtime.type is set to acp.
  • Package Name Update: Corrected the package name @marxbiotech/openclaw in version and update checks across the system.
Changelog
  • .gitignore
    • Added a new entry to ignore marxbiotech-openclaw-*.tgz files.
  • extensions/claude-node/index.ts
    • Added the main plugin definition for the Claude Code (Node) extension, registering its tool and prompt guidance.
  • extensions/claude-node/openclaw.plugin.json
    • Defined the configuration schema and metadata for the claude-node plugin.
  • extensions/claude-node/package.json
    • Created the package manifest for the @openclaw/claude-node extension.
  • extensions/claude-node/skills/claude_code/SKILL.md
    • Provided detailed documentation for the claude_code tool, including its usage, parameters, and session management.
  • extensions/claude-node/src/prompt-guidance.ts
    • Defined the system context guidance for the claude_code tool, instructing agents on when and how to use it.
  • extensions/claude-node/src/tool.ts
    • Implemented the createClaudeCodeTool function, handling the logic for dispatching Claude Code CLI commands to a node and parsing its JSON output.
  • package.json
    • Updated the main package name from openclaw to @marxbiotech/openclaw and incremented the version.
    • Added new export map entries for plugin-sdk/remote-acpx and plugin-sdk/claude-node.
  • pnpm-lock.yaml
    • Updated the lockfile to include the @sinclair/typebox dependency for the claude-node extension.
  • scripts/write-plugin-sdk-entry-dts.ts
    • Included remote-acpx in the list of plugin SDK entrypoints for TypeScript declaration file generation.
  • src/auto-reply/reply/commands-acp/shared.ts
    • Modified parseSpawnInput to allow resolving agents.list entries with runtime.type=acp for /acp spawn.
    • Updated the help text for the /acp spawn command to reflect the new agent resolution logic.
  • src/cli/update-cli/shared.ts
    • Added @marxbiotech/openclaw to the set of core package names for update checks.
  • src/gateway/acp-node-event-bridge.ts
    • Created a new pub/sub bridge to facilitate communication between the remote-acpx extension and node WebSocket events, using Symbol.for for global state sharing.
  • src/gateway/server-node-events.ts
    • Integrated the new ACP node event handler to process acp.spawned, acp.message, acp.exited, and acp.error events from nodes.
  • src/gateway/server.impl.ts
    • Registered the ACP node event sender, checker, and list provider with the gateway server.
  • src/infra/openclaw-root.ts
    • Added @marxbiotech/openclaw to the set of core package names for root directory resolution.
  • src/infra/update-runner.ts
    • Included @marxbiotech/openclaw in the core package names for update runner logic.
  • src/node-host/invoke-acp.ts
    • Implemented the Mac-side handleAcpEvent function, which manages the lifecycle of acpx subprocesses for acp.spawn, acp.turn, and acp.kill events.
  • src/node-host/runner.ts
    • Imported handleAcpEvent and added acp capabilities and commands (acp.spawn, acp.turn, acp.kill) to the node host.
    • Integrated handleAcpEvent into the node host's event processing logic for ACP-related events.
  • src/plugin-sdk/claude-node.ts
    • Defined a narrow plugin SDK surface specifically for the bundled claude-node plugin, exporting relevant types and utilities.
  • src/plugin-sdk/remote-acpx.ts
    • Created a new plugin SDK surface for the remote-acpx extension, re-exporting ACP runtime types and the new node event bridge utilities.
  • src/version.ts
    • Updated version resolution logic to include @marxbiotech/openclaw as a core package name.
  • tsconfig.plugin-sdk.dts.json
    • Added src/plugin-sdk/remote-acpx.ts to the TypeScript configuration for plugin SDK declaration file generation.
  • tsdown.config.ts
    • Included remote-acpx in the list of plugin SDK entrypoints for the build configuration.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a remote ACP runtime backend for node dispatch, including a new claude-node extension. The changes are extensive, adding a pub/sub bridge for state sharing, a Mac-side ACP handler, and wiring everything through the gateway. The implementation is generally solid and robust. I have one suggestion regarding type consistency in the new claude-node tool to improve maintainability.

Comment thread extensions/claude-node/src/tool.ts Outdated
);

// Parse result
const payload = result?.payload ?? (result as NodeInvokeResult["payload"]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic to extract payload from result is a bit confusing and suggests an inconsistent API response from callGatewayTool.

const payload = result?.payload ?? (result as NodeInvokeResult["payload"]);

The type of result is NodeInvokeResult | null | undefined, where NodeInvokeResult is { payload?: ... }. The expression result?.payload should be sufficient to access the payload. The fallback (result as NodeInvokeResult["payload"]) implies that result itself might sometimes be the payload object, which is inconsistent with the NodeInvokeResult type.

This pattern can lead to confusion and potential runtime errors. For better maintainability and type safety, I recommend making the return type of callGatewayTool consistent.

If result is always NodeInvokeResult | null | undefined, this line could be simplified to:

const payload = result?.payload;

If the current implementation is necessary due to the API's behavior, a comment explaining this would be very helpful for future maintainers.

@aehrt55 aehrt55 force-pushed the marxbiotech/remote-acp branch 11 times, most recently from f47e3ee to bd58347 Compare March 20, 2026 13:10
劉超 and others added 17 commits March 29, 2026 00:57
… token

collectStatusIssues previously checked account.channelAccessToken directly,
but this field is stripped by projectSafeChannelAccountSnapshotFields for
security. This caused 'openclaw status' to always report WARN even when the
token is valid and the LINE provider starts successfully.

Use account.configured instead, which is already computed by
buildChannelAccountSnapshot and correctly reflects whether credentials
are present. This is consistent with how other channels (e.g. Telegram)
implement their status checks.

Fixes openclaw#45693
Webhook channels (LINE, Zalo, Nextcloud Talk, BlueBubbles) are
incorrectly flagged as stale-socket during quiet periods because
snapshot.mode is always undefined, making the mode !== "webhook"
guard in evaluateChannelHealth dead code.

Add mode: "webhook" to each webhook plugin's describeAccount and
propagate described.mode in getRuntimeSnapshot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The QMD memory system uses a fixed 4:1 chars-to-tokens ratio for chunk
sizing, which severely underestimates CJK (Chinese/Japanese/Korean) text
where each character is roughly 1 token. This causes oversized chunks for
CJK users, degrading vector search quality and wasting context window space.

Changes:
- Add shared src/utils/cjk-chars.ts module with CJK-aware character
  counting (estimateStringChars) and token estimation helpers
- Update chunkMarkdown() in src/memory/internal.ts to use weighted
  character lengths for chunk boundary decisions and overlap calculation
- Replace hardcoded estimateTokensFromChars in the context report
  command with the shared utility
- Add 13 unit tests for the CJK estimation module and 5 new tests for
  CJK-aware memory chunking behavior

Backward compatible: pure ASCII/Latin text behavior is unchanged.

Closes openclaw#39965
Related: openclaw#40216
- Use code-point length instead of UTF-16 length in estimateStringChars()
  so that CJK Extension B+ surrogate pairs (U+20000+) are counted as 1
  character, not 2 (fixes ~25% overestimate for rare characters).

- Change long-line split step from maxChars to chunking.tokens so that
  CJK lines are sliced into token-budget-sized segments instead of
  char-budget-sized segments that produce ~4x oversized chunks.

- Add tests for both fixes: surrogate-pair handling and long CJK line
  splitting.

Addresses review feedback from Greptile and Codex bots.
…nsistency

- Two-pass line splitting: first slice at maxChars (unchanged for Latin),
  then re-split only CJK-heavy segments at chunking.tokens. This preserves
  the original ~800-char segments for ASCII lines while keeping CJK chunks
  within the token budget.

- Narrow surrogate-pair adjustment to CJK Extension B+ range (D840–D87E)
  only, so emoji surrogate pairs are not affected. Mixed CJK+emoji text
  is now handled consistently regardless of composition.

- Add tests: emoji handling (2), Latin backward-compat long-line (1).

Addresses Codex P1 (oversized CJK segments) and P2s (Latin over-splitting,
emoji surrogate inconsistency).
When re-splitting CJK-heavy segments at chunking.tokens, check whether the
slice boundary falls on a high surrogate (0xD800–0xDBFF) and if so extend
by one code unit to keep the pair intact.  Prevents producing broken
surrogate halves for CJK Extension B+ characters (U+20000+).

Add test verifying no lone surrogates appear when splitting lines of
surrogate-pair characters with an odd token budget.

Addresses third-round Codex P2 review comment.
@aehrt55 aehrt55 force-pushed the marxbiotech/remote-acp branch 7 times, most recently from 37febed to 2527807 Compare March 30, 2026 05:02
aehrt55 and others added 4 commits March 30, 2026 13:14
Cherry-picked from bd58347, resolved conflicts for v2026.3.28 base.
…ntRuntimeAcpConfig

Cherry-picked from 21b117e. Adapted for v2026.3.28 refactored
session-utils: added acpCwd field to GatewaySessionRow (populated from
SessionEntry.acp.cwd) since the intermediate entry object was removed
in the single-pass buildGatewaySessionRow refactor.
AgentSummary now surfaces the runtime descriptor (type, acp.cwd,
acp.nodeName, etc.) so that downstream consumers like the
coding-assistant skill can resolve the correct remote working
directory for ACP agents instead of getting the gateway-local
workspace path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aehrt55 aehrt55 force-pushed the marxbiotech/remote-acp branch from 2527807 to dd41b3a Compare March 30, 2026 05:15
@aehrt55 aehrt55 deployed to npm-release March 30, 2026 05:20 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.