code-muse 0.0.1__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 (394) hide show
  1. code_muse/__init__.py +26 -0
  2. code_muse/__main__.py +10 -0
  3. code_muse/agents/__init__.py +31 -0
  4. code_muse/agents/_builder.py +214 -0
  5. code_muse/agents/_compaction.py +506 -0
  6. code_muse/agents/_diagnostics.py +171 -0
  7. code_muse/agents/_history.py +382 -0
  8. code_muse/agents/_key_listeners.py +148 -0
  9. code_muse/agents/_non_streaming_render.py +148 -0
  10. code_muse/agents/_runtime.py +596 -0
  11. code_muse/agents/agent_creator_agent.py +603 -0
  12. code_muse/agents/agent_helios.py +47 -0
  13. code_muse/agents/agent_manager.py +740 -0
  14. code_muse/agents/agent_muse.py +78 -0
  15. code_muse/agents/agent_planning.py +44 -0
  16. code_muse/agents/agent_qa_melpomene.py +207 -0
  17. code_muse/agents/base_agent.py +194 -0
  18. code_muse/agents/event_stream_handler.py +361 -0
  19. code_muse/agents/json_agent.py +201 -0
  20. code_muse/agents/prompt_v3.py +521 -0
  21. code_muse/agents/subagent_stream_handler.py +273 -0
  22. code_muse/callbacks.py +941 -0
  23. code_muse/chatgpt_codex_client.py +333 -0
  24. code_muse/claude_cache_client.py +853 -0
  25. code_muse/cli_runner/__init__.py +319 -0
  26. code_muse/cli_runner/args.py +63 -0
  27. code_muse/cli_runner/loop.py +510 -0
  28. code_muse/cli_runner/resume.py +72 -0
  29. code_muse/cli_runner/runner.py +161 -0
  30. code_muse/command_line/__init__.py +1 -0
  31. code_muse/command_line/add_model_menu.py +1331 -0
  32. code_muse/command_line/agent_menu.py +674 -0
  33. code_muse/command_line/attachments.py +397 -0
  34. code_muse/command_line/autosave_menu.py +709 -0
  35. code_muse/command_line/clipboard.py +528 -0
  36. code_muse/command_line/colors_menu.py +530 -0
  37. code_muse/command_line/command_handler.py +262 -0
  38. code_muse/command_line/command_registry.py +150 -0
  39. code_muse/command_line/config_commands.py +711 -0
  40. code_muse/command_line/core_commands.py +740 -0
  41. code_muse/command_line/diff_menu.py +865 -0
  42. code_muse/command_line/file_path_completion.py +73 -0
  43. code_muse/command_line/load_context_completion.py +57 -0
  44. code_muse/command_line/model_picker_completion.py +512 -0
  45. code_muse/command_line/model_settings_menu.py +983 -0
  46. code_muse/command_line/onboarding_slides.py +162 -0
  47. code_muse/command_line/onboarding_wizard.py +337 -0
  48. code_muse/command_line/pagination.py +41 -0
  49. code_muse/command_line/pin_command_completion.py +329 -0
  50. code_muse/command_line/prompt_toolkit_completion.py +886 -0
  51. code_muse/command_line/session_commands.py +304 -0
  52. code_muse/command_line/shell_passthrough.py +145 -0
  53. code_muse/command_line/skills_completion.py +158 -0
  54. code_muse/command_line/types.py +18 -0
  55. code_muse/command_line/uc_menu.py +908 -0
  56. code_muse/command_line/utils.py +105 -0
  57. code_muse/command_line/wiggum_state.py +77 -0
  58. code_muse/config.py +1138 -0
  59. code_muse/config_agent.py +168 -0
  60. code_muse/config_appearance.py +241 -0
  61. code_muse/config_model.py +357 -0
  62. code_muse/config_security.py +73 -0
  63. code_muse/error_logging.py +132 -0
  64. code_muse/evals/__init__.py +35 -0
  65. code_muse/evals/eval_helpers.py +81 -0
  66. code_muse/evals/eval_runner.py +299 -0
  67. code_muse/evals/sample_evals/__init__.py +1 -0
  68. code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
  69. code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
  70. code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
  71. code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
  72. code_muse/fs_scan_cache/__init__.py +31 -0
  73. code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
  74. code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
  75. code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
  76. code_muse/fs_scan_cache/tool_integration.py +309 -0
  77. code_muse/fs_scan_cache/ttl_policy.py +44 -0
  78. code_muse/gemini_code_assist.py +383 -0
  79. code_muse/gemini_model.py +838 -0
  80. code_muse/hook_engine/README.md +105 -0
  81. code_muse/hook_engine/__init__.py +21 -0
  82. code_muse/hook_engine/aliases.py +153 -0
  83. code_muse/hook_engine/engine.py +221 -0
  84. code_muse/hook_engine/executor.py +347 -0
  85. code_muse/hook_engine/matcher.py +154 -0
  86. code_muse/hook_engine/models.py +245 -0
  87. code_muse/hook_engine/registry.py +114 -0
  88. code_muse/hook_engine/trust.py +268 -0
  89. code_muse/hook_engine/validator.py +144 -0
  90. code_muse/http_utils.py +360 -0
  91. code_muse/keymap.py +128 -0
  92. code_muse/list_filtering.py +26 -0
  93. code_muse/main.py +10 -0
  94. code_muse/messaging/__init__.py +259 -0
  95. code_muse/messaging/bus.py +621 -0
  96. code_muse/messaging/commands.py +166 -0
  97. code_muse/messaging/markdown_patches.py +57 -0
  98. code_muse/messaging/message_queue.py +397 -0
  99. code_muse/messaging/messages.py +591 -0
  100. code_muse/messaging/queue_console.py +269 -0
  101. code_muse/messaging/renderers.py +308 -0
  102. code_muse/messaging/rich_renderer.py +1158 -0
  103. code_muse/messaging/shimmer.py +154 -0
  104. code_muse/messaging/spinner/__init__.py +87 -0
  105. code_muse/messaging/spinner/console_spinner.py +250 -0
  106. code_muse/messaging/spinner/spinner_base.py +82 -0
  107. code_muse/messaging/subagent_console.py +458 -0
  108. code_muse/model_factory.py +1203 -0
  109. code_muse/model_switching.py +59 -0
  110. code_muse/model_utils.py +156 -0
  111. code_muse/models.json +66 -0
  112. code_muse/models_cache/__init__.py +26 -0
  113. code_muse/models_cache/blocking_lru_cache.py +98 -0
  114. code_muse/models_cache/cache_writer.py +86 -0
  115. code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
  116. code_muse/models_cache/sha256_hash.pyx +34 -0
  117. code_muse/models_cache/startup_integration.py +75 -0
  118. code_muse/models_dev_api.json +1 -0
  119. code_muse/models_dev_parser.py +590 -0
  120. code_muse/motion.py +126 -0
  121. code_muse/plugins/__init__.py +471 -0
  122. code_muse/plugins/agent_skills/__init__.py +32 -0
  123. code_muse/plugins/agent_skills/config.py +176 -0
  124. code_muse/plugins/agent_skills/discovery.py +309 -0
  125. code_muse/plugins/agent_skills/downloader.py +389 -0
  126. code_muse/plugins/agent_skills/installer.py +19 -0
  127. code_muse/plugins/agent_skills/metadata.py +293 -0
  128. code_muse/plugins/agent_skills/prompt_builder.py +66 -0
  129. code_muse/plugins/agent_skills/register_callbacks.py +298 -0
  130. code_muse/plugins/agent_skills/remote_catalog.py +320 -0
  131. code_muse/plugins/agent_skills/skill_catalog.py +254 -0
  132. code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
  133. code_muse/plugins/agent_skills/skills_menu.py +791 -0
  134. code_muse/plugins/autonomous_memory/__init__.py +39 -0
  135. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
  136. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
  137. code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
  138. code_muse/plugins/autonomous_memory/consolidation.py +82 -0
  139. code_muse/plugins/autonomous_memory/extraction.py +382 -0
  140. code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
  141. code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
  142. code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
  143. code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
  144. code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
  145. code_muse/plugins/aws_bedrock/__init__.py +14 -0
  146. code_muse/plugins/aws_bedrock/config.py +99 -0
  147. code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
  148. code_muse/plugins/aws_bedrock/utils.py +153 -0
  149. code_muse/plugins/azure_foundry/README.md +238 -0
  150. code_muse/plugins/azure_foundry/__init__.py +15 -0
  151. code_muse/plugins/azure_foundry/config.py +125 -0
  152. code_muse/plugins/azure_foundry/discovery.py +187 -0
  153. code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
  154. code_muse/plugins/azure_foundry/token.py +180 -0
  155. code_muse/plugins/azure_foundry/utils.py +345 -0
  156. code_muse/plugins/build_filter/__init__.py +1 -0
  157. code_muse/plugins/build_filter/register_callbacks.py +201 -0
  158. code_muse/plugins/build_filter/strategies/__init__.py +1 -0
  159. code_muse/plugins/build_filter/strategies/build.py +397 -0
  160. code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
  161. code_muse/plugins/chatgpt_oauth/config.py +52 -0
  162. code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
  163. code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
  164. code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
  165. code_muse/plugins/chatgpt_oauth/utils.py +538 -0
  166. code_muse/plugins/checkpointing/__init__.py +29 -0
  167. code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
  168. code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
  169. code_muse/plugins/checkpointing/register_callbacks.py +51 -0
  170. code_muse/plugins/checkpointing/restore_command.py +263 -0
  171. code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
  172. code_muse/plugins/checkpointing/shadow_git.py +90 -0
  173. code_muse/plugins/claude_code_hooks/__init__.py +1 -0
  174. code_muse/plugins/claude_code_hooks/config.py +188 -0
  175. code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
  176. code_muse/plugins/claude_code_oauth/README.md +167 -0
  177. code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
  178. code_muse/plugins/claude_code_oauth/__init__.py +25 -0
  179. code_muse/plugins/claude_code_oauth/config.py +52 -0
  180. code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
  181. code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
  182. code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
  183. code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
  184. code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
  185. code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
  186. code_muse/plugins/claude_code_oauth/utils.py +664 -0
  187. code_muse/plugins/copilot_auth/__init__.py +11 -0
  188. code_muse/plugins/copilot_auth/config.py +91 -0
  189. code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
  190. code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
  191. code_muse/plugins/copilot_auth/utils.py +584 -0
  192. code_muse/plugins/custom_commands/__init__.py +14 -0
  193. code_muse/plugins/custom_commands/args_injection.py +82 -0
  194. code_muse/plugins/custom_commands/command_discovery.py +89 -0
  195. code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
  196. code_muse/plugins/custom_commands/register_callbacks.py +176 -0
  197. code_muse/plugins/customizable_commands/__init__.py +0 -0
  198. code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
  199. code_muse/plugins/destructive_command_guard/__init__.py +14 -0
  200. code_muse/plugins/destructive_command_guard/detector.py +375 -0
  201. code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
  202. code_muse/plugins/example_custom_command/README.md +280 -0
  203. code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
  204. code_muse/plugins/file_permission_handler/__init__.py +4 -0
  205. code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
  206. code_muse/plugins/filter_engine/__init__.py +30 -0
  207. code_muse/plugins/filter_engine/classifier.py +153 -0
  208. code_muse/plugins/filter_engine/content_detector.py +184 -0
  209. code_muse/plugins/filter_engine/dispatcher.py +244 -0
  210. code_muse/plugins/filter_engine/register_callbacks.py +188 -0
  211. code_muse/plugins/filter_engine/registry.py +279 -0
  212. code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
  213. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
  214. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
  215. code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
  216. code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
  217. code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
  218. code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
  219. code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
  220. code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
  221. code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
  222. code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
  223. code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
  224. code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
  225. code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
  226. code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
  227. code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
  228. code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
  229. code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
  230. code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
  231. code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
  232. code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
  233. code_muse/plugins/filter_engine/verbosity.py +63 -0
  234. code_muse/plugins/force_push_guard/__init__.py +5 -0
  235. code_muse/plugins/force_push_guard/detector.py +96 -0
  236. code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
  237. code_muse/plugins/force_push_guard/test_detector.py +143 -0
  238. code_muse/plugins/frontend_emitter/__init__.py +25 -0
  239. code_muse/plugins/frontend_emitter/emitter.py +121 -0
  240. code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
  241. code_muse/plugins/gac/__init__.py +4 -0
  242. code_muse/plugins/gac/git_ops.py +136 -0
  243. code_muse/plugins/gac/prompt.py +191 -0
  244. code_muse/plugins/gac/register_callbacks.py +82 -0
  245. code_muse/plugins/hook_creator/__init__.py +1 -0
  246. code_muse/plugins/hook_creator/register_callbacks.py +34 -0
  247. code_muse/plugins/hook_manager/__init__.py +1 -0
  248. code_muse/plugins/hook_manager/config.py +289 -0
  249. code_muse/plugins/hook_manager/hooks_menu.py +563 -0
  250. code_muse/plugins/hook_manager/register_callbacks.py +227 -0
  251. code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
  252. code_muse/plugins/mindpack/__init__.py +0 -0
  253. code_muse/plugins/mindpack/factory.py +930 -0
  254. code_muse/plugins/mindpack/judge.py +573 -0
  255. code_muse/plugins/mindpack/memory.py +100 -0
  256. code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
  257. code_muse/plugins/mindpack/orchestration.py +605 -0
  258. code_muse/plugins/mindpack/register_callbacks.py +175 -0
  259. code_muse/plugins/mindpack/schemas.py +358 -0
  260. code_muse/plugins/mindpack/tools.py +387 -0
  261. code_muse/plugins/oauth_muse_html.py +226 -0
  262. code_muse/plugins/ollama_setup/__init__.py +5 -0
  263. code_muse/plugins/ollama_setup/completer.py +36 -0
  264. code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
  265. code_muse/plugins/plan_command/__init__.py +0 -0
  266. code_muse/plugins/plan_command/register_callbacks.py +206 -0
  267. code_muse/plugins/plan_mode/__init__.py +37 -0
  268. code_muse/plugins/plan_mode/mode_cycling.py +40 -0
  269. code_muse/plugins/plan_mode/plan_generation.py +68 -0
  270. code_muse/plugins/plan_mode/plan_hooks.py +74 -0
  271. code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
  272. code_muse/plugins/plan_mode/register_callbacks.py +121 -0
  273. code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
  274. code_muse/plugins/policy_engine/__init__.py +46 -0
  275. code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
  276. code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
  277. code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
  278. code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
  279. code_muse/plugins/policy_engine/register_callbacks.py +112 -0
  280. code_muse/plugins/pop_command/__init__.py +1 -0
  281. code_muse/plugins/pop_command/register_callbacks.py +189 -0
  282. code_muse/plugins/prompt_newline/__init__.py +13 -0
  283. code_muse/plugins/prompt_newline/config.py +19 -0
  284. code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
  285. code_muse/plugins/safety_status/__init__.py +0 -0
  286. code_muse/plugins/safety_status/register_callbacks.py +113 -0
  287. code_muse/plugins/semantic_compression/__init__.py +6 -0
  288. code_muse/plugins/semantic_compression/compressor.py +295 -0
  289. code_muse/plugins/semantic_compression/config.py +123 -0
  290. code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
  291. code_muse/plugins/shell_minimizer/__init__.py +50 -0
  292. code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
  293. code_muse/plugins/shell_minimizer/pipeline.py +556 -0
  294. code_muse/plugins/shell_minimizer/primitives.py +482 -0
  295. code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
  296. code_muse/plugins/shell_safety/__init__.py +6 -0
  297. code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
  298. code_muse/plugins/shell_safety/command_cache.py +149 -0
  299. code_muse/plugins/shell_safety/register_callbacks.py +202 -0
  300. code_muse/plugins/synthetic_status/__init__.py +1 -0
  301. code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
  302. code_muse/plugins/synthetic_status/status_api.py +145 -0
  303. code_muse/plugins/token_caching/__init__.py +21 -0
  304. code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
  305. code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
  306. code_muse/plugins/token_caching/register_callbacks.py +54 -0
  307. code_muse/plugins/token_caching/stats_display.py +35 -0
  308. code_muse/plugins/token_tracking/__init__.py +26 -0
  309. code_muse/plugins/token_tracking/database.py +381 -0
  310. code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
  311. code_muse/plugins/token_tracking/record.py +55 -0
  312. code_muse/plugins/token_tracking/register_callbacks.py +277 -0
  313. code_muse/plugins/token_tracking/reports.py +329 -0
  314. code_muse/plugins/universal_constructor/__init__.py +13 -0
  315. code_muse/plugins/universal_constructor/models.py +136 -0
  316. code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
  317. code_muse/plugins/universal_constructor/registry.py +390 -0
  318. code_muse/plugins/universal_constructor/runner.py +474 -0
  319. code_muse/plugins/universal_constructor/safety.py +440 -0
  320. code_muse/plugins/universal_constructor/sandbox.py +584 -0
  321. code_muse/provider_identity.py +105 -0
  322. code_muse/pydantic_patches.py +410 -0
  323. code_muse/reopenable_async_client.py +233 -0
  324. code_muse/round_robin_model.py +151 -0
  325. code_muse/secret_storage.py +74 -0
  326. code_muse/security/__init__.py +1 -0
  327. code_muse/security/redaction.cpython-314-darwin.so +0 -0
  328. code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
  329. code_muse/security/redaction.pyx +135 -0
  330. code_muse/session_storage.py +565 -0
  331. code_muse/status_display.py +261 -0
  332. code_muse/stream_parser/__init__.py +76 -0
  333. code_muse/stream_parser/assistant_text_parser.py +90 -0
  334. code_muse/stream_parser/citation_parser.py +76 -0
  335. code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
  336. code_muse/stream_parser/proposed_plan_parser.py +158 -0
  337. code_muse/stream_parser/stream_text_chunk.py +23 -0
  338. code_muse/stream_parser/stream_text_parser.py +27 -0
  339. code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
  340. code_muse/stream_parser/tagged_line_parser.pyx +251 -0
  341. code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
  342. code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
  343. code_muse/summarization_agent.py +308 -0
  344. code_muse/terminal_utils.cpython-314-darwin.so +0 -0
  345. code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
  346. code_muse/terminal_utils.pyx +483 -0
  347. code_muse/tools/__init__.py +459 -0
  348. code_muse/tools/agent_tools.py +613 -0
  349. code_muse/tools/ask_user_question/__init__.py +26 -0
  350. code_muse/tools/ask_user_question/constants.py +73 -0
  351. code_muse/tools/ask_user_question/demo_tui.py +55 -0
  352. code_muse/tools/ask_user_question/handler.py +232 -0
  353. code_muse/tools/ask_user_question/models.py +302 -0
  354. code_muse/tools/ask_user_question/registration.py +37 -0
  355. code_muse/tools/ask_user_question/renderers.py +336 -0
  356. code_muse/tools/ask_user_question/terminal_ui.py +327 -0
  357. code_muse/tools/ask_user_question/theme.py +156 -0
  358. code_muse/tools/ask_user_question/tui_loop.py +422 -0
  359. code_muse/tools/background_jobs.py +99 -0
  360. code_muse/tools/browser/__init__.py +37 -0
  361. code_muse/tools/browser/browser_control.py +289 -0
  362. code_muse/tools/browser/browser_interactions.py +545 -0
  363. code_muse/tools/browser/browser_locators.py +640 -0
  364. code_muse/tools/browser/browser_manager.py +376 -0
  365. code_muse/tools/browser/browser_navigation.py +251 -0
  366. code_muse/tools/browser/browser_screenshot.py +180 -0
  367. code_muse/tools/browser/browser_scripts.py +462 -0
  368. code_muse/tools/browser/browser_workflows.py +222 -0
  369. code_muse/tools/chrome_cdp/__init__.py +1070 -0
  370. code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
  371. code_muse/tools/command_runner.py +1401 -0
  372. code_muse/tools/common.py +1407 -0
  373. code_muse/tools/display.py +87 -0
  374. code_muse/tools/file_modifications.py +1099 -0
  375. code_muse/tools/file_operations.py +860 -0
  376. code_muse/tools/image_tools.py +185 -0
  377. code_muse/tools/meetin_proxy/__init__.py +243 -0
  378. code_muse/tools/meetin_proxy/capture_addon.py +82 -0
  379. code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
  380. code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
  381. code_muse/tools/path_policy.py +219 -0
  382. code_muse/tools/skills_tools.py +586 -0
  383. code_muse/tools/subagent_context.py +158 -0
  384. code_muse/tools/tools_content.py +50 -0
  385. code_muse/tools/universal_constructor.py +965 -0
  386. code_muse/uvx_detection.py +241 -0
  387. code_muse/version_checker.py +86 -0
  388. code_muse-0.0.1.data/data/code_muse/models.json +66 -0
  389. code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
  390. code_muse-0.0.1.dist-info/METADATA +845 -0
  391. code_muse-0.0.1.dist-info/RECORD +394 -0
  392. code_muse-0.0.1.dist-info/WHEEL +4 -0
  393. code_muse-0.0.1.dist-info/entry_points.txt +2 -0
  394. code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,596 @@
1
+ """Agent run orchestration: streaming retries, signal/key cancellation.
2
+
3
+ Replaces the monolithic ``BaseAgent.run`` coroutine. Everything here
4
+ is a free function; the agent is passed in explicitly. Integration points
5
+ preserved verbatim:
6
+
7
+ - Plugin-supplied async context managers wrap the run (see
8
+ ``on_agent_run_context``); used e.g. by plugins to set a workflow
9
+ ID and swap external toolsets in/out.
10
+ - Signal-vs-key-listener branch driven by ``cancel_agent_uses_signal()``
11
+ - Windows terminal reset on graceful SIGINT
12
+ - ``is_awaiting_user_input()`` guards interrupt handling
13
+ - Subagent task cancellation via ``_active_subagent_tasks``
14
+ - ``_RUNNING_PROCESSES`` check before cancelling the agent
15
+ """
16
+
17
+ import asyncio
18
+ import signal
19
+ import threading
20
+ import uuid
21
+ from collections.abc import Callable, Sequence
22
+ from contextlib import AsyncExitStack, suppress
23
+ from dataclasses import dataclass
24
+ from typing import Any
25
+
26
+ import httpcore
27
+ import httpx
28
+ from pydantic_ai import (
29
+ BinaryContent,
30
+ DocumentUrl,
31
+ ImageUrl,
32
+ UnexpectedModelBehavior,
33
+ UsageLimitExceeded,
34
+ UsageLimits,
35
+ )
36
+
37
+ try: # pragma: no cover - pydantic-ai version dependent
38
+ from pydantic_ai.exceptions import ModelHTTPError
39
+ except ImportError:
40
+ ModelHTTPError = None # type: ignore[misc,assignment]
41
+
42
+ try: # pragma: no cover - optional dependency
43
+ from openai import APIError as OpenAIAPIError
44
+ except ImportError:
45
+ OpenAIAPIError = None # type: ignore[assignment]
46
+
47
+ # Python 3.11+ builtin; graceful fallback for 3.10
48
+ try:
49
+ from builtins import BaseExceptionGroup # type: ignore[attr-defined]
50
+ except ImportError: # pragma: no cover - 3.10 only
51
+ BaseExceptionGroup = Exception # type: ignore[misc,assignment]
52
+
53
+ from code_muse.agents import _history, _key_listeners
54
+ from code_muse.agents._builder import build_pydantic_agent
55
+ from code_muse.agents._diagnostics import emit_exception_diagnostics
56
+ from code_muse.agents._non_streaming_render import (
57
+ StreamingTextDetector,
58
+ render_result_without_streaming,
59
+ should_render_fallback,
60
+ )
61
+ from code_muse.agents.event_stream_handler import event_stream_handler
62
+ from code_muse.callbacks import (
63
+ on_agent_exception,
64
+ on_agent_run_cancel,
65
+ on_agent_run_context,
66
+ on_agent_run_end,
67
+ on_agent_run_result,
68
+ on_agent_run_start,
69
+ on_should_skip_fallback_render,
70
+ )
71
+ from code_muse.config import (
72
+ get_enable_streaming,
73
+ get_max_hook_retries,
74
+ get_message_limit,
75
+ )
76
+ from code_muse.keymap import cancel_agent_uses_signal
77
+ from code_muse.messaging import emit_error, emit_info, emit_warning
78
+ from code_muse.model_factory import ModelFactory
79
+ from code_muse.tools.agent_tools import _active_subagent_tasks
80
+ from code_muse.tools.command_runner import is_awaiting_user_input
81
+
82
+ # ---- Streaming retry helpers ------------------------------------------------
83
+
84
+ # Every entry here is either an explicit provider "please retry" signal or an
85
+ # SSE framing / transport artifact that reliably succeeds on the next attempt.
86
+ # Keep this list substring-based and lower-case.
87
+ _RETRYABLE_SNIPPETS = (
88
+ "streamed response ended without content",
89
+ "malformed streamed sse event",
90
+ "extra json data in sse payload",
91
+ "too many requests",
92
+ "rate limit",
93
+ "rate limited",
94
+ "overloaded",
95
+ "service unavailable",
96
+ "server had an error processing your request",
97
+ "retry your request",
98
+ "internal server error",
99
+ )
100
+
101
+ _RETRYABLE_EXCEPTIONS: tuple = (
102
+ httpx.RemoteProtocolError,
103
+ httpx.ReadTimeout,
104
+ httpcore.RemoteProtocolError,
105
+ )
106
+
107
+
108
+ def _matches_retryable_snippet(msg: str) -> bool:
109
+ """Return True if ``msg`` matches any known transient pattern.
110
+
111
+ Also accepts the generic ``stream ... ended`` wording variants so we don't
112
+ have to chase every phrasing tweak providers sneak in over time.
113
+ """
114
+ msg = msg.lower()
115
+ if any(s in msg for s in _RETRYABLE_SNIPPETS):
116
+ return True
117
+ return "stream" in msg and "ended" in msg
118
+
119
+
120
+ def should_retry_streaming(exc: Exception) -> bool:
121
+ """Decide whether ``exc`` is a transient streaming hiccup worth retrying."""
122
+ if isinstance(exc, _RETRYABLE_EXCEPTIONS):
123
+ return True
124
+
125
+ msg = str(exc)
126
+ if isinstance(exc, UnexpectedModelBehavior):
127
+ return _matches_retryable_snippet(msg)
128
+
129
+ if OpenAIAPIError is not None and isinstance(exc, OpenAIAPIError):
130
+ if _matches_retryable_snippet(msg):
131
+ return True
132
+ body = getattr(exc, "body", None)
133
+ if isinstance(body, dict):
134
+ body_msg = str(body.get("message", ""))
135
+ body_type = str(body.get("type", "")).lower()
136
+ if _matches_retryable_snippet(body_msg):
137
+ return True
138
+ if "rate" in body_type and "limit" in body_type:
139
+ return True
140
+ if body_type in {"server_error", "internal_server_error", "api_error"}:
141
+ return _matches_retryable_snippet(body_msg)
142
+
143
+ # Retry on pydantic-ai ModelHTTPError rate limits (e.g. 429 from providers)
144
+ if ModelHTTPError is not None and isinstance(exc, ModelHTTPError):
145
+ status_code = getattr(exc, "status_code", None)
146
+ if status_code == 429:
147
+ return True
148
+ # Retry on 5xx server errors as well
149
+ if isinstance(status_code, int) and status_code >= 500:
150
+ return True
151
+ if _matches_retryable_snippet(msg):
152
+ return True
153
+
154
+ return False
155
+
156
+
157
+ def streaming_retry(
158
+ max_attempts: int = 3,
159
+ delays: Sequence[float] = (1, 2, 4),
160
+ ) -> Callable[[Callable[[], Any]], Callable[[], Any]]:
161
+ """Wrap a no-arg async callable with streaming-retry semantics."""
162
+
163
+ def decorator(factory: Callable[[], Any]) -> Callable[[], Any]:
164
+ async def runner() -> Any:
165
+ last_exc: Exception | None = None
166
+ for attempt in range(max_attempts):
167
+ try:
168
+ return await factory()
169
+ except Exception as exc:
170
+ if not should_retry_streaming(exc):
171
+ raise
172
+ last_exc = exc
173
+ if attempt < max_attempts - 1:
174
+ delay = delays[attempt] if attempt < len(delays) else delays[-1]
175
+ emit_warning(
176
+ f"⚡ Streaming interrupted, auto-retrying in {delay}s... "
177
+ f"(attempt {attempt + 1}/{max_attempts})"
178
+ )
179
+ await asyncio.sleep(delay)
180
+ else:
181
+ emit_error(f"❌ Streaming failed after {max_attempts} attempts")
182
+ assert last_exc is not None # loop always sets this before exiting
183
+ raise last_exc
184
+
185
+ return runner
186
+
187
+ return decorator
188
+
189
+
190
+ # ---- Small utilities --------------------------------------------------------
191
+
192
+
193
+ def _model_allows_streaming(model_name: str | None) -> bool:
194
+ """Check the model config for an explicit ``"streaming": false`` override.
195
+
196
+ Some providers (e.g. crof.ai for kimi models) have flaky SSE transports.
197
+ Setting ``"streaming": false`` in ``models.json`` disables streaming for
198
+ that model, falling back to a single-shot request like gac does.
199
+ """
200
+ if not model_name:
201
+ return True
202
+ try:
203
+ cfg = ModelFactory.load_config().get(model_name, {})
204
+ return cfg.get("streaming", True) is not False
205
+ except Exception:
206
+ return True
207
+
208
+
209
+ def _sanitize_prompt(prompt: str) -> str:
210
+ """Strip lone UTF-16 surrogates (common on Windows copy-paste)."""
211
+ if not prompt:
212
+ return prompt
213
+ try:
214
+ return prompt.encode("utf-8", errors="surrogatepass").decode(
215
+ "utf-8", errors="replace"
216
+ )
217
+ except UnicodeEncodeError, UnicodeDecodeError:
218
+ return "".join(
219
+ ch if ord(ch) < 0xD800 or ord(ch) > 0xDFFF else "\ufffd" for ch in prompt
220
+ )
221
+
222
+
223
+ def _build_prompt_payload(
224
+ prompt: str,
225
+ attachments: Sequence[BinaryContent | None],
226
+ link_attachments: Sequence[ImageUrl | DocumentUrl | None],
227
+ ) -> str | list[Any]:
228
+ """Merge prompt + binary/link attachments into the pydantic-ai payload shape."""
229
+ parts: list[Any] = []
230
+ if attachments:
231
+ parts.extend(attachments)
232
+ if link_attachments:
233
+ parts.extend(link_attachments)
234
+
235
+ if not parts:
236
+ return prompt
237
+
238
+ payload: list[Any] = []
239
+ if prompt:
240
+ payload.append(prompt)
241
+ payload.extend(parts)
242
+ return payload
243
+
244
+
245
+ def _extract_response_text(result: Any) -> str:
246
+ """Best-effort extraction of human-readable text from a pydantic-ai result."""
247
+ if result is None:
248
+ return ""
249
+ if hasattr(result, "data"):
250
+ return str(result.data) if result.data else ""
251
+ if hasattr(result, "output"):
252
+ return str(result.output) if result.output else ""
253
+ return str(result)
254
+
255
+
256
+ def _should_prepend_system_prompt(agent: Any, prompt: str) -> str:
257
+ """Prepend system prompt to user prompt on the first turn (claude-code etc)."""
258
+ from code_muse.agents._builder import load_muse_rules
259
+ from code_muse.model_utils import prepare_prompt_for_model
260
+
261
+ if agent._message_history:
262
+ return prompt
263
+
264
+ system_prompt = agent.get_full_system_prompt()
265
+ rules = load_muse_rules()
266
+ if rules:
267
+ system_prompt += f"\n{rules}"
268
+
269
+ prepared = prepare_prompt_for_model(
270
+ model_name=agent.get_model_name(),
271
+ system_prompt=system_prompt,
272
+ user_prompt=prompt,
273
+ prepend_system_to_user=True,
274
+ )
275
+ return prepared.user_prompt
276
+
277
+
278
+ def _collect_exceptions(
279
+ group: BaseException, predicate: Callable[[BaseException], bool]
280
+ ) -> list[BaseException]:
281
+ """Flatten an ExceptionGroup tree, returning leaves matching ``predicate``."""
282
+ out: list[BaseException] = []
283
+ stack: list[BaseException] = [group]
284
+ while stack:
285
+ exc = stack.pop()
286
+ if isinstance(exc, BaseExceptionGroup):
287
+ stack.extend(exc.exceptions)
288
+ elif predicate(exc):
289
+ out.append(exc)
290
+ return out
291
+
292
+
293
+ @dataclass
294
+ class RunOutcome:
295
+ """Structured result of a single agent run attempt."""
296
+
297
+ success: bool
298
+ result: Any = None
299
+ error: BaseException | None = None
300
+
301
+
302
+ # ---- The main entry point ---------------------------------------------------
303
+
304
+
305
+ async def run(
306
+ agent: Any,
307
+ prompt: str,
308
+ *,
309
+ attachments: Sequence[BinaryContent | None] = None,
310
+ link_attachments: Sequence[ImageUrl | DocumentUrl | None] = None,
311
+ output_type: type[Any | None] = None,
312
+ **kwargs: Any,
313
+ ) -> Any:
314
+ """Run ``agent`` against ``prompt`` with full tool + cancellation support."""
315
+
316
+ prompt = _sanitize_prompt(prompt)
317
+ group_id = str(uuid.uuid4())
318
+
319
+ if agent._code_generation_agent is None:
320
+ build_pydantic_agent(agent)
321
+ pydantic_agent = agent._code_generation_agent
322
+
323
+ if output_type is not None:
324
+ pydantic_agent = build_pydantic_agent(agent, output_type=output_type)
325
+
326
+ prompt = _should_prepend_system_prompt(agent, prompt)
327
+ prompt_payload = _build_prompt_payload(prompt, attachments, link_attachments)
328
+
329
+ async def _do_run(prompt_to_use: Any) -> Any:
330
+ """Run the agent once, then honour any plugin ``retry`` requests."""
331
+ usage_limits = UsageLimits(request_limit=get_message_limit())
332
+
333
+ # Streaming config gate (issue #295). When streaming is disabled we
334
+ # never install the stream handler at all and always render from the
335
+ # final result. When it's enabled we wrap the handler in a detector
336
+ # and fall back to a one-shot render only if no text actually streamed.
337
+ #
338
+ # Model-level override: models with ``"streaming": false`` in
339
+ # models.json always use non-streaming requests (e.g. kimi-k2.5
340
+ # via crof.ai whose SSE transport is flaky).
341
+ use_streaming = get_enable_streaming() and _model_allows_streaming(
342
+ agent.get_model_name()
343
+ )
344
+ detector: StreamingTextDetector | None = (
345
+ StreamingTextDetector(event_stream_handler) if use_streaming else None
346
+ )
347
+ stream_handler = detector if detector is not None else None
348
+ # When streaming is disabled we must also clear the handler stored on
349
+ # the pydantic agent itself. Some wrappers bake
350
+ # ``event_stream_handler`` into the agent at build
351
+ # time; passing ``None`` to ``.run()`` isn't enough because pydantic-ai
352
+ # falls back via ``event_stream_handler or self.event_stream_handler``.
353
+ # Nuking ``_event_stream_handler`` forces the property to return
354
+ # ``None``, which makes pydantic-ai use the non-streaming
355
+ # ``model.request()`` path instead of ``request_stream()``.
356
+ _saved_handler: Any = None
357
+ handler_was_modified = False
358
+ if not use_streaming:
359
+ _saved_handler = getattr(pydantic_agent, "_event_stream_handler", None)
360
+ pydantic_agent._event_stream_handler = None
361
+ handler_was_modified = True
362
+ # Plugins can render their own output and ask us to skip
363
+ # the non-streaming fallback render.
364
+ skip_fallback_render = on_should_skip_fallback_render(agent)
365
+
366
+ @streaming_retry()
367
+ async def _call() -> Any:
368
+ return await pydantic_agent.run(
369
+ prompt_to_use,
370
+ message_history=agent._message_history,
371
+ usage_limits=usage_limits,
372
+ event_stream_handler=stream_handler,
373
+ **kwargs,
374
+ )
375
+
376
+ async def _call_with_exception_recovery() -> Any:
377
+ """Run ``_call`` and let plugins request one exception retry."""
378
+ try:
379
+ return await _call()
380
+ except Exception as exc:
381
+ hook_results = await on_agent_exception(
382
+ exc,
383
+ agent=agent,
384
+ agent_name=agent.name,
385
+ model_name=agent.get_model_name(),
386
+ )
387
+ retry_req = next(
388
+ (r for r in hook_results if isinstance(r, dict) and r.get("retry")),
389
+ None,
390
+ )
391
+ if not retry_req:
392
+ raise
393
+
394
+ retry_delay = retry_req.get("delay", 0.0)
395
+ if retry_delay:
396
+ await asyncio.sleep(retry_delay)
397
+ return await _call()
398
+
399
+ try:
400
+ result = await _call_with_exception_recovery()
401
+
402
+ for _ in range(get_max_hook_retries()):
403
+ hook_results = await on_agent_run_result(
404
+ result,
405
+ agent_name=agent.name,
406
+ model_name=agent.get_model_name(),
407
+ )
408
+ retry_req = next(
409
+ (r for r in hook_results if isinstance(r, dict) and r.get("retry")),
410
+ None,
411
+ )
412
+ if not retry_req:
413
+ break
414
+
415
+ retry_prompt = retry_req.get("prompt", "Please continue.")
416
+ retry_delay = retry_req.get("delay", 1.0)
417
+ if hasattr(result, "all_messages"):
418
+ agent._message_history = list(result.all_messages())
419
+ await asyncio.sleep(retry_delay)
420
+
421
+ @streaming_retry()
422
+ async def _retry_call(prompt: str = retry_prompt) -> Any:
423
+ return await pydantic_agent.run(
424
+ prompt,
425
+ message_history=agent._message_history,
426
+ usage_limits=usage_limits,
427
+ event_stream_handler=stream_handler,
428
+ **kwargs,
429
+ )
430
+
431
+ result = await _retry_call()
432
+
433
+ finally:
434
+ # Restore the handler we cleared (non-streaming models).
435
+ if handler_was_modified:
436
+ pydantic_agent._event_stream_handler = _saved_handler
437
+
438
+ # Fallback render when streaming didn't surface any text to the user.
439
+ if result is not None and should_render_fallback(
440
+ detector, skip=skip_fallback_render
441
+ ):
442
+ # TODO: PEP 734 async bridge — render_result_without_streaming uses sync time.sleep
443
+ await asyncio.to_thread(render_result_without_streaming, result)
444
+
445
+ return result
446
+
447
+ async def run_agent_task() -> RunOutcome:
448
+ outcome: RunOutcome | None = None
449
+ try:
450
+ agent._message_history = _history.prune_interrupted_tool_calls(
451
+ agent._message_history
452
+ )
453
+
454
+ run_ctxs = on_agent_run_context(agent, pydantic_agent, group_id)
455
+ async with AsyncExitStack() as stack:
456
+ for cm in run_ctxs:
457
+ await stack.enter_async_context(cm)
458
+ result = await _do_run(prompt_payload)
459
+ outcome = RunOutcome(True, result=result)
460
+ except* UsageLimitExceeded as ule:
461
+ emit_info(f"Usage limit exceeded: {ule}", group_id=group_id)
462
+ emit_info(
463
+ "The agent has reached its usage limit. You can ask it to continue "
464
+ "by saying 'please continue' or similar.",
465
+ group_id=group_id,
466
+ )
467
+ outcome = RunOutcome(False, error=ule)
468
+ except* Exception as other:
469
+ unexpected = _collect_exceptions(
470
+ other,
471
+ lambda e: (
472
+ not isinstance(e, (asyncio.CancelledError, UsageLimitExceeded))
473
+ ),
474
+ )
475
+ for exc in unexpected:
476
+ emit_exception_diagnostics(exc, group_id=group_id)
477
+ outcome = RunOutcome(False, error=other)
478
+ finally:
479
+ agent._message_history = _history.prune_interrupted_tool_calls(
480
+ agent._message_history
481
+ )
482
+ if outcome is None:
483
+ return RunOutcome(False)
484
+ return outcome
485
+
486
+ agent_task = asyncio.create_task(run_agent_task())
487
+
488
+ try:
489
+ await on_agent_run_start(
490
+ agent_name=agent.name,
491
+ model_name=agent.get_model_name(),
492
+ session_id=group_id,
493
+ )
494
+ except Exception:
495
+ # Hook failures never block the agent.
496
+ pass
497
+
498
+ loop = asyncio.get_running_loop()
499
+
500
+ def schedule_agent_cancel() -> None:
501
+ from code_muse.tools.command_runner import _RUNNING_PROCESSES
502
+
503
+ if _RUNNING_PROCESSES:
504
+ emit_warning(
505
+ "Refusing to cancel Agent while a shell command is running — "
506
+ "press Ctrl+X to cancel the shell command."
507
+ )
508
+ return
509
+ if agent_task.done():
510
+ return
511
+ if _active_subagent_tasks:
512
+ emit_warning(
513
+ f"Cancelling {len(_active_subagent_tasks)} active subagent task(s)..."
514
+ )
515
+ for task in list(_active_subagent_tasks):
516
+ if not task.done():
517
+ loop.call_soon_threadsafe(task.cancel)
518
+ loop.call_soon_threadsafe(agent_task.cancel)
519
+
520
+ def keyboard_interrupt_handler(_sig, _frame):
521
+ # Let input() handle its own KeyboardInterrupt if we're mid-prompt.
522
+ if is_awaiting_user_input():
523
+ return
524
+ schedule_agent_cancel()
525
+
526
+ def graceful_sigint_handler(_sig, _frame):
527
+ from code_muse.keymap import get_cancel_agent_display_name
528
+ from code_muse.terminal_utils import reset_windows_terminal_full
529
+
530
+ reset_windows_terminal_full()
531
+ emit_info(f"Use {get_cancel_agent_display_name()} to cancel the agent task.")
532
+
533
+ original_handler = None
534
+ key_listener_stop_event: threading.Event | None = None
535
+ key_listener_thread: threading.Thread | None = None
536
+
537
+ run_success = False
538
+ run_error: BaseException | None = None
539
+ run_response_text = ""
540
+
541
+ try:
542
+ if cancel_agent_uses_signal():
543
+ original_handler = signal.signal(signal.SIGINT, keyboard_interrupt_handler)
544
+ else:
545
+ original_handler = signal.signal(signal.SIGINT, graceful_sigint_handler)
546
+ key_listener_stop_event = threading.Event()
547
+ key_listener_thread = _key_listeners.spawn_key_listener(
548
+ key_listener_stop_event,
549
+ on_escape=lambda: None, # Ctrl+X handled by command_runner
550
+ on_cancel_agent=schedule_agent_cancel,
551
+ )
552
+
553
+ outcome = await agent_task
554
+ if outcome.success:
555
+ result = outcome.result
556
+ run_success = True
557
+ run_response_text = _extract_response_text(result)
558
+ return result
559
+ else:
560
+ run_error = outcome.error
561
+ if outcome.error is not None:
562
+ raise outcome.error
563
+ except asyncio.CancelledError as exc:
564
+ run_response_text = ""
565
+ run_error = exc
566
+ await on_agent_run_cancel(group_id)
567
+ agent_task.cancel()
568
+ raise
569
+ except KeyboardInterrupt:
570
+ run_response_text = ""
571
+ if not agent_task.done():
572
+ agent_task.cancel()
573
+ except BaseExceptionGroup as e:
574
+ run_error = e
575
+ raise
576
+ except Exception as e:
577
+ run_error = e
578
+ raise
579
+ finally:
580
+ with suppress(Exception):
581
+ await on_agent_run_end(
582
+ agent_name=agent.name,
583
+ model_name=agent.get_model_name(),
584
+ session_id=group_id,
585
+ success=run_success,
586
+ error=run_error,
587
+ response_text=run_response_text,
588
+ metadata={"model": agent.get_model_name()},
589
+ )
590
+
591
+ if key_listener_stop_event is not None:
592
+ key_listener_stop_event.set()
593
+ if key_listener_thread is not None:
594
+ key_listener_thread.join(timeout=1.0)
595
+ if original_handler is not None: # SIG_DFL is 0/falsy — explicit check!
596
+ signal.signal(signal.SIGINT, original_handler)