fix(v3.7.0): resolve issue #1432 #1488
@@ -6,6 +6,12 @@ Changed `wf10_batch.robot` to be less likely to create files, and
|
||||
`plan_generation_graph.robot` to give more test answers.
|
||||
|
||||
## [Unreleased]
|
||||
- **Fix actor compiler to read LSP bindings from typed `lsp_binding` field** (#1488): Fixed
|
||||
`_extract_lsp_bindings()` in `actor/compiler.py` to read from `node.lsp_binding` (the typed
|
||||
`NodeLspBinding` Pydantic field on `NodeDefinition`) as the primary path, with
|
||||
backward-compatible fallback to the legacy `lsp_bindings` config dict key. Per-node LSP
|
||||
bindings specified via the `lsp_binding:` YAML key are no longer silently dropped. Includes
|
||||
Behave BDD and Robot Framework regression tests (#1432).
|
||||
- **TDD regression tests for automation_profile DI bypass** (#1031): Added BDD scenarios
|
||||
and Robot Framework integration tests verifying that ``_get_service()`` in
|
||||
``automation_profile.py`` resolves ``AutomationProfileService`` through the DI container
|
||||
|
||||
@@ -48,4 +48,9 @@ Below are some of the specific details of various contributions.
|
||||
* HAL 9000 has contributed the DecisionService wiring for PlanExecutor strategize persistence fix (#10813): added decision_service to the PlanExecutor constructor and wired it from the CLI dependency-injection container in `_get_plan_executor()`, plus implemented `_persist_strategy_decisions()` to persist strategy decisions as domain `Decision` objects.
|
||||
* HAL 9000 has contributed the A2A module rename standardization BDD tests (PR #10583 / issue #8615): comprehensive Behave test suite validating that all 22 A2A symbols are properly exported from `cleveragents.a2a`, no legacy ACP references remain in the module source, and documentation uses correct A2A naming conventions — fixing inline imports, unused behave symbols, cross-scenario context dependencies, and missing type annotations.
|
||||
* HAL 9000 has contributed the `ActorSelectionOverlay._render` → `_refresh_display` rename fix (PR #11176 / issue #11039, Epic #8174): renamed `_render()` method to `_refresh_display()` to avoid shadowing Textual's `Widget._render()`, fixing a crash in textual >=1.0 where `get_content_height()` would receive `None` and raise `AttributeError: 'NoneType' object has no attribute 'get_height'`.
|
||||
* HAL 9000 has contributed the actor compiler LSP binding fix (PR #1488 / issue #1432): fixed
|
||||
`_extract_lsp_bindings()` to read `node.lsp_binding` (the typed `NodeLspBinding` field on
|
||||
`NodeDefinition`) as the primary extraction path instead of only checking the untyped config
|
||||
dict, so per-node LSP bindings specified via `lsp_binding:` YAML key are no longer silently
|
||||
dropped. Includes Behave BDD and Robot Framework regression tests.
|
||||
* HAL 9000 has contributed the config-actor combined-format support fix (PR #11232 / issue #11189): added ``_detect_nested_config_actor()``, ``_flatten_config_actor()``, and handling in ``ActorConfiguration.from_blob()`` to transparently flatten the nested ``config.actor`` block from both compact-string and nested-dict forms so v3 detection, schema validation, and canonicalisation see flat data — eliminating the ``"provider is required"`` crash.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
Feature: Actor compiler reads LSP bindings from NodeDefinition.lsp_binding typed field
|
||||
As a CleverAgents developer
|
||||
I want the actor compiler to read LSP bindings from the typed lsp_binding field
|
||||
So that per-node LSP bindings are not silently dropped when specified via lsp_binding: YAML key
|
||||
|
||||
Background:
|
||||
Given the actor compiler is available
|
||||
|
||||
@tdd_issue @tdd_issue_1432
|
||||
Scenario: Node with lsp_binding typed field populates CompilationMetadata.lsp_bindings
|
||||
Given a GRAPH actor config with a node using the lsp_binding typed field
|
||||
When I compile the actor config
|
||||
Then the compilation should succeed
|
||||
And the compilation metadata should have 1 LSP binding
|
||||
And the LSP binding should reference server "local/pyright"
|
||||
|
||||
@tdd_issue @tdd_issue_1432
|
||||
Scenario: Node without lsp_binding field produces empty lsp_bindings in metadata
|
||||
Given a GRAPH actor config with a single node
|
||||
When I compile the actor config
|
||||
Then the compilation should succeed
|
||||
And the compilation metadata should have 0 LSP bindings
|
||||
@@ -23,6 +23,7 @@ from cleveragents.actor.schema import (
|
||||
ActorType,
|
||||
EdgeDefinition,
|
||||
NodeDefinition,
|
||||
NodeLspBinding,
|
||||
NodeType,
|
||||
RouteDefinition,
|
||||
)
|
||||
@@ -133,6 +134,27 @@ def step_given_lsp_bindings(context: Context) -> None:
|
||||
)
|
||||
|
||||
|
||||
@given("a GRAPH actor config with a node using the lsp_binding typed field")
|
||||
def step_given_lsp_typed_field(context: Context) -> None:
|
||||
nodes = [
|
||||
NodeDefinition(
|
||||
id="coder",
|
||||
type=NodeType.AGENT,
|
||||
name="Coder",
|
||||
description="Writes code with LSP support",
|
||||
config={"agent": "coder_agent"},
|
||||
lsp_binding=NodeLspBinding(
|
||||
server="local/pyright",
|
||||
languages=["python"],
|
||||
auto=False,
|
||||
),
|
||||
),
|
||||
]
|
||||
context.actor_config = _build_graph_config(
|
||||
"workflows/lsp_typed", nodes, [], "coder", ["coder"]
|
||||
)
|
||||
|
||||
|
||||
@given("a GRAPH actor config with a conditional routing node")
|
||||
def step_given_conditional(context: Context) -> None:
|
||||
nodes = [
|
||||
|
||||
@@ -54,3 +54,16 @@ Detect Cross-Actor Subgraph Cycle Via actor_ref Field
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} actor-compiler-cycle-detected
|
||||
|
||||
Extract LSP Binding From Typed lsp_binding Field
|
||||
[Documentation] Verify that a node with a typed lsp_binding field populates
|
||||
... CompilationMetadata.lsp_bindings — regression test for issue #1432:
|
||||
... _extract_lsp_bindings() was reading only node.config.get("lsp_bindings")
|
||||
... instead of the NodeLspBinding typed field on NodeDefinition.
|
||||
[Tags] slow
|
||||
${result}= Run Process ${PYTHON} ${HELPER} lsp-binding-field cwd=${WORKSPACE}
|
||||
Log ${result.stdout}
|
||||
Log ${result.stderr}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} actor-compiler-lsp-binding-ok
|
||||
Should Contain ${result.stdout} lsp-server: local/pyright
|
||||
|
||||
@@ -142,6 +142,55 @@ def main() -> int:
|
||||
print(f"actor-compiler-unexpected-error: {exc}")
|
||||
return 1
|
||||
|
||||
if command == "lsp-binding-field":
|
||||
# Regression test for issue #1432: _extract_lsp_bindings() must read
|
||||
# from node.lsp_binding (NodeLspBinding typed field), not only config dict.
|
||||
from cleveragents.actor.schema import (
|
||||
ActorType,
|
||||
NodeDefinition,
|
||||
NodeLspBinding,
|
||||
NodeType,
|
||||
RouteDefinition,
|
||||
)
|
||||
|
||||
node = NodeDefinition(
|
||||
id="coder",
|
||||
type=NodeType.AGENT,
|
||||
name="Coder",
|
||||
description="Code writing node with typed LSP binding",
|
||||
config={"agent": "coder_agent"},
|
||||
lsp_binding=NodeLspBinding(
|
||||
server="local/pyright",
|
||||
languages=["python"],
|
||||
auto=False,
|
||||
),
|
||||
)
|
||||
route = RouteDefinition(
|
||||
nodes=[node],
|
||||
edges=[],
|
||||
entry_node="coder",
|
||||
exit_nodes=["coder"],
|
||||
)
|
||||
config = ActorConfigSchema(
|
||||
name="workflows/lsp_typed",
|
||||
type=ActorType.GRAPH,
|
||||
description="Test actor with typed lsp_binding field",
|
||||
model="gpt-4",
|
||||
route=route,
|
||||
)
|
||||
try:
|
||||
compiled = compile_actor(config)
|
||||
bindings = compiled.metadata.lsp_bindings
|
||||
if len(bindings) == 1 and bindings[0].lsp_server_name == "local/pyright":
|
||||
print("actor-compiler-lsp-binding-ok")
|
||||
print(f"lsp-server: {bindings[0].lsp_server_name}")
|
||||
return 0
|
||||
print(f"actor-compiler-lsp-binding-fail: got {bindings}")
|
||||
return 1
|
||||
except Exception as exc:
|
||||
print(f"actor-compiler-fail: {exc}")
|
||||
return 1
|
||||
|
||||
print(f"Unknown command: {command}")
|
||||
return 1
|
||||
|
||||
|
||||
@@ -156,11 +156,36 @@ def _map_edge(edge: EdgeDefinition) -> lg_nodes.Edge:
|
||||
|
||||
|
||||
def _extract_lsp_bindings(node: NodeDefinition) -> list[LspBinding]:
|
||||
|
|
||||
"""Extract LSP bindings from a node config block."""
|
||||
"""Extract LSP bindings from a node's dedicated lsp_binding field.
|
||||
|
||||
Reads from the typed ``node.lsp_binding`` (NodeLspBinding) field and
|
||||
converts to LspBinding records. Also checks config dict for backward
|
||||
compatibility with legacy ``lsp_bindings`` config key.
|
||||
|
||||
Args:
|
||||
node: The node definition to extract bindings from.
|
||||
|
||||
Returns:
|
||||
List of LspBinding records for this node.
|
||||
"""
|
||||
|
HAL9001
commented
Suggestion: add Robot Framework integration test verifying CompilationMetadata.lsp_bindings population. Required by subtask 4. Suggestion: add Robot Framework integration test verifying CompilationMetadata.lsp_bindings population. Required by subtask 4.
|
||||
bindings: list[LspBinding] = []
|
||||
|
||||
# Primary path: read from the dedicated typed field (NodeLspBinding)
|
||||
if node.lsp_binding is not None:
|
||||
bindings.append(
|
||||
LspBinding(
|
||||
node_name=node.id,
|
||||
lsp_server_name=node.lsp_binding.server or "",
|
||||
languages=node.lsp_binding.languages or [],
|
||||
auto_detect=node.lsp_binding.auto,
|
||||
)
|
||||
)
|
||||
|
||||
# Fallback path: check config dict for backward compatibility
|
||||
raw_bindings = node.config.get("lsp_bindings", [])
|
||||
if not isinstance(raw_bindings, list):
|
||||
return bindings
|
||||
|
||||
for entry in raw_bindings:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
@@ -175,6 +200,7 @@ def _extract_lsp_bindings(node: NodeDefinition) -> list[LspBinding]:
|
||||
auto_detect=entry.get("auto_detect", True),
|
||||
)
|
||||
)
|
||||
|
||||
return bindings
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user
test
Suggestion: add @tdd_issue_1432 Behave regression test for per-node LSP binding extraction. Required by subtask 3.