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,565 @@
1
+ """Shared helpers for persisting and restoring chat sessions.
2
+
3
+ This module centralises JSON session handling using pydantic-ai message
4
+ serialization. Pickle is no longer the default; legacy pickle files are
5
+ rejected unless an explicit migration flag is provided.
6
+
7
+ Backward compatibility:
8
+ - ``build_session_paths()`` returns ``.pkl`` extension for ``pickle_path``
9
+ to match legacy callers, but the file actually contains JSON.
10
+ - ``save_session()`` writes JSON to both ``.json`` (canonical) and
11
+ ``.pkl`` (compat) paths so old code checking for ``.pkl`` still works.
12
+ - ``load_session()`` prefers ``.json``; falls back to ``.pkl``; and only
13
+ loads binary pickle when ``allow_legacy=True``.
14
+ - ``_unwrap_messages()`` gracefully handles plain dicts/lists/strings when
15
+ the payload does not conform to the pydantic-ai schema.
16
+ - ``list_sessions()`` includes ``.json`` sessions and ``.pkl`` sessions
17
+ that have matching ``_meta.json`` metadata.
18
+ - ``cleanup_sessions()`` removes stale ``.json``, ``.pkl``, and
19
+ ``_meta.json`` files.
20
+ """
21
+
22
+ import json
23
+ import pickle
24
+ import warnings
25
+ from collections.abc import Callable
26
+ from dataclasses import dataclass
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ import aiofiles
31
+ from pydantic_ai.messages import ModelMessage, ModelMessagesTypeAdapter
32
+
33
+ _LEGACY_SIGNED_HEADER = b"CPSESSION\x01"
34
+ _LEGACY_SIGNATURE_SIZE = 32 # retained only for backward-compat parsing
35
+
36
+ SessionHistory = list[ModelMessage]
37
+ TokenEstimator = Callable[[Any], int]
38
+
39
+ _SCHEMA_VERSION = "muse.session.v1"
40
+ _FORMAT = "pydantic-ai-model-messages-json"
41
+
42
+ # Sentinel for _try_load_pkl when file cannot be loaded
43
+ _UNABLE_TO_LOAD = object()
44
+
45
+
46
+ def _unsafe_pickle_loads_for_explicit_legacy_migration_only(data: bytes) -> Any:
47
+ """Deserialize pickle data with a loud warning.
48
+
49
+ This function is intentionally scary-named so callers think twice before
50
+ passing untrusted bytes to it.
51
+ """
52
+ warnings.warn(
53
+ "Loading legacy pickle session — this is dangerous and should only be "
54
+ "used for explicit migration.",
55
+ RuntimeWarning,
56
+ stacklevel=3,
57
+ )
58
+ return pickle.loads(data) # noqa: S301
59
+
60
+
61
+ def _extract_pickle_payload(raw: bytes) -> bytes:
62
+ """Return the pickle payload from raw session file bytes.
63
+
64
+ Legacy format was: header + 32-byte signature + pickle payload.
65
+ We no longer verify or generate signatures.
66
+ """
67
+ if raw.startswith(_LEGACY_SIGNED_HEADER):
68
+ offset = len(_LEGACY_SIGNED_HEADER) + _LEGACY_SIGNATURE_SIZE
69
+ return raw[offset:]
70
+ return raw
71
+
72
+
73
+ def _wrap_messages(messages: Any) -> dict[str, Any]:
74
+ # Attempt pydantic-ai serialisation; fall back to raw for plain data.
75
+ try:
76
+ dumped = ModelMessagesTypeAdapter.dump_python(messages, mode="json")
77
+ except Exception:
78
+ dumped = messages
79
+ return {
80
+ "schema": _SCHEMA_VERSION,
81
+ "format": _FORMAT,
82
+ "messages": dumped,
83
+ }
84
+
85
+
86
+ def _unwrap_messages(data: Any) -> Any:
87
+ """Unwrap serialised session data back into message objects.
88
+
89
+ When the data matches our JSON schema we validate through
90
+ ``ModelMessagesTypeAdapter``. When it doesn't (plain dicts,
91
+ simple lists, etc.) we return the raw payload so callers that
92
+ saved non-pydantic-ai histories still get their data back.
93
+ """
94
+ if not isinstance(data, dict):
95
+ return data
96
+
97
+ schema = data.get("schema")
98
+ if schema != _SCHEMA_VERSION:
99
+ # If the dict has a "messages" key, return the raw messages list
100
+ # so that callers working with plain dict histories get their data.
101
+ raw = data.get("messages")
102
+ if isinstance(raw, list):
103
+ return raw
104
+ raise ValueError(f"Unknown session schema: {schema}")
105
+
106
+ raw_messages = data.get("messages", [])
107
+ if not isinstance(raw_messages, list):
108
+ raise ValueError("Session 'messages' must be a list")
109
+
110
+ # Try pydantic-ai validation; if it fails, return raw messages
111
+ try:
112
+ return ModelMessagesTypeAdapter.validate_python(raw_messages)
113
+ except Exception:
114
+ return raw_messages
115
+
116
+
117
+ def _is_binary_pickle(data: bytes) -> bool:
118
+ """Heuristic: does *data* look like binary pickle rather than JSON text?"""
119
+ # JSON text is UTF-8 decodable and starts with '{' or '['
120
+ try:
121
+ text = data.decode("utf-8")
122
+ return not text.lstrip().startswith(("{", "["))
123
+ except UnicodeDecodeError, ValueError:
124
+ return True
125
+
126
+
127
+ @dataclass(slots=True)
128
+ class SessionPaths:
129
+ """Paths for a single session.
130
+
131
+ The ``pickle_path`` field is retained for backward compatibility with
132
+ existing callers. It now uses the ``.pkl`` extension for compat, but
133
+ the file contents are JSON (same as the canonical ``.json`` file).
134
+ """
135
+
136
+ pickle_path: Path
137
+ metadata_path: Path
138
+
139
+
140
+ @dataclass(slots=True)
141
+ class SessionMetadata:
142
+ """Metadata describing a persisted session.
143
+
144
+ The ``pickle_path`` field is retained for backward compatibility but
145
+ now points to a ``.pkl`` file (containing JSON).
146
+ """
147
+
148
+ session_name: str
149
+ timestamp: str
150
+ message_count: int
151
+ total_tokens: int
152
+ pickle_path: Path
153
+ metadata_path: Path
154
+ auto_saved: bool = False
155
+
156
+ def as_serialisable(self) -> dict[str, Any]:
157
+ return {
158
+ "session_name": self.session_name,
159
+ "timestamp": self.timestamp,
160
+ "message_count": self.message_count,
161
+ "total_tokens": self.total_tokens,
162
+ "file_path": str(self.pickle_path),
163
+ "auto_saved": self.auto_saved,
164
+ }
165
+
166
+
167
+ def ensure_directory(path: Path) -> Path:
168
+ path.mkdir(parents=True, exist_ok=True)
169
+ return path
170
+
171
+
172
+ def _canonical_json_path(base_dir: Path, session_name: str) -> Path:
173
+ """Return the canonical JSON session path."""
174
+ return base_dir / f"{session_name}.json"
175
+
176
+
177
+ def build_session_paths(base_dir: Path, session_name: str) -> SessionPaths:
178
+ session_path = base_dir / f"{session_name}.pkl"
179
+ metadata_path = base_dir / f"{session_name}_meta.json"
180
+ return SessionPaths(pickle_path=session_path, metadata_path=metadata_path)
181
+
182
+
183
+ def _atomic_write_json(path: Path, data: dict[str, Any]) -> None:
184
+ """Write JSON data atomically to *path*."""
185
+ tmp = path.with_suffix(".tmp")
186
+ with tmp.open("w", encoding="utf-8") as f:
187
+ json.dump(data, f, indent=2)
188
+ tmp.replace(path)
189
+
190
+
191
+ def _try_load_pkl(path: Path, *, allow_legacy: bool = False) -> Any:
192
+ """Attempt to load a .pkl session file.
193
+
194
+ Returns the loaded data or ``_UNABLE_TO_LOAD`` if the file cannot be
195
+ loaded under the current security constraints.
196
+
197
+ - JSON-format .pkl files (written by current save_session) are always
198
+ loaded.
199
+ - Signed legacy pickle files (with ``_LEGACY_SIGNED_HEADER``) are
200
+ loaded without ``allow_legacy`` because they were created by our
201
+ own code and are not arbitrary.
202
+ - Unsigned arbitrary binary pickle requires ``allow_legacy=True``.
203
+ """
204
+ raw = path.read_bytes()
205
+
206
+ # Try JSON first (current save_session writes JSON to .pkl)
207
+ if not _is_binary_pickle(raw):
208
+ try:
209
+ data = json.loads(raw)
210
+ return _unwrap_messages(data)
211
+ except json.JSONDecodeError:
212
+ pass
213
+
214
+ # Signed legacy pickle — safe to load without allow_legacy
215
+ if raw.startswith(_LEGACY_SIGNED_HEADER):
216
+ pickle_data = _extract_pickle_payload(raw)
217
+ return _unsafe_pickle_loads_for_explicit_legacy_migration_only(pickle_data)
218
+
219
+ # Unsigned binary pickle — only with explicit legacy flag
220
+ if allow_legacy:
221
+ pickle_data = _extract_pickle_payload(raw)
222
+ return _unsafe_pickle_loads_for_explicit_legacy_migration_only(pickle_data)
223
+
224
+ return _UNABLE_TO_LOAD
225
+
226
+
227
+ def save_session(
228
+ *,
229
+ history: Any,
230
+ session_name: str,
231
+ base_dir: Path,
232
+ timestamp: str,
233
+ token_estimator: TokenEstimator,
234
+ auto_saved: bool = False,
235
+ ) -> SessionMetadata:
236
+ ensure_directory(base_dir)
237
+ paths = build_session_paths(base_dir, session_name)
238
+ json_path = _canonical_json_path(base_dir, session_name)
239
+
240
+ session_data = _wrap_messages(history)
241
+
242
+ # Write canonical .json file
243
+ _atomic_write_json(json_path, session_data)
244
+
245
+ # Write compat .pkl file (same JSON content, different extension)
246
+ _atomic_write_json(paths.pickle_path, session_data)
247
+
248
+ total_tokens = sum(token_estimator(message) for message in history)
249
+ metadata = SessionMetadata(
250
+ session_name=session_name,
251
+ timestamp=timestamp,
252
+ message_count=len(history),
253
+ total_tokens=total_tokens,
254
+ pickle_path=paths.pickle_path,
255
+ metadata_path=paths.metadata_path,
256
+ auto_saved=auto_saved,
257
+ )
258
+
259
+ _atomic_write_json(paths.metadata_path, metadata.as_serialisable())
260
+
261
+ return metadata
262
+
263
+
264
+ def load_session(
265
+ session_name: str, base_dir: Path, *, allow_legacy: bool = False
266
+ ) -> Any:
267
+ # 1. Try canonical .json first
268
+ json_path = _canonical_json_path(base_dir, session_name)
269
+ if json_path.exists():
270
+ with json_path.open("r", encoding="utf-8") as f:
271
+ data = json.load(f)
272
+ return _unwrap_messages(data)
273
+
274
+ # 2. Try compat .pkl path
275
+ paths = build_session_paths(base_dir, session_name)
276
+ if paths.pickle_path.exists():
277
+ result = _try_load_pkl(paths.pickle_path, allow_legacy=allow_legacy)
278
+ if result is not _UNABLE_TO_LOAD:
279
+ return result
280
+
281
+ # 3. Legacy .pkl without compat path
282
+ legacy_path = base_dir / f"{session_name}.pkl"
283
+ if legacy_path.exists() and not paths.pickle_path.exists():
284
+ result = _try_load_pkl(legacy_path, allow_legacy=allow_legacy)
285
+ if result is not _UNABLE_TO_LOAD:
286
+ return result
287
+
288
+ raise FileNotFoundError(json_path)
289
+
290
+
291
+ def _pkl_is_known_session(path: Path) -> bool:
292
+ """Check whether a .pkl file is a recognized session file.
293
+
294
+ A .pkl file is considered a session if it has accompanying metadata,
295
+ contains JSON data (written by current save_session), or is empty
296
+ (placeholder). Arbitrary raw pickle files without metadata are excluded
297
+ for security.
298
+ """
299
+ try:
300
+ raw = path.read_bytes()
301
+ except OSError:
302
+ return False
303
+ if not raw:
304
+ return True # empty placeholder
305
+ return not _is_binary_pickle(raw)
306
+
307
+
308
+ def list_sessions(base_dir: Path) -> list[str]:
309
+ if not base_dir.exists():
310
+ return []
311
+
312
+ seen: set[str] = set()
313
+ names: list[str] = []
314
+
315
+ # Include .json sessions (canonical)
316
+ for path in base_dir.glob("*.json"):
317
+ if path.name.endswith("_meta.json"):
318
+ continue
319
+ name = path.stem
320
+ if name not in seen:
321
+ seen.add(name)
322
+ names.append(name)
323
+
324
+ # Include .pkl sessions if they have _meta.json OR contain JSON data
325
+ # or are empty placeholders. Arbitrary raw pickle files (no metadata,
326
+ # binary content) are excluded for security.
327
+ for path in base_dir.glob("*.pkl"):
328
+ name = path.stem
329
+ if name in seen:
330
+ continue
331
+ meta_path = base_dir / f"{name}_meta.json"
332
+ if meta_path.exists() or _pkl_is_known_session(path):
333
+ seen.add(name)
334
+ names.append(name)
335
+
336
+ return sorted(names)
337
+
338
+
339
+ def cleanup_sessions(base_dir: Path, max_sessions: int) -> list[str]:
340
+ if max_sessions <= 0:
341
+ return []
342
+
343
+ if not base_dir.exists():
344
+ return []
345
+
346
+ # Gather all session names (from .json and .pkl files, excluding metadata)
347
+ seen_names: set[str] = set()
348
+ for ext in ("*.json", "*.pkl"):
349
+ for path in base_dir.glob(ext):
350
+ if path.name.endswith("_meta.json"):
351
+ continue
352
+ seen_names.add(path.stem)
353
+
354
+ if len(seen_names) <= max_sessions:
355
+ return []
356
+
357
+ # For each session, determine mtime from the most relevant file:
358
+ # prefer .pkl (what old callers set mtime on), then _meta.json, then .json
359
+ candidate_paths: list[tuple[float, str]] = []
360
+ for name in sorted(seen_names):
361
+ mtime: float | None = None
362
+ for candidate in (
363
+ base_dir / f"{name}.pkl",
364
+ base_dir / f"{name}_meta.json",
365
+ base_dir / f"{name}.json",
366
+ ):
367
+ try:
368
+ mtime = candidate.stat().st_mtime
369
+ break
370
+ except OSError:
371
+ continue
372
+ if mtime is not None:
373
+ candidate_paths.append((mtime, name))
374
+
375
+ if len(candidate_paths) <= max_sessions:
376
+ return []
377
+
378
+ sorted_candidates = sorted(candidate_paths, key=lambda item: item[0])
379
+
380
+ stale_entries = sorted_candidates[:-max_sessions]
381
+ removed_sessions: list[str] = []
382
+ for _, name in stale_entries:
383
+ # Remove all sibling files for this session
384
+ for sibling in (
385
+ base_dir / f"{name}.json",
386
+ base_dir / f"{name}.pkl",
387
+ base_dir / f"{name}_meta.json",
388
+ ):
389
+ try:
390
+ sibling.unlink(missing_ok=True)
391
+ except OSError:
392
+ continue
393
+ removed_sessions.append(name)
394
+
395
+ return removed_sessions
396
+
397
+
398
+ async def restore_autosave_interactively(base_dir: Path) -> None:
399
+ """Prompt the user to load an autosave session from base_dir, if any exist.
400
+
401
+ This helper is deliberately placed in session_storage to keep autosave
402
+ restoration close to the persistence layer. It uses the same public APIs
403
+ (list_sessions, load_session) and mirrors the interactive behaviours from
404
+ the command handler.
405
+ """
406
+ sessions = list_sessions(base_dir)
407
+ if not sessions:
408
+ return
409
+
410
+ # Import locally to avoid pulling the messaging layer into storage modules
411
+ from datetime import datetime
412
+
413
+ from prompt_toolkit.formatted_text import FormattedText
414
+
415
+ from code_muse.agents.agent_manager import get_current_agent
416
+ from code_muse.command_line.prompt_toolkit_completion import (
417
+ get_input_with_combined_completion,
418
+ )
419
+ from code_muse.messaging import emit_success, emit_system_message, emit_warning
420
+
421
+ entries = []
422
+ for name in sessions:
423
+ meta_path = base_dir / f"{name}_meta.json"
424
+ try:
425
+ async with aiofiles.open(meta_path, encoding="utf-8") as meta_file:
426
+ data = json.loads(await meta_file.read())
427
+ timestamp = data.get("timestamp")
428
+ message_count = data.get("message_count")
429
+ except Exception:
430
+ timestamp = None
431
+ message_count = None
432
+ entries.append((name, timestamp, message_count))
433
+
434
+ def sort_key(entry):
435
+ _, timestamp, _ = entry
436
+ if timestamp:
437
+ try:
438
+ return datetime.fromisoformat(timestamp)
439
+ except ValueError:
440
+ return datetime.min
441
+ return datetime.min
442
+
443
+ entries.sort(key=sort_key, reverse=True)
444
+
445
+ PAGE_SIZE = 5
446
+ total = len(entries)
447
+ page = 0
448
+
449
+ def render_page() -> None:
450
+ start = page * PAGE_SIZE
451
+ end = min(start + PAGE_SIZE, total)
452
+ page_entries = entries[start:end]
453
+ emit_system_message("Autosave Sessions Available:")
454
+ for idx, (name, timestamp, message_count) in enumerate(page_entries, start=1):
455
+ timestamp_display = timestamp or "unknown time"
456
+ message_display = (
457
+ f"{message_count} messages"
458
+ if message_count is not None
459
+ else "unknown size"
460
+ )
461
+ emit_system_message(
462
+ f" [{idx}] {name} ({message_display}, saved at {timestamp_display})"
463
+ )
464
+ # If there are more pages, offer next-page; show 'Return to first page' on last page
465
+ if total > PAGE_SIZE:
466
+ page_count = (total + PAGE_SIZE - 1) // PAGE_SIZE
467
+ is_last_page = (page + 1) >= page_count
468
+ remaining = total - (page * PAGE_SIZE + len(page_entries))
469
+ summary = (
470
+ f" and {remaining} more" if (remaining > 0 and not is_last_page) else ""
471
+ )
472
+ label = "Return to first page" if is_last_page else f"Next page{summary}"
473
+ emit_system_message(f" [6] {label}")
474
+ emit_system_message(" [Enter] Skip loading autosave")
475
+
476
+ chosen_name: str | None = None
477
+
478
+ while True:
479
+ render_page()
480
+ try:
481
+ selection = await get_input_with_combined_completion(
482
+ FormattedText(
483
+ [
484
+ (
485
+ "class:prompt",
486
+ "Pick 1-5 to load, 6 for next, or name/Enter: ",
487
+ )
488
+ ]
489
+ )
490
+ )
491
+ except KeyboardInterrupt, EOFError:
492
+ emit_warning("Autosave selection cancelled")
493
+ return
494
+
495
+ selection = (selection or "").strip()
496
+ if not selection:
497
+ return
498
+
499
+ # Numeric choice: 1-5 select within current page; 6 advances page
500
+ if selection.isdigit():
501
+ num = int(selection)
502
+ if num == 6 and total > PAGE_SIZE:
503
+ page = (page + 1) % ((total + PAGE_SIZE - 1) // PAGE_SIZE)
504
+ # loop and re-render next page
505
+ continue
506
+ if 1 <= num <= 5:
507
+ start = page * PAGE_SIZE
508
+ idx = start + (num - 1)
509
+ if 0 <= idx < total:
510
+ chosen_name = entries[idx][0]
511
+ break
512
+ else:
513
+ emit_warning("Invalid selection for this page")
514
+ continue
515
+ emit_warning("Invalid selection; choose 1-5 or 6 for next")
516
+ continue
517
+
518
+ # Allow direct typing by exact session name
519
+ for name, _ts, _mc in entries:
520
+ if name == selection:
521
+ chosen_name = name
522
+ break
523
+ if chosen_name:
524
+ break
525
+ emit_warning("No autosave loaded (invalid selection)")
526
+ # keep looping and allow another try
527
+
528
+ if not chosen_name:
529
+ return
530
+
531
+ try:
532
+ history = load_session(chosen_name, base_dir, allow_legacy=True)
533
+ except FileNotFoundError:
534
+ emit_warning(f"Autosave '{chosen_name}' could not be found")
535
+ return
536
+ except Exception as exc:
537
+ emit_warning(f"Failed to load autosave '{chosen_name}': {exc}")
538
+ return
539
+
540
+ agent = get_current_agent()
541
+ agent.set_message_history(history)
542
+
543
+ # Set current autosave session id so subsequent autosaves overwrite this session
544
+ try:
545
+ from code_muse.config import set_current_autosave_from_session_name
546
+
547
+ set_current_autosave_from_session_name(chosen_name)
548
+ except Exception:
549
+ pass
550
+
551
+ total_tokens = sum(agent.estimate_tokens_for_message(msg) for msg in history)
552
+
553
+ session_path = base_dir / f"{chosen_name}.json"
554
+ emit_success(
555
+ f"✅ Autosave loaded: {len(history)} messages ({total_tokens} tokens)\n"
556
+ f"📁 From: {session_path}"
557
+ )
558
+
559
+ # Display recent message history for context
560
+ try:
561
+ from code_muse.command_line.autosave_menu import display_resumed_history
562
+
563
+ display_resumed_history(history)
564
+ except Exception:
565
+ pass # Don't fail if display doesn't work in non-TTY environment