diff --git a/.env.example b/.env.example index 6b8680c..7afd213 100644 --- a/.env.example +++ b/.env.example @@ -82,8 +82,8 @@ AUTO_APPROVE_IGNORE_LOW_SEVERITY=false # [optional] Built-in types use presets in code — leave ACP_COMMAND/ACP_ARGS unset. # -# claude → npx -y @zed-industries/claude-agent-acp -# codex → npx -y @zed-industries/codex-acp +# claude → npx -y @agentclientprotocol/claude-agent-acp +# codex → npx -y @agentclientprotocol/codex-acp # opencode → opencode acp # # Custom type: set ACP_AGENT_TYPE plus ACP_COMMAND and ACP_ARGS. diff --git a/README.md b/README.md index 12d3c2a..08f456a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ its own built-in knowledge. The Docker image pre-installs the public code-review ### 1. Install dependencies -Requires **Python 3.12+** and [uv](https://docs.astral.sh/uv/) (see `.python-version`; Docker image uses `python:3.12-slim`). +Requires **Python 3.12+** and [uv](https://docs.astral.sh/uv/) (see `.python-version`; Docker image +uses `python:3.12-slim`). Local npx launchers require Node.js; Claude ACP requires **Node.js 22+**. +Docker images already include Node.js 22. ```bash uv sync --extra dev @@ -143,6 +145,9 @@ they are never embedded in workspace Git URLs. | `LOG_LEVEL` | no | `INFO` | Bot log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) | | `LOG_DIR` | no | `logs` | Root directory for bot-generated files (relative to `CODE_REVIEW_BOT_ROOT`). Fixed subdirs: `sessions/` (per-review logs), `debug-reports/` (`--debug` Markdown reports). Empty string disables file logging. | +After ACP initialization, INFO logs include the agent-reported name/version, protocol version, and +launcher command; no additional package-manager lookup is performed. + ### Runtime paths | Variable | Required | Default | Description | diff --git a/docker/Dockerfile b/docker/Dockerfile index 7b0ec8f..528be17 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,7 +24,7 @@ RUN if [ -n "$APT_MIRROR" ]; then \ curl \ gnupg \ ripgrep \ - && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* @@ -82,7 +82,7 @@ USER root ARG NPM_CONFIG_REGISTRY RUN set -eu; \ if [ -n "$NPM_CONFIG_REGISTRY" ]; then npm config set registry "$NPM_CONFIG_REGISTRY"; fi; \ - npm install -g @zed-industries/claude-agent-acp; \ + npm install -g @agentclientprotocol/claude-agent-acp; \ npm list -g --depth=0 USER appuser ENV ACP_AGENT_TYPE=claude diff --git a/docker/README.md b/docker/README.md index aaa5846..a83b80e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -34,7 +34,8 @@ docker build -f docker/Dockerfile \ Select the `claude` or `opencode` target to install only that coding agent. A build without `--target` uses the final `claude` stage. Package versions are intentionally unpinned, so a rebuilt npm layer resolves the current registry version; `npm list -g --depth=0` records the resolved -version in the build log. A cached layer retains its installed version. +version in the build log. A cached layer retains its installed version. Both images include +Node.js 22; the Docker host does not need Node.js installed. ## Coding-agent image tags @@ -48,7 +49,7 @@ whhe/code-review-bot:opencode Each target persists its matching `ACP_AGENT_TYPE`. Both variants leave `ACP_COMMAND` and `ACP_ARGS` unset. Built-in presets start Claude through -`npx -y @zed-industries/claude-agent-acp` and OpenCode through `opencode acp`. Agent-specific +`npx -y @agentclientprotocol/claude-agent-acp` and OpenCode through `opencode acp`. Agent-specific Docker stages ensure Claude images do not contain OpenCode-only environment defaults. `:latest` and `:claude-code` are aliases for the same Claude image. OpenCode is published only as diff --git a/src/code_review_bot/agent/acp.py b/src/code_review_bot/agent/acp.py index a2466b3..1685a00 100644 --- a/src/code_review_bot/agent/acp.py +++ b/src/code_review_bot/agent/acp.py @@ -239,7 +239,7 @@ async def request_permission( ), ) as (conn, _proc): try: - await conn.initialize( + initialize_response = await conn.initialize( protocol_version=PROTOCOL_VERSION, client_capabilities=ClientCapabilities(), client_info=Implementation( @@ -248,6 +248,15 @@ async def request_permission( version="0.1.0", ), ) + agent_info = getattr(initialize_response, "agent_info", None) + protocol_version = getattr(initialize_response, "protocol_version", None) + logger.info( + "[agent runtime] name=%s version=%s protocol=%s command=%s", + getattr(agent_info, "name", None) or "(unknown)", + getattr(agent_info, "version", None) or "(unknown)", + protocol_version if protocol_version is not None else "(unknown)", + config.command, + ) session_kwargs: dict[str, Any] = {"cwd": cwd} if additional_directories: session_kwargs["additional_directories"] = additional_directories diff --git a/src/code_review_bot/agent/presets.py b/src/code_review_bot/agent/presets.py index ceadd67..9a6bc9c 100644 --- a/src/code_review_bot/agent/presets.py +++ b/src/code_review_bot/agent/presets.py @@ -7,8 +7,8 @@ BUILTIN_ACP_AGENT_TYPES: frozenset[str] = frozenset({"claude", "codex", "opencode"}) ACP_PRESETS: dict[BuiltinAcpAgentType, tuple[str, list[str]]] = { - "claude": ("npx", ["-y", "@zed-industries/claude-agent-acp"]), - "codex": ("npx", ["-y", "@zed-industries/codex-acp"]), + "claude": ("npx", ["-y", "@agentclientprotocol/claude-agent-acp"]), + "codex": ("npx", ["-y", "@agentclientprotocol/codex-acp"]), "opencode": ("opencode", ["acp"]), } diff --git a/tests/test_acp_presets.py b/tests/test_acp_presets.py index 9d9c0c1..5e29587 100644 --- a/tests/test_acp_presets.py +++ b/tests/test_acp_presets.py @@ -14,7 +14,7 @@ def test_is_builtin() -> None: def test_builtin_uses_preset() -> None: command, args = resolve_acp_launcher("codex") assert command == "npx" - assert args == ["-y", "@zed-industries/codex-acp"] + assert args == ["-y", "@agentclientprotocol/codex-acp"] def test_opencode_is_builtin_and_uses_native_acp() -> None: diff --git a/tests/test_acp_runtime.py b/tests/test_acp_runtime.py index 79302c8..87ac3fd 100644 --- a/tests/test_acp_runtime.py +++ b/tests/test_acp_runtime.py @@ -1,3 +1,4 @@ +import logging from collections.abc import AsyncIterator from contextlib import asynccontextmanager from pathlib import Path @@ -13,9 +14,10 @@ class _Connection: def __init__(self) -> None: self.model_calls: list[tuple[str, str]] = [] self.config_calls: list[tuple[str, str, str]] = [] + self.initialize_response = SimpleNamespace(agent_info=None, protocol_version=1) - async def initialize(self, **kwargs: object) -> None: - return None + async def initialize(self, **kwargs: object) -> SimpleNamespace: + return self.initialize_response async def new_session(self, **kwargs: object) -> SimpleNamespace: return SimpleNamespace(session_id="session-1") @@ -115,3 +117,52 @@ async def fake_spawn_agent_process( run = await agent.run_once("review") assert run.model == "provider/runtime-model" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("agent_info", "command", "expected_log"), + [ + ( + SimpleNamespace(name="claude-agent-acp", version="0.60.0"), + "npx", + "[agent runtime] name=claude-agent-acp version=0.60.0 protocol=1 command=npx", + ), + ( + None, + "agent", + "[agent runtime] name=(unknown) version=(unknown) protocol=1 command=agent", + ), + ], +) +async def test_acp_runtime_logs_agent_identity_from_initialize_response( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + caplog: pytest.LogCaptureFixture, + agent_info: SimpleNamespace | None, + command: str, + expected_log: str, +) -> None: + connection = _Connection() + connection.initialize_response = SimpleNamespace( + agent_info=agent_info, + protocol_version=1, + ) + + @asynccontextmanager + async def fake_spawn_agent_process( + client: object, + command: str, + *args: str, + env: dict[str, str] | None = None, + **kwargs: object, + ) -> AsyncIterator[tuple[_Connection, SimpleNamespace]]: + yield connection, SimpleNamespace(stdin=None, stdout=None, stderr=None, _transport=None) + + monkeypatch.setattr(acp_sdk, "spawn_agent_process", fake_spawn_agent_process) + agent = AcpCodingAgent(AcpAgentConfig(command=command, verbose=False), tmp_path) + + with caplog.at_level(logging.INFO, logger="code_review_bot.agent.acp"): + await agent.run_once("review") + + assert expected_log in caplog.messages diff --git a/tests/test_config.py b/tests/test_config.py index a30359f..2dbbff4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -113,7 +113,7 @@ def test_settings_acp_agent_type_defaults_to_claude(monkeypatch: pytest.MonkeyPa settings = Settings(_env_file=None) assert settings.acp_agent_type == "claude" assert settings.resolved_acp_command == "npx" - assert settings.resolved_acp_args == ["-y", "@zed-industries/claude-agent-acp"] + assert settings.resolved_acp_args == ["-y", "@agentclientprotocol/claude-agent-acp"] def test_settings_acp_agent_type_codex_launcher(monkeypatch: pytest.MonkeyPatch) -> None: @@ -124,7 +124,7 @@ def test_settings_acp_agent_type_codex_launcher(monkeypatch: pytest.MonkeyPatch) settings = Settings(_env_file=None) assert settings.acp_agent_type == "codex" - assert settings.resolved_acp_args == ["-y", "@zed-industries/codex-acp"] + assert settings.resolved_acp_args == ["-y", "@agentclientprotocol/codex-acp"] def test_settings_acp_agent_type_opencode_launcher(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/tests/test_opencode_image.py b/tests/test_opencode_image.py index c5e1fb0..98a6016 100644 --- a/tests/test_opencode_image.py +++ b/tests/test_opencode_image.py @@ -13,6 +13,7 @@ def test_shared_dockerfile_installs_only_selected_agent() -> None: dockerfile = path.read_text(encoding="utf-8") assert "FROM python:3.12-slim AS base" in dockerfile + assert "https://deb.nodesource.com/setup_22.x" in dockerfile assert "FROM base AS opencode" in dockerfile assert "FROM base AS claude" in dockerfile assert "FROM agent-${ACP_AGENT_TYPE}" not in dockerfile @@ -28,7 +29,7 @@ def test_shared_dockerfile_installs_only_selected_agent() -> None: "FROM base AS claude", 1 )[0] claude_stage = dockerfile.split("FROM base AS claude", 1)[1] - assert "npm install -g @zed-industries/claude-agent-acp" in claude_stage + assert "npm install -g @agentclientprotocol/claude-agent-acp" in claude_stage assert "ENV ACP_AGENT_TYPE=claude" in claude_stage assert "OPENCODE_" not in claude_stage assert "npm install -g opencode-ai" in opencode_stage