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,1611 @@
1
+ # Copyright (c) 2021 Will McGugan
2
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
3
+ # Contains code adapted from Textual (MIT License; see NOTICE).
4
+
5
+ """Patch: keep Textual selection offsets stable across tab expansion.
6
+
7
+ Problem
8
+ -------
9
+ Textual renders ``\\t`` by expanding it to spaces, but selection hit-testing
10
+ returns offsets from the rendered text. Downstream selection extraction still
11
+ uses those offsets against the original source text, so selecting text after a
12
+ tab drifts to the wrong character.
13
+
14
+ Solution
15
+ --------
16
+ When ``Content`` expands tabs for rendering, carry a small rendered-offset to
17
+ source-offset map through the rendered segments. The compositor uses that map
18
+ when converting mouse cells back to selection offsets. Runtime monkey patches
19
+ cover Textual modules imported before Chrys' site-package patcher runs.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import logging
25
+ from itertools import pairwise
26
+ from typing import Any
27
+
28
+ from rich.segment import Segment
29
+ from rich.style import Style as RichStyle
30
+
31
+ from chrys.foundation.patches.patcher import FilePatch, register
32
+
33
+ _RUNTIME_PATCH_MARKER = "_chrys_tab_selection_offsets"
34
+ # The runtime monkey patch reproduces Textual 8.2.7's compositor and content
35
+ # render path. The project pins Textual to an exact version, so a version
36
+ # equality check is the reliable signal that the patched bodies still match.
37
+ # Bump this together with the pin in pyproject.toml on every Textual upgrade.
38
+ #
39
+ # This deliberately avoids inspecting the live function source: the site-package
40
+ # patcher rewrites _compositor.py after import, which shifts line numbers and
41
+ # leaves the in-memory code object's co_firstlineno stale, so inspect.getsource
42
+ # can no longer be trusted to return the right function body.
43
+ _RUNTIME_PATCH_TEXTUAL_VERSION = "8.2.7"
44
+ logger = logging.getLogger(__name__)
45
+
46
+
47
+ def _runtime_compositor_patch_compatible() -> bool:
48
+ """Return true when the loaded Textual matches the version the patch targets."""
49
+ try:
50
+ import textual
51
+ except ImportError:
52
+ return False
53
+ return getattr(textual, "__version__", None) == _RUNTIME_PATCH_TEXTUAL_VERSION
54
+
55
+
56
+ _CONTENT_HELPER_OLD = '''\
57
+ def _strip_control_codes(
58
+ text: str, _translate_table: dict[int, None] = _CONTROL_STRIP_TRANSLATE
59
+ ) -> str:
60
+ """Remove control codes from text.
61
+
62
+ Args:
63
+ text (str): A string possibly contain control codes.
64
+
65
+ Returns:
66
+ str: String with control codes removed.
67
+ """
68
+ return text.translate(_translate_table)
69
+
70
+
71
+ @rich.repr.auto
72
+ class Span(NamedTuple):'''
73
+
74
+ _CONTENT_HELPER_NEW = '''\
75
+ def _strip_control_codes(
76
+ text: str, _translate_table: dict[int, None] = _CONTROL_STRIP_TRANSLATE
77
+ ) -> str:
78
+ """Remove control codes from text.
79
+
80
+ Args:
81
+ text (str): A string possibly contain control codes.
82
+
83
+ Returns:
84
+ str: String with control codes removed.
85
+ """
86
+ return text.translate(_translate_table)
87
+
88
+
89
+ def _expand_tabs_with_offsets(
90
+ line: "Content", tab_size: int
91
+ ) -> tuple["Content", tuple[int, ...] | None]:
92
+ """Expand tabs and map rendered character offsets back to source offsets."""
93
+ if "\\t" not in line.plain:
94
+ return line, None
95
+
96
+ if not line._spans:
97
+ # Preserve Textual's unstyled Content.expand_tabs behavior: it delegates
98
+ # to str.expandtabs(), whose tab stops are character-position based.
99
+ source_offsets: list[int] = []
100
+ source_position = 0
101
+ cell_position = 0
102
+ for character in line.plain:
103
+ if character == "\\t":
104
+ spaces = 0 if tab_size <= 0 else tab_size - cell_position % tab_size
105
+ source_offsets.extend([source_position] * spaces)
106
+ cell_position += spaces
107
+ else:
108
+ source_offsets.append(source_position)
109
+ cell_position += 1
110
+ source_position += 1
111
+ source_offsets.append(source_position)
112
+ return Content(line.plain.expandtabs(tab_size)), tuple(source_offsets)
113
+
114
+ new_text: list[Content] = []
115
+ source_offsets: list[int] = []
116
+ append = new_text.append
117
+ rendered_position = 0
118
+ source_position = 0
119
+
120
+ for part in line.split("\\t", include_separator=True):
121
+ part_text = part.plain
122
+ if part_text.endswith("\\t"):
123
+ text_before_tab = part_text[:-1]
124
+ source_offsets.extend(
125
+ range(source_position, source_position + len(text_before_tab))
126
+ )
127
+ tab_source_offset = source_position + len(text_before_tab)
128
+ rendered_position += len(text_before_tab)
129
+ spaces = 0 if tab_size <= 0 else tab_size - rendered_position % tab_size
130
+ if spaces:
131
+ part = Content(part._text[:-1] + " ", part._spans)
132
+ if spaces > 1:
133
+ part = part.extend_style(spaces - 1)
134
+ else:
135
+ part = Content(
136
+ part._text[:-1],
137
+ [
138
+ Span(span.start, min(span.end, len(text_before_tab)), span.style)
139
+ for span in part._spans
140
+ if min(span.end, len(text_before_tab)) > span.start
141
+ ],
142
+ )
143
+ source_offsets.extend([tab_source_offset] * spaces)
144
+ rendered_position += spaces
145
+ else:
146
+ source_offsets.extend(
147
+ range(source_position, source_position + len(part_text))
148
+ )
149
+ rendered_position += len(part_text)
150
+ source_position += len(part_text)
151
+ append(part)
152
+
153
+ source_offsets.append(source_position)
154
+ return EMPTY_CONTENT.join(new_text), tuple(source_offsets)
155
+
156
+
157
+ def _pad_source_offsets(
158
+ source_offsets: tuple[int, ...], left: int, right: int
159
+ ) -> tuple[int, ...]:
160
+ """Pad source offsets for rendered line padding."""
161
+ if not source_offsets:
162
+ return source_offsets
163
+ return (
164
+ *([source_offsets[0]] * left),
165
+ *source_offsets,
166
+ *([source_offsets[-1]] * right),
167
+ )
168
+
169
+
170
+ @rich.repr.auto
171
+ class Span(NamedTuple):'''
172
+
173
+
174
+ _CONTENT_HELPER_PREVIOUS_STYLED_TAB_FRAGMENT = """\
175
+ new_text: list[Content] = []
176
+ source_offsets: list[int] = []
177
+ append = new_text.append
178
+ cell_position = 0
179
+ source_position = 0
180
+
181
+ for part in line.split("\\t", include_separator=True):
182
+ part_text = part.plain
183
+ if part_text.endswith("\\t"):
184
+ text_before_tab = part_text[:-1]
185
+ source_offsets.extend(
186
+ range(source_position, source_position + len(text_before_tab))
187
+ )
188
+ tab_source_offset = source_position + len(text_before_tab)
189
+ part = Content(part._text[:-1] + " ", part._spans, part._cell_length)
190
+ cell_position += part.cell_length
191
+ spaces = 1
192
+ tab_remainder = cell_position % tab_size
193
+ if tab_remainder:
194
+ extra_spaces = tab_size - tab_remainder
195
+ part = part.extend_style(extra_spaces)
196
+ cell_position += extra_spaces
197
+ spaces += extra_spaces
198
+ source_offsets.extend([tab_source_offset] * spaces)
199
+ else:
200
+ source_offsets.extend(
201
+ range(source_position, source_position + len(part_text))
202
+ )
203
+ cell_position += part.cell_length
204
+ source_position += len(part_text)
205
+ append(part)"""
206
+
207
+
208
+ _CONTENT_HELPER_STYLED_TAB_FRAGMENT_NEW = """\
209
+ new_text: list[Content] = []
210
+ source_offsets: list[int] = []
211
+ append = new_text.append
212
+ rendered_position = 0
213
+ source_position = 0
214
+
215
+ for part in line.split("\\t", include_separator=True):
216
+ part_text = part.plain
217
+ if part_text.endswith("\\t"):
218
+ text_before_tab = part_text[:-1]
219
+ source_offsets.extend(
220
+ range(source_position, source_position + len(text_before_tab))
221
+ )
222
+ tab_source_offset = source_position + len(text_before_tab)
223
+ rendered_position += len(text_before_tab)
224
+ spaces = 0 if tab_size <= 0 else tab_size - rendered_position % tab_size
225
+ if spaces:
226
+ part = Content(part._text[:-1] + " ", part._spans)
227
+ if spaces > 1:
228
+ part = part.extend_style(spaces - 1)
229
+ else:
230
+ part = Content(
231
+ part._text[:-1],
232
+ [
233
+ Span(span.start, min(span.end, len(text_before_tab)), span.style)
234
+ for span in part._spans
235
+ if min(span.end, len(text_before_tab)) > span.start
236
+ ],
237
+ )
238
+ source_offsets.extend([tab_source_offset] * spaces)
239
+ rendered_position += spaces
240
+ else:
241
+ source_offsets.extend(
242
+ range(source_position, source_position + len(part_text))
243
+ )
244
+ rendered_position += len(part_text)
245
+ source_position += len(part_text)
246
+ append(part)"""
247
+
248
+
249
+ _CONTENT_WRAP_AND_FORMAT_OLD = '''\
250
+ def _wrap_and_format(
251
+ self,
252
+ width: int,
253
+ align: TextAlign = "left",
254
+ overflow: TextOverflow = "fold",
255
+ no_wrap: bool = False,
256
+ line_pad: int = 0,
257
+ tab_size: int = 8,
258
+ selection: Selection | None = None,
259
+ selection_style: Style | None = None,
260
+ post_style: Style | None = None,
261
+ get_style: Callable[[str | Style], Style] = Style.parse,
262
+ ) -> list[_FormattedLine]:
263
+ """Wraps the text and applies formatting.
264
+
265
+ Args:
266
+ width: Desired width.
267
+ align: Text alignment.
268
+ overflow: Overflow method.
269
+ no_wrap: Disabled wrapping.
270
+ tab_size: Cell with of tabs.
271
+ selection: Selection information or `None` if no selection.
272
+ selection_style: Selection style, or `None` if no selection.
273
+
274
+ Returns:
275
+ List of formatted lines.
276
+ """
277
+ output_lines: list[_FormattedLine] = []
278
+
279
+ if selection is not None:
280
+ get_span = selection.get_span
281
+ else:
282
+
283
+ def get_span(y: int) -> tuple[int, int] | None:
284
+ return None
285
+
286
+ for y, line in enumerate(self.split(allow_blank=True)):
287
+ if post_style is not None:
288
+ line = line.stylize(post_style)
289
+
290
+ if selection_style is not None and (span := get_span(y)) is not None:
291
+ start, end = span
292
+ if end == -1:
293
+ end = len(line.plain)
294
+ line = line.stylize(selection_style, start, end)
295
+
296
+ line = line.expand_tabs(tab_size)
297
+
298
+ if no_wrap:
299
+ if overflow == "fold":
300
+ cuts = list(range(0, line.cell_length, width))[1:]
301
+ new_lines = [
302
+ _FormattedLine(get_style, line, width, y=y, align=align)
303
+ for line in line.divide(cuts)
304
+ ]
305
+ else:
306
+ line = line.truncate(width, ellipsis=overflow == "ellipsis")
307
+ content_line = _FormattedLine(
308
+ get_style, line, width, y=y, align=align
309
+ )
310
+ new_lines = [content_line]
311
+ else:
312
+ content_line = _FormattedLine(get_style, line, width, y=y, align=align)
313
+ offsets = divide_line(
314
+ line.plain, width - line_pad * 2, fold=overflow == "fold"
315
+ )
316
+ divided_lines = content_line.content.divide(offsets)
317
+ ellipsis = overflow == "ellipsis"
318
+ divided_lines = [
319
+ (
320
+ line.truncate(width, ellipsis=ellipsis)
321
+ if last
322
+ else line.rstrip().truncate(width, ellipsis=ellipsis)
323
+ )
324
+ for last, line in loop_last(divided_lines)
325
+ ]
326
+
327
+ new_lines = [
328
+ _FormattedLine(
329
+ get_style,
330
+ content.rstrip_end(width).pad(line_pad, line_pad),
331
+ width,
332
+ offset,
333
+ y,
334
+ align=align,
335
+ )
336
+ for content, offset in zip(divided_lines, [0, *offsets])
337
+ ]
338
+ new_lines[-1].line_end = True
339
+
340
+ output_lines.extend(new_lines)
341
+
342
+ return output_lines'''
343
+
344
+ _CONTENT_WRAP_AND_FORMAT_NEW = '''\
345
+ def _wrap_and_format(
346
+ self,
347
+ width: int,
348
+ align: TextAlign = "left",
349
+ overflow: TextOverflow = "fold",
350
+ no_wrap: bool = False,
351
+ line_pad: int = 0,
352
+ tab_size: int = 8,
353
+ selection: Selection | None = None,
354
+ selection_style: Style | None = None,
355
+ post_style: Style | None = None,
356
+ get_style: Callable[[str | Style], Style] = Style.parse,
357
+ ) -> list[_FormattedLine]:
358
+ """Wraps the text and applies formatting.
359
+
360
+ Args:
361
+ width: Desired width.
362
+ align: Text alignment.
363
+ overflow: Overflow method.
364
+ no_wrap: Disabled wrapping.
365
+ tab_size: Cell with of tabs.
366
+ selection: Selection information or `None` if no selection.
367
+ selection_style: Selection style, or `None` if no selection.
368
+
369
+ Returns:
370
+ List of formatted lines.
371
+ """
372
+ output_lines: list[_FormattedLine] = []
373
+
374
+ if selection is not None:
375
+ get_span = selection.get_span
376
+ else:
377
+
378
+ def get_span(y: int) -> tuple[int, int] | None:
379
+ return None
380
+
381
+ for y, line in enumerate(self.split(allow_blank=True)):
382
+ if post_style is not None:
383
+ line = line.stylize(post_style)
384
+
385
+ if selection_style is not None and (span := get_span(y)) is not None:
386
+ start, end = span
387
+ if end == -1:
388
+ end = len(line.plain)
389
+ line = line.stylize(selection_style, start, end)
390
+
391
+ line, source_offsets = _expand_tabs_with_offsets(line, tab_size)
392
+
393
+ if no_wrap:
394
+ if overflow == "fold":
395
+ cuts = list(range(0, line.cell_length, width))[1:]
396
+ divided_lines = line.divide(cuts)
397
+ divide_offsets = [0, *cuts, len(line.plain)]
398
+ divided_source_offsets = (
399
+ [
400
+ source_offsets[start : end + 1]
401
+ for start, end in zip(divide_offsets, divide_offsets[1:], strict=False)
402
+ ]
403
+ if source_offsets is not None
404
+ else [None] * len(divided_lines)
405
+ )
406
+ new_lines = [
407
+ _FormattedLine(
408
+ get_style,
409
+ divided_line,
410
+ width,
411
+ y=y,
412
+ align=align,
413
+ source_offsets=offset_source_offsets,
414
+ )
415
+ for divided_line, offset_source_offsets in zip(
416
+ divided_lines, divided_source_offsets, strict=True
417
+ )
418
+ ]
419
+ else:
420
+ line = line.truncate(width, ellipsis=overflow == "ellipsis")
421
+ content_line = _FormattedLine(
422
+ get_style,
423
+ line,
424
+ width,
425
+ y=y,
426
+ align=align,
427
+ source_offsets=source_offsets,
428
+ )
429
+ new_lines = [content_line]
430
+ else:
431
+ content_line = _FormattedLine(
432
+ get_style,
433
+ line,
434
+ width,
435
+ y=y,
436
+ align=align,
437
+ source_offsets=source_offsets,
438
+ )
439
+ offsets = divide_line(
440
+ line.plain, width - line_pad * 2, fold=overflow == "fold"
441
+ )
442
+ divided_lines = content_line.content.divide(offsets)
443
+ divide_offsets = [0, *offsets, len(line.plain)]
444
+ divided_source_offsets = (
445
+ [
446
+ source_offsets[start : end + 1]
447
+ for start, end in zip(divide_offsets, divide_offsets[1:], strict=False)
448
+ ]
449
+ if source_offsets is not None
450
+ else [None] * len(divided_lines)
451
+ )
452
+ ellipsis = overflow == "ellipsis"
453
+ divided_lines = [
454
+ (
455
+ line.truncate(width, ellipsis=ellipsis)
456
+ if last
457
+ else line.rstrip().truncate(width, ellipsis=ellipsis)
458
+ )
459
+ for last, line in loop_last(divided_lines)
460
+ ]
461
+
462
+ new_lines = []
463
+ for content, offset, offset_source_offsets in zip(
464
+ divided_lines,
465
+ [0, *offsets],
466
+ divided_source_offsets,
467
+ strict=True,
468
+ ):
469
+ content = content.rstrip_end(width)
470
+ if offset_source_offsets is not None:
471
+ offset_source_offsets = offset_source_offsets[
472
+ : len(content.plain) + 1
473
+ ]
474
+ offset_source_offsets = _pad_source_offsets(
475
+ offset_source_offsets, line_pad, line_pad
476
+ )
477
+ new_lines.append(
478
+ _FormattedLine(
479
+ get_style,
480
+ content.pad(line_pad, line_pad),
481
+ width,
482
+ offset,
483
+ y,
484
+ align=align,
485
+ source_offsets=offset_source_offsets,
486
+ )
487
+ )
488
+ new_lines[-1].line_end = True
489
+
490
+ output_lines.extend(new_lines)
491
+
492
+ return output_lines'''
493
+
494
+
495
+ _FORMATTED_LINE_OLD = '''\
496
+ class _FormattedLine:
497
+ """A line of content with additional formatting information.
498
+
499
+ This class is used internally within Content, and you are unlikely to need it an an app.
500
+ """
501
+
502
+ def __init__(
503
+ self,
504
+ get_style: Callable[[str | Style], Style],
505
+ content: Content,
506
+ width: int,
507
+ x: int = 0,
508
+ y: int = 0,
509
+ align: TextAlign = "left",
510
+ line_end: bool = False,
511
+ link_style: Style | None = None,
512
+ ) -> None:
513
+ self.get_style = get_style
514
+ self.content = content
515
+ self.width = width
516
+ self.x = x
517
+ self.y = y
518
+ self.align = align
519
+ self.line_end = line_end
520
+ self.link_style = link_style
521
+
522
+ @property
523
+ def plain(self) -> str:
524
+ return self.content.plain
525
+
526
+ def to_strip(self, style: Style) -> tuple[list[Segment], int]:
527
+ _Segment = Segment
528
+ align = self.align
529
+ width = self.width
530
+ pad_left = pad_right = 0
531
+ content = self.content
532
+ x = self.x
533
+ y = self.y
534
+ get_style = self.get_style
535
+
536
+ if align in ("start", "left") or (align == "justify" and self.line_end):
537
+ pass
538
+
539
+ elif align == "center":
540
+ excess_space = width - self.content.cell_length
541
+ pad_left = excess_space // 2
542
+ pad_right = excess_space - pad_left
543
+
544
+ elif align in ("end", "right"):
545
+ pad_left = width - self.content.cell_length
546
+
547
+ elif align == "justify":
548
+ words = content.split(" ", include_separator=False)
549
+ words_size = sum(cell_len(word.plain.rstrip(" ")) for word in words)
550
+ num_spaces = len(words) - 1
551
+ spaces = [1] * num_spaces
552
+ index = 0
553
+ if spaces:
554
+ while words_size + num_spaces < width:
555
+ spaces[len(spaces) - index - 1] += 1
556
+ num_spaces += 1
557
+ index = (index + 1) % len(spaces)
558
+
559
+ segments: list[Segment] = []
560
+ add_segment = segments.append
561
+ x = self.x
562
+ for index, word in enumerate(words):
563
+ for text, text_style in word.render(
564
+ style, end="", parse_style=get_style
565
+ ):
566
+ add_segment(
567
+ _Segment(
568
+ text, (style + text_style).rich_style_with_offset(x, y)
569
+ )
570
+ )
571
+ x += len(text) + 1
572
+ if index < len(spaces) and (pad := spaces[index]):
573
+ add_segment(_Segment(" " * pad, (style + text_style).rich_style))
574
+
575
+ return segments, width
576
+
577
+ segments = (
578
+ [Segment(" " * pad_left, style.background_style.rich_style)]
579
+ if pad_left
580
+ else []
581
+ )
582
+ add_segment = segments.append
583
+
584
+ for text, text_style in content.render(style, end="", parse_style=get_style):
585
+ add_segment(
586
+ _Segment(text, (style + text_style).rich_style_with_offset(x, y))
587
+ )
588
+ x += len(text)
589
+
590
+ if pad_right:
591
+ segments.append(
592
+ _Segment(" " * pad_right, style.background_style.rich_style)
593
+ )
594
+
595
+ return (segments, content.cell_length + pad_left + pad_right)
596
+
597
+ def _apply_link_style(
598
+ self, link_style: RichStyle, segments: list[Segment]
599
+ ) -> list[Segment]:
600
+ _Segment = Segment
601
+ segments = [
602
+ _Segment(
603
+ text,
604
+ (
605
+ style
606
+ if style._meta is None
607
+ else (style + link_style if "@click" in style.meta else style)
608
+ ),
609
+ control,
610
+ )
611
+ for text, style, control in segments
612
+ if style is not None
613
+ ]
614
+ return segments'''
615
+
616
+ _FORMATTED_LINE_NEW = '''\
617
+ class _FormattedLine:
618
+ """A line of content with additional formatting information.
619
+
620
+ This class is used internally within Content, and you are unlikely to need it an an app.
621
+ """
622
+
623
+ def __init__(
624
+ self,
625
+ get_style: Callable[[str | Style], Style],
626
+ content: Content,
627
+ width: int,
628
+ x: int = 0,
629
+ y: int = 0,
630
+ align: TextAlign = "left",
631
+ line_end: bool = False,
632
+ link_style: Style | None = None,
633
+ source_offsets: tuple[int, ...] | None = None,
634
+ ) -> None:
635
+ self.get_style = get_style
636
+ self.content = content
637
+ self.width = width
638
+ self.x = x
639
+ self.y = y
640
+ self.align = align
641
+ self.line_end = line_end
642
+ self.link_style = link_style
643
+ self.source_offsets = source_offsets
644
+
645
+ @property
646
+ def plain(self) -> str:
647
+ return self.content.plain
648
+
649
+ def to_strip(self, style: Style) -> tuple[list[Segment], int]:
650
+ _Segment = Segment
651
+ align = self.align
652
+ width = self.width
653
+ pad_left = pad_right = 0
654
+ content = self.content
655
+ x = self.x
656
+ y = self.y
657
+ get_style = self.get_style
658
+
659
+ if align in ("start", "left") or (align == "justify" and self.line_end):
660
+ pass
661
+
662
+ elif align == "center":
663
+ excess_space = width - self.content.cell_length
664
+ pad_left = excess_space // 2
665
+ pad_right = excess_space - pad_left
666
+
667
+ elif align in ("end", "right"):
668
+ pad_left = width - self.content.cell_length
669
+
670
+ elif align == "justify":
671
+ # Justification synthesizes word-gap cells. Chrys does not route raw
672
+ # tabbed selectable content through justified widgets, so keep the
673
+ # upstream rendered-offset behavior here. If this path ever needs
674
+ # source maps, the synthetic gaps and len(text) + 1 advance below
675
+ # need explicit map entries.
676
+ words = content.split(" ", include_separator=False)
677
+ words_size = sum(cell_len(word.plain.rstrip(" ")) for word in words)
678
+ num_spaces = len(words) - 1
679
+ spaces = [1] * num_spaces
680
+ index = 0
681
+ if spaces:
682
+ while words_size + num_spaces < width:
683
+ spaces[len(spaces) - index - 1] += 1
684
+ num_spaces += 1
685
+ index = (index + 1) % len(spaces)
686
+
687
+ segments: list[Segment] = []
688
+ add_segment = segments.append
689
+ x = self.x
690
+ for index, word in enumerate(words):
691
+ for text, text_style in word.render(
692
+ style, end="", parse_style=get_style
693
+ ):
694
+ add_segment(
695
+ _Segment(
696
+ text, (style + text_style).rich_style_with_offset(x, y)
697
+ )
698
+ )
699
+ x += len(text) + 1
700
+ if index < len(spaces) and (pad := spaces[index]):
701
+ add_segment(_Segment(" " * pad, (style + text_style).rich_style))
702
+
703
+ return segments, width
704
+
705
+ segments = (
706
+ [Segment(" " * pad_left, style.background_style.rich_style)]
707
+ if pad_left
708
+ else []
709
+ )
710
+ add_segment = segments.append
711
+ source_offsets = self.source_offsets
712
+ content_offset = 0
713
+
714
+ for text, text_style in content.render(style, end="", parse_style=get_style):
715
+ offset_map: tuple[int, ...] | None = None
716
+ if source_offsets is not None:
717
+ end_offset = content_offset + len(text) + 1
718
+ offset_map = source_offsets[content_offset:end_offset]
719
+ if len(offset_map) != len(text) + 1:
720
+ offset_map = None
721
+ add_segment(
722
+ _Segment(
723
+ text,
724
+ (style + text_style).rich_style_with_offset(
725
+ x,
726
+ y,
727
+ offset_map=offset_map,
728
+ ),
729
+ )
730
+ )
731
+ x += len(text)
732
+ content_offset += len(text)
733
+
734
+ if pad_right:
735
+ segments.append(
736
+ _Segment(" " * pad_right, style.background_style.rich_style)
737
+ )
738
+
739
+ return (segments, content.cell_length + pad_left + pad_right)
740
+
741
+ def _apply_link_style(
742
+ self, link_style: RichStyle, segments: list[Segment]
743
+ ) -> list[Segment]:
744
+ _Segment = Segment
745
+ segments = [
746
+ _Segment(
747
+ text,
748
+ (
749
+ style
750
+ if style._meta is None
751
+ else (style + link_style if "@click" in style.meta else style)
752
+ ),
753
+ control,
754
+ )
755
+ for text, style, control in segments
756
+ if style is not None
757
+ ]
758
+ return segments'''
759
+
760
+
761
+ _STYLE_RICH_STYLE_WITH_OFFSET_OLD = '''\
762
+ def rich_style_with_offset(self, x: int, y: int) -> RichStyle:
763
+ """Get a Rich style with the given offset included in meta.
764
+
765
+ This is used in text selection.
766
+
767
+ Args:
768
+ x: X coordinate.
769
+ y: Y coordinate.
770
+
771
+ Returns:
772
+ A Rich Style object.
773
+ """
774
+ (
775
+ background,
776
+ foreground,
777
+ bold,
778
+ dim,
779
+ italic,
780
+ underline,
781
+ underline2,
782
+ reverse,
783
+ strike,
784
+ blink,
785
+ link,
786
+ _meta,
787
+ ) = _get_simple_attributes(self)
788
+ color = None if foreground is None else background + foreground
789
+ return RichStyle(
790
+ color=None if color is None else color.rich_color,
791
+ bgcolor=None if background is None else background.rich_color,
792
+ bold=bold,
793
+ dim=dim,
794
+ italic=italic,
795
+ underline=underline,
796
+ underline2=underline2,
797
+ reverse=reverse,
798
+ strike=strike,
799
+ blink=blink,
800
+ link=link,
801
+ meta={**self.meta, "offset": (x, y)},
802
+ )'''
803
+
804
+ _STYLE_RICH_STYLE_WITH_OFFSET_NEW = '''\
805
+ def rich_style_with_offset(
806
+ self, x: int, y: int, *, offset_map: Iterable[int] | None = None
807
+ ) -> RichStyle:
808
+ """Get a Rich style with the given offset included in meta.
809
+
810
+ This is used in text selection.
811
+
812
+ Args:
813
+ x: X coordinate.
814
+ y: Y coordinate.
815
+ offset_map: Optional mapping from rendered character offsets to
816
+ source offsets.
817
+
818
+ Returns:
819
+ A Rich Style object.
820
+ """
821
+ (
822
+ background,
823
+ foreground,
824
+ bold,
825
+ dim,
826
+ italic,
827
+ underline,
828
+ underline2,
829
+ reverse,
830
+ strike,
831
+ blink,
832
+ link,
833
+ _meta,
834
+ ) = _get_simple_attributes(self)
835
+ color = None if foreground is None else background + foreground
836
+ meta: dict[str, Any] = {**self.meta, "offset": (x, y)}
837
+ if offset_map is not None:
838
+ meta["offset_map"] = tuple(offset_map)
839
+ return RichStyle(
840
+ color=None if color is None else color.rich_color,
841
+ bgcolor=None if background is None else background.rich_color,
842
+ bold=bold,
843
+ dim=dim,
844
+ italic=italic,
845
+ underline=underline,
846
+ underline2=underline2,
847
+ reverse=reverse,
848
+ strike=strike,
849
+ blink=blink,
850
+ link=link,
851
+ meta=meta,
852
+ )'''
853
+
854
+
855
+ _COMPOSITOR_GET_WIDGET_AND_OFFSET_AT_OLD = '''\
856
+ def get_widget_and_offset_at(
857
+ self, x: int, y: int
858
+ ) -> tuple[Widget | None, Offset | None]:
859
+ """Get the Style at the given cell, the offset within the content.
860
+
861
+ Args:
862
+ x: X position within the Layout.
863
+ y: Y position within the Layout.
864
+
865
+ Returns:
866
+ A tuple of the widget at (x, y) and the offset within the widget.
867
+ """
868
+ try:
869
+ widget, region = self.get_widget_at(x, y)
870
+ except errors.NoWidget:
871
+ return None, None
872
+ if widget not in self.visible_widgets:
873
+ return None, None
874
+
875
+ if y >= widget.content_region.bottom:
876
+ x, y = widget.content_region.bottom_right_inclusive
877
+
878
+ gutter_left, gutter_right = widget.gutter.top_left
879
+ x -= region.x + gutter_left
880
+ y -= region.y + gutter_right
881
+
882
+ if y < 0:
883
+ return None, None
884
+
885
+ if x < 0:
886
+ return widget, Offset(0, y)
887
+
888
+ visible_screen_stack.set(widget.app._background_screens)
889
+ line = widget.render_line(y)
890
+
891
+ end = 0
892
+ start = 0
893
+ offset_y: int | None = None
894
+ offset_x = 0
895
+ offset_x2 = 0
896
+
897
+ from rich.cells import get_character_cell_size
898
+
899
+ offset: Offset | None = None
900
+ for segment in line:
901
+ end += segment.cell_length
902
+ style = segment.style
903
+ if style is not None and style._meta is not None:
904
+ meta = style.meta
905
+ if "offset" in meta:
906
+ offset_x, offset_y = meta["offset"]
907
+ if offset_y is None:
908
+ continue
909
+ offset_x2 = offset_x + len(segment.text)
910
+
911
+ if x < end and x >= start:
912
+ segment_cell_length = 0
913
+ cell_cut = x - start
914
+ segment_offset = 0
915
+ for character in segment.text:
916
+ if segment_cell_length >= cell_cut:
917
+ break
918
+ segment_cell_length += get_character_cell_size(character)
919
+ segment_offset += 1
920
+
921
+ offset = Offset(offset_x + segment_offset, offset_y)
922
+ break
923
+ start = end
924
+
925
+ if offset is None and offset_y is not None:
926
+ offset = Offset(offset_x2, offset_y)
927
+ return widget, offset'''
928
+
929
+ _COMPOSITOR_GET_WIDGET_AND_OFFSET_AT_NEW = '''\
930
+ def get_widget_and_offset_at(
931
+ self, x: int, y: int
932
+ ) -> tuple[Widget | None, Offset | None]:
933
+ """Get the Style at the given cell, the offset within the content.
934
+
935
+ Args:
936
+ x: X position within the Layout.
937
+ y: Y position within the Layout.
938
+
939
+ Returns:
940
+ A tuple of the widget at (x, y) and the offset within the widget.
941
+ """
942
+ try:
943
+ widget, region = self.get_widget_at(x, y)
944
+ except errors.NoWidget:
945
+ return None, None
946
+ if widget not in self.visible_widgets:
947
+ return None, None
948
+
949
+ if y >= widget.content_region.bottom:
950
+ x, y = widget.content_region.bottom_right_inclusive
951
+
952
+ gutter_left, gutter_right = widget.gutter.top_left
953
+ x -= region.x + gutter_left
954
+ y -= region.y + gutter_right
955
+
956
+ if y < 0:
957
+ return None, None
958
+
959
+ if x < 0:
960
+ return widget, Offset(0, y)
961
+
962
+ visible_screen_stack.set(widget.app._background_screens)
963
+ line = widget.render_line(y)
964
+
965
+ end = 0
966
+ start = 0
967
+ offset_y: int | None = None
968
+ offset_x = 0
969
+ offset_x2 = 0
970
+
971
+ from rich.cells import get_character_cell_size
972
+
973
+ offset: Offset | None = None
974
+ for segment in line:
975
+ end += segment.cell_length
976
+ style = segment.style
977
+ if style is not None and style._meta is not None:
978
+ meta = style.meta
979
+ if "offset" in meta:
980
+ offset_x, offset_y = meta["offset"]
981
+ if offset_y is None:
982
+ continue
983
+ offset_map = meta.get("offset_map")
984
+ if offset_map:
985
+ offset_x2 = offset_map[-1]
986
+ else:
987
+ offset_map = None
988
+ offset_x2 = offset_x + len(segment.text)
989
+
990
+ if x < end and x >= start:
991
+ segment_cell_length = 0
992
+ cell_cut = x - start
993
+ segment_offset = 0
994
+ for character in segment.text:
995
+ if segment_cell_length >= cell_cut:
996
+ break
997
+ segment_cell_length += get_character_cell_size(character)
998
+ segment_offset += 1
999
+ if offset_map is not None:
1000
+ segment_offset = min(segment_offset, len(offset_map) - 1)
1001
+ offset = Offset(offset_map[segment_offset], offset_y)
1002
+ break
1003
+ offset = Offset(offset_x + segment_offset, offset_y)
1004
+ break
1005
+ start = end
1006
+
1007
+ if offset is None and offset_y is not None:
1008
+ offset = Offset(offset_x2, offset_y)
1009
+ return widget, offset'''
1010
+
1011
+
1012
+ _STRIP_APPLY_OFFSETS_OLD = '''\
1013
+ def apply_offsets(self, x: int, y: int) -> Strip:
1014
+ """Apply offsets used in text selection.
1015
+
1016
+ Args:
1017
+ x: Offset on X axis (column).
1018
+ y: Offset on Y axis (row).
1019
+
1020
+ Returns:
1021
+ New strip.
1022
+ """
1023
+ cache_key = (x, y)
1024
+ if (cached_strip := self._offsets_cache.get(cache_key)) is not None:
1025
+ return cached_strip
1026
+ segments = self._segments
1027
+ strip_segments: list[Segment] = []
1028
+ for segment in segments:
1029
+ text, style, _ = segment
1030
+ offset_style = Style.from_meta({"offset": (x, y)})
1031
+ strip_segments.append(
1032
+ Segment(text, style + offset_style if style else offset_style)
1033
+ )
1034
+ x += len(segment.text)
1035
+ strip = Strip(strip_segments, self._cell_length)
1036
+ strip._render_cache = self._render_cache
1037
+ self._offsets_cache[cache_key] = strip
1038
+ return strip'''
1039
+
1040
+ _STRIP_APPLY_OFFSETS_NEW = '''\
1041
+ def apply_offsets(self, x: int, y: int) -> Strip:
1042
+ """Apply offsets used in text selection.
1043
+
1044
+ Args:
1045
+ x: Offset on X axis (column).
1046
+ y: Offset on Y axis (row).
1047
+
1048
+ Returns:
1049
+ New strip.
1050
+ """
1051
+ cache_key = (x, y)
1052
+ if (cached_strip := self._offsets_cache.get(cache_key)) is not None:
1053
+ return cached_strip
1054
+ segments = self._segments
1055
+ strip_segments: list[Segment] = []
1056
+ for segment in segments:
1057
+ text, style, _ = segment
1058
+ offset_meta: dict[str, object] = {"offset": (x, y)}
1059
+ if (
1060
+ style is not None
1061
+ and style._meta is not None
1062
+ and "offset_map" in style.meta
1063
+ ):
1064
+ # apply_offsets is used by line-API widgets that extract
1065
+ # selection text from rendered/cropped strips. Rebase any
1066
+ # source map to keep that rendered-offset contract.
1067
+ offset_meta["offset_map"] = tuple(range(x, x + len(text) + 1))
1068
+ offset_style = Style.from_meta(offset_meta)
1069
+ strip_segments.append(
1070
+ Segment(text, style + offset_style if style else offset_style)
1071
+ )
1072
+ x += len(segment.text)
1073
+ strip = Strip(strip_segments, self._cell_length)
1074
+ strip._render_cache = self._render_cache
1075
+ self._offsets_cache[cache_key] = strip
1076
+ return strip'''
1077
+
1078
+
1079
+ def _expand_tabs_with_offsets(line: Any, tab_size: int) -> tuple[Any, tuple[int, ...] | None]:
1080
+ """Runtime copy of the patched Textual tab expansion helper."""
1081
+ if "\t" not in line.plain:
1082
+ return line, None
1083
+
1084
+ from textual.content import EMPTY_CONTENT, Content
1085
+
1086
+ if not line._spans:
1087
+ # Preserve Textual's unstyled Content.expand_tabs behavior: it delegates
1088
+ # to str.expandtabs(), whose tab stops are character-position based.
1089
+ source_offsets: list[int] = []
1090
+ source_position = 0
1091
+ cell_position = 0
1092
+ for character in line.plain:
1093
+ if character == "\t":
1094
+ spaces = 0 if tab_size <= 0 else tab_size - cell_position % tab_size
1095
+ source_offsets.extend([source_position] * spaces)
1096
+ cell_position += spaces
1097
+ else:
1098
+ source_offsets.append(source_position)
1099
+ cell_position += 1
1100
+ source_position += 1
1101
+ source_offsets.append(source_position)
1102
+ return Content(line.plain.expandtabs(tab_size)), tuple(source_offsets)
1103
+
1104
+ new_text: list[Any] = []
1105
+ source_offsets: list[int] = []
1106
+ append = new_text.append
1107
+ rendered_position = 0
1108
+ source_position = 0
1109
+
1110
+ for part in line.split("\t", include_separator=True):
1111
+ part_text = part.plain
1112
+ if part_text.endswith("\t"):
1113
+ text_before_tab = part_text[:-1]
1114
+ source_offsets.extend(range(source_position, source_position + len(text_before_tab)))
1115
+ tab_source_offset = source_position + len(text_before_tab)
1116
+ rendered_position += len(text_before_tab)
1117
+ spaces = 0 if tab_size <= 0 else tab_size - rendered_position % tab_size
1118
+ if spaces:
1119
+ part = Content(part._text[:-1] + " ", part._spans)
1120
+ if spaces > 1:
1121
+ part = part.extend_style(spaces - 1)
1122
+ else:
1123
+ from textual.content import Span
1124
+
1125
+ part = Content(
1126
+ part._text[:-1],
1127
+ [
1128
+ Span(span.start, min(span.end, len(text_before_tab)), span.style)
1129
+ for span in part._spans
1130
+ if min(span.end, len(text_before_tab)) > span.start
1131
+ ],
1132
+ )
1133
+ source_offsets.extend([tab_source_offset] * spaces)
1134
+ rendered_position += spaces
1135
+ else:
1136
+ source_offsets.extend(range(source_position, source_position + len(part_text)))
1137
+ rendered_position += len(part_text)
1138
+ source_position += len(part_text)
1139
+ append(part)
1140
+
1141
+ source_offsets.append(source_position)
1142
+ return EMPTY_CONTENT.join(new_text), tuple(source_offsets)
1143
+
1144
+
1145
+ def _pad_source_offsets(source_offsets: tuple[int, ...], left: int, right: int) -> tuple[int, ...]:
1146
+ """Pad source offsets for rendered line padding."""
1147
+ if not source_offsets:
1148
+ return source_offsets
1149
+ return (
1150
+ *([source_offsets[0]] * left),
1151
+ *source_offsets,
1152
+ *([source_offsets[-1]] * right),
1153
+ )
1154
+
1155
+
1156
+ def apply_runtime_patch() -> None:
1157
+ """Patch Textual's loaded selection/rendering classes in the current process."""
1158
+ try:
1159
+ import textual._compositor as compositor_mod
1160
+ import textual.content as content_mod
1161
+ import textual.strip as strip_mod
1162
+ import textual.style as style_mod
1163
+ except ImportError:
1164
+ return
1165
+
1166
+ # Check compatibility before dereferencing any of Textual's private APIs;
1167
+ # a future version may rename or remove them entirely.
1168
+ if not _runtime_compositor_patch_compatible():
1169
+ logger.warning(
1170
+ "Skipping Textual tab-selection runtime patch: loaded Textual is not the pinned %s that the patch targets.",
1171
+ _RUNTIME_PATCH_TEXTUAL_VERSION,
1172
+ )
1173
+ return
1174
+
1175
+ # Runtime reapplication is cheap; only skip when every patched target has
1176
+ # our marker so a partial external monkey patch can still be repaired.
1177
+ patch_targets = (
1178
+ getattr(content_mod, "_expand_tabs_with_offsets", None),
1179
+ content_mod._FormattedLine.__init__,
1180
+ content_mod._FormattedLine.to_strip,
1181
+ content_mod.Content._wrap_and_format,
1182
+ style_mod.Style.rich_style_with_offset,
1183
+ strip_mod.Strip.apply_offsets,
1184
+ compositor_mod.Compositor.get_widget_and_offset_at,
1185
+ )
1186
+ if all(getattr(target, _RUNTIME_PATCH_MARKER, False) for target in patch_targets):
1187
+ return
1188
+
1189
+ def rich_style_with_offset(self: Any, x: int, y: int, *, offset_map: Any = None) -> RichStyle:
1190
+ (
1191
+ background,
1192
+ foreground,
1193
+ bold,
1194
+ dim,
1195
+ italic,
1196
+ underline,
1197
+ underline2,
1198
+ reverse,
1199
+ strike,
1200
+ blink,
1201
+ link,
1202
+ _meta,
1203
+ ) = style_mod._get_simple_attributes(self)
1204
+ color = None if foreground is None else background + foreground
1205
+ meta = {**self.meta, "offset": (x, y)}
1206
+ if offset_map is not None:
1207
+ meta["offset_map"] = tuple(offset_map)
1208
+ return RichStyle(
1209
+ color=None if color is None else color.rich_color,
1210
+ bgcolor=None if background is None else background.rich_color,
1211
+ bold=bold,
1212
+ dim=dim,
1213
+ italic=italic,
1214
+ underline=underline,
1215
+ underline2=underline2,
1216
+ reverse=reverse,
1217
+ strike=strike,
1218
+ blink=blink,
1219
+ link=link,
1220
+ meta=meta,
1221
+ )
1222
+
1223
+ def formatted_line_init(
1224
+ self: Any,
1225
+ get_style: Any,
1226
+ content: Any,
1227
+ width: int,
1228
+ x: int = 0,
1229
+ y: int = 0,
1230
+ align: str = "left",
1231
+ line_end: bool = False,
1232
+ link_style: Any = None,
1233
+ source_offsets: tuple[int, ...] | None = None,
1234
+ ) -> None:
1235
+ self.get_style = get_style
1236
+ self.content = content
1237
+ self.width = width
1238
+ self.x = x
1239
+ self.y = y
1240
+ self.align = align
1241
+ self.line_end = line_end
1242
+ self.link_style = link_style
1243
+ self.source_offsets = source_offsets
1244
+
1245
+ def formatted_line_to_strip(self: Any, style: Any) -> tuple[list[Segment], int]:
1246
+ _Segment = Segment
1247
+ align = self.align
1248
+ width = self.width
1249
+ pad_left = pad_right = 0
1250
+ content = self.content
1251
+ x = self.x
1252
+ y = self.y
1253
+ get_style = self.get_style
1254
+
1255
+ if align in ("start", "left") or (align == "justify" and self.line_end):
1256
+ pass
1257
+
1258
+ elif align == "center":
1259
+ excess_space = width - self.content.cell_length
1260
+ pad_left = excess_space // 2
1261
+ pad_right = excess_space - pad_left
1262
+
1263
+ elif align in ("end", "right"):
1264
+ pad_left = width - self.content.cell_length
1265
+
1266
+ elif align == "justify":
1267
+ # Justification synthesizes word-gap cells. Chrys does not route raw
1268
+ # tabbed selectable content through justified widgets, so keep the
1269
+ # upstream rendered-offset behavior here. If this path ever needs
1270
+ # source maps, the synthetic gaps and len(text) + 1 advance below
1271
+ # need explicit map entries.
1272
+ words = content.split(" ", include_separator=False)
1273
+ words_size = sum(content_mod.cell_len(word.plain.rstrip(" ")) for word in words)
1274
+ num_spaces = len(words) - 1
1275
+ spaces = [1] * num_spaces
1276
+ index = 0
1277
+ if spaces:
1278
+ while words_size + num_spaces < width:
1279
+ spaces[len(spaces) - index - 1] += 1
1280
+ num_spaces += 1
1281
+ index = (index + 1) % len(spaces)
1282
+
1283
+ segments: list[Segment] = []
1284
+ add_segment = segments.append
1285
+ x = self.x
1286
+ for index, word in enumerate(words):
1287
+ for text, text_style in word.render(style, end="", parse_style=get_style):
1288
+ add_segment(_Segment(text, (style + text_style).rich_style_with_offset(x, y)))
1289
+ x += len(text) + 1
1290
+ if index < len(spaces) and (pad := spaces[index]):
1291
+ add_segment(_Segment(" " * pad, (style + text_style).rich_style))
1292
+
1293
+ return segments, width
1294
+
1295
+ segments = [_Segment(" " * pad_left, style.background_style.rich_style)] if pad_left else []
1296
+ add_segment = segments.append
1297
+ source_offsets = self.source_offsets
1298
+ content_offset = 0
1299
+
1300
+ for text, text_style in content.render(style, end="", parse_style=get_style):
1301
+ offset_map = None
1302
+ if source_offsets is not None:
1303
+ end_offset = content_offset + len(text) + 1
1304
+ offset_map = source_offsets[content_offset:end_offset]
1305
+ if len(offset_map) != len(text) + 1:
1306
+ offset_map = None
1307
+ add_segment(
1308
+ _Segment(
1309
+ text,
1310
+ (style + text_style).rich_style_with_offset(x, y, offset_map=offset_map),
1311
+ )
1312
+ )
1313
+ x += len(text)
1314
+ content_offset += len(text)
1315
+
1316
+ if pad_right:
1317
+ segments.append(_Segment(" " * pad_right, style.background_style.rich_style))
1318
+
1319
+ return (segments, content.cell_length + pad_left + pad_right)
1320
+
1321
+ def wrap_and_format(
1322
+ self: Any,
1323
+ width: int,
1324
+ align: str = "left",
1325
+ overflow: str = "fold",
1326
+ no_wrap: bool = False,
1327
+ line_pad: int = 0,
1328
+ tab_size: int = 8,
1329
+ selection: Any = None,
1330
+ selection_style: Any = None,
1331
+ post_style: Any = None,
1332
+ get_style: Any = style_mod.Style.parse,
1333
+ ) -> list[Any]:
1334
+ output_lines: list[Any] = []
1335
+
1336
+ if selection is not None:
1337
+ get_span = selection.get_span
1338
+ else:
1339
+
1340
+ def get_span(y: int) -> tuple[int, int] | None:
1341
+ return None
1342
+
1343
+ for y, line in enumerate(self.split(allow_blank=True)):
1344
+ if post_style is not None:
1345
+ line = line.stylize(post_style)
1346
+
1347
+ if selection_style is not None and (span := get_span(y)) is not None:
1348
+ start, end = span
1349
+ if end == -1:
1350
+ end = len(line.plain)
1351
+ line = line.stylize(selection_style, start, end)
1352
+
1353
+ line, source_offsets = _expand_tabs_with_offsets(line, tab_size)
1354
+
1355
+ if no_wrap:
1356
+ if overflow == "fold":
1357
+ cuts = list(range(0, line.cell_length, width))[1:]
1358
+ divided_lines = line.divide(cuts)
1359
+ divide_offsets = [0, *cuts, len(line.plain)]
1360
+ divided_source_offsets = (
1361
+ [source_offsets[start : end + 1] for start, end in pairwise(divide_offsets)]
1362
+ if source_offsets is not None
1363
+ else [None] * len(divided_lines)
1364
+ )
1365
+ new_lines = [
1366
+ content_mod._FormattedLine(
1367
+ get_style,
1368
+ divided_line,
1369
+ width,
1370
+ y=y,
1371
+ align=align,
1372
+ source_offsets=offset_source_offsets,
1373
+ )
1374
+ for divided_line, offset_source_offsets in zip(
1375
+ divided_lines,
1376
+ divided_source_offsets,
1377
+ strict=True,
1378
+ )
1379
+ ]
1380
+ else:
1381
+ line = line.truncate(width, ellipsis=overflow == "ellipsis")
1382
+ content_line = content_mod._FormattedLine(
1383
+ get_style,
1384
+ line,
1385
+ width,
1386
+ y=y,
1387
+ align=align,
1388
+ source_offsets=source_offsets,
1389
+ )
1390
+ new_lines = [content_line]
1391
+ else:
1392
+ content_line = content_mod._FormattedLine(
1393
+ get_style,
1394
+ line,
1395
+ width,
1396
+ y=y,
1397
+ align=align,
1398
+ source_offsets=source_offsets,
1399
+ )
1400
+ offsets = content_mod.divide_line(line.plain, width - line_pad * 2, fold=overflow == "fold")
1401
+ divided_lines = content_line.content.divide(offsets)
1402
+ divide_offsets = [0, *offsets, len(line.plain)]
1403
+ divided_source_offsets = (
1404
+ [source_offsets[start : end + 1] for start, end in pairwise(divide_offsets)]
1405
+ if source_offsets is not None
1406
+ else [None] * len(divided_lines)
1407
+ )
1408
+ ellipsis = overflow == "ellipsis"
1409
+ divided_lines = [
1410
+ (
1411
+ line.truncate(width, ellipsis=ellipsis)
1412
+ if last
1413
+ else line.rstrip().truncate(width, ellipsis=ellipsis)
1414
+ )
1415
+ for last, line in content_mod.loop_last(divided_lines)
1416
+ ]
1417
+
1418
+ new_lines = []
1419
+ for content, offset, offset_source_offsets in zip(
1420
+ divided_lines,
1421
+ [0, *offsets],
1422
+ divided_source_offsets,
1423
+ strict=True,
1424
+ ):
1425
+ content = content.rstrip_end(width)
1426
+ if offset_source_offsets is not None:
1427
+ offset_source_offsets = offset_source_offsets[: len(content.plain) + 1]
1428
+ offset_source_offsets = _pad_source_offsets(offset_source_offsets, line_pad, line_pad)
1429
+ new_lines.append(
1430
+ content_mod._FormattedLine(
1431
+ get_style,
1432
+ content.pad(line_pad, line_pad),
1433
+ width,
1434
+ offset,
1435
+ y,
1436
+ align=align,
1437
+ source_offsets=offset_source_offsets,
1438
+ )
1439
+ )
1440
+ new_lines[-1].line_end = True
1441
+
1442
+ output_lines.extend(new_lines)
1443
+
1444
+ return output_lines
1445
+
1446
+ def apply_offsets(self: Any, x: int, y: int) -> Any:
1447
+ cache_key = (x, y)
1448
+ if (cached_strip := self._offsets_cache.get(cache_key)) is not None:
1449
+ return cached_strip
1450
+ segments = self._segments
1451
+ strip_segments: list[Segment] = []
1452
+ for segment in segments:
1453
+ text, style, _ = segment
1454
+ offset_meta: dict[str, object] = {"offset": (x, y)}
1455
+ if style is not None and style._meta is not None and "offset_map" in style.meta:
1456
+ # apply_offsets is used by line-API widgets that extract selection
1457
+ # text from rendered/cropped strips. Rebase any source map to keep
1458
+ # that rendered-offset contract.
1459
+ offset_meta["offset_map"] = tuple(range(x, x + len(text) + 1))
1460
+ offset_style = strip_mod.Style.from_meta(offset_meta)
1461
+ strip_segments.append(Segment(text, style + offset_style if style else offset_style))
1462
+ x += len(segment.text)
1463
+ strip = strip_mod.Strip(strip_segments, self._cell_length)
1464
+ strip._render_cache = self._render_cache
1465
+ self._offsets_cache[cache_key] = strip
1466
+ return strip
1467
+
1468
+ def get_widget_and_offset_at(self: Any, x: int, y: int) -> tuple[Any | None, Any | None]:
1469
+ try:
1470
+ widget, region = self.get_widget_at(x, y)
1471
+ except compositor_mod.errors.NoWidget:
1472
+ return None, None
1473
+ if widget not in self.visible_widgets:
1474
+ return None, None
1475
+
1476
+ if y >= widget.content_region.bottom:
1477
+ x, y = widget.content_region.bottom_right_inclusive
1478
+
1479
+ gutter_left, gutter_right = widget.gutter.top_left
1480
+ x -= region.x + gutter_left
1481
+ y -= region.y + gutter_right
1482
+
1483
+ if y < 0:
1484
+ return None, None
1485
+
1486
+ if x < 0:
1487
+ return widget, compositor_mod.Offset(0, y)
1488
+
1489
+ compositor_mod.visible_screen_stack.set(widget.app._background_screens)
1490
+ line = widget.render_line(y)
1491
+
1492
+ end = 0
1493
+ start = 0
1494
+ offset_y: int | None = None
1495
+ offset_x = 0
1496
+ offset_x2 = 0
1497
+
1498
+ from rich.cells import get_character_cell_size
1499
+
1500
+ offset = None
1501
+ for segment in line:
1502
+ end += segment.cell_length
1503
+ style = segment.style
1504
+ if style is not None and style._meta is not None:
1505
+ meta = style.meta
1506
+ if "offset" in meta:
1507
+ offset_x, offset_y = meta["offset"]
1508
+ if offset_y is None:
1509
+ continue
1510
+ offset_map = meta.get("offset_map")
1511
+ if offset_map:
1512
+ offset_x2 = offset_map[-1]
1513
+ else:
1514
+ offset_map = None
1515
+ offset_x2 = offset_x + len(segment.text)
1516
+
1517
+ if x < end and x >= start:
1518
+ segment_cell_length = 0
1519
+ cell_cut = x - start
1520
+ segment_offset = 0
1521
+ for character in segment.text:
1522
+ if segment_cell_length >= cell_cut:
1523
+ break
1524
+ segment_cell_length += get_character_cell_size(character)
1525
+ segment_offset += 1
1526
+ if offset_map is not None:
1527
+ segment_offset = min(segment_offset, len(offset_map) - 1)
1528
+ offset = compositor_mod.Offset(offset_map[segment_offset], offset_y)
1529
+ break
1530
+ offset = compositor_mod.Offset(offset_x + segment_offset, offset_y)
1531
+ break
1532
+ start = end
1533
+
1534
+ if offset is None and offset_y is not None:
1535
+ offset = compositor_mod.Offset(offset_x2, offset_y)
1536
+ return widget, offset
1537
+
1538
+ patched_targets = (
1539
+ rich_style_with_offset,
1540
+ _expand_tabs_with_offsets,
1541
+ formatted_line_init,
1542
+ formatted_line_to_strip,
1543
+ wrap_and_format,
1544
+ apply_offsets,
1545
+ get_widget_and_offset_at,
1546
+ )
1547
+ for target in patched_targets:
1548
+ setattr(target, _RUNTIME_PATCH_MARKER, True)
1549
+
1550
+ style_mod.Style.rich_style_with_offset = rich_style_with_offset
1551
+ content_mod._expand_tabs_with_offsets = _expand_tabs_with_offsets
1552
+ content_mod._FormattedLine.__init__ = formatted_line_init
1553
+ content_mod._FormattedLine.to_strip = formatted_line_to_strip
1554
+ content_mod.Content._wrap_and_format = wrap_and_format
1555
+ strip_mod.Strip.apply_offsets = apply_offsets
1556
+ compositor_mod.Compositor.get_widget_and_offset_at = get_widget_and_offset_at
1557
+ setattr(content_mod.Content, _RUNTIME_PATCH_MARKER, True)
1558
+
1559
+
1560
+ register(
1561
+ FilePatch(
1562
+ package="textual",
1563
+ module_file="content.py",
1564
+ old_fragment=_CONTENT_HELPER_OLD,
1565
+ new_fragment=_CONTENT_HELPER_NEW,
1566
+ description="Add tab expansion source-offset mapping helper",
1567
+ equivalent_fragments=(_CONTENT_HELPER_PREVIOUS_STYLED_TAB_FRAGMENT,),
1568
+ ),
1569
+ FilePatch(
1570
+ package="textual",
1571
+ module_file="content.py",
1572
+ old_fragment=_CONTENT_HELPER_PREVIOUS_STYLED_TAB_FRAGMENT,
1573
+ new_fragment=_CONTENT_HELPER_STYLED_TAB_FRAGMENT_NEW,
1574
+ description="Upgrade styled tab expansion source-offset mapping",
1575
+ ),
1576
+ FilePatch(
1577
+ package="textual",
1578
+ module_file="content.py",
1579
+ old_fragment=_CONTENT_WRAP_AND_FORMAT_OLD,
1580
+ new_fragment=_CONTENT_WRAP_AND_FORMAT_NEW,
1581
+ description="Preserve tab source offsets through Textual Content wrapping",
1582
+ ),
1583
+ FilePatch(
1584
+ package="textual",
1585
+ module_file="content.py",
1586
+ old_fragment=_FORMATTED_LINE_OLD,
1587
+ new_fragment=_FORMATTED_LINE_NEW,
1588
+ description="Emit Textual tab source offset maps from formatted lines",
1589
+ ),
1590
+ FilePatch(
1591
+ package="textual",
1592
+ module_file="style.py",
1593
+ old_fragment=_STYLE_RICH_STYLE_WITH_OFFSET_OLD,
1594
+ new_fragment=_STYLE_RICH_STYLE_WITH_OFFSET_NEW,
1595
+ description="Accept source offset maps in Textual selection styles",
1596
+ ),
1597
+ FilePatch(
1598
+ package="textual",
1599
+ module_file="_compositor.py",
1600
+ old_fragment=_COMPOSITOR_GET_WIDGET_AND_OFFSET_AT_OLD,
1601
+ new_fragment=_COMPOSITOR_GET_WIDGET_AND_OFFSET_AT_NEW,
1602
+ description="Map Textual hit-test cells through tab source offsets",
1603
+ ),
1604
+ FilePatch(
1605
+ package="textual",
1606
+ module_file="strip.py",
1607
+ old_fragment=_STRIP_APPLY_OFFSETS_OLD,
1608
+ new_fragment=_STRIP_APPLY_OFFSETS_NEW,
1609
+ description="Rebase Textual offset maps when applying strip offsets",
1610
+ ),
1611
+ )