3 Commits

Author SHA1 Message Date
CoreRasurae c8d35fe9e6 test(agents): capture inline sandbox __import__ escape regression (#107)
CI / lint (push) Successful in 49s
CI / typecheck (push) Successful in 1m17s
CI / security (push) Successful in 1m56s
CI / quality (push) Successful in 56s
CI / build (push) Successful in 1m40s
CI / integration_tests (push) Successful in 2m38s
CI / unit_tests (push) Successful in 4m41s
CI / coverage (push) Failing after 18m6s
CI / benchmark (push) Failing after 22m13s
CI / status-check (push) Failing after 11s
Adds a failing-first Behave regression test proving issue #107:
ToolAgent._execute_python_code (cleveractors.agents.tool) builds the
inline-code sandbox's __builtins__ dict per docs/index.md §13.2.1's
"Restricted Built-ins for Inline Code" table, which lists a single
module-shaped facility (json) and states the table is exhaustive. §13.2.3
additionally prohibits "dynamic import of modules other than those
explicitly listed". Despite this, safe_globals["__builtins__"] binds
"__import__" directly to the real, unrestricted __import__ builtin, so
inline code can write `import os` and reach exactly the filesystem/
network/process facilities the standard was written to keep out.

Two Scenario Outlines drive the two documented entry points that share
_execute_python_code -- a "type: tool" agent's inline `code:` body, and
the exec_python-gated `python_exec` built-in tool -- through the public
ToolAgent.process_message() API, each exercised against three dynamic-
import forms that all resolve through the same unrestricted __import__
builtin: `import os`, `from os import getcwd`, and `__import__('os')`.
Every case asserts an ExecutionError is raised instead of the import
succeeding. Confirmed all assertions fail via AssertionError when
@tdd_expected_fail is removed (os.getcwd() returns a real path today),
and pass via TddExpectedFailPolicy's inversion with the tag present.

All scenarios are tagged @tdd_issue, @tdd_issue_107, and
@tdd_expected_fail per the TDD issue-capture workflow.

The actual fix (a restricted __import__ shim permitting only `json`)
lands separately on bugfix/m1-inline-sandbox-import-restriction per
issue #107.

Refs: #107, #108
2026-08-06 14:31:11 +00:00
hurui200320 9ec578f229 feat(agents): add tool_agent_class parameter to AgentFactory and create_executor
CI / lint (pull_request) Successful in 44s
CI / typecheck (pull_request) Successful in 49s
CI / security (pull_request) Successful in 47s
CI / quality (pull_request) Successful in 33s
CI / unit_tests (pull_request) Successful in 3m4s
CI / integration_tests (pull_request) Successful in 1m2s
CI / build (pull_request) Successful in 33s
CI / coverage (pull_request) Successful in 3m5s
CI / status-check (pull_request) Successful in 2s
CI / lint (push) Successful in 31s
CI / typecheck (push) Successful in 46s
CI / security (push) Successful in 46s
CI / quality (push) Successful in 31s
CI / unit_tests (push) Successful in 3m2s
CI / integration_tests (push) Successful in 1m2s
CI / build (push) Successful in 31s
CI / coverage (push) Successful in 3m4s
CI / status-check (push) Successful in 6s
Extends AgentFactory.__init__ and create_executor() with a keyword-only
tool_agent_class: type[ToolAgent] = ToolAgent parameter so callers can
inject a custom ToolAgent subclass at runtime without monkey-patching
module globals (resolves the five-module setattr loop in cleveragents-webapp).

**AgentFactory** (agents/factory.py):
- Accepts tool_agent_class keyword argument; validates it is a ToolAgent
  subclass at construction time (fail-fast, CONTRIBUTING.md §Argument
  Validation).
- Stores as self._tool_agent_class and uses it to populate
  self.agent_types['tool'], so every create_agent('tool') call
  instantiates the supplied subclass.
- Forwards tool_agent_class to LLMAgent when creating 'llm'-typed agents
  so the LLM tool-calling loop honours the injected subclass.
- Default ToolAgent is preserved, so all existing callers are unaffected.

**LLMAgent** (agents/llm.py):
- Accepts a keyword-only tool_agent_class: type[ToolAgent] = ToolAgent
  argument, validated as a ToolAgent subclass at construction time.
- The multi-turn tool-call loop (_execute_tool_loop) constructs its
  ephemeral tool executors via self._tool_agent_class(...) across all
  three dispatch sites (regular loop, budget-exhaustion synthesis round,
  and stuck-model synthesis round), so the LLM tool-calling path no
  longer falls back to the module-level ToolAgent. This closes the gap
  that previously prevented removing the webapp monkey-patch for the LLM
  tool-calling path.

**Executor / create_executor** (runtime.py):
- Executor.__init__ accepts and validates tool_agent_class; stores as
  self.tool_agent_class.
- create_executor() accepts tool_agent_class and forwards it to Executor.

**Dispatch paths** (runtime_dispatch.py):
- _execute_tool: uses executor.tool_agent_class(...) instead of the
  module-level ToolAgent(...) so the direct-construction path (bypassing
  AgentFactory) also honours the injected subclass.
- _execute_graph and _execute_graph_stream: pass
  tool_agent_class=executor.tool_agent_class to AgentFactory so graph
  nodes using tool-typed agents pick up the subclass.
- _execute_llm and _execute_llm_stream: pass
  tool_agent_class=executor.tool_agent_class to AgentFactory so the
  single-LLM actor's internally-created LLMAgent (and its tool-calling
  loop) honours the injected subclass. Without this, a single LLM actor
  that makes tool calls silently fell back to the base ToolAgent.
- _execute_multi_actor: passes tool_agent_class=executor.tool_agent_class
  to the sub-Executor so every actor inside a multi-actor bundle (tool,
  graph, or LLM) honours the injected subclass. Without this, all
  dispatch paths reachable through a multi-actor bundle fell back to the
  base ToolAgent.
- Removed the now-unused module-level ToolAgent import; the _execute_tool
  docstring cross-reference is fully qualified so it still resolves.
  All six dispatch paths that construct or dispatch tool agents now
  forward the injected subclass.

**ReactiveCleverAgentsApp** (core/application.py):
- Removed the redundant register_agent_type('tool', ToolAgent) call from
  load_configuration and the dead type_map re-registration block in
  _create_agents; AgentFactory.__init__ already owns the 'tool'
  registration. This drops the last hardcoded module-level ToolAgent
  references flagged in the issue #73 audit.

**Tests** (features/tool_agent_class_injection.feature + steps):
- 17 BDD scenarios covering: default class when parameter omitted;
  custom subclass stored on factory and executor; invalid class rejected
  with ConfigurationError (factory, executor, and LLMAgent); factory
  create_agent returns custom instance; _execute_tool dispatch
  instantiates custom subclass; the LLM tool loop instantiates the
  injected subclass; isinstance LSP compatibility through the factory;
  the _execute_llm dispatch path forwarding tool_agent_class to
  AgentFactory (single-LLM actor making a tool call); and the
  _execute_multi_actor dispatch path forwarding tool_agent_class to the
  sub-Executor. The two new dispatch-path scenarios were verified to
  fail without the source fixes (instantiation_count=0), confirming
  they guard the injection contract for the single-LLM and multi-actor
  paths.

**Updated tests** (features/steps/runtime_coverage_steps.py,
features/steps/runtime_extended_coverage_steps.py):
- Removed the now-dead patch('cleveractors.runtime_dispatch.ToolAgent')
  mocks (and their mock instances) that no longer intercept _execute_tool
  after it switched to executor.tool_agent_class. The tool-actor scenario
  continues to pass via the real ToolAgent; the normal-coverage branch
  uses patch.object(ToolAgent, 'process_message', ...) to stub tool
  execution.

**Updated test** (features/steps/credential_executor_paths_steps.py):
- The _execute_multi_actor credentials-forwarding scenario intercepts
  Executor.__init__ with a capturing_init to assert the credentials
  dict reaches the sub-Executor. Now that _execute_multi_actor forwards
  tool_agent_class to the sub-Executor, capturing_init accepts and
  forwards that keyword so the assertion continues to pass.

**Removed obsolete test** (features/application_coverage_gaps.feature):
- Dropped the '_create_agents registers built-in agent types' scenario
  and its step definitions; the behaviour it asserted (redundant
  re-registration) was removed from _create_agents.

ISSUES CLOSED: #73
2026-07-08 09:23:34 +00:00
CoreRasurae 9753b31c7e test: add coverage gap tests improving coverage from 81.4% to 96.90%
CI / quality (push) Successful in 36s
CI / lint (push) Successful in 38s
CI / typecheck (push) Successful in 49s
CI / security (push) Successful in 51s
CI / integration_tests (push) Successful in 55s
CI / unit_tests (push) Successful in 3m35s
CI / build (push) Successful in 33s
CI / coverage (push) Successful in 3m36s
CI / status-check (push) Successful in 3s
Add 13 BDD scenarios covering previously uncovered code paths:
- SAFE_BUILTINS validation (sandbox.py: 0% → 100%)
- ConfigurationError re-raise path (config.py: 99.3% → 100%)
- CLI hello/main functions (cli.py: 72.7% → 90.9%)
- GraphState message truncation (state.py: 98.7% → 100%)
- ProgressBarManager update/context rendering (progress.py: 0% → 87.7%)
- MessageRouter regex/exact/contains routing (message_router.py: 0% → 59.4%)
- RoutingAdapter parse_routing_command (routing_adapter.py)
- DynamicRouterNode pattern-based routing (dynamic_router.py)
- EnhancedTemplateRegistry unknown template type (enhanced_registry.py: 99.2%)
- CompositeAgent null-graph error path (composite.py: 97.8% → 98.6%)

Cover routing_adapter.py (17% → 100%): all GOTO/ROUTE patterns,
create_routing_node, create_conditional_router, dynamic config conversion.

Cover dynamic_router.py (21% → 87%): execute with empty/dict/string
messages, extract_message with colon parsing, config creation, graph
extension with edge generation.

Cover message_router.py (59% → 78%): regex, exact, contains, prefix,
suffix match types, invalid regex handling, non-string message, set_state.

Exercise Node._prepare_conversation_history with invalid configs,
_runtime error paths, _execute_message_router with rules,
_execute_agent with current_message and metadata propagation,
_execute_function with dynamic_router, and _execute_conditional
with content_contains/content_not_contains/content_starts_with
and custom condition types. Improves nodes.py from 71.3% to 72.5%.

Exercise PureGraphConfig, PureLangGraph init with dict/config,
RxPyLangGraphBridge registration/connection/lookup,
ReactiveStreamRouter operator creation and condition functions,
ReactiveConfigParser config/route/graph parsing,
ToolAgent tool execution with JSON, space-separated, single,
file_read, progress_bar invocations, and
ReactiveCleverAgentsApp init/dispose/visualization.
2026-06-02 20:02:01 +01:00