iCode-TUI 0.29.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 (1138) hide show
  1. chrys/__init__.py +16 -0
  2. chrys/app/__init__.py +3 -0
  3. chrys/app/acp/__init__.py +3 -0
  4. chrys/app/acp/bridge.py +664 -0
  5. chrys/app/acp/content.py +132 -0
  6. chrys/app/acp/doc/frontend-api.md +425 -0
  7. chrys/app/acp/history.py +585 -0
  8. chrys/app/acp/server.py +1796 -0
  9. chrys/app/acp/session_manager.py +1815 -0
  10. chrys/app/acp/tool_status.py +45 -0
  11. chrys/app/cli/__init__.py +3 -0
  12. chrys/app/cli/acp.py +178 -0
  13. chrys/app/cli/app.py +165 -0
  14. chrys/app/cli/approvals.py +101 -0
  15. chrys/app/cli/headless.py +223 -0
  16. chrys/app/cli/launch_cwd.py +42 -0
  17. chrys/app/cli/profiles.py +298 -0
  18. chrys/app/cli/progress.py +857 -0
  19. chrys/app/cli/run.py +425 -0
  20. chrys/app/cli/serve.py +1593 -0
  21. chrys/app/cli/tool_summary.py +225 -0
  22. chrys/app/cli/trajectory.py +201 -0
  23. chrys/app/cli/workflow.py +601 -0
  24. chrys/app/features/__init__.py +3 -0
  25. chrys/app/features/buddy/__init__.py +3 -0
  26. chrys/app/features/buddy/actions.py +68 -0
  27. chrys/app/features/buddy/animation.py +76 -0
  28. chrys/app/features/buddy/commands.py +196 -0
  29. chrys/app/features/buddy/docs/design.md +187 -0
  30. chrys/app/features/buddy/hatchery.py +80 -0
  31. chrys/app/features/buddy/lifecycle.py +41 -0
  32. chrys/app/features/buddy/model.py +265 -0
  33. chrys/app/features/buddy/pixel_renderer.py +113 -0
  34. chrys/app/features/buddy/pixel_sprites.py +279 -0
  35. chrys/app/features/buddy/portrait.py +76 -0
  36. chrys/app/features/buddy/progression.py +58 -0
  37. chrys/app/features/buddy/replies.py +191 -0
  38. chrys/app/features/buddy/sprites/alien.toml +70 -0
  39. chrys/app/features/buddy/sprites/axolotl.toml +71 -0
  40. chrys/app/features/buddy/sprites/bat.toml +69 -0
  41. chrys/app/features/buddy/sprites/bee.toml +69 -0
  42. chrys/app/features/buddy/sprites/cactus.toml +73 -0
  43. chrys/app/features/buddy/sprites/capybara.toml +70 -0
  44. chrys/app/features/buddy/sprites/cat.toml +69 -0
  45. chrys/app/features/buddy/sprites/crab.toml +70 -0
  46. chrys/app/features/buddy/sprites/dragon.toml +71 -0
  47. chrys/app/features/buddy/sprites/duck.toml +69 -0
  48. chrys/app/features/buddy/sprites/fox.toml +69 -0
  49. chrys/app/features/buddy/sprites/frog.toml +70 -0
  50. chrys/app/features/buddy/sprites/ghost.toml +70 -0
  51. chrys/app/features/buddy/sprites/goose.toml +69 -0
  52. chrys/app/features/buddy/sprites/jelly.toml +70 -0
  53. chrys/app/features/buddy/sprites/llama.toml +70 -0
  54. chrys/app/features/buddy/sprites/mushroom.toml +69 -0
  55. chrys/app/features/buddy/sprites/octopus.toml +70 -0
  56. chrys/app/features/buddy/sprites/owl.toml +70 -0
  57. chrys/app/features/buddy/sprites/panda.toml +71 -0
  58. chrys/app/features/buddy/sprites/penguin.toml +69 -0
  59. chrys/app/features/buddy/sprites/rabbit.toml +70 -0
  60. chrys/app/features/buddy/sprites/robot.toml +72 -0
  61. chrys/app/features/buddy/sprites/shark.toml +70 -0
  62. chrys/app/features/buddy/sprites/snail.toml +69 -0
  63. chrys/app/features/buddy/sprites/snake.toml +69 -0
  64. chrys/app/features/buddy/sprites/tabby.toml +69 -0
  65. chrys/app/features/buddy/sprites/turtle.toml +69 -0
  66. chrys/app/features/buddy/store.py +143 -0
  67. chrys/app/features/session_title/__init__.py +7 -0
  68. chrys/app/features/session_title/updater.py +582 -0
  69. chrys/app/installer.py +651 -0
  70. chrys/app/parsing.py +17 -0
  71. chrys/app/tui/__init__.py +3 -0
  72. chrys/app/tui/app.py +1287 -0
  73. chrys/app/tui/behaviors/__init__.py +3 -0
  74. chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
  75. chrys/app/tui/behaviors/insert_clipboard.py +73 -0
  76. chrys/app/tui/behaviors/right_click_copy.py +146 -0
  77. chrys/app/tui/binding_display.py +139 -0
  78. chrys/app/tui/buddy_messages.py +17 -0
  79. chrys/app/tui/buddy_reply.py +86 -0
  80. chrys/app/tui/chrys.tcss +549 -0
  81. chrys/app/tui/clipboard.py +109 -0
  82. chrys/app/tui/copy_messages.py +10 -0
  83. chrys/app/tui/i18n.py +230 -0
  84. chrys/app/tui/language.py +58 -0
  85. chrys/app/tui/notifications/__init__.py +12 -0
  86. chrys/app/tui/notifications/drivers.py +180 -0
  87. chrys/app/tui/notifications/service.py +126 -0
  88. chrys/app/tui/notifications/settings.py +141 -0
  89. chrys/app/tui/screens/__init__.py +3 -0
  90. chrys/app/tui/screens/agents/__init__.py +8 -0
  91. chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
  92. chrys/app/tui/screens/agents/config.py +2142 -0
  93. chrys/app/tui/screens/agents/config.tcss +169 -0
  94. chrys/app/tui/screens/agents/panels/__init__.py +3 -0
  95. chrys/app/tui/screens/agents/panels/acp.py +1176 -0
  96. chrys/app/tui/screens/agents/panels/basic.py +497 -0
  97. chrys/app/tui/screens/agents/panels/compaction.py +231 -0
  98. chrys/app/tui/screens/agents/panels/config_card.py +218 -0
  99. chrys/app/tui/screens/agents/panels/instructions.py +91 -0
  100. chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
  101. chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
  102. chrys/app/tui/screens/agents/panels/memory.py +684 -0
  103. chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
  104. chrys/app/tui/screens/agents/panels/skills.py +806 -0
  105. chrys/app/tui/screens/agents/panels/subagents.py +652 -0
  106. chrys/app/tui/screens/agents/panels/tools.py +205 -0
  107. chrys/app/tui/screens/agents/picker.py +103 -0
  108. chrys/app/tui/screens/agents/skill_paths.py +38 -0
  109. chrys/app/tui/screens/agents/validation_messages.py +452 -0
  110. chrys/app/tui/screens/dialogs/__init__.py +35 -0
  111. chrys/app/tui/screens/dialogs/agent_load.py +834 -0
  112. chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
  113. chrys/app/tui/screens/dialogs/app_mode.py +55 -0
  114. chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
  115. chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
  116. chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
  117. chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
  118. chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
  119. chrys/app/tui/screens/dialogs/approval/body.py +110 -0
  120. chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
  121. chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
  122. chrys/app/tui/screens/dialogs/ask_user.py +148 -0
  123. chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
  124. chrys/app/tui/screens/dialogs/base.py +84 -0
  125. chrys/app/tui/screens/dialogs/confirm.py +184 -0
  126. chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
  127. chrys/app/tui/screens/dialogs/connection_test.py +183 -0
  128. chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
  129. chrys/app/tui/screens/dialogs/diagram.py +102 -0
  130. chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
  131. chrys/app/tui/screens/dialogs/editor.py +378 -0
  132. chrys/app/tui/screens/dialogs/editor.tcss +132 -0
  133. chrys/app/tui/screens/dialogs/file_picker.py +582 -0
  134. chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
  135. chrys/app/tui/screens/dialogs/fork_session.py +219 -0
  136. chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
  137. chrys/app/tui/screens/dialogs/image_compression.py +102 -0
  138. chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
  139. chrys/app/tui/screens/dialogs/man_page.py +172 -0
  140. chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
  141. chrys/app/tui/screens/dialogs/new_folder.py +277 -0
  142. chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
  143. chrys/app/tui/screens/dialogs/notice.tcss +14 -0
  144. chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
  145. chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
  146. chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
  147. chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
  148. chrys/app/tui/screens/dialogs/session_title.py +110 -0
  149. chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
  150. chrys/app/tui/screens/dialogs/tool_view.py +207 -0
  151. chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
  152. chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
  153. chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
  154. chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
  155. chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
  156. chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
  157. chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
  158. chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
  159. chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
  160. chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
  161. chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
  162. chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
  163. chrys/app/tui/screens/diff/__init__.py +22 -0
  164. chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
  165. chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
  166. chrys/app/tui/screens/diff/screen.py +1532 -0
  167. chrys/app/tui/screens/diff/screen.tcss +104 -0
  168. chrys/app/tui/screens/guides/__init__.py +7 -0
  169. chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
  170. chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
  171. chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
  172. chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
  173. chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
  174. chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
  175. chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
  176. chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
  177. chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
  178. chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
  179. chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
  180. chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
  181. chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
  182. chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
  183. chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
  184. chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
  185. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
  186. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
  187. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
  188. chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
  189. chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
  190. chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
  191. chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
  192. chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
  193. chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
  194. chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
  195. chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
  196. chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
  197. chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
  198. chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
  199. chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
  200. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
  201. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
  202. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
  203. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
  204. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
  205. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
  206. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
  207. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
  208. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
  209. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
  210. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
  211. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
  212. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
  213. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
  214. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
  215. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
  216. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
  217. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
  218. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
  219. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
  220. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
  221. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
  222. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
  223. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
  224. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
  225. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
  226. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
  227. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
  228. chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
  229. chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
  230. chrys/app/tui/screens/guides/index.py +215 -0
  231. chrys/app/tui/screens/guides/reader.py +173 -0
  232. chrys/app/tui/screens/guides/screen.py +442 -0
  233. chrys/app/tui/screens/guides/screen.tcss +52 -0
  234. chrys/app/tui/screens/logs/__init__.py +7 -0
  235. chrys/app/tui/screens/logs/screen.py +504 -0
  236. chrys/app/tui/screens/logs/screen.tcss +45 -0
  237. chrys/app/tui/screens/main/__init__.py +7 -0
  238. chrys/app/tui/screens/main/action_availability.py +30 -0
  239. chrys/app/tui/screens/main/buddy_command.py +143 -0
  240. chrys/app/tui/screens/main/commands.py +1073 -0
  241. chrys/app/tui/screens/main/config_actions.py +464 -0
  242. chrys/app/tui/screens/main/copy_actions.py +117 -0
  243. chrys/app/tui/screens/main/dialog_controllers.py +826 -0
  244. chrys/app/tui/screens/main/dialog_gateway.py +166 -0
  245. chrys/app/tui/screens/main/diff_controller.py +478 -0
  246. chrys/app/tui/screens/main/engine_read_model.py +184 -0
  247. chrys/app/tui/screens/main/event_handlers.py +2014 -0
  248. chrys/app/tui/screens/main/input_flow.py +241 -0
  249. chrys/app/tui/screens/main/live_diff.py +244 -0
  250. chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
  251. chrys/app/tui/screens/main/model_indicator.py +217 -0
  252. chrys/app/tui/screens/main/navigation.py +343 -0
  253. chrys/app/tui/screens/main/ports.py +571 -0
  254. chrys/app/tui/screens/main/recent_dirs.py +123 -0
  255. chrys/app/tui/screens/main/rollback_controller.py +586 -0
  256. chrys/app/tui/screens/main/runtime_info.py +71 -0
  257. chrys/app/tui/screens/main/screen.py +2265 -0
  258. chrys/app/tui/screens/main/screen.tcss +21 -0
  259. chrys/app/tui/screens/main/session_handlers.py +1127 -0
  260. chrys/app/tui/screens/main/session_title.py +239 -0
  261. chrys/app/tui/screens/main/settings_coordinator.py +365 -0
  262. chrys/app/tui/screens/main/settings_persistence.py +184 -0
  263. chrys/app/tui/screens/main/shell_mode.py +112 -0
  264. chrys/app/tui/screens/main/state.py +289 -0
  265. chrys/app/tui/screens/main/submit_state.py +11 -0
  266. chrys/app/tui/screens/main/subscriptions.py +177 -0
  267. chrys/app/tui/screens/main/suggestions.py +982 -0
  268. chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
  269. chrys/app/tui/screens/main/view_adapter.py +1567 -0
  270. chrys/app/tui/screens/main/workflow_browser.py +747 -0
  271. chrys/app/tui/screens/main/workflow_content.py +248 -0
  272. chrys/app/tui/screens/main/workflow_controller.py +240 -0
  273. chrys/app/tui/screens/main/workflow_feedback.py +137 -0
  274. chrys/app/tui/screens/main/workflow_flow.py +49 -0
  275. chrys/app/tui/screens/main/workflow_run_control.py +456 -0
  276. chrys/app/tui/screens/main/workflow_session_view.py +316 -0
  277. chrys/app/tui/screens/main/workspace_actions.py +272 -0
  278. chrys/app/tui/screens/main/workspace_branch.py +194 -0
  279. chrys/app/tui/screens/menus/__init__.py +8 -0
  280. chrys/app/tui/screens/menus/languages.py +114 -0
  281. chrys/app/tui/screens/models/__init__.py +7 -0
  282. chrys/app/tui/screens/models/picker.py +147 -0
  283. chrys/app/tui/screens/models/screen.py +2043 -0
  284. chrys/app/tui/screens/models/screen.tcss +255 -0
  285. chrys/app/tui/screens/sessions/__init__.py +8 -0
  286. chrys/app/tui/screens/sessions/presenter.py +586 -0
  287. chrys/app/tui/screens/sessions/screen.py +779 -0
  288. chrys/app/tui/screens/sessions/screen.tcss +127 -0
  289. chrys/app/tui/screens/settings/__init__.py +29 -0
  290. chrys/app/tui/screens/settings/danger.py +26 -0
  291. chrys/app/tui/screens/settings/dialog.py +281 -0
  292. chrys/app/tui/screens/settings/layout.py +519 -0
  293. chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
  294. chrys/app/tui/screens/settings/panes/__init__.py +3 -0
  295. chrys/app/tui/screens/settings/panes/notifications.py +248 -0
  296. chrys/app/tui/screens/settings/panes/sessions.py +148 -0
  297. chrys/app/tui/screens/settings/ports.py +55 -0
  298. chrys/app/tui/screens/settings/provenance.py +102 -0
  299. chrys/app/tui/screens/settings/rows.py +546 -0
  300. chrys/app/tui/screens/settings/settings.tcss +406 -0
  301. chrys/app/tui/screens/themes/__init__.py +8 -0
  302. chrys/app/tui/screens/themes/dialogs.py +330 -0
  303. chrys/app/tui/screens/themes/editor.py +632 -0
  304. chrys/app/tui/screens/themes/editor.tcss +268 -0
  305. chrys/app/tui/screens/themes/messages.py +95 -0
  306. chrys/app/tui/screens/themes/palette.py +694 -0
  307. chrys/app/tui/screens/themes/panel.py +113 -0
  308. chrys/app/tui/screens/themes/picker.py +203 -0
  309. chrys/app/tui/screens/themes/save.py +191 -0
  310. chrys/app/tui/support/__init__.py +3 -0
  311. chrys/app/tui/support/file_manager.py +64 -0
  312. chrys/app/tui/support/gc_freeze.py +1114 -0
  313. chrys/app/tui/support/path_shortcuts.py +50 -0
  314. chrys/app/tui/support/prompt_history.py +298 -0
  315. chrys/app/tui/support/workspace_mru.py +476 -0
  316. chrys/app/tui/terminal/__init__.py +6 -0
  317. chrys/app/tui/terminal/_pty_backend.py +114 -0
  318. chrys/app/tui/terminal/emulator/__init__.py +24 -0
  319. chrys/app/tui/terminal/emulator/buffer.py +456 -0
  320. chrys/app/tui/terminal/emulator/charsets.py +113 -0
  321. chrys/app/tui/terminal/emulator/core.py +963 -0
  322. chrys/app/tui/terminal/emulator/input.py +342 -0
  323. chrys/app/tui/terminal/emulator/parser.py +227 -0
  324. chrys/app/tui/terminal/emulator/pen.py +158 -0
  325. chrys/app/tui/terminal/launcher.py +455 -0
  326. chrys/app/tui/terminal/leaked_reports.py +44 -0
  327. chrys/app/tui/terminal/panel.py +323 -0
  328. chrys/app/tui/terminal/pty_process.py +405 -0
  329. chrys/app/tui/terminal/rendering.py +151 -0
  330. chrys/app/tui/terminal/shell.py +273 -0
  331. chrys/app/tui/terminal/shell_integration.py +199 -0
  332. chrys/app/tui/terminal/title.py +224 -0
  333. chrys/app/tui/terminal/widget.py +654 -0
  334. chrys/app/tui/theme.py +422 -0
  335. chrys/app/tui/theme_loader.py +201 -0
  336. chrys/app/tui/theme_stylesheet.py +49 -0
  337. chrys/app/tui/themes/__init__.py +3 -0
  338. chrys/app/tui/themes/document.py +120 -0
  339. chrys/app/tui/themes/preview.py +170 -0
  340. chrys/app/tui/themes/store.py +176 -0
  341. chrys/app/tui/toasts.py +69 -0
  342. chrys/app/tui/util/__init__.py +3 -0
  343. chrys/app/tui/util/context_pressure.py +146 -0
  344. chrys/app/tui/util/debug_log.py +292 -0
  345. chrys/app/tui/util/diff_entries.py +64 -0
  346. chrys/app/tui/util/formatting.py +53 -0
  347. chrys/app/tui/util/git_branch.py +326 -0
  348. chrys/app/tui/util/invocation_progress.py +57 -0
  349. chrys/app/tui/util/logo.py +32 -0
  350. chrys/app/tui/util/message_gate.py +35 -0
  351. chrys/app/tui/util/mutation_sources.py +14 -0
  352. chrys/app/tui/util/picker_text.py +27 -0
  353. chrys/app/tui/util/removal.py +47 -0
  354. chrys/app/tui/util/rich_style.py +36 -0
  355. chrys/app/tui/util/source_text.py +12 -0
  356. chrys/app/tui/util/static_update.py +88 -0
  357. chrys/app/tui/util/todo_format.py +51 -0
  358. chrys/app/tui/util/visibility.py +214 -0
  359. chrys/app/tui/widgets/__init__.py +60 -0
  360. chrys/app/tui/widgets/ask_user_controls.py +652 -0
  361. chrys/app/tui/widgets/ask_user_markdown.py +17 -0
  362. chrys/app/tui/widgets/ask_user_prompt.py +606 -0
  363. chrys/app/tui/widgets/buttons.py +52 -0
  364. chrys/app/tui/widgets/chat/__init__.py +3 -0
  365. chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
  366. chrys/app/tui/widgets/chat/chrome.py +296 -0
  367. chrys/app/tui/widgets/chat/compaction_card.py +353 -0
  368. chrys/app/tui/widgets/chat/context_fold.py +77 -0
  369. chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
  370. chrys/app/tui/widgets/chat/image_preview.py +214 -0
  371. chrys/app/tui/widgets/chat/live_renderer.py +383 -0
  372. chrys/app/tui/widgets/chat/messages.py +1039 -0
  373. chrys/app/tui/widgets/chat/panel.py +1769 -0
  374. chrys/app/tui/widgets/chat/ports.py +245 -0
  375. chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
  376. chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
  377. chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
  378. chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
  379. chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
  380. chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
  381. chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
  382. chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
  383. chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
  384. chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
  385. chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
  386. chrys/app/tui/widgets/chat/renderers/search.py +348 -0
  387. chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
  388. chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
  389. chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
  390. chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
  391. chrys/app/tui/widgets/chat/renderers/web.py +69 -0
  392. chrys/app/tui/widgets/chat/replay.py +1605 -0
  393. chrys/app/tui/widgets/chat/replay_mount.py +322 -0
  394. chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
  395. chrys/app/tui/widgets/chat/selection_controller.py +435 -0
  396. chrys/app/tui/widgets/chat/session_json.py +508 -0
  397. chrys/app/tui/widgets/chat/toc_model.py +156 -0
  398. chrys/app/tui/widgets/chat/tool_call.py +2478 -0
  399. chrys/app/tui/widgets/chat/tool_registry.py +753 -0
  400. chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
  401. chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
  402. chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
  403. chrys/app/tui/widgets/checkbox.py +24 -0
  404. chrys/app/tui/widgets/chrome/__init__.py +3 -0
  405. chrys/app/tui/widgets/chrome/app_header.py +283 -0
  406. chrys/app/tui/widgets/chrome/commands.py +95 -0
  407. chrys/app/tui/widgets/chrome/file_index.py +68 -0
  408. chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
  409. chrys/app/tui/widgets/chrome/footer.py +284 -0
  410. chrys/app/tui/widgets/chrome/image_paste.py +351 -0
  411. chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
  412. chrys/app/tui/widgets/chrome/status_bar.py +911 -0
  413. chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
  414. chrys/app/tui/widgets/click_affordance.py +24 -0
  415. chrys/app/tui/widgets/color_picker/__init__.py +254 -0
  416. chrys/app/tui/widgets/color_picker/controls.py +171 -0
  417. chrys/app/tui/widgets/color_picker/model.py +89 -0
  418. chrys/app/tui/widgets/dialog_buttons.py +58 -0
  419. chrys/app/tui/widgets/dialog_scroll.py +24 -0
  420. chrys/app/tui/widgets/diff_view/__init__.py +20 -0
  421. chrys/app/tui/widgets/diff_view/cells.py +59 -0
  422. chrys/app/tui/widgets/diff_view/code.py +280 -0
  423. chrys/app/tui/widgets/diff_view/compute.py +286 -0
  424. chrys/app/tui/widgets/diff_view/gutter.py +161 -0
  425. chrys/app/tui/widgets/diff_view/inline.py +77 -0
  426. chrys/app/tui/widgets/diff_view/palette.py +87 -0
  427. chrys/app/tui/widgets/diff_view/protocols.py +24 -0
  428. chrys/app/tui/widgets/diff_view/rows.py +138 -0
  429. chrys/app/tui/widgets/diff_view/unified.py +267 -0
  430. chrys/app/tui/widgets/diff_view/widget.py +209 -0
  431. chrys/app/tui/widgets/editor/__init__.py +79 -0
  432. chrys/app/tui/widgets/editor/coordinates.py +30 -0
  433. chrys/app/tui/widgets/editor/highlighter.py +139 -0
  434. chrys/app/tui/widgets/editor/keymaps.py +357 -0
  435. chrys/app/tui/widgets/editor/types.py +216 -0
  436. chrys/app/tui/widgets/editor/vim.py +1195 -0
  437. chrys/app/tui/widgets/editor/widget.py +401 -0
  438. chrys/app/tui/widgets/file_preview.py +58 -0
  439. chrys/app/tui/widgets/hatch.py +138 -0
  440. chrys/app/tui/widgets/input.py +66 -0
  441. chrys/app/tui/widgets/loading.py +125 -0
  442. chrys/app/tui/widgets/markdown/__init__.py +25 -0
  443. chrys/app/tui/widgets/markdown/_utils.py +21 -0
  444. chrys/app/tui/widgets/markdown/anchors.py +71 -0
  445. chrys/app/tui/widgets/markdown/blocks.py +115 -0
  446. chrys/app/tui/widgets/markdown/copyable.py +163 -0
  447. chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
  448. chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
  449. chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
  450. chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
  451. chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
  452. chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
  453. chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
  454. chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
  455. chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
  456. chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
  457. chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
  458. chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
  459. chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
  460. chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
  461. chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
  462. chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
  463. chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
  464. chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
  465. chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
  466. chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
  467. chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
  468. chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
  469. chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
  470. chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
  471. chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
  472. chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
  473. chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
  474. chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
  475. chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
  476. chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
  477. chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
  478. chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
  479. chrys/app/tui/widgets/markdown/links.py +47 -0
  480. chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
  481. chrys/app/tui/widgets/markdown/math/layout.py +739 -0
  482. chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
  483. chrys/app/tui/widgets/markdown/math/parser.py +325 -0
  484. chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
  485. chrys/app/tui/widgets/markdown/navigator.py +80 -0
  486. chrys/app/tui/widgets/markdown/parser.py +1057 -0
  487. chrys/app/tui/widgets/markdown/stream.py +73 -0
  488. chrys/app/tui/widgets/markdown/toc.py +98 -0
  489. chrys/app/tui/widgets/markdown/viewer.py +146 -0
  490. chrys/app/tui/widgets/markdown/widget.py +2046 -0
  491. chrys/app/tui/widgets/marquee.py +101 -0
  492. chrys/app/tui/widgets/option_menu.py +82 -0
  493. chrys/app/tui/widgets/page_navigator.py +118 -0
  494. chrys/app/tui/widgets/select.py +196 -0
  495. chrys/app/tui/widgets/selection.py +59 -0
  496. chrys/app/tui/widgets/sidebar/__init__.py +3 -0
  497. chrys/app/tui/widgets/sidebar/buddy.py +367 -0
  498. chrys/app/tui/widgets/sidebar/context.py +482 -0
  499. chrys/app/tui/widgets/sidebar/debug.py +195 -0
  500. chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
  501. chrys/app/tui/widgets/sidebar/panel.py +198 -0
  502. chrys/app/tui/widgets/sidebar/tasks.py +198 -0
  503. chrys/app/tui/widgets/sidebar/toc.py +166 -0
  504. chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
  505. chrys/app/tui/widgets/syntax_theme.py +85 -0
  506. chrys/app/tui/widgets/text_area.py +258 -0
  507. chrys/app/tui/widgets/trajectory/__init__.py +8 -0
  508. chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
  509. chrys/app/tui/widgets/trajectory/insights.py +1266 -0
  510. chrys/app/tui/widgets/trajectory/overview.py +1101 -0
  511. chrys/app/tui/widgets/trajectory/panel.py +983 -0
  512. chrys/app/tui/widgets/trajectory/presentation.py +311 -0
  513. chrys/app/tui/widgets/trajectory/session_info.py +124 -0
  514. chrys/app/tui/widgets/trajectory/text_view.py +124 -0
  515. chrys/app/tui/widgets/trajectory/timeline.py +380 -0
  516. chrys/app/tui/widgets/welcome.py +126 -0
  517. chrys/app/tui/widgets/workflow/__init__.py +5 -0
  518. chrys/app/tui/widgets/workflow/edges.py +89 -0
  519. chrys/app/tui/widgets/workflow/graph.py +869 -0
  520. chrys/app/tui/widgets/workflow/info.py +310 -0
  521. chrys/app/tui/widgets/workflow/node_view.py +27 -0
  522. chrys/app/tui/widgets/workflow/output.py +225 -0
  523. chrys/app/tui/widgets/workflow/panel.py +658 -0
  524. chrys/app/tui/widgets/workflow/projector.py +324 -0
  525. chrys/app/tui/widgets/workflow/records.py +69 -0
  526. chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
  527. chrys/app/tui/widgets/workflow/selection.py +151 -0
  528. chrys/app/tui/widgets/workflow/source.py +65 -0
  529. chrys/app/tui/widgets/workflow/text.py +257 -0
  530. chrys/app/tui/widgets/workflow/values.py +317 -0
  531. chrys/foundation/__init__.py +0 -0
  532. chrys/foundation/branding.py +15 -0
  533. chrys/foundation/config/__init__.py +7 -0
  534. chrys/foundation/config/coercion.py +374 -0
  535. chrys/foundation/config/context.py +34 -0
  536. chrys/foundation/config/env_file.py +141 -0
  537. chrys/foundation/config/env_layers.py +370 -0
  538. chrys/foundation/config/migrations.py +521 -0
  539. chrys/foundation/config/notification_events.py +22 -0
  540. chrys/foundation/config/process_settings.py +332 -0
  541. chrys/foundation/config/runtime_pointer.py +133 -0
  542. chrys/foundation/config/settings.py +1952 -0
  543. chrys/foundation/config/settings_store.py +996 -0
  544. chrys/foundation/config/spec.py +396 -0
  545. chrys/foundation/config/user_settings.py +140 -0
  546. chrys/foundation/config/warnings.py +262 -0
  547. chrys/foundation/config/web_values.py +135 -0
  548. chrys/foundation/config/yaml_store.py +487 -0
  549. chrys/foundation/errors/__init__.py +68 -0
  550. chrys/foundation/errors/_legacy.py +117 -0
  551. chrys/foundation/errors/_text.py +13 -0
  552. chrys/foundation/errors/_walk.py +85 -0
  553. chrys/foundation/errors/classify.py +278 -0
  554. chrys/foundation/errors/display.py +274 -0
  555. chrys/foundation/errors/formatting.py +283 -0
  556. chrys/foundation/errors/kinds.py +67 -0
  557. chrys/foundation/errors/network.py +497 -0
  558. chrys/foundation/errors/provider.py +445 -0
  559. chrys/foundation/errors/route.py +85 -0
  560. chrys/foundation/events/__init__.py +8 -0
  561. chrys/foundation/events/bus.py +143 -0
  562. chrys/foundation/events/types.py +1629 -0
  563. chrys/foundation/events/workflow.py +38 -0
  564. chrys/foundation/hosted_tools.py +142 -0
  565. chrys/foundation/i18n/__init__.py +32 -0
  566. chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
  567. chrys/foundation/i18n/catalogs.py +42 -0
  568. chrys/foundation/i18n/formatting.py +202 -0
  569. chrys/foundation/i18n/locale.py +92 -0
  570. chrys/foundation/i18n/localizer.py +187 -0
  571. chrys/foundation/i18n/messages.py +268 -0
  572. chrys/foundation/io/__init__.py +3 -0
  573. chrys/foundation/io/result_content.py +237 -0
  574. chrys/foundation/models/__init__.py +3 -0
  575. chrys/foundation/models/approval_reuse.py +23 -0
  576. chrys/foundation/models/ask_user.py +468 -0
  577. chrys/foundation/models/execution.py +18 -0
  578. chrys/foundation/models/history_markers.py +115 -0
  579. chrys/foundation/models/invocations.py +63 -0
  580. chrys/foundation/models/mutation_scope.py +28 -0
  581. chrys/foundation/models/session_env.py +87 -0
  582. chrys/foundation/models/session_surface.py +29 -0
  583. chrys/foundation/models/todos.py +74 -0
  584. chrys/foundation/models/turns.py +125 -0
  585. chrys/foundation/models/workflow_session.py +89 -0
  586. chrys/foundation/models/workspace.py +59 -0
  587. chrys/foundation/net/__init__.py +3 -0
  588. chrys/foundation/net/address_class.py +57 -0
  589. chrys/foundation/net/route_probe.py +94 -0
  590. chrys/foundation/net/url.py +107 -0
  591. chrys/foundation/observability/__init__.py +3 -0
  592. chrys/foundation/observability/exporters.py +174 -0
  593. chrys/foundation/observability/gate.py +31 -0
  594. chrys/foundation/observability/setup.py +497 -0
  595. chrys/foundation/observability/sink.py +228 -0
  596. chrys/foundation/patches/__init__.py +19 -0
  597. chrys/foundation/patches/mdurl_cache.py +59 -0
  598. chrys/foundation/patches/patcher.py +287 -0
  599. chrys/foundation/patches/staged_members.py +205 -0
  600. chrys/foundation/patches/textual_block_border.py +105 -0
  601. chrys/foundation/patches/textual_button_ansi.py +65 -0
  602. chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
  603. chrys/foundation/patches/textual_callback_cache.py +248 -0
  604. chrys/foundation/patches/textual_callback_dispatch.py +109 -0
  605. chrys/foundation/patches/textual_compositor_cjk.py +566 -0
  606. chrys/foundation/patches/textual_dispatch_cache.py +150 -0
  607. chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
  608. chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
  609. chrys/foundation/patches/textual_lru_acyclic.py +241 -0
  610. chrys/foundation/patches/textual_message_pump.py +136 -0
  611. chrys/foundation/patches/textual_node_diet.py +875 -0
  612. chrys/foundation/patches/textual_one_shot_timer.py +48 -0
  613. chrys/foundation/patches/textual_option_list.py +90 -0
  614. chrys/foundation/patches/textual_precompose.py +89 -0
  615. chrys/foundation/patches/textual_pruned_tabs.py +69 -0
  616. chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
  617. chrys/foundation/patches/textual_removed_node_caches.py +86 -0
  618. chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
  619. chrys/foundation/patches/textual_selection_extract.py +48 -0
  620. chrys/foundation/patches/textual_strip_cycles.py +119 -0
  621. chrys/foundation/patches/textual_tab_selection.py +1611 -0
  622. chrys/foundation/patches/textual_timer_skip.py +70 -0
  623. chrys/foundation/patches/textual_utf8_decoder.py +106 -0
  624. chrys/foundation/patches/textual_win_sleep.py +167 -0
  625. chrys/foundation/patches/watchdog_fsevents.py +70 -0
  626. chrys/foundation/patches/watchdog_windows.py +52 -0
  627. chrys/foundation/platform/__init__.py +556 -0
  628. chrys/foundation/platform/c_api.py +32 -0
  629. chrys/foundation/platform/child_reap.py +107 -0
  630. chrys/foundation/platform/command_line.py +95 -0
  631. chrys/foundation/platform/files.py +1470 -0
  632. chrys/foundation/platform/output_capture.py +113 -0
  633. chrys/foundation/platform/paths.py +193 -0
  634. chrys/foundation/platform/process.py +1468 -0
  635. chrys/foundation/platform/pty_output.py +19 -0
  636. chrys/foundation/platform/runtime_paths.py +281 -0
  637. chrys/foundation/platform/win_console.py +129 -0
  638. chrys/foundation/reasoning_origin.py +67 -0
  639. chrys/foundation/recovery.py +16 -0
  640. chrys/foundation/retry.py +278 -0
  641. chrys/foundation/text/__init__.py +3 -0
  642. chrys/foundation/text/encoding.py +603 -0
  643. chrys/foundation/text/images.py +304 -0
  644. chrys/foundation/text/lines.py +24 -0
  645. chrys/foundation/text/mentions.py +90 -0
  646. chrys/foundation/text/model_json.py +25 -0
  647. chrys/foundation/text/reading_order.py +20 -0
  648. chrys/foundation/text/tokenizer.py +25 -0
  649. chrys/foundation/text/tool_output.py +287 -0
  650. chrys/foundation/text/yaml_io.py +62 -0
  651. chrys/foundation/tool_call_context.py +202 -0
  652. chrys/foundation/tool_execution_stamp.py +184 -0
  653. chrys/foundation/tool_invocation_order.py +48 -0
  654. chrys/foundation/tool_kinds.py +102 -0
  655. chrys/foundation/tool_result_metadata.py +287 -0
  656. chrys/foundation/trajectory/__init__.py +10 -0
  657. chrys/foundation/trajectory/context.py +481 -0
  658. chrys/foundation/trajectory/envelope.py +571 -0
  659. chrys/foundation/trajectory/event_types.py +294 -0
  660. chrys/foundation/trajectory/finalizers.py +46 -0
  661. chrys/foundation/trajectory/fingerprint.py +62 -0
  662. chrys/foundation/trajectory/ids.py +155 -0
  663. chrys/foundation/trajectory/keys.py +99 -0
  664. chrys/foundation/trajectory/lease.py +173 -0
  665. chrys/foundation/trajectory/metadata.py +72 -0
  666. chrys/foundation/trajectory/reader.py +271 -0
  667. chrys/foundation/trajectory/recovery.py +225 -0
  668. chrys/foundation/trajectory/revisions.py +152 -0
  669. chrys/foundation/trajectory/segments.py +225 -0
  670. chrys/foundation/trajectory/tools.py +117 -0
  671. chrys/foundation/trajectory/usage.py +151 -0
  672. chrys/foundation/trajectory/writer.py +972 -0
  673. chrys/foundation/trajectory_timing.py +97 -0
  674. chrys/foundation/util/__init__.py +3 -0
  675. chrys/foundation/util/async_tasks.py +29 -0
  676. chrys/foundation/util/chrys_headers.py +23 -0
  677. chrys/foundation/util/env_templates.py +104 -0
  678. chrys/foundation/util/filenames.py +96 -0
  679. chrys/foundation/util/header_charset.py +138 -0
  680. chrys/foundation/util/httpx_helpers.py +12 -0
  681. chrys/foundation/util/lock.py +236 -0
  682. chrys/foundation/util/once_close.py +46 -0
  683. chrys/foundation/util/session_ids.py +29 -0
  684. chrys/foundation/util/shell_tokens.py +88 -0
  685. chrys/foundation/util/sub_agent_context.py +38 -0
  686. chrys/foundation/util/time.py +35 -0
  687. chrys/foundation/util/unicode_scalars.py +36 -0
  688. chrys/foundation/vendor/__init__.py +43 -0
  689. chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
  690. chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
  691. chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
  692. chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
  693. chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
  694. chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
  695. chrys/kernel/__init__.py +256 -0
  696. chrys/kernel/_content.py +1866 -0
  697. chrys/kernel/_loop_recorder.py +631 -0
  698. chrys/kernel/_null_overlay.py +1026 -0
  699. chrys/kernel/_result_ceiling.py +216 -0
  700. chrys/kernel/_serialization.py +431 -0
  701. chrys/kernel/_tool_arg_errors.py +1629 -0
  702. chrys/kernel/_tool_execution.py +1172 -0
  703. chrys/kernel/_tool_expansion.py +26 -0
  704. chrys/kernel/_types.py +2598 -0
  705. chrys/kernel/agent.py +994 -0
  706. chrys/kernel/client.py +1084 -0
  707. chrys/kernel/compaction.py +1315 -0
  708. chrys/kernel/exceptions.py +211 -0
  709. chrys/kernel/exchanges.py +683 -0
  710. chrys/kernel/identity.py +95 -0
  711. chrys/kernel/images.py +36 -0
  712. chrys/kernel/instrumentation.py +1618 -0
  713. chrys/kernel/loop.py +2308 -0
  714. chrys/kernel/middleware.py +616 -0
  715. chrys/kernel/sessions.py +604 -0
  716. chrys/kernel/tools.py +1061 -0
  717. chrys/kernel/types.py +126 -0
  718. chrys/orchestration/__init__.py +3 -0
  719. chrys/orchestration/engine/__init__.py +3 -0
  720. chrys/orchestration/engine/assembly.py +236 -0
  721. chrys/orchestration/engine/build/__init__.py +3 -0
  722. chrys/orchestration/engine/build/builder.py +1061 -0
  723. chrys/orchestration/engine/build/construction.py +408 -0
  724. chrys/orchestration/engine/build/loaded.py +139 -0
  725. chrys/orchestration/engine/engine.py +789 -0
  726. chrys/orchestration/engine/execution.py +771 -0
  727. chrys/orchestration/engine/loader.py +865 -0
  728. chrys/orchestration/engine/rollback.py +918 -0
  729. chrys/orchestration/engine/run/__init__.py +3 -0
  730. chrys/orchestration/engine/run/active_injection.py +419 -0
  731. chrys/orchestration/engine/run/attachments.py +519 -0
  732. chrys/orchestration/engine/run/bindings.py +1095 -0
  733. chrys/orchestration/engine/run/coordinator.py +1062 -0
  734. chrys/orchestration/engine/run/finalizer.py +417 -0
  735. chrys/orchestration/engine/run/input_refs.py +60 -0
  736. chrys/orchestration/engine/run/prompt_content.py +457 -0
  737. chrys/orchestration/engine/run/resume.py +299 -0
  738. chrys/orchestration/engine/run/retry.py +948 -0
  739. chrys/orchestration/engine/run/runner.py +625 -0
  740. chrys/orchestration/engine/run/runtime_skills.py +146 -0
  741. chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
  742. chrys/orchestration/engine/run/turn_hooks.py +258 -0
  743. chrys/orchestration/engine/run/turn_state.py +149 -0
  744. chrys/orchestration/engine/run/working_dir.py +50 -0
  745. chrys/orchestration/engine/session_lifecycle.py +2013 -0
  746. chrys/orchestration/engine/state/__init__.py +3 -0
  747. chrys/orchestration/engine/state/active_session.py +183 -0
  748. chrys/orchestration/engine/state/controls.py +639 -0
  749. chrys/orchestration/engine/state/current_agent.py +26 -0
  750. chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
  751. chrys/orchestration/engine/state/machine.py +193 -0
  752. chrys/orchestration/engine/state/session_writer.py +434 -0
  753. chrys/orchestration/engine/trajectory.py +714 -0
  754. chrys/orchestration/engine/usage.py +118 -0
  755. chrys/orchestration/invoker/__init__.py +3 -0
  756. chrys/orchestration/invoker/acp.py +727 -0
  757. chrys/orchestration/invoker/acp_protocol.py +1115 -0
  758. chrys/orchestration/invoker/acp_spec.py +79 -0
  759. chrys/orchestration/invoker/attempts.py +994 -0
  760. chrys/orchestration/invoker/child_compaction.py +131 -0
  761. chrys/orchestration/invoker/child_history.py +99 -0
  762. chrys/orchestration/invoker/contracts.py +253 -0
  763. chrys/orchestration/invoker/evidence.py +94 -0
  764. chrys/orchestration/invoker/kernel.py +343 -0
  765. chrys/orchestration/invoker/origin.py +79 -0
  766. chrys/orchestration/invoker/resources.py +343 -0
  767. chrys/orchestration/invoker/runtime.py +299 -0
  768. chrys/orchestration/invoker/usage.py +80 -0
  769. chrys/orchestration/session_hooks.py +117 -0
  770. chrys/orchestration/session_host.py +1023 -0
  771. chrys/orchestration/session_resources.py +95 -0
  772. chrys/orchestration/session_usage.py +219 -0
  773. chrys/orchestration/startup.py +255 -0
  774. chrys/orchestration/sub_agents/__init__.py +3 -0
  775. chrys/orchestration/sub_agents/acp_policy.py +269 -0
  776. chrys/orchestration/sub_agents/kernel_policy.py +848 -0
  777. chrys/orchestration/sub_agents/registration.py +44 -0
  778. chrys/orchestration/sub_agents/shell.py +337 -0
  779. chrys/orchestration/sub_agents/tools.py +2228 -0
  780. chrys/orchestration/workflows/__init__.py +3 -0
  781. chrys/orchestration/workflows/agent_archive.py +156 -0
  782. chrys/orchestration/workflows/agent_node.py +894 -0
  783. chrys/orchestration/workflows/agent_node_build.py +687 -0
  784. chrys/orchestration/workflows/catalog.py +332 -0
  785. chrys/orchestration/workflows/coordinator.py +771 -0
  786. chrys/orchestration/workflows/hooks.py +176 -0
  787. chrys/orchestration/workflows/preview.py +322 -0
  788. chrys/orchestration/workflows/rollback.py +151 -0
  789. chrys/orchestration/workflows/runner.py +986 -0
  790. chrys/orchestration/workflows/session.py +390 -0
  791. chrys/orchestration/workflows/settings.py +97 -0
  792. chrys/orchestration/workflows/validation.py +674 -0
  793. chrys/orchestration/workflows/worker_client.py +1002 -0
  794. chrys/service/__init__.py +3 -0
  795. chrys/service/acp_client/__init__.py +65 -0
  796. chrys/service/acp_client/_transport.py +922 -0
  797. chrys/service/acp_client/client.py +1311 -0
  798. chrys/service/acp_client/errors.py +224 -0
  799. chrys/service/acp_client/protocol.py +220 -0
  800. chrys/service/acp_client/spawn.py +239 -0
  801. chrys/service/acp_client/spec.py +100 -0
  802. chrys/service/agent_middleware/__init__.py +60 -0
  803. chrys/service/agent_middleware/_metadata_keys.py +52 -0
  804. chrys/service/agent_middleware/control/__init__.py +3 -0
  805. chrys/service/agent_middleware/control/approval.py +769 -0
  806. chrys/service/agent_middleware/control/approval_hooks.py +62 -0
  807. chrys/service/agent_middleware/control/ask_user.py +207 -0
  808. chrys/service/agent_middleware/control/interrupt.py +50 -0
  809. chrys/service/agent_middleware/control/sleep.py +118 -0
  810. chrys/service/agent_middleware/control/todo.py +117 -0
  811. chrys/service/agent_middleware/events/__init__.py +3 -0
  812. chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
  813. chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
  814. chrys/service/agent_middleware/events/intermediate_text.py +128 -0
  815. chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
  816. chrys/service/agent_middleware/events/result_persistence.py +98 -0
  817. chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
  818. chrys/service/agent_middleware/events/tool_error_text.py +22 -0
  819. chrys/service/agent_middleware/events/tool_events.py +779 -0
  820. chrys/service/agent_middleware/injection.py +510 -0
  821. chrys/service/agent_middleware/reminders/__init__.py +91 -0
  822. chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
  823. chrys/service/agent_middleware/reminders/base.py +25 -0
  824. chrys/service/agent_middleware/reminders/context_usage.py +74 -0
  825. chrys/service/agent_middleware/reminders/file_change.py +63 -0
  826. chrys/service/agent_middleware/reminders/mcp.py +39 -0
  827. chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
  828. chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
  829. chrys/service/agent_middleware/reminders/skills.py +44 -0
  830. chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
  831. chrys/service/agent_middleware/reminders/todo.py +35 -0
  832. chrys/service/agent_middleware/reminders/turn_line.py +48 -0
  833. chrys/service/agent_middleware/response_validation.py +1380 -0
  834. chrys/service/agent_middleware/system_reminder.py +1107 -0
  835. chrys/service/agent_middleware/validators.py +396 -0
  836. chrys/service/analytics/__init__.py +124 -0
  837. chrys/service/analytics/_actions.py +896 -0
  838. chrys/service/analytics/_context_evidence.py +357 -0
  839. chrys/service/analytics/_critical_path.py +179 -0
  840. chrys/service/analytics/_facts.py +919 -0
  841. chrys/service/analytics/_insights.py +769 -0
  842. chrys/service/analytics/_metric_ops.py +83 -0
  843. chrys/service/analytics/_session_projection.py +460 -0
  844. chrys/service/analytics/_timeline.py +434 -0
  845. chrys/service/analytics/_turn_graph.py +1237 -0
  846. chrys/service/analytics/_turns.py +1377 -0
  847. chrys/service/analytics/_workflows.py +171 -0
  848. chrys/service/analytics/aggregation.py +1474 -0
  849. chrys/service/analytics/classification.py +128 -0
  850. chrys/service/analytics/export.py +803 -0
  851. chrys/service/analytics/findings.py +282 -0
  852. chrys/service/analytics/math.py +79 -0
  853. chrys/service/analytics/model.py +886 -0
  854. chrys/service/analytics/reader.py +321 -0
  855. chrys/service/approval/__init__.py +3 -0
  856. chrys/service/approval/arbitration.py +102 -0
  857. chrys/service/approval/command_identity.py +109 -0
  858. chrys/service/approval/correlation.py +57 -0
  859. chrys/service/approval/grant_store.py +108 -0
  860. chrys/service/approval/judge.py +589 -0
  861. chrys/service/approval/level.py +15 -0
  862. chrys/service/approval/policy.py +209 -0
  863. chrys/service/approval/reuse.py +223 -0
  864. chrys/service/approval/reuse_binding.py +133 -0
  865. chrys/service/approval/safety_classifier.py +536 -0
  866. chrys/service/approval/turn_context.py +49 -0
  867. chrys/service/context/__init__.py +3 -0
  868. chrys/service/context/compaction/__init__.py +38 -0
  869. chrys/service/context/compaction/breaker.py +156 -0
  870. chrys/service/context/compaction/budgets.py +93 -0
  871. chrys/service/context/compaction/calibration.py +77 -0
  872. chrys/service/context/compaction/compression.py +690 -0
  873. chrys/service/context/compaction/current_turn_drop.py +458 -0
  874. chrys/service/context/compaction/events.py +201 -0
  875. chrys/service/context/compaction/exclusions.py +261 -0
  876. chrys/service/context/compaction/groups.py +361 -0
  877. chrys/service/context/compaction/last_words.py +1996 -0
  878. chrys/service/context/compaction/last_words_state.py +752 -0
  879. chrys/service/context/compaction/scoped.py +472 -0
  880. chrys/service/context/compaction/spill.py +1947 -0
  881. chrys/service/context/compaction/strategy.py +1116 -0
  882. chrys/service/context/compaction/summaries.py +319 -0
  883. chrys/service/context/compaction/turns.py +375 -0
  884. chrys/service/context/manager.py +174 -0
  885. chrys/service/context/memory_loader.py +792 -0
  886. chrys/service/context/middleware/__init__.py +3 -0
  887. chrys/service/context/middleware/usage.py +332 -0
  888. chrys/service/context/providers/__init__.py +3 -0
  889. chrys/service/context/providers/context_management.py +432 -0
  890. chrys/service/context/providers/history.py +647 -0
  891. chrys/service/context/providers/memory.py +50 -0
  892. chrys/service/context/tool_content.py +22 -0
  893. chrys/service/hooks/__init__.py +37 -0
  894. chrys/service/hooks/detached_worker.py +118 -0
  895. chrys/service/hooks/docs/README.md +14 -0
  896. chrys/service/hooks/docs/authoring.md +684 -0
  897. chrys/service/hooks/docs/configuration.md +499 -0
  898. chrys/service/hooks/docs/design.md +359 -0
  899. chrys/service/hooks/docs/project-hooks.md +348 -0
  900. chrys/service/hooks/events.py +157 -0
  901. chrys/service/hooks/loader.py +665 -0
  902. chrys/service/hooks/manager.py +807 -0
  903. chrys/service/hooks/matcher.py +111 -0
  904. chrys/service/hooks/outbox.py +294 -0
  905. chrys/service/hooks/runner.py +572 -0
  906. chrys/service/hooks/schema.py +407 -0
  907. chrys/service/llm/__init__.py +7 -0
  908. chrys/service/llm/_structured_outputs.py +192 -0
  909. chrys/service/llm/anthropic_messages/__init__.py +9 -0
  910. chrys/service/llm/anthropic_messages/client.py +254 -0
  911. chrys/service/llm/anthropic_messages/decode.py +331 -0
  912. chrys/service/llm/anthropic_messages/history.py +279 -0
  913. chrys/service/llm/anthropic_messages/request.py +323 -0
  914. chrys/service/llm/anthropic_messages/server_tools.py +319 -0
  915. chrys/service/llm/anthropic_messages/stream.py +297 -0
  916. chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
  917. chrys/service/llm/chat_completions/__init__.py +9 -0
  918. chrys/service/llm/chat_completions/client.py +369 -0
  919. chrys/service/llm/chat_completions/decode.py +338 -0
  920. chrys/service/llm/chat_completions/history.py +657 -0
  921. chrys/service/llm/chat_completions/reasoning.py +244 -0
  922. chrys/service/llm/chat_completions/request.py +192 -0
  923. chrys/service/llm/chat_completions/stream.py +546 -0
  924. chrys/service/llm/chat_completions/validation.py +308 -0
  925. chrys/service/llm/clients.py +613 -0
  926. chrys/service/llm/images.py +65 -0
  927. chrys/service/llm/mock.py +413 -0
  928. chrys/service/llm/observer.py +446 -0
  929. chrys/service/llm/one_shot.py +106 -0
  930. chrys/service/llm/openai_exceptions.py +93 -0
  931. chrys/service/llm/openai_responses/__init__.py +10 -0
  932. chrys/service/llm/openai_responses/client.py +323 -0
  933. chrys/service/llm/openai_responses/decode.py +432 -0
  934. chrys/service/llm/openai_responses/history.py +519 -0
  935. chrys/service/llm/openai_responses/hosted.py +625 -0
  936. chrys/service/llm/openai_responses/replay.py +629 -0
  937. chrys/service/llm/openai_responses/request.py +342 -0
  938. chrys/service/llm/openai_responses/stream.py +847 -0
  939. chrys/service/llm/openai_timestamps.py +57 -0
  940. chrys/service/llm/providers.py +107 -0
  941. chrys/service/llm/proxy_route.py +54 -0
  942. chrys/service/llm/raw_http_log.py +309 -0
  943. chrys/service/llm/route_facts.py +63 -0
  944. chrys/service/llm/route_sessions.py +49 -0
  945. chrys/service/llm/sdk_anthropic.py +18 -0
  946. chrys/service/llm/sdk_headers.py +47 -0
  947. chrys/service/llm/sdk_openai.py +18 -0
  948. chrys/service/llm/sdk_retry.py +59 -0
  949. chrys/service/llm/wire_client.py +246 -0
  950. chrys/service/mcp/__init__.py +3 -0
  951. chrys/service/mcp/_connection.py +235 -0
  952. chrys/service/mcp/_http_transport.py +350 -0
  953. chrys/service/mcp/_progressive.py +459 -0
  954. chrys/service/mcp/_stdio_transport.py +614 -0
  955. chrys/service/mcp/_tool_mixins.py +96 -0
  956. chrys/service/mcp/adapter.py +1031 -0
  957. chrys/service/mcp/cache.py +476 -0
  958. chrys/service/mcp/content.py +32 -0
  959. chrys/service/mcp/errors.py +169 -0
  960. chrys/service/mcp/owned.py +1330 -0
  961. chrys/service/mcp/result_limits.py +168 -0
  962. chrys/service/mcp/thinking_warning.py +95 -0
  963. chrys/service/mcp/validation.py +74 -0
  964. chrys/service/mutations/__init__.py +84 -0
  965. chrys/service/mutations/coordination.py +894 -0
  966. chrys/service/mutations/detector.py +458 -0
  967. chrys/service/mutations/git_calibrator.py +397 -0
  968. chrys/service/mutations/git_state.py +647 -0
  969. chrys/service/mutations/pipeline.py +779 -0
  970. chrys/service/mutations/scanner.py +466 -0
  971. chrys/service/mutations/snapshot_files.py +233 -0
  972. chrys/service/mutations/store.py +625 -0
  973. chrys/service/mutations/tool_names.py +15 -0
  974. chrys/service/mutations/trace.py +246 -0
  975. chrys/service/mutations/tracker.py +1289 -0
  976. chrys/service/mutations/types.py +559 -0
  977. chrys/service/mutations/workspace_changes.py +1583 -0
  978. chrys/service/profiles/__init__.py +2 -0
  979. chrys/service/profiles/agents/__init__.py +3 -0
  980. chrys/service/profiles/agents/builtins/Code.yaml +199 -0
  981. chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
  982. chrys/service/profiles/agents/builtins/General.yaml +114 -0
  983. chrys/service/profiles/agents/builtins/QA.yaml +128 -0
  984. chrys/service/profiles/agents/loader.py +808 -0
  985. chrys/service/profiles/agents/registry.py +727 -0
  986. chrys/service/profiles/agents/schema.py +371 -0
  987. chrys/service/profiles/agents/serializer.py +196 -0
  988. chrys/service/profiles/coercion.py +22 -0
  989. chrys/service/profiles/models/__init__.py +3 -0
  990. chrys/service/profiles/models/env_bridge.py +157 -0
  991. chrys/service/profiles/models/loader.py +282 -0
  992. chrys/service/profiles/models/options.py +438 -0
  993. chrys/service/profiles/models/registry.py +104 -0
  994. chrys/service/profiles/models/resolver.py +286 -0
  995. chrys/service/profiles/models/schema.py +91 -0
  996. chrys/service/profiles/models/serializer.py +76 -0
  997. chrys/service/session/__init__.py +3 -0
  998. chrys/service/session/checkpoint.py +156 -0
  999. chrys/service/session/history.py +1616 -0
  1000. chrys/service/session/message_metadata.py +151 -0
  1001. chrys/service/session/persistence.py +435 -0
  1002. chrys/service/session/runtime_metadata.py +210 -0
  1003. chrys/service/session/sub_agent_logs.py +1069 -0
  1004. chrys/service/session/sub_agent_transcript.py +654 -0
  1005. chrys/service/skills/__init__.py +3 -0
  1006. chrys/service/skills/adapter.py +408 -0
  1007. chrys/service/skills/constants.py +8 -0
  1008. chrys/service/skills/loader.py +575 -0
  1009. chrys/service/skills/model.py +130 -0
  1010. chrys/service/skills/provider.py +841 -0
  1011. chrys/service/skills/runner.py +390 -0
  1012. chrys/service/state/__init__.py +3 -0
  1013. chrys/service/state/_fork.py +710 -0
  1014. chrys/service/state/_session_files.py +175 -0
  1015. chrys/service/state/_session_meta.py +552 -0
  1016. chrys/service/state/locks.py +154 -0
  1017. chrys/service/state/serializers.py +156 -0
  1018. chrys/service/state/session_catalog.py +361 -0
  1019. chrys/service/state/session_listing.py +84 -0
  1020. chrys/service/state/session_migration.py +446 -0
  1021. chrys/service/state/session_mru.py +315 -0
  1022. chrys/service/state/store.py +2132 -0
  1023. chrys/service/state/workflow.py +158 -0
  1024. chrys/service/text_blocks.py +47 -0
  1025. chrys/service/todos/__init__.py +3 -0
  1026. chrys/service/todos/reminder.py +31 -0
  1027. chrys/service/todos/tracker.py +58 -0
  1028. chrys/service/tools/__init__.py +3 -0
  1029. chrys/service/tools/approval_targets.py +92 -0
  1030. chrys/service/tools/builtins/__init__.py +3 -0
  1031. chrys/service/tools/builtins/ask_user.py +326 -0
  1032. chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
  1033. chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
  1034. chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
  1035. chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
  1036. chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
  1037. chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
  1038. chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
  1039. chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
  1040. chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
  1041. chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
  1042. chrys/service/tools/builtins/doc_converter/registry.py +55 -0
  1043. chrys/service/tools/builtins/filesystem.py +1071 -0
  1044. chrys/service/tools/builtins/search.py +1265 -0
  1045. chrys/service/tools/builtins/shell.py +967 -0
  1046. chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
  1047. chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
  1048. chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
  1049. chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
  1050. chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
  1051. chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
  1052. chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
  1053. chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
  1054. chrys/service/tools/builtins/shell_filter.py +24 -0
  1055. chrys/service/tools/builtins/sleep.py +91 -0
  1056. chrys/service/tools/builtins/todo.py +64 -0
  1057. chrys/service/tools/builtins/web/__init__.py +6 -0
  1058. chrys/service/tools/builtins/web/build.py +268 -0
  1059. chrys/service/tools/builtins/web/config.py +338 -0
  1060. chrys/service/tools/builtins/web/credentials.py +37 -0
  1061. chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
  1062. chrys/service/tools/builtins/web/fetch/cache.py +68 -0
  1063. chrys/service/tools/builtins/web/fetch/convert.py +56 -0
  1064. chrys/service/tools/builtins/web/fetch/tool.py +223 -0
  1065. chrys/service/tools/builtins/web/hosted.py +61 -0
  1066. chrys/service/tools/builtins/web/http.py +330 -0
  1067. chrys/service/tools/builtins/web/progress.py +18 -0
  1068. chrys/service/tools/builtins/web/search/__init__.py +3 -0
  1069. chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
  1070. chrys/service/tools/builtins/web/search/html.py +152 -0
  1071. chrys/service/tools/builtins/web/search/json_api.py +227 -0
  1072. chrys/service/tools/builtins/web/search/providers.py +77 -0
  1073. chrys/service/tools/builtins/web/search/tool.py +218 -0
  1074. chrys/service/tools/builtins/web/search/types.py +28 -0
  1075. chrys/service/tools/builtins/web/tls_identity.py +66 -0
  1076. chrys/service/tools/kinds.py +115 -0
  1077. chrys/service/tools/names.py +47 -0
  1078. chrys/service/tools/registry.py +242 -0
  1079. chrys/service/tools/result_metadata.py +103 -0
  1080. chrys/service/tools/session_artifacts.py +196 -0
  1081. chrys/service/tools/spill.py +218 -0
  1082. chrys/service/tools/workspace_paths.py +81 -0
  1083. chrys/service/trajectory/__init__.py +11 -0
  1084. chrys/service/trajectory/approvals.py +201 -0
  1085. chrys/service/trajectory/compaction.py +420 -0
  1086. chrys/service/trajectory/fork.py +75 -0
  1087. chrys/service/trajectory/hooks.py +217 -0
  1088. chrys/service/trajectory/items.py +41 -0
  1089. chrys/service/trajectory/preparation.py +374 -0
  1090. chrys/service/trajectory/retries.py +95 -0
  1091. chrys/service/trajectory/revisions.py +129 -0
  1092. chrys/service/trajectory/segmented.py +163 -0
  1093. chrys/service/trajectory/session.py +762 -0
  1094. chrys/service/trajectory/state.py +83 -0
  1095. chrys/service/trajectory/sub_agents.py +169 -0
  1096. chrys/service/trajectory/tombstone.py +262 -0
  1097. chrys/service/trajectory/tools.py +323 -0
  1098. chrys/service/trajectory/validation.py +130 -0
  1099. chrys/service/trajectory/waits.py +161 -0
  1100. chrys/service/trajectory/workflow.py +148 -0
  1101. chrys/service/vision.py +128 -0
  1102. chrys/service/workflows/__init__.py +3 -0
  1103. chrys/service/workflows/admission.py +151 -0
  1104. chrys/service/workflows/artifacts.py +109 -0
  1105. chrys/service/workflows/asks.py +130 -0
  1106. chrys/service/workflows/builtins/__init__.py +12 -0
  1107. chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
  1108. chrys/service/workflows/builtins/demo-workflow.py +756 -0
  1109. chrys/service/workflows/discovery.py +554 -0
  1110. chrys/service/workflows/environment.py +284 -0
  1111. chrys/service/workflows/graph.py +430 -0
  1112. chrys/service/workflows/history.py +82 -0
  1113. chrys/service/workflows/interpreter.py +107 -0
  1114. chrys/service/workflows/journal.py +334 -0
  1115. chrys/service/workflows/layout.py +56 -0
  1116. chrys/service/workflows/ledger.py +149 -0
  1117. chrys/service/workflows/model_selection.py +18 -0
  1118. chrys/service/workflows/orphans.py +209 -0
  1119. chrys/service/workflows/outcomes.py +46 -0
  1120. chrys/service/workflows/protocol.py +210 -0
  1121. chrys/service/workflows/records.py +142 -0
  1122. chrys/service/workflows/scheduler.py +770 -0
  1123. chrys/service/workflows/sdk/__init__.py +51 -0
  1124. chrys/service/workflows/sdk/_ask.py +167 -0
  1125. chrys/service/workflows/sdk/_builder.py +781 -0
  1126. chrys/service/workflows/sdk/_values.py +92 -0
  1127. chrys/service/workflows/sdk_artifact.py +74 -0
  1128. chrys/service/workflows/store.py +494 -0
  1129. chrys/service/workflows/transcript.py +102 -0
  1130. chrys/service/workflows/values.py +73 -0
  1131. chrys/service/workflows/worker_host.py +1820 -0
  1132. chrys/workflows.py +38 -0
  1133. icode_tui-0.29.1.dist-info/METADATA +104 -0
  1134. icode_tui-0.29.1.dist-info/RECORD +1138 -0
  1135. icode_tui-0.29.1.dist-info/WHEEL +4 -0
  1136. icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
  1137. icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
  1138. icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
@@ -0,0 +1,982 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Suggestion system — commands, files, agent profiles, models, and prompt history."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ from collections.abc import Callable, Sequence
9
+ from dataclasses import dataclass
10
+ from typing import TYPE_CHECKING
11
+
12
+ from rich.text import Text
13
+ from textual.content import Content
14
+
15
+ from chrys.app.tui.i18n import render_str
16
+ from chrys.app.tui.screens.main.buddy_command import BuddyCommandController
17
+ from chrys.app.tui.screens.main.commands import MainSlashCommandRegistry, SlashCommandActionPort
18
+ from chrys.app.tui.screens.main.model_indicator import is_model_selection_locked
19
+ from chrys.app.tui.screens.main.ports import BuddyCommandView, StartWorker, SuggestionPopupView
20
+ from chrys.app.tui.screens.main.state import MainScreenServices, MainScreenState
21
+ from chrys.app.tui.support.gc_freeze import GcFreezeBlockReason
22
+ from chrys.app.tui.widgets.chrome.commands import (
23
+ SlashCommandDef,
24
+ is_slash_command_candidate,
25
+ )
26
+ from chrys.app.tui.widgets.chrome.suggestion_list import SuggestionItem
27
+ from chrys.foundation.i18n import MessageRef, msg
28
+ from chrys.foundation.i18n.formatting import format_message
29
+ from chrys.foundation.platform import safe_getcwd
30
+ from chrys.service.profiles.models.schema import is_model_profile_selectable
31
+
32
+ _FILE_QUERY_DEBOUNCE_SECONDS = 0.04
33
+ _FILE_QUERY_LIMIT = 30
34
+ _PROMPT_HISTORY_LIMIT = 100
35
+ _PROMPT_HISTORY_LINE_BREAK = " ↵ "
36
+
37
+ _SUGGESTIONS_FILES_TITLE = msg("tui.suggestions.files_title", fallback="Files under {root}")
38
+ _SUGGESTIONS_COMMANDS_TITLE = msg("tui.suggestions.commands_title", fallback="Commands")
39
+ _SUGGESTIONS_AGENTS_TITLE = msg("tui.suggestions.agents_title", fallback="Agents")
40
+ _SUGGESTIONS_MODELS_TITLE = msg("tui.suggestions.models_title", fallback="Models")
41
+ _SUGGESTIONS_PROMPT_HISTORY_TITLE = msg("tui.suggestions.prompt_history_title", fallback="Prompt History")
42
+ _SUGGESTIONS_SYSTEM_COMMANDS = msg("tui.suggestions.system_commands", fallback="System Commands")
43
+ _SUGGESTIONS_LOADED_SKILLS = msg("tui.suggestions.loaded_skills", fallback="Loaded Skills")
44
+ _SUGGESTIONS_SHADOWED = msg("tui.suggestions.shadowed", fallback="shadowed by /{name}")
45
+ _SUGGESTIONS_BOUNDED_INDEX = msg(
46
+ "tui.suggestions.bounded_index",
47
+ fallback="More files exist outside this bounded index",
48
+ )
49
+ _SUGGESTIONS_INDEX_COUNTS = msg(
50
+ "tui.suggestions.index_counts",
51
+ fallback="{file_count} files / {suggestion_count} rows indexed",
52
+ )
53
+ _COMMAND_DISABLED_TITLE = msg("tui.commands.title.disabled", fallback="Command Disabled")
54
+ _COMMAND_UNAVAILABLE_RUNNING = msg(
55
+ "tui.commands.unavailable_while_running",
56
+ fallback="/{name} is not available while agent is running",
57
+ )
58
+
59
+ if TYPE_CHECKING:
60
+ from textual.worker import Worker
61
+
62
+ from chrys.app.tui.i18n import LocaleController
63
+ from chrys.app.tui.widgets.chrome.file_index import ProjectPathIndex
64
+ from chrys.app.tui.widgets.chrome.file_scanner import ProjectPathScanResult, ProjectPathSuggestion
65
+ from chrys.foundation.events.types import RuntimeSkillDetails
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class SuggestionCallbacks:
70
+ """Screen-owned effects required by suggestions."""
71
+
72
+ notify_warning: Callable[[MessageRef | str, MessageRef | str, float | None], None]
73
+ start_worker: StartWorker
74
+ submit_user_text: Callable[[str], object]
75
+ start_agent_profile_switch: Callable[[str], object]
76
+ start_model_profile_switch: Callable[[str], object]
77
+
78
+
79
+ class SuggestionHandler:
80
+ """Manages the suggestion list popup lifecycle for /, @, #, and $ triggers.
81
+
82
+ Owns suggestion-related state and the slash command definitions.
83
+ Constructed once by the main-screen owner.
84
+ """
85
+
86
+ def __init__(
87
+ self,
88
+ *,
89
+ state: MainScreenState,
90
+ services: MainScreenServices,
91
+ view: SuggestionPopupView,
92
+ command_actions: SlashCommandActionPort,
93
+ callbacks: SuggestionCallbacks,
94
+ buddy_view: BuddyCommandView,
95
+ locale_controller: LocaleController | None = None,
96
+ ) -> None:
97
+ self._state = state
98
+ self._services = services
99
+ self._view = view
100
+ self._callbacks = callbacks
101
+ self._locale_controller = locale_controller
102
+ self._suggestion_mode: str | None = None
103
+ self._file_cache: list[ProjectPathSuggestion] | None = None
104
+ self._file_cache_root: str | None = None
105
+ self._file_index: ProjectPathIndex | None = None
106
+ self._file_index_root: str | None = None
107
+ self._file_cache_generation = 0
108
+ self._file_query_revision = 0
109
+ self._file_latest_query = ""
110
+ self._file_query_pending: tuple[str, int] | None = None
111
+ self._file_query_worker: Worker[None] | None = None
112
+ self._file_warmup_requested_root: str | None = None
113
+ self._file_warmup_worker: Worker[None] | None = None
114
+ self._prompt_history_revision = 0
115
+ self._prompt_history_draft = ""
116
+ self._slash_commands: list[SlashCommandDef] = []
117
+ self._active_command: SlashCommandDef | None = None
118
+ self._buddy_command = BuddyCommandController(
119
+ buddy_view,
120
+ render_message=self._render_message,
121
+ )
122
+ self._command_registry = MainSlashCommandRegistry(
123
+ actions=command_actions,
124
+ buddy=self._buddy_command,
125
+ render_message=self._render_message,
126
+ )
127
+
128
+ # -------------------------------------------------------------- #
129
+ # Slash command definitions
130
+ # -------------------------------------------------------------- #
131
+
132
+ def build_slash_commands(self) -> list[SlashCommandDef]:
133
+ """Build slash command list with bound actions."""
134
+ self._slash_commands = self._command_registry.build()
135
+ return self._slash_commands
136
+
137
+ @property
138
+ def buddy_command(self) -> BuddyCommandController:
139
+ """Return the stateful /buddy command controller."""
140
+ return self._buddy_command
141
+
142
+ def gc_freeze_block_reason(self) -> GcFreezeBlockReason | None:
143
+ """Block while any suggestion popup is visible."""
144
+ return GcFreezeBlockReason.SUGGESTIONS_VISIBLE if self._view.suggestions_active else None
145
+
146
+ def prepare_for_gc_freeze(self) -> None:
147
+ """Keep the acyclic warm file cache installed across freezes."""
148
+
149
+ def after_gc_freeze(self) -> None:
150
+ """Suggestion state has no mutable cyclic container to renew."""
151
+
152
+ def abort_gc_freeze(self) -> None:
153
+ """Suggestion state has no detached container to restore."""
154
+
155
+ def dispatch_slash_command(self, text: str) -> bool:
156
+ """Try to match and execute a slash command. Returns True if handled."""
157
+ parts = text[1:].split(None, 1)
158
+ if not parts:
159
+ return False
160
+ name = parts[0]
161
+ arg = parts[1] if len(parts) > 1 else ""
162
+ for cmd in self._slash_commands:
163
+ if cmd.name == name or name in cmd.aliases:
164
+ if cmd.name in self._disabled_commands():
165
+ self._callbacks.notify_warning(
166
+ _COMMAND_UNAVAILABLE_RUNNING.bind(name=cmd.name),
167
+ _COMMAND_DISABLED_TITLE.bind(),
168
+ 3,
169
+ )
170
+ return True
171
+ cmd.action(arg)
172
+ return True
173
+ return False
174
+
175
+ # -------------------------------------------------------------- #
176
+ # Suggestion display and filtering
177
+ # -------------------------------------------------------------- #
178
+
179
+ def _disabled_commands(self) -> set[str]:
180
+ """Return command names that should be disabled in the current state."""
181
+ if not self._state.run.agent_running:
182
+ return set()
183
+ return {cmd.name for cmd in self._slash_commands if not cmd.allow_while_running}
184
+
185
+ def _visible_slash_commands(self) -> list[SlashCommandDef]:
186
+ """Return slash commands that should appear in the suggestion list."""
187
+ for cmd in self._slash_commands:
188
+ if cmd.name == "resume":
189
+ cmd.hidden = not self._services.state_store
190
+ return [cmd for cmd in self._slash_commands if not cmd.hidden]
191
+
192
+ def _show_suggestions(
193
+ self,
194
+ mode: str,
195
+ items: Sequence[SuggestionItem | tuple[str, str | Content]],
196
+ disabled: set[str] | None = None,
197
+ ) -> None:
198
+ """Show the suggestion list, optionally hiding the status bar."""
199
+ if mode != "files":
200
+ self._invalidate_file_query_results()
201
+ if mode != "history":
202
+ self._invalidate_prompt_history_results()
203
+
204
+ self._suggestion_mode = mode
205
+ self._view.show_suggestions(mode, list(items), disabled=disabled, title=self._suggestion_title(mode))
206
+
207
+ def _show_suggestions_loading(self, mode: str) -> None:
208
+ """Open *mode* immediately with no selectable rows until results are ready."""
209
+ if mode != "files":
210
+ self._invalidate_file_query_results()
211
+ if mode != "history":
212
+ self._invalidate_prompt_history_results()
213
+
214
+ self._suggestion_mode = mode
215
+ self._view.show_suggestions_loading(mode, title=self._suggestion_title(mode))
216
+
217
+ def _suggestion_title(self, mode: str) -> str:
218
+ """Border title for the suggestion popup; files mode names its scan root."""
219
+ if mode == "files":
220
+ return self._render_message(_SUGGESTIONS_FILES_TITLE.bind(root=self._current_file_root()))
221
+ definition = {
222
+ "commands": _SUGGESTIONS_COMMANDS_TITLE,
223
+ "agents": _SUGGESTIONS_AGENTS_TITLE,
224
+ "models": _SUGGESTIONS_MODELS_TITLE,
225
+ "history": _SUGGESTIONS_PROMPT_HISTORY_TITLE,
226
+ }.get(mode)
227
+ return "" if definition is None else self._render_message(definition.bind())
228
+
229
+ def _render_message(self, reference: MessageRef) -> str:
230
+ controller = self._locale_controller
231
+ if controller is None:
232
+ return format_message(reference)
233
+ return render_str(controller.localizer, reference)
234
+
235
+ def dismiss_suggestions(self) -> None:
236
+ """Hide the suggestion list and restore status bar."""
237
+ self._invalidate_file_query_results()
238
+ self._invalidate_prompt_history_results()
239
+ self._view.hide_suggestions()
240
+ self._suggestion_mode = None
241
+
242
+ def _invalidate_prompt_history_results(self) -> None:
243
+ """Invalidate an in-flight prompt-history load."""
244
+ self._prompt_history_revision += 1
245
+
246
+ @staticmethod
247
+ def _prompt_history_label(text: str) -> str:
248
+ """Render a multi-line prompt as one visible suggestion row."""
249
+ return _PROMPT_HISTORY_LINE_BREAK.join(text.splitlines())
250
+
251
+ def start_prompt_history(self) -> int:
252
+ """Open prompt-history loading synchronously and return its request revision."""
253
+ self._invalidate_prompt_history_results()
254
+ revision = self._prompt_history_revision
255
+ self._prompt_history_draft = self._view.input_value()
256
+ self._show_suggestions_loading("history")
257
+ return revision
258
+
259
+ async def show_prompt_history_async(self, *, revision: int | None = None) -> None:
260
+ """Load and show the newest global prompt-history entries first."""
261
+ if revision is None:
262
+ revision = self.start_prompt_history()
263
+ elif revision != self._prompt_history_revision or self._suggestion_mode != "history":
264
+ return
265
+ history = await self._view.load_prompt_history(max_entries=_PROMPT_HISTORY_LIMIT)
266
+ if (
267
+ revision != self._prompt_history_revision
268
+ or not self._view.is_attached
269
+ or self._suggestion_mode != "history"
270
+ ):
271
+ return
272
+ items = [
273
+ SuggestionItem(
274
+ value=prompt,
275
+ label=self._prompt_history_label(prompt),
276
+ kind="history",
277
+ marquee_start=0,
278
+ )
279
+ for prompt in reversed(history)
280
+ ]
281
+ self._view.update_suggestions(items, title=self._suggestion_title("history"))
282
+
283
+ # -------------------------------------------------------------- #
284
+ # Trigger handlers (called by thin @on forwarders in MainScreen)
285
+ # -------------------------------------------------------------- #
286
+
287
+ def on_slash_triggered(self) -> None:
288
+ """Show slash command suggestions."""
289
+ disabled = self._disabled_commands()
290
+ items = self._command_suggestion_items(self._visible_slash_commands(), disabled)
291
+ items.extend(self._runtime_skill_suggestion_items())
292
+ self._show_suggestions("commands", items, disabled=disabled)
293
+
294
+ def _command_suggestion_items(
295
+ self,
296
+ commands: list[SlashCommandDef],
297
+ disabled: set[str],
298
+ ) -> list[SuggestionItem]:
299
+ """Build grouped slash-command suggestion items."""
300
+ items: list[SuggestionItem] = []
301
+ for cmd in commands:
302
+ description = self._render_message(cmd.description)
303
+ items.append(
304
+ SuggestionItem(
305
+ value=cmd.name,
306
+ # The second separator space carries the dim style: when the
307
+ # row is highlighted (reverse video), the bright-to-gray
308
+ # boundary then splits evenly between name and description.
309
+ label=Content.assemble(f"/{cmd.name} ", (" " + description, "dim")),
310
+ section=self._render_message(_SUGGESTIONS_SYSTEM_COMMANDS.bind()),
311
+ kind="command",
312
+ disabled=cmd.name in disabled,
313
+ marquee_start=len(f"/{cmd.name} ") if description else None,
314
+ )
315
+ )
316
+ return items
317
+
318
+ def _runtime_skill_suggestion_items(
319
+ self,
320
+ skills: list[RuntimeSkillDetails] | None = None,
321
+ ) -> list[SuggestionItem]:
322
+ """Build grouped runtime-skill suggestion items."""
323
+ items: list[SuggestionItem] = []
324
+ source_skills = skills if skills is not None else self._state.runtime.details.skill_details
325
+ for skill in source_skills:
326
+ shadowing_command = self._command_for_token(skill.name)
327
+ items.append(
328
+ SuggestionItem(
329
+ value=skill.name,
330
+ label=self._runtime_skill_label(skill.name, skill.description),
331
+ section=self._render_message(_SUGGESTIONS_LOADED_SKILLS.bind()),
332
+ kind="skill",
333
+ disabled=shadowing_command is not None,
334
+ disabled_reason=(
335
+ self._render_message(_SUGGESTIONS_SHADOWED.bind(name=shadowing_command.name))
336
+ if shadowing_command is not None
337
+ else ""
338
+ ),
339
+ marquee_start=len(f"/{skill.name} ") if skill.description.strip() else None,
340
+ )
341
+ )
342
+ return items
343
+
344
+ @staticmethod
345
+ def _runtime_skill_label(name: str, description: str) -> Content:
346
+ """Build a literal-text label from user-authored skill metadata."""
347
+ description = description.strip()
348
+ label = Text.assemble(f"/{name}")
349
+ if description:
350
+ # Second separator space styled dim — see _command_suggestion_items.
351
+ label.append(" ")
352
+ label.append(Text(" " + description, style="dim"))
353
+ return Content.from_rich_text(label)
354
+
355
+ def _command_for_token(self, token: str) -> SlashCommandDef | None:
356
+ """Return the slash command that would handle *token*, including aliases."""
357
+ for cmd in self._slash_commands:
358
+ if cmd.name == token or token in cmd.aliases:
359
+ return cmd
360
+ return None
361
+
362
+ def on_file_triggered(self) -> None:
363
+ """Show file search suggestions."""
364
+ self._file_latest_query = ""
365
+ self._invalidate_file_query_results()
366
+ self._show_suggestions_loading("files")
367
+ self._callbacks.start_worker(self.show_file_suggestions_async)
368
+
369
+ def on_agent_triggered(self) -> None:
370
+ """Show agent profile suggestions."""
371
+ if self._state.run.agent_running or self._state.run.agent_loading or self._services.execution_busy():
372
+ return
373
+ items, disabled = self._get_agent_items()
374
+ self._show_suggestions("agents", items, disabled=disabled)
375
+
376
+ def _get_agent_items(self) -> tuple[list[SuggestionItem], set[str]]:
377
+ """Build suggestion items and disabled set from available agent profiles."""
378
+ registry = self._services.agent_registry
379
+ if registry is None:
380
+ return [], set()
381
+ profiles = registry.list_profiles()
382
+ items: list[SuggestionItem] = []
383
+ disabled: set[str] = set()
384
+ for p in profiles:
385
+ label = p.display_name or p.name
386
+ is_current = label == self._state.runtime.profile
387
+ prefix = "◦ " if is_current else " "
388
+ items.append(
389
+ SuggestionItem(
390
+ value=p.name,
391
+ label=Content.assemble(f"{prefix}{label} ", (" " + p.description, "dim")),
392
+ marquee_start=len(f"{prefix}{label} ") if p.description else None,
393
+ )
394
+ )
395
+ if is_current:
396
+ disabled.add(p.name)
397
+ return items, disabled
398
+
399
+ def _agent_query_matches(self, text: str) -> tuple[list[str], dict[str, SuggestionItem], set[str]]:
400
+ """Return agent names matching the current # query plus labels and disabled names."""
401
+ query = text.lstrip("#\uff03")
402
+ all_items, disabled = self._get_agent_items()
403
+ item_by_name = {item.value: item for item in all_items}
404
+ if not query:
405
+ return [item.value for item in all_items], item_by_name, disabled
406
+
407
+ from chrys.app.tui.widgets.chrome.file_scanner import fuzzy_filter
408
+
409
+ all_names = [item.value for item in all_items]
410
+ matches = [value for value in fuzzy_filter(query, all_names) if value in item_by_name]
411
+ return matches, item_by_name, disabled
412
+
413
+ def _agent_fallback_should_submit(self, text: str) -> bool:
414
+ """Return whether an unselected # entry should fall through to chat submit."""
415
+ matches, _label_by_name, disabled = self._agent_query_matches(text)
416
+ if not matches:
417
+ return True
418
+ return any(match not in disabled for match in matches)
419
+
420
+ def on_model_triggered(self) -> None:
421
+ """Show selectable model-profile suggestions when model selection is unlocked."""
422
+ if (
423
+ self._state.run.agent_running
424
+ or self._state.run.agent_loading
425
+ or self._services.execution_busy()
426
+ or self._model_selection_is_locked()
427
+ ):
428
+ return
429
+ items, disabled = self._get_model_items()
430
+ self._show_suggestions("models", items, disabled=disabled)
431
+
432
+ def _model_selection_is_locked(self) -> bool:
433
+ """Return whether runtime policy prevents changing the active model."""
434
+ return is_model_selection_locked(
435
+ self._state.runtime.details.model,
436
+ runtime_confirmed=self._state.runtime.details_confirmed,
437
+ )
438
+
439
+ def _current_model_profile_id(self) -> str:
440
+ """Return the best-known current model-profile id for suggestion styling."""
441
+ if self._state.runtime.details_confirmed:
442
+ return self._state.runtime.details.model.profile_id
443
+ return self._services.active_model_profile_id
444
+
445
+ def _get_model_items(self) -> tuple[list[SuggestionItem], set[str]]:
446
+ """Build suggestion items and disabled set from selectable model profiles."""
447
+ registry = self._services.model_registry
448
+ if registry is None:
449
+ return [], set()
450
+ current_profile_id = self._current_model_profile_id()
451
+ items: list[SuggestionItem] = []
452
+ disabled: set[str] = set()
453
+ for profile in registry.list_profiles():
454
+ if not is_model_profile_selectable(profile):
455
+ continue
456
+ is_current = profile.id == current_profile_id
457
+ prefix = "◦ " if is_current else " "
458
+ items.append(
459
+ SuggestionItem(
460
+ value=profile.id,
461
+ label=Content.assemble(f"{prefix}{profile.name} ", (" " + profile.model_id, "dim")),
462
+ kind="model",
463
+ marquee_start=len(f"{prefix}{profile.name} ") if profile.model_id else None,
464
+ )
465
+ )
466
+ if is_current:
467
+ disabled.add(profile.id)
468
+ return items, disabled
469
+
470
+ def _model_query_matches(self, text: str) -> tuple[list[str], dict[str, SuggestionItem], set[str]]:
471
+ """Return model profile ids matching the current $ query."""
472
+ query = text.lstrip("$\uff04")
473
+ all_items, disabled = self._get_model_items()
474
+ item_by_id = {item.value: item for item in all_items}
475
+ if not query:
476
+ return [item.value for item in all_items], item_by_id, disabled
477
+
478
+ registry = self._services.model_registry
479
+ if registry is None:
480
+ return [], item_by_id, disabled
481
+ selectable_profiles = [profile for profile in registry.list_profiles() if profile.id in item_by_id]
482
+
483
+ from chrys.app.tui.widgets.chrome.file_scanner import fuzzy_filter
484
+
485
+ names = [profile.name for profile in selectable_profiles]
486
+ matching_names = fuzzy_filter(query, names)
487
+ ids_by_name: dict[str, list[str]] = {}
488
+ for profile in selectable_profiles:
489
+ ids_by_name.setdefault(profile.name, []).append(profile.id)
490
+ matches = [ids_by_name[name].pop(0) for name in matching_names]
491
+ return matches, item_by_id, disabled
492
+
493
+ def _model_fallback_should_submit(self, text: str) -> bool:
494
+ """Return whether an unselected $ entry should fall through to chat submit."""
495
+ matches, _item_by_id, disabled = self._model_query_matches(text)
496
+ if not matches:
497
+ return True
498
+ return any(match not in disabled for match in matches)
499
+
500
+ async def show_file_suggestions_async(self) -> None:
501
+ """Show file suggestions; ``on_file_triggered`` runs this in a screen worker."""
502
+ if self._suggestion_mode != "files" or not self._view.is_attached:
503
+ return
504
+ root = self._current_file_root()
505
+ if self._has_current_file_index(root):
506
+ self._show_latest_file_query_or_head()
507
+ return
508
+
509
+ self._file_warmup_requested_root = root
510
+ worker = self._file_warmup_worker
511
+ if worker is None or worker.is_finished:
512
+ worker = self._view.run_file_suggestion_worker(
513
+ self._run_file_warmup_loop(),
514
+ name="file-suggestion-warmup",
515
+ group="file-suggestions",
516
+ )
517
+ self._file_warmup_worker = worker
518
+ await worker.wait()
519
+
520
+ def _current_file_root(self) -> str:
521
+ """Return the cwd that file suggestions should be scoped to."""
522
+ return self._state.workspace_marker.current_cwd or self._state.workspace.current_cwd or safe_getcwd()
523
+
524
+ def _has_current_file_index(self, root: str) -> bool:
525
+ """Return whether the current cache/index pair is ready for *root*."""
526
+ return (
527
+ self._file_cache is not None
528
+ and self._file_cache_root == root
529
+ and self._file_index is not None
530
+ and self._file_index_root == root
531
+ )
532
+
533
+ async def _run_file_warmup_loop(self) -> None:
534
+ """Coalesced scan/index warmup for file suggestions."""
535
+ from chrys.app.tui.widgets.chrome.file_scanner import scan_project_paths
536
+
537
+ while self._file_warmup_requested_root is not None:
538
+ root = self._file_warmup_requested_root
539
+ self._file_warmup_requested_root = None
540
+ if self._has_current_file_index(root):
541
+ self._show_latest_file_query_or_head()
542
+ continue
543
+
544
+ cache_generation = self._file_cache_generation
545
+ if not self._file_warmup_context_current(root, cache_generation):
546
+ self._request_latest_file_warmup_if_active()
547
+ continue
548
+
549
+ scan = await scan_project_paths(root)
550
+ if not self._file_warmup_context_current(root, cache_generation):
551
+ self._request_latest_file_warmup_if_active()
552
+ continue
553
+
554
+ index = await asyncio.to_thread(self._build_file_index, scan)
555
+ if not self._file_warmup_context_current(root, cache_generation):
556
+ self._request_latest_file_warmup_if_active()
557
+ continue
558
+
559
+ self._install_file_cache(root, scan, index)
560
+ self._show_latest_file_query_or_head()
561
+
562
+ def _request_latest_file_warmup_if_active(self) -> None:
563
+ """Request one follow-up warmup for the current root if file suggestions are still active."""
564
+ if self._suggestion_mode == "files" and self._view.is_attached:
565
+ self._file_warmup_requested_root = self._current_file_root()
566
+
567
+ def _file_warmup_context_current(self, root: str, cache_generation: int) -> bool:
568
+ """Return whether a warmed scan/index is still allowed to install."""
569
+ return (
570
+ self._view.is_attached
571
+ and self._suggestion_mode == "files"
572
+ and self._current_file_root() == root
573
+ and cache_generation == self._file_cache_generation
574
+ )
575
+
576
+ def _install_file_cache(
577
+ self,
578
+ root: str,
579
+ scan: ProjectPathScanResult,
580
+ index: ProjectPathIndex,
581
+ ) -> None:
582
+ """Install a scanned file cache and already-built query index for *root*."""
583
+ self._file_cache = scan.paths
584
+ self._file_cache_root = root
585
+ self._file_index = index
586
+ self._file_index_root = root
587
+
588
+ def _current_file_index(self) -> ProjectPathIndex | None:
589
+ """Return the current file index without building one synchronously."""
590
+ if self._file_cache is None:
591
+ return None
592
+ root = self._file_cache_root or self._current_file_root()
593
+ if self._file_index is None or self._file_index_root != root:
594
+ return None
595
+ return self._file_index
596
+
597
+ @staticmethod
598
+ def _build_file_index(scan: ProjectPathScanResult) -> ProjectPathIndex:
599
+ """Build a project path index from a scanner result."""
600
+ from chrys.app.tui.widgets.chrome.file_index import ProjectPathIndex
601
+
602
+ return ProjectPathIndex.build(scan)
603
+
604
+ def _file_suggestion_items(
605
+ self,
606
+ paths: list[ProjectPathSuggestion],
607
+ *,
608
+ index: ProjectPathIndex | None = None,
609
+ ) -> list[SuggestionItem]:
610
+ """Build suggestion rows for project file and directory paths."""
611
+ items = [SuggestionItem(value=path.path, label=path.path, kind=path.kind) for path in paths]
612
+ if index is not None and index.truncated:
613
+ items.append(
614
+ SuggestionItem(
615
+ value="__chrys_file_results_truncated__",
616
+ label=self._render_message(_SUGGESTIONS_BOUNDED_INDEX.bind()),
617
+ kind="status",
618
+ disabled=True,
619
+ disabled_reason=self._render_message(
620
+ _SUGGESTIONS_INDEX_COUNTS.bind(
621
+ file_count=f"{index.file_count:,}",
622
+ suggestion_count=f"{index.suggestion_count:,}",
623
+ )
624
+ ),
625
+ )
626
+ )
627
+ return items
628
+
629
+ def _show_latest_file_query_or_head(self) -> None:
630
+ """Display the current file query, preserving immediate empty-query head behavior."""
631
+ if self._suggestion_mode != "files" or not self._view.is_attached:
632
+ return
633
+ index = self._current_file_index()
634
+ if index is None:
635
+ return
636
+ query = self._file_latest_query
637
+ if query:
638
+ self._enqueue_file_query(query)
639
+ else:
640
+ self._invalidate_file_query_results()
641
+ self._show_or_update_file_suggestions(index.head(limit=_FILE_QUERY_LIMIT))
642
+
643
+ def _show_or_update_file_suggestions(self, paths: list[ProjectPathSuggestion]) -> None:
644
+ """Show or update the file suggestion list with *paths*."""
645
+ if self._suggestion_mode != "files" or not self._view.is_attached:
646
+ return
647
+
648
+ items = self._file_suggestion_items(paths, index=self._current_file_index())
649
+ if self._view.suggestions_active:
650
+ self._view.update_suggestions(items, title=self._suggestion_title("files"))
651
+ else:
652
+ self._show_suggestions("files", items)
653
+
654
+ def _invalidate_file_query_results(self) -> None:
655
+ """Invalidate pending/returned file query results."""
656
+ self._file_query_revision += 1
657
+ self._file_query_pending = None
658
+
659
+ def _enqueue_file_query(self, query: str) -> None:
660
+ """Record and schedule the latest file query."""
661
+ self._file_latest_query = query
662
+ self._file_query_revision += 1
663
+ revision = self._file_query_revision
664
+ if self._file_cache is None and self._file_index is None:
665
+ return
666
+ self._file_query_pending = (query, revision)
667
+ if self._file_query_worker is None or self._file_query_worker.is_finished:
668
+ self._file_query_worker = self._view.run_file_suggestion_worker(
669
+ self._run_file_query_loop(),
670
+ name="file-suggestion-query",
671
+ group="file-suggestions",
672
+ )
673
+
674
+ async def _run_file_query_loop(self) -> None:
675
+ """Debounced, coalesced file-query worker loop."""
676
+ while self._file_query_pending is not None:
677
+ query, revision = self._file_query_pending
678
+ self._file_query_pending = None
679
+ await asyncio.sleep(_FILE_QUERY_DEBOUNCE_SECONDS)
680
+ if self._file_query_pending is not None:
681
+ continue
682
+
683
+ index = await self._ensure_file_index_for_query(revision)
684
+ root = self._file_index_root
685
+ if index is None or root is None or not self._file_query_context_current(revision, root, index):
686
+ continue
687
+
688
+ matches = await asyncio.to_thread(index.query, query, limit=_FILE_QUERY_LIMIT)
689
+ if self._file_query_context_current(revision, root, index):
690
+ self._apply_file_query_result(matches)
691
+
692
+ async def _ensure_file_index_for_query(self, revision: int) -> ProjectPathIndex | None:
693
+ """Build the current cache index off-loop when a test-injected cache needs one."""
694
+ index = self._current_file_index()
695
+ if index is not None:
696
+ return index
697
+ if self._file_cache is None:
698
+ return None
699
+
700
+ root = self._file_cache_root or self._current_file_root()
701
+ paths = self._file_cache
702
+ cache_generation = self._file_cache_generation
703
+ from chrys.app.tui.widgets.chrome.file_scanner import ProjectPathScanResult
704
+
705
+ scan = ProjectPathScanResult.from_suggestions(root=root, paths=paths)
706
+ index = await asyncio.to_thread(self._build_file_index, scan)
707
+ if (
708
+ revision != self._file_query_revision
709
+ or cache_generation != self._file_cache_generation
710
+ or paths is not self._file_cache
711
+ or self._current_file_root() != root
712
+ ):
713
+ return None
714
+ self._file_index = index
715
+ self._file_index_root = root
716
+ return index
717
+
718
+ def _file_query_context_current(self, revision: int, root: str, index: ProjectPathIndex) -> bool:
719
+ """Return whether a query result is still allowed to update the UI."""
720
+ return (
721
+ revision == self._file_query_revision
722
+ and self._view.is_attached
723
+ and self._suggestion_mode == "files"
724
+ and self._file_index is index
725
+ and self._file_index_root == root
726
+ and self._current_file_root() == root
727
+ )
728
+
729
+ def _apply_file_query_result(self, matches: list[ProjectPathSuggestion]) -> None:
730
+ """Apply current file-query results to the suggestion list."""
731
+ if not self._view.is_attached:
732
+ return
733
+ self._show_or_update_file_suggestions(matches)
734
+
735
+ # -------------------------------------------------------------- #
736
+ # Filtering as user types
737
+ # -------------------------------------------------------------- #
738
+
739
+ def on_text_changed(self, text: str) -> None:
740
+ """Re-filter suggestion list as user types."""
741
+ if self._suggestion_mode is None:
742
+ return
743
+
744
+ if self._suggestion_mode == "commands":
745
+ if text not in ("/", "\uff0f") and not is_slash_command_candidate(text):
746
+ self.dismiss_suggestions()
747
+ return
748
+ query = text.lstrip("/\uff0f")
749
+ if " " in query:
750
+ # Support second-level suggestions when user types "/cmd ..." directly.
751
+ parts = query.split(None, 1)
752
+ cmd_name = parts[0] if parts else ""
753
+ sub_query = parts[1] if len(parts) > 1 else ""
754
+ cmd = next(
755
+ (c for c in self._slash_commands if c.name == cmd_name or cmd_name in c.aliases),
756
+ None,
757
+ )
758
+ if not cmd or not cmd.subcommands:
759
+ self.dismiss_suggestions()
760
+ return
761
+ self._suggestion_mode = "subcommands"
762
+ self._active_command = cmd
763
+ from chrys.app.tui.widgets.chrome.file_scanner import fuzzy_filter
764
+
765
+ all_subs = cmd.subcommands()
766
+ if sub_query:
767
+ matches = fuzzy_filter(sub_query, [v for v, _ in all_subs])
768
+ sub_by_value = dict(all_subs)
769
+ items = [(m, sub_by_value[m]) for m in matches if m in sub_by_value]
770
+ self._view.show_subcommand_suggestions(items)
771
+ else:
772
+ initial = cmd.initial() if cmd.initial else None
773
+ self._view.show_subcommand_suggestions(all_subs, initial=initial)
774
+ return
775
+ from chrys.app.tui.widgets.chrome.file_scanner import fuzzy_filter
776
+
777
+ visible = self._visible_slash_commands()
778
+ all_names: list[str] = []
779
+ name_to_cmd: dict[str, SlashCommandDef] = {}
780
+ for cmd in visible:
781
+ for n in [cmd.name, *cmd.aliases]:
782
+ all_names.append(n)
783
+ name_to_cmd[n] = cmd
784
+ matches = fuzzy_filter(query, all_names, max_results=20)
785
+ # Preserve fuzzy_filter order (prefix matches before contains matches)
786
+ seen: set[str] = set()
787
+ ordered_cmds: list[SlashCommandDef] = []
788
+ for m in matches:
789
+ cmd = name_to_cmd.get(m)
790
+ if cmd and cmd.name not in seen:
791
+ seen.add(cmd.name)
792
+ ordered_cmds.append(cmd)
793
+ disabled = self._disabled_commands()
794
+ items = self._command_suggestion_items(ordered_cmds, disabled)
795
+ skills = self._state.runtime.details.skill_details
796
+ if query:
797
+ skill_names = [skill.name for skill in skills]
798
+ skill_matches = fuzzy_filter(query, skill_names, max_results=20)
799
+ skill_by_name = {skill.name: skill for skill in skills}
800
+ ordered_skills = [skill_by_name[name] for name in skill_matches if name in skill_by_name]
801
+ else:
802
+ ordered_skills = skills
803
+ items.extend(self._runtime_skill_suggestion_items(list(ordered_skills)))
804
+ self._view.update_suggestions(
805
+ items,
806
+ disabled=self._disabled_commands(),
807
+ title=self._suggestion_title("commands"),
808
+ )
809
+
810
+ elif self._suggestion_mode == "subcommands":
811
+ cmd = self._active_command
812
+ if not cmd or not cmd.subcommands or not is_slash_command_candidate(text):
813
+ self.dismiss_suggestions()
814
+ return
815
+ parts = text.split(None, 1)
816
+ query = parts[1] if len(parts) > 1 else ""
817
+ from chrys.app.tui.widgets.chrome.file_scanner import fuzzy_filter
818
+
819
+ all_subs = cmd.subcommands()
820
+ if query:
821
+ matches = fuzzy_filter(query, [v for v, _ in all_subs])
822
+ sub_by_value = dict(all_subs)
823
+ items = [(m, sub_by_value[m]) for m in matches if m in sub_by_value]
824
+ self._view.update_suggestions(items, title=self._suggestion_title("subcommands"))
825
+ else:
826
+ initial = cmd.initial() if cmd.initial else None
827
+ self._view.show_subcommand_suggestions(all_subs, initial=initial)
828
+
829
+ elif self._suggestion_mode == "files":
830
+ idx = max(text.rfind("@"), text.rfind("\uff20"))
831
+ if idx < 0:
832
+ self.dismiss_suggestions()
833
+ return
834
+ query = text[idx + 1 :]
835
+ if query:
836
+ self._enqueue_file_query(query)
837
+ else:
838
+ self._file_latest_query = ""
839
+ self._invalidate_file_query_results()
840
+ index = self._current_file_index()
841
+ if index is not None:
842
+ self._view.update_suggestions(
843
+ self._file_suggestion_items(index.head(limit=_FILE_QUERY_LIMIT), index=index),
844
+ title=self._suggestion_title("files"),
845
+ )
846
+
847
+ elif self._suggestion_mode == "agents":
848
+ if "#" not in text and "\uff03" not in text:
849
+ self.dismiss_suggestions()
850
+ return
851
+ matches, item_by_name, disabled = self._agent_query_matches(text)
852
+ items = [item_by_name[name] for name in matches]
853
+ self._view.update_suggestions(items, disabled=disabled, title=self._suggestion_title("agents"))
854
+
855
+ elif self._suggestion_mode == "models":
856
+ if "$" not in text and "\uff04" not in text:
857
+ self.dismiss_suggestions()
858
+ return
859
+ matches, item_by_id, disabled = self._model_query_matches(text)
860
+ items = [item_by_id[profile_id] for profile_id in matches]
861
+ self._view.update_suggestions(items, disabled=disabled, title=self._suggestion_title("models"))
862
+
863
+ elif self._suggestion_mode == "history" and text != self._prompt_history_draft:
864
+ self.dismiss_suggestions()
865
+
866
+ # -------------------------------------------------------------- #
867
+ # Navigation and selection
868
+ # -------------------------------------------------------------- #
869
+
870
+ def on_suggestion_navigate(self, direction: str) -> None:
871
+ self._view.move_suggestion_cursor(direction)
872
+
873
+ def on_suggestion_select(self, execute: bool) -> bool:
874
+ """Handle Tab/Enter on suggestion list. Returns True if handled."""
875
+ selected = self._view.select_highlighted_suggestion(execute=execute)
876
+ if not selected and execute:
877
+ mode = self._suggestion_mode
878
+ if mode not in {"commands", "subcommands", "agents", "models"}:
879
+ return True
880
+
881
+ text = self._view.input_value()
882
+ if mode == "agents" and not self._agent_fallback_should_submit(text):
883
+ return True
884
+ if mode == "models" and not self._model_fallback_should_submit(text):
885
+ return True
886
+ self.dismiss_suggestions()
887
+ self._view.set_input_value("")
888
+ if text and (mode in {"agents", "models"} or not self.dispatch_slash_command(text)):
889
+ self._callbacks.submit_user_text(text)
890
+ return True
891
+ return bool(selected)
892
+
893
+ def on_suggestion_selected(self, mode: str, text: str, execute: bool, kind: str = "") -> None:
894
+ """Handle a confirmed suggestion selection."""
895
+ if mode == "commands":
896
+ if kind == "skill":
897
+ self.dismiss_suggestions()
898
+ if execute:
899
+ self._view.set_input_value("")
900
+ self._callbacks.submit_user_text(f"/{text}")
901
+ else:
902
+ self._view.replace_input_trigger_text("/", f"/{text} ")
903
+ return
904
+ cmd = next((c for c in self._slash_commands if c.name == text), None)
905
+ if cmd is None:
906
+ self.dismiss_suggestions()
907
+ return
908
+ if cmd.name in self._disabled_commands():
909
+ return
910
+ if cmd.subcommands:
911
+ self._view.replace_input_trigger_text("/", f"/{text} ")
912
+ self._suggestion_mode = "subcommands"
913
+ self._active_command = cmd
914
+ sub_items = cmd.subcommands()
915
+ initial = cmd.initial() if cmd.initial else None
916
+ self._view.show_subcommand_suggestions(sub_items, initial=initial)
917
+ return
918
+ if execute:
919
+ self.dismiss_suggestions()
920
+ self._view.set_input_value("")
921
+ cmd.action("")
922
+ else:
923
+ self._view.replace_input_trigger_text("/", f"/{text} ")
924
+ self.dismiss_suggestions()
925
+
926
+ elif mode == "subcommands":
927
+ cmd = self._active_command
928
+ if not cmd or cmd.name in self._disabled_commands():
929
+ self.dismiss_suggestions()
930
+ return
931
+ # For subcommands that require parameters (e.g., "name"), keep input open
932
+ if text == "name":
933
+ self._view.replace_input_trigger_text(f"/{cmd.name} ", f"/{cmd.name} name ")
934
+ self._suggestion_mode = None
935
+ self._active_command = None
936
+ self.dismiss_suggestions()
937
+ else:
938
+ # Execute immediately for parameterless subcommands
939
+ self.dismiss_suggestions()
940
+ self._view.set_input_value("")
941
+ cmd.action(text)
942
+
943
+ elif mode == "files":
944
+ if kind == "status":
945
+ return
946
+ selected_path = text
947
+ if kind == "directory" and not selected_path.endswith(("/", "\\")):
948
+ selected_path = f"{selected_path}/"
949
+ path = f'"{selected_path}"' if " " in selected_path else selected_path
950
+ self._view.replace_input_trigger_text("@", f"@{path} ")
951
+ self.dismiss_suggestions()
952
+
953
+ elif mode == "agents":
954
+ self.dismiss_suggestions()
955
+ self._view.set_input_value("")
956
+ self._callbacks.start_agent_profile_switch(text)
957
+
958
+ elif mode == "models":
959
+ self.dismiss_suggestions()
960
+ self._view.set_input_value("")
961
+ self._callbacks.start_model_profile_switch(text)
962
+
963
+ elif mode == "history":
964
+ self.dismiss_suggestions()
965
+ self._view.set_input_value(text)
966
+
967
+ @property
968
+ def suggestion_mode(self) -> str | None:
969
+ return self._suggestion_mode
970
+
971
+ @property
972
+ def file_cache(self) -> list[ProjectPathSuggestion] | None:
973
+ return self._file_cache
974
+
975
+ @file_cache.setter
976
+ def file_cache(self, value: list[ProjectPathSuggestion] | None) -> None:
977
+ self._file_cache = value
978
+ self._file_cache_root = None
979
+ self._file_index = None
980
+ self._file_index_root = None
981
+ self._file_cache_generation += 1
982
+ self._invalidate_file_query_results()