diff --git a/.distignore b/.distignore index 2fc4603..7552e37 100644 --- a/.distignore +++ b/.distignore @@ -1,5 +1,4 @@ .claude -.datamachine .github .git .gitignore diff --git a/.github/workflows/docs-agent.yml b/.github/workflows/docs-agent.yml deleted file mode 100644 index 2f7e100..0000000 --- a/.github/workflows/docs-agent.yml +++ /dev/null @@ -1,194 +0,0 @@ -name: Docs Agent - -on: - workflow_dispatch: - inputs: - prompt: - description: Instruction for Docs Agent. - required: true - default: Generate or update technical developer documentation for Agents API from source code. Open a documentation PR only if needed. - openai_model: - description: OpenAI model to use for the imported agent flow. - required: true - default: gpt-5.5 - docs_agent_flow: - description: Docs Agent flow slug to run. - type: choice - required: true - options: - - technical-docs-bootstrap-flow - - technical-docs-maintenance-flow - - technical-docs-flow - default: technical-docs-maintenance-flow - docs_agent_ref: - description: Docs Agent ref. - required: true - default: main - data_machine_ref: - description: Data Machine ref. - required: true - default: main - data_machine_code_ref: - description: Data Machine Code ref. - required: true - default: main - -permissions: - contents: write - pull-requests: write - issues: write - -jobs: - prepare: - name: Prepare Docs Agent config - runs-on: ubuntu-latest - outputs: - docs_workflow: ${{ steps.config.outputs.docs_workflow }} - pipeline_slug: ${{ steps.config.outputs.pipeline_slug }} - docs_branch: ${{ steps.config.outputs.docs_branch }} - audience: ${{ steps.config.outputs.audience }} - success_requires_pr: ${{ steps.config.outputs.success_requires_pr }} - completion_instruction: ${{ steps.config.outputs.completion_instruction }} - fallback_pr_title: ${{ steps.config.outputs.fallback_pr_title }} - steps: - - name: Resolve flow settings - id: config - env: - DOCS_AGENT_FLOW: ${{ inputs.docs_agent_flow }} - run: | - set -euo pipefail - case "$DOCS_AGENT_FLOW" in - technical-docs-bootstrap-flow|technical-docs-maintenance-flow|technical-docs-flow) - docs_workflow="developer" - pipeline_slug="technical-docs-pipeline" - audience="developer-facing API and substrate documentation" - ;; - *) - echo "Unsupported docs_agent_flow: $DOCS_AGENT_FLOW" >&2 - exit 1 - ;; - esac - - success_requires_pr="false" - docs_branch="docs-agent/developer-docs" - fallback_pr_title="Update Agents API developer documentation" - completion_instruction="Agents API has one documentation audience: developer-facing API and substrate documentation. Preserve the existing docs/ topic structure, maintain docs/README.md as the navigation index, and place focused topic pages directly in the existing docs/ tree. If no documentation update is needed, finish cleanly without opening a pull request. If an update is needed, write only allowed documentation paths and open one pull request." - if [[ "$DOCS_AGENT_FLOW" == *-bootstrap-flow ]]; then - success_requires_pr="true" - docs_branch="docs-agent/developer-bootstrap-docs" - fallback_pr_title="Bootstrap Agents API developer documentation" - completion_instruction="This is a bootstrap run. Scaffold a complete, clean, hierarchical developer documentation surface under docs/. Cover the repository source as the source of truth, organize pages by architecture and logical module boundaries, keep docs/README.md as the navigation index, and place focused topic pages directly in the existing docs/ tree. Write every needed digestible topic page before opening one pull request. A no_changes outcome is invalid for this consumer bootstrap proof." - fi - - { - printf 'docs_workflow=%s\n' "$docs_workflow" - printf 'pipeline_slug=%s\n' "$pipeline_slug" - printf 'docs_branch=%s\n' "$docs_branch" - printf 'audience=%s\n' "$audience" - printf 'success_requires_pr=%s\n' "$success_requires_pr" - printf 'fallback_pr_title=%s\n' "$fallback_pr_title" - printf 'completion_instruction<> "$GITHUB_OUTPUT" - - docs-agent: - name: Run Docs Agent - needs: prepare - uses: Extra-Chill/homeboy-extensions/.github/workflows/datamachine-agent-ci.yml@179eedbeb1da7a0b7dfacd10a6010019fd62a68b # main - with: - bundle_path: bundles/docs-agent - bundle_repo: https://github.com/Automattic/docs-agent.git - bundle_ref: ${{ inputs.docs_agent_ref }} - bundle_path_in_repo: bundles/docs-agent - agent_slug: docs-agent - pipeline_slug: ${{ needs.prepare.outputs.pipeline_slug }} - flow_slug: ${{ inputs.docs_agent_flow }} - target_repo: Automattic/agents-api - model: ${{ inputs.openai_model }} - validation_dependencies: Extra-Chill/data-machine@${{ inputs.data_machine_ref }},Extra-Chill/data-machine-code@${{ inputs.data_machine_code_ref }},WordPress/ai-provider-for-openai@trunk - success_requires_pr: ${{ needs.prepare.outputs.success_requires_pr == 'true' }} - max_turns: 20 - step_budget: 24 - time_budget_ms: 600000 - engine_key: docs_agent - tool_results_key: github_tool_results - extra_required_abilities: '["datamachine/create-or-update-github-file"]' - allowed_repos: '["Automattic/agents-api"]' - tool_recorders: | - [ - { - "tool": "create_or_update_github_file", - "forced_parameters": { - "allowed_file_paths": ["README.md", "docs/**"], - "branch": "${{ needs.prepare.outputs.docs_branch }}" - }, - "record": { - "engine_key": "docs_agent", - "tool_results_key": "github_tool_results" - } - }, - { - "tool": "create_github_pull_request", - "record": { - "engine_key": "docs_agent", - "tool_results_key": "github_tool_results", - "event": { - "key": "pr", - "type": "pull_request", - "only_if_success": true - } - } - } - ] - fallback_pull_request: | - { - "repo": "Automattic/agents-api", - "title": "${{ needs.prepare.outputs.fallback_pr_title }}", - "head": "${{ needs.prepare.outputs.docs_branch }}", - "base": "main", - "body": "## Summary\n- Generate or update Agents API documentation from repository source.\n- Keep documentation under README.md and docs/**.\n- Generated by the Docs Agent workflow.\n\n## AI assistance\n- **AI assistance:** Yes\n- **Tool(s):** OpenCode (GPT-5.5) via Docs Agent\n- **Used for:** Generated documentation draft from repository source for human review." - } - engine_data_outputs: '{"docs_agent_pr_url":"metadata.engine_data.docs_agent.pr.url","success_status":"metadata.success_status"}' - transcript_artifact_name: docs-agent-transcript-${{ github.run_id }} - prompt: | - ${{ inputs.prompt }} - - Target repository: Automattic/agents-api - Event name: ${{ github.event_name }} - Target ref: ${{ github.ref_name }} - Head SHA: ${{ github.sha }} - Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - Writable documentation paths: README.md, docs/** - Selected docs workflow: ${{ needs.prepare.outputs.docs_workflow }} (${{ needs.prepare.outputs.audience }}) - Docs structure: maintain one developer documentation surface under docs/. Use docs/README.md as the navigation index. Keep focused topic pages at stable docs/ paths such as docs/architecture.md, docs/runtime-and-tools.md, docs/auth-consent-context-memory.md, docs/channels-workflows-operations.md, and specialized protocol/bridge pages. - - Generate documentation from source code for the selected audience. ${{ needs.prepare.outputs.completion_instruction }} - secrets: inherit - - validate-bootstrap-docs: - name: Validate bootstrap docs links - needs: - - prepare - - docs-agent - if: ${{ needs.prepare.outputs.success_requires_pr == 'true' && fromJSON(needs.docs-agent.outputs.engine_data_json).success_status == 'pr_opened' }} - runs-on: ubuntu-latest - steps: - - name: Checkout Agents API - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Checkout Docs Agent - uses: actions/checkout@v6 - with: - repository: Automattic/docs-agent - ref: ${{ inputs.docs_agent_ref }} - path: .ci/docs-agent - - - name: Repair and validate generated Markdown links - env: - DOCS_AGENT_BRANCH: ${{ needs.prepare.outputs.docs_branch }} - run: | - set -euo pipefail - php .ci/docs-agent/scripts/repair-docs-links.php "$GITHUB_WORKSPACE" "$DOCS_AGENT_BRANCH" --commit - git fetch origin "$DOCS_AGENT_BRANCH" - php tests/playground-ci/scripts/validate-docs-links.php "$GITHUB_WORKSPACE" FETCH_HEAD diff --git a/docs/channels-workflows-operations.md b/docs/channels-workflows-operations.md index ce9df19..5a7e882 100644 --- a/docs/channels-workflows-operations.md +++ b/docs/channels-workflows-operations.md @@ -223,7 +223,7 @@ Canonical session rows should expose stable generic fields when available: `sess Principal-aware stores should implement `WP_Agent_Principal_Conversation_Store` for non-user owners such as browser sessions, external channels, tokens, or system principals. Stores that hash or hide owner keys should also implement `WP_Agent_Principal_Conversation_Session_Reader` so `get`, `update-title`, and `delete` can verify ownership without exposing raw owner keys in generic rows. -Product-specific state belongs in `metadata` under namespaced keys. For example, Data Machine can keep read/progress/reporting fields under `metadata['data_machine']` while exposing the canonical Agents API fields to channel clients. Data Machine `datamachine/*chat-session*` abilities, REST routes, and CLI commands can then become compatibility/product aliases over its adapter instead of a parallel generic contract. +Product-specific state belongs in `metadata` under namespaced keys. For example, a consumer plugin can keep read/progress/reporting fields under `metadata['example_vendor']` while exposing the canonical Agents API fields to channel clients. Consumer-specific abilities, REST routes, and CLI commands can then become compatibility/product aliases over its adapter instead of a parallel generic contract. Approval primitives live in `src/Approvals/`: diff --git a/docs/default-stores-companion.md b/docs/default-stores-companion.md index c717a70..5f81a13 100644 --- a/docs/default-stores-companion.md +++ b/docs/default-stores-companion.md @@ -24,7 +24,7 @@ Rationale: core-review concern than a mandatory session post type. - **Fallback only.** When enabled, the store registers on `wp_agent_conversation_store` at low priority (5), so any host store registered at the default priority wins. - Data Machine and WordPress.com keep their own custom backends and never enable this. + Products with custom backends can keep their own stores and never enable this. Implementation: `WP_Agent_Cpt_Conversation_Store` (`src/Transcripts/class-wp-agent-cpt-conversation-store.php`), wired by diff --git a/docs/registry-and-packages.md b/docs/registry-and-packages.md index 3381eae..cfdd6dc 100644 --- a/docs/registry-and-packages.md +++ b/docs/registry-and-packages.md @@ -115,9 +115,9 @@ Adapter identity and duplicate rules: - Adapter implementations should key durable/runtime state by the normalized `(agent_slug, owner_user_id, instance_key)` tuple. Repeating materialization for the same tuple is idempotent and should return `updated`, `skipped`, `projected`, or another accurate `WP_Agent_Materialization_Result` status instead of creating a second logical agent. - Registered definition updates for an existing slug reconcile the existing durable/runtime identity. The adapter decides which fields are mutable, how package provenance is compared, and whether user-modified runtime artifacts require approval. - Removed definitions are represented by absence from the registered snapshot. Adapters that keep prior installed state may mark missing identities as `removed`, `disabled`, or `skipped` according to host policy; Agents API does not delete host state automatically. -- Source/provenance metadata remains generic. Producers should place JSON-friendly provenance in `WP_Agent::get_meta()` using keys such as `source_plugin`, `source_type`, `source_package`, and `source_version`; adapters may persist or report those values without interpreting them as a Data Machine concept. +- Source/provenance metadata remains generic. Producers should place JSON-friendly provenance in `WP_Agent::get_meta()` using keys such as `source_plugin`, `source_type`, `source_package`, and `source_version`; adapters may persist or report those values without interpreting them as a product-specific concept. -The installed-agent contract composes with package adoption but does not replace it. `WP_Agent_Package_Adoption_Orchestrator` handles artifact planning/application through artifact callbacks; the installed-agent state store handles the durable agent instance row or equivalent host record. Products such as Data Machine can implement both surfaces against their own storage and then project persisted agents into the request-local registry. +The installed-agent contract composes with package adoption but does not replace it. `WP_Agent_Package_Adoption_Orchestrator` handles artifact planning/application through artifact callbacks; the installed-agent state store handles the durable agent instance row or equivalent host record. Consumer products can implement both surfaces against their own storage and then project persisted agents into the request-local registry. This is the durable boundary decision for installed agents: Agents API owns neutral shapes and projection; products own persistence, feature flags, materializers, memory scaffolding, access grants, tokens, logs, and user-facing approval UX. diff --git a/docs/runtime-and-tools.md b/docs/runtime-and-tools.md index cc247bd..27e2712 100644 --- a/docs/runtime-and-tools.md +++ b/docs/runtime-and-tools.md @@ -317,7 +317,7 @@ $submission = AgentsAPI\AI\WP_Agent_Runtime_Tool_Lifecycle::submit_result( ); ``` -Generic lifecycle events fire as WordPress actions: `agents_api_runtime_tool_request_created`, `agents_api_runtime_tool_result_submitted`, `agents_api_runtime_tool_request_timed_out`, and `agents_api_runtime_tool_request_resumed`. The event payloads contain normalized request/result envelopes plus caller-owned context. Agents API does not define Data Machine jobs, chat-session metadata, browser storage, or any other product-specific persistence shape. +Generic lifecycle events fire as WordPress actions: `agents_api_runtime_tool_request_created`, `agents_api_runtime_tool_result_submitted`, `agents_api_runtime_tool_request_timed_out`, and `agents_api_runtime_tool_request_resumed`. The event payloads contain normalized request/result envelopes plus caller-owned context. Agents API does not define product jobs, chat-session metadata, browser storage, or any other product-specific persistence shape. ## Runtime tool declarations diff --git a/src/Registry/register-agent-runtime-bundle-importer.php b/src/Registry/register-agent-runtime-bundle-importer.php index ed29608..da05365 100644 --- a/src/Registry/register-agent-runtime-bundle-importer.php +++ b/src/Registry/register-agent-runtime-bundle-importer.php @@ -180,7 +180,7 @@ static function ( $result, array $spec, array $input = array(), int $index = 0 ) return null; } - $slug = sanitize_title( $string_value( $input['slug'] ?? null, $spec['slug'] ?? null, $agent['agent_slug'] ?? null, $bundle['bundle_slug'] ?? null ) ); + $slug = sanitize_title( $string_value( $input['slug'] ?? null, $spec['slug'] ?? null, $agent['agent_slug'] ?? null, $bundle['package_slug'] ?? null, $bundle['bundle_slug'] ?? null ) ); if ( '' === $slug ) { return new WP_Error( 'wp_agent_runtime_bundle_missing_agent_slug', @@ -228,14 +228,15 @@ static function ( $result, array $spec, array $input = array(), int $index = 0 ) $config = is_array( $agent['agent_config'] ?? null ) ? $agent['agent_config'] : array(); $meta = is_array( $agent['meta'] ?? null ) ? $agent['meta'] : array(); - $bundle_slug = $string_value( $bundle['bundle_slug'] ?? null ); - $bundle_version = $string_value( $bundle['bundle_version'] ?? null ); - if ( '' !== $bundle_slug || '' !== $bundle_version ) { + $source_type = $string_value( $bundle['source_type'] ?? null, $spec['source_type'] ?? null, 'runtime-agent-package' ); + $source_package = $string_value( $bundle['source_package'] ?? null, $spec['source_package'] ?? null, $bundle['package_slug'] ?? null, $spec['package_slug'] ?? null, $bundle['bundle_slug'] ?? null ); + $source_version = $string_value( $bundle['source_version'] ?? null, $spec['source_version'] ?? null, $bundle['package_version'] ?? null, $spec['package_version'] ?? null, $bundle['bundle_version'] ?? null ); + if ( '' !== $source_package || '' !== $source_version ) { $meta = array_merge( array( - 'source_type' => 'runtime-agent-bundle', - 'source_package' => $bundle_slug, - 'source_version' => $bundle_version, + 'source_type' => $source_type, + 'source_package' => $source_package, + 'source_version' => $source_version, ), $meta ); diff --git a/tests/agents-chat-ability-smoke.php b/tests/agents-chat-ability-smoke.php index 1b17da3..f7fbaf0 100644 --- a/tests/agents-chat-ability-smoke.php +++ b/tests/agents-chat-ability-smoke.php @@ -189,9 +189,9 @@ function smoke_assert( $expected, $actual, string $name, array &$failures, int & $runtime_principal = AgentsAPI\AI\WP_Agent_Execution_Principal::runtime( 'runtime-session-1', 'sandbox-agent', - array( 'source' => 'wp-codebox' ), + array( 'source' => 'example-runtime' ), 'workspace:demo', - 'wp-codebox-cli', + 'example-runtime-cli', array( AgentsAPI\AI\WP_Agent_Execution_Principal::AUDIENCE_CLAIM_RUNTIME_TYPE => 'wordpress-playground' ) )->to_array(); $captured_principal = array(); diff --git a/tests/bootstrap-smoke.php b/tests/bootstrap-smoke.php index 7292624..f853812 100644 --- a/tests/bootstrap-smoke.php +++ b/tests/bootstrap-smoke.php @@ -20,34 +20,34 @@ agents_api_smoke_require_module(); $namespace_map = array( - 'DataMachine\\Engine\\AI\\WP_Agent_Message' => 'AgentsAPI\\AI\\WP_Agent_Message', - 'DataMachine\\Engine\\AI\\WP_Agent_Execution_Principal' => 'AgentsAPI\\AI\\WP_Agent_Execution_Principal', - 'DataMachine\\Engine\\AI\\WP_Agent_Conversation_Request' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Request', - 'DataMachine\\Engine\\AI\\WP_Agent_Conversation_Runner' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Runner', - 'DataMachine\\Engine\\AI\\WP_Agent_Conversation_Completion_Decision' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Completion_Decision', - 'DataMachine\\Engine\\AI\\WP_Agent_Conversation_Completion_Policy' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Completion_Policy', - 'DataMachine\\Engine\\AI\\WP_Agent_Transcript_Persister' => 'AgentsAPI\\AI\\WP_Agent_Transcript_Persister', - 'DataMachine\\Engine\\AI\\WP_Agent_Null_Transcript_Persister' => 'AgentsAPI\\AI\\WP_Agent_Null_Transcript_Persister', - 'DataMachine\\Engine\\AI\\WP_Agent_Conversation_Compaction' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Compaction', - 'DataMachine\\Engine\\AI\\WP_Agent_Conversation_Result' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Result', - 'DataMachine\\Core\\Database\\Chat\\WP_Agent_Conversation_Lock' => 'AgentsAPI\\Core\\Database\\Chat\\WP_Agent_Conversation_Lock', - 'DataMachine\\Engine\\AI\\Tools\\WP_Agent_Tool_Declaration' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Declaration', - 'DataMachine\\Engine\\AI\\Tools\\WP_Agent_Tool_Call' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Call', - 'DataMachine\\Engine\\AI\\Tools\\WP_Agent_Tool_Parameters' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Parameters', - 'DataMachine\\Engine\\AI\\Tools\\Execution\\WP_Agent_Tool_Execution_Core' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Execution_Core', - 'DataMachine\\Engine\\AI\\Tools\\Execution\\WP_Agent_Tool_Executor' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Executor', - 'DataMachine\\Engine\\AI\\Tools\\WP_Agent_Tool_Source_Registry' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Source_Registry', - 'DataMachine\\Core\\Database\\Chat\\WP_Agent_Conversation_Store' => 'AgentsAPI\\Core\\Database\\Chat\\WP_Agent_Conversation_Store', - 'DataMachine\\Core\\Identity\\WP_Agent_Identity_Scope' => 'AgentsAPI\\Core\\Identity\\WP_Agent_Identity_Scope', - 'DataMachine\\Core\\Identity\\WP_Agent_Materialized_Identity' => 'AgentsAPI\\Core\\Identity\\WP_Agent_Materialized_Identity', - 'DataMachine\\Core\\Identity\\WP_Agent_Identity_Store' => 'AgentsAPI\\Core\\Identity\\WP_Agent_Identity_Store', - 'DataMachine\\Core\\Workspace\\WP_Agent_Workspace_Scope' => 'AgentsAPI\\Core\\Workspace\\WP_Agent_Workspace_Scope', - 'DataMachine\\Core\\FilesRepository\\WP_Agent_Memory_Store' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Store', - 'DataMachine\\Core\\FilesRepository\\WP_Agent_Memory_Scope' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Scope', - 'DataMachine\\Core\\FilesRepository\\WP_Agent_Memory_Metadata' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Metadata', - 'DataMachine\\Core\\FilesRepository\\WP_Agent_Memory_Query' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Query', - 'DataMachine\\Core\\FilesRepository\\WP_Agent_Memory_Store_Capabilities' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Store_Capabilities', - 'DataMachine\\Core\\FilesRepository\\WP_Agent_Memory_Validator' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Validator', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Message' => 'AgentsAPI\\AI\\WP_Agent_Message', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Execution_Principal' => 'AgentsAPI\\AI\\WP_Agent_Execution_Principal', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Conversation_Request' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Request', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Conversation_Runner' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Runner', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Conversation_Completion_Decision' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Completion_Decision', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Conversation_Completion_Policy' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Completion_Policy', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Transcript_Persister' => 'AgentsAPI\\AI\\WP_Agent_Transcript_Persister', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Null_Transcript_Persister' => 'AgentsAPI\\AI\\WP_Agent_Null_Transcript_Persister', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Conversation_Compaction' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Compaction', + 'LegacyProduct\\Engine\\AI\\WP_Agent_Conversation_Result' => 'AgentsAPI\\AI\\WP_Agent_Conversation_Result', + 'LegacyProduct\\Core\\Database\\Chat\\WP_Agent_Conversation_Lock' => 'AgentsAPI\\Core\\Database\\Chat\\WP_Agent_Conversation_Lock', + 'LegacyProduct\\Engine\\AI\\Tools\\WP_Agent_Tool_Declaration' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Declaration', + 'LegacyProduct\\Engine\\AI\\Tools\\WP_Agent_Tool_Call' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Call', + 'LegacyProduct\\Engine\\AI\\Tools\\WP_Agent_Tool_Parameters' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Parameters', + 'LegacyProduct\\Engine\\AI\\Tools\\Execution\\WP_Agent_Tool_Execution_Core' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Execution_Core', + 'LegacyProduct\\Engine\\AI\\Tools\\Execution\\WP_Agent_Tool_Executor' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Executor', + 'LegacyProduct\\Engine\\AI\\Tools\\WP_Agent_Tool_Source_Registry' => 'AgentsAPI\\AI\\Tools\\WP_Agent_Tool_Source_Registry', + 'LegacyProduct\\Core\\Database\\Chat\\WP_Agent_Conversation_Store' => 'AgentsAPI\\Core\\Database\\Chat\\WP_Agent_Conversation_Store', + 'LegacyProduct\\Core\\Identity\\WP_Agent_Identity_Scope' => 'AgentsAPI\\Core\\Identity\\WP_Agent_Identity_Scope', + 'LegacyProduct\\Core\\Identity\\WP_Agent_Materialized_Identity' => 'AgentsAPI\\Core\\Identity\\WP_Agent_Materialized_Identity', + 'LegacyProduct\\Core\\Identity\\WP_Agent_Identity_Store' => 'AgentsAPI\\Core\\Identity\\WP_Agent_Identity_Store', + 'LegacyProduct\\Core\\Workspace\\WP_Agent_Workspace_Scope' => 'AgentsAPI\\Core\\Workspace\\WP_Agent_Workspace_Scope', + 'LegacyProduct\\Core\\FilesRepository\\WP_Agent_Memory_Store' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Store', + 'LegacyProduct\\Core\\FilesRepository\\WP_Agent_Memory_Scope' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Scope', + 'LegacyProduct\\Core\\FilesRepository\\WP_Agent_Memory_Metadata' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Metadata', + 'LegacyProduct\\Core\\FilesRepository\\WP_Agent_Memory_Query' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Query', + 'LegacyProduct\\Core\\FilesRepository\\WP_Agent_Memory_Store_Capabilities' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Store_Capabilities', + 'LegacyProduct\\Core\\FilesRepository\\WP_Agent_Memory_Validator' => 'AgentsAPI\\Core\\FilesRepository\\WP_Agent_Memory_Validator', ); $context_contracts = array( @@ -59,7 +59,7 @@ 'AgentsAPI\\AI\\Context\\WP_Agent_Default_Context_Conflict_Resolver', ); -echo "\n[1] Module bootstrap exposes registration facade without Data Machine product code:\n"; +echo "\n[1] Module bootstrap exposes registration facade without product code:\n"; agents_api_smoke_assert_equals( true, defined( 'AGENTS_API_LOADED' ), 'module marks itself loaded', $failures, $passes ); agents_api_smoke_assert_equals( true, defined( 'AGENTS_API_PATH' ), 'module path constant is available', $failures, $passes ); agents_api_smoke_assert_equals( realpath( __DIR__ . '/..' ) . '/', AGENTS_API_PATH, 'module path points at plugin root directory', $failures, $passes ); @@ -135,13 +135,13 @@ foreach ( $context_contracts as $context_contract ) { agents_api_smoke_assert_equals( true, class_exists( $context_contract ) || interface_exists( $context_contract ), $context_contract . ' contract is available', $failures, $passes ); } -agents_api_smoke_assert_equals( false, class_exists( 'DataMachine\\Engine\\Agents\\AgentRegistry', false ), 'Data Machine registry is not loaded by module bootstrap', $failures, $passes ); -agents_api_smoke_assert_equals( false, class_exists( 'DataMachine\\Core\\Database\\Jobs\\Jobs', false ), 'Data Machine jobs repository is not loaded by module bootstrap', $failures, $passes ); -agents_api_smoke_assert_equals( false, class_exists( 'DataMachine\\Engine\\AI\\AIConversationLoop', false ), 'Data Machine compatibility loop is not loaded by module bootstrap', $failures, $passes ); -agents_api_smoke_assert_equals( false, class_exists( 'DataMachine\\Engine\\AI\\BuiltInAgentConversationRunner', false ), 'Data Machine built-in runner is not loaded by module bootstrap', $failures, $passes ); -agents_api_smoke_assert_equals( false, class_exists( 'DataMachine\\Core\\FilesRepository\\DiskAgentMemoryStore', false ), 'Data Machine disk memory store is not loaded by module bootstrap', $failures, $passes ); +agents_api_smoke_assert_equals( false, class_exists( 'LegacyProduct\\Engine\\Agents\\AgentRegistry', false ), 'product registry is not loaded by module bootstrap', $failures, $passes ); +agents_api_smoke_assert_equals( false, class_exists( 'LegacyProduct\\Core\\Database\\Jobs\\Jobs', false ), 'product jobs repository is not loaded by module bootstrap', $failures, $passes ); +agents_api_smoke_assert_equals( false, class_exists( 'LegacyProduct\\Engine\\AI\\AIConversationLoop', false ), 'product compatibility loop is not loaded by module bootstrap', $failures, $passes ); +agents_api_smoke_assert_equals( false, class_exists( 'LegacyProduct\\Engine\\AI\\BuiltInAgentConversationRunner', false ), 'product built-in runner is not loaded by module bootstrap', $failures, $passes ); +agents_api_smoke_assert_equals( false, class_exists( 'LegacyProduct\\Core\\FilesRepository\\DiskAgentMemoryStore', false ), 'product disk memory store is not loaded by module bootstrap', $failures, $passes ); -echo "\n[2] Module source keeps Data Machine vocabulary out of agents-api contracts:\n"; +echo "\n[2] Module source keeps product vocabulary out of agents-api contracts:\n"; $agents_api_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( AGENTS_API_PATH . 'src', FilesystemIterator::SKIP_DOTS ) ); @@ -153,23 +153,23 @@ $contents = file_get_contents( $file->getPathname() ); agents_api_smoke_assert_equals( 0, - preg_match( '/^\s*(namespace|use)\s+DataMachine\\\\/m', is_string( $contents ) ? $contents : '' ), - 'agents-api source has no Data Machine namespace declaration/import: ' . str_replace( AGENTS_API_PATH, '', $file->getPathname() ), + preg_match( '/^\s*(namespace|use)\s+LegacyProduct\\\\/m', is_string( $contents ) ? $contents : '' ), + 'agents-api source has no product namespace declaration/import: ' . str_replace( AGENTS_API_PATH, '', $file->getPathname() ), $failures, $passes ); agents_api_smoke_assert_equals( false, - false !== strpos( is_string( $contents ) ? $contents : '', 'Data Machine' ), - 'agents-api source has no Data Machine prose coupling: ' . str_replace( AGENTS_API_PATH, '', $file->getPathname() ), + false !== strpos( is_string( $contents ) ? $contents : '', 'LegacyProduct' ), + 'agents-api source has no product prose coupling: ' . str_replace( AGENTS_API_PATH, '', $file->getPathname() ), $failures, $passes ); } $bootstrap_source = (string) file_get_contents( AGENTS_API_PLUGIN_FILE ); -agents_api_smoke_assert_equals( 0, preg_match( '/^\s*(namespace|use)\s+DataMachine\\\\/m', $bootstrap_source ), 'plugin bootstrap has no Data Machine namespace declaration/import', $failures, $passes ); -agents_api_smoke_assert_equals( false, false !== strpos( $bootstrap_source, 'Data Machine' ), 'plugin bootstrap has no Data Machine prose coupling', $failures, $passes ); +agents_api_smoke_assert_equals( 0, preg_match( '/^\s*(namespace|use)\s+LegacyProduct\\\\/m', $bootstrap_source ), 'plugin bootstrap has no product namespace declaration/import', $failures, $passes ); +agents_api_smoke_assert_equals( false, false !== strpos( $bootstrap_source, 'LegacyProduct' ), 'plugin bootstrap has no product prose coupling', $failures, $passes ); echo "\n[3] Module source tree uses Agents API vocabulary:\n"; $expected_source_directories = array( diff --git a/tests/identity-smoke.php b/tests/identity-smoke.php index a67183e..dbfb540 100644 --- a/tests/identity-smoke.php +++ b/tests/identity-smoke.php @@ -61,6 +61,6 @@ echo "\n[3] Identity store contract is available without a concrete backend:\n"; agents_api_smoke_assert_equals( true, interface_exists( 'AgentsAPI\\Core\\Identity\\WP_Agent_Identity_Store' ), 'materialized identity store interface is available', $failures, $passes ); -agents_api_smoke_assert_equals( false, class_exists( 'DataMachine\\Core\\Identity\\WP_Agent_Identity_Store', false ) || interface_exists( 'DataMachine\\Core\\Identity\\WP_Agent_Identity_Store', false ), 'Data Machine identity store alias is not loaded', $failures, $passes ); +agents_api_smoke_assert_equals( false, class_exists( 'LegacyProduct\\Core\\Identity\\WP_Agent_Identity_Store', false ) || interface_exists( 'LegacyProduct\\Core\\Identity\\WP_Agent_Identity_Store', false ), 'product identity store alias is not loaded', $failures, $passes ); agents_api_smoke_finish( 'Agents API materialized identity', $failures, $passes ); diff --git a/tests/identity-store-materialization-smoke.php b/tests/identity-store-materialization-smoke.php index d51b6c4..b73dc23 100644 --- a/tests/identity-store-materialization-smoke.php +++ b/tests/identity-store-materialization-smoke.php @@ -129,6 +129,6 @@ static function () use ( $store ): AgentsAPI\Core\Identity\WP_Agent_Identity_Sto agents_api_smoke_assert_equals( array( 'kitchen-agent', 'pantry-agent' ), array_keys( $identities ), 'all registered agents materialize via filter-provided store', $failures, $passes ); agents_api_smoke_assert_equals( 13, $identities['pantry-agent']->scope->normalize()->owner_user_id ?? null, 'explicit owner_user_id overrides defaults', $failures, $passes ); agents_api_smoke_assert_equals( 'smoke', $identities['pantry-agent']->meta['materialized_by'] ?? null, 'caller metadata is merged into materialization metadata', $failures, $passes ); -agents_api_smoke_assert_equals( false, class_exists( 'DataMachine_Agent_Store', false ), 'materialization lifecycle does not load Data Machine classes', $failures, $passes ); +agents_api_smoke_assert_equals( false, class_exists( 'ExampleProduct_Agent_Store', false ), 'materialization lifecycle does not load product classes', $failures, $passes ); agents_api_smoke_finish( 'Agents API identity store materialization', $failures, $passes ); diff --git a/tests/no-product-imports-smoke.php b/tests/no-product-imports-smoke.php index 46293cd..cf1afa8 100644 --- a/tests/no-product-imports-smoke.php +++ b/tests/no-product-imports-smoke.php @@ -18,17 +18,17 @@ agents_api_smoke_assert_equals( true, is_string( $agents_api_dir ), 'agents-api directory exists', $failures, $passes ); $forbidden_namespaces = array( - 'DataMachine\\Core\\Steps', - 'DataMachine\\Core\\Database\\Jobs', - 'DataMachine\\Core\\Admin', - 'DataMachine\\Core\\Assets', - 'DataMachine\\Engine\\Handlers', - 'DataMachine\\Core\\ActionScheduler', - 'DataMachine\\Engine\\AI\\System\\Tasks\\Retention', - 'DataMachine\\Engine\\Pipelines', - 'DataMachine\\Engine\\Flows', - 'DataMachine\\Engine\\Queue', - 'DataMachine\\Core\\Content', + 'ExampleProduct\\Core\\Steps', + 'ExampleProduct\\Core\\Database\\Jobs', + 'ExampleProduct\\Core\\Admin', + 'ExampleProduct\\Core\\Assets', + 'ExampleProduct\\Engine\\Handlers', + 'ExampleProduct\\Core\\ActionScheduler', + 'ExampleProduct\\Engine\\AI\\System\\Tasks\\Retention', + 'ExampleProduct\\Engine\\Pipelines', + 'ExampleProduct\\Engine\\Flows', + 'ExampleProduct\\Engine\\Queue', + 'ExampleProduct\\Core\\Content', ); $forbidden_admin_apis = array( @@ -71,8 +71,8 @@ } } - if ( preg_match( '/(?:use\s+|new\s+|extends\s+|implements\s+|instanceof\s+)\\?DataMachine\\\\/', $source ) ) { - $matches[] = str_replace( (string) $agents_api_dir . '/', '', $file->getPathname() ) . ' imports a DataMachine namespace'; + if ( preg_match( '/(?:use\s+|new\s+|extends\s+|implements\s+|instanceof\s+)\\?ExampleProduct\\\\/', $source ) ) { + $matches[] = str_replace( (string) $agents_api_dir . '/', '', $file->getPathname() ) . ' imports an ExampleProduct namespace'; } foreach ( $forbidden_admin_apis as $function_name ) { @@ -88,7 +88,7 @@ } } -agents_api_smoke_assert_equals( array(), $matches, 'agents-api has no Data Machine product imports or admin UI registrations', $failures, $passes ); +agents_api_smoke_assert_equals( array(), $matches, 'agents-api has no product imports or admin UI registrations', $failures, $passes ); agents_api_smoke_assert_equals( $forbidden_namespaces, array_values( array_unique( $forbidden_namespaces ) ), 'forbidden namespace list has no duplicates', $failures, $passes ); agents_api_smoke_assert_equals( $forbidden_admin_apis, array_values( array_unique( $forbidden_admin_apis ) ), 'forbidden admin API list has no duplicates', $failures, $passes ); agents_api_smoke_assert_equals( $forbidden_admin_hooks, array_values( array_unique( $forbidden_admin_hooks ) ), 'forbidden admin hook list has no duplicates', $failures, $passes ); diff --git a/tests/package-capability-contract-smoke.php b/tests/package-capability-contract-smoke.php index f12bbbd..e51cb91 100644 --- a/tests/package-capability-contract-smoke.php +++ b/tests/package-capability-contract-smoke.php @@ -36,7 +36,7 @@ 'requires' => array( 'prompt/text' ), ), array( - 'type' => 'datamachine/flow', + 'type' => 'scheduler/job', 'slug' => 'daily-fetch', 'source' => 'flows/daily-fetch.json', 'requires' => array( 'schedule/cron', 'queue/actions' ), @@ -55,7 +55,7 @@ $report = WP_Agent_Package_Capability_Checker::check( $package, array( 'chat', 'prompt/text', 'queue/actions' ), - array( 'known_artifact_types' => array( 'agents/prompt', 'datamachine/flow' ) ) + array( 'known_artifact_types' => array( 'agents/prompt', 'scheduler/job' ) ) ); $report_array = $report->to_array(); @@ -70,14 +70,14 @@ $unsupported_by_key[ $artifact_report['artifact_key'] ] = $artifact_report; } -agents_api_smoke_assert_equals( array( 'schedule/cron' ), $unsupported_by_key['datamachine/flow:daily-fetch']['unsupported_capabilities'], 'artifact-level report keeps missing capability scoped to the artifact', $failures, $passes ); +agents_api_smoke_assert_equals( array( 'schedule/cron' ), $unsupported_by_key['scheduler/job:daily-fetch']['unsupported_capabilities'], 'artifact-level report keeps missing capability scoped to the artifact', $failures, $passes ); agents_api_smoke_assert_equals( true, $unsupported_by_key['vendor/custom:custom-shape']['unknown_artifact_type'], 'artifact-level report flags unknown type', $failures, $passes ); echo "\n[2] A fully capable host reports compatibility:\n"; $compatible = WP_Agent_Package_Capability_Checker::check( $package, array( 'chat', 'memory/files', 'prompt/text', 'queue/actions', 'schedule/cron', 'vendor/custom-runtime' ), - array( 'known_artifact_types' => array( 'agents/prompt', 'datamachine/flow', 'vendor/custom' ) ) + array( 'known_artifact_types' => array( 'agents/prompt', 'scheduler/job', 'vendor/custom' ) ) ); agents_api_smoke_assert_equals( true, $compatible->is_compatible(), 'all declared needs are supported', $failures, $passes ); diff --git a/tests/provider-turn-adapter-smoke.php b/tests/provider-turn-adapter-smoke.php index de3a3d8..9963d04 100644 --- a/tests/provider-turn-adapter-smoke.php +++ b/tests/provider-turn-adapter-smoke.php @@ -181,7 +181,7 @@ public function getText(): string { ); agents_api_smoke_assert_equals( 1, count( $deduped_turn['tool_calls'] ), 'fallback parser overlap is deduplicated', $failures, $passes ); -echo "\n[2] Conversation loop can run through a fake provider-turn adapter without Data Machine:\n"; +echo "\n[2] Conversation loop can run through a fake provider-turn adapter without product runtime coupling:\n"; $adapter_calls = array(); $adapter = new class( $adapter_calls ) implements AgentsAPI\AI\WP_Agent_Provider_Turn_Adapter { /** @var array> Captured requests. */ diff --git a/tests/registered-agent-materialization-adapter-smoke.php b/tests/registered-agent-materialization-adapter-smoke.php index 3d975a2..dfcc8d7 100644 --- a/tests/registered-agent-materialization-adapter-smoke.php +++ b/tests/registered-agent-materialization-adapter-smoke.php @@ -185,6 +185,6 @@ static function () use ( $adapter ): WP_Agent_Registered_Agent_Materialization_A ); agents_api_smoke_assert_equals( true, isset( $filtered_results['kitchen-agent'] ), 'filter-provided adapter is used when no adapter is passed', $failures, $passes ); -agents_api_smoke_assert_equals( false, class_exists( 'DataMachine_Agent_Store', false ), 'materialization contract does not load Data Machine classes', $failures, $passes ); +agents_api_smoke_assert_equals( false, class_exists( 'ExampleProduct_Agent_Store', false ), 'materialization contract does not load product classes', $failures, $passes ); agents_api_smoke_finish( 'Agents API registered-agent materialization adapter', $failures, $passes ); diff --git a/tests/runtime-agent-bundle-importer-smoke.php b/tests/runtime-agent-bundle-importer-smoke.php index b8ac582..def41f1 100644 --- a/tests/runtime-agent-bundle-importer-smoke.php +++ b/tests/runtime-agent-bundle-importer-smoke.php @@ -48,13 +48,14 @@ public function get_error_data(): mixed { do_action( 'init' ); $bundle = array( - 'bundle_version' => '1.2.3', - 'bundle_slug' => 'studio-web-static-site-generator', + 'source_type' => 'runtime-agent-package', + 'source_package' => 'example-site-builder', + 'source_version' => '1.2.3', 'agent' => array( - 'agent_slug' => 'studio-web-static-site-generator', - 'agent_name' => 'Static Site Generator', + 'agent_slug' => 'example-site-builder', + 'agent_name' => 'Example Site Builder', 'agent_config' => array( - 'studio_web' => array( 'prompt' => 'Cook a site.' ), + 'site_builder' => array( 'prompt' => 'Cook a site.' ), ), ), ); @@ -70,9 +71,10 @@ public function get_error_data(): mixed { agents_api_smoke_assert_equals( true, is_array( $result ) && true === ( $result['success'] ?? false ), 'inline bundle import succeeds', $failures, $passes ); agents_api_smoke_assert_equals( 'registered', $result['status'] ?? '', 'inline bundle reports registered status', $failures, $passes ); -agents_api_smoke_assert_equals( true, wp_has_agent( 'studio-web-static-site-generator' ), 'runtime agent is registered', $failures, $passes ); -agents_api_smoke_assert_equals( 'Cook a site.', wp_get_agent( 'studio-web-static-site-generator' )->get_default_config()['studio_web']['prompt'] ?? null, 'agent default config is preserved', $failures, $passes ); -agents_api_smoke_assert_equals( 'runtime-agent-bundle', wp_get_agent( 'studio-web-static-site-generator' )->get_meta()['source_type'] ?? null, 'bundle provenance is recorded', $failures, $passes ); +agents_api_smoke_assert_equals( true, wp_has_agent( 'example-site-builder' ), 'runtime agent is registered', $failures, $passes ); +agents_api_smoke_assert_equals( 'Cook a site.', wp_get_agent( 'example-site-builder' )->get_default_config()['site_builder']['prompt'] ?? null, 'agent default config is preserved', $failures, $passes ); +agents_api_smoke_assert_equals( 'runtime-agent-package', wp_get_agent( 'example-site-builder' )->get_meta()['source_type'] ?? null, 'source provenance is recorded', $failures, $passes ); +agents_api_smoke_assert_equals( 'example-site-builder', wp_get_agent( 'example-site-builder' )->get_meta()['source_package'] ?? null, 'source package provenance is recorded', $failures, $passes ); echo "\n[2] Conflict policy is enforced:\n"; $skipped = apply_filters( 'wp_agent_runtime_import_bundle', null, array( 'bundle' => $bundle ), array( 'on_conflict' => 'skip' ), 1 ); @@ -88,8 +90,8 @@ public function get_error_data(): mixed { echo "\n[4] Source bundle files and list helper use the generic importer:\n"; $source_bundle = array( - 'bundle_version' => '1.0.0', - 'bundle_slug' => 'runtime-source-agent', + 'package_version' => '1.0.0', + 'package_slug' => 'runtime-source-agent', 'agent' => array( 'agent_slug' => 'runtime-source-agent', 'agent_name' => 'Runtime Source Agent', @@ -105,7 +107,7 @@ public function get_error_data(): mixed { $helper_results = wp_agent_import_runtime_bundles( array( array( 'bundle' => array( - 'bundle_slug' => 'runtime-helper-agent', + 'package_slug' => 'runtime-helper-agent', 'agent' => array( 'agent_slug' => 'runtime-helper-agent', 'agent_name' => 'Runtime Helper Agent', diff --git a/tests/runtime-tool-policy-smoke.php b/tests/runtime-tool-policy-smoke.php index 9067a3d..de2be9f 100644 --- a/tests/runtime-tool-policy-smoke.php +++ b/tests/runtime-tool-policy-smoke.php @@ -33,18 +33,18 @@ WP_Agent_Tool_Declaration::RUNTIME_CAPABILITY_SCOPE => WP_Agent_Tool_Declaration::CAPABILITY_SCOPE_RUNTIME_LOCAL, ), ), - 'datamachine/workspace-git-push' => array( - 'name' => 'datamachine/workspace-git-push', - 'source' => 'datamachine-code', + 'control-plane/workspace-git-push' => array( + 'name' => 'control-plane/workspace-git-push', + 'source' => 'control-plane', 'description' => 'Push a workspace branch from the parent control plane.', 'runtime' => array( WP_Agent_Tool_Declaration::RUNTIME_ENVIRONMENT => WP_Agent_Tool_Declaration::ENVIRONMENT_CONTROL_PLANE, WP_Agent_Tool_Declaration::RUNTIME_CAPABILITY_SCOPE => WP_Agent_Tool_Declaration::CAPABILITY_SCOPE_CONTROL_PLANE, ), ), - 'datamachine/workspace-read' => array( - 'name' => 'datamachine/workspace-read', - 'source' => 'datamachine-code', + 'control-plane/workspace-read' => array( + 'name' => 'control-plane/workspace-read', + 'source' => 'control-plane', 'description' => 'Read a workspace file through the parent control plane.', ), ); @@ -86,10 +86,10 @@ agents_api_smoke_assert_equals( WP_Agent_Tool_Declaration::ENVIRONMENT_RUNTIME_LOCAL, $by_id['filesystem-write']['runtime'][ WP_Agent_Tool_Declaration::RUNTIME_ENVIRONMENT ] ?? '', 'runtime-local tool records execution environment', $failures, $passes ); agents_api_smoke_assert_equals( 'sandbox', $by_id['filesystem-write']['execution_location'] ?? '', 'runtime-local tool projects legacy sandbox location for consumers', $failures, $passes ); -agents_api_smoke_assert_equals( false, $by_id['datamachine/workspace-git-push']['allowed'] ?? true, 'control-plane tool is denied to runtime-local agent', $failures, $passes ); -agents_api_smoke_assert_equals( 'parent', $by_id['datamachine/workspace-git-push']['transport_visibility'] ?? '', 'control-plane tool projects parent visibility', $failures, $passes ); -agents_api_smoke_assert_equals( 'datamachine_workspace_read', $by_id['datamachine/workspace-read']['runtime_tool_id'] ?? '', 'runtime tool id defaults from tool name', $failures, $passes ); -agents_api_smoke_assert_equals( false, $by_id['datamachine/workspace-read']['allowed'] ?? true, 'tools without runtime metadata default closed', $failures, $passes ); +agents_api_smoke_assert_equals( false, $by_id['control-plane/workspace-git-push']['allowed'] ?? true, 'control-plane tool is denied to runtime-local agent', $failures, $passes ); +agents_api_smoke_assert_equals( 'parent', $by_id['control-plane/workspace-git-push']['transport_visibility'] ?? '', 'control-plane tool projects parent visibility', $failures, $passes ); +agents_api_smoke_assert_equals( 'control_plane_workspace_read', $by_id['control-plane/workspace-read']['runtime_tool_id'] ?? '', 'runtime tool id defaults from tool name', $failures, $passes ); +agents_api_smoke_assert_equals( false, $by_id['control-plane/workspace-read']['allowed'] ?? true, 'tools without runtime metadata default closed', $failures, $passes ); add_filter( 'agents_api_runtime_tool_policy',