mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
test: add cli backend live matrix metadata
This commit is contained in:
@@ -253,17 +253,17 @@ openclaw models list
|
||||
openclaw models list --json
|
||||
```
|
||||
|
||||
## Live: CLI backend smoke (Codex CLI or other local CLIs)
|
||||
## Live: CLI backend smoke (Claude, Codex, Gemini, or other local CLIs)
|
||||
|
||||
- Test: `src/gateway/gateway-cli-backend.live.test.ts`
|
||||
- Goal: validate the Gateway + agent pipeline using a local CLI backend, without touching your default config.
|
||||
- Backend-specific smoke defaults live with the owning extension's `cli-backend.ts` definition.
|
||||
- Enable:
|
||||
- `pnpm test:live` (or `OPENCLAW_LIVE_TEST=1` if invoking Vitest directly)
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND=1`
|
||||
- Defaults:
|
||||
- Model: `codex-cli/gpt-5.4`
|
||||
- Command: `codex`
|
||||
- Args: `["exec","--json","--color","never","--sandbox","read-only","--skip-git-repo-check"]`
|
||||
- Default provider/model: `claude-cli/claude-sonnet-4-6`
|
||||
- Command/args/image behavior come from the owning CLI backend plugin metadata.
|
||||
- Overrides (optional):
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.4"`
|
||||
- `OPENCLAW_LIVE_CLI_BACKEND_COMMAND="/full/path/to/codex"`
|
||||
@@ -287,11 +287,19 @@ Docker recipe:
|
||||
pnpm test:docker:live-cli-backend
|
||||
```
|
||||
|
||||
Single-provider Docker recipes:
|
||||
|
||||
```bash
|
||||
pnpm test:docker:live-cli-backend:claude
|
||||
pnpm test:docker:live-cli-backend:codex
|
||||
pnpm test:docker:live-cli-backend:gemini
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- The Docker runner lives at `scripts/test-live-cli-backend-docker.sh`.
|
||||
- It runs the live CLI-backend smoke inside the repo Docker image as the non-root `node` user.
|
||||
- For `codex-cli`, it installs the Linux `@openai/codex` package into a cached writable prefix at `OPENCLAW_DOCKER_CLI_TOOLS_DIR` (default: `~/.cache/openclaw/docker-cli-tools`).
|
||||
- It resolves CLI smoke metadata from the owning extension, then installs the matching Linux CLI package (`@anthropic-ai/claude-code`, `@openai/codex`, or `@google/gemini-cli`) into a cached writable prefix at `OPENCLAW_DOCKER_CLI_TOOLS_DIR` (default: `~/.cache/openclaw/docker-cli-tools`).
|
||||
|
||||
## Live: ACP bind smoke (`/acp spawn ... --bind here`)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/cli-backend";
|
||||
import {
|
||||
CLAUDE_CLI_BACKEND_ID,
|
||||
CLAUDE_CLI_DEFAULT_MODEL_REF,
|
||||
CLAUDE_CLI_CLEAR_ENV,
|
||||
CLAUDE_CLI_HOST_MANAGED_ENV,
|
||||
CLAUDE_CLI_MODEL_ALIASES,
|
||||
@@ -15,6 +16,14 @@ import {
|
||||
export function buildAnthropicCliBackend(): CliBackendPlugin {
|
||||
return {
|
||||
id: CLAUDE_CLI_BACKEND_ID,
|
||||
liveTest: {
|
||||
defaultModelRef: CLAUDE_CLI_DEFAULT_MODEL_REF,
|
||||
defaultImageProbe: true,
|
||||
docker: {
|
||||
npmPackage: "@anthropic-ai/claude-code",
|
||||
binaryName: "claude",
|
||||
},
|
||||
},
|
||||
bundleMcp: true,
|
||||
config: {
|
||||
command: "claude",
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeGoogleProviderConfig } from "./api.js";
|
||||
import { buildGoogleGeminiCliBackend } from "./cli-backend.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "google",
|
||||
name: "Google Setup",
|
||||
description: "Lightweight Google setup hooks",
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: "google",
|
||||
label: "Google AI Studio",
|
||||
hookAliases: ["google-antigravity", "google-vertex"],
|
||||
auth: [],
|
||||
normalizeConfig: ({ provider, providerConfig }) =>
|
||||
normalizeGoogleProviderConfig(provider, providerConfig),
|
||||
});
|
||||
api.registerCliBackend(buildGoogleGeminiCliBackend());
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,9 +4,19 @@ import {
|
||||
CLI_RESUME_WATCHDOG_DEFAULTS,
|
||||
} from "openclaw/plugin-sdk/cli-backend";
|
||||
|
||||
const CODEX_CLI_DEFAULT_MODEL_REF = "codex-cli/gpt-5.4";
|
||||
|
||||
export function buildOpenAICodexCliBackend(): CliBackendPlugin {
|
||||
return {
|
||||
id: "codex-cli",
|
||||
liveTest: {
|
||||
defaultModelRef: CODEX_CLI_DEFAULT_MODEL_REF,
|
||||
defaultImageProbe: true,
|
||||
docker: {
|
||||
npmPackage: "@openai/codex",
|
||||
binaryName: "codex",
|
||||
},
|
||||
},
|
||||
config: {
|
||||
command: "codex",
|
||||
args: [
|
||||
@@ -18,16 +28,7 @@ export function buildOpenAICodexCliBackend(): CliBackendPlugin {
|
||||
"workspace-write",
|
||||
"--skip-git-repo-check",
|
||||
],
|
||||
resumeArgs: [
|
||||
"exec",
|
||||
"resume",
|
||||
"{sessionId}",
|
||||
"--color",
|
||||
"never",
|
||||
"--sandbox",
|
||||
"workspace-write",
|
||||
"--skip-git-repo-check",
|
||||
],
|
||||
resumeArgs: ["exec", "resume", "{sessionId}", "--dangerously-bypass-approvals-and-sandbox"],
|
||||
output: "jsonl",
|
||||
resumeOutput: "text",
|
||||
input: "arg",
|
||||
|
||||
11
extensions/openai/setup-api.ts
Normal file
11
extensions/openai/setup-api.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { buildOpenAICodexCliBackend } from "./cli-backend.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "openai",
|
||||
name: "OpenAI Setup",
|
||||
description: "Lightweight OpenAI setup hooks",
|
||||
register(api) {
|
||||
api.registerCliBackend(buildOpenAICodexCliBackend());
|
||||
},
|
||||
});
|
||||
@@ -1183,8 +1183,17 @@
|
||||
"test:docker:live-acp-bind:gemini": "OPENCLAW_LIVE_ACP_BIND_AGENT=gemini bash scripts/test-live-acp-bind-docker.sh",
|
||||
"test:docker:live-build": "bash scripts/test-live-build-docker.sh",
|
||||
"test:docker:live-cli-backend": "bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:claude": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=claude-cli/claude-sonnet-4-6 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:codex": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4 bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-cli-backend:gemini": "OPENCLAW_LIVE_CLI_BACKEND_MODEL=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-cli-backend-docker.sh",
|
||||
"test:docker:live-gateway": "bash scripts/test-live-gateway-models-docker.sh",
|
||||
"test:docker:live-gateway:claude": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=claude-cli OPENCLAW_LIVE_GATEWAY_MODELS=claude-cli/claude-sonnet-4-6 bash scripts/test-live-gateway-models-docker.sh",
|
||||
"test:docker:live-gateway:codex": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=codex-cli OPENCLAW_LIVE_GATEWAY_MODELS=codex-cli/gpt-5.4 bash scripts/test-live-gateway-models-docker.sh",
|
||||
"test:docker:live-gateway:gemini": "OPENCLAW_LIVE_GATEWAY_PROVIDERS=google-gemini-cli OPENCLAW_LIVE_GATEWAY_MODELS=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-gateway-models-docker.sh",
|
||||
"test:docker:live-models": "bash scripts/test-live-models-docker.sh",
|
||||
"test:docker:live-models:claude": "OPENCLAW_LIVE_PROVIDERS=claude-cli OPENCLAW_LIVE_MODELS=claude-cli/claude-sonnet-4-6 bash scripts/test-live-models-docker.sh",
|
||||
"test:docker:live-models:codex": "OPENCLAW_LIVE_PROVIDERS=codex-cli OPENCLAW_LIVE_MODELS=codex-cli/gpt-5.4 bash scripts/test-live-models-docker.sh",
|
||||
"test:docker:live-models:gemini": "OPENCLAW_LIVE_PROVIDERS=google-gemini-cli OPENCLAW_LIVE_MODELS=google-gemini-cli/gemini-3.1-pro-preview bash scripts/test-live-models-docker.sh",
|
||||
"test:docker:mcp-channels": "bash scripts/e2e/mcp-channels-docker.sh",
|
||||
"test:docker:onboard": "bash scripts/e2e/onboard-docker.sh",
|
||||
"test:docker:openwebui": "bash scripts/e2e/openwebui-docker.sh",
|
||||
|
||||
64
scripts/print-cli-backend-live-metadata.ts
Normal file
64
scripts/print-cli-backend-live-metadata.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { resolveCliBackendConfig, resolveCliBackendLiveTest } from "../src/agents/cli-backends.js";
|
||||
|
||||
const provider = process.argv[2]?.trim().toLowerCase();
|
||||
|
||||
if (!provider) {
|
||||
console.error("usage: node scripts/print-cli-backend-live-metadata.ts <provider>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function loadFallbackBackend(id: string) {
|
||||
switch (id) {
|
||||
case "claude-cli": {
|
||||
const mod = await import("../extensions/anthropic/cli-backend.ts");
|
||||
return mod.buildAnthropicCliBackend();
|
||||
}
|
||||
case "codex-cli": {
|
||||
const mod = await import("../extensions/openai/cli-backend.ts");
|
||||
return mod.buildOpenAICodexCliBackend();
|
||||
}
|
||||
case "google-gemini-cli": {
|
||||
const mod = await import("../extensions/google/cli-backend.ts");
|
||||
return mod.buildGoogleGeminiCliBackend();
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const resolved = resolveCliBackendConfig(provider);
|
||||
const liveTest = resolveCliBackendLiveTest(provider);
|
||||
const fallbackBackend =
|
||||
!resolved || !liveTest?.defaultModelRef ? await loadFallbackBackend(provider) : null;
|
||||
const backendConfig = resolved?.config ?? fallbackBackend?.config;
|
||||
const backendLiveTest =
|
||||
liveTest ??
|
||||
(fallbackBackend
|
||||
? {
|
||||
defaultModelRef: fallbackBackend.liveTest?.defaultModelRef,
|
||||
defaultImageProbe: fallbackBackend.liveTest?.defaultImageProbe === true,
|
||||
dockerNpmPackage: fallbackBackend.liveTest?.docker?.npmPackage,
|
||||
dockerBinaryName: fallbackBackend.liveTest?.docker?.binaryName,
|
||||
}
|
||||
: null);
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify(
|
||||
{
|
||||
provider,
|
||||
command: backendConfig?.command,
|
||||
args: backendConfig?.args,
|
||||
clearEnv: backendConfig?.clearEnv ?? [],
|
||||
imageArg: backendConfig?.imageArg,
|
||||
imageMode: backendConfig?.imageMode,
|
||||
systemPromptWhen: backendConfig?.systemPromptWhen ?? "never",
|
||||
bundleMcp: resolved?.bundleMcp === true || fallbackBackend?.bundleMcp === true,
|
||||
defaultModelRef: backendLiveTest?.defaultModelRef,
|
||||
defaultImageProbe: backendLiveTest?.defaultImageProbe === true,
|
||||
dockerNpmPackage: backendLiveTest?.dockerNpmPackage,
|
||||
dockerBinaryName: backendLiveTest?.dockerBinaryName,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
@@ -9,14 +9,29 @@ CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}"
|
||||
WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$HOME/.openclaw/workspace}"
|
||||
PROFILE_FILE="${OPENCLAW_PROFILE_FILE:-$HOME/.profile}"
|
||||
CLI_TOOLS_DIR="${OPENCLAW_DOCKER_CLI_TOOLS_DIR:-$HOME/.cache/openclaw/docker-cli-tools}"
|
||||
DEFAULT_MODEL="claude-cli/claude-sonnet-4-6"
|
||||
CLI_MODEL="${OPENCLAW_LIVE_CLI_BACKEND_MODEL:-$DEFAULT_MODEL}"
|
||||
DEFAULT_PROVIDER="${OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER:-claude-cli}"
|
||||
CLI_MODEL="${OPENCLAW_LIVE_CLI_BACKEND_MODEL:-}"
|
||||
CLI_PROVIDER="${CLI_MODEL%%/*}"
|
||||
CLI_DISABLE_MCP_CONFIG="${OPENCLAW_LIVE_CLI_BACKEND_DISABLE_MCP_CONFIG:-}"
|
||||
|
||||
if [[ -z "$CLI_PROVIDER" || "$CLI_PROVIDER" == "$CLI_MODEL" ]]; then
|
||||
CLI_PROVIDER="claude-cli"
|
||||
CLI_PROVIDER="$DEFAULT_PROVIDER"
|
||||
fi
|
||||
|
||||
CLI_METADATA_JSON="$(node --import tsx "$ROOT_DIR/scripts/print-cli-backend-live-metadata.ts" "$CLI_PROVIDER")"
|
||||
read_metadata_field() {
|
||||
local field="$1"
|
||||
node -e 'const data = JSON.parse(process.argv[1]); const field = process.argv[2]; const value = data?.[field]; if (value == null) process.exit(1); process.stdout.write(typeof value === "string" ? value : JSON.stringify(value));' \
|
||||
"$CLI_METADATA_JSON" \
|
||||
"$field"
|
||||
}
|
||||
|
||||
DEFAULT_MODEL="$(read_metadata_field defaultModelRef 2>/dev/null || printf '%s' 'claude-cli/claude-sonnet-4-6')"
|
||||
CLI_MODEL="${CLI_MODEL:-$DEFAULT_MODEL}"
|
||||
CLI_DEFAULT_COMMAND="$(read_metadata_field command 2>/dev/null || true)"
|
||||
CLI_DOCKER_NPM_PACKAGE="$(read_metadata_field dockerNpmPackage 2>/dev/null || true)"
|
||||
CLI_DOCKER_BINARY_NAME="$(read_metadata_field dockerBinaryName 2>/dev/null || true)"
|
||||
|
||||
if [[ "$CLI_PROVIDER" == "claude-cli" && -z "$CLI_DISABLE_MCP_CONFIG" ]]; then
|
||||
CLI_DISABLE_MCP_CONFIG="0"
|
||||
fi
|
||||
@@ -103,13 +118,19 @@ if ((${#auth_files[@]} > 0)); then
|
||||
done
|
||||
fi
|
||||
provider="${OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER:-claude-cli}"
|
||||
default_command="${OPENCLAW_DOCKER_CLI_BACKEND_COMMAND_DEFAULT:-}"
|
||||
docker_package="${OPENCLAW_DOCKER_CLI_BACKEND_NPM_PACKAGE:-}"
|
||||
binary_name="${OPENCLAW_DOCKER_CLI_BACKEND_BINARY_NAME:-}"
|
||||
if [ -z "$binary_name" ] && [ -n "$default_command" ]; then
|
||||
binary_name="$(basename "$default_command")"
|
||||
fi
|
||||
if [ -z "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ] && [ -n "$binary_name" ]; then
|
||||
export OPENCLAW_LIVE_CLI_BACKEND_COMMAND="$HOME/.npm-global/bin/$binary_name"
|
||||
fi
|
||||
if [ -n "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ] && [ ! -x "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" ] && [ -n "$docker_package" ]; then
|
||||
npm_config_prefix="$HOME/.npm-global" npm install -g "$docker_package"
|
||||
fi
|
||||
if [ "$provider" = "claude-cli" ]; then
|
||||
if [ -z "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ]; then
|
||||
export OPENCLAW_LIVE_CLI_BACKEND_COMMAND="$HOME/.npm-global/bin/claude"
|
||||
fi
|
||||
if [ ! -x "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" ]; then
|
||||
npm_config_prefix="$HOME/.npm-global" npm install -g @anthropic-ai/claude-code
|
||||
fi
|
||||
real_claude="$HOME/.npm-global/bin/claude-real"
|
||||
if [ ! -x "$real_claude" ] && [ -x "$HOME/.npm-global/bin/claude" ]; then
|
||||
mv "$HOME/.npm-global/bin/claude" "$real_claude"
|
||||
@@ -177,6 +198,9 @@ docker run --rm -t \
|
||||
-e OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED="$AUTH_DIRS_CSV" \
|
||||
-e OPENCLAW_DOCKER_AUTH_FILES_RESOLVED="$AUTH_FILES_CSV" \
|
||||
-e OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER="$CLI_PROVIDER" \
|
||||
-e OPENCLAW_DOCKER_CLI_BACKEND_COMMAND_DEFAULT="$CLI_DEFAULT_COMMAND" \
|
||||
-e OPENCLAW_DOCKER_CLI_BACKEND_NPM_PACKAGE="$CLI_DOCKER_NPM_PACKAGE" \
|
||||
-e OPENCLAW_DOCKER_CLI_BACKEND_BINARY_NAME="$CLI_DOCKER_BINARY_NAME" \
|
||||
-e OPENCLAW_LIVE_TEST=1 \
|
||||
-e OPENCLAW_LIVE_CLI_BACKEND=1 \
|
||||
-e OPENCLAW_LIVE_CLI_BACKEND_MODEL="$CLI_MODEL" \
|
||||
|
||||
@@ -11,6 +11,13 @@ export type ResolvedCliBackend = {
|
||||
pluginId?: string;
|
||||
};
|
||||
|
||||
export type ResolvedCliBackendLiveTest = {
|
||||
defaultModelRef?: string;
|
||||
defaultImageProbe: boolean;
|
||||
dockerNpmPackage?: string;
|
||||
dockerBinaryName?: string;
|
||||
};
|
||||
|
||||
export function normalizeClaudeBackendConfig(config: CliBackendConfig): CliBackendConfig {
|
||||
const normalizeConfig = resolveFallbackCliBackendPolicy("claude-cli")?.normalizeConfig;
|
||||
return normalizeConfig ? normalizeConfig(config) : config;
|
||||
@@ -118,6 +125,23 @@ export function resolveCliBackendIds(cfg?: OpenClawConfig): Set<string> {
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function resolveCliBackendLiveTest(provider: string): ResolvedCliBackendLiveTest | null {
|
||||
const normalized = normalizeBackendKey(provider);
|
||||
const entry =
|
||||
resolvePluginSetupCliBackend({ backend: normalized }) ??
|
||||
resolveRuntimeCliBackends().find((backend) => normalizeBackendKey(backend.id) === normalized);
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
const backend = "backend" in entry ? entry.backend : entry;
|
||||
return {
|
||||
defaultModelRef: backend.liveTest?.defaultModelRef,
|
||||
defaultImageProbe: backend.liveTest?.defaultImageProbe === true,
|
||||
dockerNpmPackage: backend.liveTest?.docker?.npmPackage,
|
||||
dockerBinaryName: backend.liveTest?.docker?.binaryName,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveCliBackendConfig(
|
||||
provider: string,
|
||||
cfg?: OpenClawConfig,
|
||||
|
||||
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { expect } from "vitest";
|
||||
import { resolveCliBackendLiveTest } from "../agents/cli-backends.js";
|
||||
import {
|
||||
loadOrCreateDeviceIdentity,
|
||||
publicKeyRawBase64UrlFromPem,
|
||||
@@ -24,50 +25,6 @@ import { extractPayloadText } from "./test-helpers.agent-results.js";
|
||||
const execFileAsync = promisify(execFile);
|
||||
const CLI_GATEWAY_CONNECT_TIMEOUT_MS = 30_000;
|
||||
|
||||
export const DEFAULT_CLAUDE_ARGS = [
|
||||
"-p",
|
||||
"--output-format",
|
||||
"stream-json",
|
||||
"--include-partial-messages",
|
||||
"--verbose",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
];
|
||||
|
||||
export const DEFAULT_CODEX_ARGS = [
|
||||
"exec",
|
||||
"--json",
|
||||
"--color",
|
||||
"never",
|
||||
"--sandbox",
|
||||
"read-only",
|
||||
"--skip-git-repo-check",
|
||||
];
|
||||
|
||||
export const DEFAULT_CLEAR_ENV = [
|
||||
"ANTHROPIC_API_KEY",
|
||||
"ANTHROPIC_API_KEY_OLD",
|
||||
"ANTHROPIC_AUTH_TOKEN",
|
||||
"ANTHROPIC_BASE_URL",
|
||||
"ANTHROPIC_UNIX_SOCKET",
|
||||
"CLAUDE_CONFIG_DIR",
|
||||
"CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR",
|
||||
"CLAUDE_CODE_ENTRYPOINT",
|
||||
"CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
|
||||
"CLAUDE_CODE_OAUTH_SCOPES",
|
||||
"CLAUDE_CODE_OAUTH_TOKEN",
|
||||
"CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR",
|
||||
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
||||
"CLAUDE_CODE_PLUGIN_SEED_DIR",
|
||||
"CLAUDE_CODE_REMOTE",
|
||||
"CLAUDE_CODE_USE_COWORK_PLUGINS",
|
||||
"CLAUDE_CODE_USE_BEDROCK",
|
||||
"CLAUDE_CODE_USE_FOUNDRY",
|
||||
"CLAUDE_CODE_USE_VERTEX",
|
||||
];
|
||||
|
||||
export type BootstrapWorkspaceContext = {
|
||||
expectedInjectedFiles: string[];
|
||||
workspaceDir: string;
|
||||
@@ -147,7 +104,7 @@ export function shouldRunCliImageProbe(providerId: string): boolean {
|
||||
if (raw) {
|
||||
return isTruthyEnvValue(raw);
|
||||
}
|
||||
return providerId === "claude-cli";
|
||||
return resolveCliBackendLiveTest(providerId)?.defaultImageProbe === true;
|
||||
}
|
||||
|
||||
export function matchesCliBackendReply(text: string, expected: string): boolean {
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveCliBackendConfig, resolveCliBackendLiveTest } from "../agents/cli-backends.js";
|
||||
import { isLiveTestEnabled } from "../agents/live-test-helpers.js";
|
||||
import { parseModelRef } from "../agents/model-selection.js";
|
||||
import { clearRuntimeConfigSnapshot, type OpenClawConfig } from "../config/config.js";
|
||||
@@ -11,9 +12,6 @@ import {
|
||||
applyCliBackendLiveEnv,
|
||||
createBootstrapWorkspace,
|
||||
ensurePairedTestGatewayClientIdentity,
|
||||
DEFAULT_CLAUDE_ARGS,
|
||||
DEFAULT_CLEAR_ENV,
|
||||
DEFAULT_CODEX_ARGS,
|
||||
getFreeGatewayPort,
|
||||
matchesCliBackendReply,
|
||||
parseImageMode,
|
||||
@@ -36,7 +34,9 @@ const CLI_RESUME = isTruthyEnvValue(process.env.OPENCLAW_LIVE_CLI_BACKEND_RESUME
|
||||
const CLI_DEBUG = isTruthyEnvValue(process.env.OPENCLAW_LIVE_CLI_BACKEND_DEBUG);
|
||||
const describeLive = LIVE && CLI_LIVE ? describe : describe.skip;
|
||||
|
||||
const DEFAULT_MODEL = "claude-cli/claude-sonnet-4-6";
|
||||
const DEFAULT_PROVIDER = "claude-cli";
|
||||
const DEFAULT_MODEL =
|
||||
resolveCliBackendLiveTest(DEFAULT_PROVIDER)?.defaultModelRef ?? "claude-cli/claude-sonnet-4-6";
|
||||
const CLI_BACKEND_LIVE_TIMEOUT_MS = 420_000;
|
||||
|
||||
function logCliBackendLiveStep(step: string, details?: Record<string, unknown>): void {
|
||||
@@ -77,22 +77,10 @@ describeLive("gateway live (cli backend)", () => {
|
||||
|
||||
const providerId = parsed.provider;
|
||||
const modelKey = `${providerId}/${parsed.model}`;
|
||||
const backendResolved = resolveCliBackendConfig(providerId);
|
||||
const enableCliImageProbe = shouldRunCliImageProbe(providerId);
|
||||
logCliBackendLiveStep("model-selected", { providerId, modelKey, enableCliImageProbe });
|
||||
const providerDefaults =
|
||||
providerId === "claude-cli"
|
||||
? {
|
||||
command: "claude",
|
||||
args: DEFAULT_CLAUDE_ARGS,
|
||||
}
|
||||
: providerId === "codex-cli"
|
||||
? {
|
||||
command: "codex",
|
||||
args: DEFAULT_CODEX_ARGS,
|
||||
imageArg: "--image",
|
||||
imageMode: "repeat" as const,
|
||||
}
|
||||
: null;
|
||||
const providerDefaults = backendResolved?.config;
|
||||
|
||||
const cliCommand = process.env.OPENCLAW_LIVE_CLI_BACKEND_COMMAND ?? providerDefaults?.command;
|
||||
if (!cliCommand) {
|
||||
@@ -114,7 +102,9 @@ describeLive("gateway live (cli backend)", () => {
|
||||
parseJsonStringArray(
|
||||
"OPENCLAW_LIVE_CLI_BACKEND_CLEAR_ENV",
|
||||
process.env.OPENCLAW_LIVE_CLI_BACKEND_CLEAR_ENV,
|
||||
) ?? (providerId === "claude-cli" ? DEFAULT_CLEAR_ENV : []);
|
||||
) ??
|
||||
providerDefaults?.clearEnv ??
|
||||
[];
|
||||
const filteredCliClearEnv = cliClearEnv.filter((name) => !preservedEnv.has(name));
|
||||
const preservedCliEnv = Object.fromEntries(
|
||||
[...preservedEnv]
|
||||
@@ -136,11 +126,11 @@ describeLive("gateway live (cli backend)", () => {
|
||||
const stateDir = path.join(tempDir, "state");
|
||||
await fs.mkdir(stateDir, { recursive: true });
|
||||
process.env.OPENCLAW_STATE_DIR = stateDir;
|
||||
const bootstrapWorkspace =
|
||||
providerId === "claude-cli" ? await createBootstrapWorkspace(tempDir) : null;
|
||||
const bundleMcp = backendResolved?.bundleMcp === true;
|
||||
const bootstrapWorkspace = bundleMcp ? await createBootstrapWorkspace(tempDir) : null;
|
||||
const disableMcpConfig = process.env.OPENCLAW_LIVE_CLI_BACKEND_DISABLE_MCP_CONFIG !== "0";
|
||||
let cliArgs = baseCliArgs;
|
||||
if (providerId === "claude-cli" && disableMcpConfig) {
|
||||
if (bundleMcp && disableMcpConfig) {
|
||||
const mcpConfigPath = path.join(tempDir, "claude-mcp.json");
|
||||
await fs.writeFile(mcpConfigPath, `${JSON.stringify({ mcpServers: {} }, null, 2)}\n`);
|
||||
cliArgs = withMcpConfigOverrides(baseCliArgs, mcpConfigPath);
|
||||
@@ -176,7 +166,7 @@ describeLive("gateway live (cli backend)", () => {
|
||||
args: cliArgs,
|
||||
clearEnv: filteredCliClearEnv.length > 0 ? filteredCliClearEnv : undefined,
|
||||
env: Object.keys(preservedCliEnv).length > 0 ? preservedCliEnv : undefined,
|
||||
systemPromptWhen: providerId === "claude-cli" ? "first" : "never",
|
||||
systemPromptWhen: providerDefaults?.systemPromptWhen ?? "never",
|
||||
...(cliImageArg ? { imageArg: cliImageArg, imageMode: cliImageMode } : {}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -142,9 +142,15 @@ function resolveSetupApiPath(rootDir: string): string | null {
|
||||
}
|
||||
|
||||
const bundledExtensionDir = path.basename(rootDir);
|
||||
const repoRoot = path.resolve(path.dirname(CURRENT_MODULE_PATH), "..", "..");
|
||||
const sourceExtensionRoot = path.join(repoRoot, "extensions", bundledExtensionDir);
|
||||
if (sourceExtensionRoot !== rootDir) {
|
||||
const repoRootCandidates = [
|
||||
path.resolve(path.dirname(CURRENT_MODULE_PATH), "..", ".."),
|
||||
process.cwd(),
|
||||
];
|
||||
for (const repoRoot of repoRootCandidates) {
|
||||
const sourceExtensionRoot = path.join(repoRoot, "extensions", bundledExtensionDir);
|
||||
if (sourceExtensionRoot === rootDir) {
|
||||
continue;
|
||||
}
|
||||
const sourceFallback = findSetupApi(sourceExtensionRoot);
|
||||
if (sourceFallback) {
|
||||
return sourceFallback;
|
||||
@@ -215,7 +221,7 @@ export function resolvePluginSetupRegistry(params?: {
|
||||
});
|
||||
|
||||
for (const record of manifestRegistry.plugins) {
|
||||
const setupSource = resolveSetupApiPath(record.rootDir);
|
||||
const setupSource = record.setupSource ?? resolveSetupApiPath(record.rootDir);
|
||||
if (!setupSource) {
|
||||
continue;
|
||||
}
|
||||
@@ -411,9 +417,93 @@ export function resolvePluginSetupCliBackend(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): SetupCliBackendEntry | undefined {
|
||||
const normalized = normalizeProviderId(params.backend);
|
||||
return resolvePluginSetupRegistry(params).cliBackends.find(
|
||||
const direct = resolvePluginSetupRegistry(params).cliBackends.find(
|
||||
(entry) => normalizeProviderId(entry.backend.id) === normalized,
|
||||
);
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
const env = params.env ?? process.env;
|
||||
const discovery = discoverOpenClawPlugins({
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
cache: true,
|
||||
});
|
||||
const manifestRegistry = loadPluginManifestRegistry({
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
cache: true,
|
||||
candidates: discovery.candidates,
|
||||
diagnostics: discovery.diagnostics,
|
||||
});
|
||||
const record = manifestRegistry.plugins.find((entry) =>
|
||||
entry.cliBackends.some((backendId) => normalizeProviderId(backendId) === normalized),
|
||||
);
|
||||
if (!record) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const setupSource = record.setupSource ?? resolveSetupApiPath(record.rootDir);
|
||||
if (!setupSource) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let mod: OpenClawPluginModule;
|
||||
try {
|
||||
mod = getJiti(setupSource)(setupSource) as OpenClawPluginModule;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
const resolved = resolveRegister((mod as { default?: OpenClawPluginModule }).default ?? mod);
|
||||
if (!resolved.register) {
|
||||
return undefined;
|
||||
}
|
||||
if (resolved.definition?.id && resolved.definition.id !== record.id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let matchedBackend: CliBackendPlugin | undefined;
|
||||
const localBackendKeys = new Set<string>();
|
||||
const api = buildPluginApi({
|
||||
id: record.id,
|
||||
name: record.name ?? record.id,
|
||||
version: record.version,
|
||||
description: record.description,
|
||||
source: setupSource,
|
||||
rootDir: record.rootDir,
|
||||
registrationMode: "setup-only",
|
||||
config: {} as OpenClawConfig,
|
||||
runtime: EMPTY_RUNTIME,
|
||||
logger: NOOP_LOGGER,
|
||||
resolvePath: (input) => input,
|
||||
handlers: {
|
||||
registerProvider() {},
|
||||
registerConfigMigration() {},
|
||||
registerAutoEnableProbe() {},
|
||||
registerCliBackend(backend) {
|
||||
const key = normalizeProviderId(backend.id);
|
||||
if (localBackendKeys.has(key)) {
|
||||
return;
|
||||
}
|
||||
localBackendKeys.add(key);
|
||||
if (key === normalized) {
|
||||
matchedBackend = backend;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const result = resolved.register(api);
|
||||
if (result && typeof result.then === "function") {
|
||||
return undefined;
|
||||
}
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return matchedBackend ? { pluginId: record.id, backend: matchedBackend } : undefined;
|
||||
}
|
||||
|
||||
export function runPluginSetupConfigMigrations(params: {
|
||||
|
||||
@@ -2023,6 +2023,20 @@ export type CliBackendPlugin = {
|
||||
id: string;
|
||||
/** Default backend config before user overrides from `agents.defaults.cliBackends`. */
|
||||
config: CliBackendConfig;
|
||||
/**
|
||||
* Optional live-smoke metadata owned by the backend plugin.
|
||||
*
|
||||
* Keep provider-specific test wiring here instead of scattering it across
|
||||
* Docker wrappers, docs, and gateway live tests.
|
||||
*/
|
||||
liveTest?: {
|
||||
defaultModelRef?: string;
|
||||
defaultImageProbe?: boolean;
|
||||
docker?: {
|
||||
npmPackage?: string;
|
||||
binaryName?: string;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Whether OpenClaw should inject bundle MCP config for this backend.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user