kweaver-dolphin 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.
Files changed (199) hide show
  1. DolphinLanguageSDK/__init__.py +58 -0
  2. dolphin/__init__.py +62 -0
  3. dolphin/cli/__init__.py +20 -0
  4. dolphin/cli/args/__init__.py +9 -0
  5. dolphin/cli/args/parser.py +567 -0
  6. dolphin/cli/builtin_agents/__init__.py +22 -0
  7. dolphin/cli/commands/__init__.py +4 -0
  8. dolphin/cli/interrupt/__init__.py +8 -0
  9. dolphin/cli/interrupt/handler.py +205 -0
  10. dolphin/cli/interrupt/keyboard.py +82 -0
  11. dolphin/cli/main.py +49 -0
  12. dolphin/cli/multimodal/__init__.py +34 -0
  13. dolphin/cli/multimodal/clipboard.py +327 -0
  14. dolphin/cli/multimodal/handler.py +249 -0
  15. dolphin/cli/multimodal/image_processor.py +214 -0
  16. dolphin/cli/multimodal/input_parser.py +149 -0
  17. dolphin/cli/runner/__init__.py +8 -0
  18. dolphin/cli/runner/runner.py +989 -0
  19. dolphin/cli/ui/__init__.py +10 -0
  20. dolphin/cli/ui/console.py +2795 -0
  21. dolphin/cli/ui/input.py +340 -0
  22. dolphin/cli/ui/layout.py +425 -0
  23. dolphin/cli/ui/stream_renderer.py +302 -0
  24. dolphin/cli/utils/__init__.py +8 -0
  25. dolphin/cli/utils/helpers.py +135 -0
  26. dolphin/cli/utils/version.py +49 -0
  27. dolphin/core/__init__.py +107 -0
  28. dolphin/core/agent/__init__.py +10 -0
  29. dolphin/core/agent/agent_state.py +69 -0
  30. dolphin/core/agent/base_agent.py +970 -0
  31. dolphin/core/code_block/__init__.py +0 -0
  32. dolphin/core/code_block/agent_init_block.py +0 -0
  33. dolphin/core/code_block/assign_block.py +98 -0
  34. dolphin/core/code_block/basic_code_block.py +1865 -0
  35. dolphin/core/code_block/explore_block.py +1327 -0
  36. dolphin/core/code_block/explore_block_v2.py +712 -0
  37. dolphin/core/code_block/explore_strategy.py +672 -0
  38. dolphin/core/code_block/judge_block.py +220 -0
  39. dolphin/core/code_block/prompt_block.py +32 -0
  40. dolphin/core/code_block/skill_call_deduplicator.py +291 -0
  41. dolphin/core/code_block/tool_block.py +129 -0
  42. dolphin/core/common/__init__.py +17 -0
  43. dolphin/core/common/constants.py +176 -0
  44. dolphin/core/common/enums.py +1173 -0
  45. dolphin/core/common/exceptions.py +133 -0
  46. dolphin/core/common/multimodal.py +539 -0
  47. dolphin/core/common/object_type.py +165 -0
  48. dolphin/core/common/output_format.py +432 -0
  49. dolphin/core/common/types.py +36 -0
  50. dolphin/core/config/__init__.py +16 -0
  51. dolphin/core/config/global_config.py +1289 -0
  52. dolphin/core/config/ontology_config.py +133 -0
  53. dolphin/core/context/__init__.py +12 -0
  54. dolphin/core/context/context.py +1580 -0
  55. dolphin/core/context/context_manager.py +161 -0
  56. dolphin/core/context/var_output.py +82 -0
  57. dolphin/core/context/variable_pool.py +356 -0
  58. dolphin/core/context_engineer/__init__.py +41 -0
  59. dolphin/core/context_engineer/config/__init__.py +5 -0
  60. dolphin/core/context_engineer/config/settings.py +402 -0
  61. dolphin/core/context_engineer/core/__init__.py +7 -0
  62. dolphin/core/context_engineer/core/budget_manager.py +327 -0
  63. dolphin/core/context_engineer/core/context_assembler.py +583 -0
  64. dolphin/core/context_engineer/core/context_manager.py +637 -0
  65. dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
  66. dolphin/core/context_engineer/example/incremental_example.py +267 -0
  67. dolphin/core/context_engineer/example/traditional_example.py +334 -0
  68. dolphin/core/context_engineer/services/__init__.py +5 -0
  69. dolphin/core/context_engineer/services/compressor.py +399 -0
  70. dolphin/core/context_engineer/utils/__init__.py +6 -0
  71. dolphin/core/context_engineer/utils/context_utils.py +441 -0
  72. dolphin/core/context_engineer/utils/message_formatter.py +270 -0
  73. dolphin/core/context_engineer/utils/token_utils.py +139 -0
  74. dolphin/core/coroutine/__init__.py +15 -0
  75. dolphin/core/coroutine/context_snapshot.py +154 -0
  76. dolphin/core/coroutine/context_snapshot_profile.py +922 -0
  77. dolphin/core/coroutine/context_snapshot_store.py +268 -0
  78. dolphin/core/coroutine/execution_frame.py +145 -0
  79. dolphin/core/coroutine/execution_state_registry.py +161 -0
  80. dolphin/core/coroutine/resume_handle.py +101 -0
  81. dolphin/core/coroutine/step_result.py +101 -0
  82. dolphin/core/executor/__init__.py +18 -0
  83. dolphin/core/executor/debug_controller.py +630 -0
  84. dolphin/core/executor/dolphin_executor.py +1063 -0
  85. dolphin/core/executor/executor.py +624 -0
  86. dolphin/core/flags/__init__.py +27 -0
  87. dolphin/core/flags/definitions.py +49 -0
  88. dolphin/core/flags/manager.py +113 -0
  89. dolphin/core/hook/__init__.py +95 -0
  90. dolphin/core/hook/expression_evaluator.py +499 -0
  91. dolphin/core/hook/hook_dispatcher.py +380 -0
  92. dolphin/core/hook/hook_types.py +248 -0
  93. dolphin/core/hook/isolated_variable_pool.py +284 -0
  94. dolphin/core/interfaces.py +53 -0
  95. dolphin/core/llm/__init__.py +0 -0
  96. dolphin/core/llm/llm.py +495 -0
  97. dolphin/core/llm/llm_call.py +100 -0
  98. dolphin/core/llm/llm_client.py +1285 -0
  99. dolphin/core/llm/message_sanitizer.py +120 -0
  100. dolphin/core/logging/__init__.py +20 -0
  101. dolphin/core/logging/logger.py +526 -0
  102. dolphin/core/message/__init__.py +8 -0
  103. dolphin/core/message/compressor.py +749 -0
  104. dolphin/core/parser/__init__.py +8 -0
  105. dolphin/core/parser/parser.py +405 -0
  106. dolphin/core/runtime/__init__.py +10 -0
  107. dolphin/core/runtime/runtime_graph.py +926 -0
  108. dolphin/core/runtime/runtime_instance.py +446 -0
  109. dolphin/core/skill/__init__.py +14 -0
  110. dolphin/core/skill/context_retention.py +157 -0
  111. dolphin/core/skill/skill_function.py +686 -0
  112. dolphin/core/skill/skill_matcher.py +282 -0
  113. dolphin/core/skill/skillkit.py +700 -0
  114. dolphin/core/skill/skillset.py +72 -0
  115. dolphin/core/trajectory/__init__.py +10 -0
  116. dolphin/core/trajectory/recorder.py +189 -0
  117. dolphin/core/trajectory/trajectory.py +522 -0
  118. dolphin/core/utils/__init__.py +9 -0
  119. dolphin/core/utils/cache_kv.py +212 -0
  120. dolphin/core/utils/tools.py +340 -0
  121. dolphin/lib/__init__.py +93 -0
  122. dolphin/lib/debug/__init__.py +8 -0
  123. dolphin/lib/debug/visualizer.py +409 -0
  124. dolphin/lib/memory/__init__.py +28 -0
  125. dolphin/lib/memory/async_processor.py +220 -0
  126. dolphin/lib/memory/llm_calls.py +195 -0
  127. dolphin/lib/memory/manager.py +78 -0
  128. dolphin/lib/memory/sandbox.py +46 -0
  129. dolphin/lib/memory/storage.py +245 -0
  130. dolphin/lib/memory/utils.py +51 -0
  131. dolphin/lib/ontology/__init__.py +12 -0
  132. dolphin/lib/ontology/basic/__init__.py +0 -0
  133. dolphin/lib/ontology/basic/base.py +102 -0
  134. dolphin/lib/ontology/basic/concept.py +130 -0
  135. dolphin/lib/ontology/basic/object.py +11 -0
  136. dolphin/lib/ontology/basic/relation.py +63 -0
  137. dolphin/lib/ontology/datasource/__init__.py +27 -0
  138. dolphin/lib/ontology/datasource/datasource.py +66 -0
  139. dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
  140. dolphin/lib/ontology/datasource/sql.py +845 -0
  141. dolphin/lib/ontology/mapping.py +177 -0
  142. dolphin/lib/ontology/ontology.py +733 -0
  143. dolphin/lib/ontology/ontology_context.py +16 -0
  144. dolphin/lib/ontology/ontology_manager.py +107 -0
  145. dolphin/lib/skill_results/__init__.py +31 -0
  146. dolphin/lib/skill_results/cache_backend.py +559 -0
  147. dolphin/lib/skill_results/result_processor.py +181 -0
  148. dolphin/lib/skill_results/result_reference.py +179 -0
  149. dolphin/lib/skill_results/skillkit_hook.py +324 -0
  150. dolphin/lib/skill_results/strategies.py +328 -0
  151. dolphin/lib/skill_results/strategy_registry.py +150 -0
  152. dolphin/lib/skillkits/__init__.py +44 -0
  153. dolphin/lib/skillkits/agent_skillkit.py +155 -0
  154. dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
  155. dolphin/lib/skillkits/env_skillkit.py +250 -0
  156. dolphin/lib/skillkits/mcp_adapter.py +616 -0
  157. dolphin/lib/skillkits/mcp_skillkit.py +771 -0
  158. dolphin/lib/skillkits/memory_skillkit.py +650 -0
  159. dolphin/lib/skillkits/noop_skillkit.py +31 -0
  160. dolphin/lib/skillkits/ontology_skillkit.py +89 -0
  161. dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
  162. dolphin/lib/skillkits/resource/__init__.py +52 -0
  163. dolphin/lib/skillkits/resource/models/__init__.py +6 -0
  164. dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
  165. dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
  166. dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
  167. dolphin/lib/skillkits/resource/skill_cache.py +215 -0
  168. dolphin/lib/skillkits/resource/skill_loader.py +395 -0
  169. dolphin/lib/skillkits/resource/skill_validator.py +406 -0
  170. dolphin/lib/skillkits/resource_skillkit.py +11 -0
  171. dolphin/lib/skillkits/search_skillkit.py +163 -0
  172. dolphin/lib/skillkits/sql_skillkit.py +274 -0
  173. dolphin/lib/skillkits/system_skillkit.py +509 -0
  174. dolphin/lib/skillkits/vm_skillkit.py +65 -0
  175. dolphin/lib/utils/__init__.py +9 -0
  176. dolphin/lib/utils/data_process.py +207 -0
  177. dolphin/lib/utils/handle_progress.py +178 -0
  178. dolphin/lib/utils/security.py +139 -0
  179. dolphin/lib/utils/text_retrieval.py +462 -0
  180. dolphin/lib/vm/__init__.py +11 -0
  181. dolphin/lib/vm/env_executor.py +895 -0
  182. dolphin/lib/vm/python_session_manager.py +453 -0
  183. dolphin/lib/vm/vm.py +610 -0
  184. dolphin/sdk/__init__.py +60 -0
  185. dolphin/sdk/agent/__init__.py +12 -0
  186. dolphin/sdk/agent/agent_factory.py +236 -0
  187. dolphin/sdk/agent/dolphin_agent.py +1106 -0
  188. dolphin/sdk/api/__init__.py +4 -0
  189. dolphin/sdk/runtime/__init__.py +8 -0
  190. dolphin/sdk/runtime/env.py +363 -0
  191. dolphin/sdk/skill/__init__.py +10 -0
  192. dolphin/sdk/skill/global_skills.py +706 -0
  193. dolphin/sdk/skill/traditional_toolkit.py +260 -0
  194. kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
  195. kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
  196. kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
  197. kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
  198. kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
  199. kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,989 @@
1
+ """
2
+ Dolphin Agent Runner
3
+
4
+ This module contains the execution logic for running Dolphin agents.
5
+ The main runDolphinAgent function has been refactored into smaller,
6
+ single-responsibility functions for better maintainability.
7
+
8
+ Features:
9
+ - Fixed bottom input layout with scrollable content
10
+ - ESC key interrupt support for agent execution
11
+ - Status bar with spinner animation during processing
12
+ """
13
+
14
+ import asyncio
15
+ import datetime
16
+ import json
17
+ import logging
18
+ import os
19
+ import random
20
+ import sys
21
+ import traceback
22
+ import uuid
23
+ from typing import Any, Dict, Optional
24
+
25
+ from rich.console import Console as RichConsole
26
+
27
+ from dolphin.core import flags
28
+ from dolphin.core.common.exceptions import DebuggerQuitException, UserInterrupt
29
+ from dolphin.core.agent.agent_state import AgentState, PauseType
30
+ from dolphin.core.logging.logger import console
31
+ from dolphin.cli.ui.console import console_session_start, console_display_session_info
32
+
33
+ from dolphin.cli.args.parser import Args
34
+ from dolphin.cli.utils.helpers import buildVariables, outputVariablesToJson
35
+ from dolphin.cli.interrupt.handler import InterruptToken
36
+ from dolphin.cli.ui.layout import LayoutManager
37
+
38
+
39
+ async def initializeEnvironment(args: Args):
40
+ """Initialize Dolphin environment
41
+
42
+ Args:
43
+ args: Parsed CLI arguments
44
+
45
+ Returns:
46
+ Tuple of (env, globalConfig)
47
+ """
48
+ from dolphin.sdk.runtime.env import Env
49
+ from dolphin.core.config.global_config import GlobalConfig
50
+
51
+ globalConfigPath = args.config if args.config else "./config/global.yaml"
52
+ globalConfig = GlobalConfig.from_yaml(globalConfigPath)
53
+
54
+ env = Env(
55
+ globalConfig=globalConfig,
56
+ agentFolderPath=args.folder,
57
+ skillkitFolderPath=args.skillFolder,
58
+ output_variables=[],
59
+ verbose=args.saveHistory,
60
+ is_cli=True, # CLI mode: enable Rich/terminal beautification
61
+ log_level=(
62
+ logging.DEBUG if flags.is_enabled(flags.DEBUG_MODE) else logging.INFO
63
+ ),
64
+ )
65
+
66
+ return env, globalConfig
67
+
68
+
69
+ async def loadAndPrepareAgent(env, args: Args, initialVariables: Dict[str, Any]):
70
+ """Load and prepare agent for execution
71
+
72
+ Args:
73
+ env: Dolphin environment
74
+ args: Parsed CLI arguments
75
+ initialVariables: Initial variables to pass to agent
76
+
77
+ Returns:
78
+ Prepared agent instance
79
+ """
80
+ from dolphin.sdk.agent.dolphin_agent import DolphinAgent
81
+
82
+ availableAgents = env.getAgentNames()
83
+
84
+ if args.agent not in availableAgents:
85
+ console(f"Error: Agent '{args.agent}' not found in folder '{args.folder}'")
86
+ console(f"Available agents: {availableAgents}")
87
+ sys.exit(1)
88
+
89
+ agent = env.getAgent(args.agent)
90
+
91
+ # Check if agent is in ERROR state and reset if needed
92
+ if (agent is not None and hasattr(agent, "state") and agent.state.name == "ERROR"):
93
+ agent = await _recoverAgentFromError(env, args, agent)
94
+
95
+ await agent.initialize()
96
+
97
+ if agent is None:
98
+ console(f"Error: Failed to get agent instance for '{args.agent}'")
99
+ sys.exit(1)
100
+
101
+ # Configure trajectory
102
+ if args.trajectoryPath:
103
+ agent.set_trajectorypath(args.trajectoryPath)
104
+ agent.set_agent_name(args.agent)
105
+
106
+ # Setup context
107
+ env._setupAgentContext(agent)
108
+
109
+ # Initialize with variables
110
+ if initialVariables:
111
+ initParams = {"variables": initialVariables}
112
+ await agent.executor.executor_init(initParams)
113
+
114
+ return agent
115
+
116
+
117
+ async def _recoverAgentFromError(env, args: Args, agent):
118
+ """Recover agent from ERROR state
119
+
120
+ Args:
121
+ env: Dolphin environment
122
+ args: Parsed CLI arguments
123
+ agent: Agent in error state
124
+
125
+ Returns:
126
+ Recovered agent instance
127
+ """
128
+ from dolphin.sdk.agent.dolphin_agent import DolphinAgent
129
+
130
+ agentFilePath = None
131
+ for filePath in env._scanDolphinFiles(args.folder):
132
+ try:
133
+ tempAgent = DolphinAgent(
134
+ file_path=filePath,
135
+ global_config=env.globalConfig,
136
+ global_skills=env.globalSkills,
137
+ global_types=env.global_types,
138
+ )
139
+ if tempAgent.get_name() == args.agent:
140
+ agentFilePath = filePath
141
+ break
142
+ except Exception:
143
+ continue
144
+
145
+ if agentFilePath:
146
+ freshAgent = DolphinAgent(
147
+ file_path=agentFilePath,
148
+ global_config=env.globalConfig,
149
+ global_skills=env.globalSkills,
150
+ global_types=env.global_types,
151
+ )
152
+ env.agents[args.agent] = freshAgent
153
+ return freshAgent
154
+ else:
155
+ console(
156
+ f"Warning: Could not find file path for agent '{args.agent}', "
157
+ "proceeding with existing instance"
158
+ )
159
+ return agent
160
+
161
+
162
+ def _get_skillkit_info(agent) -> Optional[Dict[str, int]]:
163
+ """Extract skillkit information from agent for display.
164
+
165
+ Args:
166
+ agent: The DolphinAgent instance
167
+
168
+ Returns:
169
+ Dict mapping skillkit name to tool count, or None if unavailable
170
+ """
171
+ try:
172
+ context = agent.get_context()
173
+ if context is None:
174
+ return None
175
+
176
+ all_skills = context.all_skills.getSkills() if context.all_skills else []
177
+ if not all_skills:
178
+ return None
179
+
180
+ # Group skills by owner skillkit name
181
+ skillkit_counts: Dict[str, int] = {}
182
+ for skill in all_skills:
183
+ owner_name = getattr(skill, 'owner_name', None)
184
+ if owner_name:
185
+ skillkit_counts[owner_name] = skillkit_counts.get(owner_name, 0) + 1
186
+ else:
187
+ # Skills without owner go to "builtin"
188
+ skillkit_counts["builtin"] = skillkit_counts.get("builtin", 0) + 1
189
+
190
+ return skillkit_counts if skillkit_counts else None
191
+ except Exception:
192
+ return None
193
+
194
+
195
+ def _handle_user_interrupt(agent, layout, source: str) -> None:
196
+ """Handle user interrupt (ESC or Ctrl-C) by setting up agent state for resumption.
197
+
198
+ This is a shared handler for both UserInterrupt and asyncio.CancelledError,
199
+ as they have identical semantics: user wants to provide new input.
200
+
201
+ Args:
202
+ agent: The DolphinAgent instance
203
+ layout: The LayoutManager instance
204
+ source: String identifying the interrupt source for logging ("UserInterrupt" or "CancelledError")
205
+ """
206
+ from dolphin.core.agent.agent_state import AgentState, PauseType
207
+ from dolphin.cli.ui.console import StatusBar
208
+
209
+ layout.hide_status()
210
+
211
+ # Set agent state for proper resumption with context preservation
212
+ agent._state = AgentState.PAUSED
213
+ agent._pause_type = PauseType.USER_INTERRUPT
214
+
215
+ # Clear the interrupt event so future calls don't immediately re-interrupt
216
+ if hasattr(agent, 'clear_interrupt'):
217
+ agent.clear_interrupt()
218
+ elif hasattr(agent, 'get_interrupt_event'):
219
+ event = agent.get_interrupt_event()
220
+ if event:
221
+ event.clear()
222
+
223
+ StatusBar._debug_log(f"_handle_user_interrupt: handled {source}, agent state set to PAUSED/USER_INTERRUPT")
224
+
225
+
226
+ async def runConversationLoop(agent, args: Args, initialVariables: Dict[str, Any]) -> bool:
227
+ """Run the main conversation loop with fixed layout and interrupt support.
228
+
229
+ Args:
230
+ agent: Agent instance
231
+ args: Parsed CLI arguments
232
+ initialVariables: Initial variables
233
+
234
+ Returns:
235
+ True if should enter post-mortem after interactive mode ends
236
+ """
237
+ # Initialize layout manager and interrupt token
238
+ layout = LayoutManager(enabled=args.interactive)
239
+ interrupt_token = InterruptToken()
240
+
241
+ currentQuery = args.query
242
+
243
+ if currentQuery:
244
+ agent.add_bucket(bucket_name="_query", content=currentQuery)
245
+
246
+ if args.interactive:
247
+ mode = "Interactive"
248
+ # Setup scroll region FIRST, then print banner inside the scrollable area
249
+ layout.start_session(mode, args.agent)
250
+ console_session_start(mode, args.agent)
251
+
252
+ # Display available skillkits and command hints
253
+ skillkit_info = _get_skillkit_info(agent)
254
+ console_display_session_info(skillkit_info, show_commands=True)
255
+
256
+ if flags.is_enabled(flags.DEBUG_MODE):
257
+ console("💡 输入 /debug 进入实时调试,/trace /snapshot /vars 快速查看", verbose=args.saveHistory)
258
+ else:
259
+ mode = "Execution"
260
+ # No layout for non-interactive mode, just print banner
261
+ console_session_start(mode, args.agent)
262
+
263
+ # Display available skillkits (no command hints in non-interactive mode)
264
+ skillkit_info = _get_skillkit_info(agent)
265
+ console_display_session_info(skillkit_info, show_commands=False)
266
+
267
+ isFirstExecution = True
268
+ enterPostmortemAfterInteractive = False
269
+
270
+ # DEBUG: Import StatusBar for logging
271
+ from dolphin.cli.ui.console import StatusBar
272
+ StatusBar._debug_log(f"runConversationLoop: starting, interactive={args.interactive}, currentQuery={currentQuery!r}")
273
+
274
+ try:
275
+ # Bind interrupt token to agent and event loop
276
+ interrupt_token.bind(agent, asyncio.get_running_loop())
277
+
278
+ while True:
279
+ StatusBar._debug_log(f"runConversationLoop: loop iteration, isFirstExecution={isFirstExecution}, currentQuery={currentQuery!r}")
280
+
281
+ # Prompt for input if not first execution and interactive mode
282
+ if not currentQuery and args.interactive and not isFirstExecution:
283
+ StatusBar._debug_log(f"runConversationLoop: calling _promptUserInput")
284
+ currentQuery, shouldBreak, debugCommand = await _promptUserInput(
285
+ args, interrupt_token
286
+ )
287
+
288
+ # Handle live debug command
289
+ if debugCommand is not None:
290
+ await _handleLiveDebugCommand(agent, debugCommand)
291
+ currentQuery = None
292
+ continue
293
+
294
+ if shouldBreak:
295
+ if flags.is_enabled(flags.DEBUG_MODE) and args.interactive:
296
+ enterPostmortemAfterInteractive = True
297
+ break
298
+
299
+ try:
300
+ # Clear interrupt state before execution
301
+ interrupt_token.clear()
302
+
303
+ # Show inline status bar (simplified - no fixed positioning)
304
+ if args.interactive:
305
+ layout.show_status("Processing your request", "esc to interrupt")
306
+
307
+ # Start keyboard monitor for ESC interrupt
308
+ monitor_stop = None
309
+ monitor_task = None
310
+ if args.interactive:
311
+ import threading
312
+ from dolphin.cli.interrupt.keyboard import _monitor_interrupt
313
+ monitor_stop = threading.Event()
314
+ monitor_task = asyncio.create_task(_monitor_interrupt(interrupt_token, monitor_stop))
315
+
316
+ try:
317
+ if isFirstExecution:
318
+ StatusBar._debug_log(f"runConversationLoop: running first execution")
319
+ await _runFirstExecution(agent, args, initialVariables)
320
+ isFirstExecution = False
321
+ StatusBar._debug_log(f"runConversationLoop: first execution done")
322
+ else:
323
+ await _runSubsequentExecution(agent, args, currentQuery)
324
+ finally:
325
+ # Stop keyboard monitor
326
+ if monitor_stop:
327
+ monitor_stop.set()
328
+ if monitor_task:
329
+ try:
330
+ await monitor_task
331
+ except:
332
+ pass
333
+
334
+ # Hide status bar after completion
335
+ if args.interactive:
336
+ layout.hide_status()
337
+ StatusBar._debug_log(f"runConversationLoop: status bar hidden")
338
+
339
+ except DebuggerQuitException:
340
+ layout.hide_status()
341
+ console("✅ 调试会话已结束。")
342
+ break
343
+ except UserInterrupt:
344
+ # UserInterrupt: user pressed ESC, interrupt() was called
345
+ StatusBar._debug_log(f"runConversationLoop: UserInterrupt caught, continuing loop")
346
+ if args.interactive:
347
+ _handle_user_interrupt(agent, layout, "UserInterrupt")
348
+ isFirstExecution = False
349
+ else:
350
+ raise
351
+ except asyncio.CancelledError:
352
+ # CancelledError: Ctrl-C SIGINT or asyncio task cancellation
353
+ StatusBar._debug_log(f"runConversationLoop: CancelledError caught, continuing loop")
354
+ if args.interactive:
355
+ _handle_user_interrupt(agent, layout, "CancelledError")
356
+ isFirstExecution = False
357
+ else:
358
+ raise
359
+ except Exception as e:
360
+ StatusBar._debug_log(f"runConversationLoop: Exception caught: {type(e).__name__}: {e}")
361
+ raise
362
+
363
+ currentQuery = None
364
+ StatusBar._debug_log(f"runConversationLoop: after execution, about to check interactive={args.interactive}")
365
+
366
+
367
+ if not args.interactive:
368
+ StatusBar._debug_log(f"runConversationLoop: not interactive, breaking")
369
+ break
370
+
371
+ # Final output for interactive mode
372
+ if args.interactive and args.outputVariables:
373
+ outputVariablesToJson(agent.get_context(), args.outputVariables)
374
+
375
+ finally:
376
+ StatusBar._debug_log(f"runConversationLoop: finally block executing")
377
+ # Cleanup
378
+ interrupt_token.unbind()
379
+ if args.interactive:
380
+ layout.end_session()
381
+
382
+ return enterPostmortemAfterInteractive
383
+
384
+
385
+ async def _promptUserInput(
386
+ args: Args,
387
+ interrupt_token: Optional[InterruptToken] = None
388
+ ) -> tuple:
389
+ """Prompt user for input in interactive mode with ESC interrupt support.
390
+
391
+ Simplified version: input follows content naturally, no scroll region management.
392
+
393
+ Args:
394
+ args: Parsed CLI arguments
395
+ interrupt_token: Optional InterruptToken for ESC handling
396
+
397
+ Returns:
398
+ Tuple of (query, shouldBreak, debugCommand)
399
+ - query: User query string or None
400
+ - shouldBreak: Whether to break the conversation loop
401
+ - debugCommand: Debug command if user requested live debug, else None
402
+ """
403
+ try:
404
+ from dolphin.cli.ui.input import (
405
+ prompt_conversation_with_multimodal,
406
+ EscapeInterrupt
407
+ )
408
+ from dolphin.cli.ui.console import StatusBar
409
+ import sys
410
+
411
+ StatusBar._debug_log(f"_promptUserInput: starting (simplified)")
412
+
413
+ # Ensure cursor is visible before prompting
414
+ sys.stdout.write("\033[?25h")
415
+ sys.stdout.flush()
416
+
417
+ try:
418
+ # Get any real-time input buffered while the agent was running
419
+ default_text = ""
420
+ if interrupt_token:
421
+ default_text = interrupt_token.get_realtime_input(consume=True)
422
+ if default_text:
423
+ StatusBar._debug_log(f"_promptUserInput: found realtime buffer: {default_text!r}")
424
+
425
+ # Use multimodal-aware prompt that processes @paste, @image:, @url: syntax
426
+ # Returns: str for plain text, List[Dict] for multimodal content
427
+ currentQuery = await prompt_conversation_with_multimodal(
428
+ prompt_text="> ",
429
+ interrupt_token=interrupt_token,
430
+ verbose=True
431
+ )
432
+ StatusBar._debug_log(f"_promptUserInput: got input: {currentQuery!r}")
433
+
434
+ except EscapeInterrupt:
435
+ # ESC pressed during input - treat as empty input
436
+ StatusBar._debug_log(f"_promptUserInput: EscapeInterrupt")
437
+ return None, False, None
438
+
439
+ except (EOFError, KeyboardInterrupt):
440
+ from dolphin.cli.ui.console import console_conversation_end
441
+ console_conversation_end()
442
+ return None, True, None
443
+
444
+ # Handle multimodal content (List[Dict]) - return directly without string checks
445
+ if isinstance(currentQuery, list):
446
+ # This is multimodal content, return as-is
447
+ return currentQuery, False, None
448
+
449
+ # For string input, check exit commands and debug prefixes
450
+ if not currentQuery or currentQuery.lower().strip() in ["exit", "quit", "q", ""]:
451
+ console("Conversation ended", verbose=args.saveHistory)
452
+ return None, True, None
453
+
454
+ # Check for debug command prefixes (live debug mode)
455
+ # /debug enters REPL, others execute once and return to conversation
456
+ debugPrefixes = {
457
+ "/debug": None, # /debug or /debug <cmd> -> enters REPL or executes single cmd
458
+ "/trace": "trace",
459
+ "/snapshot": "snapshot",
460
+ "/vars": "vars",
461
+ "/var": "var",
462
+ "/progress": "progress",
463
+ "/help": "help",
464
+ }
465
+
466
+ queryLower = currentQuery.lower()
467
+ for prefix, defaultCmd in debugPrefixes.items():
468
+ if queryLower.startswith(prefix):
469
+ # Extract the debug command
470
+ remainder = currentQuery[len(prefix):].strip()
471
+ if defaultCmd:
472
+ # For /trace, /snapshot, etc., the command is the prefix itself
473
+ debugCmd = f"{defaultCmd} {remainder}".strip() if remainder else defaultCmd
474
+ else:
475
+ # For /debug, remainder is the full command (or 'help' if empty)
476
+ debugCmd = remainder if remainder else "help"
477
+ return None, False, debugCmd
478
+
479
+ return currentQuery, False, None
480
+
481
+
482
+ async def _handleLiveDebugCommand(agent, debugCommand: str) -> None:
483
+ """Handle live debug command during conversation
484
+
485
+ Args:
486
+ agent: Agent instance
487
+ debugCommand: Debug command string (e.g., "trace", "vars", "snapshot json")
488
+ """
489
+ debugCtrl = getattr(agent.executor, 'debug_controller', None)
490
+
491
+ if debugCtrl is None:
492
+ # No debug controller yet, create a temporary one for inspection
493
+ from dolphin.core.executor.debug_controller import DebugController
494
+ context = agent.get_context()
495
+ if context is None:
496
+ console("⚠️ Agent 尚未初始化,无法进入调试模式")
497
+ return
498
+ debugCtrl = DebugController(context)
499
+
500
+ await debugCtrl.enter_live_debug(debugCommand)
501
+
502
+
503
+ async def _runFirstExecution(agent, args: Args, initialVariables: Dict[str, Any]) -> None:
504
+ """Run first execution of agent"""
505
+ from dolphin.cli.ui.console import StatusBar
506
+ StatusBar._debug_log(f"_runFirstExecution: starting")
507
+
508
+ debugKwargs = {"debug_mode": flags.is_enabled(flags.DEBUG_MODE)}
509
+ if flags.is_enabled(flags.DEBUG_MODE):
510
+ if args.breakOnStart:
511
+ debugKwargs["break_on_start"] = True
512
+ if args.breakAt:
513
+ debugKwargs["break_at"] = args.breakAt
514
+
515
+ try:
516
+ async for result in agent.arun(**debugKwargs, **initialVariables):
517
+ pass
518
+ StatusBar._debug_log(f"_runFirstExecution: arun completed")
519
+ except Exception as e:
520
+ StatusBar._debug_log(f"_runFirstExecution: exception during arun: {e}")
521
+ raise
522
+
523
+ if args.outputVariables and not args.interactive:
524
+ outputVariablesToJson(agent.get_context(), args.outputVariables)
525
+
526
+ StatusBar._debug_log(f"_runFirstExecution: done")
527
+
528
+
529
+ async def _runSubsequentExecution(agent, args, query) -> None:
530
+ """Run subsequent execution (chat mode or resume)
531
+
532
+ Args:
533
+ query: User input - can be str for text or List[Dict] for multimodal content
534
+ """
535
+ from dolphin.core.agent.agent_state import AgentState, PauseType
536
+
537
+ # Check if the agent is paused due to user interrupt
538
+ # We use getattr/direct access for performance in CLI
539
+ is_user_interrupted = (
540
+ agent.state == AgentState.PAUSED and
541
+ getattr(agent, '_pause_type', None) == PauseType.USER_INTERRUPT
542
+ )
543
+
544
+ if is_user_interrupted:
545
+ # For UserInterrupt, we treat it as a multi-turn conversation continuation
546
+ # rather than a block restart. This allows LLM to see the partial output
547
+ # it was generating and continue from there with the new user input.
548
+ #
549
+ # Key insight: UserInterrupt is semantically "user wants to provide new input",
550
+ # which is the same as achat's purpose - continue the conversation.
551
+
552
+ # Clear interrupt state before continuing
553
+ if hasattr(agent, 'clear_interrupt'):
554
+ agent.clear_interrupt()
555
+
556
+ # Reset pause state for the agent so it can accept new work
557
+ agent._pause_type = None
558
+ if hasattr(agent, '_resume_handle'):
559
+ agent._resume_handle = None
560
+
561
+ # Reset agent state to RUNNING so interrupt() can work during execution
562
+ agent._state = AgentState.RUNNING
563
+
564
+ # Use achat with preserve_context=True to keep the scratchpad content
565
+ # This ensures LLM can see its partial output from before the interrupt
566
+ async for result in agent.achat(message=query, preserve_context=True):
567
+ pass
568
+ else:
569
+ # Also set state to RUNNING for normal achat path
570
+ agent._state = AgentState.RUNNING
571
+ async for result in agent.achat(message=query):
572
+ pass
573
+
574
+ if args.outputVariables:
575
+ outputVariablesToJson(agent.get_context(), args.outputVariables)
576
+
577
+
578
+ async def saveExecutionArtifacts(agent, args: Args) -> None:
579
+ """Save execution trace and snapshots
580
+
581
+ Args:
582
+ agent: Agent instance
583
+ args: Parsed CLI arguments
584
+ """
585
+ if not args.saveHistory:
586
+ return
587
+
588
+ # Save trajectory if not already saved via trajectoryPath
589
+ if not args.trajectoryPath:
590
+ agent.save_trajectory(agent_name=args.agent, force_save=True)
591
+
592
+ try:
593
+ currentTime = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")
594
+ randomSuffix = f"{random.randint(10000, 99999)}"
595
+
596
+ # Save execution trace
597
+ await _saveExecutionTrace(agent, args, currentTime, randomSuffix)
598
+
599
+ # Save snapshot analysis in debug mode
600
+ if flags.is_enabled(flags.DEBUG_MODE):
601
+ await _saveSnapshotAnalysis(agent, args, currentTime, randomSuffix)
602
+
603
+ except Exception as e:
604
+ console(f"Warning: Failed to save execution trace: {e}")
605
+ if args.saveHistory:
606
+ traceback.print_exc()
607
+
608
+
609
+ async def _saveExecutionTrace(agent, args: Args, currentTime: str, randomSuffix: str) -> None:
610
+ """Save execution trace to file"""
611
+ traceContent = agent.get_execution_trace()
612
+
613
+ if args.tracePath:
614
+ tracePath = args.tracePath
615
+ traceDir = os.path.dirname(tracePath)
616
+ if traceDir:
617
+ os.makedirs(traceDir, exist_ok=True)
618
+ else:
619
+ traceDir = "data/execution_trace"
620
+ os.makedirs(traceDir, exist_ok=True)
621
+ traceFilename = f"execution_trace_{currentTime}_{randomSuffix}.txt"
622
+ tracePath = os.path.join(traceDir, traceFilename)
623
+
624
+ with open(tracePath, "w", encoding="utf-8") as f:
625
+ f.write(traceContent)
626
+
627
+ console(f"Execution trace saved to: {tracePath}", verbose=args.saveHistory)
628
+
629
+
630
+ async def _saveSnapshotAnalysis(agent, args: Args, currentTime: str, randomSuffix: str) -> None:
631
+ """Save snapshot analysis in debug mode"""
632
+ try:
633
+ snapshotDir = "data/snapshot_analysis"
634
+ os.makedirs(snapshotDir, exist_ok=True)
635
+
636
+ # Save Markdown format
637
+ snapshotAnalysis = agent.get_snapshot_analysis(
638
+ title=f"Debug Snapshot Analysis - {args.agent}"
639
+ )
640
+ snapshotFilename = f"snapshot_analysis_{currentTime}_{randomSuffix}.md"
641
+ snapshotPath = os.path.join(snapshotDir, snapshotFilename)
642
+
643
+ with open(snapshotPath, "w", encoding="utf-8") as f:
644
+ f.write(snapshotAnalysis)
645
+
646
+ console(f"Snapshot analysis saved to: {snapshotPath}", verbose=args.saveHistory)
647
+
648
+ # Save JSON format
649
+ snapshotJson = agent.get_snapshot_analysis(format='json')
650
+ snapshotJsonFilename = f"snapshot_analysis_{currentTime}_{randomSuffix}.json"
651
+ snapshotJsonPath = os.path.join(snapshotDir, snapshotJsonFilename)
652
+
653
+ with open(snapshotJsonPath, "w", encoding="utf-8") as f:
654
+ json.dump(snapshotJson, f, ensure_ascii=False, indent=2)
655
+
656
+ console(f"Snapshot analysis JSON saved to: {snapshotJsonPath}", verbose=args.saveHistory)
657
+
658
+ except Exception as e:
659
+ console(f"Warning: Failed to save snapshot analysis: {e}")
660
+ if args.saveHistory:
661
+ traceback.print_exc()
662
+
663
+
664
+ async def enterPostmortemIfNeeded(agent, args: Args, enterPostmortem: bool) -> None:
665
+ """Enter post-mortem debug mode if conditions are met
666
+
667
+ Args:
668
+ agent: Agent instance
669
+ args: Parsed CLI arguments
670
+ enterPostmortem: Whether to enter post-mortem
671
+ """
672
+ try:
673
+ shouldEnter = (
674
+ flags.is_enabled(flags.DEBUG_MODE)
675
+ and not getattr(args, 'autoContinue', False)
676
+ and (not args.interactive or enterPostmortem)
677
+ )
678
+
679
+ if shouldEnter:
680
+ debugCtrl = getattr(agent.executor, 'debug_controller', None)
681
+ if debugCtrl is not None:
682
+ console("\n✅ 程序执行完毕,已进入 Post-Mortem 调试模式。输入 'help' 查看命令,'quit' 退出。")
683
+ await debugCtrl.post_mortem_loop()
684
+ else:
685
+ console("⚠️ 未找到调试控制器,无法进入 Post-Mortem 模式。")
686
+ except Exception as e:
687
+ console(f"Warning: Post-Mortem 调试模式发生错误: {e}")
688
+ if args.saveHistory:
689
+ traceback.print_exc()
690
+
691
+
692
+ async def runDolphinAgent(args: Args) -> None:
693
+ """Run Dolphin Language agent
694
+
695
+ This is the main orchestrator function that coordinates:
696
+ 1. Environment initialization
697
+ 2. Agent loading and preparation
698
+ 3. Conversation loop execution
699
+ 4. Artifact saving
700
+ 5. Post-mortem debugging (if applicable)
701
+
702
+ Args:
703
+ args: Parsed CLI arguments
704
+ """
705
+ from dolphin.cli.utils.helpers import validateArgs
706
+
707
+ validateArgs(args)
708
+
709
+ richConsole = RichConsole()
710
+ initialVariables = buildVariables(args)
711
+
712
+ userId = args.userId if args.userId else str(uuid.uuid4())
713
+ sessionId = args.sessionId if args.sessionId else str(uuid.uuid4())
714
+
715
+ env = None
716
+ try:
717
+ with richConsole.status("[bold green]Initializing Dolphin Environment...") as status:
718
+ status.update("[bold blue]Loading configuration...[/]")
719
+ env, _ = await initializeEnvironment(args)
720
+
721
+ status.update(f"[bold blue]Loading agents from:[/][white] {args.folder}[/]")
722
+ if args.skillFolder:
723
+ status.update(
724
+ f"[bold blue]Loading agents from:[/][white] {args.folder}[/] "
725
+ f"[dim](& skills from {args.skillFolder})[/]"
726
+ )
727
+
728
+ status.update(f"[bold blue]Initializing agent:[/][white] {args.agent}[/]")
729
+ agent = await loadAndPrepareAgent(env, args, initialVariables)
730
+
731
+ agent.set_user_id(userId)
732
+ agent.set_session_id(sessionId)
733
+
734
+ status.update("[bold green]Ready![/]")
735
+
736
+ # Run conversation
737
+ enterPostmortem = await runConversationLoop(agent, args, initialVariables)
738
+
739
+ # Save artifacts
740
+ await saveExecutionArtifacts(agent, args)
741
+
742
+ # Post-mortem
743
+ await enterPostmortemIfNeeded(agent, args, enterPostmortem)
744
+
745
+ await env.ashutdown()
746
+
747
+ except Exception as e:
748
+ _handle_execution_error(e, args)
749
+ if env is not None and hasattr(env, "ashutdown"):
750
+ await env.ashutdown()
751
+ sys.exit(1)
752
+
753
+
754
+ def _handle_execution_error(e: Exception, args: Args) -> None:
755
+ """Handle execution errors with user-friendly output.
756
+
757
+ For known DolphinException types, display a clean error message.
758
+ For unknown exceptions, display the full traceback.
759
+
760
+ Args:
761
+ e: The exception that occurred
762
+ args: Parsed CLI arguments
763
+ """
764
+ from dolphin.core.common.exceptions import DolphinException, SkillException
765
+
766
+ # Determine verbosity level
767
+ show_full_traceback = flags.is_enabled(flags.DEBUG_MODE) or getattr(args, 'vv', False)
768
+
769
+ # Check if this is a known exception type with a friendly message
770
+ root_cause = _extract_root_cause(e)
771
+
772
+ if isinstance(root_cause, SkillException):
773
+ # SkillException has a detailed, user-friendly message
774
+ console(f"\n❌ Skill Error:\n{root_cause.message}")
775
+ if show_full_traceback:
776
+ console("\n--- Full Traceback (debug mode) ---")
777
+ traceback.print_exc()
778
+ elif isinstance(root_cause, DolphinException):
779
+ # Check if the message contains embedded SkillException info
780
+ skill_error_msg = _extract_skill_error_message(e)
781
+ if skill_error_msg:
782
+ # Display the extracted skill error message in a clean format
783
+ console(f"\n❌ Skill Error:\n{skill_error_msg}")
784
+ else:
785
+ # Other DolphinException types - show concise error
786
+ console(f"\n❌ Error [{root_cause.code}]: {root_cause.message}")
787
+ if show_full_traceback:
788
+ console("\n--- Full Traceback (debug mode) ---")
789
+ traceback.print_exc()
790
+ else:
791
+ # Unknown exception - show more details
792
+ console(f"\n❌ Error executing Dolphin agent: {e}")
793
+ if show_full_traceback or args.saveHistory:
794
+ traceback.print_exc()
795
+ else:
796
+ console("💡 Run with --vv or --debug for full traceback")
797
+
798
+
799
+ def _extract_root_cause(e: Exception) -> Exception:
800
+ """Extract the root cause from a chain of exceptions.
801
+
802
+ Traverses the exception chain (__cause__ and __context__) to find
803
+ the original DolphinException that triggered the error.
804
+
805
+ Args:
806
+ e: The top-level exception
807
+
808
+ Returns:
809
+ The root cause exception (a DolphinException if found, otherwise the original)
810
+ """
811
+ from dolphin.core.common.exceptions import DolphinException
812
+
813
+ # First, check if the current exception is already a DolphinException
814
+ if isinstance(e, DolphinException):
815
+ return e
816
+
817
+ # Traverse __cause__ chain (explicit "raise ... from ...")
818
+ current = e
819
+ while current.__cause__ is not None:
820
+ current = current.__cause__
821
+ if isinstance(current, DolphinException):
822
+ return current
823
+
824
+ # Traverse __context__ chain (implicit exception chaining)
825
+ current = e
826
+ while current.__context__ is not None:
827
+ current = current.__context__
828
+ if isinstance(current, DolphinException):
829
+ return current
830
+
831
+ # No DolphinException found, return original
832
+ return e
833
+
834
+
835
+ def _extract_skill_error_message(e: Exception) -> Optional[str]:
836
+ """Try to extract a user-friendly skill error message from exception string.
837
+
838
+ Some exceptions wrap SkillException as a string (using str(e) instead of 'from e'),
839
+ so we need to parse the string to extract the formatted error message.
840
+
841
+ Args:
842
+ e: The exception to analyze
843
+
844
+ Returns:
845
+ The extracted skill error message if found, None otherwise
846
+ """
847
+ error_str = str(e)
848
+
849
+ # Look for the SkillException pattern in the message
850
+ # Pattern: "Skill 'xxx' not found.\n\nAvailable skills..."
851
+ import re
852
+
853
+ # Try to find the skill error block
854
+ skill_error_pattern = r"(Skill '[^']+' not found\..*?Verify that the required skillkit module is loaded)"
855
+ match = re.search(skill_error_pattern, error_str, re.DOTALL)
856
+
857
+ if match:
858
+ return match.group(1)
859
+
860
+ # Alternative: look for SKILL_NOT_FOUND pattern
861
+ if "SKILL_NOT_FOUND" in error_str and "Available skills" in error_str:
862
+ # Extract from "Skill '" to the end of "Possible fixes" section
863
+ start_idx = error_str.find("Skill '")
864
+ if start_idx != -1:
865
+ # Find the end of the message (after "Possible fixes" section)
866
+ end_patterns = ["module is loaded", "skillkit module is loaded"]
867
+ end_idx = len(error_str)
868
+ for pattern in end_patterns:
869
+ idx = error_str.find(pattern, start_idx)
870
+ if idx != -1:
871
+ end_idx = min(end_idx, idx + len(pattern))
872
+ if end_idx > start_idx:
873
+ return error_str[start_idx:end_idx]
874
+
875
+ return None
876
+
877
+
878
+ async def runDolphin(args: Args) -> None:
879
+ """Run Dolphin Language program
880
+
881
+ Args:
882
+ args: Parsed CLI arguments
883
+ """
884
+ if args.agent:
885
+ # User specified a custom agent
886
+ await runDolphinAgent(args)
887
+ elif args.useBuiltinAgent:
888
+ # Use builtin explore agent (default explore mode)
889
+ await runBuiltinExploreAgent(args)
890
+ else:
891
+ console("Error: Must specify --agent or use explore mode")
892
+ sys.exit(1)
893
+
894
+
895
+ async def runBuiltinExploreAgent(args: Args) -> None:
896
+ """Run the builtin explore agent for interactive coding assistance
897
+
898
+ This provides a Claude Code / Codex-like experience with access to
899
+ local environment tools (bash, python, file operations).
900
+
901
+ Args:
902
+ args: Parsed CLI arguments
903
+ """
904
+ from dolphin.cli.builtin_agents import BUILTIN_AGENTS_DIR, DEFAULT_EXPLORE_AGENT
905
+ from dolphin.sdk.runtime.env import Env
906
+ from dolphin.core.config.global_config import GlobalConfig
907
+ from dolphin.lib.skillkits.env_skillkit import EnvSkillkit
908
+ import logging
909
+
910
+ # Set the builtin agent directory and agent name
911
+ args.folder = BUILTIN_AGENTS_DIR
912
+ args.agent = DEFAULT_EXPLORE_AGENT
913
+
914
+ # Disable EXPLORE_BLOCK_V2 for explore mode (continue_exploration not yet supported in V2)
915
+ flags.set_flag(flags.EXPLORE_BLOCK_V2, False)
916
+
917
+ # Initialize environment
918
+ globalConfigPath = args.config if args.config else "./config/global.yaml"
919
+ globalConfig = GlobalConfig.from_yaml(globalConfigPath)
920
+
921
+ # Create environment with builtin agents directory
922
+ env = Env(
923
+ globalConfig=globalConfig,
924
+ agentFolderPath=BUILTIN_AGENTS_DIR,
925
+ skillkitFolderPath=args.skillFolder,
926
+ output_variables=[],
927
+ verbose=args.saveHistory,
928
+ is_cli=True,
929
+ log_level=(
930
+ logging.DEBUG if flags.is_enabled(flags.DEBUG_MODE) else logging.INFO
931
+ ),
932
+ )
933
+
934
+ # Register EnvSkillkit for local bash/python execution
935
+ env_skillkit = EnvSkillkit()
936
+ env_skillkit.setGlobalConfig(globalConfig)
937
+ for skill in env_skillkit.getSkills():
938
+ env.globalSkills.installedSkillset.addSkill(skill)
939
+ env.globalSkills._syncAllSkills()
940
+
941
+ console(f"[bold green]👋 Hi! I'm Dolphin, your AI Pair Programmer.[/]")
942
+ console(f" I can help you write code, debug issues, and explore this project.")
943
+ console(f" What would you like to do today?\n")
944
+
945
+ # Run the agent with the enhanced environment
946
+ await _runDolphinAgentWithEnv(env, args)
947
+
948
+
949
+ async def _runDolphinAgentWithEnv(env, args: Args) -> None:
950
+ """Run Dolphin agent with a pre-configured environment
951
+
952
+ Args:
953
+ env: Pre-configured Dolphin environment
954
+ args: Parsed CLI arguments
955
+ """
956
+ from dolphin.cli.utils.helpers import validateArgs, buildVariables, outputVariablesToJson
957
+
958
+ richConsole = RichConsole()
959
+ initialVariables = buildVariables(args)
960
+
961
+ userId = args.userId if args.userId else str(uuid.uuid4())
962
+ sessionId = args.sessionId if args.sessionId else str(uuid.uuid4())
963
+
964
+ try:
965
+ with richConsole.status("[bold green]Initializing agent...[/]") as status:
966
+ status.update(f"[bold blue]Loading agent:[/][white] {args.agent}[/]")
967
+ agent = await loadAndPrepareAgent(env, args, initialVariables)
968
+
969
+ agent.set_user_id(userId)
970
+ agent.set_session_id(sessionId)
971
+
972
+ status.update("[bold green]Ready![/]")
973
+
974
+ # Run conversation
975
+ enterPostmortem = await runConversationLoop(agent, args, initialVariables)
976
+
977
+ # Save artifacts
978
+ await saveExecutionArtifacts(agent, args)
979
+
980
+ # Post-mortem
981
+ await enterPostmortemIfNeeded(agent, args, enterPostmortem)
982
+
983
+ await env.ashutdown()
984
+
985
+ except Exception as e:
986
+ _handle_execution_error(e, args)
987
+ if env is not None and hasattr(env, "ashutdown"):
988
+ await env.ashutdown()
989
+ sys.exit(1)