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,918 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Engine-internal companion module for rollback orchestration.
4
+
5
+ Pure rollback-file helpers live in :mod:`chrys.service.mutations.snapshot_files`.
6
+ This module owns the engine-facing workflow: validate the requested
7
+ target, optionally revert file mutations, promote a saved snapshot, and
8
+ reload the session while suppressing stale in-memory saves.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ import contextlib
15
+ import json
16
+ import logging
17
+ from collections.abc import Awaitable, Callable
18
+ from dataclasses import dataclass
19
+ from pathlib import Path
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ from chrys.foundation.events.types import Error, RollbackResult, SessionRestore, UserRollback, Warning
23
+ from chrys.foundation.i18n import DisplayBlock, msg
24
+ from chrys.foundation.util.async_tasks import await_task_quiescence
25
+ from chrys.foundation.util.lock import FileLock
26
+ from chrys.orchestration.engine.state.machine import EngineState
27
+ from chrys.service.mutations.snapshot_files import (
28
+ rollback_snapshot_for_target,
29
+ rollback_snapshot_paths,
30
+ snapshot_target_turn,
31
+ snapshot_target_turns,
32
+ write_rollback_snapshot,
33
+ )
34
+ from chrys.service.mutations.snapshot_files import (
35
+ turn_prompt_previews as collect_turn_prompt_previews,
36
+ )
37
+ from chrys.service.mutations.workspace_changes import format_partial_revert_notice, format_retained_changes_notice
38
+ from chrys.service.state.store import SESSION_BACKUP_FILE_NAME, SESSION_RECOVERY_FILE_NAME, recorded_surface
39
+
40
+ if TYPE_CHECKING:
41
+ from chrys.foundation.config.settings_store import SettingsHandle
42
+ from chrys.foundation.events.bus import EventBus
43
+ from chrys.orchestration.engine.run.turn_state import TurnRuntimeState
44
+ from chrys.orchestration.engine.session_lifecycle import SessionLifecycle
45
+ from chrys.orchestration.engine.state.active_session import ActiveSession
46
+ from chrys.orchestration.engine.state.current_agent import CurrentAgent
47
+ from chrys.orchestration.engine.state.lifecycle_permits import LifecyclePermits
48
+ from chrys.orchestration.engine.state.machine import EngineStateMachine
49
+ from chrys.orchestration.engine.state.session_writer import SessionWriter
50
+ from chrys.orchestration.engine.trajectory import TrajectoryRecorder
51
+ from chrys.service.mutations.workspace_changes import WorkspaceChangeTracker
52
+ from chrys.service.session.history import SessionHistoryManager
53
+
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+ _ROLLBACK_NO_SESSION = msg(
58
+ "rollback.no_session",
59
+ fallback="No active session to roll back.",
60
+ )
61
+ _ROLLBACK_CONVERSATION_CHANGED = msg(
62
+ "rollback.conversation_changed",
63
+ fallback="Rollback cancelled because the conversation changed after the picker was loaded.",
64
+ )
65
+ _ROLLBACK_CONVERSATION_ADVANCED = msg(
66
+ "rollback.conversation_advanced",
67
+ fallback="Rollback cancelled because the conversation advanced from turn {expected_turn} to turn {current_turn}.",
68
+ )
69
+ _ROLLBACK_RUNTIME_CHANGED = msg(
70
+ "rollback.runtime_changed",
71
+ fallback="Rollback cancelled because the workspace or runtime changed after the picker was loaded.",
72
+ )
73
+ _ROLLBACK_RELATIVE_TURNS_INVALID = msg(
74
+ "rollback.relative_turns_invalid",
75
+ fallback="relative_turns must be positive.",
76
+ )
77
+ _ROLLBACK_TURNS_UNAVAILABLE = msg(
78
+ "rollback.turns_unavailable",
79
+ fallback="Cannot roll back {requested_turns} turns; the session currently has {current_turns}.",
80
+ )
81
+ _ROLLBACK_TARGET_TURN_INVALID = msg(
82
+ "rollback.target_turn_invalid",
83
+ fallback="target_turn must be >= 0.",
84
+ )
85
+ _ROLLBACK_TURN_UNAVAILABLE = msg(
86
+ "rollback.turn_unavailable",
87
+ fallback="Cannot roll back to turn {target_turn}; available turns: {available}",
88
+ )
89
+ _ROLLBACK_RESET_FAILED = msg(
90
+ "rollback.reset_failed",
91
+ fallback="Rollback to welcome could not reset the session because the session state is busy.",
92
+ )
93
+ _ROLLBACK_SNAPSHOT_MISSING = msg(
94
+ "rollback.snapshot_missing",
95
+ fallback="Snapshot for turn {target_turn} is missing.",
96
+ )
97
+ _ROLLBACK_SWAP_LOCKED = msg(
98
+ "rollback.swap_locked",
99
+ fallback="Timed out waiting for session lock: {detail}",
100
+ multiline=True,
101
+ )
102
+ _ROLLBACK_SWAP_FAILED = msg(
103
+ "rollback.swap_failed",
104
+ fallback="Failed to restore snapshot: {detail}",
105
+ multiline=True,
106
+ )
107
+ _ROLLBACK_SESSION_CHANGED = msg(
108
+ "rollback.session_changed",
109
+ fallback="Rollback cancelled because the active session changed.",
110
+ )
111
+ _ROLLBACK_REFUSED = msg(
112
+ "rollback.refused",
113
+ fallback="Rollback is not allowed in state {state}.",
114
+ )
115
+
116
+
117
+ @dataclass(slots=True)
118
+ class _RollbackLockLease:
119
+ """Idempotent owner for a pre-commit rollback write lock."""
120
+
121
+ lock: FileLock | None = None
122
+
123
+ @property
124
+ def held(self) -> bool:
125
+ """Return whether the lease still owns its lock."""
126
+ return self.lock is not None
127
+
128
+ def release(self) -> None:
129
+ """Release the lock once, tolerating defensive cleanup paths."""
130
+ lock = self.lock
131
+ self.lock = None
132
+ if lock is not None:
133
+ lock.release()
134
+
135
+
136
+ def _read_envelope_meta(path: Path) -> dict[str, Any]:
137
+ """The envelope's ``meta``; empty when the file is missing or unreadable."""
138
+ try:
139
+ envelope = json.loads(path.read_text(encoding="utf-8"))
140
+ except OSError, ValueError:
141
+ return {}
142
+ meta = envelope.get("meta") if isinstance(envelope, dict) else None
143
+ return meta if isinstance(meta, dict) else {}
144
+
145
+
146
+ def _read_session_overlays(session_file: Path) -> dict[str, str]:
147
+ """Capture session-scoped meta before a snapshot overwrites ``session.json``.
148
+
149
+ These describe the session, not the turns it holds, so the current
150
+ value wins over whatever the snapshot carried: ``custom_title`` is the
151
+ user's pin (or explicit clear — an empty string must survive too, or a
152
+ pre-clear snapshot would resurrect the pin), and ``last_surface`` names
153
+ the surface whose turn last worked in the session, which a rollback,
154
+ not being a turn, never changes. That turn may be one a crash cut
155
+ short, recorded only in a newer sidecar the restore after the swap
156
+ discards, so the surface is read as the store would read it.
157
+ ``generated_title`` is deliberately NOT preserved: it summarizes the
158
+ conversation, and after a rollback the snapshot's own value is the one
159
+ that describes the restored turns — the newer title describes history
160
+ that no longer exists. Each value is captured verbatim; one the files
161
+ never recorded (or an unreadable envelope) yields nothing for it.
162
+ """
163
+ meta = _read_envelope_meta(session_file)
164
+ overlays = {"custom_title": value} if isinstance(value := meta.get("custom_title"), str) else {}
165
+ surface = recorded_surface(meta, _read_envelope_meta(session_file.with_name(SESSION_RECOVERY_FILE_NAME)))
166
+ if surface is not None:
167
+ overlays["last_surface"] = surface
168
+ return overlays
169
+
170
+
171
+ def _reapply_session_overlays(session_file: Path, overlays: dict[str, str]) -> None:
172
+ """Merge preserved session-scoped meta back into the restored snapshot, best-effort."""
173
+ if not overlays:
174
+ return
175
+ from chrys.foundation.platform.files import atomic_write_text
176
+
177
+ try:
178
+ envelope = json.loads(session_file.read_text(encoding="utf-8"))
179
+ meta = envelope.get("meta") if isinstance(envelope, dict) else None
180
+ if not isinstance(meta, dict):
181
+ return
182
+ meta.update(overlays)
183
+ atomic_write_text(session_file, json.dumps(envelope, indent=2, ensure_ascii=False))
184
+ except OSError, ValueError:
185
+ logger.warning("Failed to preserve session metadata across rollback for %s", session_file, exc_info=True)
186
+
187
+
188
+ def capture_snapshot_writer(session: ActiveSession, settings_handle: SettingsHandle) -> Callable[[], None]:
189
+ """Freeze snapshot metadata while deferring path resolution and file I/O."""
190
+ session_id = session.session_id
191
+ turn_number = session.turn_number
192
+ keep = settings_handle.settings.rollback_snapshots_keep
193
+ session_dir_for = session.session_dir_for
194
+ lock_path_for = session.session_write_lock_path
195
+
196
+ def write() -> None:
197
+ session_dir = session_dir_for(session_id) if session_id else None
198
+ lock_path = lock_path_for(session_id) if session_id else None
199
+ write_rollback_snapshot(
200
+ session_dir=session_dir,
201
+ session_id=session_id,
202
+ turn_number=turn_number,
203
+ keep=keep,
204
+ lock_path=lock_path,
205
+ )
206
+
207
+ return write
208
+
209
+
210
+ class RollbackController:
211
+ """Validates rollback requests and restores session and workspace state."""
212
+
213
+ def __init__(
214
+ self,
215
+ *,
216
+ session: ActiveSession,
217
+ current: CurrentAgent,
218
+ permits: LifecyclePermits,
219
+ writer: SessionWriter,
220
+ turn_state: TurnRuntimeState,
221
+ lifecycle: SessionLifecycle,
222
+ bus: EventBus,
223
+ history: SessionHistoryManager,
224
+ fsm: EngineStateMachine,
225
+ workspace_change_tracker: WorkspaceChangeTracker,
226
+ trajectory_recorder: TrajectoryRecorder,
227
+ settings_handle: SettingsHandle,
228
+ ) -> None:
229
+ self._session = session
230
+ self._current = current
231
+ self._permits = permits
232
+ self._writer = writer
233
+ self._turn_state = turn_state
234
+ self._lifecycle = lifecycle
235
+ self._bus = bus
236
+ self._history = history
237
+ self._fsm = fsm
238
+ self._workspace_change_tracker = workspace_change_tracker
239
+ self._trajectory_recorder = trajectory_recorder
240
+ self._settings_handle = settings_handle
241
+
242
+ async def begin_rollback_projection(
243
+ self,
244
+ *,
245
+ session_id: str | None,
246
+ session_generation: int,
247
+ ) -> str | None:
248
+ """Fence prompt admission while the TUI projects live rollback state."""
249
+ if self._permits.session_generation != session_generation:
250
+ return None
251
+ if session_id is not None and self._session.session_id != session_id:
252
+ return None
253
+ return await self._permits.prepare_session_transition_if_current(
254
+ "rollback_projection",
255
+ session_id=self._session.session_id,
256
+ session_generation=session_generation,
257
+ )
258
+
259
+ def finish_rollback_projection(self, owner: str) -> None:
260
+ """Release a read-only rollback projection fence without committing it."""
261
+ self._permits.finish_session_transition(owner)
262
+
263
+ def write_snapshot(self) -> None:
264
+ """Copy the current ``session.json`` to ``snapshots/turn_N.json``."""
265
+ capture_snapshot_writer(self._session, self._settings_handle)()
266
+
267
+ def turn_prompt_previews(self) -> dict[int, str]:
268
+ """Return ``{turn_number: first_user_prompt}`` for every known turn."""
269
+ if not self._history.is_bound:
270
+ return {}
271
+ blocks = self._history.state.get("compressed_msgs", []) or []
272
+ return collect_turn_prompt_previews(list(self._history.messages), blocks)
273
+
274
+ def first_rolled_back_user_text(self, target_turn: int) -> str:
275
+ """Return the first user prompt that rollback to ``target_turn`` discards."""
276
+ previews = self.turn_prompt_previews()
277
+ discarded_turns = [turn for turn in previews if turn > target_turn]
278
+ return previews[min(discarded_turns)] if discarded_turns else ""
279
+
280
+ def available_turns(self) -> list[int]:
281
+ """Return turn-counts that can be targeted by a rollback."""
282
+ session_dir = self._session.session_dir
283
+ if session_dir is None:
284
+ return []
285
+ turns = snapshot_target_turns(session_dir)
286
+ current_turn = self._session.turn_number
287
+ if current_turn > 0:
288
+ turns = {turn for turn in turns if turn < current_turn}
289
+ if (self._session.mutation_tracker is not None and self._session.mutation_tracker.get_all_turns()) or (
290
+ self._history.is_bound and self._history.messages
291
+ ):
292
+ turns.add(0)
293
+ if 0 not in turns:
294
+ # No welcome anchor means every remaining snapshot target would be
295
+ # orphaned in the picker.
296
+ return []
297
+ return sorted(turns)
298
+
299
+ async def on_user_rollback(
300
+ self,
301
+ event: UserRollback,
302
+ *,
303
+ atomic_copy_file: Callable[[Path, Path], None],
304
+ lock_timeout_seconds: float,
305
+ ) -> None:
306
+ """Handle rollback under an exclusive prompt/session-transition boundary."""
307
+ sid = self._session.session_id
308
+ session_generation = self._permits.session_generation
309
+ if sid is None:
310
+ await self._bus.publish(
311
+ Warning(
312
+ code="rollback_no_session",
313
+ message="No active session to roll back.",
314
+ display_message=_ROLLBACK_NO_SESSION.bind(),
315
+ ),
316
+ )
317
+ return
318
+ if await self._cancel_if_rollback_owner_changed(event, sid, session_generation):
319
+ return
320
+ if await self._refuse_if_rollback_state_invalid(sid):
321
+ return
322
+
323
+ transition_owner = await self._permits.prepare_session_transition_if_current(
324
+ "rollback",
325
+ session_id=sid,
326
+ session_generation=session_generation,
327
+ )
328
+ if transition_owner is None:
329
+ await self._cancel_if_rollback_owner_changed(event, sid, session_generation)
330
+ return
331
+ try:
332
+ prepared = await self._prepare_user_rollback_with_permit(
333
+ event,
334
+ sid=sid,
335
+ session_generation=session_generation,
336
+ )
337
+ if prepared is None:
338
+ return
339
+ target_turn, rolled_back_user_text = prepared
340
+ await self._execute_user_rollback_with_permit(
341
+ event,
342
+ sid=sid,
343
+ session_generation=session_generation,
344
+ transition_owner=transition_owner,
345
+ target_turn=target_turn,
346
+ rolled_back_user_text=rolled_back_user_text,
347
+ atomic_copy_file=atomic_copy_file,
348
+ lock_timeout_seconds=lock_timeout_seconds,
349
+ )
350
+ finally:
351
+ self._permits.finish_session_transition(transition_owner)
352
+
353
+ async def _prepare_user_rollback_with_permit(
354
+ self,
355
+ event: UserRollback,
356
+ *,
357
+ sid: str,
358
+ session_generation: int,
359
+ ) -> tuple[int, str] | None:
360
+ """Quiesce and validate rollback without invalidating the current generation."""
361
+ lifecycle_task = self._turn_state.lease.run_task
362
+ await self._permits.wait_for_agent_load_idle()
363
+ target_turn = event.target_turn
364
+ if await self._cancel_if_rollback_owner_changed(event, sid, session_generation):
365
+ return None
366
+ if await self._refuse_if_rollback_state_invalid(sid):
367
+ return None
368
+
369
+ # IDLE is reached before final persistence and after-turn hooks finish.
370
+ # New admission is fenced and older reserved admissions have drained, so
371
+ # this task cannot be replaced while rollback waits for durable quiescence.
372
+ await await_task_quiescence(lifecycle_task)
373
+ if await self._cancel_if_rollback_owner_changed(event, sid, session_generation):
374
+ return None
375
+ if await self._refuse_if_rollback_state_invalid(sid):
376
+ return None
377
+
378
+ if (
379
+ event.expected_conversation_revision is not None
380
+ and self._turn_state.lease.conversation_revision != event.expected_conversation_revision
381
+ ):
382
+ await self._bus.publish(
383
+ Warning(
384
+ code="rollback_conversation_changed",
385
+ message="Rollback cancelled because the conversation changed after the picker was loaded.",
386
+ display_message=_ROLLBACK_CONVERSATION_CHANGED.bind(),
387
+ session_id=sid,
388
+ ),
389
+ )
390
+ return None
391
+
392
+ if event.expected_current_turn is not None and self._session.turn_number != event.expected_current_turn:
393
+ await self._bus.publish(
394
+ Warning(
395
+ code="rollback_conversation_changed",
396
+ message=(
397
+ "Rollback cancelled because the conversation advanced "
398
+ f"from turn {event.expected_current_turn} to turn {self._session.turn_number}."
399
+ ),
400
+ display_message=_ROLLBACK_CONVERSATION_ADVANCED.bind(
401
+ expected_turn=event.expected_current_turn,
402
+ current_turn=self._session.turn_number,
403
+ ),
404
+ session_id=sid,
405
+ ),
406
+ )
407
+ return None
408
+
409
+ if (
410
+ event.expected_build_generation is not None
411
+ and self._permits.build_generation != event.expected_build_generation
412
+ ) or (
413
+ event.expected_workspace_cwd is not None and self._session.workspace_cwd() != event.expected_workspace_cwd
414
+ ):
415
+ await self._bus.publish(
416
+ Warning(
417
+ code="rollback_runtime_changed",
418
+ message="Rollback cancelled because the workspace or runtime changed after the picker was loaded.",
419
+ display_message=_ROLLBACK_RUNTIME_CHANGED.bind(),
420
+ session_id=sid,
421
+ ),
422
+ )
423
+ return None
424
+
425
+ if event.relative_turns is not None:
426
+ if event.relative_turns <= 0:
427
+ await self._bus.publish(
428
+ Warning(
429
+ code="rollback_invalid_turn",
430
+ message="relative_turns must be positive.",
431
+ display_message=_ROLLBACK_RELATIVE_TURNS_INVALID.bind(),
432
+ session_id=sid,
433
+ ),
434
+ )
435
+ return None
436
+ target_turn = self._session.turn_number - event.relative_turns
437
+ if target_turn < 0:
438
+ await self._bus.publish(
439
+ Warning(
440
+ code="rollback_unavailable",
441
+ message=(
442
+ f"Cannot roll back {event.relative_turns} turns; "
443
+ f"the session currently has {self._session.turn_number}."
444
+ ),
445
+ display_message=_ROLLBACK_TURNS_UNAVAILABLE.bind(
446
+ requested_turns=event.relative_turns,
447
+ current_turns=self._session.turn_number,
448
+ ),
449
+ session_id=sid,
450
+ ),
451
+ )
452
+ return None
453
+
454
+ if target_turn < 0:
455
+ await self._bus.publish(
456
+ Warning(
457
+ code="rollback_invalid_turn",
458
+ message="target_turn must be >= 0.",
459
+ display_message=_ROLLBACK_TARGET_TURN_INVALID.bind(),
460
+ session_id=sid,
461
+ ),
462
+ )
463
+ return None
464
+
465
+ available = self.available_turns()
466
+ if target_turn not in available:
467
+ await self._bus.publish(
468
+ Warning(
469
+ code="rollback_unavailable",
470
+ message=f"Cannot roll back to turn {target_turn}; available turns: {available}",
471
+ display_message=_ROLLBACK_TURN_UNAVAILABLE.bind(
472
+ target_turn=target_turn,
473
+ available=str(available),
474
+ ),
475
+ session_id=sid,
476
+ ),
477
+ )
478
+ return None
479
+
480
+ return target_turn, self.first_rolled_back_user_text(target_turn)
481
+
482
+ async def _execute_user_rollback_with_permit(
483
+ self,
484
+ event: UserRollback,
485
+ *,
486
+ sid: str,
487
+ session_generation: int,
488
+ transition_owner: str,
489
+ target_turn: int,
490
+ rolled_back_user_text: str,
491
+ atomic_copy_file: Callable[[Path, Path], None],
492
+ lock_timeout_seconds: float,
493
+ ) -> None:
494
+ """Secure rollback resources, then commit immediately before mutation."""
495
+
496
+ welcome_reset_lock: FileLock | None = None
497
+ if target_turn == 0:
498
+ await self._writer.flush()
499
+ reset_lock_acquired, welcome_reset_lock = self._acquire_welcome_reset_write_lock(sid, lock_timeout_seconds)
500
+ if not reset_lock_acquired:
501
+ await self._bus.publish(
502
+ Error(
503
+ code="rollback_reset_failed",
504
+ message="Rollback to welcome could not reset the session because the session state is busy.",
505
+ display_message=_ROLLBACK_RESET_FAILED.bind(),
506
+ session_id=sid,
507
+ ),
508
+ )
509
+ return
510
+
511
+ welcome_lock_lease = _RollbackLockLease(welcome_reset_lock)
512
+ try:
513
+ await self._execute_user_rollback_after_resource_preflight(
514
+ event,
515
+ sid=sid,
516
+ session_generation=session_generation,
517
+ transition_owner=transition_owner,
518
+ target_turn=target_turn,
519
+ rolled_back_user_text=rolled_back_user_text,
520
+ atomic_copy_file=atomic_copy_file,
521
+ lock_timeout_seconds=lock_timeout_seconds,
522
+ welcome_lock_lease=welcome_lock_lease,
523
+ )
524
+ finally:
525
+ welcome_lock_lease.release()
526
+
527
+ async def _execute_user_rollback_after_resource_preflight(
528
+ self,
529
+ event: UserRollback,
530
+ *,
531
+ sid: str,
532
+ session_generation: int,
533
+ transition_owner: str,
534
+ target_turn: int,
535
+ rolled_back_user_text: str,
536
+ atomic_copy_file: Callable[[Path, Path], None],
537
+ lock_timeout_seconds: float,
538
+ welcome_lock_lease: _RollbackLockLease,
539
+ ) -> None:
540
+ """Build the rollback plan and mutate only after secured preflight resources."""
541
+
542
+ tracker = self._session.mutation_tracker
543
+ tracker_turn_ids: list[int] = sorted(t.turn_id for t in tracker.get_all_turns()) if tracker is not None else []
544
+ retained_notice = (
545
+ format_retained_changes_notice(
546
+ tracker,
547
+ cwd=self._session.workspace_cwd(),
548
+ max_entries=self._settings_handle.settings.workspace_change_notice_max_entries,
549
+ )
550
+ if target_turn == 0 and not event.revert_changes
551
+ else None
552
+ )
553
+
554
+ restore_results: list = []
555
+ plan_exclusions: list[tuple[str, str]] = []
556
+ plan_warnings: list[str] = []
557
+ plan_candidates: list[str] = []
558
+ revert_detection_truncated = False
559
+ welcome_file_rollback: Callable[[], Awaitable[None]] | None = None
560
+ post_swap_file_rollback: Callable[[], None] | None = None
561
+ if event.revert_changes and tracker is not None and tracker_turn_ids:
562
+ rollback_turn_ids = {tid for tid in tracker_turn_ids if tid > target_turn}
563
+ only_paths: set[str] | None = set(event.selected_paths) if event.selected_paths else None
564
+ if rollback_turn_ids:
565
+ # Truncated detection means the rolled-back turns may hold
566
+ # writes that never became plan candidates; the flag lives on
567
+ # the turns rollback_turns removes, so capture it here.
568
+ revert_detection_truncated = any(
569
+ turn.detection_truncated for turn in tracker.get_all_turns() if turn.turn_id in rollback_turn_ids
570
+ )
571
+ try:
572
+ # Authoritative peer re-check before the only destructive
573
+ # consumer acts: force-reclassify against the coordination
574
+ # registry (a peer's claim may have landed after our
575
+ # finalize), then let the coordinator exclude paths a peer
576
+ # modified since and warn about in-flight peer commands.
577
+ # Coordination failures must not break rollback itself.
578
+ coordinator = self._session.mutation_coordinator
579
+ loop = asyncio.get_running_loop()
580
+ # The workspace cwd doubles as the registry fallback root:
581
+ # outside a git repo, claims were published under it, and
582
+ # the destructive rollback-time check must rediscover the
583
+ # same peer files.
584
+ workspace_cwd = self._session.workspace_cwd()
585
+ if coordinator is not None:
586
+ try:
587
+ await loop.run_in_executor(
588
+ None,
589
+ lambda: coordinator.reclassify(tracker, force=True, fallback_root=workspace_cwd),
590
+ )
591
+ except Exception:
592
+ logger.debug("Pre-rollback attribution re-check failed", exc_info=True)
593
+ # Build the plan FIRST: rollback_turns removes the turns
594
+ # it rolls back, so exclusions/warnings cannot be
595
+ # reconstructed afterwards — execute from the pre-built
596
+ # plan and thread its report into the result event.
597
+ plan = tracker.get_rollback_plan_for_turns(rollback_turn_ids)
598
+ if coordinator is not None:
599
+ try:
600
+ plan = await loop.run_in_executor(
601
+ None,
602
+ lambda: coordinator.augment_rollback_plan(
603
+ tracker,
604
+ plan,
605
+ scope_paths=[workspace_cwd],
606
+ fallback_root=workspace_cwd,
607
+ ),
608
+ )
609
+ except Exception:
610
+ logger.debug("Rollback plan peer augmentation failed", exc_info=True)
611
+ plan_exclusions = [(path, reason.value) for path, reason in plan.exclusions]
612
+ plan_warnings = [w.message for w in plan.warnings]
613
+ # Every path the rolled-back turns touched — entries the
614
+ # restore will attempt plus exclusions it never will. The
615
+ # turns are removed during execution, so the retained set
616
+ # can only be derived from this pre-built list afterwards.
617
+ plan_candidates = [path for path, _ in plan.entries] + [path for path, _ in plan.exclusions]
618
+
619
+ def _apply_file_rollback() -> None:
620
+ nonlocal restore_results
621
+ try:
622
+ restore_results = tracker.rollback_turns(
623
+ rollback_turn_ids,
624
+ only_paths=only_paths,
625
+ plan=plan,
626
+ )
627
+ except Exception:
628
+ logger.exception("Rollback file-restore failed (target_turn=%d)", target_turn)
629
+ restore_results = []
630
+
631
+ if target_turn == 0:
632
+
633
+ async def _apply_welcome_file_rollback() -> None:
634
+ _apply_file_rollback()
635
+
636
+ welcome_file_rollback = _apply_welcome_file_rollback
637
+ else:
638
+ post_swap_file_rollback = _apply_file_rollback
639
+ except Exception:
640
+ logger.exception("Rollback file-restore failed (target_turn=%d)", target_turn)
641
+ restore_results = []
642
+
643
+ if await self._cancel_if_rollback_owner_changed(event, sid, session_generation):
644
+ return
645
+
646
+ session_dir = self._session.session_dir
647
+ if target_turn == 0:
648
+ # Keep the old registry for the audit record: a successful reset
649
+ # replaces the live history before the rollback event is committed.
650
+ rollback_history_state = self._history.state if self._history.is_bound else None
651
+ self._permits.commit_session_transition(transition_owner)
652
+
653
+ try:
654
+ reset_succeeded = await self._lifecycle.reset_session_to_welcome(
655
+ sid,
656
+ write_lock_held=welcome_lock_lease.held,
657
+ after_delete=welcome_file_rollback,
658
+ before_restart=welcome_lock_lease.release,
659
+ )
660
+ finally:
661
+ welcome_lock_lease.release()
662
+ if not reset_succeeded:
663
+ await self._bus.publish(
664
+ Error(
665
+ code="rollback_reset_failed",
666
+ message="Rollback to welcome could not reset the session because the session state is busy.",
667
+ display_message=_ROLLBACK_RESET_FAILED.bind(),
668
+ session_id=sid,
669
+ ),
670
+ )
671
+ return
672
+ # The reset has committed and released the welcome write lock. Its
673
+ # restarted recorder is bound lazily to the same log, so activation
674
+ # recovers the old branch and this event atomically opens the new one.
675
+ # A failed reset never reaches here and therefore never supersedes the
676
+ # branch of the session that was restored.
677
+ await self._trajectory_recorder.rollback(
678
+ target_turn=0,
679
+ history_state=rollback_history_state,
680
+ )
681
+ if retained_notice:
682
+ self._workspace_change_tracker.queue_safety_notice(retained_notice, cwd=self._session.workspace_cwd())
683
+ self._queue_partial_revert_notice(
684
+ plan_candidates, restore_results, detection_truncated=revert_detection_truncated
685
+ )
686
+ files_reverted = sum(1 for r in restore_results if r.changed)
687
+ await self._bus.publish(
688
+ RollbackResult(
689
+ session_id=sid,
690
+ target_turn=0,
691
+ rolled_back_user_text=rolled_back_user_text,
692
+ files_reverted=files_reverted,
693
+ restore_results=restore_results,
694
+ exclusions=plan_exclusions,
695
+ warnings=plan_warnings,
696
+ ),
697
+ )
698
+ return
699
+
700
+ snapshot_path = rollback_snapshot_for_target(session_dir, target_turn)
701
+ session_file = session_dir / "session.json" if session_dir else None
702
+ if snapshot_path is None or session_file is None or not snapshot_path.exists():
703
+ await self._bus.publish(
704
+ Warning(
705
+ code="rollback_snapshot_missing",
706
+ message=f"Snapshot for turn {target_turn} is missing.",
707
+ display_message=_ROLLBACK_SNAPSHOT_MISSING.bind(target_turn=target_turn),
708
+ session_id=sid,
709
+ ),
710
+ )
711
+ return
712
+
713
+ snapshot_missing_after_lock = False
714
+ try:
715
+ lock_path = self._session.session_write_lock_path(sid)
716
+ if lock_path is None:
717
+ raise OSError("session state store is unavailable")
718
+ with FileLock(lock_path, timeout=lock_timeout_seconds):
719
+ if not snapshot_path.exists():
720
+ snapshot_missing_after_lock = True
721
+ else:
722
+ overlays = _read_session_overlays(session_file)
723
+ self._permits.commit_session_transition(transition_owner)
724
+ atomic_copy_file(snapshot_path, session_file)
725
+ _reapply_session_overlays(session_file, overlays)
726
+ backup_file = session_file.with_name(SESSION_BACKUP_FILE_NAME)
727
+ try:
728
+ # Copy the restored (overlay-patched) primary so both files
729
+ # stay identical.
730
+ atomic_copy_file(session_file, backup_file)
731
+ except OSError:
732
+ with contextlib.suppress(OSError):
733
+ backup_file.unlink()
734
+ logger.warning("Failed to update rollback backup %s", backup_file, exc_info=True)
735
+ except TimeoutError as exc:
736
+ await self._bus.publish(
737
+ Error(
738
+ code="rollback_swap_locked",
739
+ message=f"Timed out waiting for session lock: {exc}",
740
+ display_message=_ROLLBACK_SWAP_LOCKED.bind(detail=DisplayBlock(str(exc))),
741
+ session_id=sid,
742
+ ),
743
+ )
744
+ return
745
+ except OSError as exc:
746
+ await self._bus.publish(
747
+ Error(
748
+ code="rollback_swap_failed",
749
+ message=f"Failed to restore snapshot: {exc}",
750
+ display_message=_ROLLBACK_SWAP_FAILED.bind(detail=DisplayBlock(str(exc))),
751
+ session_id=sid,
752
+ ),
753
+ )
754
+ return
755
+
756
+ if snapshot_missing_after_lock:
757
+ await self._bus.publish(
758
+ Warning(
759
+ code="rollback_snapshot_missing",
760
+ message=f"Snapshot for turn {target_turn} is missing.",
761
+ display_message=_ROLLBACK_SNAPSHOT_MISSING.bind(target_turn=target_turn),
762
+ session_id=sid,
763
+ ),
764
+ )
765
+ return
766
+
767
+ if post_swap_file_rollback is not None:
768
+ post_swap_file_rollback()
769
+
770
+ if session_dir is not None:
771
+ snap_dir = session_dir / "snapshots"
772
+ if snap_dir.is_dir():
773
+ for path in rollback_snapshot_paths(session_dir):
774
+ turn = snapshot_target_turn(path)
775
+ if turn is None:
776
+ continue
777
+ if turn <= target_turn:
778
+ continue
779
+ try:
780
+ path.unlink()
781
+ except OSError:
782
+ logger.debug("Failed to prune stale snapshot %s", path, exc_info=True)
783
+
784
+ # The rollback opens a new trajectory branch: recorded on the outgoing
785
+ # runtime (the restore below closes it and the restored session resumes
786
+ # on the new branch), after the swap lock is released — activation
787
+ # takes the same lock — and while the live registry still names the
788
+ # first superseded turn.
789
+ record_cancelled = False
790
+ try:
791
+ await self._trajectory_recorder.rollback(
792
+ target_turn=target_turn,
793
+ history_state=self._history.state if self._history.is_bound else None,
794
+ )
795
+ except asyncio.CancelledError:
796
+ # The swap has committed but the engine still holds the pre-rollback
797
+ # history: leaving here would let the next save write that history
798
+ # back over the file the rollback just restored. The audit record is
799
+ # the only part that can be dropped, so the cancellation waits for the
800
+ # restore below and completes right after it.
801
+ task = asyncio.current_task()
802
+ if task is not None:
803
+ task.uncancel()
804
+ record_cancelled = True
805
+
806
+ with self._session.saves_suppressed():
807
+ await self._lifecycle.on_session_restore(SessionRestore(session_id=sid, ignore_recovery=True))
808
+ if record_cancelled:
809
+ raise asyncio.CancelledError
810
+
811
+ if event.revert_changes:
812
+ self._workspace_change_tracker.invalidate()
813
+ # After the invalidate: the stale baseline can never rediscover
814
+ # files the revert retained, so they must be reported directly.
815
+ self._queue_partial_revert_notice(
816
+ plan_candidates, restore_results, detection_truncated=revert_detection_truncated
817
+ )
818
+ await self._writer.save_current_session()
819
+
820
+ files_reverted = sum(1 for r in restore_results if r.changed)
821
+ await self._bus.publish(
822
+ RollbackResult(
823
+ session_id=sid,
824
+ target_turn=target_turn,
825
+ rolled_back_user_text=rolled_back_user_text,
826
+ files_reverted=files_reverted,
827
+ restore_results=restore_results,
828
+ exclusions=plan_exclusions,
829
+ warnings=plan_warnings,
830
+ ),
831
+ )
832
+
833
+ def _queue_partial_revert_notice(
834
+ self,
835
+ plan_candidates: list[str],
836
+ restore_results: list,
837
+ *,
838
+ detection_truncated: bool = False,
839
+ ) -> None:
840
+ """Report rollback candidates the revert did not actually restore.
841
+
842
+ Covers a partial ``selected_paths`` selection (unselected entries are
843
+ never attempted), plan exclusions, per-file restore failures, and a
844
+ restore pass that failed wholesale (empty results). Truncated
845
+ detection on a rolled-back turn means writes may exist that never
846
+ became candidates, so it warns even when every known candidate
847
+ restored. A fully-applied revert with complete detection queues
848
+ nothing.
849
+ """
850
+ if not plan_candidates and not detection_truncated:
851
+ return
852
+ restored = {result.path for result in restore_results if result.ok}
853
+ retained = [path for path in plan_candidates if path not in restored]
854
+ cwd = self._session.workspace_cwd()
855
+ notice = format_partial_revert_notice(
856
+ retained,
857
+ cwd=cwd,
858
+ max_entries=self._settings_handle.settings.workspace_change_notice_max_entries,
859
+ detection_incomplete=detection_truncated,
860
+ )
861
+ if notice:
862
+ self._workspace_change_tracker.queue_safety_notice(notice, cwd=cwd)
863
+
864
+ async def _cancel_if_rollback_owner_changed(
865
+ self,
866
+ event: UserRollback,
867
+ session_id: str,
868
+ session_generation: int,
869
+ ) -> bool:
870
+ """Reject a deferred rollback that no longer belongs to its requester."""
871
+ if (
872
+ self._session.session_id == session_id
873
+ and self._permits.session_generation == session_generation
874
+ and (event.session_id is None or event.session_id == session_id)
875
+ ):
876
+ return False
877
+ await self._bus.publish(
878
+ Warning(
879
+ code="rollback_session_changed",
880
+ message="Rollback cancelled because the active session changed.",
881
+ display_message=_ROLLBACK_SESSION_CHANGED.bind(),
882
+ session_id=session_id,
883
+ )
884
+ )
885
+ return True
886
+
887
+ async def _refuse_if_rollback_state_invalid(self, session_id: str) -> bool:
888
+ """Preserve the immediate refusal contract for a genuinely active turn."""
889
+ if self._fsm.state in (EngineState.IDLE, EngineState.INTERRUPTED, EngineState.FAILED):
890
+ return False
891
+ await self._bus.publish(
892
+ Warning(
893
+ code="rollback_refused",
894
+ message=f"Rollback is not allowed in state {self._fsm.state.name}.",
895
+ display_message=_ROLLBACK_REFUSED.bind(state=self._fsm.state.name),
896
+ session_id=session_id,
897
+ ),
898
+ )
899
+ return True
900
+
901
+ def _acquire_welcome_reset_write_lock(
902
+ self,
903
+ session_id: str,
904
+ timeout: float,
905
+ ) -> tuple[bool, FileLock | None]:
906
+ lock_path = self._session.session_write_lock_path(session_id)
907
+ if lock_path is None:
908
+ return True, None
909
+ lock = FileLock(lock_path, timeout=timeout)
910
+ try:
911
+ lock.acquire()
912
+ return True, lock
913
+ except TimeoutError:
914
+ logger.warning("Timed out acquiring session lock for welcome rollback %s", session_id, exc_info=True)
915
+ return False, None
916
+ except OSError:
917
+ logger.warning("Failed to acquire session lock for welcome rollback %s", session_id, exc_info=True)
918
+ return False, None