feat: add OpenClaw as an agentic workflow engine#16295
Conversation
Add OpenClaw agent platform as a new experimental engine for gh-aw, enabling workflows to use OpenClaw's ACP-based tool system. New files: - openclaw_engine.go: Main engine implementation with firewall support - openclaw_mcp.go: MCP configuration rendering via JSON format - parse_openclaw_log.cjs: JavaScript log parser for structured JSON output - 14 unit tests, 5 compilation tests, 42 JS tests - 3 sample workflows (PR review, issue triage, code quality) - Smoke test workflow for CI validation Modified files: - constants.go: Version, engine name, env vars for OpenClaw - domains.go: Default domains and domain helper functions - firewall.go: Auto-enable firewall for OpenClaw engine - agentic_engine.go: Engine registration - compiler_orchestrator_engine.go: Firewall enablement call - main_workflow_schema.json: Added openclaw to engine enum - Updated test counts across affected test files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds OpenClaw as a new (experimental) agentic workflow engine alongside existing Claude/Codex/Copilot engines, including compiler + firewall defaults, MCP rendering, and log parsing for structured JSON output.
Changes:
- Introduces the OpenClaw engine implementation (install, execution, firewall integration, MCP config rendering, log metrics parsing).
- Adds a new JS log parser (
parse_openclaw_log) with extensive tests and wires it into workflows. - Updates constants, schema, engine registry, domain allowlists, and various tests/sample workflows to include the new engine.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/openclaw_engine.go | Implements the OpenClaw engine execution/install behavior, env wiring, firewall wrapping, and log metrics parsing. |
| pkg/workflow/openclaw_mcp.go | Renders MCP server configuration for OpenClaw using the shared JSON renderer. |
| pkg/workflow/openclaw_engine_test.go | Unit tests for OpenClaw engine properties, steps generation, constants, and log metrics parsing. |
| pkg/workflow/compiler_openclaw_test.go | Integration tests validating compilation output for OpenClaw workflows across key scenarios. |
| actions/setup/js/parse_openclaw_log.cjs | JS parser for OpenClaw --json logs, producing step summary markdown + structured logEntries. |
| actions/setup/js/parse_openclaw_log.test.cjs | Vitest suite for OpenClaw log parser behavior across many log shapes and error cases. |
| pkg/constants/constants.go | Adds OpenClaw engine constants, default version, model env vars, and engine option metadata. |
| pkg/constants/constants_test.go | Updates expected agentic engine list to include OpenClaw. |
| pkg/workflow/domains.go | Adds OpenClaw default domains + helper functions, and hooks it into sanitization domain computation. |
| pkg/workflow/firewall.go | Adds OpenClaw-specific “enable firewall by default” hook mirroring existing engine behavior. |
| pkg/workflow/agentic_engine.go | Registers the OpenClaw engine in the engine registry. |
| pkg/workflow/agentic_engine_test.go | Updates engine registry tests to account for the additional built-in engine. |
| pkg/workflow/compiler_orchestrator_engine.go | Enables firewall-by-default behavior for OpenClaw when network restrictions are present. |
| pkg/workflow/imported_steps_validation_test.go | Makes strict-mode secret error assertion tolerant to OpenClaw/Claude wording ordering. |
| pkg/parser/schemas/main_workflow_schema.json | Adds openclaw to the engine enum (string and object forms) with updated descriptions. |
| pkg/cli/workflows/test-openclaw-review.md | Adds a sample OpenClaw workflow for PR review. |
| pkg/cli/workflows/test-openclaw-issue-triage.md | Adds a sample OpenClaw workflow for issue triage. |
| pkg/cli/workflows/test-openclaw-code-quality.md | Adds a sample OpenClaw workflow for code quality analysis. |
| pkg/cli/completions_test.go | Updates engine name completion test expectations to include OpenClaw. |
| .github/workflows/smoke-openclaw.md | Adds an OpenClaw smoke-test workflow definition. |
| .github/workflows/smoke-openclaw.lock.yml | Generated lockfile output for the OpenClaw smoke-test workflow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for _, line := range lines { | ||
| // Count tool calls from JSON output | ||
| if strings.Contains(line, "\"type\":\"tool_call\"") || strings.Contains(line, "\"type\": \"tool_call\"") { | ||
| // Try to extract tool name | ||
| toolName := "unknown" | ||
| if idx := strings.Index(line, "\"name\":\""); idx != -1 { |
There was a problem hiding this comment.
ParseLogMetrics only detects tool calls by searching for "type":"tool_call" (and a single spaced variant). The JS parser also treats tool_use as a tool invocation, so OpenClaw runs that emit tool_use (or use tool/function fields for names) won’t be counted in Go metrics.
Consider handling both tool_call and tool_use (and extracting the tool name from name/tool/function), and using FinalizeToolCallsAndSequence/FinalizeToolMetrics to produce stable, sorted output.
| // Test with tool calls in JSON | ||
| logContent := `{"type":"tool_call","name":"test_tool"} | ||
| {"type":"message","content":"thinking..."} | ||
| {"type":"tool_call","name":"another_tool"}` | ||
| metrics = engine.ParseLogMetrics(logContent, false) | ||
| if len(metrics.ToolCalls) != 2 { |
There was a problem hiding this comment.
TestOpenClawEngine_ParseLogMetrics exercises only the tool_call JSON shape. Since the OpenClaw log parser (JS) also supports tool_use, adding a test case for tool_use lines would ensure Go metrics stay in sync with the supported log formats.
| case "tool_result": { | ||
| const result = entry.result || entry.output || entry.content || ""; | ||
| const resultStr = typeof result === "string" ? result : JSON.stringify(result, null, 2); | ||
| const isError = entry.is_error || entry.error || false; | ||
| const toolUseId = `tool_result_${logEntries.length}`; | ||
|
|
||
| logEntries.push({ | ||
| type: "user", | ||
| message: { | ||
| content: [ | ||
| { | ||
| type: "tool_result", | ||
| tool_use_id: toolUseId, | ||
| content: resultStr, | ||
| is_error: isError, | ||
| }, | ||
| ], | ||
| }, | ||
| }); |
There was a problem hiding this comment.
tool_result entries are emitted with a fresh tool_use_id (tool_result_${logEntries.length}), which will never match the id generated for the preceding tool_use entry. This breaks log_parser_shared.cjs pairing logic (it joins tool results to tool calls by matching tool_use_id to the tool_use id), so tool results won’t render under their corresponding tool call.
Track the most recent (or a queue/stack of) generated tool_use IDs and reuse that ID for the subsequent tool_result entry (or use an ID from the OpenClaw JSON if one exists). Also coerce is_error to a boolean (e.g., Boolean(entry.is_error || entry.error)), since entry.error can be a string.
|
|
||
| expect(result.logEntries.length).toBe(2); | ||
| expect(result.logEntries[1].type).toBe("user"); | ||
| expect(result.logEntries[1].message.content[0].type).toBe("tool_result"); |
There was a problem hiding this comment.
The tests cover parsing tool_result, but don’t assert that the generated tool_result.tool_use_id matches the preceding tool_use.id. Given the shared renderer pairs tool results to tool calls by ID, adding an assertion for correct pairing would prevent regressions in the log format mapping.
| expect(result.logEntries[1].message.content[0].type).toBe("tool_result"); | |
| expect(result.logEntries[1].message.content[0].type).toBe("tool_result"); | |
| const toolCall = result.logEntries[0].message.content[0]; | |
| const toolResult = result.logEntries[1].message.content[0]; | |
| expect(toolResult.tool_use_id).toBe(toolCall.id); |
Summary
experimental— requiresfeatures: experimental-engines: truein strict modeWhat is OpenClaw?
OpenClaw is an agent platform that supports ACP-based tool integration. It provides:
--jsonflag) for reliable log parsingFiles Created
pkg/workflow/openclaw_engine.gopkg/workflow/openclaw_mcp.gopkg/workflow/openclaw_engine_test.gopkg/workflow/compiler_openclaw_test.goactions/setup/js/parse_openclaw_log.cjsactions/setup/js/parse_openclaw_log.test.cjspkg/cli/workflows/test-openclaw-review.mdpkg/cli/workflows/test-openclaw-issue-triage.mdpkg/cli/workflows/test-openclaw-code-quality.md.github/workflows/smoke-openclaw.mdFiles Modified
pkg/constants/constants.goOpenClawEngine,DefaultOpenClawVersion, env varspkg/workflow/domains.goOpenClawDefaultDomainsand domain helper functionspkg/workflow/firewall.goenableFirewallByDefaultForOpenClaw()pkg/workflow/agentic_engine.gopkg/workflow/compiler_orchestrator_engine.gopkg/parser/schemas/main_workflow_schema.jsonopenclawto engine enumUsage
Simple frontmatter:
With custom agent and options:
Testing Done
make fmt— passesmake build— passesmake test-unit— passes (all existing + new tests)make test-js— OpenClaw tests pass (42/42); 3 pre-existing failures in unrelated safe_outputs_mcp_server_defaults testsTest plan
🤖 Generated with Claude Code