diff --git a/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml b/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml index 766e338b6eb..4fe39508fe7 100644 --- a/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml +++ b/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml @@ -427,6 +427,7 @@ jobs: add_profile_suite live-cli-backend-docker "stable full" add_profile_suite live-acp-bind-docker "stable full" add_profile_suite live-codex-harness-docker "stable full" + add_profile_suite live-subagent-announce-docker "stable full" add_profile_suite native-live-extensions-a-k "full" add_profile_suite native-live-extensions-media-audio "full" @@ -2291,6 +2292,12 @@ jobs: timeout_minutes: 40 profile_env_only: false profiles: stable full + - suite_id: live-subagent-announce-docker + label: Docker live subagent announce + command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 20m bash .release-harness/scripts/test-live-subagent-announce-docker.sh + timeout_minutes: 25 + profile_env_only: false + profiles: stable full env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} diff --git a/package.json b/package.json index 710141e8c79..5007b7ba11c 100644 --- a/package.json +++ b/package.json @@ -1599,6 +1599,7 @@ "test:docker:live-codex-harness": "bash scripts/test-live-codex-harness-docker.sh", "test:docker:live-codex-npm-plugin": "bash scripts/e2e/codex-npm-plugin-live-docker.sh", "test:docker:live-plugin-tool": "bash scripts/e2e/live-plugin-tool-docker.sh", + "test:docker:live-subagent-announce": "bash scripts/test-live-subagent-announce-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.5 bash scripts/test-live-gateway-models-docker.sh", diff --git a/scripts/check-changed.mjs b/scripts/check-changed.mjs index 09fb38061c3..3bd516b2e98 100644 --- a/scripts/check-changed.mjs +++ b/scripts/check-changed.mjs @@ -21,6 +21,7 @@ const LIVE_DOCKER_AUTH_SHELL_TARGETS = [ "scripts/test-live-codex-harness-docker.sh", "scripts/test-live-gateway-models-docker.sh", "scripts/test-live-models-docker.sh", + "scripts/test-live-subagent-announce-docker.sh", ]; export function createChangedCheckChildEnv(baseEnv = process.env) { diff --git a/scripts/lib/docker-e2e-scenarios.mjs b/scripts/lib/docker-e2e-scenarios.mjs index 004aaf38ace..0e3067ef2bc 100644 --- a/scripts/lib/docker-e2e-scenarios.mjs +++ b/scripts/lib/docker-e2e-scenarios.mjs @@ -379,6 +379,17 @@ export const tailLanes = [ timeoutMs: LIVE_ACP_TIMEOUT_MS, weight: 3, }), + liveLane( + "live-subagent-announce", + liveDockerScriptCommand("test-live-subagent-announce-docker.sh"), + { + cacheKey: "subagent-announce", + provider: "openai", + resources: ["npm"], + timeoutMs: 25 * 60 * 1000, + weight: 3, + }, + ), liveLane( "live-codex-bind", liveDockerScriptCommand( diff --git a/scripts/test-live-subagent-announce-docker.sh b/scripts/test-live-subagent-announce-docker.sh new file mode 100644 index 00000000000..6ca35209772 --- /dev/null +++ b/scripts/test-live-subagent-announce-docker.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ROOT_DIR="${OPENCLAW_LIVE_DOCKER_REPO_ROOT:-$SCRIPT_ROOT_DIR}" +ROOT_DIR="$(cd "$ROOT_DIR" && pwd)" +TRUSTED_HARNESS_DIR="${OPENCLAW_LIVE_DOCKER_TRUSTED_HARNESS_DIR:-$SCRIPT_ROOT_DIR}" +if [[ -z "$TRUSTED_HARNESS_DIR" || ! -d "$TRUSTED_HARNESS_DIR" ]]; then + echo "ERROR: trusted live Docker harness directory not found: ${TRUSTED_HARNESS_DIR:-}." >&2 + exit 1 +fi +TRUSTED_HARNESS_DIR="$(cd "$TRUSTED_HARNESS_DIR" && pwd)" +source "$TRUSTED_HARNESS_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_live_default_profile_file)" +DOCKER_USER="${OPENCLAW_DOCKER_USER:-node}" +DOCKER_HOME_MOUNT=() +DOCKER_EXTRA_ENV_FILES=() +DOCKER_TRUSTED_HARNESS_CONTAINER_DIR="/trusted-harness" +DOCKER_TRUSTED_HARNESS_MOUNT=(-v "$TRUSTED_HARNESS_DIR":"$DOCKER_TRUSTED_HARNESS_CONTAINER_DIR":ro) +TEMP_DIRS=() + +cleanup_temp_dirs() { + if ((${#TEMP_DIRS[@]} > 0)); then + rm -rf "${TEMP_DIRS[@]}" + fi +} +trap cleanup_temp_dirs EXIT + +if [[ -n "${OPENCLAW_DOCKER_CACHE_HOME_DIR:-}" ]]; then + CACHE_HOME_DIR="${OPENCLAW_DOCKER_CACHE_HOME_DIR}" +elif openclaw_live_is_ci; then + CACHE_HOME_DIR="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-docker-cache.XXXXXX")" + TEMP_DIRS+=("$CACHE_HOME_DIR") +else + CACHE_HOME_DIR="$HOME/.cache/openclaw/docker-cache" +fi +mkdir -p "$CACHE_HOME_DIR" + +if openclaw_live_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") + DOCKER_HOME_MOUNT=(-v "$DOCKER_HOME_DIR":/home/node) +fi + +PROFILE_MOUNT=() +PROFILE_STATUS="none" +if [[ -f "$PROFILE_FILE" && -r "$PROFILE_FILE" ]]; then + PROFILE_MOUNT=(-v "$PROFILE_FILE":/home/node/.profile:ro) + PROFILE_STATUS="$PROFILE_FILE" +fi + +if [[ -n "${OPENAI_API_KEY:-}" || -n "${OPENAI_BASE_URL:-}" ]]; then + docker_env_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-subagent-live-env.XXXXXX")" + TEMP_DIRS+=("$docker_env_dir") + docker_env_file="$docker_env_dir/openai.env" + { + if [[ -n "${OPENAI_API_KEY:-}" ]]; then + printf 'OPENCLAW_DOCKER_LIVE_OPENAI_API_KEY=%s\n' "${OPENAI_API_KEY}" + fi + if [[ -n "${OPENAI_BASE_URL:-}" ]]; then + printf 'OPENCLAW_DOCKER_LIVE_OPENAI_BASE_URL=%s\n' "${OPENAI_BASE_URL}" + fi + } >"$docker_env_file" + DOCKER_EXTRA_ENV_FILES+=(--env-file "$docker_env_file") +fi + +CONTAINER_NODE_OPTIONS="${OPENCLAW_DOCKER_NODE_OPTIONS:-${NODE_OPTIONS:-}}" +if [[ -z "$(openclaw_live_trim "$CONTAINER_NODE_OPTIONS")" ]]; then + CONTAINER_NODE_OPTIONS="--max-old-space-size=4096" +fi +CONTAINER_NODE_OPTIONS="$CONTAINER_NODE_OPTIONS --disable-warning=ExperimentalWarning" + +read -r -d '' LIVE_TEST_CMD <<'EOF' || true +set -euo pipefail +[ -f "$HOME/.profile" ] && [ -r "$HOME/.profile" ] && source "$HOME/.profile" || true +if [ -n "${OPENCLAW_DOCKER_LIVE_OPENAI_API_KEY:-}" ]; then + export OPENAI_API_KEY="$OPENCLAW_DOCKER_LIVE_OPENAI_API_KEY" + unset OPENCLAW_DOCKER_LIVE_OPENAI_API_KEY +fi +if [ -n "${OPENCLAW_DOCKER_LIVE_OPENAI_BASE_URL:-}" ]; then + export OPENAI_BASE_URL="$OPENCLAW_DOCKER_LIVE_OPENAI_BASE_URL" + unset OPENCLAW_DOCKER_LIVE_OPENAI_BASE_URL +fi +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" +mkdir -p "$XDG_CACHE_HOME" "$COREPACK_HOME" "$NPM_CONFIG_CACHE" +chmod 700 "$XDG_CACHE_HOME" "$COREPACK_HOME" "$NPM_CONFIG_CACHE" || true +tmp_dir="$(mktemp -d)" +trusted_scripts_dir="${OPENCLAW_LIVE_DOCKER_SCRIPTS_DIR:-/src/scripts}" +source "$trusted_scripts_dir/lib/live-docker-stage.sh" +openclaw_live_stage_source_tree "$tmp_dir" +openclaw_live_stage_node_modules "$tmp_dir" +openclaw_live_link_runtime_tree "$tmp_dir" +openclaw_live_stage_state_dir "$tmp_dir/.openclaw-state" +openclaw_live_prepare_staged_config +cd "$tmp_dir" +OPENCLAW_LIVE_TEST=1 \ +OPENCLAW_LIVE_SUBAGENT_E2E=1 \ +OPENCLAW_VITEST_MAX_WORKERS="${OPENCLAW_VITEST_MAX_WORKERS:-1}" \ +node scripts/test-live.mjs -- src/agents/subagent-announce.live.test.ts -- --reporter=verbose +EOF + +OPENCLAW_LIVE_DOCKER_REPO_ROOT="$ROOT_DIR" "$TRUSTED_HARNESS_DIR/scripts/test-live-build-docker.sh" + +echo "==> Run subagent announce live test in Docker" +echo "==> Target: src/agents/subagent-announce.live.test.ts" +echo "==> Model: ${OPENCLAW_LIVE_SUBAGENT_E2E_MODEL:-openai/gpt-5.5}" +echo "==> Profile file: $PROFILE_STATUS" +DOCKER_RUN_ARGS=(docker run --rm -t \ + -u "$DOCKER_USER" \ + --entrypoint bash \ + -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ + -e HOME=/home/node \ + -e NODE_OPTIONS="$CONTAINER_NODE_OPTIONS" \ + -e OPENCLAW_SKIP_CHANNELS=1 \ + -e OPENCLAW_SUPPRESS_NOTES=1 \ + -e OPENCLAW_LIVE_DOCKER_SCRIPTS_DIR="${DOCKER_TRUSTED_HARNESS_CONTAINER_DIR}/scripts" \ + -e OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE="${OPENCLAW_LIVE_DOCKER_SOURCE_STAGE_MODE:-copy}" \ + -e OPENCLAW_LIVE_TEST=1 \ + -e OPENCLAW_LIVE_TEST_QUIET="${OPENCLAW_LIVE_TEST_QUIET:-}" \ + -e OPENCLAW_LIVE_WRAPPER_HEARTBEAT_MS="${OPENCLAW_LIVE_WRAPPER_HEARTBEAT_MS:-}" \ + -e OPENCLAW_LIVE_SUBAGENT_E2E=1 \ + -e OPENCLAW_LIVE_SUBAGENT_E2E_MODEL="${OPENCLAW_LIVE_SUBAGENT_E2E_MODEL:-}" \ + -e OPENCLAW_VITEST_FS_MODULE_CACHE=0 \ + -e OPENCLAW_VITEST_MAX_WORKERS="${OPENCLAW_VITEST_MAX_WORKERS:-1}") +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 "$ROOT_DIR":/src:ro \ + -v "$CONFIG_DIR":/home/node/.openclaw \ + -v "$WORKSPACE_DIR":/home/node/.openclaw/workspace) +openclaw_live_append_array DOCKER_RUN_ARGS PROFILE_MOUNT +DOCKER_RUN_ARGS+=(\ + "$LIVE_IMAGE_NAME" \ + -lc "$LIVE_TEST_CMD") +"${DOCKER_RUN_ARGS[@]}" diff --git a/src/agents/subagent-announce.live.test.ts b/src/agents/subagent-announce.live.test.ts index ccbcb543307..837c6608e4a 100644 --- a/src/agents/subagent-announce.live.test.ts +++ b/src/agents/subagent-announce.live.test.ts @@ -31,8 +31,8 @@ type InProcessAgentDispatch = | { phase: "started"; resultText?: undefined } | { phase: "completed"; resultText: string }; -const REQUEST_TIMEOUT_MS = 4 * 60_000; -const WAIT_TIMEOUT_MS = 5 * 60_000; +const REQUEST_TIMEOUT_MS = 8 * 60_000; +const WAIT_TIMEOUT_MS = 8 * 60_000; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -62,7 +62,7 @@ function openAiConfig( agentRuntime: { id: "pi" }, apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" }, baseUrl: "https://api.openai.com/v1", - timeoutSeconds: 180, + timeoutSeconds: 300, models: [ { id: modelKey.replace(/^openai\//u, ""), @@ -87,8 +87,8 @@ function openAiConfig( sandbox: { mode: "off" }, subagents: { allowAgents: ["*"], - runTimeoutSeconds: 120, - announceTimeoutMs: 120_000, + runTimeoutSeconds: 300, + announceTimeoutMs: 300_000, archiveAfterMinutes: 60, }, }, @@ -253,7 +253,7 @@ describeLive("subagent announce live", () => { taskName: "steered_child", cleanup: "keep", context: "isolated", - runTimeoutSeconds: 120, + runTimeoutSeconds: 300, })}.`, `Step 2: after spawn returns status="accepted", call subagents with exactly this JSON input: ${JSON.stringify( { @@ -312,6 +312,6 @@ describeLive("subagent announce live", () => { ).toBe(true); expect(inProcessAgentDispatches.length).toBeGreaterThanOrEqual(1); }, - 6 * 60_000, + 10 * 60_000, ); }); diff --git a/test/scripts/changed-lanes.test.ts b/test/scripts/changed-lanes.test.ts index 842d629624a..96c788504e7 100644 --- a/test/scripts/changed-lanes.test.ts +++ b/test/scripts/changed-lanes.test.ts @@ -491,6 +491,7 @@ describe("scripts/changed-lanes", () => { "scripts/test-live-codex-harness-docker.sh", "scripts/test-live-gateway-models-docker.sh", "scripts/test-live-models-docker.sh", + "scripts/test-live-subagent-announce-docker.sh", ], }); const schedulerDryRun = plan.commands.find( diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 84bd43f994f..553fd01e983 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -367,6 +367,7 @@ describe("package artifact reuse", () => { ); expect(workflow).toContain('add_profile_suite live-gateway-advisory-docker-xai-zai "full"'); expect(workflow).toContain('add_profile_suite live-cli-backend-docker "stable full"'); + expect(workflow).toContain('add_profile_suite live-subagent-announce-docker "stable full"'); expect(workflow).toContain( "inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id", ); @@ -375,6 +376,7 @@ describe("package artifact reuse", () => { expect(workflow).toContain("suite_id: live-gateway-advisory-docker-deepseek-fireworks"); expect(workflow).toContain("suite_id: live-gateway-advisory-docker-opencode-openrouter"); expect(workflow).toContain("suite_id: live-gateway-advisory-docker-xai-zai"); + expect(workflow).toContain("suite_id: live-subagent-announce-docker"); expect(workflow).toContain("suite_group: live-gateway-advisory-docker"); expect(workflow).toContain("OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks"); expect(workflow).toContain("OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter"); @@ -483,6 +485,7 @@ describe("package artifact reuse", () => { readFileSync("scripts/test-live-gateway-models-docker.sh", "utf8"), readFileSync("scripts/test-live-cli-backend-docker.sh", "utf8"), readFileSync("scripts/test-live-acp-bind-docker.sh", "utf8"), + readFileSync("scripts/test-live-subagent-announce-docker.sh", "utf8"), ]; const build = readFileSync("scripts/test-live-build-docker.sh", "utf8"); const stage = readFileSync("scripts/lib/live-docker-stage.sh", "utf8"); @@ -502,6 +505,9 @@ describe("package artifact reuse", () => { expect(workflow).toContain( 'command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-codex-harness-docker.sh', ); + expect(workflow).toContain( + 'command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 20m bash .release-harness/scripts/test-live-subagent-announce-docker.sh', + ); expect(scenarios).toContain("function liveDockerScriptCommand"); expect(scenarios).toContain( "if [ -d .release-harness/scripts ]; then harness=.release-harness", @@ -511,6 +517,9 @@ describe("package artifact reuse", () => { expect(scenarios).toMatch(/liveDockerScriptCommand\(\s*"test-live-cli-backend-docker\.sh"/u); expect(scenarios).toMatch(/liveDockerScriptCommand\(\s*"test-live-acp-bind-docker\.sh"/u); expect(scenarios).toMatch(/liveDockerScriptCommand\(\s*"test-live-codex-harness-docker\.sh"/u); + expect(scenarios).toMatch( + /liveDockerScriptCommand\(\s*"test-live-subagent-announce-docker\.sh"/u, + ); expect(scheduler).toContain("function liveDockerHarnessScriptCommand"); expect(scheduler).toContain('liveDockerHarnessScriptCommand("test-live-build-docker.sh")'); expect(harness).toContain('source "$TRUSTED_HARNESS_DIR/scripts/lib/live-docker-auth.sh"');