feat(interfaces): add server client stubs
CI / lint (pull_request) Successful in 18s
CI / benchmark-publish (pull_request) Has been skipped
CI / quality (pull_request) Successful in 20s
CI / typecheck (pull_request) Successful in 30s
CI / build (pull_request) Successful in 15s
CI / security (pull_request) Successful in 29s
CI / unit_tests (pull_request) Successful in 1m59s
CI / integration_tests (pull_request) Successful in 3m35s
CI / docker (pull_request) Successful in 39s
CI / coverage (pull_request) Successful in 3m39s
CI / lint (push) Successful in 24s
CI / security (push) Successful in 29s
CI / typecheck (push) Successful in 38s
CI / quality (push) Successful in 32s
CI / build (push) Successful in 15s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m5s
CI / integration_tests (push) Successful in 4m42s
CI / docker (push) Successful in 42s
CI / coverage (push) Successful in 6m53s
CI / benchmark-publish (push) Has been cancelled
CI / benchmark-regression (pull_request) Successful in 24m12s

Add protocol stubs for remote server communication infrastructure:
- ServerClient: Health check and version negotiation protocol
- RemoteExecutionClient: Remote plan execution and status protocol
- AuthClient: Authentication and token management protocol
- StubServerClient/StubRemoteExecutionClient/StubAuthClient: Stub
  implementations raising NotImplementedError for all methods
- ServerConnectionConfig: Validated config model (server_url, namespace,
  auth_token_ref, tls_verify)
- Config keys: core.server_url, core.server_namespace, core.server_tls_verify
- CLI: agents connect <url> stub command with explicit warning
- CLI: agents info shows Server Mode (disabled/stubbed)

ISSUES CLOSED: #201
This commit was merged in pull request #507.
This commit is contained in:
2026-03-02 07:54:20 +00:00
parent c43e1f8260
commit c781848cb6
17 changed files with 1561 additions and 6 deletions
+88
View File
@@ -0,0 +1,88 @@
"""ASV benchmarks for server client stubs and connection config.
Measures the performance of:
- ServerConnectionConfig construction and validation
- StubServerClient/StubRemoteExecutionClient/StubAuthClient instantiation
- Config key registry lookups for server.* keys
"""
from __future__ import annotations
import importlib
import sys
from pathlib import Path
# Ensure the local *source* tree is importable even when ASV has an
# older build of the package installed.
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
# Force-reload so ASV picks up the source tree version.
import cleveragents # noqa: E402
importlib.reload(cleveragents)
from cleveragents.acp.clients import ( # noqa: E402
StubAuthClient,
StubRemoteExecutionClient,
StubServerClient,
)
from cleveragents.acp.server_config import ServerConnectionConfig # noqa: E402
from cleveragents.application.services.config_service import _REGISTRY # noqa: E402
# ---------------------------------------------------------------------------
# Config resolution benchmarks
# ---------------------------------------------------------------------------
class ServerConfigResolutionSuite:
"""Benchmark ServerConnectionConfig construction overhead."""
timeout = 60
def time_create_config_minimal(self) -> None:
ServerConnectionConfig(server_url="https://agents.example.com")
def time_create_config_full(self) -> None:
ServerConnectionConfig(
server_url="https://agents.example.com",
namespace="my-team",
auth_token_ref="CLEVERAGENTS_SERVER_TOKEN",
tls_verify=False,
)
def time_registry_lookup_server_url(self) -> None:
_ = _REGISTRY["server.url"]
def time_registry_lookup_server_tls(self) -> None:
_ = _REGISTRY["server.tls-verify"]
def time_registry_lookup_server_namespace(self) -> None:
_ = _REGISTRY["server.namespace"]
# ---------------------------------------------------------------------------
# Stub client benchmarks
# ---------------------------------------------------------------------------
class StubClientInstantiationSuite:
"""Benchmark stub client instantiation overhead."""
timeout = 60
def time_instantiate_stub_server_client(self) -> None:
StubServerClient()
def time_instantiate_stub_execution_client(self) -> None:
StubRemoteExecutionClient()
def time_instantiate_stub_auth_client(self) -> None:
StubAuthClient()
def time_instantiate_all_stubs(self) -> None:
StubServerClient()
StubRemoteExecutionClient()
StubAuthClient()
+83
View File
@@ -0,0 +1,83 @@
# Server Client Stubs
## Overview
CleverAgents defines three protocol interfaces for server communication. In
the current release all implementations are **stubs** that raise
`NotImplementedError`. When server mode is implemented as a separate project
the concrete clients will replace these stubs.
## Client Protocols
### ServerClient
Health check and version negotiation with the server.
| Method | Return | Description |
|--------|--------|-------------|
| `health_check()` | `bool` | Check server health |
| `get_version()` | `str` | Get server version |
### RemoteExecutionClient
Remote plan execution and status tracking.
| Method | Return | Description |
|--------|--------|-------------|
| `execute_plan(plan_id)` | `dict` | Submit plan for remote execution |
| `get_plan_status(plan_id)` | `dict` | Query remote plan status |
| `cancel_plan(plan_id)` | `bool` | Cancel remote plan execution |
### AuthClient
Authentication and token management.
| Method | Return | Description |
|--------|--------|-------------|
| `authenticate(token)` | `bool` | Authenticate with server |
| `validate_token(token)` | `bool` | Validate token validity |
## Stub Implementations
- `StubServerClient` — raises `NotImplementedError` for all methods
- `StubRemoteExecutionClient` — validates arguments, then raises
`NotImplementedError`
- `StubAuthClient` — validates arguments, then raises `NotImplementedError`
## ServerConnectionConfig
Validated Pydantic model for server connection parameters.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `server_url` | `str` | *(required)* | Server URL (must be `http://` or `https://`) |
| `namespace` | `str` | `"default"` | Namespace on the server |
| `auth_token_ref` | `str \| None` | `None` | Reference to auth token |
| `tls_verify` | `bool` | `True` | Verify TLS certificates |
## Config Keys
| Key | Type | Default | Env Variable | Description |
|-----|------|---------|-------------|-------------|
| `server.url` | string | *(not set)* | `CLEVERAGENTS_SERVER_URL` | Server URL |
| `server.token` | string | *(not set)* | `CLEVERAGENTS_SERVER_TOKEN` | Auth token |
| `server.tls-verify` | bool | `true` | `CLEVERAGENTS_SERVER_TLS_VERIFY` | TLS verification |
| `server.namespace` | string | `"default"` | `CLEVERAGENTS_SERVER_NAMESPACE` | Server namespace |
## Connection Behavior
**Current mode: stub**
When `server.url` is configured, the system reports `Server Mode: stubbed` in
`agents info` output. No actual network communication occurs. The `agents server
connect <url>` command persists the URL to configuration and prints an explicit
warning that server connection is not yet implemented.
When `server.url` is not configured, the system reports `Server Mode: disabled`.
## CLI Commands
```
agents server connect <url> [--namespace NS] [--tls-verify|--no-tls-verify]
agents server status
```
+183
View File
@@ -0,0 +1,183 @@
@phase2 @acp @server-stubs
Feature: Server Client Stubs and Connection Config
As a developer
I want protocol stubs for server communication
So that the server client interfaces are defined before implementation
# -----------------------------------------------------------------------
# ServerConnectionConfig validation
# -----------------------------------------------------------------------
Scenario: ServerConnectionConfig accepts valid HTTPS URL
When I create a ServerConnectionConfig with url "https://agents.example.com"
Then the config server_url should be "https://agents.example.com"
And the config namespace should be "default"
And the config tls_verify should be true
Scenario: ServerConnectionConfig accepts valid HTTP URL
When I create a ServerConnectionConfig with url "http://localhost:8080"
Then the config server_url should be "http://localhost:8080"
Scenario: ServerConnectionConfig accepts custom namespace
When I create a ServerConnectionConfig with url "https://agents.example.com" and namespace "my-team"
Then the config namespace should be "my-team"
Scenario: ServerConnectionConfig accepts auth_token_ref
When I create a ServerConnectionConfig with url "https://agents.example.com" and auth_token_ref "CLEVERAGENTS_SERVER_TOKEN"
Then the config auth_token_ref should be "CLEVERAGENTS_SERVER_TOKEN"
Scenario: ServerConnectionConfig rejects empty URL
When I try to create a ServerConnectionConfig with empty url
Then a server config validation error should be raised
Scenario: ServerConnectionConfig rejects non-HTTP URL
When I try to create a ServerConnectionConfig with url "ftp://agents.example.com"
Then a server config validation error should be raised
Scenario: ServerConnectionConfig rejects whitespace-only URL
When I try to create a ServerConnectionConfig with url " "
Then a server config validation error should be raised
Scenario: ServerConnectionConfig rejects empty namespace
When I try to create a ServerConnectionConfig with url "https://ok.com" and empty namespace
Then a server config validation error should be raised
Scenario: ServerConnectionConfig is frozen
When I create a ServerConnectionConfig with url "https://agents.example.com"
Then the config should be immutable
Scenario: ServerConnectionConfig tls_verify defaults to true
When I create a ServerConnectionConfig with url "https://agents.example.com"
Then the config tls_verify should be true
Scenario: ServerConnectionConfig with tls_verify false
When I create a ServerConnectionConfig with url "http://localhost:8080" and tls_verify false
Then the config tls_verify should be false
# -----------------------------------------------------------------------
# StubServerClient — all methods raise NotImplementedError
# -----------------------------------------------------------------------
Scenario: StubServerClient health_check raises NotImplementedError
Given a new StubServerClient
When I call health_check on the stub server client
Then a NotImplementedError should be raised with message containing "not yet implemented"
Scenario: StubServerClient get_version raises NotImplementedError
Given a new StubServerClient
When I call get_version on the stub server client
Then a NotImplementedError should be raised with message containing "not yet implemented"
# -----------------------------------------------------------------------
# StubRemoteExecutionClient — all methods raise NotImplementedError
# -----------------------------------------------------------------------
Scenario: StubRemoteExecutionClient execute_plan raises NotImplementedError
Given a new StubRemoteExecutionClient
When I call execute_plan with plan_id "PLAN001" on the stub execution client
Then a NotImplementedError should be raised with message containing "not yet implemented"
Scenario: StubRemoteExecutionClient get_plan_status raises NotImplementedError
Given a new StubRemoteExecutionClient
When I call get_plan_status with plan_id "PLAN001" on the stub execution client
Then a NotImplementedError should be raised with message containing "not yet implemented"
Scenario: StubRemoteExecutionClient cancel_plan raises NotImplementedError
Given a new StubRemoteExecutionClient
When I call cancel_plan with plan_id "PLAN001" on the stub execution client
Then a NotImplementedError should be raised with message containing "not yet implemented"
Scenario: StubRemoteExecutionClient rejects empty plan_id
Given a new StubRemoteExecutionClient
When I call execute_plan with empty plan_id on the stub execution client
Then a ValueError should be raised for empty plan_id
# -----------------------------------------------------------------------
# StubAuthClient — all methods raise NotImplementedError
# -----------------------------------------------------------------------
Scenario: StubAuthClient authenticate raises NotImplementedError
Given a new StubAuthClient
When I call authenticate with token "tok_test" on the stub auth client
Then a NotImplementedError should be raised with message containing "not yet implemented"
Scenario: StubAuthClient validate_token raises NotImplementedError
Given a new StubAuthClient
When I call validate_token with token "tok_test" on the stub auth client
Then a NotImplementedError should be raised with message containing "not yet implemented"
Scenario: StubAuthClient rejects empty token
Given a new StubAuthClient
When I call authenticate with empty token on the stub auth client
Then a ValueError should be raised for empty token
# -----------------------------------------------------------------------
# Protocol conformance
# -----------------------------------------------------------------------
Scenario: StubServerClient conforms to ServerClient protocol
Then StubServerClient should be a runtime instance of ServerClient
Scenario: StubRemoteExecutionClient conforms to RemoteExecutionClient protocol
Then StubRemoteExecutionClient should be a runtime instance of RemoteExecutionClient
Scenario: StubAuthClient conforms to AuthClient protocol
Then StubAuthClient should be a runtime instance of AuthClient
# -----------------------------------------------------------------------
# Config keys registration
# -----------------------------------------------------------------------
Scenario: server.url config key is registered
Then config key "server.url" should be registered
Scenario: server.token config key is registered
Then config key "server.token" should be registered
Scenario: server.tls-verify config key is registered
Then config key "server.tls-verify" should be registered
Scenario: server.namespace config key is registered
Then config key "server.namespace" should be registered
Scenario: server.tls-verify defaults to true
Then config key "server.tls-verify" should have default true
Scenario: server.namespace defaults to "default"
Then config key "server.namespace" should have default "default"
# -----------------------------------------------------------------------
# Config keys load from environment
# -----------------------------------------------------------------------
Scenario: server.url loads from CLEVERAGENTS_SERVER_URL environment
Then config key "server.url" should use env var "CLEVERAGENTS_SERVER_URL"
Scenario: server.tls-verify loads from CLEVERAGENTS_SERVER_TLS_VERIFY environment
Then config key "server.tls-verify" should use env var "CLEVERAGENTS_SERVER_TLS_VERIFY"
Scenario: server.namespace loads from CLEVERAGENTS_SERVER_NAMESPACE environment
Then config key "server.namespace" should use env var "CLEVERAGENTS_SERVER_NAMESPACE"
# -----------------------------------------------------------------------
# CLI connect command
# -----------------------------------------------------------------------
Scenario: CLI server connect prints stub warning
Given a temporary home directory for server tests
When I invoke server connect with url "https://stub.example.com" and format "json"
Then the server connect output should contain "not yet implemented"
And the server connect output should contain "stubbed"
Scenario: CLI server connect rejects invalid URL
Given a temporary home directory for server tests
When I invoke server connect with url "ftp://bad-url" and format "json"
Then the server connect should exit with non-zero code
# -----------------------------------------------------------------------
# Server mode resolution
# -----------------------------------------------------------------------
Scenario: resolve_server_mode returns disabled when no URL configured
Given no server URL is configured
Then resolve_server_mode should return "disabled"
@@ -159,7 +159,7 @@ def step_verify_catalog_sections(context: Any) -> None:
@then("the registry should contain at least 20 keys")
def step_verify_catalog_count(context: Any) -> None:
# _build_catalog registers ~103 keys from the source
# _build_catalog registers ~105 keys from the source
assert len(_REGISTRY) >= 20, f"Expected >=20 keys, got {len(_REGISTRY)}"
+447
View File
@@ -0,0 +1,447 @@
"""Step definitions for server client stubs Behave scenarios."""
from __future__ import annotations
import os
import tempfile
from behave import given, then, use_step_matcher, when
from behave.runner import Context
from pydantic import ValidationError
from cleveragents.acp.clients import (
AuthClient,
RemoteExecutionClient,
ServerClient,
StubAuthClient,
StubRemoteExecutionClient,
StubServerClient,
)
from cleveragents.acp.server_config import ServerConnectionConfig
from cleveragents.application.services.config_service import _REGISTRY
use_step_matcher("re")
# ---------------------------------------------------------------------------
# ServerConnectionConfig — creation
# ---------------------------------------------------------------------------
@when(r'I create a ServerConnectionConfig with url "(?P<url>[^"]+)"')
def step_create_config(context: Context, url: str) -> None:
context.server_config = ServerConnectionConfig(server_url=url)
@when(
r'I create a ServerConnectionConfig with url "(?P<url>[^"]+)" '
r'and namespace "(?P<ns>[^"]+)"'
)
def step_create_config_ns(context: Context, url: str, ns: str) -> None:
context.server_config = ServerConnectionConfig(server_url=url, namespace=ns)
@when(
r'I create a ServerConnectionConfig with url "(?P<url>[^"]+)" '
r'and auth_token_ref "(?P<ref>[^"]+)"'
)
def step_create_config_auth(context: Context, url: str, ref: str) -> None:
context.server_config = ServerConnectionConfig(server_url=url, auth_token_ref=ref)
@when(
r'I create a ServerConnectionConfig with url "(?P<url>[^"]+)" '
r"and tls_verify false"
)
def step_create_config_tls_false(context: Context, url: str) -> None:
context.server_config = ServerConnectionConfig(server_url=url, tls_verify=False)
# ---------------------------------------------------------------------------
# ServerConnectionConfig — assertions
# ---------------------------------------------------------------------------
@then(r'the config server_url should be "(?P<expected>[^"]+)"')
def step_config_url(context: Context, expected: str) -> None:
assert context.server_config.server_url == expected, (
f"Expected '{expected}', got '{context.server_config.server_url}'"
)
@then(r'the config namespace should be "(?P<expected>[^"]+)"')
def step_config_namespace(context: Context, expected: str) -> None:
assert context.server_config.namespace == expected, (
f"Expected '{expected}', got '{context.server_config.namespace}'"
)
@then(r'the config auth_token_ref should be "(?P<expected>[^"]+)"')
def step_config_auth_ref(context: Context, expected: str) -> None:
assert context.server_config.auth_token_ref == expected, (
f"Expected '{expected}', got '{context.server_config.auth_token_ref}'"
)
@then(r"the config tls_verify should be true")
def step_config_tls_true(context: Context) -> None:
assert context.server_config.tls_verify is True
@then(r"the config tls_verify should be false")
def step_config_tls_false(context: Context) -> None:
assert context.server_config.tls_verify is False
@then(r"the config should be immutable")
def step_config_immutable(context: Context) -> None:
cfg = context.server_config
raised = False
try:
cfg.__setattr__("server_url", "http://changed.example.com")
except (ValidationError, AttributeError, TypeError):
raised = True
assert raised, "Setting attribute on frozen model should have raised an error"
# ---------------------------------------------------------------------------
# ServerConnectionConfig — validation errors
# ---------------------------------------------------------------------------
@when(r"I try to create a ServerConnectionConfig with empty url")
def step_create_config_empty_url(context: Context) -> None:
context.caught_error = None
try:
ServerConnectionConfig(server_url="")
except ValidationError as exc:
context.caught_error = exc
@when(r'I try to create a ServerConnectionConfig with url "(?P<url>[^"]+)"')
def step_create_config_invalid_url(context: Context, url: str) -> None:
context.caught_error = None
try:
ServerConnectionConfig(server_url=url)
except ValidationError as exc:
context.caught_error = exc
@when(
r'I try to create a ServerConnectionConfig with url "(?P<url>[^"]+)" '
r"and empty namespace"
)
def step_create_config_empty_ns(context: Context, url: str) -> None:
context.caught_error = None
try:
ServerConnectionConfig(server_url=url, namespace="")
except ValidationError as exc:
context.caught_error = exc
@then(r"a server config validation error should be raised")
def step_server_config_validation_error(context: Context) -> None:
assert context.caught_error is not None, "Expected a ValidationError"
assert isinstance(context.caught_error, ValidationError), (
f"Expected ValidationError, got {type(context.caught_error)}"
)
# ---------------------------------------------------------------------------
# StubServerClient
# ---------------------------------------------------------------------------
@given(r"a new StubServerClient")
def step_stub_server_client(context: Context) -> None:
context.stub_server = StubServerClient()
@when(r"I call health_check on the stub server client")
def step_call_health_check(context: Context) -> None:
context.caught_error = None
context.ctx_error = None
try:
context.stub_server.health_check()
except NotImplementedError as exc:
context.caught_error = exc
context.ctx_error = exc
@when(r"I call get_version on the stub server client")
def step_call_get_version(context: Context) -> None:
context.caught_error = None
context.ctx_error = None
try:
context.stub_server.get_version()
except NotImplementedError as exc:
context.caught_error = exc
context.ctx_error = exc
# ---------------------------------------------------------------------------
# StubRemoteExecutionClient
# ---------------------------------------------------------------------------
@given(r"a new StubRemoteExecutionClient")
def step_stub_exec_client(context: Context) -> None:
context.stub_exec = StubRemoteExecutionClient()
@when(
r'I call execute_plan with plan_id "(?P<plan_id>[^"]+)" '
r"on the stub execution client"
)
def step_call_execute_plan(context: Context, plan_id: str) -> None:
context.caught_error = None
context.ctx_error = None
try:
context.stub_exec.execute_plan(plan_id)
except NotImplementedError as exc:
context.caught_error = exc
context.ctx_error = exc
@when(
r'I call get_plan_status with plan_id "(?P<plan_id>[^"]+)" '
r"on the stub execution client"
)
def step_call_get_plan_status(context: Context, plan_id: str) -> None:
context.caught_error = None
context.ctx_error = None
try:
context.stub_exec.get_plan_status(plan_id)
except NotImplementedError as exc:
context.caught_error = exc
context.ctx_error = exc
@when(
r'I call cancel_plan with plan_id "(?P<plan_id>[^"]+)" '
r"on the stub execution client"
)
def step_call_cancel_plan(context: Context, plan_id: str) -> None:
context.caught_error = None
context.ctx_error = None
try:
context.stub_exec.cancel_plan(plan_id)
except NotImplementedError as exc:
context.caught_error = exc
context.ctx_error = exc
@when(r"I call execute_plan with empty plan_id on the stub execution client")
def step_call_execute_plan_empty(context: Context) -> None:
context.caught_error = None
try:
context.stub_exec.execute_plan("")
except ValueError as exc:
context.caught_error = exc
# ---------------------------------------------------------------------------
# StubAuthClient
# ---------------------------------------------------------------------------
@given(r"a new StubAuthClient")
def step_stub_auth_client(context: Context) -> None:
context.stub_auth = StubAuthClient()
@when(r'I call authenticate with token "(?P<token>[^"]+)" on the stub auth client')
def step_call_authenticate(context: Context, token: str) -> None:
context.caught_error = None
context.ctx_error = None
try:
context.stub_auth.authenticate(token)
except NotImplementedError as exc:
context.caught_error = exc
context.ctx_error = exc
@when(r'I call validate_token with token "(?P<token>[^"]+)" on the stub auth client')
def step_call_validate_token(context: Context, token: str) -> None:
context.caught_error = None
context.ctx_error = None
try:
context.stub_auth.validate_token(token)
except NotImplementedError as exc:
context.caught_error = exc
context.ctx_error = exc
@when(r"I call authenticate with empty token on the stub auth client")
def step_call_authenticate_empty(context: Context) -> None:
context.caught_error = None
try:
context.stub_auth.authenticate("")
except ValueError as exc:
context.caught_error = exc
# ---------------------------------------------------------------------------
# NotImplementedError and ValueError assertions
# ---------------------------------------------------------------------------
# Note: the step "a NotImplementedError should be raised with message
# containing ..." is already defined in project_context_cli_steps.py
# and looks at context.ctx_error. Our stub steps store errors in
# context.caught_error, so we bridge by also writing to ctx_error
# in each step that catches NotImplementedError.
@then(r"a ValueError should be raised for empty plan_id")
def step_value_error_plan_id(context: Context) -> None:
assert context.caught_error is not None, "Expected ValueError"
assert isinstance(context.caught_error, ValueError), (
f"Expected ValueError, got {type(context.caught_error)}"
)
@then(r"a ValueError should be raised for empty token")
def step_value_error_token(context: Context) -> None:
assert context.caught_error is not None, "Expected ValueError"
assert isinstance(context.caught_error, ValueError), (
f"Expected ValueError, got {type(context.caught_error)}"
)
# ---------------------------------------------------------------------------
# Protocol conformance
# ---------------------------------------------------------------------------
@then(r"StubServerClient should be a runtime instance of ServerClient")
def step_server_protocol(context: Context) -> None:
assert isinstance(StubServerClient(), ServerClient)
@then(
r"StubRemoteExecutionClient should be a runtime instance of RemoteExecutionClient"
)
def step_exec_protocol(context: Context) -> None:
assert isinstance(StubRemoteExecutionClient(), RemoteExecutionClient)
@then(r"StubAuthClient should be a runtime instance of AuthClient")
def step_auth_protocol(context: Context) -> None:
assert isinstance(StubAuthClient(), AuthClient)
# ---------------------------------------------------------------------------
# Config key registration
# ---------------------------------------------------------------------------
@then(r'config key "(?P<key>[^"]+)" should be registered')
def step_config_key_registered(context: Context, key: str) -> None:
assert key in _REGISTRY, f"Config key '{key}' not found in registry"
@then(r'config key "(?P<key>[^"]+)" should have default true')
def step_config_key_default_true(context: Context, key: str) -> None:
entry = _REGISTRY[key]
assert entry.default is True, f"Expected True, got {entry.default}"
@then(r'config key "(?P<key>[^"]+)" should have default "(?P<default>[^"]+)"')
def step_config_key_default_str(context: Context, key: str, default: str) -> None:
entry = _REGISTRY[key]
assert str(entry.default) == default, f"Expected '{default}', got '{entry.default}'"
@then(r'config key "(?P<key>[^"]+)" should use env var "(?P<env_var>[^"]+)"')
def step_config_key_env_var(context: Context, key: str, env_var: str) -> None:
entry = _REGISTRY[key]
assert entry.env_var == env_var, (
f"Expected env var '{env_var}', got '{entry.env_var}'"
)
# ---------------------------------------------------------------------------
# CLI integration (isolated — uses a temporary HOME to avoid config bleed)
# ---------------------------------------------------------------------------
@given(r"a temporary home directory for server tests")
def step_tmp_home_for_server(context: Context) -> None:
"""Set HOME to a fresh temporary directory so config writes are isolated."""
context.server_test_home = tempfile.mkdtemp()
context.server_test_old_home = os.environ.get("HOME")
os.environ["HOME"] = context.server_test_home
@when(
r'I invoke server connect with url "(?P<url>[^"]+)" '
r'and format "(?P<fmt>[^"]+)"'
)
def step_invoke_server_connect(context: Context, url: str, fmt: str) -> None:
"""Run ``server connect <url> --format <fmt>`` and capture output."""
import io
import sys
from cleveragents.cli.main import main as cli_main
captured = io.StringIO()
old_stdout = sys.stdout
sys.stdout = captured
try:
context.server_connect_exit = cli_main(
["server", "connect", url, "--format", fmt]
)
except SystemExit as exc:
context.server_connect_exit = exc.code if exc.code is not None else 0
finally:
sys.stdout = old_stdout
# Restore HOME
old_home = getattr(context, "server_test_old_home", None)
if old_home is not None:
os.environ["HOME"] = old_home
else:
os.environ.pop("HOME", None)
context.server_connect_output = captured.getvalue()
@then(r'the server connect output should contain "(?P<text>[^"]+)"')
def step_server_connect_output_contains(context: Context, text: str) -> None:
assert text in context.server_connect_output, (
f"Expected '{text}' in output: {context.server_connect_output!r}"
)
@then(r"the server connect should exit with non-zero code")
def step_server_connect_non_zero(context: Context) -> None:
assert context.server_connect_exit != 0, (
f"Expected non-zero exit, got {context.server_connect_exit}"
)
# ---------------------------------------------------------------------------
# Server mode resolution
# ---------------------------------------------------------------------------
@given(r"no server URL is configured")
def step_no_server_url(context: Context) -> None:
# Ensure the env var is not set and use a clean temp HOME
os.environ.pop("CLEVERAGENTS_SERVER_URL", None)
context.clean_home = tempfile.mkdtemp()
context.old_home = os.environ.get("HOME")
os.environ["HOME"] = context.clean_home
@then(r'resolve_server_mode should return "(?P<mode>[^"]+)"')
def step_resolve_server_mode(context: Context, mode: str) -> None:
from cleveragents.cli.commands.server import resolve_server_mode
try:
result = resolve_server_mode()
assert result == mode, f"Expected '{mode}', got '{result}'"
finally:
old_home = getattr(context, "old_home", None)
if old_home is not None:
os.environ["HOME"] = old_home
else:
os.environ.pop("HOME", None)
+1 -1
View File
@@ -9,7 +9,7 @@ ${HELPER} ${CURDIR}/helper_config_resolution.py
*** Test Cases ***
Config Service Registers All Spec Keys
[Documentation] Verify that the registry contains exactly 103 keys (102 spec + 1 skills)
[Documentation] Verify that the registry contains exactly 105 keys (102 spec + 1 skills + 2 server stubs)
${result}= Run Process ${PYTHON} ${HELPER} registry-key-count cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
+7 -3
View File
@@ -46,13 +46,17 @@ def _cleanup(tmpdir: Path) -> None:
def registry_key_count() -> None:
"""Verify that the registry contains exactly 103 keys."""
"""Verify that the registry contains exactly 105 keys.
102 spec-defined + 1 skills extension + 2 server stub keys
(server.tls-verify, server.namespace).
"""
registry = ConfigService.registry()
count = len(registry)
if count == 103:
if count == 105:
print("config-registry-key-count-ok")
else:
print(f"FAIL: expected 103 keys, got {count}", file=sys.stderr)
print(f"FAIL: expected 105 keys, got {count}", file=sys.stderr)
sys.exit(1)
+174
View File
@@ -0,0 +1,174 @@
"""Helper script for server_stubs.robot smoke tests.
Each subcommand is a self-contained check that prints a sentinel on success.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
# Ensure local source tree is importable
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from cleveragents.acp.clients import ( # noqa: E402
AuthClient,
RemoteExecutionClient,
ServerClient,
StubAuthClient,
StubRemoteExecutionClient,
StubServerClient,
)
from cleveragents.acp.server_config import ServerConnectionConfig # noqa: E402
from cleveragents.application.services.config_service import _REGISTRY # noqa: E402
# ---------------------------------------------------------------------------
# Subcommands
# ---------------------------------------------------------------------------
def stub_clients() -> None:
"""Verify all stub clients raise NotImplementedError."""
server = StubServerClient()
try:
server.health_check()
print("FAIL: should have raised NotImplementedError", file=sys.stderr)
sys.exit(1)
except NotImplementedError:
pass
try:
server.get_version()
print("FAIL: should have raised NotImplementedError", file=sys.stderr)
sys.exit(1)
except NotImplementedError:
pass
exec_client = StubRemoteExecutionClient()
try:
exec_client.execute_plan("PLAN001")
print("FAIL: should have raised NotImplementedError", file=sys.stderr)
sys.exit(1)
except NotImplementedError:
pass
auth_client = StubAuthClient()
try:
auth_client.authenticate("tok_test")
print("FAIL: should have raised NotImplementedError", file=sys.stderr)
sys.exit(1)
except NotImplementedError:
pass
# Verify protocol conformance
if not isinstance(server, ServerClient):
print("FAIL: StubServerClient not ServerClient", file=sys.stderr)
sys.exit(1)
if not isinstance(exec_client, RemoteExecutionClient):
print(
"FAIL: StubRemoteExecutionClient not RemoteExecutionClient", file=sys.stderr
)
sys.exit(1)
if not isinstance(auth_client, AuthClient):
print("FAIL: StubAuthClient not AuthClient", file=sys.stderr)
sys.exit(1)
print("server-stub-clients-ok")
def config_validation() -> None:
"""Verify ServerConnectionConfig validation."""
# Valid config
cfg = ServerConnectionConfig(server_url="https://agents.example.com")
if cfg.server_url != "https://agents.example.com":
print("FAIL: wrong url", file=sys.stderr)
sys.exit(1)
if cfg.namespace != "default":
print("FAIL: wrong namespace", file=sys.stderr)
sys.exit(1)
if cfg.tls_verify is not True:
print("FAIL: wrong tls_verify", file=sys.stderr)
sys.exit(1)
# Invalid: empty URL
try:
ServerConnectionConfig(server_url="")
print("FAIL: should have rejected empty URL", file=sys.stderr)
sys.exit(1)
except Exception:
pass
# Invalid: non-HTTP URL
try:
ServerConnectionConfig(server_url="ftp://bad.example.com")
print("FAIL: should have rejected ftp URL", file=sys.stderr)
sys.exit(1)
except Exception:
pass
print("server-config-validation-ok")
def config_keys() -> None:
"""Verify server config keys are registered."""
required_keys = [
"server.url",
"server.token",
"server.tls-verify",
"server.namespace",
]
for key in required_keys:
if key not in _REGISTRY:
print(f"FAIL: '{key}' not in registry", file=sys.stderr)
sys.exit(1)
# Check defaults
if _REGISTRY["server.tls-verify"].default is not True:
print("FAIL: server.tls-verify default not True", file=sys.stderr)
sys.exit(1)
if _REGISTRY["server.namespace"].default != "default":
print("FAIL: server.namespace default not 'default'", file=sys.stderr)
sys.exit(1)
print("server-config-keys-ok")
def server_mode() -> None:
"""Verify resolve_server_mode returns disabled."""
# Ensure no server URL is set
os.environ.pop("CLEVERAGENTS_SERVER_URL", None)
from cleveragents.cli.commands.server import resolve_server_mode
mode = resolve_server_mode()
if mode != "disabled":
print(f"FAIL: expected 'disabled', got '{mode}'", file=sys.stderr)
sys.exit(1)
print("server-mode-ok")
# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------
_COMMANDS = {
"stub-clients": stub_clients,
"config-validation": config_validation,
"config-keys": config_keys,
"server-mode": server_mode,
}
def main() -> None:
if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS:
print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr)
sys.exit(2)
_COMMANDS[sys.argv[1]]()
if __name__ == "__main__":
main()
+49
View File
@@ -0,0 +1,49 @@
*** Settings ***
Documentation Smoke tests for server client stubs and connection config
Resource ${CURDIR}/common.resource
Suite Setup Setup Test Environment
Suite Teardown Cleanup Test Environment
*** Variables ***
${HELPER} ${CURDIR}/helper_server_stubs.py
*** Test Cases ***
Server Stub Client NotImplementedError
[Documentation] Verify StubServerClient raises NotImplementedError
${result}= Run Process ${PYTHON} ${HELPER} stub-clients cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} server-stub-clients-ok
Server Connection Config Validation
[Documentation] Verify ServerConnectionConfig validates URLs
${result}= Run Process ${PYTHON} ${HELPER} config-validation cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} server-config-validation-ok
Server Config Keys Registered
[Documentation] Verify server config keys are in the registry
${result}= Run Process ${PYTHON} ${HELPER} config-keys cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} server-config-keys-ok
Server Mode Resolution
[Documentation] Verify resolve_server_mode returns disabled when no URL
${result}= Run Process ${PYTHON} ${HELPER} server-mode cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} server-mode-ok
CLI Server Connect Stub
[Documentation] Verify CLI server connect prints stub warning
${result}= Run Process ${PYTHON} -m cleveragents server connect https://stub.example.com --format json cwd=${WORKSPACE}
Log ${result.stdout}
Log ${result.stderr}
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} not yet implemented
+20
View File
@@ -10,10 +10,22 @@ no network, no authentication.
In **server mode** the :class:`AcpHttpTransport` is a stub that raises
:class:`AcpNotAvailableError` for every operation. When server mode is
implemented the concrete transport will replace these stubs.
Server client protocols (:class:`ServerClient`, :class:`RemoteExecutionClient`,
:class:`AuthClient`) and their stub implementations are provided for forward
compatibility. :class:`ServerConnectionConfig` validates connection parameters.
"""
from __future__ import annotations
from cleveragents.acp.clients import (
AuthClient,
RemoteExecutionClient,
ServerClient,
StubAuthClient,
StubRemoteExecutionClient,
StubServerClient,
)
from cleveragents.acp.errors import (
AcpError,
AcpNotAvailableError,
@@ -29,6 +41,7 @@ from cleveragents.acp.models import (
AcpResponse,
AcpVersion,
)
from cleveragents.acp.server_config import ServerConnectionConfig
from cleveragents.acp.transport import AcpHttpTransport
from cleveragents.acp.versioning import AcpVersionNegotiator
@@ -46,4 +59,11 @@ __all__ = [
"AcpVersion",
"AcpVersionMismatchError",
"AcpVersionNegotiator",
"AuthClient",
"RemoteExecutionClient",
"ServerClient",
"ServerConnectionConfig",
"StubAuthClient",
"StubRemoteExecutionClient",
"StubServerClient",
]
+197
View File
@@ -0,0 +1,197 @@
"""Server client protocol stubs for remote server communication.
Defines three Protocol interfaces (``ServerClient``, ``RemoteExecutionClient``,
``AuthClient``) and their stub implementations that raise
:class:`NotImplementedError`. When server mode is implemented as a separate
project the concrete clients will replace these stubs.
"""
from __future__ import annotations
from typing import Any, Protocol, runtime_checkable
# ---------------------------------------------------------------------------
# Protocol definitions
# ---------------------------------------------------------------------------
@runtime_checkable
class ServerClient(Protocol):
"""Protocol for server health and version negotiation."""
def health_check(self) -> bool:
"""Check server health.
Returns:
``True`` when the server is healthy and reachable.
"""
...
def get_version(self) -> str:
"""Get server version string.
Returns:
The semantic version reported by the server.
"""
...
@runtime_checkable
class RemoteExecutionClient(Protocol):
"""Protocol for remote plan execution."""
def execute_plan(self, plan_id: str) -> dict[str, Any]:
"""Submit a plan for remote execution.
Args:
plan_id: Identifier of the plan to execute.
Returns:
Execution metadata including status and any server-assigned ID.
"""
...
def get_plan_status(self, plan_id: str) -> dict[str, Any]:
"""Query the status of a remotely-executing plan.
Args:
plan_id: Identifier of the plan.
Returns:
Status metadata including phase, progress, and elapsed time.
"""
...
def cancel_plan(self, plan_id: str) -> bool:
"""Request cancellation of a remotely-executing plan.
Args:
plan_id: Identifier of the plan to cancel.
Returns:
``True`` if the cancellation request was accepted.
"""
...
@runtime_checkable
class AuthClient(Protocol):
"""Protocol for authentication with the server."""
def authenticate(self, token: str) -> bool:
"""Authenticate with the server using a bearer token.
Args:
token: Authentication token.
Returns:
``True`` when authentication succeeds.
"""
...
def validate_token(self, token: str) -> bool:
"""Validate that a token is still valid without a full auth flow.
Args:
token: Token to validate.
Returns:
``True`` when the token is valid and not expired.
"""
...
# ---------------------------------------------------------------------------
# Stub implementations — raise NotImplementedError for all operations
# ---------------------------------------------------------------------------
_STUB_MSG = "Server client is not yet implemented"
class StubServerClient:
"""Stub implementation of :class:`ServerClient`.
All methods raise :class:`NotImplementedError`.
"""
def health_check(self) -> bool:
"""Raise :class:`NotImplementedError`."""
raise NotImplementedError(_STUB_MSG)
def get_version(self) -> str:
"""Raise :class:`NotImplementedError`."""
raise NotImplementedError(_STUB_MSG)
class StubRemoteExecutionClient:
"""Stub implementation of :class:`RemoteExecutionClient`.
All methods raise :class:`NotImplementedError`.
"""
def execute_plan(self, plan_id: str) -> dict[str, Any]:
"""Raise :class:`NotImplementedError`.
Args:
plan_id: Identifier of the plan to execute.
"""
if not plan_id or not isinstance(plan_id, str):
raise ValueError("plan_id must be a non-empty string")
raise NotImplementedError(_STUB_MSG)
def get_plan_status(self, plan_id: str) -> dict[str, Any]:
"""Raise :class:`NotImplementedError`.
Args:
plan_id: Identifier of the plan.
"""
if not plan_id or not isinstance(plan_id, str):
raise ValueError("plan_id must be a non-empty string")
raise NotImplementedError(_STUB_MSG)
def cancel_plan(self, plan_id: str) -> bool:
"""Raise :class:`NotImplementedError`.
Args:
plan_id: Identifier of the plan to cancel.
"""
if not plan_id or not isinstance(plan_id, str):
raise ValueError("plan_id must be a non-empty string")
raise NotImplementedError(_STUB_MSG)
class StubAuthClient:
"""Stub implementation of :class:`AuthClient`.
All methods raise :class:`NotImplementedError`.
"""
def authenticate(self, token: str) -> bool:
"""Raise :class:`NotImplementedError`.
Args:
token: Authentication token.
"""
if not token or not isinstance(token, str):
raise ValueError("token must be a non-empty string")
raise NotImplementedError(_STUB_MSG)
def validate_token(self, token: str) -> bool:
"""Raise :class:`NotImplementedError`.
Args:
token: Token to validate.
"""
if not token or not isinstance(token, str):
raise ValueError("token must be a non-empty string")
raise NotImplementedError(_STUB_MSG)
__all__ = [
"AuthClient",
"RemoteExecutionClient",
"ServerClient",
"StubAuthClient",
"StubRemoteExecutionClient",
"StubServerClient",
]
+54
View File
@@ -0,0 +1,54 @@
"""Server connection configuration model.
Provides :class:`ServerConnectionConfig`, a validated Pydantic model for
server connection parameters. In the current stub implementation the model
is used by the CLI ``connect`` command and ``info`` output but no actual
network calls are made.
"""
from __future__ import annotations
from pydantic import BaseModel, ConfigDict, field_validator
class ServerConnectionConfig(BaseModel):
"""Validated configuration for connecting to a CleverAgents server.
Attributes:
server_url: Base URL of the server (must start with ``http://`` or
``https://``).
namespace: Namespace on the server (defaults to ``"default"``).
auth_token_ref: Optional reference to an authentication token
(e.g. environment variable name or secret store key).
tls_verify: Whether to verify TLS certificates (defaults to
``True``).
"""
model_config = ConfigDict(frozen=True, strict=False)
server_url: str
namespace: str = "default"
auth_token_ref: str | None = None
tls_verify: bool = True
@field_validator("server_url")
@classmethod
def _validate_server_url(cls, value: str) -> str:
if not value or not value.strip():
raise ValueError("server_url must not be empty")
stripped = value.strip()
if not stripped.startswith(("http://", "https://")):
raise ValueError("server_url must start with http:// or https://")
return stripped
@field_validator("namespace")
@classmethod
def _validate_namespace(cls, value: str) -> str:
if not value or not value.strip():
raise ValueError("namespace must not be empty")
return value.strip()
__all__ = [
"ServerConnectionConfig",
]
@@ -272,6 +272,24 @@ def _build_catalog() -> None:
env_var="CLEVERAGENTS_SERVER_SYNC_INTERVAL",
description="Seconds between automatic background syncs.",
)
_register(
"server",
"tls-verify",
bool,
True,
project_scopable=False,
env_var="CLEVERAGENTS_SERVER_TLS_VERIFY",
description="Verify TLS certificates when connecting to the server.",
)
_register(
"server",
"namespace",
str,
"default",
project_scopable=False,
env_var="CLEVERAGENTS_SERVER_NAMESPACE",
description="Default namespace on the remote server.",
)
# ── actor.* (5 keys) ────────────────────────────────────────────────
_register(
+214
View File
@@ -0,0 +1,214 @@
"""Server connection CLI commands.
Provides the ``agents server connect`` stub command and server-mode
status helpers. All actual server communication is stubbed out
commands persist configuration and print explicit warnings.
"""
from __future__ import annotations
from pathlib import Path
from typing import Annotated, Any
import typer
from pydantic import ValidationError
from rich.console import Console
from rich.panel import Panel
from cleveragents.acp.server_config import ServerConnectionConfig
from cleveragents.application.services.config_service import ConfigService
from cleveragents.cli.formatting import OutputFormat, format_output
app: Any = typer.Typer(help="Server connection management (stub).")
console: Console = Console()
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
_STUB_WARNING: str = (
"Server connection is not yet implemented. "
"The URL has been saved to configuration but no connection will be made."
)
def _get_config_service() -> ConfigService:
"""Return a ``ConfigService`` wired to the standard config paths.
Paths are computed lazily from ``Path.home()`` so that changes to
``HOME`` (e.g. in tests) are picked up correctly.
"""
config_dir = Path.home() / ".cleveragents"
config_path = config_dir / "config.toml"
return ConfigService(config_dir=config_dir, config_path=config_path)
def resolve_server_mode() -> str:
"""Determine the current server mode.
Returns:
``"disabled"`` when no server URL is configured, ``"stubbed"``
when a URL is present but the client is not yet implemented.
"""
svc = _get_config_service()
try:
resolved = svc.resolve("server.url")
if resolved.value is not None and str(resolved.value).strip():
return "stubbed"
except (ValueError, KeyError):
pass
return "disabled"
# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------
@app.command("connect")
def server_connect(
server_url: Annotated[
str,
typer.Argument(
help="Server URL to connect to (e.g. https://agents.example.com)"
),
],
namespace: Annotated[
str,
typer.Option("--namespace", "-n", help="Namespace on the server"),
] = "default",
tls_verify: Annotated[
bool,
typer.Option("--tls-verify/--no-tls-verify", help="Verify TLS certificates"),
] = True,
fmt: Annotated[
str,
typer.Option("--format", "-f", help="Output format: rich, plain, json, yaml"),
] = "rich",
) -> None:
"""Connect to a CleverAgents server (stub).
Validates and persists the server URL, namespace, and TLS settings
to the global configuration file. No actual connection is made
server mode is not yet implemented.
Examples::
agents server connect https://agents.example.com
agents server connect https://agents.example.com --namespace my-team
agents server connect http://localhost:8080 --no-tls-verify
"""
# Validate the connection config
try:
config = ServerConnectionConfig(
server_url=server_url,
namespace=namespace,
tls_verify=tls_verify,
)
except ValidationError as exc:
console.print(f"[red]Invalid server configuration:[/red] {exc}")
raise typer.Exit(code=1) from exc
# Persist to config file
svc = _get_config_service()
config_data = svc.read_config()
config_data["server.url"] = config.server_url
config_data["server.namespace"] = config.namespace
config_data["server.tls-verify"] = config.tls_verify
svc.write_config(config_data)
result: dict[str, Any] = {
"server_url": config.server_url,
"namespace": config.namespace,
"tls_verify": config.tls_verify,
"status": "stubbed",
"warning": _STUB_WARNING,
}
if fmt != OutputFormat.RICH.value:
typer.echo(format_output(result, fmt))
return
console.print(
Panel(
f"[bold]Server URL:[/bold] {config.server_url}\n"
f"[bold]Namespace:[/bold] {config.namespace}\n"
f"[bold]TLS Verify:[/bold] {config.tls_verify}\n"
f"[bold]Status:[/bold] stubbed\n"
f"\n[yellow]{_STUB_WARNING}[/yellow]",
title="Server Connection (Stub)",
expand=False,
)
)
@app.command("status")
def server_status(
fmt: Annotated[
str,
typer.Option("--format", "-f", help="Output format: rich, plain, json, yaml"),
] = "rich",
) -> None:
"""Show current server connection status.
Displays the configured server URL (if any) and whether server mode
is disabled or stubbed.
"""
mode = resolve_server_mode()
svc = _get_config_service()
server_url: str | None = None
namespace: str = "default"
tls_verify: bool = True
try:
resolved_url = svc.resolve("server.url")
if resolved_url.value is not None:
server_url = str(resolved_url.value)
except (ValueError, KeyError):
pass
try:
resolved_ns = svc.resolve("server.namespace")
if resolved_ns.value is not None:
namespace = str(resolved_ns.value)
except (ValueError, KeyError):
pass
try:
resolved_tls = svc.resolve("server.tls-verify")
if resolved_tls.value is not None:
tls_verify = bool(resolved_tls.value)
except (ValueError, KeyError):
pass
result: dict[str, Any] = {
"server_mode": mode,
"server_url": server_url,
"namespace": namespace,
"tls_verify": tls_verify,
}
if fmt != OutputFormat.RICH.value:
typer.echo(format_output(result, fmt))
return
url_display = server_url if server_url else "(not configured)"
console.print(
Panel(
f"[bold]Server Mode:[/bold] {mode}\n"
f"[bold]Server URL:[/bold] {url_display}\n"
f"[bold]Namespace:[/bold] {namespace}\n"
f"[bold]TLS Verify:[/bold] {tls_verify}",
title="Server Status",
expand=False,
)
)
__all__ = [
"app",
"resolve_server_mode",
"server_connect",
"server_status",
]
+5 -1
View File
@@ -125,12 +125,16 @@ def build_info_data() -> dict[str, Any]:
else:
storage["logs"] = "0 MB"
from cleveragents.cli.commands.server import resolve_server_mode
server_mode = resolve_server_mode()
return {
"version": __version__,
"data_dir": str(data_dir),
"config_path": str(config_path),
"database": db_url,
"server_mode": "disabled",
"server_mode": server_mode,
"platform": f"{platform.system()} {platform.release()} ({platform.machine()})",
"automation": settings.default_automation_profile,
"providers_configured": len(configured_providers),
+7
View File
@@ -96,6 +96,7 @@ def _register_subcommands() -> None:
)
from cleveragents.cli.commands.auto_debug import app as auto_debug_app
from cleveragents.cli.commands.repl import _repl_app
from cleveragents.cli.commands.server import app as server_app
except Exception as exc: # pragma: no cover
import traceback
@@ -188,6 +189,11 @@ def _register_subcommands() -> None:
name="repl",
help="Start an interactive REPL session",
)
app.add_typer(
server_app,
name="server",
help="Server connection management (stub)",
)
_subcommands_registered = True
@@ -595,6 +601,7 @@ def main(args: list[str] | None = None) -> int:
"automation-profile", # Automation profile management
"invariant", # Invariant constraint management
"repl", # Interactive REPL
"server", # Server connection management
"tell", # Shortcut for plan tell
"build", # Shortcut for plan build
"apply", # Shortcut for plan apply
+13
View File
@@ -483,3 +483,16 @@ validation_errors # noqa: B018, F821
get_spawn_decisions # noqa: B018, F821
build_spawn_entries # noqa: B018, F821
validate_spawn # noqa: B018, F821
# Server client stubs — public API for server mode (#201)
ServerClient # noqa: B018, F821
RemoteExecutionClient # noqa: B018, F821
AuthClient # noqa: B018, F821
StubServerClient # noqa: B018, F821
StubRemoteExecutionClient # noqa: B018, F821
StubAuthClient # noqa: B018, F821
ServerConnectionConfig # noqa: B018, F821
resolve_server_mode # noqa: B018, F821
server_connect # noqa: B018, F821
server_status # noqa: B018, F821
_STUB_WARNING # noqa: B018, F821