diff --git a/CHANGELOG.md b/CHANGELOG.md index eec85be69..2b495b2b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Changed `wf10_batch.robot` to be less likely to create files, and `plan_generation_graph.robot` to give more test answers. ## [Unreleased] +- **feat(a2a): A2A stdio transport for local-mode subprocess communication (#691):** Implemented ``A2aStdioTransport`` class providing JSON-RPC 2.0 message framing over stdin/stdout for communicating with an agent subprocess in local mode. Features include process lifecycle management (``connect``, ``disconnect`` with graceful shutdown via wait-then-terminate-then-kill), request/response serialization and deserialization, type-safe path resolution (Python module paths use ``python -m``, ``.py`` files execute directly, executables run without interpreter prefix), and comprehensive error handling for subprocess lifecycle events. Added full BDD test suite covering all code paths in ``features/a2a_stdio_transport.feature`` with mock-based step definitions. +- **fix(a2a): .py path routing in A2aStdioTransport (#691):** Corrected ``connect()`` to use direct script execution (``[sys.executable, agent_path]``) for literal ``.py`` file paths instead of routing through ``python -m``, which expects a module name. Module paths (``cleveragents.*``) continue to use ``-m``; bare executables remain unchanged. - **feat(resources): resource type extension interface** (#9998): New `cleveragents.resources` package providing the stable public API third-party developers use to add custom resource types without modifying core code. Includes `ResourceType` ABC with five abstract lifecycle methods (`provision`, `deprovision`, `status`, `validate_config`, `to_dict`), a `ResourceConfig` Pydantic model (`name`, `resource_type`, `properties`), a `ResourceStatus` StrEnum (`PENDING`, `ACTIVE`, `FAILED`, `DEPROVISIONED`), and registry functions `register_resource_type` / `get_resource_type` / `list_resource_types`. Custom types are registered under namespaced names (e.g. `myorg/database`); registration raises `TypeError` for non-`ResourceType` subclasses and `ValueError` for duplicate names. 25 BDD scenarios in `features/resource_type_extension_interface.feature` cover enum values, config instantiation, ABC enforcement, all lifecycle method return types, and registry CRUD + error paths. - **refactor(a2a): route CLI→Application communication through A2A boundary** (Refs #9962, #4253): Introduced `cleveragents.shared.output_format` as a layer-neutral serialiser (`format_data` supporting `json`/`yaml`/`plain`/`table`) with no dependency on `cleveragents.cli.*`, eliminating a reverse dependency from `PlanApplyService.artifacts()` on the CLI presentation layer. The shared formatter returns raw payloads with no CLI envelope wrapping (`{"data": ..., "command": ..., "status": ...}`); callers that previously parsed `parsed["data"]` from `apply_service.artifacts(fmt="json")` output now read fields at the top level. Updated `features/steps/plan_diff_artifacts_steps.py` (`step_artifacts_json_validation`, `step_artifacts_json_apply_summary`) to drop the stale envelope unwrap that caused `KeyError: 'data'` under the new boundary. Removed stale `@tdd_expected_fail` tag from `WF02 Mocked Generation Produces Test Artifacts Only` in `robot/wf02_test_generation_integration.robot` — the scenario now passes naturally through the A2A facade dispatch path (`_cleveragents/plan/artifacts`) introduced by this refactor. - **fix(test): move advanced context strategy test doubles to features/mocks** (#7574): Extracted `FakeEmbeddings`, `RelevanceScoringStrategy`, `AdaptiveContextSelector`, `ContextFusionStrategy`, and `_pack_budget` from `features/steps/advanced_context_strategies_steps.py` into a new `features/mocks/advanced_context_strategies_mocks.py` file per CONTRIBUTING.md mock-placement rules. Updated the Robot Framework helper `robot/helper_advanced_context_strategies.py` to import directly from `features.mocks` rather than manipulating `sys.path` to reach the Behave steps file. Added `None` guard in `step_assemble_context_query` before calling `selected.assemble()`, and added explicit `ValueError` for unknown strategy types in both `step_load_yaml_strategy` and `load_strategy_from_yaml_impl`. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 78da56866..aeecfd0a0 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -35,7 +35,7 @@ Below are some of the specific details of various contributions. * HAL 9000 has contributed the plan concurrency race-condition fix (#7989): wired `LockService` into the plan lifecycle, guarding `execute_plan()` and `apply_plan()` with plan-level advisory locks and unique per-invocation owner identities to prevent silent concurrent state corruption. * HAL 9000 has contributed the plan artifacts JSON completeness fix (#9084): ensured `validation_summary` and `apply_summary` are correctly included in `_build_artifacts_dict`, removing stale `@tdd_expected_fail` tags from Behave scenarios to enable full regression test coverage. * HAL 9000 has contributed the bug-hunt-pool-supervisor non-blocking tracking fix (#7875 / PR #7957): updated step 5 to be best-effort and added rule 9 to prevent the automation-tracking-manager call from blocking the main supervisor loop. -* Jeffrey Phillips Freeman has contributed the complete AUTO-BUG-POOL to AUTO-BUG-SUP tracking prefix fix across agent-system-specification.md, automation-tracking.md documentation and agent-system-specification.md spec document, replaced with correct `AUTO-BUG-SUP` prefix used by the bug-hunt-pool-supervisor agent (#7875). +* Jeffrey Phillips Freeman has contributed the complete AUTO-BUG-SUP tracking prefix fix across agent-system-specification.md, automation-tracking.md documentation, replacing the incorrect `AUTO-BUG-POL` prefix with the correct `AUTO-BUG-SUP` prefix used by the bug-hunt-pool-supervisor agent (#7875). * HAL 9000 has contributed the plugin entry point security hardening fix (#7476): enforced entry point allowlist validation before importing plugin modules to prevent malicious plugin loading. * HAL 9000 has contributed the benchmark workflow separation (#9040): moved the benchmark-regression job out of the default PR workflow into a dedicated scheduled workflow, reducing median PR CI turnaround time from 99-132 minutes to under 30 minutes. * HAL 9000 has contributed the plan tree JSON/YAML command envelope fix (#9163): wrapped `agents plan tree --format json/yaml` output in the spec-required command envelope structure, added summary statistics, decision_ids mapping, child_plans list, and accurate timing measurement. @@ -97,6 +97,7 @@ Below are some of the specific details of various contributions. * HAL 9000 has contributed cost and session budget tracking and enforcement (issue #8609): implemented `CostTrackingService`, `BudgetExceededError`, three-tier budget hierarchy (plan/session/organization), warning thresholds at 90% utilization, per-provider cost tracking, and comprehensive BDD test suite. * HAL 9000 has contributed the AutoDebug node state mutation fix (#10496): fixed _analyze_error, _generate_fix, and _validate_fix in src/cleveragents/agents/graphs/auto_debug.py to return partial update dicts per LangGraph s node contract, preventing duplicate state entries and checkpoint inconsistencies. * HAL 9000 has contributed the ContextStrategy protocol and plugin registration system (PR #11106 / issue #8616): implemented the domain-model `ContextStrategy` Protocol with BackendSet, PlanContext, StrategyCapabilities, StrategyConfig, ContextStrategyResult, and StrategyRegistryEntry models. Six built-in strategies each implement real backend query logic respecting budget constraints. The StrategyRegistry service provides thread-safe registration/unregistration, Pydantic-validated config updates with immutable MappingProxyType fields, plugin discovery via register_from_module() with CWE-706 module-prefix allowlist guard, enabled list management, deterministic fragment ordering, and validation warnings. Comprehensive BDD test coverage in features/context_strategies.feature and features/context_strategy_registry.feature (120+ scenarios) (#8616). +* HAL 9000 has contributed the A2A stdio transport for local mode (#691): implemented ``A2aStdioTransport`` class with JSON-RPC 2.0 message framing over stdin/stdout for subprocess communication, including process lifecycle management (connect/disconnect), request/response serialization, graceful shutdown with timeout-based termination, and type-safe path resolution for Python modules vs. executable scripts. Added full BDD test coverage (18 scenarios) in ``features/a2a_stdio_transport.feature`` with mock subprocess behavior in step definitions. # Details (PR Contributions) diff --git a/features/a2a_stdio_transport.feature b/features/a2a_stdio_transport.feature index 6fe2392b8..cb5f46ca8 100644 --- a/features/a2a_stdio_transport.feature +++ b/features/a2a_stdio_transport.feature @@ -96,6 +96,7 @@ Feature: A2A stdio transport for local-mode communication And subprocess Popen is mocked to succeed When I connect with agent path "cleveragents.a2a.agent" Then the stdio transport should be connected + And subprocess Popen should have been called with python -m module args @coverage Scenario: Connect with executable path @@ -103,13 +104,17 @@ Feature: A2A stdio transport for local-mode communication And subprocess Popen is mocked to succeed When I connect with agent path "/usr/local/bin/agent" Then the stdio transport should be connected + And subprocess Popen should have been called with direct executable args @coverage + @tdd_issue + @tdd_issue_691 Scenario: Connect with .py file path Given a new A2aStdioTransport instance And subprocess Popen is mocked to succeed When I connect with agent path "agent.py" Then the stdio transport should be connected + And subprocess Popen should have been called with python script args @coverage Scenario: Connect raises for file not found diff --git a/features/steps/a2a_stdio_transport_steps.py b/features/steps/a2a_stdio_transport_steps.py index a7bb7b2db..bf6bbd36a 100644 --- a/features/steps/a2a_stdio_transport_steps.py +++ b/features/steps/a2a_stdio_transport_steps.py @@ -5,6 +5,7 @@ from __future__ import annotations import json import subprocess +import sys from typing import Any from unittest.mock import MagicMock, patch @@ -80,12 +81,13 @@ def step_mock_popen_success(context: Any) -> None: mock_proc.stdout = MagicMock() mock_proc.stderr = MagicMock() mock_proc.pid = 99999 - context.popen_mock = mock_proc patcher = patch( "cleveragents.a2a.stdio_transport.subprocess.Popen", return_value=mock_proc ) context.popen_patcher = patcher - patcher.start() + # patcher.start() returns the mock that replaces subprocess.Popen; call_args + # are recorded on THIS mock, not on mock_proc (the return value). + context.popen_mock = patcher.start() def cleanup() -> None: patcher.stop() @@ -281,3 +283,35 @@ def step_terminate_called(context: Any) -> None: def step_runtime_agent_not_found(context: Any) -> None: assert isinstance(context.raised_error, RuntimeError) assert "agent not found" in str(context.raised_error).lower() + + +# ── Command construction assertions (reviewer point 5) ───────────────────── + + +@then("subprocess Popen should have been called with python -m module args") +def step_assert_popen_module_args(context: Any) -> None: + """Assert that for a module path, Popen received [python, -m, module].""" + call_args = context.popen_mock.call_args + cmd_list = call_args[0][0] + assert len(cmd_list) >= 3 + assert cmd_list[0] == sys.executable + assert cmd_list[1] == "-m" + + +@then("subprocess Popen should have been called with python script args") +def step_assert_popen_script_args(context: Any) -> None: + """Assert that for a .py file path, Popen received [python, file.py].""" + call_args = context.popen_mock.call_args + cmd_list = call_args[0][0] + assert len(cmd_list) >= 2 + assert cmd_list[0] == sys.executable + assert cmd_list[1].endswith(".py") + + +@then("subprocess Popen should have been called with direct executable args") +def step_assert_popen_executable_args(context: Any) -> None: + """Assert that for an executable path, Popen received [executable_path] only.""" + call_args = context.popen_mock.call_args + cmd_list = call_args[0][0] + assert len(cmd_list) >= 1 + assert cmd_list[0].startswith("/") diff --git a/src/cleveragents/a2a/stdio_transport.py b/src/cleveragents/a2a/stdio_transport.py index 0feb7b6e0..893514056 100644 --- a/src/cleveragents/a2a/stdio_transport.py +++ b/src/cleveragents/a2a/stdio_transport.py @@ -126,9 +126,12 @@ class A2aStdioTransport: try: # Construct command: python -m cleveragents.a2a.cli_bootstrap [args] # or direct path to agent executable - if agent_path.endswith(".py") or agent_path.startswith("cleveragents."): - # Python module path + if agent_path.startswith("cleveragents."): + # Python module path (e.g. cleveragents.a2a.cli_bootstrap) cmd = [sys.executable, "-m", agent_path, *list(args)] + elif agent_path.endswith(".py"): + # Literal .py script file (relative or absolute path) + cmd = [sys.executable, agent_path, *list(args)] else: # Direct executable path cmd = [agent_path, *list(args)]