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,925 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Renderers for ``edit_file`` and ``write_file`` tools.
4
+
5
+ Before/after file content is provided by the mutation tracking system
6
+ via ``file_snapshot``. Large snapshots may be disk-backed refs and
7
+ are resolved only when the inline diff/copy/detail view needs them.
8
+
9
+ edit_file: split DiffView (side-by-side before/after).
10
+ write_file: unified DiffView (new file content).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import logging
17
+ import os
18
+ import re
19
+ from contextlib import suppress
20
+ from difflib import unified_diff
21
+ from typing import TYPE_CHECKING, Any, ClassVar
22
+
23
+ from rich.text import Text
24
+ from textual.timer import Timer
25
+ from textual.widget import Widget
26
+ from textual.widgets import Static
27
+
28
+ import chrys.app.tui.widgets.file_preview as _file_preview
29
+ from chrys.app.tui.i18n import render_str, render_text, widget_localizer
30
+ from chrys.app.tui.support.gc_freeze import GcAbsorbReason, GcAbsorbRequested
31
+ from chrys.app.tui.util.static_update import update_static_in_place
32
+ from chrys.app.tui.util.visibility import is_widget_shown_on_active_screen
33
+ from chrys.app.tui.widgets.chat.file_snapshot import FileSnapshotPayload, FileSnapshotRef
34
+ from chrys.app.tui.widgets.chat.tool_call import (
35
+ TOOL_CARD_REJECTED,
36
+ BaseToolCard,
37
+ ToolCardHeader,
38
+ fmt_duration,
39
+ tool_result_render_status,
40
+ )
41
+ from chrys.app.tui.widgets.chat.tool_view_builders import ToolViewDiff, build_output_views
42
+ from chrys.foundation.i18n import msg
43
+ from chrys.foundation.tool_kinds import KIND_FILESYSTEM_WRITE
44
+
45
+ if TYPE_CHECKING:
46
+ from textual.app import ComposeResult
47
+
48
+ _FILE_EDIT_WRITING = msg("tui.tool_card.file_edit.writing", fallback="writing")
49
+ _FILE_EDIT_EDITING = msg("tui.tool_card.file_edit.editing", fallback="editing")
50
+ _FILE_EDIT_ERROR = msg("tui.tool_card.file_edit.status.error", fallback="error")
51
+ _FILE_EDIT_CHANGES = msg("tui.tool_card.file_edit.status.changes", fallback="changes")
52
+ _FILE_EDIT_CHANGE_COUNTS = msg(
53
+ "tui.tool_card.file_edit.status.change_counts",
54
+ fallback="+{additions}, -{removals}",
55
+ )
56
+ _FILE_EDIT_LINES_WRITTEN = msg(
57
+ "tui.tool_card.file_edit.status.lines_written",
58
+ fallback="{lines} lines written",
59
+ )
60
+ _FILE_EDIT_OVERWRITE = msg("tui.tool_card.file_edit.overwrite", fallback="(overwrite)")
61
+ _FILE_EDIT_DIFF = msg("tui.tool_card.file_edit.section.diff", fallback="Diff")
62
+ _FILE_EDIT_DIFF_PREVIEW = msg(
63
+ "tui.tool_card.file_edit.section.diff_preview",
64
+ fallback="Diff Preview",
65
+ )
66
+ _FILE_EDIT_FULL_DIFF_OMITTED = msg(
67
+ "tui.tool_card.file_edit.full_diff_omitted",
68
+ fallback=(
69
+ "@@ Full diff omitted: snapshot is {total_chars} chars (limit {limit_chars}); "
70
+ "showing bounded diff around first change @@"
71
+ ),
72
+ )
73
+ _FILE_EDIT_BOUNDED_PREVIEW_UNCHANGED = msg(
74
+ "tui.tool_card.file_edit.bounded_preview_unchanged",
75
+ fallback="@@ Bounded preview contains no changed lines @@",
76
+ )
77
+
78
+
79
+ _WRITE_FILE_LINE_COUNT_SCAN_CHARS = 64 * 1024
80
+ _FILE_COPY_FULL_DIFF_MAX_CHARS = 64 * 1024
81
+ _FILE_COPY_PREVIEW_LINES = 12
82
+ _FILE_COPY_CONTEXT_LINES = 3
83
+ _FILE_COPY_DIFF_SCAN_CHUNK_CHARS = 4096
84
+ _EDIT_FILE_INLINE_MAX_DISPLAY_LINES = 24
85
+ _INLINE_DIFF_MOUNT_DELAY_SECONDS = 0.2
86
+ _WRITE_FILE_RESULT_LINES_RE = re.compile(r"\((\d+) lines?\)")
87
+
88
+ logger = logging.getLogger(__name__)
89
+
90
+
91
+ def _write_file_line_count(result: str, after: str) -> str:
92
+ """Return the written line count without materializing all lines."""
93
+ if match := _WRITE_FILE_RESULT_LINES_RE.search(result):
94
+ return match.group(1)
95
+ if not after:
96
+ return "0"
97
+ scan_end = min(len(after), _WRITE_FILE_LINE_COUNT_SCAN_CHARS)
98
+ newlines = after.count("\n", 0, scan_end)
99
+ if scan_end == len(after):
100
+ count = newlines + (0 if after.endswith("\n") else 1)
101
+ return str(count)
102
+ count = newlines if after[scan_end - 1] == "\n" else newlines + 1
103
+ return f"{count}+"
104
+
105
+
106
+ def _line_ending_sensitive_change(before: str, after: str, *, changed_index: int | None = None) -> bool:
107
+ """Return true when content lines match but terminators differ."""
108
+ if changed_index is not None:
109
+ before_char = before[changed_index] if changed_index < len(before) else ""
110
+ after_char = after[changed_index] if changed_index < len(after) else ""
111
+ return before_char in {"\r", "\n", ""} and after_char in {"\r", "\n", ""}
112
+ if not before or not after:
113
+ return False
114
+ return before != after and before.splitlines() == after.splitlines()
115
+
116
+
117
+ def _split_line_ending(line: str) -> tuple[str, str]:
118
+ """Return ``(body, label)`` for a possibly terminated line chunk."""
119
+ if line.endswith("\r\n"):
120
+ return line[:-2], "CRLF"
121
+ if line.endswith("\n"):
122
+ return line[:-1], "LF"
123
+ if line.endswith("\r"):
124
+ return line[:-1], "CR"
125
+ return line, "none"
126
+
127
+
128
+ def _format_eol_marker(label: str) -> str:
129
+ """Return a visible line-ending marker for copied diffs."""
130
+ return " [no newline]" if label == "none" else f" [EOL: {label}]"
131
+
132
+
133
+ def _copy_diff_source_lines(text: str, *, show_eol_markers: bool) -> list[str]:
134
+ """Return source lines for copy-only diffs, optionally exposing EOLs."""
135
+ if not show_eol_markers:
136
+ return text.splitlines()
137
+ lines: list[str] = []
138
+ for line in text.splitlines(keepends=True):
139
+ body, eol = _split_line_ending(line)
140
+ lines.append(_file_preview.sanitize_preview_line(body) + _format_eol_marker(eol))
141
+ return lines
142
+
143
+
144
+ def _first_mismatch_index(before: str, after: str) -> int | None:
145
+ """Return the first differing character index without splitting snapshots."""
146
+ common_len = min(len(before), len(after))
147
+ pos = 0
148
+ while pos < common_len:
149
+ end = min(pos + _FILE_COPY_DIFF_SCAN_CHUNK_CHARS, common_len)
150
+ if before[pos:end] == after[pos:end]:
151
+ pos = end
152
+ continue
153
+ for idx in range(pos, end):
154
+ if before[idx] != after[idx]:
155
+ return idx
156
+ if len(before) != len(after):
157
+ return common_len
158
+ return None
159
+
160
+
161
+ def _line_start_with_context(text: str, index: int, context_lines: int) -> int:
162
+ """Return the line start before *index*, including bounded context."""
163
+ if not text:
164
+ return 0
165
+ index = min(max(index, 0), len(text))
166
+ start = text.rfind("\n", 0, index) + 1
167
+ for _ in range(context_lines):
168
+ if start == 0:
169
+ break
170
+ prev_newline = text.rfind("\n", 0, start - 1)
171
+ start = 0 if prev_newline < 0 else prev_newline + 1
172
+ return start
173
+
174
+
175
+ def _sanitize_bounded_diff_line(line: str, changed_offset: int | None, max_chars: int) -> str:
176
+ """Return one terminal-safe bounded line for diff previews."""
177
+ if len(line) <= max_chars:
178
+ return _file_preview.sanitize_preview_line(line)
179
+
180
+ if changed_offset is None:
181
+ return _file_preview.sanitize_preview_line(line[:max_chars]) + "..."
182
+
183
+ changed_offset = min(max(changed_offset, 0), len(line))
184
+ budget = max(1, max_chars - 6)
185
+ start = max(0, changed_offset - (budget // 2))
186
+ end = min(len(line), start + budget)
187
+ start = max(0, end - budget)
188
+ prefix = "..." if start > 0 else ""
189
+ suffix = "..." if end < len(line) else ""
190
+ return prefix + _file_preview.sanitize_preview_line(line[start:end]) + suffix
191
+
192
+
193
+ def _bounded_changed_window(
194
+ text: str,
195
+ changed_index: int,
196
+ *,
197
+ max_lines: int,
198
+ max_line_chars: int,
199
+ show_eol_markers: bool = False,
200
+ ) -> list[str]:
201
+ """Return bounded lines around the actual changed region."""
202
+ if not text or max_lines <= 0 or max_line_chars <= 0:
203
+ return []
204
+
205
+ changed_index = min(max(changed_index, 0), len(text))
206
+ pos = _line_start_with_context(text, changed_index, _FILE_COPY_CONTEXT_LINES)
207
+ lines: list[str] = []
208
+ while pos <= len(text) and len(lines) < max_lines:
209
+ newline = text.find("\n", pos)
210
+ if newline < 0:
211
+ line_end = len(text)
212
+ next_pos = len(text) + 1
213
+ else:
214
+ line_end = newline
215
+ next_pos = newline + 1
216
+
217
+ changed_offset: int | None = None
218
+ if pos <= changed_index <= line_end:
219
+ changed_offset = changed_index - pos
220
+ body, eol = _split_line_ending(text[pos:next_pos])
221
+ line = _sanitize_bounded_diff_line(body, changed_offset, max_line_chars)
222
+ if show_eol_markers:
223
+ line += _format_eol_marker(eol)
224
+ lines.append(line)
225
+
226
+ if newline < 0:
227
+ break
228
+ pos = next_pos
229
+
230
+ return lines
231
+
232
+
233
+ class _FileToolCall(BaseToolCard):
234
+ """Base renderer for file mutation tools (edit_file, write_file).
235
+
236
+ Before/after content is supplied via ``file_snapshot`` in
237
+ ``set_complete()`` — sourced from the mutation tracking system
238
+ during live execution and from serialized blobs during replay.
239
+ """
240
+
241
+ DEFAULT_CSS = """
242
+ _FileToolCall {
243
+ height: auto;
244
+ padding: 0 0 0 2;
245
+ margin: 0 0 1 0;
246
+ }
247
+ _FileToolCall #ft-label {
248
+ height: auto;
249
+ }
250
+ _FileToolCall > #ft-panel {
251
+ height: auto;
252
+ margin: 0 0 0 2;
253
+ border: round $tui-border-primary 50%;
254
+ border-title-color: $text;
255
+ border-title-style: bold;
256
+ border-subtitle-align: right;
257
+ border-subtitle-color: $text-muted;
258
+ padding: 0 1;
259
+ }
260
+ _FileToolCall.-success > #ft-panel {
261
+ border: round $tui-border-success 30%;
262
+ border-title-color: $success;
263
+ border-subtitle-color: $success;
264
+
265
+ border-title-style: not bold;
266
+ }
267
+ _FileToolCall.-error > #ft-panel {
268
+ border: round $tui-border-error 50%;
269
+ border-title-color: $text-error;
270
+ border-subtitle-color: $text-error;
271
+
272
+ border-title-style: not bold;
273
+ }
274
+ _FileToolCall.-rejected > #ft-panel {
275
+ border: round $tui-border-warning 50%;
276
+ border-title-color: $warning;
277
+ border-subtitle-color: $warning;
278
+
279
+ border-title-style: not bold;
280
+ }
281
+ _FileToolCall #ft-spinner {
282
+ height: auto;
283
+ }
284
+ _FileToolCall #ft-content {
285
+ height: auto;
286
+ display: none;
287
+ }
288
+ _FileToolCall.-done #ft-spinner {
289
+ display: none;
290
+ }
291
+ _FileToolCall.-done #ft-content {
292
+ display: block;
293
+ }
294
+ """
295
+
296
+ _SPINNERS: ClassVar[str] = "◐◓◑◒"
297
+
298
+ def __init__(
299
+ self,
300
+ call_id: str,
301
+ tool_name: str,
302
+ args_summary: str = "",
303
+ args: dict[str, Any] | None = None,
304
+ ) -> None:
305
+ super().__init__(call_id, tool_name, args_summary, args=args)
306
+ self._spin_idx = 0
307
+ self._file_path = self.args.get("path", "")
308
+ self._before_content = ""
309
+ self._after_content: str | None = None
310
+ self._snapshot_ref: FileSnapshotRef | None = None
311
+ self._diff_pending = False
312
+ self._diff_mounting = False
313
+ self._diff_timer: Timer | None = None
314
+ self._content_release_epoch = 0
315
+ self._timer: Timer | None = None
316
+
317
+ def _label_suffix(self) -> str:
318
+ """Extra text for the label. Override in subclasses."""
319
+ return ""
320
+
321
+ def _label_text(self, duration_ms: int = 0) -> Text:
322
+ t = Text()
323
+ t.append("• ", style="bold")
324
+ t.append(self.tool_name, style="bold")
325
+ if duration_ms:
326
+ t.append(f" ({fmt_duration(duration_ms)})", style="dim")
327
+ if self._file_path:
328
+ t.append(f" {self._file_path}", style="dim")
329
+ suffix = self._label_suffix()
330
+ if suffix:
331
+ t.append(f" {suffix}", style="dim italic")
332
+ return t
333
+
334
+ def compose(self) -> ComposeResult:
335
+ yield ToolCardHeader(self._label_text(), id="ft-label")
336
+ panel = Widget(id="ft-panel")
337
+ panel.border_title = Text(os.path.basename(self._file_path)) if self._file_path else Text(self.tool_name)
338
+ yield panel
339
+
340
+ def on_mount(self) -> None:
341
+ panel = self.query_one("#ft-panel")
342
+ panel.mount(Static(self._render_spinner(), id="ft-spinner"))
343
+ panel.mount(Widget(id="ft-content"))
344
+ self._timer = self.set_interval(0.12, self._spin)
345
+
346
+ def on_unmount(self) -> None:
347
+ if self._timer is not None:
348
+ self._timer.stop()
349
+ if self._diff_timer is not None:
350
+ self._diff_timer.stop()
351
+
352
+ def _spin(self) -> None:
353
+ if self.status == "running":
354
+ self._spin_idx = (self._spin_idx + 1) % len(self._SPINNERS)
355
+ # Only a shown card repaints (see ``ToolCall._spin``); the first tick after it shows paints it.
356
+ if not is_widget_shown_on_active_screen(self):
357
+ return
358
+ with suppress(Exception):
359
+ update_static_in_place(self.query_one("#ft-spinner", Static), self._render_spinner())
360
+
361
+ def _render_spinner(self) -> Text:
362
+ t = Text()
363
+ t.append(f"{self._SPINNERS[self._spin_idx]} ", style="yellow")
364
+ t.append(render_str(widget_localizer(self), _FILE_EDIT_WRITING.bind()), style="yellow")
365
+ return t
366
+
367
+ def _get_after_content(self) -> str:
368
+ """Return the after-mutation file content from snapshot."""
369
+ self._ensure_snapshot_loaded()
370
+ return self._after_content if self._after_content is not None else ""
371
+
372
+ def _has_snapshot_data(self) -> bool:
373
+ """Return true when a text snapshot is available inline or by disk ref."""
374
+ return self._after_content is not None or self._snapshot_ref is not None
375
+
376
+ def _ensure_snapshot_loaded(self) -> None:
377
+ """Load a disk-backed snapshot ref on demand."""
378
+ if self._snapshot_ref is None or self._after_content is not None:
379
+ return
380
+ self._before_content, self._after_content = self._snapshot_ref.resolve()
381
+
382
+ async def _ensure_snapshot_loaded_async(self) -> None:
383
+ """Load a disk-backed snapshot ref without blocking the UI loop."""
384
+ if self._snapshot_ref is None or self._after_content is not None:
385
+ return
386
+ before, after = await asyncio.to_thread(self._snapshot_ref.resolve)
387
+ if self._snapshot_ref is not None and self._after_content is None:
388
+ self._before_content = before
389
+ self._after_content = after
390
+
391
+ async def _copy_after_snapshot_load(self) -> None:
392
+ """Load a disk-backed snapshot before copying tool details."""
393
+ await self._ensure_snapshot_loaded_async()
394
+ self.copy_tool_execution()
395
+
396
+ async def _open_view_after_snapshot_load(self) -> None:
397
+ """Load a disk-backed snapshot before opening the full detail view."""
398
+ await self._ensure_snapshot_loaded_async()
399
+ self.open_tool_view()
400
+
401
+ def _defer_tool_copy(self) -> bool:
402
+ """Schedule copy handling when a disk-backed snapshot must be loaded first."""
403
+ if self._snapshot_ref is not None and self._after_content is None:
404
+ self.run_worker(self._copy_after_snapshot_load(), exclusive=True, group="file-snapshot-actions")
405
+ return True
406
+ return False
407
+
408
+ def _defer_tool_view(self) -> bool:
409
+ """Schedule view handling when a disk-backed snapshot must be loaded first."""
410
+ if self._snapshot_ref is not None and self._after_content is None:
411
+ self.run_worker(self._open_view_after_snapshot_load(), exclusive=True, group="file-snapshot-actions")
412
+ return True
413
+ return False
414
+
415
+ def _tool_copy_full_diff_section(self) -> tuple[str, str, str] | None:
416
+ """Return the full mutation diff for small snapshots."""
417
+ path = self._file_path or self.tool_name
418
+ after = self._get_after_content()
419
+ show_eol_markers = _line_ending_sensitive_change(self._before_content, after)
420
+ diff_lines = unified_diff(
421
+ _copy_diff_source_lines(self._before_content, show_eol_markers=show_eol_markers),
422
+ _copy_diff_source_lines(after, show_eol_markers=show_eol_markers),
423
+ fromfile=f"before/{path}",
424
+ tofile=f"after/{path}",
425
+ lineterm="",
426
+ )
427
+ diff_text = "\n".join(diff_lines)
428
+ if diff_text:
429
+ return (self._render_message(_FILE_EDIT_DIFF.bind()), "diff", diff_text)
430
+ return None
431
+
432
+ def _tool_copy_preview_diff_section(self) -> tuple[str, str, str] | None:
433
+ """Return a bounded real diff around the first changed region."""
434
+ after = self._get_after_content()
435
+ before = self._before_content
436
+ path = self._file_path or self.tool_name
437
+ total_chars = len(before) + len(after)
438
+ changed_index = _first_mismatch_index(before, after)
439
+ if changed_index is None:
440
+ return None
441
+ show_eol_markers = _line_ending_sensitive_change(before, after, changed_index=changed_index)
442
+ before_lines = _bounded_changed_window(
443
+ before,
444
+ max_lines=_FILE_COPY_PREVIEW_LINES,
445
+ max_line_chars=_file_preview.WRITE_FILE_PREVIEW_MAX_LINE_CHARS,
446
+ changed_index=changed_index,
447
+ show_eol_markers=show_eol_markers,
448
+ )
449
+ after_lines = _bounded_changed_window(
450
+ after,
451
+ max_lines=_FILE_COPY_PREVIEW_LINES,
452
+ max_line_chars=_file_preview.WRITE_FILE_PREVIEW_MAX_LINE_CHARS,
453
+ changed_index=changed_index,
454
+ show_eol_markers=show_eol_markers,
455
+ )
456
+ lines = [
457
+ f"--- before/{path}",
458
+ f"+++ after/{path}",
459
+ self._render_message(
460
+ _FILE_EDIT_FULL_DIFF_OMITTED.bind(
461
+ total_chars=total_chars,
462
+ limit_chars=_FILE_COPY_FULL_DIFF_MAX_CHARS,
463
+ )
464
+ ),
465
+ ]
466
+ preview_diff = unified_diff(
467
+ before_lines,
468
+ after_lines,
469
+ lineterm="",
470
+ )
471
+ with suppress(StopIteration):
472
+ next(preview_diff)
473
+ next(preview_diff)
474
+ lines.extend(preview_diff)
475
+ if len(lines) == 3:
476
+ lines.append(self._render_message(_FILE_EDIT_BOUNDED_PREVIEW_UNCHANGED.bind()))
477
+ return (self._render_message(_FILE_EDIT_DIFF_PREVIEW.bind()), "diff", "\n".join(lines))
478
+
479
+ def _tool_copy_diff_section(self) -> tuple[str, str, str] | None:
480
+ """Return the copy diff section, bounded for oversized snapshots."""
481
+ after = self._get_after_content()
482
+ before = self._before_content
483
+ if len(before) + len(after) > _FILE_COPY_FULL_DIFF_MAX_CHARS:
484
+ return self._tool_copy_preview_diff_section()
485
+ return self._tool_copy_full_diff_section()
486
+
487
+ def _tool_copy_sections(self) -> list[tuple[str, str, str]]:
488
+ """Include the mutation diff in copy payloads when snapshot data exists."""
489
+ sections = super()._tool_copy_sections()
490
+ if self.has_class("-rejected") or self._result_is_error():
491
+ return sections
492
+ if not self._has_snapshot_data():
493
+ return sections
494
+
495
+ diff_section = self._tool_copy_diff_section()
496
+ if diff_section is not None:
497
+ sections.append(diff_section)
498
+ return sections
499
+
500
+ def _tool_view_output_widgets(self) -> list[Widget]:
501
+ """Show the full, untruncated mutation diff in the view modal."""
502
+ if self.has_class("-rejected") or self._result_is_error() or not self._has_snapshot_data():
503
+ return super()._tool_view_output_widgets()
504
+ after = self._get_after_content()
505
+ # No content at all (e.g. empty write) — DiffView would render blank.
506
+ if not self._before_content and not after:
507
+ return super()._tool_view_output_widgets()
508
+ # No-op mutation (identical snapshot) — DiffView has no opcodes to show,
509
+ # so fall back to the textual result rather than a blank tab.
510
+ if self._before_content == after:
511
+ return super()._tool_view_output_widgets()
512
+ # Oversized snapshots (e.g. large/minified write_file output, or a big
513
+ # line-ending-only rewrite) would make a full diff run expensive highlight
514
+ # + opcode work and mount thousands of lines. Fall back to the bounded
515
+ # preview diff the copy path already uses for the same threshold. This is
516
+ # checked before the EOL branch so large EOL-only changes are bounded too;
517
+ # _tool_copy_diff_section()'s preview path is itself EOL-aware.
518
+ if len(self._before_content) + len(after) > _FILE_COPY_FULL_DIFF_MAX_CHARS:
519
+ section = self._tool_copy_diff_section()
520
+ if section is not None:
521
+ return build_output_views(
522
+ [section],
523
+ dark=self._view_dark(),
524
+ render_message=self._render_message,
525
+ )
526
+ return super()._tool_view_output_widgets()
527
+ # Line-ending-only changes vanish under DiffView's splitlines(); use the
528
+ # EOL-marked unified diff so the change stays visible.
529
+ if _line_ending_sensitive_change(self._before_content, after):
530
+ section = self._tool_copy_full_diff_section()
531
+ if section is not None:
532
+ return build_output_views(
533
+ [section],
534
+ dark=self._view_dark(),
535
+ render_message=self._render_message,
536
+ )
537
+ return super()._tool_view_output_widgets()
538
+
539
+ return [
540
+ ToolViewDiff(
541
+ self._file_path or self.tool_name,
542
+ self._before_content,
543
+ after,
544
+ render_message=self._render_message,
545
+ )
546
+ ]
547
+
548
+ def _build_diff(self) -> Widget | Static | None:
549
+ """Build the diff widget. Override in subclasses."""
550
+ return None
551
+
552
+ def _is_in_collapsed_tool_group(self) -> bool:
553
+ """Return true when this tool sits inside a collapsed tool group."""
554
+ from chrys.app.tui.widgets.chat.tool_call import ToolGroup
555
+
556
+ return any(isinstance(ancestor, ToolGroup) and ancestor.collapsed for ancestor in self.ancestors)
557
+
558
+ def _has_inline_diff_child(self) -> bool:
559
+ """Return true when the inline content container already has a mounted child.
560
+
561
+ A child that ``release_collapsed_content`` already removed stays in the
562
+ node list until its prune completes; it must not count as mounted or a
563
+ rebuild racing that prune would skip itself and strand the card.
564
+ """
565
+ with suppress(Exception):
566
+ content = self.query_one("#ft-content")
567
+ return any(not getattr(child, "_pruning", False) for child in content.children)
568
+ return False
569
+
570
+ def release_collapsed_content(self) -> None:
571
+ """Drop expensive inline diff widgets while a completed group is collapsed.
572
+
573
+ The raw snapshots remain on this widget, so expanding the group can
574
+ rebuild the inline preview on demand and the detailed view/copy paths
575
+ still have full content.
576
+ """
577
+ if (
578
+ self.status != "complete"
579
+ or self.has_class("-rejected")
580
+ or self._result_is_error()
581
+ or not self._has_snapshot_data()
582
+ ):
583
+ return
584
+ self._diff_pending = True
585
+ self._content_release_epoch += 1
586
+ if self._diff_timer is not None:
587
+ self._diff_timer.stop()
588
+ self._diff_timer = None
589
+ self.add_class("-done")
590
+ with suppress(Exception):
591
+ content = self.query_one("#ft-content")
592
+ for child in list(content.children):
593
+ child.remove()
594
+ if self._snapshot_ref is not None:
595
+ self._before_content = ""
596
+ self._after_content = None
597
+
598
+ def _update_title(self, result: str, *, defer_snapshot: bool = False) -> None:
599
+ """Update panel border title on completion. Override in subclasses."""
600
+
601
+ def set_complete(
602
+ self,
603
+ result: str,
604
+ duration_ms: int = 0,
605
+ *,
606
+ file_snapshot: FileSnapshotPayload | None = None,
607
+ lazy: bool = False,
608
+ **kwargs: Any,
609
+ ) -> None:
610
+ """Mark as complete and mount diff view.
611
+
612
+ *file_snapshot* supplies ``(before, after)`` content from the
613
+ mutation tracking system (both live and replay paths).
614
+
615
+ If *lazy* is True, defer diff construction entirely until
616
+ ``mount_diff_if_pending()`` is called (used during replay when the
617
+ tool group is collapsed).
618
+ """
619
+ if file_snapshot is not None:
620
+ if isinstance(file_snapshot, FileSnapshotRef):
621
+ self._snapshot_ref = file_snapshot
622
+ self._before_content = ""
623
+ self._after_content = None
624
+ else:
625
+ self._snapshot_ref = None
626
+ self._before_content = file_snapshot[0]
627
+ self._after_content = file_snapshot[1]
628
+ self.result_text = result
629
+ self.duration_ms = duration_ms
630
+ self.status = "complete"
631
+ metadata = kwargs.get("metadata")
632
+ self.metadata = metadata if isinstance(metadata, dict) else {}
633
+ if self._timer is not None:
634
+ self._timer.stop()
635
+
636
+ # Update label
637
+ with suppress(Exception):
638
+ self.query_one("#ft-label", Static).update(self._label_text(duration_ms))
639
+
640
+ self.approval = kwargs.get("approval")
641
+ render_status = self._result_render_status()
642
+ self.status = render_status
643
+ if render_status == "rejected":
644
+ self.add_class("-rejected")
645
+ panel = self.query_one("#ft-panel")
646
+ panel.border_title = Text(os.path.basename(self._file_path))
647
+ panel.border_subtitle = render_text(widget_localizer(self), TOOL_CARD_REJECTED.bind())
648
+ with suppress(Exception):
649
+ content = self.query_one("#ft-content")
650
+ content.mount(Static(Text(result)))
651
+ self.add_class("-done")
652
+ self._show_tool_copy_button()
653
+ elif render_status == "error":
654
+ self.add_class("-error")
655
+ panel = self.query_one("#ft-panel")
656
+ panel.border_title = Text(os.path.basename(self._file_path))
657
+ panel.border_subtitle = render_text(widget_localizer(self), _FILE_EDIT_ERROR.bind())
658
+ with suppress(Exception):
659
+ content = self.query_one("#ft-content")
660
+ content.mount(Static(Text(result)))
661
+ self.add_class("-done")
662
+ self._show_tool_copy_button()
663
+ else:
664
+ self.add_class("-success")
665
+ defer_snapshot = isinstance(file_snapshot, FileSnapshotRef)
666
+ self._update_title(result, defer_snapshot=defer_snapshot)
667
+ self._show_tool_copy_button()
668
+ if lazy or defer_snapshot:
669
+ # Store flag — diff will be built on first expand
670
+ self._diff_pending = True
671
+ else:
672
+ # Defer diff mounting — syntax highlighting is expensive and
673
+ # blocks the event loop. A short timer lets fast-completing
674
+ # tool groups collapse first, avoiding work for historical
675
+ # diffs that are no longer visible.
676
+ self._diff_timer = self.set_timer(_INLINE_DIFF_MOUNT_DELAY_SECONDS, self._schedule_mount_diff)
677
+
678
+ async def mount_diff_if_pending(self) -> bool:
679
+ """Build and mount the diff if it was deferred (lazy mode).
680
+
681
+ Called when the parent ToolGroup is first expanded after replay.
682
+ """
683
+ if not self._diff_pending or self.has_class("-rejected") or self._result_is_error():
684
+ return False
685
+ return await self._mount_diff_async(post_absorb=False)
686
+
687
+ def _result_render_status(self) -> str:
688
+ return tool_result_render_status(
689
+ self.result_text,
690
+ self.approval,
691
+ self.tool_kind or KIND_FILESYSTEM_WRITE,
692
+ self.metadata,
693
+ )
694
+
695
+ def _result_is_error(self) -> bool:
696
+ return self._result_render_status() == "error"
697
+
698
+ async def mount_pending_content(self) -> bool:
699
+ """Build and mount deferred inline content after the parent group expands."""
700
+ return await self.mount_diff_if_pending()
701
+
702
+ def _schedule_mount_diff(self) -> None:
703
+ """Schedule async diff mounting after the short collapse grace period."""
704
+ self._diff_timer = None
705
+ if not self.is_attached:
706
+ self._diff_pending = True
707
+ return
708
+ self.call_later(self._mount_live_diff_async)
709
+
710
+ async def _mount_live_diff_async(self) -> None:
711
+ """Mount a deferred live diff and request absorb after prewarming."""
712
+ await self._mount_diff_async(post_absorb=True)
713
+
714
+ async def _mount_diff_async(self, *, post_absorb: bool) -> bool:
715
+ """Build and mount the diff widget, computing highlights off-thread."""
716
+ if not self.is_attached:
717
+ self._diff_pending = True
718
+ self.add_class("-done")
719
+ return False
720
+ self._diff_timer = None
721
+ if self._diff_mounting:
722
+ return False
723
+ if self._has_inline_diff_child():
724
+ self._diff_pending = False
725
+ self.add_class("-done")
726
+ return False
727
+ if self._is_in_collapsed_tool_group():
728
+ self._diff_pending = True
729
+ self.add_class("-done")
730
+ return False
731
+ self._diff_mounting = True
732
+ release_epoch = self._content_release_epoch
733
+ mounted = False
734
+ mount_error: Exception | None = None
735
+ try:
736
+ await self._ensure_snapshot_loaded_async()
737
+ if self._is_in_collapsed_tool_group():
738
+ self._diff_pending = True
739
+ return False
740
+ diff_widget = self._build_diff()
741
+ if diff_widget is not None:
742
+ from chrys.app.tui.widgets.diff_view.inline import InlineUnifiedDiffLines
743
+ from chrys.app.tui.widgets.diff_view.protocols import SupportsPrepare
744
+
745
+ # Pre-compute Pygments highlighting + SequenceMatcher in a
746
+ # worker thread so compose() finds cached results instantly.
747
+ if isinstance(diff_widget, SupportsPrepare):
748
+ try:
749
+ await diff_widget.prepare()
750
+ except Exception:
751
+ diff_widget = Static(Text(self.result_text))
752
+ if self._is_in_collapsed_tool_group():
753
+ self._diff_pending = True
754
+ return False
755
+ if self._has_inline_diff_child():
756
+ self._diff_pending = False
757
+ return False
758
+ try:
759
+ content = self.query_one("#ft-content")
760
+ await content.mount(diff_widget)
761
+ mounted = True
762
+ if isinstance(diff_widget, InlineUnifiedDiffLines):
763
+ if post_absorb:
764
+
765
+ def prewarm_and_request_absorb() -> None:
766
+ diff_widget.prewarm_visible_rows()
767
+ if (
768
+ self.is_attached
769
+ and diff_widget.is_attached
770
+ and not self._is_in_collapsed_tool_group()
771
+ ):
772
+ self.post_message(GcAbsorbRequested(GcAbsorbReason.STABLE_CONTENT_MOUNTED))
773
+
774
+ self.call_after_refresh(prewarm_and_request_absorb)
775
+ else:
776
+ self.call_after_refresh(diff_widget.prewarm_visible_rows)
777
+ elif post_absorb:
778
+ self.post_message(GcAbsorbRequested(GcAbsorbReason.STABLE_CONTENT_MOUNTED))
779
+ except Exception as exc:
780
+ mount_error = exc
781
+ if release_epoch != self._content_release_epoch:
782
+ # The group collapsed while the mount was in flight and
783
+ # ``release_collapsed_content`` already dropped the widget we
784
+ # just attached. Keep the diff deferred so the next expand
785
+ # rebuilds it, and rebuild right away if the group is already
786
+ # expanded again (the expand pass saw us mid-mount and skipped).
787
+ self._diff_pending = True
788
+ if not self._is_in_collapsed_tool_group():
789
+ self.call_later(self._mount_diff_async, post_absorb=post_absorb)
790
+ return False
791
+ if diff_widget is not None and not mounted:
792
+ # The mount itself failed. Keep the diff deferred so the next
793
+ # expand (or explicit mount request) retries instead of leaving
794
+ # the card without inline content and nothing left to rebuild.
795
+ logger.warning(
796
+ "Inline diff mount failed for %s; deferring the rebuild to the next expand.",
797
+ self.call_id,
798
+ exc_info=mount_error,
799
+ )
800
+ self._diff_pending = True
801
+ return False
802
+ self._diff_pending = False
803
+ return mounted
804
+ finally:
805
+ self._diff_mounting = False
806
+ self.add_class("-done")
807
+
808
+ def set_error(self, error: str) -> None:
809
+ """Mark as failed."""
810
+ self.result_text = error
811
+ self.status = "error"
812
+ if self._timer is not None:
813
+ self._timer.stop()
814
+
815
+ panel = self.query_one("#ft-panel")
816
+ panel.border_title = Text(os.path.basename(self._file_path))
817
+ panel.border_subtitle = render_text(widget_localizer(self), _FILE_EDIT_ERROR.bind())
818
+ self.add_class("-error")
819
+ with suppress(Exception):
820
+ content = self.query_one("#ft-content")
821
+ content.mount(Static(Text(error)))
822
+ self.add_class("-done")
823
+ self._show_tool_copy_button()
824
+
825
+
826
+ # ---------------------------------------------------------------------------
827
+ # EditFileToolCall — split diff view
828
+ # ---------------------------------------------------------------------------
829
+
830
+
831
+ class EditFileToolCall(_FileToolCall):
832
+ """Renderer for ``edit_file`` — full-file split DiffView."""
833
+
834
+ def _render_spinner(self) -> Text:
835
+ t = Text()
836
+ t.append(f"{self._SPINNERS[self._spin_idx]} ", style="yellow")
837
+ t.append(render_str(widget_localizer(self), _FILE_EDIT_EDITING.bind()), style="yellow")
838
+ return t
839
+
840
+ def _build_diff(self) -> Widget | Static | None:
841
+ from chrys.app.tui.widgets.diff_view.inline import InlineUnifiedDiffLines
842
+
843
+ after = self._get_after_content()
844
+ if not self._before_content and not after:
845
+ return Static(Text(self.result_text))
846
+ path = self._file_path
847
+ return InlineUnifiedDiffLines(
848
+ path,
849
+ path,
850
+ self._before_content,
851
+ after,
852
+ auto_height=True,
853
+ max_display_lines=_EDIT_FILE_INLINE_MAX_DISPLAY_LINES,
854
+ )
855
+
856
+ def _update_title(self, result: str, *, defer_snapshot: bool = False) -> None:
857
+ panel = self.query_one("#ft-panel")
858
+ panel.border_title = Text(os.path.basename(self._file_path))
859
+ if defer_snapshot:
860
+ panel.border_subtitle = render_text(widget_localizer(self), _FILE_EDIT_CHANGES.bind())
861
+ return
862
+ # Compute counts cheaply via difflib without creating a full DiffView.
863
+ # The DiffView for display is built separately in _build_diff().
864
+ import difflib
865
+
866
+ after = self._get_after_content()
867
+ before_lines = self._before_content.splitlines()
868
+ after_lines = after.splitlines()
869
+ sm = difflib.SequenceMatcher(lambda c: c in {" ", "\t"}, before_lines, after_lines, autojunk=True)
870
+ additions = 0
871
+ removals = 0
872
+ for tag, i1, i2, j1, j2 in sm.get_opcodes():
873
+ if tag == "delete":
874
+ removals += i2 - i1
875
+ elif tag == "replace":
876
+ additions += j2 - j1
877
+ removals += i2 - i1
878
+ elif tag == "insert":
879
+ additions += j2 - j1
880
+ panel.border_subtitle = render_text(
881
+ widget_localizer(self),
882
+ _FILE_EDIT_CHANGE_COUNTS.bind(additions=additions, removals=removals),
883
+ )
884
+
885
+
886
+ # ---------------------------------------------------------------------------
887
+ # WriteFileToolCall — unified diff view
888
+ # ---------------------------------------------------------------------------
889
+
890
+
891
+ class WriteFileToolCall(_FileToolCall):
892
+ """Renderer for ``write_file`` — unified DiffView showing new content."""
893
+
894
+ def _label_suffix(self) -> str:
895
+ overwrite = self.args.get("overwrite", False)
896
+ return render_str(widget_localizer(self), _FILE_EDIT_OVERWRITE.bind()) if overwrite else ""
897
+
898
+ _PREVIEW_LINES = 10
899
+
900
+ def _build_diff(self) -> Widget | Static | None:
901
+ from chrys.app.tui.widgets.diff_view.inline import InlineUnifiedDiffLines
902
+
903
+ after = self._get_after_content()
904
+ if not after:
905
+ return Static(Text(self.result_text)) if self.result_text else None
906
+ preview_text = _file_preview.write_file_preview_text(
907
+ after,
908
+ max_lines=self._PREVIEW_LINES,
909
+ max_line_chars=_file_preview.WRITE_FILE_PREVIEW_MAX_LINE_CHARS,
910
+ )
911
+ path = self._file_path
912
+ return InlineUnifiedDiffLines(path, path, "", preview_text, max_display_lines=10)
913
+
914
+ def _update_title(self, result: str, *, defer_snapshot: bool = False) -> None:
915
+ panel = self.query_one("#ft-panel")
916
+ panel.border_title = Text(os.path.basename(self._file_path))
917
+ if defer_snapshot and _WRITE_FILE_RESULT_LINES_RE.search(result) is None:
918
+ total_lines = "?"
919
+ else:
920
+ after = "" if defer_snapshot else self._get_after_content()
921
+ total_lines = _write_file_line_count(result, after)
922
+ panel.border_subtitle = render_text(
923
+ widget_localizer(self),
924
+ _FILE_EDIT_LINES_WRITTEN.bind(lines=total_lines),
925
+ )