agentic-cli 0.1.0__py3-none-any.whl
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/__init__.py +55 -0
- agentic_cli/cli/__init__.py +22 -0
- agentic_cli/cli/app.py +487 -0
- agentic_cli/cli/builtin_commands.py +453 -0
- agentic_cli/cli/commands.py +444 -0
- agentic_cli/config.py +453 -0
- agentic_cli/knowledge_base/__init__.py +51 -0
- agentic_cli/knowledge_base/embeddings.py +246 -0
- agentic_cli/knowledge_base/manager.py +404 -0
- agentic_cli/knowledge_base/models.py +234 -0
- agentic_cli/knowledge_base/sources.py +352 -0
- agentic_cli/knowledge_base/vector_store.py +291 -0
- agentic_cli/llm/__init__.py +5 -0
- agentic_cli/llm/thinking.py +54 -0
- agentic_cli/logging.py +152 -0
- agentic_cli/persistence/__init__.py +19 -0
- agentic_cli/persistence/artifacts.py +244 -0
- agentic_cli/persistence/session.py +388 -0
- agentic_cli/tools/__init__.py +75 -0
- agentic_cli/tools/executor.py +396 -0
- agentic_cli/tools/registry.py +345 -0
- agentic_cli/tools/resilience.py +447 -0
- agentic_cli/tools/search.py +196 -0
- agentic_cli/tools/standard.py +203 -0
- agentic_cli/workflow/__init__.py +12 -0
- agentic_cli/workflow/config.py +34 -0
- agentic_cli/workflow/events.py +191 -0
- agentic_cli/workflow/manager.py +495 -0
- agentic_cli-0.1.0.dist-info/METADATA +72 -0
- agentic_cli-0.1.0.dist-info/RECORD +32 -0
- agentic_cli-0.1.0.dist-info/WHEEL +4 -0
- agentic_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
agentic_cli/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Agentic CLI - A framework for building domain-specific agentic CLI applications.
|
|
2
|
+
|
|
3
|
+
This package provides the core infrastructure for building CLI applications
|
|
4
|
+
powered by LLM agents, including:
|
|
5
|
+
|
|
6
|
+
- CLI framework with thinking boxes and rich output
|
|
7
|
+
- Workflow management for agent orchestration
|
|
8
|
+
- Generic tools (search, code execution, document generation)
|
|
9
|
+
- Knowledge base with vector search
|
|
10
|
+
- Session persistence
|
|
11
|
+
|
|
12
|
+
Domain-specific applications extend the base classes to provide their own
|
|
13
|
+
agents, prompts, and configuration.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from agentic_cli.cli.app import BaseCLIApp
|
|
17
|
+
from agentic_cli.cli.commands import Command, CommandRegistry
|
|
18
|
+
from agentic_cli.workflow.manager import WorkflowManager
|
|
19
|
+
from agentic_cli.workflow.config import AgentConfig
|
|
20
|
+
from agentic_cli.workflow.events import WorkflowEvent, EventType
|
|
21
|
+
from agentic_cli.config import (
|
|
22
|
+
BaseSettings,
|
|
23
|
+
SettingsContext,
|
|
24
|
+
SettingsValidationError,
|
|
25
|
+
get_settings,
|
|
26
|
+
set_settings,
|
|
27
|
+
set_context_settings,
|
|
28
|
+
get_context_settings,
|
|
29
|
+
validate_settings,
|
|
30
|
+
reload_settings,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# CLI
|
|
35
|
+
"BaseCLIApp",
|
|
36
|
+
"Command",
|
|
37
|
+
"CommandRegistry",
|
|
38
|
+
# Workflow
|
|
39
|
+
"WorkflowManager",
|
|
40
|
+
"AgentConfig",
|
|
41
|
+
"WorkflowEvent",
|
|
42
|
+
"EventType",
|
|
43
|
+
# Settings
|
|
44
|
+
"BaseSettings",
|
|
45
|
+
"SettingsContext",
|
|
46
|
+
"SettingsValidationError",
|
|
47
|
+
"get_settings",
|
|
48
|
+
"set_settings",
|
|
49
|
+
"set_context_settings",
|
|
50
|
+
"get_context_settings",
|
|
51
|
+
"validate_settings",
|
|
52
|
+
"reload_settings",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""CLI framework for agentic applications."""
|
|
2
|
+
|
|
3
|
+
from thinking_prompt import AppInfo
|
|
4
|
+
|
|
5
|
+
from agentic_cli.cli.commands import (
|
|
6
|
+
Command,
|
|
7
|
+
CommandCategory,
|
|
8
|
+
CommandRegistry,
|
|
9
|
+
ParsedArgs,
|
|
10
|
+
create_simple_command,
|
|
11
|
+
)
|
|
12
|
+
from agentic_cli.cli.app import BaseCLIApp
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AppInfo",
|
|
16
|
+
"BaseCLIApp",
|
|
17
|
+
"Command",
|
|
18
|
+
"CommandCategory",
|
|
19
|
+
"CommandRegistry",
|
|
20
|
+
"ParsedArgs",
|
|
21
|
+
"create_simple_command",
|
|
22
|
+
]
|
agentic_cli/cli/app.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"""Base CLI Application for agentic applications.
|
|
2
|
+
|
|
3
|
+
This module provides the base CLI application that:
|
|
4
|
+
1. Uses ThinkingPromptSession for all UI (thinking boxes, messages, etc.)
|
|
5
|
+
2. Connects to domain-specific workflow managers
|
|
6
|
+
3. Tracks message history for persistence
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
18
|
+
|
|
19
|
+
from prompt_toolkit.completion import WordCompleter
|
|
20
|
+
from prompt_toolkit.history import InMemoryHistory
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
|
|
23
|
+
from thinking_prompt import ThinkingPromptSession, AppInfo
|
|
24
|
+
from thinking_prompt.styles import ThinkingPromptStyles
|
|
25
|
+
|
|
26
|
+
from agentic_cli.cli.commands import Command, CommandRegistry
|
|
27
|
+
from agentic_cli.config import BaseSettings
|
|
28
|
+
from agentic_cli.logging import Loggers, configure_logging, bind_context
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from agentic_cli.workflow import WorkflowManager, EventType
|
|
32
|
+
|
|
33
|
+
logger = Loggers.cli()
|
|
34
|
+
|
|
35
|
+
# Thread pool for background initialization (single worker)
|
|
36
|
+
_init_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="workflow-init")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# === Message History for Persistence ===
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MessageType(Enum):
|
|
43
|
+
"""Types of messages in history."""
|
|
44
|
+
|
|
45
|
+
USER = "user"
|
|
46
|
+
ASSISTANT = "assistant"
|
|
47
|
+
SYSTEM = "system"
|
|
48
|
+
ERROR = "error"
|
|
49
|
+
WARNING = "warning"
|
|
50
|
+
SUCCESS = "success"
|
|
51
|
+
THINKING = "thinking"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class Message:
|
|
56
|
+
"""A message stored in history for persistence."""
|
|
57
|
+
|
|
58
|
+
content: str
|
|
59
|
+
message_type: MessageType
|
|
60
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
61
|
+
metadata: dict = field(default_factory=dict)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MessageHistory:
|
|
65
|
+
"""Simple message history for persistence."""
|
|
66
|
+
|
|
67
|
+
def __init__(self) -> None:
|
|
68
|
+
self._messages: list[Message] = []
|
|
69
|
+
|
|
70
|
+
def add(
|
|
71
|
+
self,
|
|
72
|
+
content: str,
|
|
73
|
+
message_type: MessageType | str,
|
|
74
|
+
timestamp: datetime | None = None,
|
|
75
|
+
**metadata: object,
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Add a message to history."""
|
|
78
|
+
if isinstance(message_type, str):
|
|
79
|
+
message_type = MessageType(message_type)
|
|
80
|
+
self._messages.append(
|
|
81
|
+
Message(
|
|
82
|
+
content=content,
|
|
83
|
+
message_type=message_type,
|
|
84
|
+
timestamp=timestamp or datetime.now(),
|
|
85
|
+
metadata=dict(metadata),
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def get_all(self) -> list[Message]:
|
|
90
|
+
"""Get all messages."""
|
|
91
|
+
return list(self._messages)
|
|
92
|
+
|
|
93
|
+
def get_by_type(self, message_type: MessageType) -> list[Message]:
|
|
94
|
+
"""Get messages of a specific type."""
|
|
95
|
+
return [m for m in self._messages if m.message_type == message_type]
|
|
96
|
+
|
|
97
|
+
def clear(self) -> None:
|
|
98
|
+
"""Clear all messages."""
|
|
99
|
+
self._messages.clear()
|
|
100
|
+
|
|
101
|
+
def __len__(self) -> int:
|
|
102
|
+
return len(self._messages)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# === Default Styles ===
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_default_styles() -> ThinkingPromptStyles:
|
|
109
|
+
"""Get default styles for ThinkingPromptSession."""
|
|
110
|
+
return ThinkingPromptStyles(
|
|
111
|
+
thinking_box="fg:#6c757d",
|
|
112
|
+
thinking_box_border="fg:#495057",
|
|
113
|
+
thinking_box_hint="fg:#6c757d italic",
|
|
114
|
+
user_message="fg:#17a2b8",
|
|
115
|
+
assistant_message="fg:#e9ecef",
|
|
116
|
+
system_message="fg:#6c757d italic",
|
|
117
|
+
error_message="fg:#dc3545 bold",
|
|
118
|
+
warning_message="fg:#ffc107",
|
|
119
|
+
success_message="fg:#28a745",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# === Base CLI Application ===
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BaseCLIApp(ABC):
|
|
127
|
+
"""
|
|
128
|
+
Base CLI Application for agentic applications.
|
|
129
|
+
|
|
130
|
+
Domain-specific applications extend this class and implement:
|
|
131
|
+
- get_app_info(): Provide app name, version, welcome message
|
|
132
|
+
- get_settings(): Provide domain-specific settings
|
|
133
|
+
- create_workflow_manager(): Create domain-specific workflow manager
|
|
134
|
+
- register_commands(): Register domain-specific commands (optional)
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, settings: BaseSettings | None = None) -> None:
|
|
138
|
+
"""Initialize the CLI application.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
settings: Optional settings override
|
|
142
|
+
"""
|
|
143
|
+
# === Configuration ===
|
|
144
|
+
self._settings = settings or self.get_settings()
|
|
145
|
+
configure_logging(self._settings)
|
|
146
|
+
|
|
147
|
+
logger.info("app_starting", app_name=self._settings.app_name)
|
|
148
|
+
|
|
149
|
+
# === Message History (for persistence) ===
|
|
150
|
+
self.message_history = MessageHistory()
|
|
151
|
+
|
|
152
|
+
# === Command Registry ===
|
|
153
|
+
self.command_registry = CommandRegistry()
|
|
154
|
+
self._register_builtin_commands()
|
|
155
|
+
self.register_commands() # Domain-specific commands
|
|
156
|
+
|
|
157
|
+
# === Workflow Manager (initialized in background) ===
|
|
158
|
+
self._workflow: WorkflowManager | None = None
|
|
159
|
+
self._init_task: asyncio.Task[None] | None = None
|
|
160
|
+
self._init_error: Exception | None = None
|
|
161
|
+
|
|
162
|
+
# === UI: ThinkingPromptSession ===
|
|
163
|
+
command_completions = [
|
|
164
|
+
"/" + name for name in self.command_registry.get_completions()
|
|
165
|
+
]
|
|
166
|
+
completer = WordCompleter(command_completions, ignore_case=True)
|
|
167
|
+
|
|
168
|
+
self.session = ThinkingPromptSession(
|
|
169
|
+
message=">>> ",
|
|
170
|
+
app_info=self.get_app_info(),
|
|
171
|
+
styles=self.get_styles(),
|
|
172
|
+
history=InMemoryHistory(),
|
|
173
|
+
completer=completer,
|
|
174
|
+
enable_status_bar=True,
|
|
175
|
+
status_text="Ctrl+C: cancel | Ctrl+D: exit | /help: commands",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# === State ===
|
|
179
|
+
self.should_exit = False
|
|
180
|
+
|
|
181
|
+
# === Rich Console (for commands that need direct console access) ===
|
|
182
|
+
self.console = Console()
|
|
183
|
+
|
|
184
|
+
logger.debug("app_initialized_fast")
|
|
185
|
+
|
|
186
|
+
@abstractmethod
|
|
187
|
+
def get_app_info(self) -> AppInfo:
|
|
188
|
+
"""Get the application info for ThinkingPromptSession.
|
|
189
|
+
|
|
190
|
+
Domain projects implement this to provide their app name,
|
|
191
|
+
version, and welcome message.
|
|
192
|
+
"""
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
@abstractmethod
|
|
196
|
+
def get_settings(self) -> BaseSettings:
|
|
197
|
+
"""Get the application settings.
|
|
198
|
+
|
|
199
|
+
Domain projects implement this to provide their settings class.
|
|
200
|
+
"""
|
|
201
|
+
...
|
|
202
|
+
|
|
203
|
+
@abstractmethod
|
|
204
|
+
def create_workflow_manager(self) -> "WorkflowManager":
|
|
205
|
+
"""Create the workflow manager for this domain.
|
|
206
|
+
|
|
207
|
+
Domain projects implement this to create their workflow manager
|
|
208
|
+
with domain-specific agents.
|
|
209
|
+
"""
|
|
210
|
+
...
|
|
211
|
+
|
|
212
|
+
def get_styles(self) -> ThinkingPromptStyles:
|
|
213
|
+
"""Get styles for ThinkingPromptSession.
|
|
214
|
+
|
|
215
|
+
Override to customize styles.
|
|
216
|
+
"""
|
|
217
|
+
return get_default_styles()
|
|
218
|
+
|
|
219
|
+
def register_commands(self) -> None:
|
|
220
|
+
"""Register domain-specific commands.
|
|
221
|
+
|
|
222
|
+
Override to register additional commands.
|
|
223
|
+
"""
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def workflow(self) -> "WorkflowManager":
|
|
228
|
+
"""Get the workflow manager.
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
RuntimeError: If workflow is not yet initialized
|
|
232
|
+
"""
|
|
233
|
+
if self._workflow is None:
|
|
234
|
+
raise RuntimeError("Workflow not initialized yet")
|
|
235
|
+
return self._workflow
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def default_user(self) -> str:
|
|
239
|
+
"""Get the default user name."""
|
|
240
|
+
return self._settings.default_user
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def settings(self) -> BaseSettings:
|
|
244
|
+
"""Get the application settings."""
|
|
245
|
+
return self._settings
|
|
246
|
+
|
|
247
|
+
def stop(self) -> None:
|
|
248
|
+
"""Stop the application."""
|
|
249
|
+
self.should_exit = True
|
|
250
|
+
self.session.exit()
|
|
251
|
+
|
|
252
|
+
def _register_builtin_commands(self) -> None:
|
|
253
|
+
"""Register built-in commands."""
|
|
254
|
+
from agentic_cli.cli.builtin_commands import (
|
|
255
|
+
HelpCommand,
|
|
256
|
+
ClearCommand,
|
|
257
|
+
ExitCommand,
|
|
258
|
+
StatusCommand,
|
|
259
|
+
SaveCommand,
|
|
260
|
+
LoadCommand,
|
|
261
|
+
SessionsCommand,
|
|
262
|
+
SettingsCommand,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
self.command_registry.register(HelpCommand())
|
|
266
|
+
self.command_registry.register(ClearCommand())
|
|
267
|
+
self.command_registry.register(ExitCommand())
|
|
268
|
+
self.command_registry.register(StatusCommand())
|
|
269
|
+
self.command_registry.register(SaveCommand())
|
|
270
|
+
self.command_registry.register(LoadCommand())
|
|
271
|
+
self.command_registry.register(SessionsCommand())
|
|
272
|
+
self.command_registry.register(SettingsCommand())
|
|
273
|
+
|
|
274
|
+
async def _background_init(self) -> None:
|
|
275
|
+
"""Initialize WorkflowManager in background thread."""
|
|
276
|
+
loop = asyncio.get_running_loop()
|
|
277
|
+
|
|
278
|
+
def _create_workflow() -> "WorkflowManager":
|
|
279
|
+
return self.create_workflow_manager()
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
logger.debug("background_init_starting")
|
|
283
|
+
self._workflow = await loop.run_in_executor(
|
|
284
|
+
_init_executor, _create_workflow
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Update status bar to show ready
|
|
288
|
+
model = self._workflow.model
|
|
289
|
+
self.session.status_text = f"{model} | Ctrl+C: cancel | /help: commands"
|
|
290
|
+
logger.info("background_init_complete", model=model)
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
self._init_error = e
|
|
294
|
+
# Don't log to console - error will be shown via UI when user interacts
|
|
295
|
+
self.session.status_text = "Init failed - check API keys"
|
|
296
|
+
|
|
297
|
+
async def _ensure_initialized(self) -> bool:
|
|
298
|
+
"""Wait for background initialization to complete.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
True if initialization succeeded, False otherwise
|
|
302
|
+
"""
|
|
303
|
+
if self._workflow is not None:
|
|
304
|
+
return True
|
|
305
|
+
|
|
306
|
+
if self._init_task is None:
|
|
307
|
+
return False
|
|
308
|
+
|
|
309
|
+
if not self._init_task.done():
|
|
310
|
+
# Show user we're waiting for initialization
|
|
311
|
+
self.session.start_thinking(lambda: "Waiting for initialization...")
|
|
312
|
+
try:
|
|
313
|
+
await self._init_task
|
|
314
|
+
finally:
|
|
315
|
+
self.session.finish_thinking(add_to_history=False)
|
|
316
|
+
|
|
317
|
+
if self._init_error:
|
|
318
|
+
self.session.add_error(f"Initialization failed: {self._init_error}")
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
return self._workflow is not None
|
|
322
|
+
|
|
323
|
+
async def process_input(self, user_input: str) -> None:
|
|
324
|
+
"""Process user input.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
user_input: The raw user input string
|
|
328
|
+
"""
|
|
329
|
+
user_input = user_input.strip()
|
|
330
|
+
|
|
331
|
+
if not user_input:
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
# Route to appropriate handler
|
|
335
|
+
if user_input.startswith("/"):
|
|
336
|
+
await self._handle_command(user_input)
|
|
337
|
+
else:
|
|
338
|
+
await self._handle_message(user_input)
|
|
339
|
+
|
|
340
|
+
async def _handle_command(self, user_input: str) -> None:
|
|
341
|
+
"""Handle slash command execution.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
user_input: The command string starting with /
|
|
345
|
+
"""
|
|
346
|
+
parts = user_input[1:].split(maxsplit=1)
|
|
347
|
+
command_name = parts[0] if parts else ""
|
|
348
|
+
args = parts[1] if len(parts) > 1 else ""
|
|
349
|
+
|
|
350
|
+
command = self.command_registry.get(command_name)
|
|
351
|
+
if command:
|
|
352
|
+
logger.debug("executing_command", command=command.name, args=args)
|
|
353
|
+
try:
|
|
354
|
+
self.session.add_response(f"Executing command: /{command.name} {args}")
|
|
355
|
+
await command.execute(args, self)
|
|
356
|
+
logger.debug("command_completed", command=command.name)
|
|
357
|
+
except Exception as e:
|
|
358
|
+
self.session.add_error(f"Error executing command: {e}")
|
|
359
|
+
else:
|
|
360
|
+
self.session.add_error(f"Unknown command: /{command_name}")
|
|
361
|
+
self.session.add_message("system", "Type /help to see available commands")
|
|
362
|
+
|
|
363
|
+
async def _handle_message(self, message: str) -> None:
|
|
364
|
+
"""Route message through agentic workflow.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
message: User message to process
|
|
368
|
+
"""
|
|
369
|
+
# Wait for initialization if needed
|
|
370
|
+
if not await self._ensure_initialized():
|
|
371
|
+
self.session.add_error(
|
|
372
|
+
"Cannot process message - workflow not initialized. "
|
|
373
|
+
"Please check your API keys (GOOGLE_API_KEY or ANTHROPIC_API_KEY)."
|
|
374
|
+
)
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
# Import EventType here (workflow module is now loaded)
|
|
378
|
+
from agentic_cli.workflow import EventType
|
|
379
|
+
|
|
380
|
+
bind_context(user_id=self._settings.default_user)
|
|
381
|
+
logger.info("handling_message", message_length=len(message))
|
|
382
|
+
|
|
383
|
+
# Track message in history
|
|
384
|
+
self.message_history.add(message, MessageType.USER)
|
|
385
|
+
|
|
386
|
+
# Status line for thinking box (single line updates)
|
|
387
|
+
status_line = "Processing..."
|
|
388
|
+
thinking_started = False
|
|
389
|
+
|
|
390
|
+
# Accumulate content for history
|
|
391
|
+
thinking_content: list[str] = []
|
|
392
|
+
response_content: list[str] = []
|
|
393
|
+
|
|
394
|
+
def get_status() -> str:
|
|
395
|
+
return status_line
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
self.session.start_thinking(get_status)
|
|
399
|
+
thinking_started = True
|
|
400
|
+
|
|
401
|
+
async for event in self.workflow.process(
|
|
402
|
+
message=message,
|
|
403
|
+
user_id=self._settings.default_user,
|
|
404
|
+
):
|
|
405
|
+
if event.type == EventType.TEXT:
|
|
406
|
+
# Stream response directly to console
|
|
407
|
+
self.session.add_response(event.content, markdown=True)
|
|
408
|
+
response_content.append(event.content)
|
|
409
|
+
|
|
410
|
+
elif event.type == EventType.THINKING:
|
|
411
|
+
# Stream thinking directly to console
|
|
412
|
+
status_line = "Thinking..."
|
|
413
|
+
self.session.add_message("system", event.content)
|
|
414
|
+
thinking_content.append(event.content)
|
|
415
|
+
|
|
416
|
+
elif event.type == EventType.TOOL_CALL:
|
|
417
|
+
# Update status line in thinking box
|
|
418
|
+
tool_name = event.metadata.get("tool_name", "unknown")
|
|
419
|
+
status_line = f"Calling: {tool_name}"
|
|
420
|
+
|
|
421
|
+
elif event.type == EventType.CODE_EXECUTION:
|
|
422
|
+
# Update status with execution result
|
|
423
|
+
result_preview = (
|
|
424
|
+
event.content[:40] + "..."
|
|
425
|
+
if len(event.content) > 40
|
|
426
|
+
else event.content
|
|
427
|
+
)
|
|
428
|
+
status_line = f"Result: {result_preview}"
|
|
429
|
+
|
|
430
|
+
elif event.type == EventType.EXECUTABLE_CODE:
|
|
431
|
+
# Update status when executing code
|
|
432
|
+
lang = event.metadata.get("language", "python")
|
|
433
|
+
status_line = f"Running {lang} code..."
|
|
434
|
+
|
|
435
|
+
elif event.type == EventType.FILE_DATA:
|
|
436
|
+
# Update status with file info
|
|
437
|
+
status_line = f"File: {event.content}"
|
|
438
|
+
|
|
439
|
+
# Finish thinking box (don't add status to history)
|
|
440
|
+
if thinking_started:
|
|
441
|
+
self.session.finish_thinking(add_to_history=False)
|
|
442
|
+
|
|
443
|
+
# Add accumulated content to message history
|
|
444
|
+
if thinking_content:
|
|
445
|
+
self.message_history.add(
|
|
446
|
+
"".join(thinking_content), MessageType.THINKING
|
|
447
|
+
)
|
|
448
|
+
if response_content:
|
|
449
|
+
self.message_history.add(
|
|
450
|
+
"".join(response_content), MessageType.ASSISTANT
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
logger.debug("message_handled_successfully")
|
|
454
|
+
|
|
455
|
+
except Exception as e:
|
|
456
|
+
if thinking_started:
|
|
457
|
+
self.session.finish_thinking(add_to_history=False)
|
|
458
|
+
self.session.add_error(f"Workflow error: {e}")
|
|
459
|
+
self.message_history.add(str(e), MessageType.ERROR)
|
|
460
|
+
|
|
461
|
+
async def run(self) -> None:
|
|
462
|
+
"""Run the main application loop."""
|
|
463
|
+
logger.info("repl_starting")
|
|
464
|
+
|
|
465
|
+
# Start background initialization (non-blocking)
|
|
466
|
+
self._init_task = asyncio.create_task(self._background_init())
|
|
467
|
+
|
|
468
|
+
# Register input handler
|
|
469
|
+
@self.session.on_input
|
|
470
|
+
async def handle_input(text: str) -> None:
|
|
471
|
+
if self.should_exit:
|
|
472
|
+
return
|
|
473
|
+
await self.process_input(text)
|
|
474
|
+
|
|
475
|
+
# Run the session - user sees prompt immediately!
|
|
476
|
+
await self.session.run_async()
|
|
477
|
+
|
|
478
|
+
# Cleanup: cancel init task if still running
|
|
479
|
+
if self._init_task and not self._init_task.done():
|
|
480
|
+
self._init_task.cancel()
|
|
481
|
+
try:
|
|
482
|
+
await self._init_task
|
|
483
|
+
except asyncio.CancelledError:
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
logger.info("app_ending")
|
|
487
|
+
self.session.add_message("system", "Goodbye!")
|