Revert "refactor(cli): remove bundled cli text providers"

This reverts commit 05d351c430.
This commit is contained in:
Peter Steinberger
2026-04-06 12:18:18 +01:00
parent 5fa166ed11
commit c39f061003
155 changed files with 9203 additions and 94 deletions

View File

@@ -583,6 +583,7 @@ function describeCronSeamKinds(relativePath, source) {
const seamKinds = [];
const importsAgentRunner = hasAnyImportSource(source, [
"../../agents/cli-runner.js",
"../../agents/pi-embedded.js",
"../../agents/model-fallback.js",
"../../agents/subagent-registry.js",
@@ -624,7 +625,9 @@ function describeCronSeamKinds(relativePath, source) {
if (
importsAgentRunner &&
/\brunEmbeddedPiAgent\b|\brunWithModelFallback\b|\bregisterAgentRunContext\b/.test(source)
/\brunCliAgent\b|\brunEmbeddedPiAgent\b|\brunWithModelFallback\b|\bregisterAgentRunContext\b/.test(
source,
)
) {
seamKinds.push("cron-agent-handoff");
}

View File

@@ -908,6 +908,8 @@ if (!inspect.gatewayMethods.includes("demo.marketplace.shortcut.v2")) {
console.log("ok");
NODE
echo "Running bundle MCP CLI-agent e2e..."
pnpm exec vitest run --config vitest.e2e.config.ts src/agents/cli-runner.bundle-mcp.e2e.test.ts
EOF
echo "OK"

View File

@@ -0,0 +1,177 @@
import { randomUUID } from "node:crypto";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { clearRuntimeConfigSnapshot, loadConfig } from "../src/config/config.js";
import { GatewayClient } from "../src/gateway/client.js";
import { startGatewayServer } from "../src/gateway/server.js";
import { extractPayloadText } from "../src/gateway/test-helpers.agent-results.js";
import { getFreePortBlockWithPermissionFallback } from "../src/test-utils/ports.js";
import { GATEWAY_CLIENT_NAMES } from "../src/utils/message-channel.js";
const DEFAULT_CODEX_ARGS = [
"exec",
"--json",
"--color",
"never",
"--sandbox",
"read-only",
"--skip-git-repo-check",
];
async function connectClient(params: { url: string; token: string }) {
return await new Promise<GatewayClient>((resolve, reject) => {
let done = false;
const finish = (result: { client?: GatewayClient; error?: Error }) => {
if (done) {
return;
}
done = true;
clearTimeout(connectTimeout);
if (result.error) {
reject(result.error);
return;
}
resolve(result.client as GatewayClient);
};
const client = new GatewayClient({
url: params.url,
token: params.token,
clientName: GATEWAY_CLIENT_NAMES.TEST,
clientVersion: "dev",
mode: "test",
onHelloOk: () => finish({ client }),
onConnectError: (error) => finish({ error }),
onClose: (code, reason) =>
finish({ error: new Error(`gateway closed during connect (${code}): ${reason}`) }),
});
const connectTimeout = setTimeout(
() => finish({ error: new Error("gateway connect timeout") }),
10_000,
);
connectTimeout.unref();
client.start();
});
}
async function getFreeGatewayPort(): Promise<number> {
return await getFreePortBlockWithPermissionFallback({
offsets: [0, 1, 2, 4],
fallbackBase: 40_000,
});
}
async function main() {
const preservedEnv = new Set(
JSON.parse(process.env.OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV ?? "[]") as string[],
);
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-inline-bootstrap-"));
const workspaceRootDir = path.join(tempDir, "workspace");
const workspaceDir = path.join(workspaceRootDir, "dev");
const soulSecret = `SOUL-${randomUUID()}`;
const identitySecret = `IDENTITY-${randomUUID()}`;
const userSecret = `USER-${randomUUID()}`;
await fs.mkdir(workspaceDir, { recursive: true });
await fs.writeFile(
path.join(workspaceDir, "AGENTS.md"),
[
"# AGENTS.md",
"",
"When the user sends a BOOTSTRAP_CHECK token, reply with exactly:",
`BOOTSTRAP_OK ${soulSecret} ${identitySecret} ${userSecret}`,
"Do not add any other words or punctuation.",
].join("\n"),
);
await fs.writeFile(path.join(workspaceDir, "SOUL.md"), `${soulSecret}\n`);
await fs.writeFile(path.join(workspaceDir, "IDENTITY.md"), `${identitySecret}\n`);
await fs.writeFile(path.join(workspaceDir, "USER.md"), `${userSecret}\n`);
const cfg = loadConfig();
const existingBackends = cfg.agents?.defaults?.cliBackends ?? {};
const codexBackend = existingBackends["codex-cli"] ?? {};
const cliCommand =
process.env.OPENCLAW_LIVE_CLI_BACKEND_COMMAND ?? codexBackend.command ?? "codex";
const cliArgs = codexBackend.args ?? DEFAULT_CODEX_ARGS;
const cliClearEnv = (codexBackend.clearEnv ?? []).filter((name) => !preservedEnv.has(name));
const preservedCliEnv = Object.fromEntries(
[...preservedEnv]
.map((name) => [name, process.env[name]])
.filter((entry): entry is [string, string] => typeof entry[1] === "string"),
);
const nextCfg = {
...cfg,
agents: {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
workspace: workspaceRootDir,
model: { primary: "codex-cli/gpt-5.4" },
models: { "codex-cli/gpt-5.4": {} },
cliBackends: {
...existingBackends,
"codex-cli": {
...codexBackend,
command: cliCommand,
args: cliArgs,
clearEnv: cliClearEnv.length > 0 ? cliClearEnv : undefined,
env: Object.keys(preservedCliEnv).length > 0 ? preservedCliEnv : undefined,
systemPromptWhen: "first",
},
},
sandbox: { mode: "off" },
},
},
};
const tempConfigPath = path.join(tempDir, "openclaw.json");
await fs.writeFile(tempConfigPath, `${JSON.stringify(nextCfg, null, 2)}\n`);
process.env.OPENCLAW_CONFIG_PATH = tempConfigPath;
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
const port = await getFreeGatewayPort();
const token = `test-${randomUUID()}`;
process.env.OPENCLAW_GATEWAY_TOKEN = token;
const server = await startGatewayServer(port, {
bind: "loopback",
auth: { mode: "token", token },
controlUiEnabled: false,
});
const client = await connectClient({ url: `ws://127.0.0.1:${port}`, token });
try {
const payload = await client.request(
"agent",
{
sessionKey: `agent:dev:inline-cli-bootstrap-${randomUUID()}`,
idempotencyKey: `idem-${randomUUID()}`,
message: `BOOTSTRAP_CHECK ${randomUUID()}`,
deliver: false,
},
{ expectFinal: true, timeoutMs: 60_000 },
);
const text = extractPayloadText(payload?.result);
process.stdout.write(
`${JSON.stringify({
ok: true,
text,
expectedText: `BOOTSTRAP_OK ${soulSecret} ${identitySecret} ${userSecret}`,
systemPromptReport: payload?.result?.meta?.systemPromptReport ?? null,
})}\n`,
);
} finally {
await client.stopAndWait();
await server.close({ reason: "bootstrap live probe done" });
await fs.rm(tempDir, { recursive: true, force: true });
clearRuntimeConfigSnapshot();
}
}
try {
await main();
process.exit(0);
} catch (error) {
process.stderr.write(`${String(error)}\n`);
process.exit(1);
}

View File

@@ -29,6 +29,12 @@ openclaw_live_should_include_auth_dir_for_provider() {
local provider
provider="$(openclaw_live_trim "${1:-}")"
case "$provider" in
anthropic | claude-cli)
printf '%s\n' ".claude"
;;
codex-cli | openai-codex)
printf '%s\n' ".codex"
;;
minimax | minimax-portal)
printf '%s\n' ".minimax"
;;
@@ -39,11 +45,11 @@ openclaw_live_should_include_auth_file_for_provider() {
local provider
provider="$(openclaw_live_trim "${1:-}")"
case "$provider" in
openai-codex)
codex-cli | openai-codex)
printf '%s\n' ".codex/auth.json"
printf '%s\n' ".codex/config.toml"
;;
anthropic)
anthropic | claude-cli)
printf '%s\n' ".claude.json"
printf '%s\n' ".claude/.credentials.json"
printf '%s\n' ".claude/settings.json"

View File

@@ -58,6 +58,7 @@
"github-copilot-login",
"github-copilot-token",
"cli-runtime",
"cli-backend",
"hook-runtime",
"host-runtime",
"process-runtime",

View File

@@ -18,7 +18,7 @@ case "$ACP_AGENT" in
CLI_BIN="claude"
;;
codex)
AUTH_PROVIDER="openai-codex"
AUTH_PROVIDER="codex-cli"
CLI_PACKAGE="@openai/codex"
CLI_BIN="codex"
;;

View File

@@ -0,0 +1,171 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$ROOT_DIR/scripts/lib/live-docker-auth.sh"
IMAGE_NAME="${OPENCLAW_IMAGE:-openclaw:local}"
LIVE_IMAGE_NAME="${OPENCLAW_LIVE_IMAGE:-${IMAGE_NAME}-live}"
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="codex-cli/gpt-5.4"
CLI_MODEL="${OPENCLAW_LIVE_CLI_BACKEND_MODEL:-$DEFAULT_MODEL}"
CLI_PROVIDER="${CLI_MODEL%%/*}"
if [[ -z "$CLI_PROVIDER" || "$CLI_PROVIDER" == "$CLI_MODEL" ]]; then
CLI_PROVIDER="codex-cli"
fi
mkdir -p "$CLI_TOOLS_DIR"
PROFILE_MOUNT=()
if [[ -f "$PROFILE_FILE" ]]; then
PROFILE_MOUNT=(-v "$PROFILE_FILE":/home/node/.profile:ro)
fi
AUTH_DIRS=()
AUTH_FILES=()
if [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then
while IFS= read -r auth_dir; do
[[ -n "$auth_dir" ]] || continue
AUTH_DIRS+=("$auth_dir")
done < <(openclaw_live_collect_auth_dirs)
while IFS= read -r auth_file; do
[[ -n "$auth_file" ]] || continue
AUTH_FILES+=("$auth_file")
done < <(openclaw_live_collect_auth_files)
else
while IFS= read -r auth_dir; do
[[ -n "$auth_dir" ]] || continue
AUTH_DIRS+=("$auth_dir")
done < <(openclaw_live_collect_auth_dirs_from_csv "$CLI_PROVIDER")
while IFS= read -r auth_file; do
[[ -n "$auth_file" ]] || continue
AUTH_FILES+=("$auth_file")
done < <(openclaw_live_collect_auth_files_from_csv "$CLI_PROVIDER")
fi
AUTH_DIRS_CSV=""
if ((${#AUTH_DIRS[@]} > 0)); then
AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")"
fi
AUTH_FILES_CSV=""
if ((${#AUTH_FILES[@]} > 0)); then
AUTH_FILES_CSV="$(openclaw_live_join_csv "${AUTH_FILES[@]}")"
fi
EXTERNAL_AUTH_MOUNTS=()
if ((${#AUTH_DIRS[@]} > 0)); then
for auth_dir in "${AUTH_DIRS[@]}"; do
host_path="$HOME/$auth_dir"
if [[ -d "$host_path" ]]; then
EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro)
fi
done
fi
if ((${#AUTH_FILES[@]} > 0)); then
for auth_file in "${AUTH_FILES[@]}"; do
host_path="$HOME/$auth_file"
if [[ -f "$host_path" ]]; then
EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth-files/"$auth_file":ro)
fi
done
fi
read -r -d '' LIVE_TEST_CMD <<'EOF' || true
set -euo pipefail
[ -f "$HOME/.profile" ] && source "$HOME/.profile" || true
export PATH="$HOME/.npm-global/bin:$PATH"
IFS=',' read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}"
IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}"
if ((${#auth_dirs[@]} > 0)); then
for auth_dir in "${auth_dirs[@]}"; do
[ -n "$auth_dir" ] || continue
if [ -d "/host-auth/$auth_dir" ]; then
mkdir -p "$HOME/$auth_dir"
cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir"
chmod -R u+rwX "$HOME/$auth_dir" || true
fi
done
fi
if ((${#auth_files[@]} > 0)); then
for auth_file in "${auth_files[@]}"; do
[ -n "$auth_file" ] || continue
if [ -f "/host-auth-files/$auth_file" ]; then
mkdir -p "$(dirname "$HOME/$auth_file")"
cp "/host-auth-files/$auth_file" "$HOME/$auth_file"
chmod u+rw "$HOME/$auth_file" || true
fi
done
fi
provider="${OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER:-codex-cli}"
if [ "$provider" = "codex-cli" ]; then
if [ -z "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ]; then
export OPENCLAW_LIVE_CLI_BACKEND_COMMAND="$HOME/.npm-global/bin/codex"
fi
if [ ! -x "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" ]; then
npm_config_prefix="$HOME/.npm-global" npm install -g @openai/codex
fi
fi
tmp_dir="$(mktemp -d)"
cleanup() {
rm -rf "$tmp_dir"
}
trap cleanup EXIT
tar -C /src \
--exclude=.git \
--exclude=node_modules \
--exclude=dist \
--exclude=ui/dist \
--exclude=ui/node_modules \
-cf - . | tar -C "$tmp_dir" -xf -
ln -s /app/node_modules "$tmp_dir/node_modules"
ln -s /app/dist "$tmp_dir/dist"
if [ -d /app/dist-runtime/extensions ]; then
export OPENCLAW_BUNDLED_PLUGINS_DIR=/app/dist-runtime/extensions
elif [ -d /app/dist/extensions ]; then
export OPENCLAW_BUNDLED_PLUGINS_DIR=/app/dist/extensions
fi
cd "$tmp_dir"
pnpm test:live src/gateway/gateway-cli-backend.live.test.ts
EOF
echo "==> Build live-test image: $LIVE_IMAGE_NAME (target=build)"
docker build --target build -t "$LIVE_IMAGE_NAME" -f "$ROOT_DIR/Dockerfile" "$ROOT_DIR"
echo "==> Run CLI backend live test in Docker"
echo "==> Model: $CLI_MODEL"
echo "==> Provider: $CLI_PROVIDER"
echo "==> External auth dirs: ${AUTH_DIRS_CSV:-none}"
echo "==> External auth files: ${AUTH_FILES_CSV:-none}"
docker run --rm -t \
-u node \
--entrypoint bash \
-e OPENAI_API_KEY \
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
-e HOME=/home/node \
-e NODE_OPTIONS=--disable-warning=ExperimentalWarning \
-e OPENCLAW_SKIP_CHANNELS=1 \
-e OPENCLAW_VITEST_FS_MODULE_CACHE=0 \
-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_LIVE_TEST=1 \
-e OPENCLAW_LIVE_CLI_BACKEND=1 \
-e OPENCLAW_LIVE_CLI_BACKEND_MODEL="$CLI_MODEL" \
-e OPENCLAW_LIVE_CLI_BACKEND_COMMAND="${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" \
-e OPENCLAW_LIVE_CLI_BACKEND_ARGS="${OPENCLAW_LIVE_CLI_BACKEND_ARGS:-}" \
-e OPENCLAW_LIVE_CLI_BACKEND_CLEAR_ENV="${OPENCLAW_LIVE_CLI_BACKEND_CLEAR_ENV:-}" \
-e OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV="${OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV:-}" \
-e OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE="${OPENCLAW_LIVE_CLI_BACKEND_RESUME_PROBE:-}" \
-e OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE="${OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE:-}" \
-e OPENCLAW_LIVE_CLI_BACKEND_IMAGE_ARG="${OPENCLAW_LIVE_CLI_BACKEND_IMAGE_ARG:-}" \
-e OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE="${OPENCLAW_LIVE_CLI_BACKEND_IMAGE_MODE:-}" \
-v "$ROOT_DIR":/src:ro \
-v "$CONFIG_DIR":/home/node/.openclaw \
-v "$WORKSPACE_DIR":/home/node/.openclaw/workspace \
-v "$CLI_TOOLS_DIR":/home/node/.npm-global \
"${EXTERNAL_AUTH_MOUNTS[@]}" \
"${PROFILE_MOUNT[@]}" \
"$LIVE_IMAGE_NAME" \
-lc "$LIVE_TEST_CMD"