diff --git a/src/felix_agent_sdk/_version.py b/src/felix_agent_sdk/_version.py index d3ec452..3ced358 100644 --- a/src/felix_agent_sdk/_version.py +++ b/src/felix_agent_sdk/_version.py @@ -1 +1 @@ -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/src/felix_agent_sdk/visualization/helix_visualizer.py b/src/felix_agent_sdk/visualization/helix_visualizer.py index 6ed7963..c45f13c 100644 --- a/src/felix_agent_sdk/visualization/helix_visualizer.py +++ b/src/felix_agent_sdk/visualization/helix_visualizer.py @@ -286,8 +286,21 @@ def render( day: Current simulation day for the header. extra_info: Arbitrary key-value pairs shown in the header. """ - sys.stdout.write(clear_screen()) - sys.stdout.write(self.render_to_string(tick=tick, day=day, extra_info=extra_info)) + # Ensure stdout can handle Unicode block-drawing characters on Windows + # where the default encoding is cp1252. + if hasattr(sys.stdout, "reconfigure"): + try: + sys.stdout.reconfigure(encoding="utf-8") + except (AttributeError, OSError): + pass # already reconfigured, unsupported, or redirected stream + + output = clear_screen() + self.render_to_string( + tick=tick, day=day, extra_info=extra_info + ) + try: + sys.stdout.write(output) + except UnicodeEncodeError: + sys.stdout.buffer.write(output.encode("utf-8")) sys.stdout.flush() def render_to_string( diff --git a/tests/conftest.py b/tests/conftest.py index 1aaac09..4de71c3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,7 +17,6 @@ CompletionResult, MessageRole, ProviderConfig, - StreamChunk, ) diff --git a/tests/integration/test_providers.py b/tests/integration/test_providers.py index 1e8f4f9..96bf7db 100644 --- a/tests/integration/test_providers.py +++ b/tests/integration/test_providers.py @@ -20,7 +20,7 @@ import pytest -from felix_agent_sdk.providers.types import ChatMessage, CompletionResult, MessageRole, StreamChunk +from felix_agent_sdk.providers.types import ChatMessage, CompletionResult, MessageRole # --------------------------------------------------------------------------- # Skip conditions diff --git a/tests/integration/test_workflow_pipeline.py b/tests/integration/test_workflow_pipeline.py index 5919e00..5b9acf3 100644 --- a/tests/integration/test_workflow_pipeline.py +++ b/tests/integration/test_workflow_pipeline.py @@ -9,7 +9,6 @@ from unittest.mock import MagicMock -import pytest from felix_agent_sdk.providers.base import BaseProvider from felix_agent_sdk.providers.types import CompletionResult @@ -157,7 +156,7 @@ def test_hub_shutdown(self): ) workflow = FelixWorkflow(config, provider) - result = workflow.run("Test cleanup") + workflow.run("Test cleanup") # If cleanup failed we'd get errors on subsequent runs result2 = workflow.run("Second run") diff --git a/tests/unit/test_anthropic_provider.py b/tests/unit/test_anthropic_provider.py index dd76ef3..6fa5528 100644 --- a/tests/unit/test_anthropic_provider.py +++ b/tests/unit/test_anthropic_provider.py @@ -2,8 +2,6 @@ from __future__ import annotations -from contextlib import contextmanager -from typing import Any from unittest.mock import MagicMock, patch import pytest @@ -20,7 +18,6 @@ ChatMessage, CompletionResult, MessageRole, - StreamChunk, ) diff --git a/tests/unit/test_central_post.py b/tests/unit/test_central_post.py index 618f3bc..6a3caf2 100644 --- a/tests/unit/test_central_post.py +++ b/tests/unit/test_central_post.py @@ -398,7 +398,8 @@ def test_multiple_callbacks_for_same_event(self, central_post): def test_remove_lifecycle_callback(self, central_post): log = [] - cb = lambda aid: log.append(aid) + def cb(aid): + return log.append(aid) central_post.add_lifecycle_callback(AgentLifecycleEvent.FAILED, cb) central_post.remove_lifecycle_callback(AgentLifecycleEvent.FAILED, cb) central_post.emit_lifecycle_event(AgentLifecycleEvent.FAILED, "agent-1") diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index ee639a1..fb88f02 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -2,8 +2,6 @@ from __future__ import annotations -import os -import tempfile from pathlib import Path import pytest diff --git a/tests/unit/test_compression.py b/tests/unit/test_compression.py index 77df80e..e348d8a 100644 --- a/tests/unit/test_compression.py +++ b/tests/unit/test_compression.py @@ -2,7 +2,6 @@ from __future__ import annotations -import json import pytest diff --git a/tests/unit/test_context_builder.py b/tests/unit/test_context_builder.py index 6316273..8975c4b 100644 --- a/tests/unit/test_context_builder.py +++ b/tests/unit/test_context_builder.py @@ -2,7 +2,6 @@ from __future__ import annotations -import pytest from felix_agent_sdk.workflows.context_builder import ( CollaborativeContextBuilder, diff --git a/tests/unit/test_dynamic_spawner.py b/tests/unit/test_dynamic_spawner.py index f1d58dc..6198593 100644 --- a/tests/unit/test_dynamic_spawner.py +++ b/tests/unit/test_dynamic_spawner.py @@ -9,7 +9,7 @@ from felix_agent_sdk.communication.central_post import CentralPost from felix_agent_sdk.communication.spoke import SpokeManager from felix_agent_sdk.core.helix import HelixConfig -from felix_agent_sdk.events import EventBus, EventType +from felix_agent_sdk.events import EventBus from felix_agent_sdk.providers.base import BaseProvider from felix_agent_sdk.providers.types import CompletionResult from felix_agent_sdk.spawning import ConfidenceMonitor, DynamicSpawner diff --git a/tests/unit/test_events.py b/tests/unit/test_events.py index 6d5a719..496062c 100644 --- a/tests/unit/test_events.py +++ b/tests/unit/test_events.py @@ -3,7 +3,6 @@ from __future__ import annotations import threading -import time import pytest diff --git a/tests/unit/test_helix_visualizer.py b/tests/unit/test_helix_visualizer.py index fa742eb..d5939bf 100644 --- a/tests/unit/test_helix_visualizer.py +++ b/tests/unit/test_helix_visualizer.py @@ -317,3 +317,39 @@ def test_footer_without_monitor(self, viz): assert "Confidence" in output # Should not crash without monitor assert "Trend" not in output + + +# ------------------------------------------------------------------ +# Windows encoding fallback +# ------------------------------------------------------------------ + + +class TestRenderEncodingFallback: + def test_render_survives_unicode_encode_error(self, viz, monkeypatch): + """render() should not crash when stdout can't handle Unicode.""" + import io + + viz.register_agent("a1", "Agent1", color="cyan") + viz.update("a1", 0.5, 0.8) + + # Simulate a stdout that raises UnicodeEncodeError on write + class FakeStdout: + def write(self, s): + raise UnicodeEncodeError("cp1252", s, 0, 1, "bad char") + + def flush(self): + ... # intentionally empty — test double + + buffer = io.BytesIO() + + def reconfigure(self, **kwargs): + ... # intentionally empty — simulates already-reconfigured + + fake = FakeStdout() + monkeypatch.setattr("sys.stdout", fake) + + # Should not raise — falls back to buffer.write(utf-8) + viz.render() + + # Verify something was written to the buffer + assert fake.buffer.tell() > 0 diff --git a/tests/unit/test_knowledge_store.py b/tests/unit/test_knowledge_store.py index 0788eb3..f592516 100644 --- a/tests/unit/test_knowledge_store.py +++ b/tests/unit/test_knowledge_store.py @@ -2,7 +2,6 @@ from __future__ import annotations -import time import pytest @@ -10,7 +9,6 @@ ConfidenceLevel, KnowledgeEntry, KnowledgeQuery, - KnowledgeStore, KnowledgeType, ) diff --git a/tests/unit/test_llm_agent_streaming.py b/tests/unit/test_llm_agent_streaming.py index fa516fe..96951d9 100644 --- a/tests/unit/test_llm_agent_streaming.py +++ b/tests/unit/test_llm_agent_streaming.py @@ -6,7 +6,7 @@ from felix_agent_sdk.agents.llm_agent import LLMAgent, LLMTask from felix_agent_sdk.core.helix import HelixConfig, HelixGeometry -from felix_agent_sdk.events import EventBus, EventType +from felix_agent_sdk.events import EventBus from felix_agent_sdk.providers.base import BaseProvider from felix_agent_sdk.providers.types import CompletionResult, StreamChunk from felix_agent_sdk.streaming import CallbackStreamHandler, StreamHandler diff --git a/tests/unit/test_local_provider.py b/tests/unit/test_local_provider.py index 46937c5..c33e05f 100644 --- a/tests/unit/test_local_provider.py +++ b/tests/unit/test_local_provider.py @@ -4,7 +4,6 @@ from unittest.mock import MagicMock, patch -import pytest from felix_agent_sdk.providers.local import LocalProvider from felix_agent_sdk.providers.openai_provider import OpenAIProvider diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index 1ac70f8..b3642bc 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -5,7 +5,6 @@ import json import logging -import pytest from felix_agent_sdk.events import EventBus, EventType, FelixEvent from felix_agent_sdk.utils.logging import ( @@ -25,7 +24,6 @@ def _capture_handler() -> logging.Handler: """Return a handler that stores formatted records in handler.records.""" handler = logging.StreamHandler() handler.records: list[str] = [] # type: ignore[attr-defined] - original_emit = handler.emit def capturing_emit(record: logging.LogRecord) -> None: handler.records.append(handler.format(record)) # type: ignore[attr-defined] @@ -169,7 +167,7 @@ def test_logs_events(self): configure_logging(FelixLogConfig(level="DEBUG", output_handler=handler)) bus = EventBus() - bridge = EventLogBridge(bus) + EventLogBridge(bus) bus.emit(FelixEvent( event_type=EventType.WORKFLOW_STARTED, @@ -184,7 +182,7 @@ def test_json_bridge(self): configure_logging(FelixLogConfig(level="DEBUG", format="json", output_handler=handler)) bus = EventBus() - bridge = EventLogBridge(bus) + EventLogBridge(bus) bus.emit(FelixEvent( event_type=EventType.TASK_COMPLETED, @@ -215,7 +213,7 @@ def test_custom_level_map(self): configure_logging(FelixLogConfig(level="DEBUG", output_handler=handler)) bus = EventBus() - bridge = EventLogBridge(bus, level_map={"custom.event": "WARNING"}) + EventLogBridge(bus, level_map={"custom.event": "WARNING"}) bus.emit(FelixEvent(event_type="custom.event", source="test")) @@ -226,7 +224,7 @@ def test_default_level_is_debug(self): configure_logging(FelixLogConfig(level="DEBUG", output_handler=handler)) bus = EventBus() - bridge = EventLogBridge(bus) + EventLogBridge(bus) bus.emit(FelixEvent(event_type="unknown.event.type", source="test")) @@ -237,7 +235,7 @@ def test_workflow_events_log_at_info(self): configure_logging(FelixLogConfig(level="DEBUG", output_handler=handler)) bus = EventBus() - bridge = EventLogBridge(bus) + EventLogBridge(bus) bus.emit(FelixEvent(event_type=EventType.WORKFLOW_STARTED, source="wf")) bus.emit(FelixEvent(event_type=EventType.WORKFLOW_COMPLETED, source="wf")) diff --git a/tests/unit/test_messages.py b/tests/unit/test_messages.py index 13636a0..a96d7ab 100644 --- a/tests/unit/test_messages.py +++ b/tests/unit/test_messages.py @@ -5,7 +5,6 @@ import time import uuid -import pytest from felix_agent_sdk.communication.messages import Message, MessageType diff --git a/tests/unit/test_openai_provider.py b/tests/unit/test_openai_provider.py index dc74fc3..2bdb47c 100644 --- a/tests/unit/test_openai_provider.py +++ b/tests/unit/test_openai_provider.py @@ -18,7 +18,6 @@ ChatMessage, CompletionResult, MessageRole, - StreamChunk, ) diff --git a/tests/unit/test_registry.py b/tests/unit/test_registry.py index ba761cd..1cd4af5 100644 --- a/tests/unit/test_registry.py +++ b/tests/unit/test_registry.py @@ -2,7 +2,7 @@ from __future__ import annotations -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest diff --git a/tests/unit/test_spoke.py b/tests/unit/test_spoke.py index cb709a0..277f06f 100644 --- a/tests/unit/test_spoke.py +++ b/tests/unit/test_spoke.py @@ -4,7 +4,6 @@ from unittest.mock import MagicMock -import pytest from felix_agent_sdk.communication.central_post import CentralPost from felix_agent_sdk.communication.messages import Message, MessageType @@ -384,7 +383,7 @@ def test_broadcast_excludes_specified_ids(self, central_post, spoke_manager): assert len(confirmations) == 2 def test_broadcast_skips_disconnected_spokes(self, central_post, spoke_manager): - spoke1 = spoke_manager.create_spoke("agent-1") + spoke_manager.create_spoke("agent-1") spoke2 = spoke_manager.create_spoke("agent-2") spoke2.disconnect() confirmations = spoke_manager.broadcast_message( diff --git a/tests/unit/test_streaming.py b/tests/unit/test_streaming.py index 0224532..621a22c 100644 --- a/tests/unit/test_streaming.py +++ b/tests/unit/test_streaming.py @@ -4,7 +4,7 @@ import pytest -from felix_agent_sdk.events import EventBus, EventType +from felix_agent_sdk.events import EventBus from felix_agent_sdk.providers.types import StreamChunk from felix_agent_sdk.streaming import ( CallbackStreamHandler, diff --git a/tests/unit/test_synthesizer.py b/tests/unit/test_synthesizer.py index 01179d9..c68ed55 100644 --- a/tests/unit/test_synthesizer.py +++ b/tests/unit/test_synthesizer.py @@ -4,7 +4,6 @@ from unittest.mock import MagicMock -import pytest from felix_agent_sdk.agents.llm_agent import LLMResult from felix_agent_sdk.providers.base import BaseProvider @@ -108,7 +107,7 @@ def test_without_compression(self): synth = WorkflowSynthesizer(provider, config) results = [_make_result("a1", "Data", 0.5)] - output = synth.synthesize(results, "task") + synth.synthesize(results, "task") assert provider.complete.called diff --git a/tests/unit/test_task_memory.py b/tests/unit/test_task_memory.py index 78a8f6e..b9cb66c 100644 --- a/tests/unit/test_task_memory.py +++ b/tests/unit/test_task_memory.py @@ -2,12 +2,10 @@ from __future__ import annotations -import pytest from felix_agent_sdk.memory.task_memory import ( TaskComplexity, TaskExecution, - TaskMemory, TaskMemoryQuery, TaskOutcome, TaskPattern, diff --git a/tests/unit/test_workflow_config.py b/tests/unit/test_workflow_config.py index 2bbee1f..eca4529 100644 --- a/tests/unit/test_workflow_config.py +++ b/tests/unit/test_workflow_config.py @@ -2,7 +2,6 @@ from __future__ import annotations -import pytest from felix_agent_sdk.core.helix import HelixConfig from felix_agent_sdk.workflows.config import (