agentic-cli 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/CHANGELOG.md +17 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/CLAUDE.md +10 -3
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/PKG-INFO +1 -1
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/pyproject.toml +1 -1
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/__init__.py +2 -2
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/message_processor.py +219 -124
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/workflow_controller.py +75 -8
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/constants.py +9 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/__init__.py +6 -4
- agentic_cli-0.4.1/src/agentic_cli/tools/standard.py → agentic_cli-0.4.2/src/agentic_cli/tools/arxiv_tools.py +5 -205
- agentic_cli-0.4.2/src/agentic_cli/tools/execution_tools.py +51 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/file_read.py +2 -4
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/file_write.py +3 -2
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/glob_tool.py +2 -5
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/grep_tool.py +3 -4
- agentic_cli-0.4.2/src/agentic_cli/tools/interaction_tools.py +71 -0
- agentic_cli-0.4.2/src/agentic_cli/tools/knowledge_tools.py +110 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/memory_tools.py +5 -5
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/task_tools.py +4 -6
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/__init__.py +13 -5
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/adk/__init__.py +3 -7
- agentic_cli-0.4.2/src/agentic_cli/workflow/adk/event_processor.py +261 -0
- agentic_cli-0.4.1/src/agentic_cli/workflow/adk_manager.py → agentic_cli-0.4.2/src/agentic_cli/workflow/adk/manager.py +22 -207
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/base_manager.py +5 -122
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/context.py +1 -1
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/events.py +8 -2
- agentic_cli-0.4.2/src/agentic_cli/workflow/langgraph/graph_builder.py +426 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/manager.py +48 -391
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/state.py +2 -15
- agentic_cli-0.4.2/src/agentic_cli/workflow/task_progress.py +154 -0
- agentic_cli-0.4.2/src/agentic_cli/workflow/tool_summaries.py +164 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/integration/test_adk_integration.py +2 -2
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/integration/test_langgraph_integration.py +21 -1
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/integration/test_live.py +2 -2
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_langgraph.py +12 -11
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_memory.py +6 -6
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_token_caching.py +7 -13
- agentic_cli-0.4.2/tests/test_tool_summaries.py +369 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_tools.py +31 -22
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_workflow.py +11 -11
- agentic_cli-0.4.2/tests/test_workflow_controller.py +283 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/.gitignore +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/LICENSE +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/README.md +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/environment.yml +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/arxiv_demo.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/fileops_demo.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/hello_agent.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/hello_langgraph.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/memory_demo.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/planning_demo.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/research_demo/README.md +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/research_demo/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/research_demo/__main__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/research_demo/agents.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/research_demo/app.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/research_demo/commands.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/research_demo/settings.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/shell_demo.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/webfetch_demo.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/examples/websearch_demo.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/requirements-dev.txt +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/requirements.txt +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/app.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/builtin_commands.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/commands.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/settings.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/settings_command.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/cli/settings_introspection.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/config.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/hitl/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/hitl/approval.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/hitl/checkpoints.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/hitl/config.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/knowledge_base/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/knowledge_base/embeddings.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/knowledge_base/manager.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/knowledge_base/models.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/knowledge_base/sources.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/knowledge_base/vector_store.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/logging.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/persistence/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/persistence/_utils.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/persistence/artifacts.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/persistence/session.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/resolvers.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/settings_persistence.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/executor.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/hitl_tools.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/planning_tools.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/registry.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/search.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/audit.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/classifier.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/config.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/executor.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/models.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/path_analyzer.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/preprocessor.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/risk_assessor.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/sandbox.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/shell/tokenizer.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/webfetch/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/webfetch/converter.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/webfetch/fetcher.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/webfetch/robots.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/webfetch/summarizer.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/webfetch/validator.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/tools/webfetch_tool.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/adk/llm_event_logger.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/config.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/persistence/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/persistence/checkpointers.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/persistence/stores.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/tools/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/tools/file_search.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/langgraph/tools/shell.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/settings.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/src/agentic_cli/workflow/thinking.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/conftest.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/integration/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/integration/conftest.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/integration/helpers.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_commands.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_config.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_embeddings.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_hitl.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_knowledge_base.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_persistence.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_planning.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_task_tools.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_thinking.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_vector_store.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/test_webfetch.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/tools/__init__.py +0 -0
- {agentic_cli-0.4.1 → agentic_cli-0.4.2}/tests/tools/test_shell_security.py +0 -0
|
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.2] - 2026-02-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Claude Model Support**: Auto-switch to LangGraph orchestrator for Anthropic models
|
|
12
|
+
- **Tool Result Summaries**: Meaningful one-liners for 18 tools instead of generic "Returned N fields"
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **LangGraph Native ToolNode**: Replaced custom tool node with LangChain's native ToolNode
|
|
16
|
+
- **Workflow Manager Decomposition**: Extracted cohesive collaborators from workflow managers
|
|
17
|
+
- **Message Processor Dispatch**: Extracted event handlers into dispatch table
|
|
18
|
+
- **Tool Module Split**: Split monolithic standard.py into domain-specific tool modules
|
|
19
|
+
- **Code Cleanup**: Deleted re-export shim, moved adk_manager into adk/, renamed context accessors
|
|
20
|
+
- **Atomic File Writes**: write_file and edit_file now use atomic writes
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- `diff_compare` tool producing ugly dict repr for summary field
|
|
24
|
+
|
|
8
25
|
## [0.4.1] - 2026-02-08
|
|
9
26
|
|
|
10
27
|
### Changed
|
|
@@ -32,21 +32,28 @@ agentic-cli/
|
|
|
32
32
|
│ │ └── settings*.py # Settings UI (introspection, dialog)
|
|
33
33
|
│ ├── workflow/
|
|
34
34
|
│ │ ├── base_manager.py # BaseWorkflowManager (abstract)
|
|
35
|
-
│ │ ├──
|
|
35
|
+
│ │ ├── task_progress.py # build_task_progress_event(), parse_plan_progress()
|
|
36
36
|
│ │ ├── events.py # WorkflowEvent, EventType
|
|
37
37
|
│ │ ├── thinking.py # ThinkingDetector
|
|
38
38
|
│ │ ├── config.py # AgentConfig
|
|
39
39
|
│ │ ├── context.py # ContextVars for tool access (get_context_*())
|
|
40
|
-
│ │ ├── adk/ # ADK
|
|
40
|
+
│ │ ├── adk/ # ADK orchestrator
|
|
41
|
+
│ │ │ ├── manager.py # GoogleADKWorkflowManager
|
|
42
|
+
│ │ │ ├── event_processor.py # ADKEventProcessor
|
|
43
|
+
│ │ │ └── llm_event_logger.py # LLM traffic logging
|
|
41
44
|
│ │ └── langgraph/ # LangGraph orchestrator
|
|
42
45
|
│ │ ├── manager.py # LangGraphWorkflowManager
|
|
46
|
+
│ │ ├── graph_builder.py # LangGraphBuilder (graph + LLM factory)
|
|
43
47
|
│ │ ├── state.py
|
|
44
48
|
│ │ ├── persistence/ # Checkpointers, stores
|
|
45
49
|
│ │ └── tools/ # LangChain-compatible wrappers
|
|
46
50
|
│ ├── tools/
|
|
47
51
|
│ │ ├── registry.py # ToolRegistry, @register_tool, ToolCategory, PermissionLevel
|
|
48
52
|
│ │ ├── executor.py # SafePythonExecutor
|
|
49
|
-
│ │ ├──
|
|
53
|
+
│ │ ├── knowledge_tools.py # search_knowledge_base, ingest_to_knowledge_base
|
|
54
|
+
│ │ ├── arxiv_tools.py # search_arxiv, fetch_arxiv_paper, analyze_arxiv_paper
|
|
55
|
+
│ │ ├── execution_tools.py # execute_python
|
|
56
|
+
│ │ ├── interaction_tools.py # ask_clarification
|
|
50
57
|
│ │ ├── file_read.py # read_file, diff_compare
|
|
51
58
|
│ │ ├── file_write.py # write_file, edit_file
|
|
52
59
|
│ │ ├── glob_tool.py # glob
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-cli
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: A framework for building domain-specific agentic CLI applications
|
|
5
5
|
Project-URL: Homepage, https://github.com/shoom1/agentic-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/shoom1/agentic-cli
|
|
@@ -45,7 +45,7 @@ from agentic_cli.cli.settings import CLISettingsMixin
|
|
|
45
45
|
|
|
46
46
|
# Heavy imports - lazy loaded on first access
|
|
47
47
|
_lazy_imports = {
|
|
48
|
-
"GoogleADKWorkflowManager": "agentic_cli.workflow.
|
|
48
|
+
"GoogleADKWorkflowManager": "agentic_cli.workflow.adk.manager",
|
|
49
49
|
"BaseWorkflowManager": "agentic_cli.workflow.base_manager",
|
|
50
50
|
"LangGraphWorkflowManager": "agentic_cli.workflow.langgraph.manager",
|
|
51
51
|
}
|
|
@@ -95,4 +95,4 @@ __all__ = [
|
|
|
95
95
|
"PathResolver",
|
|
96
96
|
]
|
|
97
97
|
|
|
98
|
-
__version__ = "0.4.
|
|
98
|
+
__version__ = "0.4.2"
|
|
@@ -11,7 +11,7 @@ import asyncio
|
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
12
|
from datetime import datetime
|
|
13
13
|
from enum import Enum
|
|
14
|
-
from typing import TYPE_CHECKING
|
|
14
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
15
15
|
|
|
16
16
|
from agentic_cli.logging import Loggers, bind_context
|
|
17
17
|
|
|
@@ -92,6 +92,34 @@ class MessageHistory:
|
|
|
92
92
|
return len(self._messages)
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
# === Event Processing State ===
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class _EventProcessingState:
|
|
100
|
+
"""Mutable state shared across event handlers during process()."""
|
|
101
|
+
|
|
102
|
+
status_line: str = "Processing..."
|
|
103
|
+
task_progress_display: str | None = None
|
|
104
|
+
thinking_started: bool = False
|
|
105
|
+
thinking_content: list[str] = field(default_factory=list)
|
|
106
|
+
response_content: list[str] = field(default_factory=list)
|
|
107
|
+
|
|
108
|
+
def get_status(self) -> str:
|
|
109
|
+
"""Build status display with current action and task progress."""
|
|
110
|
+
lines = [self.status_line]
|
|
111
|
+
if self.task_progress_display:
|
|
112
|
+
lines.append(self.task_progress_display)
|
|
113
|
+
return "\n".join(lines)
|
|
114
|
+
|
|
115
|
+
def reset_for_retry(self) -> None:
|
|
116
|
+
"""Reset state for retry after rate limit."""
|
|
117
|
+
self.status_line = "Processing..."
|
|
118
|
+
self.task_progress_display = None
|
|
119
|
+
self.thinking_content.clear()
|
|
120
|
+
self.response_content.clear()
|
|
121
|
+
|
|
122
|
+
|
|
95
123
|
# === Message Processor ===
|
|
96
124
|
|
|
97
125
|
|
|
@@ -156,8 +184,8 @@ class MessageProcessor:
|
|
|
156
184
|
)
|
|
157
185
|
return
|
|
158
186
|
|
|
159
|
-
# Import
|
|
160
|
-
from agentic_cli.workflow import
|
|
187
|
+
# Import WorkflowEvent here (workflow module is now loaded)
|
|
188
|
+
from agentic_cli.workflow import WorkflowEvent
|
|
161
189
|
|
|
162
190
|
bind_context(user_id=settings.default_user)
|
|
163
191
|
logger.info("handling_message", message_length=len(message))
|
|
@@ -166,35 +194,17 @@ class MessageProcessor:
|
|
|
166
194
|
if settings.log_activity:
|
|
167
195
|
self._message_history.add(message, MessageType.USER)
|
|
168
196
|
|
|
169
|
-
|
|
170
|
-
status_line = "Processing..."
|
|
171
|
-
task_progress_display: str | None = None
|
|
172
|
-
thinking_started = False
|
|
173
|
-
|
|
174
|
-
# Accumulate content for history
|
|
175
|
-
thinking_content: list[str] = []
|
|
176
|
-
response_content: list[str] = []
|
|
177
|
-
|
|
178
|
-
def get_status() -> str:
|
|
179
|
-
"""Build status display with current action and task progress."""
|
|
180
|
-
lines = [status_line]
|
|
181
|
-
|
|
182
|
-
# Add task progress if available (from TASK_PROGRESS events)
|
|
183
|
-
if task_progress_display:
|
|
184
|
-
lines.append(task_progress_display)
|
|
185
|
-
|
|
186
|
-
return "\n".join(lines)
|
|
187
|
-
|
|
197
|
+
state = _EventProcessingState()
|
|
188
198
|
workflow = workflow_controller.workflow
|
|
199
|
+
dispatch = self._get_event_dispatch()
|
|
189
200
|
|
|
190
201
|
# Set up direct callback so HITL tools can prompt the user without
|
|
191
202
|
# deadlocking (the Future pattern blocks the ADK runner, preventing
|
|
192
203
|
# the USER_INPUT_REQUIRED event from ever being yielded).
|
|
193
204
|
async def _handle_input(request: "UserInputRequest") -> str:
|
|
194
|
-
|
|
195
|
-
if thinking_started:
|
|
205
|
+
if state.thinking_started:
|
|
196
206
|
ui.finish_thinking(add_to_history=False)
|
|
197
|
-
thinking_started = False
|
|
207
|
+
state.thinking_started = False
|
|
198
208
|
|
|
199
209
|
wf_event = WorkflowEvent.user_input_required(
|
|
200
210
|
request_id=request.request_id,
|
|
@@ -206,129 +216,52 @@ class MessageProcessor:
|
|
|
206
216
|
)
|
|
207
217
|
response = await self._prompt_user_input(wf_event, workflow, ui)
|
|
208
218
|
|
|
209
|
-
ui.start_thinking(get_status)
|
|
210
|
-
thinking_started = True
|
|
219
|
+
ui.start_thinking(state.get_status)
|
|
220
|
+
state.thinking_started = True
|
|
211
221
|
return response
|
|
212
222
|
|
|
213
223
|
workflow._user_input_callback = _handle_input
|
|
214
224
|
try:
|
|
215
225
|
while True:
|
|
216
226
|
try:
|
|
217
|
-
ui.start_thinking(get_status)
|
|
218
|
-
thinking_started = True
|
|
227
|
+
ui.start_thinking(state.get_status)
|
|
228
|
+
state.thinking_started = True
|
|
219
229
|
|
|
220
230
|
async for event in workflow.process(
|
|
221
231
|
message=message,
|
|
222
232
|
user_id=settings.default_user,
|
|
223
233
|
):
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
response_content.append(event.content)
|
|
228
|
-
|
|
229
|
-
elif event.type == EventType.THINKING:
|
|
230
|
-
# Stream thinking to console only if verbose_thinking is enabled
|
|
231
|
-
status_line = "Thinking..."
|
|
232
|
-
if settings.verbose_thinking:
|
|
233
|
-
ui.add_message("system", event.content)
|
|
234
|
-
thinking_content.append(event.content)
|
|
235
|
-
|
|
236
|
-
elif event.type == EventType.TOOL_CALL:
|
|
237
|
-
# Update status line in thinking box
|
|
238
|
-
tool_name = event.metadata.get("tool_name", "unknown")
|
|
239
|
-
status_line = f"Calling: {tool_name}"
|
|
240
|
-
|
|
241
|
-
elif event.type == EventType.TOOL_RESULT:
|
|
242
|
-
# Display tool result summary
|
|
243
|
-
tool_name = event.metadata.get("tool_name", "unknown")
|
|
244
|
-
success = event.metadata.get("success", True)
|
|
245
|
-
duration = event.metadata.get("duration_ms")
|
|
246
|
-
icon = "+" if success else "x"
|
|
247
|
-
duration_str = f" ({duration}ms)" if duration else ""
|
|
248
|
-
status_line = f"{icon} {tool_name}: {event.content}{duration_str}"
|
|
249
|
-
# Also show in message area for visibility
|
|
250
|
-
style = "green" if success else "red"
|
|
251
|
-
ui.add_rich(
|
|
252
|
-
f"[{style}]{icon}[/{style}] {tool_name}: {event.content}{duration_str}"
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
elif event.type == EventType.USER_INPUT_REQUIRED:
|
|
256
|
-
# Legacy path: handle USER_INPUT_REQUIRED events
|
|
257
|
-
# that may still arrive from the ADK event stream.
|
|
258
|
-
if thinking_started:
|
|
259
|
-
ui.finish_thinking(add_to_history=False)
|
|
260
|
-
thinking_started = False
|
|
261
|
-
|
|
262
|
-
response = await self._prompt_user_input(event, workflow, ui)
|
|
263
|
-
workflow.provide_user_input(
|
|
264
|
-
event.metadata["request_id"],
|
|
265
|
-
response,
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
# Resume thinking box
|
|
269
|
-
ui.start_thinking(get_status)
|
|
270
|
-
thinking_started = True
|
|
271
|
-
|
|
272
|
-
elif event.type == EventType.CODE_EXECUTION:
|
|
273
|
-
# Update status with execution result
|
|
274
|
-
result_preview = (
|
|
275
|
-
event.content[:40] + "..."
|
|
276
|
-
if len(event.content) > 40
|
|
277
|
-
else event.content
|
|
278
|
-
)
|
|
279
|
-
status_line = f"Result: {result_preview}"
|
|
280
|
-
|
|
281
|
-
elif event.type == EventType.EXECUTABLE_CODE:
|
|
282
|
-
# Update status when executing code
|
|
283
|
-
lang = event.metadata.get("language", "python")
|
|
284
|
-
status_line = f"Running {lang} code..."
|
|
285
|
-
|
|
286
|
-
elif event.type == EventType.FILE_DATA:
|
|
287
|
-
# Update status with file info
|
|
288
|
-
status_line = f"File: {event.content}"
|
|
289
|
-
|
|
290
|
-
elif event.type == EventType.TASK_PROGRESS:
|
|
291
|
-
# Task progress update - event.content has compact display,
|
|
292
|
-
# event.metadata has progress stats
|
|
293
|
-
progress = event.metadata.get("progress", {})
|
|
294
|
-
if progress.get("total", 0) > 0:
|
|
295
|
-
completed = progress.get("completed", 0)
|
|
296
|
-
total = progress["total"]
|
|
297
|
-
# Build display: separator + task list
|
|
298
|
-
task_progress_display = f"--- Tasks: {completed}/{total} ---"
|
|
299
|
-
if event.content:
|
|
300
|
-
task_progress_display += f"\n{event.content}"
|
|
301
|
-
|
|
302
|
-
# Update status line with current task if available
|
|
303
|
-
current_task = event.metadata.get("current_task_description")
|
|
304
|
-
if current_task:
|
|
305
|
-
status_line = f"Working on: {current_task}"
|
|
234
|
+
handler = dispatch.get(event.type)
|
|
235
|
+
if handler is not None:
|
|
236
|
+
await handler(self, event, state, ui, settings, workflow)
|
|
306
237
|
|
|
307
238
|
# Finish thinking box (don't add status to history)
|
|
308
|
-
if thinking_started:
|
|
239
|
+
if state.thinking_started:
|
|
309
240
|
ui.finish_thinking(add_to_history=False)
|
|
310
241
|
|
|
311
242
|
# Add accumulated content to message history (if logging enabled)
|
|
312
243
|
if settings.log_activity:
|
|
313
|
-
if thinking_content:
|
|
244
|
+
if state.thinking_content:
|
|
314
245
|
self._message_history.add(
|
|
315
|
-
"".join(thinking_content),
|
|
246
|
+
"".join(state.thinking_content),
|
|
247
|
+
MessageType.THINKING,
|
|
316
248
|
)
|
|
317
|
-
if response_content:
|
|
249
|
+
if state.response_content:
|
|
318
250
|
self._message_history.add(
|
|
319
|
-
"".join(response_content),
|
|
251
|
+
"".join(state.response_content),
|
|
252
|
+
MessageType.ASSISTANT,
|
|
320
253
|
)
|
|
321
254
|
|
|
322
255
|
logger.debug("message_handled_successfully")
|
|
323
256
|
break # Success — exit retry loop
|
|
324
257
|
|
|
325
258
|
except Exception as e:
|
|
326
|
-
if thinking_started:
|
|
259
|
+
if state.thinking_started:
|
|
327
260
|
ui.finish_thinking(add_to_history=False)
|
|
328
|
-
thinking_started = False
|
|
261
|
+
state.thinking_started = False
|
|
329
262
|
|
|
330
263
|
# Check for 429 rate limit errors — prompt user to wait and retry
|
|
331
|
-
from agentic_cli.workflow.
|
|
264
|
+
from agentic_cli.workflow.adk.event_processor import (
|
|
332
265
|
_is_rate_limit_error,
|
|
333
266
|
_parse_retry_delay,
|
|
334
267
|
)
|
|
@@ -342,11 +275,7 @@ class MessageProcessor:
|
|
|
342
275
|
if retry:
|
|
343
276
|
ui.add_warning(f"Waiting {delay:.0f}s before retrying...")
|
|
344
277
|
await asyncio.sleep(delay)
|
|
345
|
-
|
|
346
|
-
status_line = "Processing..."
|
|
347
|
-
task_progress_display = None
|
|
348
|
-
thinking_content.clear()
|
|
349
|
-
response_content.clear()
|
|
278
|
+
state.reset_for_retry()
|
|
350
279
|
continue # Retry the loop
|
|
351
280
|
|
|
352
281
|
# Non-429 or user chose cancel
|
|
@@ -406,3 +335,169 @@ class MessageProcessor:
|
|
|
406
335
|
default=default or "",
|
|
407
336
|
)
|
|
408
337
|
return result or ""
|
|
338
|
+
|
|
339
|
+
# === Event handler methods ===
|
|
340
|
+
|
|
341
|
+
async def _handle_text(
|
|
342
|
+
self,
|
|
343
|
+
event: "WorkflowEvent",
|
|
344
|
+
state: _EventProcessingState,
|
|
345
|
+
ui: "ThinkingPromptSession",
|
|
346
|
+
settings: "BaseSettings",
|
|
347
|
+
workflow: object,
|
|
348
|
+
) -> None:
|
|
349
|
+
"""Handle TEXT events — stream response to console."""
|
|
350
|
+
ui.add_response(event.content, markdown=True)
|
|
351
|
+
state.response_content.append(event.content)
|
|
352
|
+
|
|
353
|
+
async def _handle_thinking(
|
|
354
|
+
self,
|
|
355
|
+
event: "WorkflowEvent",
|
|
356
|
+
state: _EventProcessingState,
|
|
357
|
+
ui: "ThinkingPromptSession",
|
|
358
|
+
settings: "BaseSettings",
|
|
359
|
+
workflow: object,
|
|
360
|
+
) -> None:
|
|
361
|
+
"""Handle THINKING events — update status, optionally display."""
|
|
362
|
+
state.status_line = "Thinking..."
|
|
363
|
+
if settings.verbose_thinking:
|
|
364
|
+
ui.add_message("system", event.content)
|
|
365
|
+
state.thinking_content.append(event.content)
|
|
366
|
+
|
|
367
|
+
async def _handle_tool_call(
|
|
368
|
+
self,
|
|
369
|
+
event: "WorkflowEvent",
|
|
370
|
+
state: _EventProcessingState,
|
|
371
|
+
ui: "ThinkingPromptSession",
|
|
372
|
+
settings: "BaseSettings",
|
|
373
|
+
workflow: object,
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Handle TOOL_CALL events — update status line."""
|
|
376
|
+
tool_name = event.metadata.get("tool_name", "unknown")
|
|
377
|
+
state.status_line = f"Calling: {tool_name}"
|
|
378
|
+
|
|
379
|
+
async def _handle_tool_result(
|
|
380
|
+
self,
|
|
381
|
+
event: "WorkflowEvent",
|
|
382
|
+
state: _EventProcessingState,
|
|
383
|
+
ui: "ThinkingPromptSession",
|
|
384
|
+
settings: "BaseSettings",
|
|
385
|
+
workflow: object,
|
|
386
|
+
) -> None:
|
|
387
|
+
"""Handle TOOL_RESULT events — display result summary."""
|
|
388
|
+
tool_name = event.metadata.get("tool_name", "unknown")
|
|
389
|
+
success = event.metadata.get("success", True)
|
|
390
|
+
duration = event.metadata.get("duration_ms")
|
|
391
|
+
icon = "+" if success else "x"
|
|
392
|
+
duration_str = f" ({duration}ms)" if duration else ""
|
|
393
|
+
state.status_line = f"{icon} {tool_name}: {event.content}{duration_str}"
|
|
394
|
+
style = "green" if success else "red"
|
|
395
|
+
ui.add_rich(
|
|
396
|
+
f"[{style}]{icon}[/{style}] {tool_name}: {event.content}{duration_str}"
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
async def _handle_user_input_required(
|
|
400
|
+
self,
|
|
401
|
+
event: "WorkflowEvent",
|
|
402
|
+
state: _EventProcessingState,
|
|
403
|
+
ui: "ThinkingPromptSession",
|
|
404
|
+
settings: "BaseSettings",
|
|
405
|
+
workflow: object,
|
|
406
|
+
) -> None:
|
|
407
|
+
"""Handle USER_INPUT_REQUIRED events — legacy ADK event stream path."""
|
|
408
|
+
if state.thinking_started:
|
|
409
|
+
ui.finish_thinking(add_to_history=False)
|
|
410
|
+
state.thinking_started = False
|
|
411
|
+
|
|
412
|
+
response = await self._prompt_user_input(event, workflow, ui)
|
|
413
|
+
workflow.provide_user_input(
|
|
414
|
+
event.metadata["request_id"],
|
|
415
|
+
response,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Resume thinking box
|
|
419
|
+
ui.start_thinking(state.get_status)
|
|
420
|
+
state.thinking_started = True
|
|
421
|
+
|
|
422
|
+
async def _handle_code_execution(
|
|
423
|
+
self,
|
|
424
|
+
event: "WorkflowEvent",
|
|
425
|
+
state: _EventProcessingState,
|
|
426
|
+
ui: "ThinkingPromptSession",
|
|
427
|
+
settings: "BaseSettings",
|
|
428
|
+
workflow: object,
|
|
429
|
+
) -> None:
|
|
430
|
+
"""Handle CODE_EXECUTION events — update status with result preview."""
|
|
431
|
+
result_preview = (
|
|
432
|
+
event.content[:40] + "..."
|
|
433
|
+
if len(event.content) > 40
|
|
434
|
+
else event.content
|
|
435
|
+
)
|
|
436
|
+
state.status_line = f"Result: {result_preview}"
|
|
437
|
+
|
|
438
|
+
async def _handle_executable_code(
|
|
439
|
+
self,
|
|
440
|
+
event: "WorkflowEvent",
|
|
441
|
+
state: _EventProcessingState,
|
|
442
|
+
ui: "ThinkingPromptSession",
|
|
443
|
+
settings: "BaseSettings",
|
|
444
|
+
workflow: object,
|
|
445
|
+
) -> None:
|
|
446
|
+
"""Handle EXECUTABLE_CODE events — update status with language."""
|
|
447
|
+
lang = event.metadata.get("language", "python")
|
|
448
|
+
state.status_line = f"Running {lang} code..."
|
|
449
|
+
|
|
450
|
+
async def _handle_file_data(
|
|
451
|
+
self,
|
|
452
|
+
event: "WorkflowEvent",
|
|
453
|
+
state: _EventProcessingState,
|
|
454
|
+
ui: "ThinkingPromptSession",
|
|
455
|
+
settings: "BaseSettings",
|
|
456
|
+
workflow: object,
|
|
457
|
+
) -> None:
|
|
458
|
+
"""Handle FILE_DATA events — update status with filename."""
|
|
459
|
+
state.status_line = f"File: {event.content}"
|
|
460
|
+
|
|
461
|
+
async def _handle_task_progress(
|
|
462
|
+
self,
|
|
463
|
+
event: "WorkflowEvent",
|
|
464
|
+
state: _EventProcessingState,
|
|
465
|
+
ui: "ThinkingPromptSession",
|
|
466
|
+
settings: "BaseSettings",
|
|
467
|
+
workflow: object,
|
|
468
|
+
) -> None:
|
|
469
|
+
"""Handle TASK_PROGRESS events — update progress display."""
|
|
470
|
+
progress = event.metadata.get("progress", {})
|
|
471
|
+
if progress.get("total", 0) > 0:
|
|
472
|
+
completed = progress.get("completed", 0)
|
|
473
|
+
total = progress["total"]
|
|
474
|
+
state.task_progress_display = f"--- Tasks: {completed}/{total} ---"
|
|
475
|
+
if event.content:
|
|
476
|
+
state.task_progress_display += f"\n{event.content}"
|
|
477
|
+
|
|
478
|
+
current_task = event.metadata.get("current_task_description")
|
|
479
|
+
if current_task:
|
|
480
|
+
state.status_line = f"Working on: {current_task}"
|
|
481
|
+
|
|
482
|
+
# === Dispatch table ===
|
|
483
|
+
|
|
484
|
+
_EVENT_DISPATCH: ClassVar[dict | None] = None
|
|
485
|
+
|
|
486
|
+
@classmethod
|
|
487
|
+
def _get_event_dispatch(cls) -> dict:
|
|
488
|
+
"""Get or build the EventType → handler dispatch table."""
|
|
489
|
+
if cls._EVENT_DISPATCH is None:
|
|
490
|
+
from agentic_cli.workflow import EventType
|
|
491
|
+
|
|
492
|
+
cls._EVENT_DISPATCH = {
|
|
493
|
+
EventType.TEXT: cls._handle_text,
|
|
494
|
+
EventType.THINKING: cls._handle_thinking,
|
|
495
|
+
EventType.TOOL_CALL: cls._handle_tool_call,
|
|
496
|
+
EventType.TOOL_RESULT: cls._handle_tool_result,
|
|
497
|
+
EventType.USER_INPUT_REQUIRED: cls._handle_user_input_required,
|
|
498
|
+
EventType.CODE_EXECUTION: cls._handle_code_execution,
|
|
499
|
+
EventType.EXECUTABLE_CODE: cls._handle_executable_code,
|
|
500
|
+
EventType.FILE_DATA: cls._handle_file_data,
|
|
501
|
+
EventType.TASK_PROGRESS: cls._handle_task_progress,
|
|
502
|
+
}
|
|
503
|
+
return cls._EVENT_DISPATCH
|
|
@@ -28,6 +28,20 @@ logger = Loggers.cli()
|
|
|
28
28
|
_init_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="workflow-init")
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def _is_claude_model(model: str | None) -> bool:
|
|
32
|
+
"""Check if a model string refers to a Claude (Anthropic) model."""
|
|
33
|
+
return model is not None and model.startswith("claude-")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_effective_model(
|
|
37
|
+
model: str | None, settings: "BaseSettings"
|
|
38
|
+
) -> str | None:
|
|
39
|
+
"""Resolve the effective model from explicit override or settings."""
|
|
40
|
+
if model:
|
|
41
|
+
return model
|
|
42
|
+
return getattr(settings, "default_model", None)
|
|
43
|
+
|
|
44
|
+
|
|
31
45
|
def create_workflow_manager_from_settings(
|
|
32
46
|
agent_configs: list["AgentConfig"],
|
|
33
47
|
settings: "BaseSettings",
|
|
@@ -38,7 +52,9 @@ def create_workflow_manager_from_settings(
|
|
|
38
52
|
"""Factory function to create the appropriate workflow manager based on settings.
|
|
39
53
|
|
|
40
54
|
Creates either a GoogleADKWorkflowManager or LangGraphWorkflowManager
|
|
41
|
-
based on the settings.orchestrator configuration.
|
|
55
|
+
based on the settings.orchestrator configuration. Claude models are
|
|
56
|
+
automatically routed to LangGraph because ADK's LiteLLM adapter has
|
|
57
|
+
critical issues with tool calling, thinking, and streaming.
|
|
42
58
|
|
|
43
59
|
Args:
|
|
44
60
|
agent_configs: List of agent configurations.
|
|
@@ -61,8 +77,19 @@ def create_workflow_manager_from_settings(
|
|
|
61
77
|
from agentic_cli.workflow.settings import OrchestratorType
|
|
62
78
|
|
|
63
79
|
orchestrator = getattr(settings, "orchestrator", OrchestratorType.ADK)
|
|
80
|
+
effective_model = _resolve_effective_model(model, settings)
|
|
81
|
+
use_langgraph = orchestrator == OrchestratorType.LANGGRAPH or _is_claude_model(
|
|
82
|
+
effective_model
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if use_langgraph and _is_claude_model(effective_model) and orchestrator != OrchestratorType.LANGGRAPH:
|
|
86
|
+
logger.info(
|
|
87
|
+
"auto_switching_to_langgraph",
|
|
88
|
+
model=effective_model,
|
|
89
|
+
reason="Claude models require LangGraph orchestrator (ADK LiteLLM adapter has critical issues)",
|
|
90
|
+
)
|
|
64
91
|
|
|
65
|
-
if
|
|
92
|
+
if use_langgraph:
|
|
66
93
|
try:
|
|
67
94
|
from agentic_cli.workflow.langgraph import LangGraphWorkflowManager
|
|
68
95
|
|
|
@@ -77,13 +104,13 @@ def create_workflow_manager_from_settings(
|
|
|
77
104
|
)
|
|
78
105
|
except ImportError as e:
|
|
79
106
|
raise ImportError(
|
|
80
|
-
f"LangGraph orchestrator
|
|
107
|
+
f"LangGraph orchestrator required but dependencies not installed. "
|
|
81
108
|
f"Install with: pip install agentic-cli[langgraph]\n"
|
|
82
109
|
f"Original error: {e}"
|
|
83
110
|
) from e
|
|
84
111
|
|
|
85
112
|
else: # Default to ADK
|
|
86
|
-
from agentic_cli.workflow.
|
|
113
|
+
from agentic_cli.workflow.adk.manager import GoogleADKWorkflowManager
|
|
87
114
|
|
|
88
115
|
return GoogleADKWorkflowManager(
|
|
89
116
|
agent_configs=agent_configs,
|
|
@@ -129,15 +156,15 @@ class WorkflowController:
|
|
|
129
156
|
settings: Application settings instance
|
|
130
157
|
"""
|
|
131
158
|
self._settings = settings
|
|
159
|
+
self._agent_configs = agent_configs
|
|
160
|
+
self._app_name = settings.app_name
|
|
132
161
|
|
|
133
162
|
# Closure captures agent_configs for lazy creation (used by _background_init)
|
|
134
|
-
app_name = settings.app_name
|
|
135
|
-
|
|
136
163
|
def _create_workflow() -> "BaseWorkflowManager":
|
|
137
164
|
return create_workflow_manager_from_settings(
|
|
138
165
|
agent_configs=agent_configs,
|
|
139
166
|
settings=settings,
|
|
140
|
-
app_name=
|
|
167
|
+
app_name=self._app_name,
|
|
141
168
|
)
|
|
142
169
|
|
|
143
170
|
self._create_fn = _create_workflow
|
|
@@ -250,9 +277,35 @@ class WorkflowController:
|
|
|
250
277
|
|
|
251
278
|
return self._workflow is not None
|
|
252
279
|
|
|
280
|
+
def _needs_orchestrator_swap(self, new_model: str | None) -> bool:
|
|
281
|
+
"""Check if switching to new_model requires a different orchestrator.
|
|
282
|
+
|
|
283
|
+
Returns True when the model family changes (e.g. Gemini → Claude or
|
|
284
|
+
Claude → Gemini) and the current manager type doesn't match what the
|
|
285
|
+
factory would create for the new model.
|
|
286
|
+
"""
|
|
287
|
+
if new_model is None or self._workflow is None:
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
from agentic_cli.workflow.settings import OrchestratorType
|
|
291
|
+
|
|
292
|
+
orchestrator = getattr(self._settings, "orchestrator", OrchestratorType.ADK)
|
|
293
|
+
new_needs_langgraph = orchestrator == OrchestratorType.LANGGRAPH or _is_claude_model(new_model)
|
|
294
|
+
|
|
295
|
+
# Check current manager type by class name to avoid isinstance issues
|
|
296
|
+
# when the class may not be importable (LangGraph not installed)
|
|
297
|
+
current_cls_name = type(self._workflow).__name__
|
|
298
|
+
current_is_langgraph = current_cls_name == "LangGraphWorkflowManager"
|
|
299
|
+
|
|
300
|
+
return new_needs_langgraph != current_is_langgraph
|
|
301
|
+
|
|
253
302
|
async def reinitialize(self, model: str | None = None) -> None:
|
|
254
303
|
"""Reinitialize the workflow with optional new model.
|
|
255
304
|
|
|
305
|
+
If the new model requires a different orchestrator (e.g. switching from
|
|
306
|
+
Gemini on ADK to Claude on LangGraph), the entire workflow manager is
|
|
307
|
+
replaced. Otherwise, the existing manager is reinitalized in place.
|
|
308
|
+
|
|
256
309
|
Args:
|
|
257
310
|
model: Optional new model to use
|
|
258
311
|
|
|
@@ -263,7 +316,21 @@ class WorkflowController:
|
|
|
263
316
|
if self._workflow is None:
|
|
264
317
|
raise RuntimeError("Cannot reinitialize - workflow not initialized")
|
|
265
318
|
|
|
266
|
-
|
|
319
|
+
if self._needs_orchestrator_swap(model):
|
|
320
|
+
logger.info(
|
|
321
|
+
"orchestrator_swap",
|
|
322
|
+
old_model=self._workflow.model,
|
|
323
|
+
new_model=model,
|
|
324
|
+
)
|
|
325
|
+
self._workflow = create_workflow_manager_from_settings(
|
|
326
|
+
agent_configs=self._agent_configs,
|
|
327
|
+
settings=self._settings,
|
|
328
|
+
app_name=self._app_name,
|
|
329
|
+
model=model,
|
|
330
|
+
)
|
|
331
|
+
await self._workflow.initialize_services()
|
|
332
|
+
else:
|
|
333
|
+
await self._workflow.reinitialize(model=model, preserve_sessions=True)
|
|
267
334
|
|
|
268
335
|
async def cancel_init(self) -> None:
|
|
269
336
|
"""Cancel pending initialization task if still running."""
|
|
@@ -10,3 +10,12 @@ def truncate(text: str, max_length: int = CONTENT_PREVIEW_LENGTH) -> str:
|
|
|
10
10
|
if len(text) > max_length:
|
|
11
11
|
return text[:max_length] + "..."
|
|
12
12
|
return text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def format_size(size_bytes: int) -> str:
|
|
16
|
+
"""Format byte count as human-readable string (B, KB, MB)."""
|
|
17
|
+
if size_bytes < 1024:
|
|
18
|
+
return f"{size_bytes}B"
|
|
19
|
+
elif size_bytes < 1024 * 1024:
|
|
20
|
+
return f"{size_bytes / 1024:.1f}KB"
|
|
21
|
+
return f"{size_bytes / (1024 * 1024):.1f}MB"
|