mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:50:42 +00:00
Harden Codex harness control surfaces (#77459)
* fix(scripts): find codex protocol source from worktrees * fix(test): keep codex harness docker caches writable * fix(test): relax live codex cache mount permissions * test(codex): add live docker harness debug output * fix(test): detect numeric ci env in codex docker harness * fix(codex): skip duplicate agent-command telemetry * fix(tooling): skip sparse-missing oxlint tsconfig * fix(tooling): route changed checks through testbox * fix(qa): keep coverage json source-clean * fix(test): preflight codex docker auth * fix(codex): validate bind option values * fix(codex): parse quoted command arguments * fix(codex): reject extra control args * fix(codex): use content for blank bound prompts * fix(codex): decode local image file urls * fix(codex): treat local media urls as images * fix(codex): keep windows media paths local * fix(codex): reject malformed diagnostics confirmations * fix(codex): reject malformed resume commands * fix(codex): reject malformed thread actions * fix(codex): reject malformed turn controls * fix(codex): reject malformed model controls * fix(codex): resolve empty user input prompts * fix(codex): enforce user input options * fix(codex): reject ambiguous computer-use actions * fix(codex): ignore stale bound turn notifications * test(gateway): close task registries in gateway harness * test(gateway): route cleanup through task seams * fix(codex): describe current permission approvals * fix(codex): disclose command approval amendments * fix(codex): preserve approval detail under truncation * fix(codex): propagate dynamic tool failures * test(codex): align dynamic tool block contract * fix(codex): reject extra read-only command operands * fix(codex): escape command readout fields * fix(codex): escape status probe errors * fix(codex): narrow formatted thread details * fix(codex): escape successful status summaries * fix(codex): escape bound control replies * fix(codex): escape user input prompts * fix(codex): escape control failure replies * fix(codex): escape approval prompt text * test(codex): narrow escaped reply assertions * test(codex): complete strict reply fixtures * test(codex): preserve account fixture literals * test(codex): align status probe fixtures * fix(codex): satisfy sanitizer regex lint * fix(codex): harden command readouts * fix(codex): harden bound image inputs * fix(codex): sanitize command failure replies * test(codex): complete rate limit fixture * test(tooling): isolate postinstall compile cache fixture * fix(codex): keep app-server event ownership explicit --------- Co-authored-by: pashpashpash <nik@vault77.ai>
This commit is contained in:
@@ -33,6 +33,52 @@ export function createChangedCheckChildEnv(baseEnv = process.env) {
|
||||
};
|
||||
}
|
||||
|
||||
function isTruthyEnvFlag(value) {
|
||||
const normalized = String(value ?? "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no";
|
||||
}
|
||||
|
||||
export function shouldDelegateChangedCheckToTestbox(argv = [], env = process.env) {
|
||||
if (!isTruthyEnvFlag(env.OPENCLAW_TESTBOX)) {
|
||||
return false;
|
||||
}
|
||||
if (isTruthyEnvFlag(env.OPENCLAW_TESTBOX_REMOTE_RUN)) {
|
||||
return false;
|
||||
}
|
||||
if (isTruthyEnvFlag(env.CI) || isTruthyEnvFlag(env.GITHUB_ACTIONS)) {
|
||||
return false;
|
||||
}
|
||||
if (argv.includes("--dry-run")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function buildChangedCheckTestboxArgs(argv = []) {
|
||||
return [
|
||||
"testbox:run",
|
||||
"--",
|
||||
"OPENCLAW_TESTBOX=1",
|
||||
"OPENCLAW_TESTBOX_REMOTE_RUN=1",
|
||||
"pnpm",
|
||||
"check:changed",
|
||||
...argv,
|
||||
];
|
||||
}
|
||||
|
||||
export async function runChangedCheckViaTestbox(argv = [], env = process.env) {
|
||||
console.error(
|
||||
"[check:changed] OPENCLAW_TESTBOX=1 set; delegating to Blacksmith Testbox via `pnpm testbox:run`.",
|
||||
);
|
||||
return await runManagedCommand({
|
||||
bin: "pnpm",
|
||||
args: buildChangedCheckTestboxArgs(argv),
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
export function createChangedCheckPlan(result, options = {}) {
|
||||
const commands = [];
|
||||
const baseEnv = createChangedCheckChildEnv(options.env ?? process.env);
|
||||
@@ -283,21 +329,26 @@ function isDirectRun() {
|
||||
}
|
||||
|
||||
if (isDirectRun()) {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const paths =
|
||||
args.paths.length > 0
|
||||
? args.paths
|
||||
: args.staged
|
||||
? listStagedChangedPaths()
|
||||
: listChangedPathsFromGit({ base: args.base, head: args.head });
|
||||
const result = detectChangedLanesForPaths({
|
||||
paths,
|
||||
base: args.base,
|
||||
head: args.head,
|
||||
staged: args.staged,
|
||||
});
|
||||
process.exitCode = await runChangedCheck(result, {
|
||||
...args,
|
||||
explicitPaths: args.paths.length > 0,
|
||||
});
|
||||
const argv = process.argv.slice(2);
|
||||
if (shouldDelegateChangedCheckToTestbox(argv, process.env)) {
|
||||
process.exitCode = await runChangedCheckViaTestbox(argv, process.env);
|
||||
} else {
|
||||
const args = parseArgs(argv);
|
||||
const paths =
|
||||
args.paths.length > 0
|
||||
? args.paths
|
||||
: args.staged
|
||||
? listStagedChangedPaths()
|
||||
: listChangedPathsFromGit({ base: args.base, head: args.head });
|
||||
const result = detectChangedLanesForPaths({
|
||||
paths,
|
||||
base: args.base,
|
||||
head: args.head,
|
||||
staged: args.staged,
|
||||
});
|
||||
process.exitCode = await runChangedCheck(result, {
|
||||
...args,
|
||||
explicitPaths: args.paths.length > 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { resolveCodexAppServerProtocolSource } from "./lib/codex-app-server-protocol-source.js";
|
||||
|
||||
const codexRepo = process.env.OPENCLAW_CODEX_REPO
|
||||
? path.resolve(process.env.OPENCLAW_CODEX_REPO)
|
||||
: path.resolve(process.cwd(), "../codex");
|
||||
const schemaRoot = path.join(codexRepo, "codex-rs/app-server-protocol/schema/typescript");
|
||||
const sourceSchemaRoot = path.join(codexRepo, "codex-rs/app-server-protocol/schema");
|
||||
const { sourceRoot: sourceSchemaRoot } = await resolveCodexAppServerProtocolSource(process.cwd());
|
||||
const schemaRoot = path.join(sourceSchemaRoot, "typescript");
|
||||
const generatedRoot = path.resolve(
|
||||
process.cwd(),
|
||||
"extensions/codex/src/app-server/protocol-generated",
|
||||
@@ -104,12 +102,14 @@ if (failures.length > 0) {
|
||||
for (const failure of failures) {
|
||||
console.error(`- ${failure}`);
|
||||
}
|
||||
console.error("Run `pnpm codex-app-server:protocol:sync` after refreshing ../codex.");
|
||||
console.error(
|
||||
`Run \`pnpm codex-app-server:protocol:sync\` after refreshing the Codex checkout at ${path.resolve(sourceSchemaRoot, "../../..")}.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Codex app-server generated protocol matches OpenClaw bridge assumptions: ${schemaRoot}`,
|
||||
`Codex app-server generated protocol matches OpenClaw bridge assumptions: ${sourceSchemaRoot}`,
|
||||
);
|
||||
|
||||
async function compareGeneratedProtocolMirror(): Promise<void> {
|
||||
@@ -130,14 +130,12 @@ async function compareGeneratedProtocolMirror(): Promise<void> {
|
||||
);
|
||||
const target = await fs.readFile(path.join(targetTsRoot, file), "utf8");
|
||||
if (source !== target) {
|
||||
failures.push(
|
||||
`protocol-generated/typescript/${file}: differs from normalized ../codex schema`,
|
||||
);
|
||||
failures.push(`protocol-generated/typescript/${file}: differs from normalized source schema`);
|
||||
}
|
||||
}
|
||||
for (const file of targetFiles) {
|
||||
if (!sourceSet.has(file)) {
|
||||
failures.push(`protocol-generated/typescript/${file}: no longer present in ../codex schema`);
|
||||
failures.push(`protocol-generated/typescript/${file}: no longer present in source schema`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +159,7 @@ async function compareGeneratedProtocolMirror(): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
if (source !== target) {
|
||||
failures.push(`protocol-generated/json/${schema}: differs from ../codex schema`);
|
||||
failures.push(`protocol-generated/json/${schema}: differs from source schema`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
scripts/lib/codex-app-server-protocol-source.ts
Normal file
74
scripts/lib/codex-app-server-protocol-source.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const PROTOCOL_SCHEMA_RELATIVE_PATH = "codex-rs/app-server-protocol/schema";
|
||||
|
||||
export async function resolveCodexAppServerProtocolSource(repoRoot: string): Promise<{
|
||||
codexRepo: string;
|
||||
sourceRoot: string;
|
||||
}> {
|
||||
const candidates = await collectCodexRepoCandidates(repoRoot);
|
||||
const checked: string[] = [];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const codexRepo = path.resolve(candidate);
|
||||
if (checked.includes(codexRepo)) {
|
||||
continue;
|
||||
}
|
||||
checked.push(codexRepo);
|
||||
const sourceRoot = path.join(codexRepo, PROTOCOL_SCHEMA_RELATIVE_PATH);
|
||||
if (await isDirectory(path.join(sourceRoot, "typescript"))) {
|
||||
return { codexRepo, sourceRoot };
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
[
|
||||
"Codex app-server protocol schema not found.",
|
||||
"Set OPENCLAW_CODEX_REPO to a checkout of openai/codex, or keep a sibling `codex` checkout next to the primary OpenClaw checkout.",
|
||||
`Checked: ${checked.join(", ") || "<none>"}`,
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
async function collectCodexRepoCandidates(repoRoot: string): Promise<string[]> {
|
||||
const candidates = [
|
||||
process.env.OPENCLAW_CODEX_REPO,
|
||||
path.resolve(repoRoot, "../codex"),
|
||||
await resolvePrimaryWorktreeSiblingCodex(repoRoot),
|
||||
];
|
||||
return candidates.filter((candidate): candidate is string => Boolean(candidate));
|
||||
}
|
||||
|
||||
async function resolvePrimaryWorktreeSiblingCodex(repoRoot: string): Promise<string | undefined> {
|
||||
const gitFilePath = path.join(repoRoot, ".git");
|
||||
let gitFile: string;
|
||||
try {
|
||||
gitFile = await fs.readFile(gitFilePath, "utf8");
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const match = /^gitdir:\s*(.+)$/m.exec(gitFile);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const gitDir = path.resolve(repoRoot, match[1].trim());
|
||||
const worktreeMarker = `${path.sep}.git${path.sep}worktrees${path.sep}`;
|
||||
const markerIndex = gitDir.indexOf(worktreeMarker);
|
||||
if (markerIndex < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const primaryWorktreeRoot = gitDir.slice(0, markerIndex);
|
||||
return path.join(path.dirname(primaryWorktreeRoot), "codex");
|
||||
}
|
||||
|
||||
async function isDirectory(candidate: string): Promise<boolean> {
|
||||
try {
|
||||
return (await fs.stat(candidate)).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
56
scripts/qa-coverage-report.ts
Normal file
56
scripts/qa-coverage-report.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { runQaCoverageReportCommand } from "../extensions/qa-lab/src/cli.runtime.ts";
|
||||
|
||||
type Options = {
|
||||
json?: boolean;
|
||||
output?: string;
|
||||
repoRoot?: string;
|
||||
};
|
||||
|
||||
function takeValue(args: string[], index: number, flag: string): string {
|
||||
const value = args[index + 1];
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new Error(`${flag} requires a value.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseArgs(args: string[]): Options {
|
||||
const opts: Options = {};
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
switch (arg) {
|
||||
case "--help":
|
||||
case "-h":
|
||||
process.stdout.write(`Usage: openclaw qa coverage [options]
|
||||
|
||||
Options:
|
||||
--json Print machine-readable JSON
|
||||
--output <path> Write the report to a file
|
||||
--repo-root <path> Repository root to target
|
||||
-h, --help Display help
|
||||
`);
|
||||
process.exit(0);
|
||||
case "--json":
|
||||
opts.json = true;
|
||||
break;
|
||||
case "--output":
|
||||
opts.output = takeValue(args, index, arg);
|
||||
index += 1;
|
||||
break;
|
||||
case "--repo-root":
|
||||
opts.repoRoot = takeValue(args, index, arg);
|
||||
index += 1;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown qa coverage option: ${arg}`);
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
const opts = parseArgs(process.argv.slice(2));
|
||||
await runQaCoverageReportCommand({
|
||||
...(opts.json ? { json: true } : {}),
|
||||
...(opts.output ? { output: opts.output } : {}),
|
||||
...(opts.repoRoot ? { repoRoot: opts.repoRoot } : {}),
|
||||
});
|
||||
@@ -796,6 +796,7 @@ const shouldUseExistingDistForGatewayClient = (deps, buildRequirement) =>
|
||||
statMtime(deps.distEntry, deps.fs) != null;
|
||||
|
||||
const isQaParityReportCommand = (args) => args[0] === "qa" && args[1] === "parity-report";
|
||||
const isQaCoverageReportCommand = (args) => args[0] === "qa" && args[1] === "coverage";
|
||||
|
||||
const shouldRunQaParityReportFromSource = (deps, buildRequirement) =>
|
||||
buildRequirement.reason === "missing_private_qa_dist" &&
|
||||
@@ -803,6 +804,12 @@ const shouldRunQaParityReportFromSource = (deps, buildRequirement) =>
|
||||
deps.env.OPENCLAW_FORCE_BUILD !== "1" &&
|
||||
statMtime(path.join(deps.cwd, "extensions", "qa-lab", "src", "cli.runtime.ts"), deps.fs) != null;
|
||||
|
||||
const shouldRunQaCoverageReportFromSource = (deps, buildRequirement) =>
|
||||
buildRequirement.reason === "missing_private_qa_dist" &&
|
||||
isQaCoverageReportCommand(deps.args) &&
|
||||
deps.env.OPENCLAW_FORCE_BUILD !== "1" &&
|
||||
statMtime(path.join(deps.cwd, "extensions", "qa-lab", "src", "cli.runtime.ts"), deps.fs) != null;
|
||||
|
||||
const runQaParityReportFromSource = async (deps) => {
|
||||
const sourceEntrypoint = path.join(deps.cwd, "scripts", "qa-parity-report.ts");
|
||||
const nodeProcess = deps.spawn(
|
||||
@@ -823,6 +830,26 @@ const runQaParityReportFromSource = async (deps) => {
|
||||
return res.exitCode ?? 1;
|
||||
};
|
||||
|
||||
const runQaCoverageReportFromSource = async (deps) => {
|
||||
const sourceEntrypoint = path.join(deps.cwd, "scripts", "qa-coverage-report.ts");
|
||||
const nodeProcess = deps.spawn(
|
||||
deps.execPath,
|
||||
["--import", "tsx", sourceEntrypoint, ...deps.args.slice(2)],
|
||||
{
|
||||
cwd: deps.cwd,
|
||||
env: deps.env,
|
||||
stdio: deps.outputTee ? ["inherit", "pipe", "pipe"] : "inherit",
|
||||
},
|
||||
);
|
||||
pipeSpawnedOutput(nodeProcess, deps);
|
||||
const res = await waitForSpawnedProcess(nodeProcess, deps);
|
||||
const interruptedExitCode = getInterruptedSpawnExitCode(res);
|
||||
if (interruptedExitCode !== null) {
|
||||
return interruptedExitCode;
|
||||
}
|
||||
return res.exitCode ?? 1;
|
||||
};
|
||||
|
||||
export async function runNodeMain(params = {}) {
|
||||
const deps = {
|
||||
spawn: params.spawn ?? spawn,
|
||||
@@ -862,6 +889,7 @@ export async function runNodeMain(params = {}) {
|
||||
buildRequirement,
|
||||
);
|
||||
const useQaParityReportSource = shouldRunQaParityReportFromSource(deps, buildRequirement);
|
||||
const useQaCoverageReportSource = shouldRunQaCoverageReportFromSource(deps, buildRequirement);
|
||||
if (useExistingGatewayClientDist) {
|
||||
buildRequirement = { shouldBuild: false, reason: "gateway_client_existing_dist" };
|
||||
}
|
||||
@@ -870,6 +898,11 @@ export async function runNodeMain(params = {}) {
|
||||
exitCode = await runQaParityReportFromSource(deps);
|
||||
return await closeRunNodeOutputTee(deps, exitCode);
|
||||
}
|
||||
if (useQaCoverageReportSource) {
|
||||
logRunner("Running QA coverage report from source without rebuilding private QA dist.", deps);
|
||||
exitCode = await runQaCoverageReportFromSource(deps);
|
||||
return await closeRunNodeOutputTee(deps, exitCode);
|
||||
}
|
||||
if (!buildRequirement.shouldBuild) {
|
||||
if (!useExistingGatewayClientDist) {
|
||||
const runtimePostBuildRequirement = resolveRuntimePostBuildRequirement(deps);
|
||||
|
||||
@@ -52,16 +52,24 @@ export function filterSparseMissingOxlintTargets(
|
||||
} = {},
|
||||
) {
|
||||
if (!isSparseCheckoutEnabled({ cwd })) {
|
||||
return { args, hadExplicitTargets: false, remainingExplicitTargets: 0, skippedTargets: [] };
|
||||
return {
|
||||
args,
|
||||
hadExplicitTargets: false,
|
||||
remainingExplicitTargets: 0,
|
||||
skippedTargets: [],
|
||||
skippedConfigs: [],
|
||||
};
|
||||
}
|
||||
|
||||
const filteredArgs = [];
|
||||
const skippedTargets = [];
|
||||
const skippedConfigs = [];
|
||||
let hadExplicitTargets = false;
|
||||
let remainingExplicitTargets = 0;
|
||||
let consumeNextValue = false;
|
||||
|
||||
for (const arg of args) {
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
if (consumeNextValue) {
|
||||
filteredArgs.push(arg);
|
||||
consumeNextValue = false;
|
||||
@@ -74,6 +82,29 @@ export function filterSparseMissingOxlintTargets(
|
||||
}
|
||||
|
||||
if (arg.startsWith("--")) {
|
||||
if (arg === "--tsconfig") {
|
||||
const value = args[index + 1];
|
||||
if (value !== undefined) {
|
||||
index += 1;
|
||||
if (!fileExists(path.resolve(cwd, value)) && isTrackedPath({ cwd, target: value })) {
|
||||
skippedConfigs.push(value);
|
||||
continue;
|
||||
}
|
||||
filteredArgs.push(arg, value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (arg.startsWith("--tsconfig=")) {
|
||||
const value = arg.slice("--tsconfig=".length);
|
||||
if (
|
||||
value &&
|
||||
!fileExists(path.resolve(cwd, value)) &&
|
||||
isTrackedPath({ cwd, target: value })
|
||||
) {
|
||||
skippedConfigs.push(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
filteredArgs.push(arg);
|
||||
if (!arg.includes("=") && OXLINT_VALUE_FLAGS.has(arg)) {
|
||||
consumeNextValue = true;
|
||||
@@ -97,7 +128,13 @@ export function filterSparseMissingOxlintTargets(
|
||||
filteredArgs.push(arg);
|
||||
}
|
||||
|
||||
return { args: filteredArgs, hadExplicitTargets, remainingExplicitTargets, skippedTargets };
|
||||
return {
|
||||
args: filteredArgs,
|
||||
hadExplicitTargets,
|
||||
remainingExplicitTargets,
|
||||
skippedTargets,
|
||||
skippedConfigs,
|
||||
};
|
||||
}
|
||||
|
||||
function getSparseCheckoutEnabled({ cwd }) {
|
||||
@@ -159,6 +196,12 @@ export async function main(argv = process.argv.slice(2), runtimeEnv = process.en
|
||||
`[oxlint] sparse checkout is missing tracked target(s); skipping ${sparseTargets.skippedTargets.join(", ")}`,
|
||||
);
|
||||
}
|
||||
if (sparseTargets.skippedConfigs.length > 0) {
|
||||
console.error(
|
||||
`[oxlint] sparse checkout is missing tracked config(s); skipping oxlint: ${sparseTargets.skippedConfigs.join(", ")}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (sparseTargets.hadExplicitTargets && sparseTargets.remainingExplicitTargets === 0) {
|
||||
console.error("[oxlint] no present sparse-checkout targets remain; skipping oxlint.");
|
||||
return;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { resolveCodexAppServerProtocolSource } from "./lib/codex-app-server-protocol-source.js";
|
||||
|
||||
const codexRepo = process.env.OPENCLAW_CODEX_REPO
|
||||
? path.resolve(process.env.OPENCLAW_CODEX_REPO)
|
||||
: path.resolve(process.cwd(), "../codex");
|
||||
|
||||
const sourceRoot = path.join(codexRepo, "codex-rs/app-server-protocol/schema");
|
||||
const { sourceRoot } = await resolveCodexAppServerProtocolSource(process.cwd());
|
||||
const targetRoot = path.resolve(
|
||||
process.cwd(),
|
||||
"extensions/codex/src/app-server/protocol-generated",
|
||||
|
||||
@@ -22,9 +22,15 @@ DOCKER_USER="${OPENCLAW_DOCKER_USER:-node}"
|
||||
DOCKER_HOME_MOUNT=()
|
||||
DOCKER_TRUSTED_HARNESS_MOUNT=()
|
||||
DOCKER_TRUSTED_HARNESS_CONTAINER_DIR=""
|
||||
DOCKER_CACHE_CONTAINER_DIR="/tmp/openclaw-cache"
|
||||
DOCKER_CLI_TOOLS_CONTAINER_DIR="/tmp/openclaw-npm-global"
|
||||
DOCKER_EXTRA_ENV_FILES=()
|
||||
DOCKER_AUTH_PRESTAGED=0
|
||||
|
||||
openclaw_live_codex_harness_is_ci() {
|
||||
[[ -n "${CI:-}" && "${CI:-}" != "false" ]] || [[ -n "${GITHUB_ACTIONS:-}" && "${GITHUB_ACTIONS:-}" != "false" ]]
|
||||
}
|
||||
|
||||
openclaw_live_codex_harness_append_build_extension() {
|
||||
local extension="${1:?extension required}"
|
||||
local current="${OPENCLAW_DOCKER_BUILD_EXTENSIONS:-${OPENCLAW_EXTENSIONS:-}}"
|
||||
@@ -50,6 +56,13 @@ if [[ "$CODEX_HARNESS_AUTH_MODE" == "api-key" && -z "${OPENAI_API_KEY:-}" ]]; th
|
||||
echo "ERROR: OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key requires OPENAI_API_KEY." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$CODEX_HARNESS_AUTH_MODE" != "api-key" && ! -s "$HOME/.codex/auth.json" ]]; then
|
||||
echo "ERROR: OPENCLAW_LIVE_CODEX_HARNESS_AUTH=codex-auth requires ~/.codex/auth.json before building the live Docker image." >&2
|
||||
if [[ -n "${OPENAI_API_KEY:-}" ]]; then
|
||||
echo "If this is a Testbox/API-key run, set OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key and run through openclaw-testbox-env." >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup_temp_dirs() {
|
||||
if ((${#TEMP_DIRS[@]} > 0)); then
|
||||
@@ -60,7 +73,7 @@ trap cleanup_temp_dirs EXIT
|
||||
|
||||
if [[ -n "${OPENCLAW_DOCKER_CLI_TOOLS_DIR:-}" ]]; then
|
||||
CLI_TOOLS_DIR="${OPENCLAW_DOCKER_CLI_TOOLS_DIR}"
|
||||
elif [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then
|
||||
elif openclaw_live_codex_harness_is_ci; then
|
||||
CLI_TOOLS_DIR="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-docker-cli-tools.XXXXXX")"
|
||||
TEMP_DIRS+=("$CLI_TOOLS_DIR")
|
||||
else
|
||||
@@ -68,7 +81,7 @@ else
|
||||
fi
|
||||
if [[ -n "${OPENCLAW_DOCKER_CACHE_HOME_DIR:-}" ]]; then
|
||||
CACHE_HOME_DIR="${OPENCLAW_DOCKER_CACHE_HOME_DIR}"
|
||||
elif [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then
|
||||
elif openclaw_live_codex_harness_is_ci; then
|
||||
CACHE_HOME_DIR="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-docker-cache.XXXXXX")"
|
||||
TEMP_DIRS+=("$CACHE_HOME_DIR")
|
||||
else
|
||||
@@ -77,7 +90,10 @@ fi
|
||||
|
||||
mkdir -p "$CLI_TOOLS_DIR"
|
||||
mkdir -p "$CACHE_HOME_DIR"
|
||||
if [[ "${CI:-}" == "true" || "${GITHUB_ACTIONS:-}" == "true" ]]; then
|
||||
if openclaw_live_codex_harness_is_ci; then
|
||||
chmod 0777 "$CLI_TOOLS_DIR" "$CACHE_HOME_DIR" || true
|
||||
fi
|
||||
if openclaw_live_codex_harness_is_ci; then
|
||||
DOCKER_USER="$(id -u):$(id -g)"
|
||||
DOCKER_HOME_DIR="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-docker-home.XXXXXX")"
|
||||
TEMP_DIRS+=("$DOCKER_HOME_DIR")
|
||||
@@ -146,6 +162,11 @@ export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
|
||||
export COREPACK_HOME="${COREPACK_HOME:-$XDG_CACHE_HOME/node/corepack}"
|
||||
export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-$XDG_CACHE_HOME/npm}"
|
||||
export npm_config_cache="$NPM_CONFIG_CACHE"
|
||||
if [ "${OPENCLAW_LIVE_CODEX_HARNESS_DEBUG:-}" = "1" ]; then
|
||||
id
|
||||
mount | grep -E 'openclaw-cache|openclaw-npm|/home/node' || true
|
||||
ls -ld "$HOME" "$XDG_CACHE_HOME" "$NPM_CONFIG_PREFIX" 2>/dev/null || true
|
||||
fi
|
||||
# Force the Codex harness to use the staged `~/.codex` auth files. This lane
|
||||
# is not meant to exercise raw OpenAI API-key routing unless the lane
|
||||
# explicitly opts into API-key auth for CI.
|
||||
@@ -254,6 +275,12 @@ DOCKER_RUN_ARGS=(docker run --rm -t \
|
||||
--entrypoint bash \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-e HOME=/home/node \
|
||||
-e NPM_CONFIG_PREFIX="$DOCKER_CLI_TOOLS_CONTAINER_DIR" \
|
||||
-e npm_config_prefix="$DOCKER_CLI_TOOLS_CONTAINER_DIR" \
|
||||
-e XDG_CACHE_HOME="$DOCKER_CACHE_CONTAINER_DIR" \
|
||||
-e COREPACK_HOME="$DOCKER_CACHE_CONTAINER_DIR/node/corepack" \
|
||||
-e NPM_CONFIG_CACHE="$DOCKER_CACHE_CONTAINER_DIR/npm" \
|
||||
-e npm_config_cache="$DOCKER_CACHE_CONTAINER_DIR/npm" \
|
||||
-e NODE_OPTIONS=--disable-warning=ExperimentalWarning \
|
||||
-e OPENCLAW_AGENT_HARNESS_FALLBACK=none \
|
||||
-e OPENCLAW_DOCKER_AUTH_PRESTAGED="$DOCKER_AUTH_PRESTAGED" \
|
||||
@@ -287,14 +314,22 @@ openclaw_live_append_array DOCKER_RUN_ARGS DOCKER_EXTRA_ENV_FILES
|
||||
openclaw_live_append_array DOCKER_RUN_ARGS DOCKER_HOME_MOUNT
|
||||
openclaw_live_append_array DOCKER_RUN_ARGS DOCKER_TRUSTED_HARNESS_MOUNT
|
||||
DOCKER_RUN_ARGS+=(\
|
||||
-v "$CACHE_HOME_DIR":/home/node/.cache \
|
||||
-v "$CACHE_HOME_DIR":"$DOCKER_CACHE_CONTAINER_DIR" \
|
||||
-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)
|
||||
-v "$CLI_TOOLS_DIR":"$DOCKER_CLI_TOOLS_CONTAINER_DIR")
|
||||
openclaw_live_append_array DOCKER_RUN_ARGS EXTERNAL_AUTH_MOUNTS
|
||||
openclaw_live_append_array DOCKER_RUN_ARGS PROFILE_MOUNT
|
||||
DOCKER_RUN_ARGS+=(\
|
||||
"$LIVE_IMAGE_NAME" \
|
||||
-lc "$LIVE_TEST_CMD")
|
||||
if [[ "${OPENCLAW_LIVE_CODEX_HARNESS_DEBUG:-}" == "1" ]]; then
|
||||
echo "==> Docker debug: host ids and mounted dirs"
|
||||
id
|
||||
ls -ld "$CACHE_HOME_DIR" "$CLI_TOOLS_DIR" "${DOCKER_HOME_DIR:-$HOME}" 2>/dev/null || true
|
||||
printf '==> Docker debug args:'
|
||||
printf ' %q' "${DOCKER_RUN_ARGS[@]}"
|
||||
printf '\n'
|
||||
fi
|
||||
"${DOCKER_RUN_ARGS[@]}"
|
||||
|
||||
Reference in New Issue
Block a user