diff --git a/scripts/e2e/agent-bundle-mcp-tools-docker.sh b/scripts/e2e/agent-bundle-mcp-tools-docker.sh index 29c62e4e1fb..510a0b252c0 100755 --- a/scripts/e2e/agent-bundle-mcp-tools-docker.sh +++ b/scripts/e2e/agent-bundle-mcp-tools-docker.sh @@ -35,9 +35,9 @@ set -e if [ "$status" -ne 0 ]; then echo "Docker OpenClaw bundle MCP tool availability smoke failed" - cat "$RUN_LOG" + docker_e2e_print_log "$RUN_LOG" exit "$status" fi -cat "$RUN_LOG" +docker_e2e_print_log "$RUN_LOG" echo "OK" diff --git a/scripts/e2e/commitments-safety-docker.sh b/scripts/e2e/commitments-safety-docker.sh index 43e4c02faf1..71ff1d93378 100755 --- a/scripts/e2e/commitments-safety-docker.sh +++ b/scripts/e2e/commitments-safety-docker.sh @@ -31,7 +31,7 @@ set -e if [ "$status" -ne 0 ]; then echo "Docker commitments safety smoke failed" - cat "$RUN_LOG" + docker_e2e_print_log "$RUN_LOG" exit "$status" fi diff --git a/scripts/e2e/crestodian-first-run-docker.sh b/scripts/e2e/crestodian-first-run-docker.sh index eddf5aa0418..02afad27040 100644 --- a/scripts/e2e/crestodian-first-run-docker.sh +++ b/scripts/e2e/crestodian-first-run-docker.sh @@ -35,9 +35,9 @@ set -e if [ "$status" -ne 0 ]; then echo "Docker Crestodian first-run smoke failed" - cat "$RUN_LOG" + docker_e2e_print_log "$RUN_LOG" exit "$status" fi -cat "$RUN_LOG" +docker_e2e_print_log "$RUN_LOG" echo "OK" diff --git a/scripts/e2e/crestodian-planner-docker.sh b/scripts/e2e/crestodian-planner-docker.sh index 633c803fdcb..8dbfb665c96 100755 --- a/scripts/e2e/crestodian-planner-docker.sh +++ b/scripts/e2e/crestodian-planner-docker.sh @@ -35,9 +35,9 @@ set -e if [ "$status" -ne 0 ]; then echo "Docker Crestodian planner fallback smoke failed" - cat "$RUN_LOG" + docker_e2e_print_log "$RUN_LOG" exit "$status" fi -cat "$RUN_LOG" +docker_e2e_print_log "$RUN_LOG" echo "OK" diff --git a/scripts/e2e/crestodian-rescue-docker.sh b/scripts/e2e/crestodian-rescue-docker.sh index 1cef91a7e3c..985ab0a96b1 100755 --- a/scripts/e2e/crestodian-rescue-docker.sh +++ b/scripts/e2e/crestodian-rescue-docker.sh @@ -35,9 +35,9 @@ set -e if [ "$status" -ne 0 ]; then echo "Docker Crestodian rescue smoke failed" - cat "$RUN_LOG" + docker_e2e_print_log "$RUN_LOG" exit "$status" fi -cat "$RUN_LOG" +docker_e2e_print_log "$RUN_LOG" echo "OK" diff --git a/scripts/e2e/plugin-binding-command-escape-docker.sh b/scripts/e2e/plugin-binding-command-escape-docker.sh index 4bfc1ed3bb8..42ddcdd37c9 100644 --- a/scripts/e2e/plugin-binding-command-escape-docker.sh +++ b/scripts/e2e/plugin-binding-command-escape-docker.sh @@ -40,16 +40,24 @@ set -e if [ "$status" -ne 0 ]; then echo "Docker plugin binding command escape smoke failed" - cat "$RUN_LOG" + docker_e2e_print_log "$RUN_LOG" exit "$status" fi if ! node - "$RUN_LOG" <<'NODE' const fs = require("node:fs"); const logPath = process.argv[2]; -const text = fs - .readFileSync(logPath, "utf8") - .replace(/\x1B\[[0-?]*[ -/]*[@-~]/gu, ""); +const scanBytes = 65536; +const stat = fs.statSync(logPath); +const length = Math.min(stat.size, scanBytes); +const buffer = Buffer.alloc(length); +const fd = fs.openSync(logPath, "r"); +try { + fs.readSync(fd, buffer, 0, length, stat.size - length); +} finally { + fs.closeSync(fd); +} +const text = buffer.toString("utf8").replace(/\x1B\[[0-?]*[ -/]*[@-~]/gu, ""); if (!/(?:^|\n)\s*Tests\s+3 passed\b/u.test(text)) { console.error("expected focused Vitest summary for exactly 3 passed tests"); @@ -59,7 +67,7 @@ if (!/(?:^|\n)\s*Tests\s+3 passed\b/u.test(text)) { NODE then echo "Docker plugin binding command escape smoke did not stay focused" - cat "$RUN_LOG" + docker_e2e_print_log "$RUN_LOG" exit 1 fi diff --git a/scripts/e2e/session-runtime-context-docker.sh b/scripts/e2e/session-runtime-context-docker.sh index 4a5a1aff516..9a9d256e05f 100644 --- a/scripts/e2e/session-runtime-context-docker.sh +++ b/scripts/e2e/session-runtime-context-docker.sh @@ -32,7 +32,7 @@ set -e if [ "$status" -ne 0 ]; then echo "Docker session runtime context smoke failed" - cat "$RUN_LOG" + docker_e2e_print_log "$RUN_LOG" exit "$status" fi diff --git a/scripts/plugin-sdk-surface-report.mjs b/scripts/plugin-sdk-surface-report.mjs index f89b36bba51..16c211a3e4c 100644 --- a/scripts/plugin-sdk-surface-report.mjs +++ b/scripts/plugin-sdk-surface-report.mjs @@ -14,7 +14,46 @@ import { } from "./lib/plugin-sdk-entries.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const checkOnly = process.argv.includes("--check"); +function usage() { + return `Usage: node scripts/plugin-sdk-surface-report.mjs [--check] + +Reports plugin SDK export surface metadata. + +Options: + --check Fail when SDK surface budgets are exceeded. + -h, --help Show this help. +`; +} + +function parseArgs(argv) { + const args = { check: false, help: false }; + for (const arg of argv) { + if (arg === "--check") { + args.check = true; + continue; + } + if (arg === "--help" || arg === "-h") { + args.help = true; + continue; + } + throw new Error(`Unknown plugin SDK surface report option: ${arg}`); + } + return args; +} + +let cliArgs; +try { + cliArgs = parseArgs(process.argv.slice(2)); +} catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); +} +if (cliArgs.help) { + process.stdout.write(usage()); + process.exit(0); +} + +const checkOnly = cliArgs.check; const publicEntrypointSet = new Set(publicPluginSdkEntrypoints); const localOnlyEntrypointSet = new Set(privateLocalOnlyPluginSdkEntrypoints); const deprecatedPublicEntrypointSet = new Set(deprecatedPublicPluginSdkEntrypoints); diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index 8a9eebd81c6..5c9986df50a 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -56,6 +56,14 @@ const QR_IMPORT_DOCKER_E2E_PATH = "scripts/e2e/qr-import-docker.sh"; const MULTI_NODE_UPDATE_DOCKER_E2E_PATH = "scripts/e2e/multi-node-update-docker.sh"; const BUNDLED_PLUGIN_INSTALL_UNINSTALL_E2E_PATH = "scripts/e2e/bundled-plugin-install-uninstall-docker.sh"; +const AGENT_BUNDLE_MCP_TOOLS_DOCKER_E2E_PATH = + "scripts/e2e/agent-bundle-mcp-tools-docker.sh"; +const COMMITMENTS_SAFETY_DOCKER_E2E_PATH = "scripts/e2e/commitments-safety-docker.sh"; +const CRESTODIAN_FIRST_RUN_DOCKER_E2E_PATH = "scripts/e2e/crestodian-first-run-docker.sh"; +const CRESTODIAN_PLANNER_DOCKER_E2E_PATH = "scripts/e2e/crestodian-planner-docker.sh"; +const CRESTODIAN_RESCUE_DOCKER_E2E_PATH = "scripts/e2e/crestodian-rescue-docker.sh"; +const SESSION_RUNTIME_CONTEXT_DOCKER_E2E_PATH = + "scripts/e2e/session-runtime-context-docker.sh"; const BUNDLED_PLUGIN_INSTALL_UNINSTALL_SWEEP_PATH = "scripts/e2e/lib/bundled-plugin-install-uninstall/sweep.sh"; const BUNDLED_PLUGIN_INSTALL_UNINSTALL_PROBE_PATH = @@ -3360,6 +3368,31 @@ output="$(cat "$sampler_log")" } }); + it("keeps captured Docker E2E run log replay bounded", () => { + for (const path of [ + AGENT_BUNDLE_MCP_TOOLS_DOCKER_E2E_PATH, + COMMITMENTS_SAFETY_DOCKER_E2E_PATH, + CRESTODIAN_FIRST_RUN_DOCKER_E2E_PATH, + CRESTODIAN_PLANNER_DOCKER_E2E_PATH, + CRESTODIAN_RESCUE_DOCKER_E2E_PATH, + PLUGIN_BINDING_COMMAND_ESCAPE_DOCKER_E2E_PATH, + SESSION_RUNTIME_CONTEXT_DOCKER_E2E_PATH, + ]) { + const runner = readFileSync(path, "utf8"); + + expect(runner, path).toContain('RUN_LOG="$(mktemp'); + expect(runner, path).toContain('docker_e2e_print_log "$RUN_LOG"'); + expect(runner, path).not.toContain('cat "$RUN_LOG"'); + } + + const pluginBinding = readFileSync(PLUGIN_BINDING_COMMAND_ESCAPE_DOCKER_E2E_PATH, "utf8"); + expect(pluginBinding).toContain("const scanBytes = 65536"); + expect(pluginBinding).toContain("fs.statSync(logPath)"); + expect(pluginBinding).toContain("fs.readSync(fd, buffer, 0, length, stat.size - length)"); + expect(pluginBinding).not.toContain("process.env.OPENCLAW_DOCKER_E2E_LOG_PRINT_BYTES"); + expect(pluginBinding).not.toContain('readFileSync(logPath, "utf8")'); + }); + it("keeps Open WebUI Docker E2E resource-guarded", () => { const runner = readFileSync(OPENWEBUI_DOCKER_E2E_PATH, "utf8"); diff --git a/test/scripts/plugin-sdk-surface-report.test.ts b/test/scripts/plugin-sdk-surface-report.test.ts index a4154b82dff..b448459dfc6 100644 --- a/test/scripts/plugin-sdk-surface-report.test.ts +++ b/test/scripts/plugin-sdk-surface-report.test.ts @@ -27,6 +27,30 @@ function readDefaultPublicFunctionExportBudget() { } describe("plugin SDK surface report", () => { + it("rejects unknown CLI options before collecting SDK stats", () => { + const result = spawnSync(process.execPath, ["scripts/plugin-sdk-surface-report.mjs", "--chekc"], { + cwd: process.cwd(), + encoding: "utf8", + }); + + expect(result.status).toBe(1); + expect(result.stdout).toBe(""); + expect(result.stderr.trim()).toBe("Unknown plugin SDK surface report option: --chekc"); + expect(result.stderr).not.toContain("at "); + }); + + it("prints help before collecting SDK stats", () => { + const result = spawnSync(process.execPath, ["scripts/plugin-sdk-surface-report.mjs", "--help"], { + cwd: process.cwd(), + encoding: "utf8", + }); + + expect(result.status).toBe(0); + expect(result.stdout).toContain("Usage: node scripts/plugin-sdk-surface-report.mjs"); + expect(result.stderr).toBe(""); + expect(result.stdout).not.toContain("all SDK entrypoints:"); + }); + it("rejects loose numeric budget env vars before collecting SDK stats", () => { const result = runSurfaceReport({ OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS: "1e9",