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,1567 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Textual view adapter for the main screen."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import contextlib
8
+ import inspect
9
+ import logging
10
+ from collections.abc import Awaitable, Callable, Sequence
11
+ from datetime import datetime
12
+ from typing import TYPE_CHECKING, Any, TypedDict, cast
13
+
14
+ from textual.css.query import NoMatches
15
+ from textual.widgets import Footer
16
+
17
+ from chrys.app.tui.buddy_messages import BUDDY_TITLE
18
+ from chrys.app.tui.clipboard import copy_text_to_clipboards
19
+ from chrys.app.tui.notifications import NotificationEvent
20
+ from chrys.app.tui.screens.main.dialog_controllers import (
21
+ AgentLoadDialogHandle,
22
+ AnswerQuestionDialogResult,
23
+ ApprovalBypassDecision,
24
+ ApprovalDialogHandle,
25
+ ImageCompressionDialogHandle,
26
+ InlineQuestionDialogResult,
27
+ QuestionDialogHandle,
28
+ QuestionDialogResult,
29
+ )
30
+ from chrys.app.tui.screens.main.ports import (
31
+ ConfirmMessage,
32
+ InputLabel,
33
+ InputRestore,
34
+ NotificationSeverity,
35
+ StatusMessage,
36
+ StatusTrail,
37
+ SuggestionEntry,
38
+ )
39
+ from chrys.app.tui.terminal.panel import ShellPanel
40
+ from chrys.app.tui.terminal.widget import Terminal
41
+ from chrys.app.tui.widgets import PromptDraft
42
+ from chrys.app.tui.widgets.chat.file_snapshot import FileSnapshotPayload
43
+ from chrys.app.tui.widgets.chat.panel import ChatPanel
44
+ from chrys.app.tui.widgets.chat.session_json import SessionJsonPanel
45
+ from chrys.app.tui.widgets.chrome.app_header import AppHeader
46
+ from chrys.app.tui.widgets.chrome.image_paste import clipboard_image_dir_for_session
47
+ from chrys.app.tui.widgets.chrome.input_bar import InputBar
48
+ from chrys.app.tui.widgets.chrome.status_bar import (
49
+ STATUS_AGENT_LOAD_FAILED,
50
+ STATUS_INTERACTIVE_MODE,
51
+ STATUS_INTERRUPTED,
52
+ STATUS_RETRYING,
53
+ STATUS_RUNNING_TOOL,
54
+ STATUS_SHELL_MODE,
55
+ StatusBar,
56
+ )
57
+ from chrys.app.tui.widgets.sidebar.context import ContextUsageState
58
+ from chrys.app.tui.widgets.sidebar.panel import SidebarPanel
59
+ from chrys.app.tui.widgets.sidebar.tasks import TodoListState
60
+ from chrys.app.tui.widgets.sidebar.toc import ConversationToc
61
+ from chrys.app.tui.widgets.trajectory import TrajectoryDashboard
62
+ from chrys.foundation.events.types import (
63
+ AgentRuntimeDetails,
64
+ ApprovalRequest,
65
+ ApprovalReviewed,
66
+ QuestionToUser,
67
+ )
68
+ from chrys.foundation.i18n.formatting import format_message
69
+ from chrys.foundation.models.ask_user import AskUserAnswer
70
+ from chrys.service.approval.policy import ApprovalMode
71
+ from chrys.service.profiles.models.schema import DEFAULT_MAX_CONTEXT_TOKENS
72
+ from chrys.service.trajectory.session import trajectory_events_path
73
+
74
+ if TYPE_CHECKING:
75
+ from textual.worker import Worker
76
+
77
+ from chrys.app.tui.app import ChrysApp
78
+ from chrys.app.tui.i18n import LocaleController
79
+ from chrys.app.tui.screens.diff import RollbackProgressModal
80
+ from chrys.app.tui.screens.diff.rollback_modal import RollbackModalState
81
+ from chrys.app.tui.screens.main.engine_read_model import RollbackReadState
82
+ from chrys.app.tui.screens.main.screen import MainScreen
83
+ from chrys.app.tui.screens.main.state import MainScreenState
84
+ from chrys.app.tui.util.diff_entries import DiffFileEntry, DiffLoadResult
85
+ from chrys.app.tui.widgets.chrome.commands import ManPageSpec
86
+ from chrys.foundation.events.types import ProvisionalPresentation
87
+ from chrys.foundation.models.session_surface import SessionSurface
88
+ from chrys.foundation.models.todos import TodoItem
89
+ from chrys.service.approval.judge import JudgeVerdict
90
+ from chrys.service.context.providers.history import CompressedBlock
91
+ from chrys.service.state.store import StateStore
92
+
93
+ logger = logging.getLogger(__name__)
94
+
95
+
96
+ def _push_screen_untyped(app: Any, screen: object, callback: object | None = None) -> object:
97
+ """Push adapter-typed screens or callbacks that Textual's push_screen overloads cannot model."""
98
+ return app.push_screen(screen) if callback is None else app.push_screen(screen, callback)
99
+
100
+
101
+ def _judge_verdict(event: ApprovalReviewed) -> JudgeVerdict:
102
+ from chrys.service.approval.judge import JudgeVerdict
103
+
104
+ return JudgeVerdict(approved=event.approved, reason=event.reason)
105
+
106
+
107
+ class _StatusFlashKwargs(TypedDict, total=False):
108
+ """Keyword subset forwarded only when requested by the presenter."""
109
+
110
+ error: bool
111
+ caution: bool
112
+ trail: StatusTrail
113
+
114
+
115
+ class MainScreenViewAdapter:
116
+ """Own Textual widget lookup and screen side effects for main-screen controllers."""
117
+
118
+ def __init__(
119
+ self,
120
+ screen: MainScreen,
121
+ *,
122
+ state: MainScreenState,
123
+ state_store: StateStore | None = None,
124
+ locale_controller: LocaleController | None = None,
125
+ ) -> None:
126
+ self._screen = screen
127
+ self._state = state
128
+ self._state_store = state_store
129
+ self._locale_controller = locale_controller
130
+ self._rollback_progress_modal: RollbackProgressModal | None = None
131
+ self._input_restore_generation = 0
132
+ # The sessions browser's surface filter per mode (workflow mode or not), kept while the app runs.
133
+ self._session_surfaces: dict[bool, frozenset[SessionSurface]] = {}
134
+ # The status bar as shell mode found it, restored when shell mode exits.
135
+ self._shell_status_snapshot: dict = {}
136
+
137
+ # -------------------------------------------------------------- #
138
+ # Generic screen effects
139
+ # -------------------------------------------------------------- #
140
+
141
+ def debug(self, key: str, message: str = "") -> None:
142
+ self._screen._debug(key, message)
143
+
144
+ def notify(
145
+ self,
146
+ message: StatusMessage,
147
+ *,
148
+ title: StatusMessage,
149
+ severity: NotificationSeverity = "information",
150
+ timeout: float | None = 3,
151
+ ) -> None:
152
+ self._screen.notify(
153
+ self._render_status_message(message),
154
+ title=self._render_status_message(title),
155
+ severity=severity,
156
+ timeout=timeout,
157
+ markup=False,
158
+ )
159
+
160
+ def notify_event(self, event: NotificationEvent) -> None:
161
+ try:
162
+ cast("ChrysApp", self._screen.app).notification_service.notify(event)
163
+ except Exception:
164
+ logger.debug("Notification dispatch failed", exc_info=True)
165
+
166
+ def notify_buddy(
167
+ self,
168
+ message: StatusMessage,
169
+ *,
170
+ severity: NotificationSeverity = "information",
171
+ timeout: float = 10,
172
+ ) -> None:
173
+ self._screen.notify(
174
+ self._render_status_message(message),
175
+ title=self._render_status_message(BUDDY_TITLE.bind()),
176
+ severity=severity,
177
+ timeout=timeout,
178
+ markup=False,
179
+ )
180
+
181
+ def _render_status_message(self, message: StatusMessage) -> str:
182
+ if isinstance(message, str):
183
+ return message
184
+ controller = self._locale_controller
185
+ if controller is None:
186
+ return format_message(message)
187
+ from chrys.app.tui.i18n import render_str
188
+
189
+ return render_str(controller.localizer, message)
190
+
191
+ def refresh_buddy_panel(self, *, focus_tab: bool) -> None:
192
+ try:
193
+ sidebar = self._screen.query_one(SidebarPanel)
194
+ except NoMatches: # the screen is going away, or never had a sidebar
195
+ return
196
+ if focus_tab:
197
+ sidebar.focus_tab("tab-buddy")
198
+ sidebar.buddy_panel.reload()
199
+
200
+ def push_screen(self, screen: object, callback: object | None = None) -> object:
201
+ return _push_screen_untyped(self._screen.app, screen, callback)
202
+
203
+ def open_guide(self, locale_controller: LocaleController | None) -> None:
204
+ from chrys.app.tui.screens.guides import GuideDialog
205
+
206
+ self._screen.app.push_screen(GuideDialog(locale_controller=locale_controller))
207
+
208
+ def show_man_pages(self, pages: list[ManPageSpec], *, start_index: int = 0) -> None:
209
+ from chrys.app.tui.screens.dialogs.man_page import ManPageDialog
210
+
211
+ self._screen.app.push_screen(
212
+ ManPageDialog(
213
+ pages,
214
+ start_index=start_index,
215
+ locale_controller=self._locale_controller,
216
+ )
217
+ )
218
+
219
+ def open_runtime_details(self, details: AgentRuntimeDetails) -> None:
220
+ from chrys.app.tui.screens.dialogs.runtime_details import RuntimeDetailsDialog
221
+
222
+ self._screen.app.push_screen(RuntimeDetailsDialog(details))
223
+
224
+ def run_worker(self, awaitable: Awaitable[Any], *, group: str) -> None:
225
+ self._screen.run_worker(awaitable, exclusive=False, group=group)
226
+
227
+ def call_after_refresh(self, callback: Callable[[], None]) -> None:
228
+ self._screen.call_after_refresh(callback)
229
+
230
+ def stop_shell(self) -> None:
231
+ self._screen.query_one(ShellPanel).stop()
232
+
233
+ def exit_app(self) -> None:
234
+ self._screen.app.exit()
235
+
236
+ # -------------------------------------------------------------- #
237
+ # Input flow
238
+ # -------------------------------------------------------------- #
239
+
240
+ def set_terminal_title_for_user_message(self, text: str) -> None:
241
+ self._screen._session_title.set_terminal_title_for_user_message(text)
242
+
243
+ def clear_terminal_title_result(self) -> None:
244
+ self._screen._session_title.clear_terminal_title_result()
245
+
246
+ def set_terminal_title_for_cwd(self, cwd: str | None = None) -> None:
247
+ self._screen._session_title.set_terminal_title_for_cwd(cwd)
248
+
249
+ def current_chat_session_id(self) -> str:
250
+ return self._screen.query_one(ChatPanel).session_id
251
+
252
+ def current_session_id(self) -> str:
253
+ workflow = self._screen._workflow
254
+ return workflow.session_id if workflow.workflow_mode else self.current_chat_session_id()
255
+
256
+ def chat_session_id(self) -> str:
257
+ return self.current_chat_session_id()
258
+
259
+ def add_input_history(self, text: str, *, session_id: str | None) -> None:
260
+ self._screen.query_one(InputBar).add_to_history(text, session_id=session_id)
261
+
262
+ def set_retry_mode(self, enabled: bool, *, label: InputLabel = "Retry") -> None:
263
+ input_bar = self._screen.query_one(InputBar)
264
+ input_bar._retry_label = label
265
+ # retry_mode is always_update: assignment refreshes even when the
266
+ # flag is unchanged and only the label above differs.
267
+ input_bar.retry_mode = enabled
268
+
269
+ def set_input_retry_mode(self, value: bool) -> None:
270
+ self._screen.query_one(InputBar).retry_mode = value
271
+
272
+ def set_input_retry(self, label: InputLabel, value: bool = True) -> None:
273
+ self.set_retry_mode(value, label=label)
274
+
275
+ def start_run_status(self, label: StatusMessage) -> None:
276
+ status = self._screen.query_one(StatusBar)
277
+ status.start_run()
278
+ status.show(label)
279
+
280
+ async def render_user_message(self, text: str, *, created_at: datetime, contents: list[Any] | None) -> None:
281
+ await self._screen.query_one(ChatPanel).add_user_message(text, created_at=created_at, contents=contents)
282
+
283
+ async def render_user_retry_note(self, text: str, *, created_at: datetime) -> None:
284
+ await self._screen.query_one(ChatPanel).add_user_message(text, is_injection=True, created_at=created_at)
285
+
286
+ def hide_trailing_status_action(self) -> None:
287
+ self._screen.query_one(ChatPanel).hide_trailing_status_action()
288
+
289
+ def set_retry_pending(self, pending: bool) -> None:
290
+ self._screen.query_one(InputBar).retry_pending = pending
291
+ self._screen.query_one(ChatPanel).set_trailing_status_action_disabled(pending)
292
+
293
+ def clear_ask_user_inline_prompts(self) -> None:
294
+ self._screen.query_one(ChatPanel).clear_ask_user_inline_prompts()
295
+
296
+ def lock_input_with_text(self) -> None:
297
+ self._screen.query_one(InputBar).lock_with_text()
298
+
299
+ def unlock_input_keep_if_locked(self) -> None:
300
+ input_bar = self._screen.query_one(InputBar)
301
+ if input_bar.locked:
302
+ input_bar.unlock_and_keep()
303
+
304
+ def flash_interrupted(self) -> None:
305
+ self._screen.query_one(StatusBar).flash(STATUS_INTERRUPTED.bind(), caution=True)
306
+
307
+ async def render_interrupted(self) -> None:
308
+ await self._screen.query_one(ChatPanel).add_interrupted()
309
+
310
+ def update_toc(self) -> None:
311
+ with contextlib.suppress(Exception):
312
+ chat = self._screen.query_one(ChatPanel)
313
+ toc = self._screen.query_one(ConversationToc)
314
+ toc.update_items(chat.toc_items)
315
+
316
+ # -------------------------------------------------------------- #
317
+ # Status/header/chat presentation
318
+ # -------------------------------------------------------------- #
319
+
320
+ def set_tool_info(self, trail: StatusTrail) -> None:
321
+ self._screen.query_one(StatusBar).set_tool_info(trail)
322
+
323
+ def show_status(self, text: StatusMessage) -> None:
324
+ self._screen.query_one(StatusBar).show(text)
325
+
326
+ def clear_status(self) -> None:
327
+ self._screen.query_one(StatusBar).clear_status()
328
+
329
+ def flash_status(
330
+ self,
331
+ text: StatusMessage,
332
+ *,
333
+ error: bool = False,
334
+ caution: bool = False,
335
+ trail: StatusTrail | None = None,
336
+ ) -> None:
337
+ kwargs = _StatusFlashKwargs()
338
+ if error:
339
+ kwargs["error"] = True
340
+ if caution:
341
+ kwargs["caution"] = True
342
+ if trail is not None:
343
+ kwargs["trail"] = trail
344
+ self._screen.query_one(StatusBar).flash(text, **kwargs)
345
+
346
+ def flash_turn_complete(self) -> None:
347
+ self._screen.query_one(StatusBar).flash_completed()
348
+
349
+ def mark_terminal_title_completed(self) -> None:
350
+ self._screen._session_title.mark_terminal_title_completed()
351
+
352
+ def mark_terminal_title_failed(self) -> None:
353
+ self._screen._session_title.mark_terminal_title_failed()
354
+
355
+ def start_tool_status(self, tool_name: str) -> None:
356
+ status = self._screen.query_one(StatusBar)
357
+ status.add_tool_call()
358
+ status.show(STATUS_RUNNING_TOOL.bind(tool_name=tool_name))
359
+
360
+ def set_chat_profile(self, profile: str) -> None:
361
+ self._screen.chat_profile_name = profile
362
+ self._screen.query_one(ChatPanel).set_profile(profile)
363
+
364
+ def set_chat_tool_kinds(self, tool_kinds: dict[str, str]) -> None:
365
+ self._screen.query_one(ChatPanel).set_tool_kinds(tool_kinds)
366
+
367
+ async def clear_chat(self) -> None:
368
+ await self._screen.query_one(ChatPanel).clear()
369
+
370
+ def set_chat_workspace_cwd(self, cwd: str) -> None:
371
+ self._screen.chat_workspace_cwd = cwd
372
+ self._screen.query_one(ChatPanel).set_workspace_cwd(cwd)
373
+
374
+ def update_welcome(self, *, profile: str = "", cwd: str = "") -> None:
375
+ self._screen.query_one(ChatPanel).update_welcome(profile=profile, cwd=cwd)
376
+
377
+ def set_chat_session_id(self, session_id: str) -> None:
378
+ dashboard = self._screen.query_one(TrajectoryDashboard)
379
+ if dashboard.foreground and session_id != self._screen.chat_session_id:
380
+ self.hide_trajectory_dashboard()
381
+ self._screen.chat_session_id = session_id
382
+ self._screen.query_one(ChatPanel).set_session_id(session_id)
383
+
384
+ def set_session_title_state(
385
+ self,
386
+ *,
387
+ custom: str | None = None,
388
+ generated: str | None = None,
389
+ fallback: str | None = None,
390
+ ) -> None:
391
+ self._screen._session_title.set_session_title_state(custom=custom, generated=generated, fallback=fallback)
392
+
393
+ def reset_session_title_state(self) -> None:
394
+ self._screen._session_title.reset_session_title_state()
395
+
396
+ def session_custom_title(self) -> str:
397
+ return self._screen._session_title.custom_title
398
+
399
+ def set_context_usage_state(self, state: ContextUsageState | None) -> None:
400
+ self._screen.context_usage_state = state
401
+
402
+ @property
403
+ def context_usage_state(self) -> ContextUsageState | None:
404
+ return self._screen.context_usage_state
405
+
406
+ def set_todo_state(self, items: tuple[TodoItem, ...]) -> None:
407
+ self._screen.todo_state = TodoListState(items=tuple(items))
408
+
409
+ def clear_todos(self) -> None:
410
+ self._screen.todo_state = TodoListState()
411
+
412
+ def set_header_approval_mode(self, mode: ApprovalMode) -> None:
413
+ self._screen.header_approval_mode = mode
414
+
415
+ def set_status_profile(self, profile: str, *, description: str = "") -> None:
416
+ self._screen.query_one(StatusBar).set_profile(profile, description=description)
417
+
418
+ def set_input_clipboard_dir(self, session_id: str | None) -> None:
419
+ self._screen.query_one(InputBar).set_clipboard_image_dir(
420
+ clipboard_image_dir_for_session(self._state_store, session_id)
421
+ )
422
+
423
+ def set_input_paste_cwd(self, cwd: str) -> None:
424
+ self._screen.query_one(InputBar).set_paste_cwd(cwd)
425
+
426
+ def restore_input_text(self, text: str) -> InputRestore | None:
427
+ """Hand a consumed prompt back to an empty composer.
428
+
429
+ Returns proof of that restore for a caller that has to rewrite the same
430
+ text afterwards, or ``None`` when nothing was written — the composer
431
+ already held a draft, which is the user's and never ours to replace.
432
+
433
+ Every attempt consumes a generation, declined ones included, so an
434
+ outstanding credential dies the moment anything else tries to restore.
435
+ """
436
+ self._input_restore_generation += 1
437
+ input_bar = self._screen.query_one(InputBar)
438
+ if input_bar.locked:
439
+ input_bar.unlock_and_keep()
440
+ if not input_bar.restore_draft(text):
441
+ return None
442
+ return InputRestore(generation=self._input_restore_generation, text=text)
443
+
444
+ def rewrite_restored_input(self, restore: InputRestore | None, text: str) -> bool:
445
+ """Rewrite the text *restore* put there, while that restore still stands.
446
+
447
+ Authority comes from the credential, never from what the composer reads:
448
+ it buys nothing once the restore never happened, or any later restore —
449
+ successful or declined — superseded it. Byte identity is then required
450
+ on top, so an edit made under the credential's own restore hands the
451
+ draft back to the user.
452
+
453
+ The credential is spent either way, so it can never be replayed.
454
+ """
455
+ if restore is None or restore.generation != self._input_restore_generation:
456
+ return False
457
+ self._input_restore_generation += 1
458
+ input_bar = self._screen.query_one(InputBar)
459
+ if input_bar.value != restore.text:
460
+ return False
461
+ # Rewriting identical text would still reset the cursor and break an
462
+ # in-progress IME composition, so leave the composer alone.
463
+ if text != restore.text:
464
+ input_bar.value = text
465
+ return True
466
+
467
+ async def change_shell_directory(self, cwd: str) -> None:
468
+ await self._screen.query_one(ShellPanel).change_directory(cwd)
469
+
470
+ def reset_context_usage(self, max_context_tokens: int = 0) -> None:
471
+ self._screen.query_one(SidebarPanel).context_panel.reset(max_context_tokens)
472
+
473
+ def clear_compressed_blocks(self) -> None:
474
+ self._screen.query_one(SidebarPanel).context_panel.clear_blocks()
475
+
476
+ def restore_session_chrome(self, *, session_id: str, profile: str, cwd: str) -> None:
477
+ self.set_chat_session_id(session_id)
478
+ self.set_chat_profile(profile)
479
+ self.set_chat_workspace_cwd(cwd)
480
+ self.update_welcome(profile=profile, cwd=cwd)
481
+
482
+ # -------------------------------------------------------------- #
483
+ # Copy and fold actions
484
+ # -------------------------------------------------------------- #
485
+
486
+ def chat_copy_messages(self, target: str) -> list[tuple[str, str]]:
487
+ panel = self._screen.query_one(ChatPanel)
488
+ if target == "all":
489
+ return list(panel.get_all_messages())
490
+ if target == "user":
491
+ return list(panel.get_user_messages())
492
+ return list(panel.get_agent_responses())
493
+
494
+ def copy_text(self, text: str) -> None:
495
+ copy_text_to_clipboards(self._screen.app, text)
496
+
497
+ def toggle_chat_fold_all(self) -> bool:
498
+ return self._screen.query_one(ChatPanel).toggle_fold_all()
499
+
500
+ # -------------------------------------------------------------- #
501
+ # Navigation and overlays
502
+ # -------------------------------------------------------------- #
503
+
504
+ def open_log_viewer(self) -> None:
505
+ from chrys.app.tui.screens.logs.screen import LogViewerScreen
506
+
507
+ self._screen.app.push_screen(LogViewerScreen())
508
+
509
+ def open_sessions_screen(self, state_store: StateStore, *, current_session_id: str, on_result: object) -> None:
510
+ from chrys.app.tui.screens.sessions import SessionsScreen
511
+
512
+ workflow_mode = self._screen._workflow.workflow_mode
513
+
514
+ def remember(surfaces: frozenset[SessionSurface]) -> None:
515
+ self._session_surfaces[workflow_mode] = surfaces
516
+
517
+ _push_screen_untyped(
518
+ self._screen.app,
519
+ SessionsScreen(
520
+ state_store,
521
+ current_session_id=current_session_id,
522
+ locale_controller=self._locale_controller,
523
+ workflow_mode=workflow_mode,
524
+ surfaces=self._session_surfaces.get(workflow_mode),
525
+ on_surfaces_changed=remember,
526
+ ),
527
+ on_result,
528
+ )
529
+
530
+ async def restore_workflow_session(self, session_id: str) -> None:
531
+ if self._screen._workflow.workflow_mode:
532
+ await self._screen._workflow.session_view.restore_session(session_id)
533
+
534
+ def toggle_sidebar(self) -> None:
535
+ self._screen.query_one(SidebarPanel).toggle()
536
+
537
+ def open_theme_picker(self, on_result: object) -> None:
538
+ from chrys.app.tui.screens.themes import ThemesScreen
539
+
540
+ _push_screen_untyped(self._screen.app, ThemesScreen(self._screen.app.theme), on_result)
541
+
542
+ def apply_theme_setting(self, name: str) -> bool:
543
+ return cast("ChrysApp", self._screen.app).apply_theme_setting(name)
544
+
545
+ def open_language_picker(self, current_locale: str, on_result: object) -> None:
546
+ from chrys.app.tui.screens.menus.languages import LanguagesScreen
547
+
548
+ _push_screen_untyped(self._screen.app, LanguagesScreen(current_locale), on_result)
549
+
550
+ def dashboard_visible(self) -> bool:
551
+ return self._screen.query_one(TrajectoryDashboard).foreground
552
+
553
+ def show_trajectory_dashboard(self) -> None:
554
+ session_id = self.current_chat_session_id()
555
+ path = (
556
+ trajectory_events_path(self._state_store.session_dir(session_id))
557
+ if session_id and self._state_store is not None
558
+ else None
559
+ )
560
+ self._screen.query_one(TrajectoryDashboard).show_session(session_id, path)
561
+ self.sync_main_surface()
562
+
563
+ def hide_trajectory_dashboard(self) -> None:
564
+ self._screen.query_one(TrajectoryDashboard).hide_dashboard()
565
+ self.sync_main_surface()
566
+ self._screen.query_one(InputBar).focus_input()
567
+
568
+ def select_trajectory_turn(self, turn_id: str) -> None:
569
+ dashboard = self._screen.query_one(TrajectoryDashboard)
570
+ dashboard.select_turn(turn_id)
571
+ self.sync_main_surface()
572
+
573
+ def sync_main_surface(self) -> None:
574
+ dashboard = self._screen.query_one(TrajectoryDashboard)
575
+ shell = self._state.shell.active
576
+ workflow = self._screen._workflow.workflow_mode
577
+ dashboard.display = dashboard.foreground and not shell
578
+ self._screen._workflow_panel.display = workflow and not dashboard.foreground and not shell
579
+ chat = not workflow and not dashboard.foreground and not shell
580
+ self._screen.query_one(ChatPanel).display = chat
581
+ self._screen.query_one(StatusBar).display = shell or chat
582
+ self._screen.query_one(InputBar).display = chat
583
+ self._screen.query_one(Footer).display = not shell
584
+ self._screen._sync_workflow_timer()
585
+
586
+ def open_confirm_dialog(
587
+ self,
588
+ *,
589
+ title: StatusMessage,
590
+ message: ConfirmMessage,
591
+ confirm_label: StatusMessage,
592
+ confirm_variant: str,
593
+ on_result: object,
594
+ ) -> None:
595
+ from chrys.app.tui.screens.dialogs.confirm import ConfirmDialog
596
+
597
+ _push_screen_untyped(
598
+ self._screen.app,
599
+ ConfirmDialog(
600
+ title=title,
601
+ message=message,
602
+ confirm_label=confirm_label,
603
+ confirm_variant=confirm_variant,
604
+ locale_controller=self._locale_controller,
605
+ ),
606
+ on_result,
607
+ )
608
+
609
+ def dismiss_interrupt_confirm(self) -> None:
610
+ from chrys.app.tui.screens.dialogs.confirm import ConfirmDialog
611
+
612
+ if isinstance(self._screen.app.screen, ConfirmDialog):
613
+ self._screen.app.screen.dismiss(False)
614
+
615
+ def scroll_chat_to_turn(self, turn_id: str) -> None:
616
+ self._screen.query_one(ChatPanel).scroll_to_turn(turn_id)
617
+
618
+ # -------------------------------------------------------------- #
619
+ # Diff and rollback
620
+ # -------------------------------------------------------------- #
621
+
622
+ def open_diff_screen(
623
+ self,
624
+ periods_data: dict[int, list[DiffFileEntry]],
625
+ *,
626
+ cwd: str,
627
+ subtitle_parts: tuple[str, ...],
628
+ session_id: str,
629
+ all_entries: list[DiffFileEntry],
630
+ load_data: Callable[[], Awaitable[DiffLoadResult]] | None = None,
631
+ ) -> None:
632
+ from chrys.app.tui.screens.diff import DiffScreen
633
+
634
+ self._screen.app.push_screen(
635
+ DiffScreen(
636
+ periods_data,
637
+ cwd=cwd,
638
+ subtitle_parts=subtitle_parts,
639
+ session_id=session_id,
640
+ all_entries=all_entries,
641
+ load_data=load_data,
642
+ locale_controller=self._locale_controller,
643
+ )
644
+ )
645
+
646
+ def open_rollback_modal(
647
+ self,
648
+ *,
649
+ cwd: str,
650
+ load_state: Callable[[], Awaitable[RollbackReadState | None]],
651
+ on_result: object,
652
+ ) -> None:
653
+ from chrys.app.tui.screens.diff import RollbackModal
654
+
655
+ # RollbackReadState explicitly provides every RollbackModalState member.
656
+ modal_load_state = cast("Callable[[], Awaitable[RollbackModalState | None]]", load_state)
657
+ modal = RollbackModal(
658
+ tracker=None,
659
+ available_turns=[],
660
+ cwd=cwd,
661
+ load_state=modal_load_state,
662
+ locale_controller=self._locale_controller,
663
+ )
664
+ _push_screen_untyped(self._screen.app, modal, on_result)
665
+
666
+ def open_rollback_progress_modal(self, operation: Callable[[], Awaitable[None]]) -> None:
667
+ from chrys.app.tui.screens.diff import RollbackProgressModal
668
+
669
+ def _start_worker(awaitable: Awaitable[None]) -> object:
670
+ # A restore handoff can leave this operation finishing behind its
671
+ # dismissed modal. A later rollback must not cancel that tail.
672
+ return self._screen.run_worker(awaitable, exclusive=False, group="rollback-progress")
673
+
674
+ modal = RollbackProgressModal(
675
+ operation,
676
+ start_worker=_start_worker,
677
+ locale_controller=self._locale_controller,
678
+ )
679
+ self._rollback_progress_modal = modal
680
+
681
+ def _clear_modal(_result: None = None) -> None:
682
+ if self._rollback_progress_modal is modal:
683
+ self._rollback_progress_modal = None
684
+
685
+ self._screen.app.push_screen(modal, _clear_modal)
686
+
687
+ async def restore_welcome_rollback(self, *, session_id: str | None, profile: str, cwd: str) -> None:
688
+ self._screen.query_one(InputBar).retry_mode = False
689
+ panel = self._screen.query_one(ChatPanel)
690
+ await panel.clear()
691
+ # The rolled-back session's file is gone; stale title state would
692
+ # otherwise keep suppressing future auto-title updates (a custom
693
+ # title in UI state blocks them) and pin a stale terminal title.
694
+ self.reset_session_title_state()
695
+ if session_id:
696
+ self._screen.chat_session_id = session_id
697
+ panel.set_session_id(session_id)
698
+ self._screen.chat_workspace_cwd = cwd
699
+ max_context_tokens = (
700
+ self._screen.context_usage_state.max_context_tokens
701
+ if self._screen.context_usage_state
702
+ else DEFAULT_MAX_CONTEXT_TOKENS
703
+ )
704
+ self._screen.context_usage_state = ContextUsageState.with_window(
705
+ used_tokens=0,
706
+ max_context_tokens=max_context_tokens,
707
+ )
708
+ panel.set_workspace_cwd(cwd)
709
+ panel.update_welcome(profile=profile, cwd=cwd)
710
+ self.update_toc()
711
+ self.clear_todos()
712
+ with contextlib.suppress(Exception):
713
+ self._screen.query_one(SidebarPanel).context_panel.reset()
714
+
715
+ # -------------------------------------------------------------- #
716
+ # Chat transcript rendering
717
+ # -------------------------------------------------------------- #
718
+
719
+ async def add_error(self, message: str, *, action_label: str | None = "Retry") -> None:
720
+ await self._screen.query_one(ChatPanel).add_error(message, action_label=action_label)
721
+
722
+ async def add_system(self, text: str, *, key: str | None = None) -> None:
723
+ await self._screen.query_one(ChatPanel).add_system(text, key=key)
724
+
725
+ async def update_system(self, key: str, text: str) -> None:
726
+ await self._screen.query_one(ChatPanel).update_system(key, text)
727
+
728
+ async def remove_system(self, key: str) -> None:
729
+ await self._screen.query_one(ChatPanel).remove_system(key)
730
+
731
+ async def replay_history(
732
+ self,
733
+ messages: list[dict[str, Any]],
734
+ *,
735
+ initial_profile: str,
736
+ file_snapshots: dict[str, list[FileSnapshotPayload]] | None,
737
+ compressed_blocks: dict[str, CompressedBlock] | None,
738
+ ) -> None:
739
+ await self._screen.query_one(ChatPanel).replay_history(
740
+ messages,
741
+ initial_profile=initial_profile,
742
+ file_snapshots=file_snapshots,
743
+ compressed_blocks=compressed_blocks,
744
+ )
745
+
746
+ async def add_agent_message(
747
+ self,
748
+ text: str,
749
+ *,
750
+ is_final: bool,
751
+ is_intermediate: bool = False,
752
+ structured_output_completed: bool = False,
753
+ presentation: ProvisionalPresentation | None = None,
754
+ created_at: datetime | None = None,
755
+ ) -> None:
756
+ await self._screen.query_one(ChatPanel).add_agent_message(
757
+ text,
758
+ is_final=is_final,
759
+ is_intermediate=is_intermediate,
760
+ structured_output_completed=structured_output_completed,
761
+ presentation=presentation,
762
+ created_at=created_at,
763
+ )
764
+
765
+ async def accept_presentation_attempt(self, attempt_id: str, segment_ids: tuple[str, ...]) -> None:
766
+ await self._screen.query_one(ChatPanel).accept_presentation_attempt(attempt_id, segment_ids)
767
+
768
+ async def reject_presentation_attempt(self, attempt_id: str) -> None:
769
+ await self._screen.query_one(ChatPanel).reject_presentation_attempt(attempt_id)
770
+
771
+ async def add_user_injection(self, text: str, *, created_at: datetime) -> None:
772
+ await self._screen.query_one(ChatPanel).add_user_message(text, is_injection=True, created_at=created_at)
773
+
774
+ def unlock_input_after_consumed_injection(self) -> None:
775
+ self._screen.query_one(InputBar).unlock_and_clear()
776
+
777
+ def unlock_input_after_abandoned_injection(self) -> None:
778
+ self._screen.query_one(InputBar).unlock_and_keep()
779
+
780
+ async def add_tool_start(
781
+ self,
782
+ call_id: str,
783
+ tool_name: str,
784
+ tool_kind: str,
785
+ args_json: str,
786
+ *,
787
+ args: dict[str, Any] | None,
788
+ provider_hosted: bool = False,
789
+ hosted_family: str = "",
790
+ provider: str = "",
791
+ provider_item_type: str = "",
792
+ provider_status: str = "",
793
+ provider_call_id: str = "",
794
+ canonical_status: str = "running",
795
+ ) -> None:
796
+ await self._screen.query_one(ChatPanel).add_tool_start(
797
+ call_id,
798
+ tool_name,
799
+ tool_kind,
800
+ args_json,
801
+ args=args,
802
+ provider_hosted=provider_hosted,
803
+ hosted_family=hosted_family,
804
+ provider=provider,
805
+ provider_item_type=provider_item_type,
806
+ provider_status=provider_status,
807
+ provider_call_id=provider_call_id,
808
+ canonical_status=canonical_status,
809
+ )
810
+
811
+ def update_tool_progress(
812
+ self,
813
+ call_id: str,
814
+ lines: list[str],
815
+ *,
816
+ image_contents: list[object] | None = None,
817
+ snapshot_metadata: dict[str, Any] | None = None,
818
+ provider_status: str = "",
819
+ ) -> None:
820
+ self._screen.query_one(ChatPanel).update_tool_progress(
821
+ call_id,
822
+ lines,
823
+ image_contents=image_contents,
824
+ snapshot_metadata=snapshot_metadata,
825
+ provider_status=provider_status,
826
+ )
827
+
828
+ def update_tool_args(self, call_id: str, args: dict[str, Any]) -> None:
829
+ with contextlib.suppress(Exception):
830
+ self._screen.query_one(ChatPanel).update_tool_args(call_id, args)
831
+
832
+ def update_tool_status(
833
+ self,
834
+ call_id: str,
835
+ status: str,
836
+ *,
837
+ provider_status: str = "",
838
+ metadata: dict[str, Any] | None = None,
839
+ ) -> None:
840
+ self._screen.query_one(ChatPanel).update_tool_status(
841
+ call_id,
842
+ status,
843
+ provider_status=provider_status,
844
+ metadata=metadata,
845
+ )
846
+
847
+ async def add_tool_result(
848
+ self,
849
+ call_id: str,
850
+ tool_name: str,
851
+ result: str,
852
+ duration_ms: int,
853
+ *,
854
+ image_contents: list[object] | None,
855
+ file_snapshot: FileSnapshotPayload | None,
856
+ approval: Any,
857
+ metadata: dict[str, Any],
858
+ artifacts: list[dict[str, Any]] | None = None,
859
+ provider_status: str = "",
860
+ canonical_status: str = "completed",
861
+ ) -> None:
862
+ await self._screen.query_one(ChatPanel).add_tool_result(
863
+ call_id,
864
+ tool_name,
865
+ result,
866
+ duration_ms,
867
+ image_contents=image_contents,
868
+ file_snapshot=file_snapshot,
869
+ approval=approval,
870
+ metadata=metadata,
871
+ artifacts=artifacts,
872
+ provider_status=provider_status,
873
+ canonical_status=canonical_status,
874
+ )
875
+
876
+ async def add_context_fold(
877
+ self, context_id: str, summary: str, freed_messages: int, turn_range: tuple[int, int]
878
+ ) -> bool:
879
+ panel = self._screen.query_one(ChatPanel)
880
+ await panel.add_context_fold(context_id, summary, freed_messages, turn_range)
881
+ return panel.mark_turn_range_compressed(turn_range)
882
+
883
+ def add_compressed_block(
884
+ self,
885
+ context_id: str,
886
+ summary: str,
887
+ freed_messages: int = 0,
888
+ turn_range: tuple[int, int] = (0, 0),
889
+ ) -> None:
890
+ context_panel = self._screen.query_one(SidebarPanel).context_panel
891
+ context_panel.add_compressed_block(context_id, summary, freed_messages, turn_range)
892
+
893
+ async def add_compaction_start(self, compaction_id: str) -> None:
894
+ await self._screen.query_one(ChatPanel).add_compaction_start(compaction_id)
895
+
896
+ def complete_compaction(
897
+ self,
898
+ compaction_id: str,
899
+ *,
900
+ outcome: str,
901
+ duration_ms: int = 0,
902
+ last_words: str = "",
903
+ format_violation: str = "",
904
+ failure_reason: str = "",
905
+ ) -> None:
906
+ self._screen.query_one(ChatPanel).complete_compaction(
907
+ compaction_id,
908
+ outcome=outcome,
909
+ duration_ms=duration_ms,
910
+ last_words=last_words,
911
+ format_violation=format_violation,
912
+ failure_reason=failure_reason,
913
+ )
914
+
915
+ def show_compaction_retry(self, message: str, attempt: int, max_attempts: int, delay_seconds: int) -> None:
916
+ self._screen.query_one(ChatPanel).show_compaction_retry(message, attempt, max_attempts, delay_seconds)
917
+
918
+ # -------------------------------------------------------------- #
919
+ # Sub-agent transcript rendering
920
+ # -------------------------------------------------------------- #
921
+
922
+ def link_sub_agent_invocation(
923
+ self,
924
+ parent_call_id: str,
925
+ invocation_id: str,
926
+ agent_name: str,
927
+ sub_agent_log_file: str = "",
928
+ *,
929
+ tool_name: str = "",
930
+ ) -> None:
931
+ self._screen.query_one(ChatPanel).link_sub_agent_invocation(
932
+ parent_call_id,
933
+ invocation_id,
934
+ agent_name,
935
+ sub_agent_log_file,
936
+ tool_name=tool_name,
937
+ )
938
+
939
+ def add_sub_agent_message(
940
+ self,
941
+ agent_name: str,
942
+ invocation_id: str,
943
+ text: str,
944
+ *,
945
+ presentation: ProvisionalPresentation | None = None,
946
+ ) -> None:
947
+ self._screen.query_one(ChatPanel).add_sub_agent_message(
948
+ agent_name,
949
+ invocation_id,
950
+ text,
951
+ presentation=presentation,
952
+ )
953
+
954
+ def accept_sub_agent_presentation(
955
+ self,
956
+ invocation_id: str,
957
+ attempt_id: str,
958
+ segment_ids: tuple[str, ...],
959
+ ) -> None:
960
+ self._screen.query_one(ChatPanel).accept_sub_agent_presentation(
961
+ invocation_id,
962
+ attempt_id,
963
+ segment_ids,
964
+ )
965
+
966
+ def reject_sub_agent_presentation(self, invocation_id: str, attempt_id: str) -> None:
967
+ self._screen.query_one(ChatPanel).reject_sub_agent_presentation(invocation_id, attempt_id)
968
+
969
+ async def add_sub_agent_tool_start(
970
+ self,
971
+ agent_name: str,
972
+ invocation_id: str,
973
+ tool_name: str,
974
+ args: dict[str, Any],
975
+ call_id: str,
976
+ *,
977
+ tool_kind: str,
978
+ provider_hosted: bool = False,
979
+ hosted_family: str = "",
980
+ provider: str = "",
981
+ provider_item_type: str = "",
982
+ provider_status: str = "",
983
+ provider_call_id: str = "",
984
+ ) -> None:
985
+ await self._screen.query_one(ChatPanel).add_sub_agent_tool_start(
986
+ agent_name,
987
+ invocation_id,
988
+ tool_name,
989
+ args,
990
+ call_id,
991
+ tool_kind=tool_kind,
992
+ provider_hosted=provider_hosted,
993
+ hosted_family=hosted_family,
994
+ provider=provider,
995
+ provider_item_type=provider_item_type,
996
+ provider_status=provider_status,
997
+ provider_call_id=provider_call_id,
998
+ )
999
+
1000
+ def update_sub_agent_tool_args(self, invocation_id: str, call_id: str, args: dict[str, Any]) -> None:
1001
+ self._screen.query_one(ChatPanel).update_sub_agent_tool_args(invocation_id, call_id, args)
1002
+
1003
+ def update_sub_agent_tool_status(
1004
+ self,
1005
+ invocation_id: str,
1006
+ call_id: str,
1007
+ status: str,
1008
+ *,
1009
+ metadata: dict[str, Any] | None = None,
1010
+ provider_status: str = "",
1011
+ ) -> None:
1012
+ self._screen.query_one(ChatPanel).update_sub_agent_tool_status(
1013
+ invocation_id,
1014
+ call_id,
1015
+ status,
1016
+ metadata=metadata,
1017
+ provider_status=provider_status,
1018
+ )
1019
+
1020
+ def update_sub_agent_tool_progress(
1021
+ self,
1022
+ invocation_id: str,
1023
+ call_id: str,
1024
+ lines: list[str],
1025
+ *,
1026
+ image_contents: list[object] | None = None,
1027
+ snapshot_metadata: dict[str, Any] | None = None,
1028
+ provider_status: str = "",
1029
+ ) -> None:
1030
+ self._screen.query_one(ChatPanel).update_sub_agent_tool_progress(
1031
+ invocation_id,
1032
+ call_id,
1033
+ lines,
1034
+ image_contents=image_contents,
1035
+ snapshot_metadata=snapshot_metadata,
1036
+ provider_status=provider_status,
1037
+ )
1038
+
1039
+ def complete_sub_agent_tool(
1040
+ self,
1041
+ agent_name: str,
1042
+ invocation_id: str,
1043
+ call_id: str,
1044
+ result: str,
1045
+ duration_ms: int,
1046
+ *,
1047
+ image_contents: list[object] | None,
1048
+ artifacts: list[dict[str, Any]] | None,
1049
+ approval: Any,
1050
+ metadata: dict[str, Any],
1051
+ provider_status: str = "",
1052
+ canonical_status: str = "completed",
1053
+ ) -> None:
1054
+ self._screen.query_one(ChatPanel).complete_sub_agent_tool(
1055
+ agent_name,
1056
+ invocation_id,
1057
+ call_id,
1058
+ result,
1059
+ duration_ms,
1060
+ image_contents=image_contents,
1061
+ artifacts=artifacts,
1062
+ approval=approval,
1063
+ metadata=metadata,
1064
+ provider_status=provider_status,
1065
+ canonical_status=canonical_status,
1066
+ )
1067
+
1068
+ def update_sub_agent_progress(
1069
+ self,
1070
+ invocation_id: str,
1071
+ tool_call_count: int,
1072
+ total_tokens: int,
1073
+ total_usage_tokens: int,
1074
+ usage_unreported_attempts: int = 0,
1075
+ ) -> None:
1076
+ self._screen.query_one(ChatPanel).update_sub_agent_progress(
1077
+ invocation_id,
1078
+ tool_call_count,
1079
+ total_tokens,
1080
+ total_usage_tokens,
1081
+ usage_unreported_attempts,
1082
+ )
1083
+
1084
+ def add_sub_agent_compaction_start(self, agent_name: str, invocation_id: str, compaction_id: str) -> None:
1085
+ self._screen.query_one(ChatPanel).add_sub_agent_compaction_start(agent_name, invocation_id, compaction_id)
1086
+
1087
+ def complete_sub_agent_compaction(
1088
+ self,
1089
+ invocation_id: str,
1090
+ compaction_id: str,
1091
+ *,
1092
+ outcome: str,
1093
+ duration_ms: int = 0,
1094
+ format_violation: str = "",
1095
+ failure_reason: str = "",
1096
+ ) -> None:
1097
+ self._screen.query_one(ChatPanel).complete_sub_agent_compaction(
1098
+ invocation_id,
1099
+ compaction_id,
1100
+ outcome=outcome,
1101
+ duration_ms=duration_ms,
1102
+ format_violation=format_violation,
1103
+ failure_reason=failure_reason,
1104
+ )
1105
+
1106
+ def record_sub_agent_compaction_committed(self, invocation_id: str, compaction_id: str) -> None:
1107
+ self._screen.query_one(ChatPanel).record_sub_agent_compaction_committed(
1108
+ invocation_id,
1109
+ compaction_id,
1110
+ )
1111
+
1112
+ def sub_agent_retry_attempt(
1113
+ self,
1114
+ invocation_id: str,
1115
+ message: str,
1116
+ attempt: int,
1117
+ max_attempts: int,
1118
+ delay_seconds: int,
1119
+ ) -> None:
1120
+ self._screen.query_one(ChatPanel).sub_agent_retry_attempt(
1121
+ invocation_id,
1122
+ message,
1123
+ attempt,
1124
+ max_attempts,
1125
+ delay_seconds,
1126
+ )
1127
+
1128
+ def sub_agent_paused(
1129
+ self,
1130
+ invocation_id: str,
1131
+ reason: str,
1132
+ last_error: str,
1133
+ retry_attempts: int,
1134
+ diagnostic_path: str | None = None,
1135
+ last_error_display: str | None = None,
1136
+ ) -> None:
1137
+ self._screen.query_one(ChatPanel).sub_agent_paused(
1138
+ invocation_id,
1139
+ reason,
1140
+ last_error,
1141
+ retry_attempts,
1142
+ diagnostic_path,
1143
+ last_error_display,
1144
+ )
1145
+
1146
+ def sub_agent_resumed_after_pause(self, invocation_id: str) -> None:
1147
+ self._screen.query_one(ChatPanel).sub_agent_resumed_after_pause(invocation_id)
1148
+
1149
+ def sub_agent_cascade_aborted(self, invocation_id: str) -> None:
1150
+ self._screen.query_one(ChatPanel).sub_agent_cascade_aborted(invocation_id)
1151
+
1152
+ def sub_agent_aborted(self, invocation_id: str, last_error: str) -> None:
1153
+ self._screen.query_one(ChatPanel).sub_agent_aborted(invocation_id, last_error)
1154
+
1155
+ async def show_retry_attempt(
1156
+ self,
1157
+ message: str,
1158
+ attempt: int,
1159
+ max_attempts: int,
1160
+ delay_seconds: int,
1161
+ ) -> None:
1162
+ panel = self._screen.query_one(ChatPanel)
1163
+ await panel.prepare_retry()
1164
+ await panel.add_retry(message, attempt, max_attempts, delay_seconds)
1165
+ self.show_status(STATUS_RETRYING.bind(attempt=attempt, max_attempts=max_attempts))
1166
+
1167
+ # -------------------------------------------------------------- #
1168
+ # Approval dialogs
1169
+ # -------------------------------------------------------------- #
1170
+
1171
+ async def build_approval_body(self, event: ApprovalRequest) -> object | None:
1172
+ from chrys.app.tui.screens.dialogs.approval import create_approval_body
1173
+
1174
+ return await create_approval_body(
1175
+ event.tool_name,
1176
+ event.tool_kind,
1177
+ event.args,
1178
+ workspace_cwd=event.workspace_cwd,
1179
+ )
1180
+
1181
+ def approval_body_bypass(self, body: object | None) -> ApprovalBypassDecision | None:
1182
+ from chrys.app.tui.screens.dialogs.approval import ApprovalBody
1183
+
1184
+ if body is None:
1185
+ return None
1186
+ if not isinstance(body, ApprovalBody):
1187
+ return None
1188
+ bypass = body.bypass
1189
+ if bypass is None:
1190
+ return None
1191
+ return ApprovalBypassDecision(
1192
+ approved=bypass.approved,
1193
+ reason=bypass.reason,
1194
+ debug_reason=bypass.debug_reason,
1195
+ )
1196
+
1197
+ def show_approval_dialog(
1198
+ self,
1199
+ event: ApprovalRequest,
1200
+ approval_body: object | None,
1201
+ on_result: Callable[[tuple[bool, str, dict[str, Any] | None] | None], None],
1202
+ *,
1203
+ verdict: ApprovalReviewed | None,
1204
+ ) -> ApprovalDialogHandle:
1205
+ from chrys.app.tui.screens.dialogs.approval import ApprovalBody, ApprovalDialog
1206
+
1207
+ body = approval_body if isinstance(approval_body, ApprovalBody) else None
1208
+ dialog = ApprovalDialog(
1209
+ caller_name=event.caller_name,
1210
+ tool_name=event.tool_name,
1211
+ tool_kind=event.tool_kind,
1212
+ args=event.args,
1213
+ judging=event.judging,
1214
+ approval_body=body,
1215
+ presentation_kind=event.presentation_kind,
1216
+ reuse_offer=event.reuse_offer,
1217
+ verdict=_judge_verdict(verdict) if verdict is not None else None,
1218
+ )
1219
+ self._screen.app.push_screen(dialog, on_result)
1220
+ return dialog
1221
+
1222
+ def deliver_approval_verdict(self, dialog: ApprovalDialogHandle, event: ApprovalReviewed) -> None:
1223
+ from chrys.app.tui.screens.dialogs.approval import ApprovalDialog
1224
+
1225
+ if isinstance(dialog, ApprovalDialog):
1226
+ # The dialog keeps a verdict that beats its mount and shows it then.
1227
+ dialog.receive_verdict(_judge_verdict(event))
1228
+
1229
+ def dismiss_approval_dialog(self, dialog: ApprovalDialogHandle) -> None:
1230
+ """Close a cancelled approval through its cancellation-only path."""
1231
+ from chrys.app.tui.screens.dialogs.approval import ApprovalDialog
1232
+
1233
+ if isinstance(dialog, ApprovalDialog):
1234
+ dialog.dismiss_due_to_cancellation()
1235
+
1236
+ def approval_dialog_tool_name(self, dialog: ApprovalDialogHandle) -> str:
1237
+ from chrys.app.tui.screens.dialogs.approval import ApprovalDialog
1238
+
1239
+ if isinstance(dialog, ApprovalDialog):
1240
+ return dialog.tool_name
1241
+ return ""
1242
+
1243
+ def set_auto_review_count(self, count: int) -> None:
1244
+ self._screen.query_one(AppHeader).set_auto_review_count(count)
1245
+
1246
+ def notify_approval_required(self) -> None:
1247
+ self.notify_event(NotificationEvent.APPROVAL_REQUIRED)
1248
+
1249
+ # -------------------------------------------------------------- #
1250
+ # Ask-user dialogs
1251
+ # -------------------------------------------------------------- #
1252
+
1253
+ def show_question_dialog(
1254
+ self,
1255
+ event: QuestionToUser,
1256
+ draft: PromptDraft | None,
1257
+ on_result: Callable[[object], None],
1258
+ ) -> QuestionDialogHandle:
1259
+ from chrys.app.tui.screens.dialogs.ask_user import AskUserDialog
1260
+
1261
+ # Session identity determines whether the question belongs to the Chat transcript.
1262
+ dialog = AskUserDialog(
1263
+ request_id=event.request_id,
1264
+ questions=event.questions,
1265
+ caller_name=event.caller_name,
1266
+ draft=draft,
1267
+ allow_inline=bool(event.call_id)
1268
+ and (event.session_id is None or event.session_id == self.current_chat_session_id()),
1269
+ )
1270
+ self._screen.app.push_screen(dialog, on_result)
1271
+ return dialog
1272
+
1273
+ def parse_question_dialog_result(self, result: object) -> QuestionDialogResult:
1274
+ from chrys.app.tui.screens.dialogs.ask_user import AskUserInlineResult
1275
+
1276
+ if result is None:
1277
+ return None
1278
+ if isinstance(result, AskUserInlineResult):
1279
+ return InlineQuestionDialogResult(draft=result.draft)
1280
+ if isinstance(result, tuple) and len(result) == 2:
1281
+ request_id, answers = result
1282
+ if (
1283
+ isinstance(request_id, str)
1284
+ and isinstance(answers, tuple)
1285
+ and all(isinstance(answer, AskUserAnswer) for answer in answers)
1286
+ ):
1287
+ return AnswerQuestionDialogResult(request_id=request_id, answers=answers)
1288
+ return None
1289
+
1290
+ def show_question_inline(self, event: QuestionToUser, draft: PromptDraft | None = None) -> bool:
1291
+ if not event.call_id:
1292
+ return False
1293
+ try:
1294
+ panel = self._screen.query_one(ChatPanel)
1295
+ except Exception:
1296
+ return False
1297
+ return panel.show_ask_user_inline(
1298
+ event.call_id,
1299
+ event.request_id,
1300
+ event.questions,
1301
+ draft=draft,
1302
+ )
1303
+
1304
+ def clear_question_inline(self, call_id: str, request_id: str) -> bool:
1305
+ """Clear a timed-out inline prompt if its request still matches."""
1306
+ try:
1307
+ panel = self._screen.query_one(ChatPanel)
1308
+ except Exception:
1309
+ return False
1310
+ return panel.clear_ask_user_inline(call_id, request_id)
1311
+
1312
+ def question_can_reopen_modal(self, event: QuestionToUser) -> bool:
1313
+ if not event.call_id:
1314
+ return True
1315
+ try:
1316
+ panel = self._screen.query_one(ChatPanel)
1317
+ except Exception:
1318
+ return True
1319
+ return panel.is_tool_running(event.call_id)
1320
+
1321
+ def notify_ask_user(self) -> None:
1322
+ self.notify_event(NotificationEvent.ASK_USER)
1323
+
1324
+ def focus_input(self) -> None:
1325
+ with contextlib.suppress(Exception):
1326
+ self._screen.query_one(InputBar).focus_input()
1327
+
1328
+ # -------------------------------------------------------------- #
1329
+ # Suggestions
1330
+ # -------------------------------------------------------------- #
1331
+
1332
+ @property
1333
+ def is_attached(self) -> bool:
1334
+ return self._screen.is_attached
1335
+
1336
+ @property
1337
+ def suggestions_active(self) -> bool:
1338
+ with contextlib.suppress(Exception):
1339
+ return bool(self._screen.query_one(InputBar).suggestions_active)
1340
+ return False
1341
+
1342
+ def show_suggestions(
1343
+ self,
1344
+ mode: str,
1345
+ items: Sequence[SuggestionEntry],
1346
+ *,
1347
+ disabled: set[str] | None = None,
1348
+ initial: str | None = None,
1349
+ title: str = "",
1350
+ ) -> None:
1351
+ from chrys.app.tui.widgets.chrome.suggestion_list import SuggestionList
1352
+
1353
+ with contextlib.suppress(NoMatches):
1354
+ self._screen.query_one(SuggestionList).show(
1355
+ mode, list(items), disabled=disabled, initial=initial, title=title
1356
+ )
1357
+ self._screen.query_one(InputBar).set_suggestions_active(True, mode=mode)
1358
+
1359
+ def show_suggestions_loading(self, mode: str, *, title: str = "") -> None:
1360
+ """Open a suggestion popup immediately with only its loading indicator."""
1361
+ from chrys.app.tui.widgets.chrome.suggestion_list import SuggestionList
1362
+
1363
+ with contextlib.suppress(NoMatches):
1364
+ self._screen.query_one(SuggestionList).show_loading(mode, title=title)
1365
+ self._screen.query_one(InputBar).set_suggestions_active(True, mode=mode)
1366
+
1367
+ def update_suggestions(
1368
+ self,
1369
+ items: Sequence[SuggestionEntry],
1370
+ *,
1371
+ disabled: set[str] | None = None,
1372
+ title: str | None = None,
1373
+ ) -> None:
1374
+ from chrys.app.tui.widgets.chrome.suggestion_list import SuggestionList
1375
+
1376
+ with contextlib.suppress(NoMatches):
1377
+ self._screen.query_one(SuggestionList).update(list(items), disabled=disabled, title=title)
1378
+
1379
+ def hide_suggestions(self) -> None:
1380
+ from chrys.app.tui.widgets.chrome.suggestion_list import SuggestionList
1381
+
1382
+ with contextlib.suppress(NoMatches):
1383
+ self._screen.query_one(SuggestionList).hide()
1384
+ input_bar = self._screen.query_one(InputBar)
1385
+ input_bar.set_suggestions_active(False)
1386
+ input_bar.focus_input()
1387
+
1388
+ async def load_prompt_history(self, *, max_entries: int) -> list[str]:
1389
+ """Load cross-session prompt history without blocking the TUI loop."""
1390
+ return await self._screen.query_one(InputBar).load_prompt_history(max_entries=max_entries)
1391
+
1392
+ def run_file_suggestion_worker(self, awaitable: Awaitable[None], *, name: str, group: str) -> Worker[None]:
1393
+ return self._screen.run_worker(awaitable, name=name, group=group)
1394
+
1395
+ def move_suggestion_cursor(self, direction: str) -> None:
1396
+ from chrys.app.tui.widgets.chrome.suggestion_list import SuggestionList
1397
+
1398
+ suggestions = self._screen.query_one(SuggestionList)
1399
+ if direction == "up":
1400
+ suggestions.move_cursor_up()
1401
+ return
1402
+ suggestions.move_cursor_down()
1403
+
1404
+ def select_highlighted_suggestion(self, *, execute: bool) -> bool:
1405
+ from chrys.app.tui.widgets.chrome.suggestion_list import SuggestionList
1406
+
1407
+ return bool(self._screen.query_one(SuggestionList).select_highlighted(execute=execute))
1408
+
1409
+ def show_subcommand_suggestions(
1410
+ self,
1411
+ items: Sequence[SuggestionEntry],
1412
+ *,
1413
+ initial: str | None = None,
1414
+ ) -> None:
1415
+ from chrys.app.tui.widgets.chrome.suggestion_list import SuggestionList
1416
+
1417
+ self._screen.query_one(SuggestionList).show("subcommands", list(items), initial=initial)
1418
+
1419
+ def input_value(self) -> str:
1420
+ return str(self._screen.query_one(InputBar).value)
1421
+
1422
+ def set_input_value(self, value: str) -> None:
1423
+ self._screen.query_one(InputBar).value = value
1424
+
1425
+ def replace_input_trigger_text(self, trigger: str, replacement: str) -> None:
1426
+ self._screen.query_one(InputBar).replace_trigger_text(trigger, replacement)
1427
+
1428
+ # -------------------------------------------------------------- #
1429
+ # Shell mode, focus, and paste
1430
+ # -------------------------------------------------------------- #
1431
+
1432
+ def enter_shell_mode(self) -> None:
1433
+ dashboard = self._screen.query_one(TrajectoryDashboard)
1434
+ session_json = self._screen.query_one(SessionJsonPanel)
1435
+ shell = self._screen.query_one(ShellPanel)
1436
+ status = self._screen.query_one(StatusBar)
1437
+ sidebar = self._screen.query_one(SidebarPanel)
1438
+ self._shell_status_snapshot = status.snapshot()
1439
+ if dashboard.suspend_for_shell_mode():
1440
+ session_json.suspend_for_shell_mode()
1441
+ else:
1442
+ session_json.hide_session_json()
1443
+ # The dashboard hides the status bar while foreground; shell mode needs it.
1444
+ self.sync_main_surface()
1445
+ sidebar.add_class("-shell-active")
1446
+ shell.show()
1447
+ # Flash last: its layout-free face flip rebuilds the compositor map
1448
+ # synchronously, and any widget repaint that lands before the screen's
1449
+ # deferred relayout paints from that map — it must already describe
1450
+ # the final shell layout or the half-applied one flashes on screen.
1451
+ status.flash(STATUS_SHELL_MODE.bind(), warn=True)
1452
+ shell.query_one(Terminal).focus()
1453
+
1454
+ def exit_shell_mode(self) -> None:
1455
+ dashboard = self._screen.query_one(TrajectoryDashboard)
1456
+ session_json = self._screen.query_one(SessionJsonPanel)
1457
+ shell = self._screen.query_one(ShellPanel)
1458
+ status = self._screen.query_one(StatusBar)
1459
+ input_bar = self._screen.query_one(InputBar)
1460
+ sidebar = self._screen.query_one(SidebarPanel)
1461
+ shell.hide()
1462
+ dashboard_restored = dashboard.finish_shell_mode()
1463
+ session_json_visible = dashboard.session_json_visible
1464
+ session_json.finish_shell_mode(restore=session_json_visible)
1465
+ self.sync_main_surface()
1466
+ sidebar.remove_class("-shell-active")
1467
+ # Restore last for the same reason flash runs last in enter_shell_mode:
1468
+ # its map rebuild must see the fully restored layout.
1469
+ status.restore(self._shell_status_snapshot)
1470
+ if not dashboard_restored and input_bar.display:
1471
+ input_bar.focus_input()
1472
+
1473
+ def set_alternate_screen_active(self, active: bool) -> None:
1474
+ status = self._screen.query_one(StatusBar)
1475
+ footer = self._screen.query_one(Footer)
1476
+ sidebar = self._screen.query_one(SidebarPanel)
1477
+ sidebar.suppress_visibility("alternate-screen", active)
1478
+ if active:
1479
+ footer.display = False
1480
+ status.flash(STATUS_INTERACTIVE_MODE.bind(), warn=True)
1481
+ self._screen.query_one(ShellPanel).query_one(Terminal).focus()
1482
+ return
1483
+ if self._state.shell.active:
1484
+ status.flash(STATUS_SHELL_MODE.bind(), warn=True)
1485
+ self._screen.query_one(ShellPanel).query_one(Terminal).focus()
1486
+ else:
1487
+ footer.display = True
1488
+ status.clear_status()
1489
+
1490
+ def insert_paste_payload(self, text: str) -> bool:
1491
+ try:
1492
+ input_bar = self._screen.query_one(InputBar)
1493
+ except Exception:
1494
+ return False
1495
+ if not text:
1496
+ return input_bar.insert_clipboard_image_paste()
1497
+ return input_bar.insert_image_paste_payload(text)
1498
+
1499
+ # -------------------------------------------------------------- #
1500
+ # Agent-load dialogs
1501
+ # -------------------------------------------------------------- #
1502
+
1503
+ def create_agent_load_dialog(self, *, title: StatusMessage, subtitle: str) -> AgentLoadDialogHandle:
1504
+ from chrys.app.tui.screens.dialogs.agent_load import AgentLoadDialog
1505
+
1506
+ return AgentLoadDialog(title=title, subtitle=subtitle, locale_controller=self._locale_controller)
1507
+
1508
+ def prepare_agent_load_ui(
1509
+ self,
1510
+ *,
1511
+ title: StatusMessage,
1512
+ session_id: str | None,
1513
+ update_clipboard_dir: bool,
1514
+ capture_status_snapshot: bool,
1515
+ ) -> dict | None:
1516
+ status = self._screen.query_one(StatusBar)
1517
+ snapshot = status.snapshot() if capture_status_snapshot else None
1518
+ status.start_run()
1519
+ status.show(title)
1520
+
1521
+ input_bar = self._screen.query_one(InputBar)
1522
+ if update_clipboard_dir:
1523
+ input_bar.set_clipboard_image_dir(clipboard_image_dir_for_session(self._state_store, session_id))
1524
+ return snapshot
1525
+
1526
+ async def push_agent_load_dialog(self, dialog: AgentLoadDialogHandle) -> None:
1527
+ rollback_modal = self._rollback_progress_modal
1528
+ if rollback_modal is not None:
1529
+ self._rollback_progress_modal = None
1530
+ rollback_modal.handoff_to_session_restore()
1531
+
1532
+ def _restore_input_focus(_result: None = None) -> None:
1533
+ if self._state.shell.active or self._state.shell.fullscreen_terminal:
1534
+ return
1535
+ with contextlib.suppress(Exception):
1536
+ self._screen.query_one(InputBar).focus_input()
1537
+
1538
+ pushed = _push_screen_untyped(self._screen.app, dialog, _restore_input_focus)
1539
+ if inspect.isawaitable(pushed):
1540
+ await pushed
1541
+
1542
+ def restore_agent_load_status(self, snapshot: dict) -> None:
1543
+ with contextlib.suppress(Exception):
1544
+ self._screen.query_one(StatusBar).restore(snapshot)
1545
+
1546
+ def show_load_status(self, message: StatusMessage) -> None:
1547
+ self._screen.query_one(StatusBar).show(message)
1548
+
1549
+ def render_status_message(self, message: StatusMessage) -> str:
1550
+ return self._render_status_message(message)
1551
+
1552
+ def flash_agent_load_failed(self, message: str) -> None:
1553
+ self._screen.query_one(StatusBar).flash(STATUS_AGENT_LOAD_FAILED.bind(message=message), error=True)
1554
+
1555
+ # -------------------------------------------------------------- #
1556
+ # Image-compression dialogs
1557
+ # -------------------------------------------------------------- #
1558
+
1559
+ def create_image_compression_dialog(self, *, title: StatusMessage) -> ImageCompressionDialogHandle:
1560
+ from chrys.app.tui.screens.dialogs.image_compression import ImageCompressionDialog
1561
+
1562
+ return ImageCompressionDialog(title=title, locale_controller=self._locale_controller)
1563
+
1564
+ async def push_image_compression_dialog(self, dialog: ImageCompressionDialogHandle) -> None:
1565
+ pushed = _push_screen_untyped(self._screen.app, dialog)
1566
+ if inspect.isawaitable(pushed):
1567
+ await pushed