Skip to content

Install OpenClaw channel plugin via installer#90

Merged
bglusman merged 5 commits into
mainfrom
codex-openclaw-channel-installer
Apr 30, 2026
Merged

Install OpenClaw channel plugin via installer#90
bglusman merged 5 commits into
mainfrom
codex-openclaw-channel-installer

Conversation

@bglusman
Copy link
Copy Markdown
Owner

@bglusman bglusman commented Apr 30, 2026

Summary

  • add a bundled Calciforge OpenClaw channel plugin and install it during openclaw-channel managed-agent setup
  • require inbound auth plus reply callback auth/webhook fields for non-interactive OpenClaw installer specs
  • document the managed-agent installer pattern, with OpenClaw as the first concrete implementation and ZeroClaw parity as follow-on work
  • tighten install output/tests so ZeroClaw and OpenClaw managed-agent paths are described separately
  • prefer OpenClaw's documented api.registerHttpRoute(...) plugin HTTP API, with the older internal route registry only as a compatibility fallback for deployed versions that lack the public API

Verification

  • node --check crates/calciforge-openclaw-channel-plugin/index.js
  • jq . crates/calciforge-openclaw-channel-plugin/openclaw.plugin.json >/dev/null
  • cargo test -p calciforge install:: -- --nocapture
  • cargo test -p calciforge openclaw -- --nocapture
  • cargo test -p calciforge agents_doc -- --nocapture
  • git diff --check
  • cargo test -p calciforge
  • pre-commit hook: rustfmt, clippy, gitleaks
  • pre-push hook: fmt, clippy, workspace unit tests, loom tests

Review Notes

  • Current OpenClaw docs describe api.registerHttpRoute(...) as the clean plugin route API. The plugin uses that first. The internal hashed route registry remains only as a fallback so we can test against older installed gateways without blocking the branch.
  • The installer still only has concrete plugin automation for OpenClaw. The new ClawTarget fields and docs frame the reusable managed-agent shape, but ZeroClaw still needs the real TOML patcher and equivalent callback/auth installer path.

Copilot AI review requested due to automatic review settings April 30, 2026 16:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class “managed agent” installation support for the openclaw-channel adapter by bundling an OpenClaw channel plugin and wiring installer/validation/docs around required auth + reply-callback configuration.

Changes:

  • Bundle and remotely install an OpenClaw channel plugin during calciforge install for openclaw-channel targets, including config patching and service restart handling.
  • Extend installer specs/models/wizard to require inbound auth + reply webhook/auth for non-interactive OpenClaw installs, and improve install output/test coverage for OpenClaw vs ZeroClaw paths.
  • Add guardrails that reject OpenClaw model IDs when using openai-compat, plus documentation updates describing the managed-agent installer pattern and model-vs-agent separation.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs/roadmap/agent-recipes-orchestrators.md Roadmap note introducing a unified managed-agent installer pattern.
docs/model-gateway.md Clarifies UX separation between model routes and agents.
docs/codex-openclaw-integration.md Updates OpenClaw integration example to include reply auth + installer spec example.
docs/agents.md Updates openclaw-channel requirements, adds reply auth example and installer guidance.
docs/agent-adapters.md Documents OpenClaw as first-class managed agent in calciforge install.
crates/calciforge/src/install/wizard.rs Adds interactive collection of managed-agent auth + reply callback fields for OpenClaw.
crates/calciforge/src/install/model.rs Extends ClawTarget with auth + reply callback fields used by managed installs.
crates/calciforge/src/install/executor.rs Patches OpenClaw config to add plugin entry, installs plugin files remotely, and restarts OpenClaw service as needed.
crates/calciforge/src/install/cli.rs Requires auth/reply fields in --claw spec for openclaw-channel, with updated parsing/tests.
crates/calciforge/src/doctor.rs Adds diagnostics rejecting OpenClaw model IDs under openai-compat.
crates/calciforge/src/config/validator.rs Adds config validation rejecting OpenClaw model IDs under openai-compat.
crates/calciforge/src/adapters/openai_compat.rs Rejects dispatching OpenClaw model IDs through openai-compat, with tests.
crates/calciforge-openclaw-channel-plugin/openclaw.plugin.json Adds bundled OpenClaw channel plugin manifest (config schema + metadata).
crates/calciforge-openclaw-channel-plugin/index.js Implements the OpenClaw channel plugin bridging /calciforge/inbound to Calciforge /hooks/reply.

Comment thread crates/calciforge/src/install/executor.rs
Comment thread crates/calciforge/src/install/executor.rs
Comment thread crates/calciforge/src/install/wizard.rs Outdated
Comment thread crates/calciforge/src/install/cli.rs
Comment thread crates/calciforge-openclaw-channel-plugin/index.js
Copilot AI review requested due to automatic review settings April 30, 2026 17:26
@bglusman bglusman marked this pull request as ready for review April 30, 2026 17:27
@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a bundled OpenClaw “calciforge-channel” plugin and extends the managed-agent installer flow so OpenClaw installs can be fully automated (auth + reply callbacks), while tightening validation/doctoring to prevent misconfiguring OpenClaw agents as generic model routes.

Changes:

  • Bundle and remotely install the OpenClaw channel plugin during calciforge install for openclaw-channel targets, including service restart logic.
  • Require and plumb managed-agent auth + reply callback fields through installer CLI + wizard + OpenClaw config patching.
  • Add guardrails preventing openai-compat from using OpenClaw model IDs (validator, doctor, and adapter-level enforcement) and update docs to reflect the managed-agent pattern.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
docs/roadmap/agent-recipes-orchestrators.md Documents the new first-class “managed agent” installer pattern as a roadmap item.
docs/model-gateway.md Clarifies UX separation between “agents” and “model/chat routes”.
docs/codex-openclaw-integration.md Adds installer-managed OpenClaw token/webhook example.
docs/agents.md Updates OpenClaw agent requirements + installer example and adds reply_auth_token to sample config.
docs/agent-adapters.md Documents OpenClaw as an installer-managed first-class agent (and notes ZeroClaw parity follow-up).
crates/calciforge/src/install/wizard.rs Collects OpenClaw managed-agent auth + reply callback fields in interactive installs.
crates/calciforge/src/install/model.rs Extends ClawTarget with auth_token, reply_webhook, reply_auth_token.
crates/calciforge/src/install/executor.rs Patches OpenClaw config to write the plugin entry, installs plugin files over SSH, and restarts openclaw-gateway; updates tests accordingly.
crates/calciforge/src/install/cli.rs Requires OpenClaw managed-agent fields in non-interactive --claw specs and redacts secret values in parse errors.
crates/calciforge/src/doctor.rs Errors when openai-compat is configured with an OpenClaw model ID.
crates/calciforge/src/config/validator.rs Rejects openai-compat agents configured with OpenClaw model IDs.
crates/calciforge/src/adapters/openai_compat.rs Runtime enforcement: rejects OpenClaw model IDs in openai-compat dispatch.
crates/calciforge-openclaw-channel-plugin/package.json Declares module type for the bundled plugin.
crates/calciforge-openclaw-channel-plugin/openclaw.plugin.json Adds plugin manifest + config schema for managed-agent tokens/webhook.
crates/calciforge-openclaw-channel-plugin/index.js Implements the OpenClaw channel plugin: inbound route + auth + async run + reply webhook delivery.
Comments suppressed due to low confidence (1)

crates/calciforge/src/install/executor.rs:825

  • Writing the patched OpenClaw config via SshClient::write_file embeds the full file content (including auth_token / reply_auth_token) into the remote shell command as a base64 string. That can leak secrets via process listings (ps) on both the local machine running ssh and the remote host. Prefer a write mechanism that streams content over stdin (or uses scp/SFTP) so secrets are never placed in argv, and use that for writing openclaw.json (and any other secret-bearing files).
    // Read current config.
    let current = deps
        .ssh
        .read_file(&claw.host, key, &config_path)
        .map_err(|e| anyhow::anyhow!("failed to read remote config: {}", e))?;

    let patched = match &claw.adapter {
        ClawKind::OpenClawChannel => patch_openclaw_config(
            &current,
            &claw.name,
            claw.auth_token.as_deref(),
            claw.reply_webhook.as_deref(),
            claw.reply_auth_token.as_deref(),
            claw.policy_endpoint.as_deref(),
        )
        .map_err(|e| anyhow::anyhow!("failed to patch openclaw.json: {}", e))?,
        ClawKind::ZeroClawNative => {
            // ZeroClaw uses TOML — full patching deferred; use safe stub for now.
            // TODO (follow-on): implement real TOML patching for ZeroClaw config.
            patch_zeroclaw_config_stub(&current, &claw.name)
        }
        _ => {
            // Non-SSH adapters should never reach apply_remote_config.
            return Err(anyhow::anyhow!(
                "apply_remote_config called for non-SSH adapter '{}'",
                claw.adapter.kind_label()
            ));
        }
    };

    deps.ssh
        .write_file(&claw.host, key, &config_path, &patched)
        .map_err(|e| anyhow::anyhow!("failed to write patched config: {}", e))?;

Comment on lines 848 to 862
if matches!(claw.adapter, ClawKind::OpenClawChannel) {
if let Some(detail) = configure_remote_openclaw_proxy_env(claw, deps)? {
details.push(detail);
install_remote_openclaw_channel_plugin(claw, deps)?;
details.push("installed Calciforge OpenClaw channel plugin".to_string());

let restarted_by_proxy =
if let Some(detail) = configure_remote_openclaw_proxy_env(claw, deps)? {
details.push(detail);
true
} else {
false
};
if !restarted_by_proxy {
details.push(restart_remote_openclaw_service(claw, deps)?);
}
}
Comment on lines 1074 to 1082
fn patch_openclaw_config(
current_content: &str,
_claw_name: &str,
_calciforge_endpoint: &str,
auth_token: Option<&str>,
reply_webhook: Option<&str>,
reply_auth_token: Option<&str>,
policy_endpoint: Option<&str>,
) -> Result<String> {
// Parse the existing config (handles JSON5 / JSONC comments).
@@ -1130,6 +1312,8 @@ mod tests {
ssh.push_success("");
// apply: read-back verify (new in S1 implementation)
ssh.push_success(r#"{"version": "2026.3.13", "hooks": {"enabled": true, "entries": {"test-claw": {"enabled": true, "url": "http://host:18789", "token": "tok"}}}}"#);
id: "calciforge-channel",
name: "Calciforge",
description: "Calciforge inbound channel",
configSchema: { type: "object", properties: {}, additionalProperties: true },
}
}

fn is_openclaw_model_id(model: &str) -> bool {
Comment on lines 1768 to 1772
/// patch_openclaw_config preserves existing hooks fields without mutation.
#[test]
fn patch_openclaw_config_preserves_existing_hooks() {
let input =
r#"{"hooks": {"enabled": false, "entries": {"calciforge": {"enabled": true}}}}"#;
Comment on lines +399 to +423
fn collect_managed_agent_auth(
adapter_str: &str,
) -> Result<(Option<String>, Option<String>, Option<String>)> {
if adapter_str != "openclaw-channel" {
return Ok((None, None, None));
}

let auth_token: String = Password::new()
.with_prompt(" Inbound bearer token (must match Calciforge agent api_key)")
.interact()
.context("failed to read inbound token")?;
let reply_webhook: String = Input::new()
.with_prompt(" Calciforge reply webhook URL")
.interact_text()
.context("failed to read reply webhook")?;
let reply_auth_token: String = Password::new()
.with_prompt(" Reply webhook bearer token (must match reply_auth_token)")
.interact()
.context("failed to read reply token")?;

Ok((
Some(auth_token.trim().to_string()),
Some(reply_webhook.trim().to_string()),
Some(reply_auth_token.trim().to_string()),
))
Comment on lines +218 to +225
async function readJsonBody(req) {
const chunks = [];
await new Promise((resolve, reject) => {
req.on("data", (chunk) => chunks.push(chunk));
req.on("end", resolve);
req.on("error", reject);
});
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
@bglusman bglusman force-pushed the codex-openclaw-channel-installer branch from 430e306 to a08da97 Compare April 30, 2026 18:06
@bglusman bglusman merged commit 023a0b6 into main Apr 30, 2026
17 checks passed
@bglusman bglusman deleted the codex-openclaw-channel-installer branch April 30, 2026 18:25
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.

2 participants