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,1039 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Chat message widgets and inline conversation status actions."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import contextlib
8
+ import re
9
+ from collections.abc import Callable
10
+ from dataclasses import dataclass
11
+ from datetime import UTC
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from rich.console import Group
15
+ from rich.text import Text
16
+ from textual.containers import Horizontal
17
+ from textual.geometry import Region
18
+ from textual.message import Message
19
+ from textual.reactive import reactive
20
+ from textual.selection import Selection
21
+ from textual.strip import Strip
22
+ from textual.widget import Widget
23
+ from textual.widgets import Button, Static
24
+
25
+ from chrys.app.tui.clipboard import copy_text_to_clipboards
26
+ from chrys.app.tui.copy_messages import COPIED_TITLE
27
+ from chrys.app.tui.i18n import render_str, widget_localizer
28
+ from chrys.app.tui.util.source_text import sanitize_source_text
29
+ from chrys.app.tui.widgets.chat.image_preview import ChatImagePreview, ImagePreviewGrid, extract_image_previews
30
+ from chrys.app.tui.widgets.click_affordance import ClickAffordance
31
+ from chrys.app.tui.widgets.markdown import VirtualizedMarkdown
32
+ from chrys.app.tui.widgets.markdown.parser import create_user_text_markdown_parser
33
+ from chrys.foundation.i18n import DisplayBlock, MessageRef, msg
34
+ from chrys.foundation.i18n.formatting import format_message
35
+ from chrys.foundation.util.time import parse_created_at
36
+
37
+ if TYPE_CHECKING:
38
+ from textual.app import ComposeResult
39
+
40
+
41
+ _THINK_RE = re.compile(r"<think>(.*?)</think>", re.DOTALL | re.IGNORECASE)
42
+
43
+ INTERRUPTED_ERROR_HEADER_MESSAGE = msg(
44
+ "transcript.interrupted.error_header",
45
+ fallback="✗ Error",
46
+ )
47
+ INTERRUPTED_WARNING_HEADER_MESSAGE = msg(
48
+ "transcript.interrupted.warning_header",
49
+ fallback="⚠ Interrupted",
50
+ )
51
+ INTERRUPTED_REASON_PLAIN_MESSAGE = msg(
52
+ "transcript.interrupted.reason_plain",
53
+ fallback="{reason}",
54
+ multiline=True,
55
+ )
56
+ INTERRUPTED_REASON_BY_USER_MESSAGE = msg(
57
+ "transcript.interrupted.reason_by_user",
58
+ fallback="{reason} by user",
59
+ multiline=True,
60
+ )
61
+ INTERRUPTED_RETRY_ACTION_MESSAGE = msg(
62
+ "transcript.interrupted.retry_action",
63
+ fallback="Retry",
64
+ )
65
+ INTERRUPTED_CONTINUE_ACTION_MESSAGE = msg(
66
+ "transcript.interrupted.continue_action",
67
+ fallback="Continue",
68
+ )
69
+ _AGENT_RESPONSE_COPIED = msg("tui.copy.agent_response", fallback="Copied agent response")
70
+ _THINK_PREFIX = msg("tui.chat.think_prefix", fallback="Think: {body}", multiline=True)
71
+ _AGENT_RESPONSE_COPY_TOOLTIP = msg(
72
+ "tui.chat.copy_agent_response_tooltip",
73
+ fallback="Copy raw agent response",
74
+ )
75
+ COPY_MESSAGE_BUTTON = msg("tui.chat.copy_message_button", fallback="copy")
76
+ _USER_MESSAGE_COPIED = msg("tui.copy.user_message", fallback="Copied your message")
77
+ _USER_MESSAGE_COPY_TOOLTIP = msg("tui.chat.copy_user_message_tooltip", fallback="Copy your raw message")
78
+ _AGENT_FALLBACK_LABEL = msg("tui.chat.agent_fallback_label", fallback="Agent")
79
+ _RETRY_MESSAGE = msg(
80
+ "tui.chat.retry_message",
81
+ fallback="{message} Retrying in {delay_seconds}s ({attempt}/{max_attempts})...",
82
+ multiline=True,
83
+ )
84
+
85
+
86
+ def _format_duration_ms(duration_ms: int) -> str:
87
+ from chrys.app.tui.widgets.chat.tool_call import fmt_duration
88
+
89
+ return fmt_duration(duration_ms)
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class InterruptedMessageCopy:
94
+ """Resolved, mount-stable copy for an interruption notice."""
95
+
96
+ header: str
97
+ reason: str
98
+ retry_action: str
99
+ continue_action: str
100
+
101
+
102
+ def resolve_interrupted_message_copy(
103
+ reason: str,
104
+ source: str,
105
+ resolver: Callable[[MessageRef], str] = format_message,
106
+ ) -> InterruptedMessageCopy:
107
+ """Resolve interruption chrome and its closed-set source prose."""
108
+ header_definition = INTERRUPTED_ERROR_HEADER_MESSAGE if source == "error" else INTERRUPTED_WARNING_HEADER_MESSAGE
109
+ reason_definition = INTERRUPTED_REASON_BY_USER_MESSAGE if source == "user" else INTERRUPTED_REASON_PLAIN_MESSAGE
110
+ return InterruptedMessageCopy(
111
+ header=resolver(header_definition.bind()),
112
+ reason=resolver(reason_definition.bind(reason=DisplayBlock(reason))),
113
+ retry_action=resolver(INTERRUPTED_RETRY_ACTION_MESSAGE.bind()),
114
+ continue_action=resolver(INTERRUPTED_CONTINUE_ACTION_MESSAGE.bind()),
115
+ )
116
+
117
+
118
+ def format_message_created_at(value: Any) -> str:
119
+ """Format a persisted message timestamp in the user's local time."""
120
+ dt = parse_created_at(value)
121
+ if dt is None:
122
+ return ""
123
+
124
+ if dt.tzinfo is None or dt.utcoffset() is None:
125
+ dt = dt.replace(tzinfo=UTC)
126
+ local = dt.astimezone()
127
+ hour = local.hour % 12 or 12
128
+ suffix = "AM" if local.hour < 12 else "PM"
129
+ return f"- {hour}:{local.minute:02d} {suffix}"
130
+
131
+
132
+ def strip_think(text: str) -> str:
133
+ """Remove ``<think>…</think>`` blocks entirely."""
134
+ return _THINK_RE.sub("", text).strip()
135
+
136
+
137
+ def think_to_italic(
138
+ text: str,
139
+ render_message: Callable[[MessageRef], str] = format_message,
140
+ ) -> str:
141
+ """Convert ``<think>…</think>`` blocks to italic markdown with a *Think:* prefix."""
142
+
143
+ def _repl(m: re.Match[str]) -> str:
144
+ inner = m.group(1).strip()
145
+ if not inner:
146
+ return ""
147
+ # Wrap each paragraph with * for italic. A single * pair around a
148
+ # multi-paragraph block breaks in CommonMark, so wrap per paragraph.
149
+ parts: list[str] = []
150
+ for para in re.split(r"\n{2,}", inner):
151
+ stripped = para.strip()
152
+ if stripped:
153
+ parts.append(f"*{stripped}*")
154
+ body = "\n\n".join(parts)
155
+ return render_message(_THINK_PREFIX.bind(body=DisplayBlock(body)))
156
+
157
+ return _THINK_RE.sub(_repl, text).strip()
158
+
159
+
160
+ def process_think_tags(
161
+ text: str,
162
+ *,
163
+ intermediate: bool = False,
164
+ render_message: Callable[[MessageRef], str] = format_message,
165
+ ) -> str:
166
+ """Process ``<think>`` tags: italic for intermediate messages, strip for final."""
167
+ if not text:
168
+ return text
169
+ return think_to_italic(text, render_message) if intermediate else strip_think(text)
170
+
171
+
172
+ def format_chat_copy_payload(messages: list[tuple[str, str]]) -> str:
173
+ """Return the transcript payload used by chat copy actions."""
174
+ return "\n\n".join(f"[{name}]\n{text}" for name, text in messages)
175
+
176
+
177
+ def _user_header_text(*, timestamp: str, is_injection: bool) -> Text:
178
+ """The user message header: the ``You`` label and its timestamp."""
179
+ header = Text()
180
+ if is_injection:
181
+ header.append("└ ", style="dim")
182
+ header.append("\u276f You", style="bold cyan")
183
+ if timestamp:
184
+ header.append(f" {timestamp}", style="dim")
185
+ return header
186
+
187
+
188
+ class _UserHeader(Static):
189
+ """Header of a user message."""
190
+
191
+ def __init__(self, content: Text) -> None:
192
+ super().__init__(content, classes="message-header")
193
+
194
+ def get_selection(self, selection: Selection) -> tuple[str, str] | None:
195
+ """Copy the user header as a transcript label, not decorative chrome."""
196
+ _ = selection
197
+ return "[You]", "\n"
198
+
199
+
200
+ class _UserImagePreview(Static):
201
+ """Non-selectable image-preview renderer for a user message."""
202
+
203
+ ALLOW_SELECT = False
204
+
205
+ DEFAULT_CSS = """
206
+ _UserImagePreview {
207
+ height: auto;
208
+ background: transparent;
209
+ }
210
+ """
211
+
212
+ def __init__(self, image_previews: list[ChatImagePreview], *, is_injection: bool) -> None:
213
+ self._image_previews = image_previews
214
+ self._is_injection = is_injection
215
+ self._image_preview_lines: dict[tuple[int, str], list[Text]] = {}
216
+ super().__init__()
217
+
218
+ def render(self) -> Group:
219
+ max_width = self.content_size.width or self.size.width or 80
220
+ indent = " " if self._is_injection else ""
221
+ return Group(Text(""), *self._render_image_preview_lines(max_width=max_width, indent=indent))
222
+
223
+ def _render_image_preview_lines(self, *, max_width: int, indent: str) -> list[Text]:
224
+ """Return cached image-preview render lines for the current message width."""
225
+ key = (max_width, indent)
226
+ lines = self._image_preview_lines.get(key)
227
+ if lines is None:
228
+ grid = ImagePreviewGrid(
229
+ self._image_previews,
230
+ max_width=max_width,
231
+ indent=indent,
232
+ )
233
+ lines = grid.render_lines()
234
+ self._image_preview_lines[key] = lines
235
+ return lines
236
+
237
+ def on_resize(self, _event: Any = None) -> None:
238
+ """Refresh image previews when terminal width changes."""
239
+ self.refresh()
240
+
241
+ def get_selection(self, selection: Selection) -> tuple[str, str] | None:
242
+ _ = selection
243
+ return None
244
+
245
+
246
+ class MessageHeaderRow(Horizontal):
247
+ """Header row of a chat message: its label, then inline actions."""
248
+
249
+ ALLOW_SELECT = False
250
+
251
+ DEFAULT_CSS = """
252
+ MessageHeaderRow {
253
+ width: 100%;
254
+ height: 1;
255
+ }
256
+ MessageHeaderRow > .message-header {
257
+ width: auto;
258
+ height: 1;
259
+ padding: 0 1 0 0;
260
+ }
261
+ MessageHeaderRow > MessageCopyButton {
262
+ display: none;
263
+ width: auto;
264
+ height: 1;
265
+ padding: 0 0 0 1;
266
+ color: $text-muted;
267
+ text-style: dim not bold;
268
+ pointer: pointer;
269
+ }
270
+ MessageHeaderRow > MessageCopyButton:hover {
271
+ color: $accent;
272
+ text-style: underline;
273
+ }
274
+ """
275
+
276
+ def get_selection(self, selection: Selection) -> tuple[str, str] | None:
277
+ _ = selection
278
+ return None
279
+
280
+
281
+ class MessageCopyButton(ClickAffordance):
282
+ """Clickable copy affordance in a chat message header; copies the raw text."""
283
+
284
+ ALLOW_SELECT = False
285
+
286
+ class Clicked(Message):
287
+ """Posted when the copy affordance is clicked."""
288
+
289
+ CLICK_MESSAGE = Clicked
290
+
291
+ def __init__(self, *, tooltip: str, text: str | None = None) -> None:
292
+ super().__init__(Text(text or format_message(COPY_MESSAGE_BUTTON.bind())))
293
+ self.tooltip = tooltip
294
+
295
+ def get_selection(self, selection: Selection) -> tuple[str, str] | None:
296
+ _ = selection
297
+ return None
298
+
299
+
300
+ class UserMessage(Widget):
301
+ """User message block with cyan accent, rendered as markdown.
302
+
303
+ Newlines stay line breaks and HTML tags stay text, as the user typed them.
304
+ """
305
+
306
+ DEFAULT_CSS = """
307
+ UserMessage {
308
+ margin: 1 0;
309
+ padding: 0 1 0 1;
310
+ border-left: thick $tui-border-user-message $border-opacity;
311
+ background: $boost;
312
+ height: auto;
313
+ }
314
+ UserMessage.-compressed {
315
+ border-left: thick $tui-border-neutral-160 $border-opacity;
316
+ color: $text-muted;
317
+ }
318
+ UserMessage.-compressed > VirtualizedMarkdown {
319
+ color: $text-muted;
320
+ }
321
+ /* After -compressed, so a selected turn shows its selection even when compressed. */
322
+ UserMessage.-highlighted {
323
+ background: $primary 18%;
324
+ border-left: thick $tui-border-selected-turn $border-opacity;
325
+ }
326
+ UserMessage.-highlighted:ansi {
327
+ background: $tui-ansi-turn-background;
328
+ border-left: thick $tui-border-neutral-128;
329
+ }
330
+ /* An injected note never has a left border, selected or not: the :ansi twin
331
+ ties the selected rule's specificity and wins by coming after it. */
332
+ UserMessage.-injection {
333
+ margin-left: 2;
334
+ margin-top: 0;
335
+ border-left: none;
336
+ }
337
+ UserMessage.-injection:ansi {
338
+ border-left: none;
339
+ }
340
+ UserMessage > VirtualizedMarkdown {
341
+ padding: 0;
342
+ height: auto;
343
+ min-height: 1;
344
+ background: transparent;
345
+ }
346
+ UserMessage.-injection > VirtualizedMarkdown {
347
+ padding: 0 0 0 2;
348
+ }
349
+ """
350
+
351
+ def __init__(
352
+ self,
353
+ text: str,
354
+ timestamp: str = "",
355
+ compressed: bool = False,
356
+ is_injection: bool = False,
357
+ contents: list[Any] | None = None,
358
+ ) -> None:
359
+ self._text = text
360
+ self._ts = timestamp
361
+ self._compressed = compressed
362
+ self._is_injection = is_injection
363
+ self._image_previews = extract_image_previews(contents)
364
+ super().__init__()
365
+ if compressed:
366
+ self.add_class("-compressed")
367
+ if is_injection:
368
+ self.add_class("-injection")
369
+
370
+ def compose(self) -> ComposeResult:
371
+ with MessageHeaderRow():
372
+ yield _UserHeader(_user_header_text(timestamp=self._ts, is_injection=self._is_injection))
373
+ copy_button = MessageCopyButton(
374
+ tooltip=self._render_message(_USER_MESSAGE_COPY_TOOLTIP.bind()),
375
+ text=self._render_message(COPY_MESSAGE_BUTTON.bind()),
376
+ )
377
+ copy_button.display = self._should_show_copy_button()
378
+ yield copy_button
379
+ yield VirtualizedMarkdown(
380
+ sanitize_source_text(self._text),
381
+ parser_factory=create_user_text_markdown_parser,
382
+ )
383
+ if self._image_previews:
384
+ yield _UserImagePreview(self._image_previews, is_injection=self._is_injection)
385
+
386
+ @property
387
+ def text(self) -> str:
388
+ """The message as the user wrote it."""
389
+ return self._text
390
+
391
+ @property
392
+ def is_injection(self) -> bool:
393
+ """Whether this user message is a mid-turn injection."""
394
+ return self._is_injection
395
+
396
+ def get_selection(self, selection: Selection) -> tuple[str, str] | None:
397
+ """User message content is copied by its header/body children."""
398
+ _ = selection
399
+ return None
400
+
401
+ def _render_message(self, reference: MessageRef) -> str:
402
+ return render_str(widget_localizer(self), reference)
403
+
404
+ def _should_show_copy_button(self) -> bool:
405
+ """Offer copy for text outside compressed history, as agent responses do."""
406
+ return not self._compressed and bool(self._text.strip())
407
+
408
+ def copy_user_message(self) -> None:
409
+ """Copy the message as the user wrote it to the available clipboards."""
410
+ if not self._text.strip():
411
+ return
412
+ copy_text_to_clipboards(self.app, self._text)
413
+ localizer = widget_localizer(self)
414
+ self.notify(
415
+ render_str(localizer, _USER_MESSAGE_COPIED.bind()),
416
+ title=render_str(localizer, COPIED_TITLE.bind()),
417
+ timeout=2,
418
+ markup=False,
419
+ )
420
+
421
+ def on_message_copy_button_clicked(self, event: MessageCopyButton.Clicked) -> None:
422
+ """Handle clicks from the header copy affordance."""
423
+ event.stop()
424
+ if not self._should_show_copy_button():
425
+ return
426
+ self.copy_user_message()
427
+
428
+
429
+ class _AgentHeader(Static):
430
+ """Clickable header for AgentMessage — posts Clicked on click."""
431
+
432
+ class Clicked(Message):
433
+ """Posted when the header is clicked."""
434
+
435
+ def __init__(self, label: str, content: Text) -> None:
436
+ self._copy_label = label
437
+ super().__init__(content, classes="agent-header message-header")
438
+
439
+ def get_selection(self, selection: Selection) -> tuple[str, str] | None:
440
+ """Copy the agent header as a transcript label, not decorative chrome."""
441
+ return f"[{self._copy_label}]", "\n"
442
+
443
+ def on_click(self) -> None:
444
+ self.post_message(self.Clicked())
445
+
446
+
447
+ class AgentMessage(Widget):
448
+ """Agent message with markdown rendering and streaming support.
449
+
450
+ Uses VirtualizedMarkdown for rich rendering of agent responses including
451
+ code blocks, tables, lists, and other markdown elements.
452
+ Click the header to collapse/expand the message body.
453
+ """
454
+
455
+ DEFAULT_CSS = """
456
+ AgentMessage {
457
+ margin: 1 0;
458
+ padding: 0 0 0 1;
459
+ border-left: thick $tui-border-agent-message $border-opacity;
460
+ height: auto;
461
+ }
462
+ AgentMessage.--intermediate {
463
+ border-left: thick $tui-border-neutral-gray $border-opacity;
464
+ color: $text-muted;
465
+ }
466
+ AgentMessage.--intermediate > VirtualizedMarkdown {
467
+ color: $text-muted;
468
+ }
469
+ AgentMessage.--compressed {
470
+ border-left: thick $tui-border-neutral-100 $border-opacity;
471
+ color: $text-muted;
472
+ }
473
+ AgentMessage.--compressed > VirtualizedMarkdown {
474
+ color: $text-muted;
475
+ }
476
+ AgentMessage.--structured-completion > VirtualizedMarkdown {
477
+ color: $success;
478
+ }
479
+ AgentMessage > VirtualizedMarkdown {
480
+ padding: 0;
481
+ height: auto;
482
+ min-height: 1;
483
+ background: transparent;
484
+ }
485
+ AgentMessage > .agent-cursor {
486
+ height: 1;
487
+ padding: 0 1;
488
+ }
489
+ """
490
+
491
+ collapsed: reactive[bool] = reactive(False)
492
+
493
+ def __init__(
494
+ self,
495
+ text: str = "",
496
+ is_final: bool = True,
497
+ profile_name: str = "",
498
+ is_intermediate: bool = False,
499
+ is_compressed: bool = False,
500
+ is_structured_completion: bool = False,
501
+ timestamp: str = "",
502
+ duration_ms: int | None = None,
503
+ ) -> None:
504
+ self._source_text = text
505
+ self._processed_text = process_think_tags(text, intermediate=is_intermediate)
506
+ self._processed_text_stale = False
507
+ """``_source_text`` changed since ``_processed_text`` was derived; ``text`` re-derives it."""
508
+ self._body_stale = False
509
+ """The markdown body has not been handed the latest streamed text."""
510
+ self._body_sync_scheduled = False
511
+ """A callback that brings the markdown body up to date is queued on this widget."""
512
+ self._is_final = is_final
513
+ self._profile_name = profile_name
514
+ self._is_intermediate = is_intermediate
515
+ self._is_compressed = is_compressed
516
+ self._is_structured_completion = is_structured_completion
517
+ self._ts = timestamp
518
+ self._duration_ms = duration_ms
519
+ self._md_widget: VirtualizedMarkdown | None = None
520
+ self._background_strip_cache_key: tuple[int, int, int, str, str, int] | None = None
521
+ self._background_strip_cache: Strip | None = None
522
+ cls = ""
523
+ if is_intermediate:
524
+ cls = "--intermediate"
525
+ elif is_compressed:
526
+ cls = "--compressed"
527
+ elif is_structured_completion:
528
+ cls = "--structured-completion"
529
+ super().__init__(classes=cls)
530
+
531
+ @property
532
+ def text(self) -> str:
533
+ """The response text with think blocks processed, as of the latest ``stream_update()``."""
534
+ if self._processed_text_stale:
535
+ self._processed_text = process_think_tags(self._source_text)
536
+ self._processed_text_stale = False
537
+ return self._processed_text
538
+
539
+ @property
540
+ def profile_name(self) -> str:
541
+ """The name of the agent that wrote this message; empty when none was given."""
542
+ return self._profile_name
543
+
544
+ def _header_text(self) -> Text:
545
+ label = self._copy_label()
546
+ arrow = "\u25b6" if self.collapsed else "\u25c7"
547
+ t = Text.assemble((f"{arrow} {label}", "bold green"))
548
+ if self._ts:
549
+ t.append(f" {self._ts}", style="dim")
550
+ if self._duration_ms is not None:
551
+ t.append(f" ({_format_duration_ms(self._duration_ms)})", style="dim")
552
+ return t
553
+
554
+ def _copy_label(self) -> str:
555
+ return self._profile_name or self._render_message(_AGENT_FALLBACK_LABEL.bind())
556
+
557
+ def _render_message(self, reference: MessageRef) -> str:
558
+ return render_str(widget_localizer(self), reference)
559
+
560
+ def compose(self) -> ComposeResult:
561
+ if self._is_intermediate:
562
+ self._processed_text = process_think_tags(
563
+ self._source_text,
564
+ intermediate=True,
565
+ render_message=self._render_message,
566
+ )
567
+ self._processed_text_stale = False
568
+ with MessageHeaderRow():
569
+ yield _AgentHeader(self._copy_label(), self._header_text())
570
+ copy_button = MessageCopyButton(
571
+ tooltip=self._render_message(_AGENT_RESPONSE_COPY_TOOLTIP.bind()),
572
+ text=self._render_message(COPY_MESSAGE_BUTTON.bind()),
573
+ )
574
+ copy_button.display = self._should_show_copy_button()
575
+ yield copy_button
576
+ self._md_widget = VirtualizedMarkdown(self.text)
577
+ # The body starts from the current text, so a queued sync has nothing to hand it.
578
+ self._body_stale = False
579
+ yield self._md_widget
580
+ if not self._is_final:
581
+ yield Static(Text(" \u258d", style="bold green"), classes="agent-cursor")
582
+
583
+ def get_selection(self, selection: Selection) -> tuple[str, str] | None:
584
+ """Agent content is copied by its header/body children."""
585
+ return None
586
+
587
+ def render_lines(self, crop: Region) -> list[Strip]:
588
+ """Render the repeated message chrome without per-row style work.
589
+
590
+ AgentMessage only paints a left border, padding, and transparent
591
+ background; child widgets render the header/body text. Every row of
592
+ that chrome is identical, so a clipped parent scroll can reuse one
593
+ strip instead of asking Textual's generic style renderer to rebuild
594
+ it for each visible markdown row.
595
+ """
596
+ if not self.is_attached:
597
+ return [Strip.blank(crop.width) for _ in crop.line_range]
598
+
599
+ if crop.width <= 0 or crop.height <= 0:
600
+ return []
601
+ if not self._can_reuse_chrome_strip():
602
+ return super().render_lines(crop)
603
+
604
+ cache_key = (
605
+ crop.x,
606
+ crop.width,
607
+ self.size.width,
608
+ self.app.theme,
609
+ str(self.app.console.color_system),
610
+ # Private Textual cache key: this fast path is intentionally tied
611
+ # to Textual's current style invalidation model and covered by TUI
612
+ # render-cache tests.
613
+ self.styles._cache_key,
614
+ )
615
+ cached = self._background_strip_cache
616
+ if cached is None or cache_key != self._background_strip_cache_key:
617
+ cached = Widget.render_lines(self, Region(crop.x, 0, crop.width, 1))[0]
618
+ self._background_strip_cache = cached
619
+ self._background_strip_cache_key = cache_key
620
+ return [cached] * crop.height
621
+
622
+ def _can_reuse_chrome_strip(self) -> bool:
623
+ """Return true when every AgentMessage chrome row is visually identical."""
624
+ styles = self.styles
625
+ if styles.gutter.height or self._border_title is not None or self._border_subtitle is not None:
626
+ return False
627
+ if any(edge for edge, _color in styles.outline):
628
+ return False
629
+ if styles.has_rule("hatch") and styles.hatch != "none":
630
+ return False
631
+ if styles.tint.a or styles.background_tint.a:
632
+ return False
633
+ if styles.text_opacity != 1.0 or self.opacity != 1.0:
634
+ return False
635
+ if styles.line_pad:
636
+ return False
637
+ return styles.text_align in {"start", "left"}
638
+
639
+ def notify_style_update(self) -> None:
640
+ self._background_strip_cache_key = None
641
+ self._background_strip_cache = None
642
+ super().notify_style_update()
643
+
644
+ def watch_collapsed(self, collapsed: bool) -> None:
645
+ if self._md_widget is not None:
646
+ self._md_widget.display = not collapsed
647
+ with contextlib.suppress(Exception):
648
+ self.query_one(_AgentHeader).update(self._header_text())
649
+
650
+ def on__agent_header_clicked(self) -> None:
651
+ if self._is_final:
652
+ self.collapsed = not self.collapsed
653
+
654
+ def set_timestamp(self, timestamp: str) -> None:
655
+ """Update the header timestamp."""
656
+ if not timestamp or timestamp == self._ts:
657
+ return
658
+ self._ts = timestamp
659
+ with contextlib.suppress(Exception):
660
+ self.query_one(_AgentHeader).update(self._header_text())
661
+
662
+ def _should_show_copy_button(self) -> bool:
663
+ """Return true when this widget represents a final raw agent response."""
664
+ return (
665
+ self._is_final
666
+ and not self._is_intermediate
667
+ and not self._is_compressed
668
+ and not self._is_structured_completion
669
+ and bool(self.format_agent_response_copy().strip())
670
+ )
671
+
672
+ def _show_copy_button_if_ready(self) -> None:
673
+ if not self._should_show_copy_button():
674
+ return
675
+ with contextlib.suppress(Exception):
676
+ self.query_one(MessageCopyButton).display = True
677
+
678
+ def format_agent_response_copy(self) -> str:
679
+ """Return the raw response text copied by the inline button."""
680
+ return self.text
681
+
682
+ def copy_agent_response(self) -> None:
683
+ """Copy this finalized agent response to the available clipboards."""
684
+ payload = self.format_agent_response_copy()
685
+ if not payload.strip():
686
+ return
687
+ copy_text_to_clipboards(self.app, payload)
688
+ localizer = widget_localizer(self)
689
+ self.notify(
690
+ render_str(localizer, _AGENT_RESPONSE_COPIED.bind()),
691
+ title=render_str(localizer, COPIED_TITLE.bind()),
692
+ timeout=2,
693
+ markup=False,
694
+ )
695
+
696
+ def on_message_copy_button_clicked(self, event: MessageCopyButton.Clicked) -> None:
697
+ """Handle clicks from the finalized-response copy affordance."""
698
+ event.stop()
699
+ if not self._should_show_copy_button():
700
+ return
701
+ self.copy_agent_response()
702
+
703
+ def stream_update(self, text: str, is_final: bool = False, *, timestamp: str = "") -> None:
704
+ """Update the message text (for streaming).
705
+
706
+ The main turn replays its buffered answer as a synchronous burst of
707
+ cumulative updates, one per line. A non-final update only records the
708
+ newest text: the markdown body catches up once per message-loop turn,
709
+ so a burst costs one think-tag pass and one markdown update instead of
710
+ one per line, while ``text`` always reads the newest text. A final
711
+ update hands the body its text before the cursor and copy button change.
712
+ """
713
+ self._source_text = text
714
+ self._processed_text_stale = True
715
+ self._body_stale = True
716
+ self._is_final = is_final
717
+ if timestamp:
718
+ self.set_timestamp(timestamp)
719
+ if is_final:
720
+ self._sync_body()
721
+ try:
722
+ cursor = self.query_one(".agent-cursor")
723
+ cursor.remove()
724
+ except Exception:
725
+ pass
726
+ self._show_copy_button_if_ready()
727
+ elif not self._body_sync_scheduled:
728
+ self._body_sync_scheduled = self.call_later(self._run_scheduled_body_sync)
729
+ if not self._body_sync_scheduled:
730
+ # The message pump is closing and refused the callback.
731
+ self._sync_body()
732
+
733
+ def _run_scheduled_body_sync(self) -> None:
734
+ self._body_sync_scheduled = False
735
+ self._sync_body()
736
+
737
+ def _sync_body(self) -> None:
738
+ """Hand the markdown body the current text if it has not seen it yet."""
739
+ if not self._body_stale:
740
+ return
741
+ self._body_stale = False
742
+ if self._md_widget is not None:
743
+ self._md_widget.update(self.text)
744
+
745
+
746
+ class SystemMessage(Static):
747
+ """System notification — dim, unobtrusive."""
748
+
749
+ DEFAULT_CSS = """
750
+ SystemMessage {
751
+ margin: 1 0;
752
+ padding: 0 1;
753
+ color: $text-muted;
754
+ height: auto;
755
+ }
756
+ SystemMessage.-warning {
757
+ color: $warning;
758
+ }
759
+ """
760
+
761
+ def __init__(self, text: str, *, warning: bool = False) -> None:
762
+ super().__init__(Text(f" ◦ {text}"))
763
+ self.set_class(warning, "-warning")
764
+
765
+
766
+ class ConversationStatusAction:
767
+ """Messages emitted by inline failed/interrupted conversation actions."""
768
+
769
+ class Pressed(Message):
770
+ """Posted when the inline retry/continue action is clicked."""
771
+
772
+
773
+ class ErrorMessage(Widget):
774
+ """Error display with red accent."""
775
+
776
+ DEFAULT_CSS = """
777
+ ErrorMessage {
778
+ margin: 1 0;
779
+ padding: 0 1 0 2;
780
+ border-left: thick $tui-border-status-error $border-opacity;
781
+ height: auto;
782
+ }
783
+ ErrorMessage > .status-body {
784
+ height: auto;
785
+ color: $error;
786
+ }
787
+ ErrorMessage > .status-action-row {
788
+ height: 1;
789
+ margin: 1 0 0 0;
790
+ }
791
+ ErrorMessage > .status-action-row > Button {
792
+ height: 1;
793
+ min-width: 10;
794
+ border: none;
795
+ background: $error;
796
+ color: $button-color-foreground;
797
+ text-style: bold;
798
+ }
799
+ ErrorMessage > .status-action-row > Button:hover,
800
+ ErrorMessage > .status-action-row > Button:focus,
801
+ ErrorMessage > .status-action-row > Button.-active {
802
+ background: $foreground;
803
+ color: $background;
804
+ text-style: bold;
805
+ }
806
+ """
807
+
808
+ def __init__(self, text: str, *, action_label: str | None = None) -> None:
809
+ self._text = text
810
+ self._action_label = action_label
811
+ super().__init__()
812
+
813
+ def _render_text(self) -> Text:
814
+ t = Text()
815
+ t.append(render_str(widget_localizer(self), INTERRUPTED_ERROR_HEADER_MESSAGE.bind()), style="bold")
816
+ t.append("\n")
817
+ t.append(self._text)
818
+ return t
819
+
820
+ def compose(self) -> ComposeResult:
821
+ yield Static(self._render_text(), classes="status-body")
822
+ if self._action_label is not None:
823
+ with Horizontal(classes="status-action-row"):
824
+ yield Button(self._action_label, compact=True, classes="status-action-btn")
825
+
826
+ def on_mount(self) -> None:
827
+ self._size_action_button()
828
+
829
+ def _size_action_button(self) -> None:
830
+ if self._action_label is None:
831
+ return
832
+ button = self.query_one(".status-action-btn", Button)
833
+ width = len(self._action_label) + 4
834
+ button.styles.width = width
835
+ button.styles.min_width = width
836
+
837
+ def hide_action(self) -> None:
838
+ """Hide the inline retry action after it has been used."""
839
+ for row in self.query(".status-action-row"):
840
+ row.display = False
841
+
842
+ def set_action_disabled(self, disabled: bool) -> None:
843
+ """Prevent repeat clicks while the retry is being admitted."""
844
+ for button in self.query(".status-action-btn").results(Button):
845
+ button.disabled = disabled
846
+
847
+ def on_button_pressed(self, event: Button.Pressed) -> None:
848
+ if not event.button.has_class("status-action-btn"):
849
+ return
850
+ event.stop()
851
+ if event.button.disabled:
852
+ return
853
+ self.post_message(ConversationStatusAction.Pressed())
854
+
855
+
856
+ class RetryMessage(Static):
857
+ """Transient retry notice — shown while the executor waits before retrying."""
858
+
859
+ DEFAULT_CSS = """
860
+ RetryMessage {
861
+ margin: 1 0;
862
+ padding: 0 1 0 2;
863
+ border-left: thick $tui-border-status-error $border-opacity;
864
+ height: auto;
865
+ color: $error;
866
+ }
867
+ """
868
+
869
+ def __init__(self, message: str, attempt: int, max_attempts: int, delay_seconds: int) -> None:
870
+ self._message = message
871
+ self._attempt = attempt
872
+ self._max_attempts = max_attempts
873
+ self._delay = delay_seconds
874
+ super().__init__()
875
+
876
+ def render(self) -> Text:
877
+ t = Text()
878
+ t.append(self._render_message(INTERRUPTED_ERROR_HEADER_MESSAGE.bind()), style="bold")
879
+ t.append("\n")
880
+ t.append(
881
+ self._render_message(
882
+ _RETRY_MESSAGE.bind(
883
+ message=DisplayBlock(self._message),
884
+ delay_seconds=self._delay,
885
+ attempt=self._attempt,
886
+ max_attempts=self._max_attempts,
887
+ )
888
+ ),
889
+ )
890
+ return t
891
+
892
+ def _render_message(self, reference: MessageRef) -> str:
893
+ return render_str(widget_localizer(self), reference)
894
+
895
+
896
+ class InterruptedMessage(Widget):
897
+ """Interruption notice — yellow for user interrupts, red for errors."""
898
+
899
+ DEFAULT_CSS = """
900
+ InterruptedMessage {
901
+ margin: 1 0;
902
+ padding: 0 1 0 2;
903
+ border-left: thick $tui-border-status-warning $border-opacity;
904
+ height: auto;
905
+ }
906
+ InterruptedMessage.-error {
907
+ border-left: thick $tui-border-status-error $border-opacity;
908
+ }
909
+ InterruptedMessage.-error > .status-action-row > Button {
910
+ background: $error;
911
+ }
912
+ InterruptedMessage > .status-body {
913
+ height: auto;
914
+ color: $warning;
915
+ }
916
+ InterruptedMessage.-error > .status-body {
917
+ color: $error;
918
+ }
919
+ InterruptedMessage > .status-action-row {
920
+ height: 1;
921
+ margin: 1 0 0 0;
922
+ }
923
+ InterruptedMessage > .status-action-row > Button {
924
+ height: 1;
925
+ min-width: 10;
926
+ border: none;
927
+ background: $warning;
928
+ color: $button-color-foreground;
929
+ text-style: bold;
930
+ }
931
+ InterruptedMessage > .status-action-row > Button:hover,
932
+ InterruptedMessage > .status-action-row > Button:focus,
933
+ InterruptedMessage > .status-action-row > Button.-active {
934
+ background: $foreground;
935
+ color: $background;
936
+ text-style: bold;
937
+ }
938
+ """
939
+
940
+ def __init__(
941
+ self,
942
+ reason: str = "Execution interrupted",
943
+ source: str = "user",
944
+ *,
945
+ action_label: str | None = None,
946
+ header: str | None = None,
947
+ ) -> None:
948
+ if header is None:
949
+ copy = resolve_interrupted_message_copy(reason, source)
950
+ header = copy.header
951
+ reason = copy.reason
952
+ self._header = header
953
+ self._reason = reason
954
+ self._source = source
955
+ self._action_label = action_label
956
+ super().__init__()
957
+ if source == "error":
958
+ self.add_class("-error")
959
+
960
+ def _render_text(self) -> Text:
961
+ t = Text()
962
+ t.append(self._header, style="bold")
963
+ t.append("\n")
964
+ t.append(self._reason)
965
+ return t
966
+
967
+ def compose(self) -> ComposeResult:
968
+ yield Static(self._render_text(), classes="status-body")
969
+ if self._action_label is not None:
970
+ with Horizontal(classes="status-action-row"):
971
+ yield Button(self._action_label, compact=True, classes="status-action-btn")
972
+
973
+ def on_mount(self) -> None:
974
+ self._size_action_button()
975
+
976
+ def _size_action_button(self) -> None:
977
+ if self._action_label is None:
978
+ return
979
+ button = self.query_one(".status-action-btn", Button)
980
+ width = Text(self._action_label).cell_len + 4
981
+ button.styles.width = width
982
+ button.styles.min_width = width
983
+
984
+ def hide_action(self) -> None:
985
+ """Hide the inline retry/continue action after it has been used."""
986
+ for row in self.query(".status-action-row"):
987
+ row.display = False
988
+
989
+ def set_action_disabled(self, disabled: bool) -> None:
990
+ """Prevent repeat clicks while the retry is being admitted."""
991
+ for button in self.query(".status-action-btn").results(Button):
992
+ button.disabled = disabled
993
+
994
+ def on_button_pressed(self, event: Button.Pressed) -> None:
995
+ if not event.button.has_class("status-action-btn"):
996
+ return
997
+ event.stop()
998
+ if event.button.disabled:
999
+ return
1000
+ self.post_message(ConversationStatusAction.Pressed())
1001
+
1002
+
1003
+ class ThinkingIndicator(Widget):
1004
+ """Loading indicator with label text."""
1005
+
1006
+ DEFAULT_CSS = """
1007
+ ThinkingIndicator {
1008
+ margin: 1 0;
1009
+ padding: 0 1;
1010
+ height: 1;
1011
+ layout: horizontal;
1012
+ }
1013
+ ThinkingIndicator > LoadingIndicator {
1014
+ width: 5;
1015
+ height: 1;
1016
+ color: $accent;
1017
+ }
1018
+ ThinkingIndicator > .thinking-label {
1019
+ width: auto;
1020
+ height: 1;
1021
+ color: $accent;
1022
+ }
1023
+ """
1024
+
1025
+ def __init__(self, **kwargs: Any) -> None:
1026
+ super().__init__(**kwargs)
1027
+
1028
+ def compose(self) -> ComposeResult:
1029
+ from textual.widgets import LoadingIndicator
1030
+
1031
+ yield LoadingIndicator()
1032
+ yield Static("thinking", classes="thinking-label", id="thinking-label")
1033
+
1034
+ def update_label(self, label: str) -> None:
1035
+ """Change the displayed text."""
1036
+ import contextlib
1037
+
1038
+ with contextlib.suppress(Exception):
1039
+ self.query_one("#thinking-label", Static).update(Text(label))