diff --git a/.github/actions/official-sdk/action.yml b/.github/actions/official-sdk/action.yml new file mode 100644 index 000000000..772351542 --- /dev/null +++ b/.github/actions/official-sdk/action.yml @@ -0,0 +1,17 @@ +name: Pinned official client +description: Install the official Python SDK that contracts/agents-api/upstream.json pins. +runs: + using: composite + steps: + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + - name: Install pinned official client + shell: bash + run: | + python - <<'PY' + import json, subprocess, sys + pin = json.load(open('contracts/agents-api/upstream.json')) + subprocess.run([sys.executable, '-m', 'pip', 'install', '-r', 'services/core/tests/requirements.txt', + 'openai @ git+https://github.com/openai/openai-python@' + pin['commit']], check=True) + PY diff --git a/.github/workflows/api-acceptance.yml b/.github/workflows/api-acceptance.yml index 667066a52..9510546b2 100644 --- a/.github/workflows/api-acceptance.yml +++ b/.github/workflows/api-acceptance.yml @@ -45,17 +45,7 @@ jobs: OAC_DEV_CORE_BUILD_DIR="$RUNNER_TEMP/oac-core-build" make build-core "$RUNNER_TEMP/oac-core-build/oac-core-device" --help "$RUNNER_TEMP/oac-core-build/oac-core-environment-key" --help - - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - name: Install pinned official client - run: | - python - <<'PY' - import json, subprocess, sys - pin = json.load(open('contracts/agents-api/upstream.json')) - subprocess.run([sys.executable, '-m', 'pip', 'install', '-r', 'services/core/tests/requirements.txt', - 'openai @ git+https://github.com/openai/openai-python@' + pin['commit']], check=True) - PY + - uses: ./.github/actions/official-sdk - name: Verify official-client HTTP compatibility env: OAC_TEST_DATABASE_URL: postgres://oac:oac_test_only@127.0.0.1:${{ job.services.postgres.ports['5432'] }}/oac_ci_tests?sslmode=disable @@ -64,7 +54,6 @@ jobs: run: | python services/core/tests/official_schema_test.py python services/core/tests/official_client.py - go test ./services/core/tests/integration -run '^(TestFunctionStateOfficialClientReadsAndLiveEvents|TestSavedReferenceRetryOfficialClient|TestAgentUpdateOfficialClient|TestAgentDeletionOfficialClient|TestAutomationsOfficialClient|TestSessionAgentFilterOfficialClient|TestSessionDeletionOfficialClient|TestEnvironmentInitialFailureOfficialClient|TestSelfHostedInitialCreationOfficialClient|TestSelfHostedCancellationOfficialClient|TestStandaloneEnvironmentOfficialClientPostgres)$' -count=1 - uses: ./.github/actions/e2b-provider if: inputs.container - name: Verify the distribution's Core image diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9ee59a3ab..3e975dac2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -138,8 +138,12 @@ jobs: - name: Build standalone Core and test services and clients if: matrix.part == 'core' run: make build-core check-core-packages + - uses: ./.github/actions/official-sdk + if: matrix.part == 'integration' - name: Test Core integration shard ${{ matrix.shard }} if: matrix.part == 'integration' + env: + OAC_TEST_OFFICIAL_SDK_PYTHON: python run: make check-core-integration OAC_CORE_INTEGRATION_SHARD=${{ matrix.shard }} distribution: diff --git a/docs/maintainers.md b/docs/maintainers.md index cfae5a5f3..c608ec7ba 100644 --- a/docs/maintainers.md +++ b/docs/maintainers.md @@ -181,7 +181,7 @@ The planner compares the PR event's tested merge commit with its verified first | `hygiene` | Repository links, bundled documentation integrity, and CI planner/gate tests; runs for every change | | `distribution` | Harness catalog and installer schema, install/apply/recovery/cleanup tests, Compose parsing and initialization fixtures, release/download and bundle contracts, Go console tests and build; needs Docker Compose for template parsing, no pnpm install or browser | | `compose` | Real startup from empty volumes using images built from the checkout, sign-in and API access, file upload and node installer download, then URL reconfiguration and container recreation with preserved credentials and data; needs Docker, Go and network access, no model credentials | -| `backend` | Parallel parts, each with a dedicated PostgreSQL guard: `runtime` (sqlc freshness, Runtime/shared Go tests, Linux microsandbox helper, daemon build), `core` (standalone Core build, Core service and client tests) and three `integration` shards of the serial Core integration test package | +| `backend` | Parallel parts, each with a dedicated PostgreSQL guard: `runtime` (sqlc freshness, Runtime/shared Go tests, Linux microsandbox helper, daemon build), `core` (standalone Core build, Core service and client tests) and three `integration` shards of the serial Core integration test package, run with the pinned official client | | `harness` | Claude Code adapter tests and packaging, MiniMax companion scripts | | `example` | Optional application typecheck, tests, build and isolated browser acceptance | | `web` | TypeScript, Web/client tests and Web build | diff --git a/docs/zh/maintainers.md b/docs/zh/maintainers.md index c3b994c47..4cc3c3158 100644 --- a/docs/zh/maintainers.md +++ b/docs/zh/maintainers.md @@ -1,7 +1,7 @@ --- title: "构建并发布 OpenAgentCore" source: docs/maintainers.md -source_hash: af77ec1c4a27d4742497068cee88195f07e9fb38e48bed89b9120e21e1a726b4 +source_hash: f398cbc404cb012621eb6483715015114175d671c2dfbfd03b2ad52c773bd4d2 --- 本指南面向负责构建和发布 OpenAgentCore 的维护者。要安装 Core 和 Web,请按 [安装 Core 和 Web](getting-started/install.md) 操作。安装程序代码遵循的规则见 [部署](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/README.md) 和 [节点安装程序](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/node/README.md);必需检查见 [CONTRIBUTING](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/CONTRIBUTING.md#required-checks)。 @@ -183,7 +183,7 @@ gh workflow run core-release --repo MiniMax-AI/OpenAgentCore --ref main \ | `hygiene` | 仓库链接、随包文档完整性以及 CI 计划器/门禁测试;每次变更都会运行 | | `distribution` | Harness 目录和安装程序模式、安装/应用/恢复/清理测试、Compose 解析和初始化固定数据、发布/下载和捆绑包契约、Go 控制台测试与构建;模板解析需要 Docker Compose,不需要 pnpm install 或浏览器 | | `compose` | 使用从当前检出构建的镜像从空数据卷实际启动、登录和 API 访问、上传文件和下载节点安装程序,然后在保留凭据和数据的同时重新配置 URL 并重新创建容器;需要 Docker、Go 和网络访问,不需要模型凭据 | -| `backend` | 并行部分,每部分都有专用 PostgreSQL 保护检查:`runtime`(sqlc 新鲜度、Runtime/共享 Go 测试、Linux microsandbox 辅助程序、守护进程构建)、`core`(独立 Core 构建、Core 服务和客户端测试),以及串行 Core 集成测试包的三个 `integration` 分片 | +| `backend` | 并行部分,每部分都有专用 PostgreSQL 保护检查:`runtime`(sqlc 新鲜度、Runtime/共享 Go 测试、Linux microsandbox 辅助程序、守护进程构建)、`core`(独立 Core 构建、Core 服务和客户端测试),以及串行 Core 集成测试包的三个 `integration` 分片(使用固定版本的官方客户端运行) | | `harness` | Claude Code adapter 测试和打包、MiniMax 配套脚本 | | `example` | 可选的应用程序类型检查、测试、构建和隔离的浏览器验收 | | `web` | TypeScript、Web/客户端测试和 Web 构建 | diff --git a/scripts/ci_plan.py b/scripts/ci_plan.py index dcf1ea340..308df0c6b 100644 --- a/scripts/ci_plan.py +++ b/scripts/ci_plan.py @@ -23,6 +23,7 @@ ".github/actions/node/action.yml": (*NODE_JOBS, "lint"), ".github/actions/mcode-companion/action.yml": ("native", "lint"), ".github/actions/e2b-provider/action.yml": ("api", "lint"), + ".github/actions/official-sdk/action.yml": ("backend", "api", "lint"), ".github/workflows/cache-warm.yml": ("lint",), "scripts/ci_plan.py": JOBS, "scripts/ci_plan_test.py": ("hygiene",), diff --git a/services/core/tests/integration/template_null_selection_public_test.go b/services/core/tests/integration/template_null_selection_public_test.go index 38712502e..4209b942b 100644 --- a/services/core/tests/integration/template_null_selection_public_test.go +++ b/services/core/tests/integration/template_null_selection_public_test.go @@ -80,7 +80,7 @@ func TestTemplateNullSelectionOfficialClientPostgres(t *testing.T) { } } }) - if len(receipt.Sessions) != 9 || len(receipt.Expected) != 9 || len(receipt.RejectedKeys) != 10 { + if len(receipt.Sessions) != 10 || len(receipt.Expected) != 10 || len(receipt.RejectedKeys) != 7 { t.Fatalf("incomplete acceptance receipt: sessions=%d expectations=%d rejections=%d", len(receipt.Sessions), len(receipt.Expected), len(receipt.RejectedKeys)) } for label, id := range receipt.Sessions { @@ -136,10 +136,10 @@ func TestTemplateNullSelectionOfficialClientPostgres(t *testing.T) { query string want int }{ - {"SELECT count(*) FROM sessions WHERE tenant_id=$1", 9}, - {"SELECT count(*) FROM environments e JOIN sessions s ON s.environment_id=e.id WHERE s.tenant_id=$1", 9}, - {"SELECT count(*) FROM environment_setups e JOIN sessions s ON s.environment_id=e.environment_id WHERE s.tenant_id=$1", 9}, - {"SELECT count(*) FROM initial_environment_files f JOIN sessions s ON s.environment_id=f.environment_id WHERE s.tenant_id=$1", 9}, + {"SELECT count(*) FROM sessions WHERE tenant_id=$1", 10}, + {"SELECT count(*) FROM environments e JOIN sessions s ON s.environment_id=e.id WHERE s.tenant_id=$1", 10}, + {"SELECT count(*) FROM environment_setups e JOIN sessions s ON s.environment_id=e.environment_id WHERE s.tenant_id=$1", 10}, + {"SELECT count(*) FROM initial_environment_files f JOIN sessions s ON s.environment_id=f.environment_id WHERE s.tenant_id=$1", 10}, {"SELECT count(*) FROM turns t JOIN sessions s ON s.id=t.session_id WHERE s.tenant_id=$1", 0}, {"SELECT count(*) FROM environment_input_reservations e JOIN sessions s ON s.id=e.session_id WHERE s.tenant_id=$1", 0}, } { @@ -154,5 +154,5 @@ func TestTemplateNullSelectionOfficialClientPostgres(t *testing.T) { t.Fatalf("rejected creation persisted: %d %v", count, err) } } - t.Log("nine supported selection cases and rejected network restrictions passed SDK/raw HTTP, frozen encrypted bytes, tenant isolation, atomic rejection and reopened retries; no Runtime or model execution") + t.Log("ten supported selection cases and rejected widenings passed SDK/raw HTTP, frozen encrypted bytes, tenant isolation, atomic rejection and reopened retries; no Runtime or model execution") } diff --git a/services/core/tests/official_template_null_selection.py b/services/core/tests/official_template_null_selection.py index 2eea24093..9bcb3d7c7 100644 --- a/services/core/tests/official_template_null_selection.py +++ b/services/core/tests/official_template_null_selection.py @@ -185,7 +185,6 @@ def create(label, body, selection, sdk_create=False): missing = copy.deepcopy(base) missing["environment"]["environment_template_id"] = "00000000-0000-0000-0000-000000000001" assert reject(missing, 404, settings["foreign"]) == denied - reject({**base, "environment": {**base["environment"], "network": {"access": "restricted", "allowed_domains": ["example.com"]}}}, 400) reject({**base, "environment": {**base["environment"], "skills": [{"type": "skill_reference", "skill_id": foreign_skill.id}]}}, 404) reject({**base, "environment": {**base["environment"], "capability_directories": ["/outside"]}}, 400) malformed = copy.deepcopy(plugin) @@ -194,8 +193,7 @@ def create(label, body, selection, sdk_create=False): disabled = {"access": "disabled", "allowed_domains": []} api.update(template_id, network={"access": "disabled"}) assert api.update(template_id, name="network omission preserves").network.to_dict() == disabled - reject(base, 400) - reject({**base, "environment": {**base["environment"], "network": None}}, 400) + create("inherit-disabled", {**base, "environment": {**base["environment"], "network": None}}, {**default, "network": disabled}) reject({**base, "environment": {**base["environment"], "network": {"access": "enabled"}}}, 400) enabled = {"access": "enabled", "allowed_domains": []} assert api.update(template_id, network=None).network.to_dict() == enabled