diff --git a/.github/workflows/agent-e2e.yml b/.github/workflows/agent-e2e.yml index 9a936700..ae4c1f50 100644 --- a/.github/workflows/agent-e2e.yml +++ b/.github/workflows/agent-e2e.yml @@ -1,7 +1,11 @@ name: Agent E2E -# Runs the agent e2e suites (e2e/) against the released Agentspan -# server JAR — a full Conductor server with the agent runtime baked in. +# Runs the agent e2e suites (e2e/) against the Conductor OSS server boot +# JAR (Maven Central) — the server this SDK ships against, with the agent +# runtime on by default from 3.32.0-rc.8 onward. The Agentspan server JAR +# is no longer used here; the Agentspan CLI binary is still downloaded +# (its own separate pin) since e2e/test_suite16_cli_skills.py drives it +# against this same server. # tests/integration/ai stays manual-only (same as upstream Agentspan, # which never ran its tests/integration in CI): run it locally against # a live server with `pytest tests/integration/ai`. @@ -12,12 +16,18 @@ name: Agent E2E on: [pull_request, workflow_dispatch] +# least privilege: the token is only used to download the public agentspan +# CLI release asset (gh release download) — read access suffices +permissions: + contents: read + concurrency: group: agent-e2e-${{ github.ref }} cancel-in-progress: true env: - AGENTSPAN_VERSION: "0.4.3" # pinned server/CLI release — bump deliberately + CONDUCTOR_SERVER_VERSION: "3.32.0-rc.8" # pinned server release — bump deliberately + AGENTSPAN_CLI_VERSION: "0.4.4" # pinned CLI release — independent of the server (revert to 0.4.3 if this breaks) jobs: agent-e2e: @@ -48,22 +58,20 @@ jobs: id: jar_cache uses: actions/cache@v4 with: - path: agentspan-server.jar - key: agentspan-server-${{ env.AGENTSPAN_VERSION }} + path: conductor-server.jar + key: conductor-oss-server-${{ env.CONDUCTOR_SERVER_VERSION }} - - name: Download server JAR from release + - name: Download server JAR from Maven Central if: steps.jar_cache.outputs.cache-hit != 'true' - env: - GH_TOKEN: ${{ github.token }} run: | - gh release download "v${AGENTSPAN_VERSION}" --repo agentspan-ai/agentspan \ - --pattern "agentspan-server-${AGENTSPAN_VERSION}.jar" --output agentspan-server.jar + curl -fL --retry 3 -o conductor-server.jar \ + "https://repo1.maven.org/maven2/org/conductoross/conductor-server/${CONDUCTOR_SERVER_VERSION}/conductor-server-${CONDUCTOR_SERVER_VERSION}-boot.jar" - name: Download CLI binary from release env: GH_TOKEN: ${{ github.token }} run: | - gh release download "v${AGENTSPAN_VERSION}" --repo agentspan-ai/agentspan \ + gh release download "v${AGENTSPAN_CLI_VERSION}" --repo agentspan-ai/agentspan \ --pattern "agentspan_linux_amd64" --output agentspan chmod +x agentspan @@ -80,7 +88,7 @@ jobs: - name: Start server run: | - java -jar agentspan-server.jar --server.port=8080 > server.log 2>&1 & + java -jar conductor-server.jar --server.port=8080 > server.log 2>&1 & - name: Wait for server health run: | diff --git a/e2e/conftest.py b/e2e/conftest.py index 85324fa6..442fa008 100644 --- a/e2e/conftest.py +++ b/e2e/conftest.py @@ -110,14 +110,23 @@ def _run(self, *args: str) -> subprocess.CompletedProcess: def set(self, name: str, value: str) -> None: result = self._run("credentials", "set", name, value) - assert result.returncode == 0, ( - f"credentials set {name} failed: {result.stderr}" - ) + if result.returncode != 0: + # conductor-oss standalone serves secrets from the server process + # env — the store is read-only there, so the write-dependent + # lifecycle steps cannot run (a server-flavor capability, not an + # SDK regression; mirrors the Java/C# ports' assumption-skip). + if "read-only" in result.stderr.lower(): + pytest.skip( + "server secret store is read-only (env-backed) — " + "skipping write-dependent step" + ) + raise AssertionError(f"credentials set {name} failed: {result.stderr}") def delete(self, name: str) -> None: result = self._run("credentials", "delete", name) - # Ignore "not found" errors during cleanup - if result.returncode != 0 and "not found" not in result.stderr.lower(): + # Ignore "not found" and "read-only" errors during cleanup — best-effort + stderr = result.stderr.lower() + if result.returncode != 0 and "not found" not in stderr and "read-only" not in stderr: raise AssertionError( f"credentials delete {name} failed: {result.stderr}" ) @@ -162,9 +171,10 @@ def get_task_by_name(execution_id: str, task_ref_prefix: str) -> list: # reason unrelated to the SDK. Probe the running server once and skip those tests # when unsupported. # -# TODO: once agentspan cuts a release that implements runtimeMetadata, bump -# AGENTSPAN_VERSION in .github/workflows/agent-e2e.yml — this guard then lets the -# credential tests run automatically (no test change needed). +# conductor-oss implements this from 3.32.0-rc.8 onward (see +# .github/workflows/agent-e2e.yml's CONDUCTOR_SERVER_VERSION) — this guard lets the +# credential tests run automatically against any server that has it, no test +# change needed. _RUNTIME_METADATA_SUPPORT = None @@ -208,6 +218,6 @@ def requires_runtime_metadata(): pytest.skip( "server does not persist/deliver TaskDef.runtimeMetadata " "(conductor-oss PR #1255) — worker credential injection requires it. " - "TODO: bump AGENTSPAN_VERSION in .github/workflows/agent-e2e.yml once a " - "release ships runtimeMetadata support." + "Needs conductor-oss >= 3.32.0-rc.8 (see CONDUCTOR_SERVER_VERSION in " + ".github/workflows/agent-e2e.yml)." ) diff --git a/e2e/test_suite26_worker_credentials.py b/e2e/test_suite26_worker_credentials.py index c10272fa..d842abcf 100644 --- a/e2e/test_suite26_worker_credentials.py +++ b/e2e/test_suite26_worker_credentials.py @@ -89,7 +89,17 @@ def _put_secret(name: str, value: str) -> None: headers={"Content-Type": "text/plain"}, timeout=10, ) - r.raise_for_status() + if not r.ok: + # conductor-oss standalone serves secrets from the server process + # env — the store is read-only there, so the write-dependent + # lifecycle steps cannot run (a server-flavor capability, not an + # SDK regression; mirrors the Java/C# ports' assumption-skip). + if "read-only" in r.text.lower(): + pytest.skip( + "server secret store is read-only (env-backed) — " + "skipping write-dependent step" + ) + r.raise_for_status() def _delete_secret(name: str) -> None: diff --git a/e2e/test_suite5_http_tools.py b/e2e/test_suite5_http_tools.py index a98e99a1..ab11fde7 100644 --- a/e2e/test_suite5_http_tools.py +++ b/e2e/test_suite5_http_tools.py @@ -457,11 +457,12 @@ def test_http_lifecycle(self, runtime, cli_credentials, model): ["mcp-testkit", "--help"], capture_output=True, text=True, - timeout=5, + timeout=15, ) - except FileNotFoundError: + except (FileNotFoundError, subprocess.TimeoutExpired): pytest.skip( - "mcp-testkit not installed — required for Suite 5 HTTP tools test" + "mcp-testkit not installed or unresponsive — required for " + "Suite 5 HTTP tools test" ) server_proc = None