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,613 @@
1
+ # agent_tools.py
2
+ import asyncio
3
+ import hashlib
4
+ import json
5
+ import re
6
+ import threading
7
+ import traceback
8
+ from contextlib import AsyncExitStack, suppress
9
+ from datetime import datetime
10
+ from functools import partial
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from pydantic import BaseModel
15
+
16
+ # Import Agent from pydantic_ai to create temporary agents for invocation
17
+ from pydantic_ai import Agent, RunContext, UsageLimits
18
+ from pydantic_ai.messages import ModelMessage, ModelMessagesTypeAdapter
19
+
20
+ from code_muse.callbacks import (
21
+ on_agent_run_cancel,
22
+ on_agent_run_context,
23
+ on_invoke_agent,
24
+ )
25
+ from code_muse.config import (
26
+ DATA_DIR,
27
+ get_message_limit,
28
+ )
29
+ from code_muse.messaging import (
30
+ SubAgentInvocationMessage,
31
+ SubAgentResponseMessage,
32
+ emit_error,
33
+ emit_info,
34
+ emit_success,
35
+ get_message_bus,
36
+ get_session_context,
37
+ set_session_context,
38
+ )
39
+ from code_muse.secret_storage import atomic_write_private_json
40
+ from code_muse.tools.common import generate_group_id
41
+ from code_muse.tools.subagent_context import subagent_context
42
+
43
+ # Set to track active subagent invocation tasks
44
+ _active_subagent_tasks: set[asyncio.Task] = set()
45
+
46
+ # PERF-08: Cache model instances by name to avoid rebuilding AsyncAnthropic
47
+ # clients, ClaudeCacheAsyncClient, etc. on every invoke_agent call.
48
+ _model_instance_cache: dict[str, Any] = {}
49
+ # FREE-THREADED: _model_instance_cache_lock guards a cache accessed from async
50
+ # invoke_agent and potentially sync paths. Keep threading.Lock for safety.
51
+ _model_instance_cache_lock = threading.Lock()
52
+
53
+
54
+ def _generate_session_hash_suffix() -> str:
55
+ """Generate a short SHA1 hash suffix based on current timestamp for uniqueness.
56
+
57
+ Returns:
58
+ A 6-character hex string, e.g., "a3f2b1"
59
+ """
60
+ timestamp = str(datetime.now().timestamp())
61
+ return hashlib.sha1(timestamp.encode()).hexdigest()[:6]
62
+
63
+
64
+ # Regex pattern for kebab-case session IDs
65
+ SESSION_ID_PATTERN = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
66
+ SESSION_ID_MAX_LENGTH = 128
67
+
68
+
69
+ def _validate_session_id(session_id: str) -> None:
70
+ """Validate that a session ID follows kebab-case naming conventions.
71
+
72
+ Args:
73
+ session_id: The session identifier to validate
74
+
75
+ Raises:
76
+ ValueError: If the session_id is invalid
77
+
78
+ Valid format:
79
+ - Lowercase letters (a-z)
80
+ - Numbers (0-9)
81
+ - Hyphens (-) to separate words
82
+ - No uppercase, no underscores, no special characters
83
+ - Length between 1 and 128 characters
84
+
85
+ Examples:
86
+ Valid: "my-session", "agent-session-1", "discussion-about-code"
87
+ Invalid: "MySession", "my_session", "my session", "my--session"
88
+ """
89
+ if not session_id:
90
+ raise ValueError("session_id cannot be empty")
91
+
92
+ if len(session_id) > SESSION_ID_MAX_LENGTH:
93
+ raise ValueError(
94
+ f"Invalid session_id '{session_id}': must be {SESSION_ID_MAX_LENGTH} characters or less"
95
+ )
96
+
97
+ if not SESSION_ID_PATTERN.match(session_id):
98
+ raise ValueError(
99
+ f"Invalid session_id '{session_id}': must be kebab-case "
100
+ "(lowercase letters, numbers, and hyphens only). "
101
+ "Examples: 'my-session', 'agent-session-1', 'discussion-about-code'"
102
+ )
103
+
104
+
105
+ def _get_subagent_sessions_dir() -> Path:
106
+ """Get the directory for storing subagent session data.
107
+
108
+ Returns:
109
+ Path to XDG data directory/subagent_sessions/
110
+ """
111
+ sessions_dir = Path(DATA_DIR) / "subagent_sessions"
112
+ sessions_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
113
+ return sessions_dir
114
+
115
+
116
+ def _save_session_history(
117
+ session_id: str,
118
+ message_history: list[ModelMessage],
119
+ agent_name: str,
120
+ initial_prompt: str | None = None,
121
+ ) -> None:
122
+ """Save session history to filesystem as JSON with atomic private writes.
123
+
124
+ Writes a canonical ``.json`` file and a backward-compat ``.pkl`` file
125
+ (containing the same JSON payload) so old callers checking for
126
+ ``.pkl`` still find what they expect.
127
+
128
+ Args:
129
+ session_id: The session identifier (must be kebab-case)
130
+ message_history: List of messages to save
131
+ agent_name: Name of the agent being invoked
132
+ initial_prompt: The first prompt that started this session (for .txt metadata)
133
+
134
+ Raises:
135
+ ValueError: If session_id is not valid kebab-case format
136
+ """
137
+ # Validate session_id format before saving
138
+ _validate_session_id(session_id)
139
+
140
+ sessions_dir = _get_subagent_sessions_dir()
141
+
142
+ # Build the JSON payload
143
+ session_data = {
144
+ "schema": "muse.subagent.session.v1",
145
+ "format": "pydantic-ai-model-messages-json",
146
+ "messages": ModelMessagesTypeAdapter.dump_python(message_history, mode="json"),
147
+ }
148
+
149
+ # Save canonical .json file
150
+ json_path = sessions_dir / f"{session_id}.json"
151
+ atomic_write_private_json(json_path, session_data)
152
+
153
+ # Save compat .pkl file (same JSON content, different extension)
154
+ pkl_path = sessions_dir / f"{session_id}.pkl"
155
+ try:
156
+ tmp_pkl = pkl_path.with_suffix(".tmp")
157
+ with open(tmp_pkl, "w", encoding="utf-8") as f:
158
+ json.dump(session_data, f, indent=2)
159
+ tmp_pkl.replace(pkl_path)
160
+ except OSError:
161
+ pass # best-effort compat file
162
+
163
+ # Save or update txt file with metadata
164
+ txt_path = sessions_dir / f"{session_id}.txt"
165
+ if not txt_path.exists() and initial_prompt:
166
+ # Only write initial metadata on first save
167
+ metadata = {
168
+ "session_id": session_id,
169
+ "agent_name": agent_name,
170
+ "initial_prompt": initial_prompt,
171
+ "created_at": datetime.now().isoformat(),
172
+ "message_count": len(message_history),
173
+ }
174
+ with open(txt_path, "w") as f:
175
+ json.dump(metadata, f, indent=2)
176
+ elif txt_path.exists():
177
+ # Update message count on subsequent saves
178
+ try:
179
+ with open(txt_path) as f:
180
+ metadata = json.load(f)
181
+ metadata["message_count"] = len(message_history)
182
+ metadata["last_updated"] = datetime.now().isoformat()
183
+ with open(txt_path, "w") as f:
184
+ json.dump(metadata, f, indent=2)
185
+ except Exception:
186
+ pass # If we can't update metadata, no big deal
187
+
188
+
189
+ def _load_session_history(session_id: str) -> list[ModelMessage]:
190
+ """Load session history from filesystem.
191
+
192
+ Prefers the canonical ``.json`` file; falls back to ``.pkl``
193
+ (which contains the same JSON payload for backward compat).
194
+
195
+ Args:
196
+ session_id: The session identifier (must be kebab-case)
197
+
198
+ Returns:
199
+ List of ModelMessage objects, or empty list if session doesn't exist
200
+
201
+ Raises:
202
+ ValueError: If session_id is not valid kebab-case format
203
+ """
204
+ # Validate session_id format before loading
205
+ _validate_session_id(session_id)
206
+
207
+ sessions_dir = _get_subagent_sessions_dir()
208
+
209
+ # Try canonical .json first
210
+ json_path = sessions_dir / f"{session_id}.json"
211
+ if json_path.exists():
212
+ try:
213
+ with open(json_path, encoding="utf-8") as f:
214
+ session_data = json.load(f)
215
+ raw_messages = session_data.get("messages", [])
216
+ if isinstance(raw_messages, list):
217
+ try:
218
+ return ModelMessagesTypeAdapter.validate_python(raw_messages)
219
+ except Exception:
220
+ # Validation may fail due to pydantic-ai version
221
+ # differences; return raw messages as best effort
222
+ return raw_messages
223
+ except Exception:
224
+ pass
225
+
226
+ # Try compat .pkl (contains JSON written by current save)
227
+ pkl_path = sessions_dir / f"{session_id}.pkl"
228
+ if pkl_path.exists():
229
+ try:
230
+ with open(pkl_path, encoding="utf-8") as f:
231
+ session_data = json.load(f)
232
+ raw_messages = session_data.get("messages", [])
233
+ if isinstance(raw_messages, list):
234
+ try:
235
+ return ModelMessagesTypeAdapter.validate_python(raw_messages)
236
+ except Exception:
237
+ return raw_messages
238
+ except UnicodeDecodeError, ValueError:
239
+ pass
240
+
241
+ return []
242
+
243
+
244
+ class AgentInfo(BaseModel):
245
+ """Information about an available agent."""
246
+
247
+ name: str
248
+ display_name: str
249
+ description: str
250
+
251
+
252
+ class ListAgentsOutput(BaseModel):
253
+ """Output for the list_agents tool."""
254
+
255
+ agents: list[AgentInfo]
256
+ error: str | None = None
257
+
258
+
259
+ class AgentInvokeOutput(BaseModel):
260
+ """Output for the invoke_agent tool."""
261
+
262
+ response: str | None
263
+ agent_name: str
264
+ session_id: str | None = None
265
+ error: str | None = None
266
+
267
+
268
+ def register_list_agents(agent):
269
+ """Register the list_agents tool with the provided agent.
270
+
271
+ Args:
272
+ agent: The agent to register the tool with
273
+ """
274
+
275
+ @agent.tool
276
+ def list_agents(context: RunContext) -> ListAgentsOutput:
277
+ """List all available sub-agents that can be invoked."""
278
+ # Generate a group ID for this tool execution
279
+ group_id = generate_group_id("list_agents")
280
+
281
+ from rich.text import Text
282
+
283
+ from code_muse.config import get_banner_color
284
+
285
+ list_agents_color = get_banner_color("list_agents")
286
+
287
+ try:
288
+ from code_muse.agents import get_agent_descriptions, get_available_agents
289
+
290
+ # Get available agents and their descriptions from the agent manager
291
+ agents_dict = get_available_agents()
292
+ descriptions_dict = get_agent_descriptions()
293
+
294
+ # Convert to list of AgentInfo objects
295
+ agents = [
296
+ AgentInfo(
297
+ name=name,
298
+ display_name=display_name,
299
+ description=descriptions_dict.get(name, "No description available"),
300
+ )
301
+ for name, display_name in agents_dict.items()
302
+ ]
303
+
304
+ # Quiet output - banner and count on same line
305
+ agent_count = len(agents)
306
+ emit_info(
307
+ Text.from_markup(
308
+ f"[bold white on {list_agents_color}] LIST AGENTS [/bold white on {list_agents_color}] "
309
+ f"[dim]Found {agent_count} agent(s).[/dim]"
310
+ ),
311
+ message_group=group_id,
312
+ )
313
+
314
+ return ListAgentsOutput(agents=agents)
315
+
316
+ except Exception as e:
317
+ error_msg = f"Error listing agents: {str(e)}"
318
+ emit_error(error_msg, message_group=group_id)
319
+ return ListAgentsOutput(agents=[], error=error_msg)
320
+
321
+ return list_agents
322
+
323
+
324
+ def register_invoke_agent(agent):
325
+ """Register the invoke_agent tool with the provided agent.
326
+
327
+ Args:
328
+ agent: The agent to register the tool with
329
+ """
330
+
331
+ @agent.tool
332
+ async def invoke_agent(
333
+ context: RunContext, agent_name: str, prompt: str, session_id: str | None = None
334
+ ) -> AgentInvokeOutput:
335
+ """Invoke a specific sub-agent with a given prompt.
336
+
337
+ Returns:
338
+ AgentInvokeOutput: Contains response, agent_name, session_id, and error fields.
339
+ """
340
+ from code_muse.agents.agent_manager import load_agent
341
+
342
+ # Validate user-provided session_id if given
343
+ if session_id is not None:
344
+ try:
345
+ _validate_session_id(session_id)
346
+ except ValueError as e:
347
+ # Return error immediately if session_id is invalid
348
+ group_id = generate_group_id("invoke_agent", agent_name)
349
+ emit_error(str(e), message_group=group_id)
350
+ return AgentInvokeOutput(
351
+ response=None, agent_name=agent_name, error=str(e)
352
+ )
353
+
354
+ # Generate a group ID for this tool execution
355
+ group_id = generate_group_id("invoke_agent", agent_name)
356
+
357
+ # Check if this is an existing session or a new one
358
+ # For user-provided session_id, check if it exists
359
+ # For None, we'll generate a new one below
360
+ if session_id is not None:
361
+ message_history = _load_session_history(session_id)
362
+ is_new_session = len(message_history) == 0
363
+ else:
364
+ message_history = []
365
+ is_new_session = True
366
+
367
+ # Generate or finalize session_id
368
+ if session_id is None:
369
+ # Auto-generate a session ID with hash suffix for uniqueness
370
+ # Example: "qa-expert-session-a3f2b1"
371
+ hash_suffix = _generate_session_hash_suffix()
372
+ session_id = f"{agent_name}-session-{hash_suffix}"
373
+ elif is_new_session:
374
+ # User provided a base name for a NEW session - append hash suffix
375
+ # Example: "review-auth" -> "review-auth-a3f2b1"
376
+ hash_suffix = _generate_session_hash_suffix()
377
+ session_id = f"{session_id}-{hash_suffix}"
378
+ # else: continuing existing session, use session_id as-is
379
+
380
+ # Lazy imports to avoid circular dependency
381
+ from code_muse.agents.subagent_stream_handler import subagent_stream_handler
382
+
383
+ # Fire plugin hook so plugins can observe/log agent invocations
384
+ with suppress(Exception):
385
+ await on_invoke_agent(
386
+ agent_name, prompt, session_id=session_id, context=context
387
+ )
388
+
389
+ # Emit structured invocation message via MessageBus
390
+ bus = get_message_bus()
391
+ bus.emit(
392
+ SubAgentInvocationMessage(
393
+ agent_name=agent_name,
394
+ session_id=session_id,
395
+ prompt=prompt,
396
+ is_new_session=is_new_session,
397
+ message_count=len(message_history),
398
+ )
399
+ )
400
+
401
+ # Save current session context and set the new one for this sub-agent
402
+ previous_session_id = get_session_context()
403
+ set_session_context(session_id)
404
+
405
+ # Set browser session for browser tools (qa-melpomene, etc.)
406
+ # This allows parallel agent invocations to each have their own browser
407
+ from code_muse.tools.browser.browser_manager import (
408
+ set_browser_session,
409
+ )
410
+
411
+ browser_session_token = set_browser_session(f"browser-{session_id}")
412
+
413
+ # Bound up-front so the ``except`` block can always reach for it even
414
+ # if load_agent() itself fails before assignment.
415
+ agent_config = None
416
+
417
+ try:
418
+ # Lazy import to break circular dependency with messaging module
419
+ from code_muse.model_factory import ModelFactory, make_model_settings
420
+
421
+ # Load the specified agent config
422
+ agent_config = load_agent(agent_name)
423
+
424
+ # Seed the wrapper's message history with the loaded session so that
425
+ # ``make_history_processor(agent_config)`` — wired into the temp
426
+ # agent's ``history_processors`` — mutates ``agent_config._message_history``
427
+ # in place as the run progresses. That means on a mid-run crash we
428
+ # can read partial progress straight off the wrapper below.
429
+ agent_config.set_message_history(list(message_history))
430
+
431
+ # Get the current model for creating a temporary agent
432
+ model_name = agent_config.get_model_name()
433
+ models_config = ModelFactory.load_config()
434
+
435
+ # Only proceed if we have a valid model configuration
436
+ if model_name not in models_config:
437
+ raise ValueError(f"Model '{model_name}' not found in configuration")
438
+
439
+ # PERF-08: Reuse cached model instance for the same model name
440
+ with _model_instance_cache_lock:
441
+ model = _model_instance_cache.get(model_name)
442
+ if model is None:
443
+ model = ModelFactory.get_model(model_name, models_config)
444
+ with _model_instance_cache_lock:
445
+ _model_instance_cache[model_name] = model
446
+
447
+ # Create a temporary agent instance to avoid interfering with current agent state
448
+ instructions = agent_config.get_full_system_prompt()
449
+
450
+ # Add AGENTS.md content to subagents.
451
+ # ``load_muse_rules`` lives on the builder module since the
452
+ # base_agent split in 79dfc3c8; it's not a method on the agent.
453
+ from code_muse.agents._builder import load_muse_rules
454
+
455
+ agent_rules = load_muse_rules()
456
+ if agent_rules:
457
+ instructions += f"\n\n{agent_rules}"
458
+
459
+ # Apply prompt additions (like file permission handling) to temporary agents
460
+ from code_muse import callbacks
461
+ from code_muse.model_utils import prepare_prompt_for_model
462
+
463
+ prompt_additions = callbacks.on_load_prompt()
464
+ if len(prompt_additions):
465
+ instructions += "\n" + "\n".join(prompt_additions)
466
+
467
+ # Handle claude-code models: swap instructions, and prepend system prompt only on first message
468
+ prepared = prepare_prompt_for_model(
469
+ model_name,
470
+ instructions,
471
+ prompt,
472
+ prepend_system_to_user=is_new_session, # Only prepend on first message
473
+ )
474
+ instructions = prepared.instructions
475
+ prompt = prepared.user_prompt
476
+
477
+ model_settings = make_model_settings(model_name)
478
+
479
+ external_servers = []
480
+
481
+ from code_muse.agents._compaction import make_history_processor
482
+
483
+ # Build the pydantic-ai agent. external servers are always included in
484
+ # the constructor; plugins may swap them out at run
485
+ # time via the ``agent_run_context`` hook if their wrapper can't
486
+ # handle them directly.
487
+ temp_agent = Agent(
488
+ model=model,
489
+ instructions=instructions,
490
+ output_type=str,
491
+ retries=3,
492
+ toolsets=external_servers,
493
+ history_processors=[make_history_processor(agent_config)],
494
+ model_settings=model_settings,
495
+ )
496
+
497
+ # Register the tools that the agent needs
498
+ from code_muse.tools import register_tools_for_agent
499
+
500
+ agent_tools = agent_config.get_available_tools()
501
+ register_tools_for_agent(temp_agent, agent_tools, model_name=model_name)
502
+
503
+ # Always use subagent_stream_handler to silence output and update console manager
504
+ # This ensures all sub-agent output goes through the aggregated dashboard
505
+ stream_handler = partial(subagent_stream_handler, session_id=session_id)
506
+
507
+ # Wrap the agent run in subagent context for tracking
508
+ with subagent_context(agent_name):
509
+ run_ctxs = on_agent_run_context(agent_config, temp_agent, group_id)
510
+ async with AsyncExitStack() as stack:
511
+ for cm in run_ctxs:
512
+ await stack.enter_async_context(cm)
513
+ task = asyncio.create_task(
514
+ temp_agent.run(
515
+ prompt,
516
+ message_history=message_history,
517
+ usage_limits=UsageLimits(request_limit=get_message_limit()),
518
+ event_stream_handler=stream_handler,
519
+ )
520
+ )
521
+ _active_subagent_tasks.add(task)
522
+
523
+ try:
524
+ result = await task
525
+ finally:
526
+ _active_subagent_tasks.discard(task)
527
+ if task.cancelled():
528
+ await on_agent_run_cancel(group_id)
529
+
530
+ # Extract the response from the result
531
+ response = result.output
532
+
533
+ # Update the session history with the new messages from this interaction
534
+ # The result contains all_messages which includes the full conversation
535
+ updated_history = result.all_messages()
536
+
537
+ # Save to filesystem (include initial prompt only for new sessions)
538
+ _save_session_history(
539
+ session_id=session_id,
540
+ message_history=updated_history,
541
+ agent_name=agent_name,
542
+ initial_prompt=prompt if is_new_session else None,
543
+ )
544
+
545
+ # Emit structured response message via MessageBus
546
+ bus.emit(
547
+ SubAgentResponseMessage(
548
+ agent_name=agent_name,
549
+ session_id=session_id,
550
+ response=response,
551
+ message_count=len(updated_history),
552
+ )
553
+ )
554
+
555
+ # Emit clean completion summary
556
+ emit_success(
557
+ f"✓ {agent_name} completed successfully", message_group=group_id
558
+ )
559
+
560
+ return AgentInvokeOutput(
561
+ response=response, agent_name=agent_name, session_id=session_id
562
+ )
563
+
564
+ except Exception as e:
565
+ # Emit clean failure summary
566
+ emit_error(f"✗ {agent_name} failed: {str(e)}", message_group=group_id)
567
+
568
+ # Full traceback for debugging
569
+ error_msg = f"Error invoking agent '{agent_name}': {traceback.format_exc()}"
570
+ emit_error(error_msg, message_group=group_id)
571
+
572
+ # Save whatever progress the agent made before crashing. The history
573
+ # processor keeps ``agent_config._message_history`` in sync with each
574
+ # completed turn, so this captures every committed turn up to the
575
+ # failure point. Best-effort: a save failure must not mask the
576
+ # original error, so we swallow anything the save itself raises.
577
+ try:
578
+ partial_history = (
579
+ agent_config.get_message_history() if agent_config else []
580
+ )
581
+ if partial_history and len(partial_history) > len(message_history):
582
+ _save_session_history(
583
+ session_id=session_id,
584
+ message_history=partial_history,
585
+ agent_name=agent_name,
586
+ initial_prompt=prompt if is_new_session else None,
587
+ )
588
+ emit_info(
589
+ f"💾 Saved partial session '{session_id}' "
590
+ f"({len(partial_history)} message(s)) before error",
591
+ message_group=group_id,
592
+ )
593
+ except Exception:
594
+ pass
595
+
596
+ return AgentInvokeOutput(
597
+ response=None,
598
+ agent_name=agent_name,
599
+ session_id=session_id,
600
+ error=error_msg,
601
+ )
602
+
603
+ finally:
604
+ # Restore the previous session context
605
+ set_session_context(previous_session_id)
606
+ # Reset browser session context
607
+ from code_muse.tools.browser.browser_manager import (
608
+ _browser_session_var,
609
+ )
610
+
611
+ _browser_session_var.reset(browser_session_token)
612
+
613
+ return invoke_agent
@@ -0,0 +1,26 @@
1
+ """Ask User Question tool for Muse.
2
+
3
+ This tool allows agents to ask users interactive multiple-choice questions
4
+ through a terminal TUI interface. Uses prompt_toolkit for the split-panel
5
+ UI similar to the /colors command.
6
+ """
7
+
8
+ from .handler import ask_user_question
9
+ from .models import (
10
+ AskUserQuestionInput,
11
+ AskUserQuestionOutput,
12
+ Question,
13
+ QuestionAnswer,
14
+ QuestionOption,
15
+ )
16
+ from .registration import register_ask_user_question
17
+
18
+ __all__ = [
19
+ "ask_user_question",
20
+ "register_ask_user_question",
21
+ "AskUserQuestionInput",
22
+ "AskUserQuestionOutput",
23
+ "Question",
24
+ "QuestionAnswer",
25
+ "QuestionOption",
26
+ ]