Skip to content

feat: add RuntimeAdapter interface and OpenClawGatewayAdapter (Phase 1)#499

Open
HiddenPuppy wants to merge 3 commits into
mainfrom
fix/issue-498
Open

feat: add RuntimeAdapter interface and OpenClawGatewayAdapter (Phase 1)#499
HiddenPuppy wants to merge 3 commits into
mainfrom
fix/issue-498

Conversation

@HiddenPuppy
Copy link
Copy Markdown
Collaborator

Summary

Implements Phase 1 of the ClawWork Next RuntimeAdapter architecture (issue #498). Creates a runtime-agnostic RuntimeAdapter interface and wraps the existing OpenClaw Gateway transport behind it with OpenClawGatewayAdapter.

Changes

  • packages/shared/src/runtime-adapter.ts — New file with RuntimeAdapter interface, RuntimeCapabilities, ExecutionRef, normalized ExecutionEvent types, and ExecutionRegistry
  • packages/core/src/ports/runtime-adapter-port.ts — New port type for dependency injection
  • packages/core/src/services/openclaw-gateway-adapter.ts — New adapter that wraps GatewayTransportPort behind RuntimeAdapter, bridging OpenClaw Gateway events (chat/agent/approval) into unified ExecutionEvent format
  • packages/shared/src/index.ts — Export new types
  • packages/core/src/index.ts — Export new types

Testing

Existing test suite passes. No user-visible behavior change — the existing GatewayTransportPort-based flow remains the default direct mode.

Fixes #498

Implements Phase 1 of the ClawWork Next RuntimeAdapter architecture.

- Adds shared RuntimeAdapter interface and normalized ExecutionEvent types
- Creates RuntimeAdapterPort for dependency injection
- Implements OpenClawGatewayAdapter wrapping GatewayTransportPort
- Bridges OpenClaw Gateway events (chat/agent/approval) to unified events
- No user-visible behavior change; backward compatible

Fixes #498
@HiddenPuppy HiddenPuppy requested a review from samzong as a code owner May 25, 2026 02:46
@github-actions
Copy link
Copy Markdown
Contributor

Hi @HiddenPuppy,
Thanks for your pull request!
If the PR is ready, use the /auto-cc command to assign Reviewer to Review.
We will review it shortly.

Details

Instructions for interacting with me using comments are available here.
If you have questions or suggestions related to my behavior, please file an issue against the gh-ci-bot repository.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

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 implements the first phase of the ClawWork Next RuntimeAdapter architecture. By decoupling the core services from specific transport implementations, it establishes a standardized interface for interacting with various agent runtimes. The changes provide a unified event system and abstraction layer, ensuring that future runtime integrations can be added without modifying core business logic.

Highlights

  • RuntimeAdapter Interface: Introduced a new RuntimeAdapter interface in packages/shared to abstract agent runtime interactions, including capabilities, execution events, and lifecycle management.
  • OpenClawGatewayAdapter Implementation: Implemented OpenClawGatewayAdapter to wrap the existing GatewayTransportPort, normalizing gateway events into the new unified ExecutionEvent format.
  • Dependency Injection: Added RuntimeAdapterPort to packages/core to facilitate runtime-agnostic service development.
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 the 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 counterproductive. 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.

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
Contributor

@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 new RuntimeAdapter abstraction layer to decouple the core service logic from specific agent runtimes like OpenClaw Gateway or ACP. It includes the definition of the RuntimeAdapter interface and capabilities in the shared package, a dependency injection port in the core package, and a concrete implementation for the OpenClaw Gateway. Feedback focuses on critical reliability and logic issues: unsafe JSON parsing of tool arguments could crash the event listener, a bug in the unsubscription logic prevents re-subscribing to gateway events, and inconsistent event mapping for approval resolutions may break UI correlation.

toolCallId: toolData.toolCallId,
name: toolData.name,
status: status as 'running' | 'done' | 'error',
args: toolData.args ? JSON.parse(toolData.args) : undefined,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Using JSON.parse directly on data received from the gateway is unsafe. If the gateway sends malformed JSON or an empty string in the args field, this will throw an exception and crash the entire event listener for this adapter. Consider using a safe JSON parsing utility or wrapping this in a try-catch block.

Suggested change
args: toolData.args ? JSON.parse(toolData.args) : undefined,
args: toolData.args ? (() => { try { return JSON.parse(toolData.args); } catch { return undefined; } })() : undefined,

Comment on lines +265 to +268
if (listeners.size === 0) {
removeGatewayEvent();
removeGatewayStatus();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

This cleanup logic introduces a bug. Once removeGatewayEvent() and removeGatewayStatus() are called (when the last listener unsubscribes), the transport listeners are permanently detached for this adapter instance. If a new listener is added later via onExecutionEvent, it will never receive events because the transport connection is not re-established.

Since this adapter is intended to be a long-lived service wrapping the transport, it is safer to remove this premature cleanup or implement a reference-counting mechanism that re-subscribes when the first listener is added.

Suggested change
if (listeners.size === 0) {
removeGatewayEvent();
removeGatewayStatus();
}
if (listeners.size === 0) {
// Avoid permanent unsubscription here as it prevents re-subscription later.
// If cleanup is required, implement ref-counting to re-establish listeners.
}

Comment on lines +174 to +182
const payload = data.payload as { id: string; decision?: string };
emit({
type: 'execution.approval.resolved',
executionId: data.gatewayId,
runtimeId: data.gatewayId,
timestamp: Date.now(),
id: payload.id,
decision: payload.decision,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The execution.approval.resolved event is missing the sessionKey and uses data.gatewayId as the executionId. This is inconsistent with the execution.approval.requested event (lines 161-172) and will likely break correlation in the UI if the approval was associated with a specific session. You should extract the sessionKey from the payload if available.

      const payload = data.payload as { id: string; decision?: string };
      const sessionKey = (data.payload as Record<string, unknown>).sessionKey as string | undefined;
      emit({
        type: 'execution.approval.resolved',
        executionId: sessionKey || data.gatewayId,
        runtimeId: data.gatewayId,
        sessionKey,
        timestamp: Date.now(),
        id: payload.id,
        decision: payload.decision,
      });

ClawWork Bot added 2 commits May 25, 2026 10:56
The type definition had maxProtocol: 3 (literal) but the code passes
maxProtocol: 4. This was introduced by commit ea65c56 which widened
the protocol range but didn't update the type. Fixes the CI test failure.

Related: #498, #499
PWA gateway client still used maxProtocol: 3 after the protocol type
was updated to 4. Matches the Desktop fix.

Related: #498, #499
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.

Plan ClawWork Next RuntimeAdapter path for ACP multi-agent support

1 participant