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,1062 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Event-facing turn coordination for main-agent turns."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ from collections.abc import Awaitable, Callable
9
+ from datetime import datetime
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ from chrys.foundation.config.settings_store import SettingsHandle
13
+ from chrys.foundation.events.bus import EventBus
14
+ from chrys.foundation.events.types import (
15
+ Error,
16
+ InvocationAborted,
17
+ InvocationAbortRequested,
18
+ InvocationCascadeAborted,
19
+ InvocationPaused,
20
+ InvocationResumed,
21
+ InvocationRetryRequested,
22
+ UserInject,
23
+ UserInjectCancel,
24
+ UserInjectResult,
25
+ UserInterrupt,
26
+ UserMessage,
27
+ UserRetry,
28
+ Warning,
29
+ )
30
+ from chrys.foundation.i18n import msg
31
+ from chrys.foundation.models.execution import ExecutionSnapshot
32
+ from chrys.orchestration.engine.execution import (
33
+ CurrentRunInjectionWindow,
34
+ CurrentRunScope,
35
+ PreAdmissionPreparationEntry,
36
+ PreAdmissionPreparationTracker,
37
+ PromptAdmissionScope,
38
+ )
39
+ from chrys.orchestration.engine.loader import AgentLoader
40
+ from chrys.orchestration.engine.run import sub_agent_coordination
41
+ from chrys.orchestration.engine.run.active_injection import ActiveTurnInjector
42
+ from chrys.orchestration.engine.run.attachments import (
43
+ AttachmentDiscoveryResult,
44
+ discover_image_mentions,
45
+ discover_image_references,
46
+ load_image_attachments,
47
+ )
48
+ from chrys.orchestration.engine.run.finalizer import TurnFinalizer, _expire_current_run_scope
49
+ from chrys.orchestration.engine.run.prompt_content import PromptContentPreparer
50
+ from chrys.orchestration.engine.run.retry import RetryCoordinator
51
+ from chrys.orchestration.engine.run.runner import TurnRunner
52
+ from chrys.orchestration.engine.run.runtime_skills import RuntimeSkillRefresher
53
+ from chrys.orchestration.engine.run.turn_hooks import PromptSubmitGate, TurnHookDispatcher
54
+ from chrys.orchestration.engine.run.turn_state import CurrentTurnInput, TurnRuntimeState
55
+ from chrys.orchestration.engine.run.working_dir import refuse_while_working_dir_missing
56
+ from chrys.orchestration.engine.state.machine import EngineState, EngineStateMachine, Trigger
57
+ from chrys.orchestration.engine.state.session_writer import SessionWriter
58
+ from chrys.orchestration.engine.trajectory import TrajectoryRecorder
59
+ from chrys.orchestration.invoker.contracts import AbortCause
60
+ from chrys.orchestration.invoker.resources import TurnTaskBinding
61
+ from chrys.service.hooks.schema import HookDecision
62
+ from chrys.service.mutations.workspace_changes import WorkspaceChangeTracker
63
+ from chrys.service.session.history import SessionHistoryManager
64
+ from chrys.service.session.persistence import SessionPersistence
65
+ from chrys.service.trajectory.preparation import (
66
+ PreparationOutcome,
67
+ PreparationScope,
68
+ PreparationTrace,
69
+ input_admission_wait,
70
+ )
71
+
72
+ if TYPE_CHECKING:
73
+ from chrys.foundation.trajectory.context import TrajectoryContext
74
+ from chrys.orchestration.engine.state.active_session import ActiveSession
75
+ from chrys.orchestration.engine.state.current_agent import CurrentAgent
76
+ from chrys.orchestration.engine.state.lifecycle_permits import LifecyclePermits
77
+
78
+
79
+ _IMAGE_COMPRESSION_TIMEOUT_SECONDS = 30.0
80
+
81
+ _COORDINATOR_ENGINE_NOT_STARTED = msg(
82
+ "coordinator.engine_not_started",
83
+ fallback="Engine not started",
84
+ )
85
+ _COORDINATOR_INTERRUPT_IGNORED_LOADING = msg(
86
+ "coordinator.interrupt_ignored_loading",
87
+ fallback="Interrupt ignored while agent infrastructure is loading.",
88
+ )
89
+ _COORDINATOR_PROMPT_ADMISSION_CONFLICT = msg(
90
+ "coordinator.prompt_admission_conflict",
91
+ fallback="Prompt could not be admitted because another turn started.",
92
+ )
93
+
94
+
95
+ class TurnCoordinator:
96
+ """Event-facing facade for one main-agent turn lifecycle.
97
+
98
+ Current queries re-read live values through current and session at each use.
99
+ Admission captures retain identities such as ActiveInjectionTarget,
100
+ CurrentRunScope, and RebuildControlToken across awaits and compare them
101
+ at their validation checkpoints.
102
+ """
103
+
104
+ def __init__(
105
+ self,
106
+ *,
107
+ turn_state: TurnRuntimeState,
108
+ current: CurrentAgent,
109
+ session: ActiveSession,
110
+ permits: LifecyclePermits,
111
+ writer: SessionWriter,
112
+ loader: AgentLoader,
113
+ hooks: TurnHookDispatcher,
114
+ bus: EventBus,
115
+ fsm: EngineStateMachine,
116
+ history: SessionHistoryManager,
117
+ trajectory_recorder: TrajectoryRecorder,
118
+ workspace_change_tracker: WorkspaceChangeTracker,
119
+ settings_handle: SettingsHandle,
120
+ persistence: SessionPersistence,
121
+ on_successful_turn: Callable[[], None],
122
+ on_turn_started: Callable[[], None],
123
+ prompt_content_preparer: PromptContentPreparer | None = None,
124
+ ) -> None:
125
+ self._turn_state = turn_state
126
+ self._current = current
127
+ self._session = session
128
+ self._permits = permits
129
+ self._writer = writer
130
+ self._loader = loader
131
+ self._hooks = hooks
132
+ self._bus = bus
133
+ self._fsm = fsm
134
+ self._history = history
135
+ self._trajectory_recorder = trajectory_recorder
136
+ self._workspace_change_tracker = workspace_change_tracker
137
+ self._settings_handle = settings_handle
138
+ self._persistence = persistence
139
+ self._on_successful_turn = on_successful_turn
140
+ self._on_turn_started = on_turn_started
141
+ self._gate = PromptSubmitGate(session=self._session, current=self._current, bus=self._bus, fsm=self._fsm)
142
+ self._content = prompt_content_preparer or PromptContentPreparer(
143
+ session=self._session,
144
+ current=self._current,
145
+ bus=self._bus,
146
+ history=self._history,
147
+ fsm=self._fsm,
148
+ discover_mentions=discover_image_mentions,
149
+ discover_references=discover_image_references,
150
+ load_attachments=load_image_attachments,
151
+ compression_timeout_seconds=_IMAGE_COMPRESSION_TIMEOUT_SECONDS,
152
+ )
153
+ self._skills = RuntimeSkillRefresher(
154
+ current=self._current, loader=self._loader, session=self._session, bus=self._bus
155
+ )
156
+ self._injector = ActiveTurnInjector(
157
+ turn_state=self._turn_state,
158
+ current=self._current,
159
+ permits=self._permits,
160
+ session=self._session,
161
+ fsm=self._fsm,
162
+ bus=self._bus,
163
+ gate=self._gate,
164
+ content=self._content,
165
+ skills=self._skills,
166
+ )
167
+ self._finalizer = TurnFinalizer(
168
+ current=self._current,
169
+ session=self._session,
170
+ turn_state=self._turn_state,
171
+ writer=self._writer,
172
+ history=self._history,
173
+ trajectory_recorder=self._trajectory_recorder,
174
+ fsm=self._fsm,
175
+ workspace_change_tracker=self._workspace_change_tracker,
176
+ settings_handle=self._settings_handle,
177
+ bus=self._bus,
178
+ persistence=self._persistence,
179
+ on_successful_turn=self._on_successful_turn,
180
+ hooks=self._hooks,
181
+ )
182
+
183
+ def _runner(self) -> TurnRunner:
184
+ """Create the service for one execution attempt."""
185
+ return TurnRunner(
186
+ current=self._current,
187
+ session=self._session,
188
+ turn_state=self._turn_state,
189
+ history=self._history,
190
+ workspace_change_tracker=self._workspace_change_tracker,
191
+ settings_handle=self._settings_handle,
192
+ trajectory_recorder=self._trajectory_recorder,
193
+ fsm=self._fsm,
194
+ on_turn_started=self._on_turn_started,
195
+ finalizer=self._finalizer,
196
+ hooks=self._hooks,
197
+ skills=self._skills,
198
+ content=self._content,
199
+ retry_factory=self._retry,
200
+ )
201
+
202
+ def _retry(self) -> RetryCoordinator:
203
+ """Create the service for one execution attempt."""
204
+ return RetryCoordinator(
205
+ turn_state=self._turn_state,
206
+ current=self._current,
207
+ permits=self._permits,
208
+ session=self._session,
209
+ fsm=self._fsm,
210
+ history=self._history,
211
+ bus=self._bus,
212
+ trajectory_recorder=self._trajectory_recorder,
213
+ gate=self._gate,
214
+ injector=self._injector,
215
+ content=self._content,
216
+ skills=self._skills,
217
+ retry_and_save=self.retry_and_save,
218
+ )
219
+
220
+ @property
221
+ def turn_state(self) -> TurnRuntimeState:
222
+ """The runtime state shared with lifecycle operations."""
223
+ return self._turn_state
224
+
225
+ @property
226
+ def run_task(self) -> asyncio.Task[None] | None:
227
+ """Current run task, including a retry task installed during finalization."""
228
+ return self._turn_state.lease.run_task
229
+
230
+ @property
231
+ def current_input(self) -> CurrentTurnInput:
232
+ """Current prompt data used by crash-recovery checkpoint writes."""
233
+ return self._turn_state.current_input
234
+
235
+ @property
236
+ def is_turn_active(self) -> bool:
237
+ """Return whether the FSM currently admits active-turn injection."""
238
+ return self._fsm.is_running()
239
+
240
+ @property
241
+ def is_turn_lifecycle_active(self) -> bool:
242
+ """Return whether execution or post-run finalization is still in flight."""
243
+ return self._turn_state.lease.execution_busy()
244
+
245
+ def was_run_task_finally_saved(self, task: asyncio.Task[None]) -> bool:
246
+ """Return whether *task* completed the durable final session save."""
247
+ return self._turn_state.lease.was_run_task_finally_saved(task)
248
+
249
+ def clear_run_task(self) -> None:
250
+ """Clear the visible run task after shutdown has drained or cancelled it."""
251
+ self._turn_state.lease.release_run_task()
252
+
253
+ async def wait_for_run_task(self) -> None:
254
+ """Wait for the active run-task chain, preserving task failure semantics."""
255
+ await self._turn_state.lease.observe_run_task_chain(propagate_inner_cancel=True)
256
+ await self._turn_state.lease.settle_notifications()
257
+
258
+ async def run_fresh(
259
+ self,
260
+ text: str,
261
+ created_at: datetime | str | None = None,
262
+ contents: list[Any] | None = None,
263
+ *,
264
+ run_scope: CurrentRunScope | None = None,
265
+ injection_window: CurrentRunInjectionWindow | None = None,
266
+ admission_preparation: PreparationTrace | None = None,
267
+ binding_failure: Exception | None = None,
268
+ ) -> None:
269
+ """Execute a fresh agent pass against the current engine state."""
270
+
271
+ await self._runner().run_fresh(
272
+ text,
273
+ created_at=created_at,
274
+ contents=contents,
275
+ run_scope=run_scope,
276
+ injection_window=injection_window,
277
+ admission_preparation=admission_preparation,
278
+ binding_failure=binding_failure,
279
+ )
280
+
281
+ async def run_retry(
282
+ self,
283
+ additional_text: str = "",
284
+ created_at: datetime | str | None = None,
285
+ *,
286
+ run_scope: CurrentRunScope | None = None,
287
+ injection_window: CurrentRunInjectionWindow | None = None,
288
+ admission_preparation: PreparationTrace | None = None,
289
+ binding_failure: Exception | None = None,
290
+ ) -> None:
291
+ """Execute a retry/resume pass against the current engine state."""
292
+
293
+ await self._runner().run_retry(
294
+ additional_text,
295
+ created_at=created_at,
296
+ run_scope=run_scope,
297
+ injection_window=injection_window,
298
+ admission_preparation=admission_preparation,
299
+ binding_failure=binding_failure,
300
+ )
301
+
302
+ async def finalize_current_run(self) -> None:
303
+ """Finalize the current executor pass against the current engine state."""
304
+
305
+ await self._runner().finalize_current_run()
306
+
307
+ async def on_user_message(self, event: UserMessage) -> None:
308
+ """Handle a user message by running the executor as an async task.
309
+
310
+ On accepted fresh turns, mutates ``event.prepared_contents`` before
311
+ returning so sequential ``EventBus.publish`` callers can render the exact
312
+ multimodal payload that will be sent to the model.
313
+ """
314
+ if await self._turn_state.lease.refuse_while_workflow_active(self._bus, self._session.session_id):
315
+ return
316
+ # Track injection-identified submits so a concurrent cancel knows an
317
+ # admission is still in flight to observe its mark (no-op for None).
318
+ turn_state = self._turn_state
319
+ turn_state.begin_inflight_injection(event.injection_id)
320
+ preparation_tracker = PreAdmissionPreparationTracker()
321
+ handed_off = False
322
+ try:
323
+ await self._start_pre_admission_preparation(preparation_tracker)
324
+ handed_off = await self._admit_user_message(event, preparation_tracker=preparation_tracker)
325
+ except asyncio.CancelledError:
326
+ preparation = preparation_tracker.preparation
327
+ if (
328
+ preparation is not None
329
+ and not preparation_tracker.preparation_handed_off
330
+ and not preparation.finished_state
331
+ ):
332
+ preparation.finished_soon(outcome=PreparationOutcome.CANCELLED)
333
+ raise
334
+ except BaseException:
335
+ preparation = preparation_tracker.preparation
336
+ if (
337
+ preparation is not None
338
+ and not preparation_tracker.preparation_handed_off
339
+ and not preparation.finished_state
340
+ ):
341
+ preparation.finished_soon(outcome=PreparationOutcome.PREPARATION_FAILED)
342
+ raise
343
+ finally:
344
+ turn_state.lease.deregister_pre_admission_preparation(preparation_tracker.current)
345
+ turn_state.finish_inflight_injection(event.injection_id)
346
+ preparation = preparation_tracker.preparation
347
+ if (
348
+ preparation is not None
349
+ and not handed_off
350
+ and not preparation_tracker.preparation_handed_off
351
+ and not preparation.finished_state
352
+ ):
353
+ await preparation.finished(outcome=PreparationOutcome.PREPARATION_FAILED)
354
+
355
+ async def _admit_user_message(
356
+ self,
357
+ event: UserMessage,
358
+ *,
359
+ preparation_tracker: PreAdmissionPreparationTracker,
360
+ ) -> bool:
361
+ """Admit one message and report whether its preparation changed owners."""
362
+ admission: PromptAdmissionScope | None = None
363
+ admission_released = False
364
+ while True:
365
+ await self._wait_for_pre_admission_gate(self._permits.wait_for_agent_load_idle, preparation_tracker)
366
+ preparation = preparation_tracker.preparation
367
+ if self._current.loaded is None:
368
+ if self._turn_state.lease.prompt_admission_closed:
369
+ await self._wait_for_pre_admission_gate(
370
+ self._turn_state.lease.wait_for_prompt_admission_open,
371
+ preparation_tracker,
372
+ )
373
+ continue
374
+ await self._bus.publish(
375
+ Error(
376
+ code="not_ready",
377
+ message="Engine not started",
378
+ display_message=_COORDINATOR_ENGINE_NOT_STARTED.bind(),
379
+ session_id=self._session.session_id,
380
+ )
381
+ )
382
+ if preparation is not None:
383
+ await preparation.finished(outcome=PreparationOutcome.NOT_READY)
384
+ return False
385
+
386
+ # A run is in-flight — FSM covers RUNNING, PENDING_RETRY, and
387
+ # AWAITING_SUB_AGENTS (parent task pinned on a sub-agent's
388
+ # ``pending_decision`` future). Gate on the FSM so the intent
389
+ # travels with the state contract, not with the executor's
390
+ # ``_running`` implementation detail — a new state that should
391
+ # also inject only has to be added to
392
+ # :meth:`EngineStateMachine.is_running` to be covered here.
393
+ if self._fsm.is_running():
394
+ return await self._injector.inject(
395
+ event.text,
396
+ created_at=event.timestamp,
397
+ route="fsm_active",
398
+ reject_images_without_target=True,
399
+ injection_id=event.injection_id,
400
+ preparation=preparation,
401
+ preparation_tracker=preparation_tracker,
402
+ )
403
+
404
+ # If a previous run task is still cleaning up (e.g., interrupt teardown),
405
+ # wait for it to finish before starting a new run. Without this, the
406
+ # message would be injected into the dying run and then drained/lost.
407
+ if self._turn_state.lease.run_task is not None and not self._turn_state.lease.run_task.done():
408
+ if self._current.loaded.bindings.state.running:
409
+ # TurnBindings is actively running (not just cleaning up) — inject
410
+ return await self._injector.inject(
411
+ event.text,
412
+ created_at=event.timestamp,
413
+ route="executor_fallback",
414
+ reject_images_without_target=True,
415
+ injection_id=event.injection_id,
416
+ preparation=preparation,
417
+ preparation_tracker=preparation_tracker,
418
+ )
419
+ # TurnBindings finished but _post_run() is still running (session
420
+ # save, FSM transition). Wait for cleanup to complete so the
421
+ # new run starts from a clean state.
422
+ await self._wait_for_pre_admission_gate(self._wait_for_existing_run_task, preparation_tracker)
423
+ preparation = preparation_tracker.preparation
424
+
425
+ if self._turn_state.lease.prompt_admission_closed:
426
+ await self._wait_for_pre_admission_gate(
427
+ self._turn_state.lease.wait_for_prompt_admission_open,
428
+ preparation_tracker,
429
+ )
430
+ continue
431
+ if self._turn_state.lease.active_admission_count() > 0:
432
+ await self._wait_for_pre_admission_gate(
433
+ self._turn_state.lease.wait_for_active_admissions_idle,
434
+ preparation_tracker,
435
+ )
436
+ continue
437
+ if await refuse_while_working_dir_missing(self._bus, self._session):
438
+ if preparation is not None:
439
+ await preparation.finished(outcome=PreparationOutcome.REJECTED)
440
+ return False
441
+ # A workflow run may have taken the lease while this message was preparing.
442
+ if await self._turn_state.lease.refuse_while_workflow_active(self._bus, self._session.session_id):
443
+ if preparation is not None:
444
+ await preparation.finished(outcome=PreparationOutcome.REJECTED)
445
+ return False
446
+ admission = self._turn_state.lease.reserve_prompt_admission(
447
+ kind="fresh",
448
+ session_generation=self._permits.session_generation,
449
+ build_generation=self._permits.build_generation,
450
+ preparation_trace=preparation,
451
+ )
452
+ if admission is None:
453
+ await self._wait_for_pre_admission_gate(
454
+ self._turn_state.lease.wait_for_prompt_admission_open,
455
+ preparation_tracker,
456
+ )
457
+ continue
458
+ self._turn_state.lease.deregister_pre_admission_preparation(preparation_tracker.current)
459
+ break
460
+
461
+ try:
462
+ # A locked-input submit can outlive its run and arrive here as a
463
+ # fresh turn; honor a cancel that landed while it waited so Esc
464
+ # never lets the withdrawn text start a new turn.
465
+ if await self._abandon_cancelled_injection_submit(event):
466
+ if preparation is not None:
467
+ await preparation.finished(outcome=PreparationOutcome.CANCELLED)
468
+ return False
469
+ admission_session_id = self._session.session_id
470
+ decision = await self._evaluate_user_prompt_submit(
471
+ event.text,
472
+ injected=False,
473
+ target_operation_id=preparation.committed_operation_id if preparation is not None else None,
474
+ trajectory_context=preparation.context if preparation is not None else None,
475
+ )
476
+ if not self._admission_owner_is_current(admission):
477
+ if preparation is not None:
478
+ await preparation.finished(outcome=PreparationOutcome.OWNER_CHANGED)
479
+ return False
480
+ if await self._handle_user_prompt_submit_decision(decision, injected=False):
481
+ if preparation is not None:
482
+ await preparation.finished(outcome=PreparationOutcome.REJECTED)
483
+ return False
484
+
485
+ if event.prepared_contents is not None:
486
+ contents = list(event.prepared_contents)
487
+ if await self.reject_text_only_prepared_contents(
488
+ event.text,
489
+ contents,
490
+ admission=admission,
491
+ event_session_id=admission_session_id,
492
+ ):
493
+ if preparation is not None:
494
+ await preparation.finished(outcome=PreparationOutcome.IMAGE_REJECTED)
495
+ return False
496
+ else:
497
+ contents = await self.prepare_user_contents(
498
+ event.text,
499
+ admission=admission,
500
+ event_session_id=admission_session_id,
501
+ )
502
+ if contents is None:
503
+ if preparation is not None:
504
+ await preparation.finished(outcome=PreparationOutcome.PREPARATION_FAILED)
505
+ return False
506
+ event.prepared_contents = contents
507
+ if not self._admission_owner_is_current(admission):
508
+ if preparation is not None:
509
+ await preparation.finished(outcome=PreparationOutcome.OWNER_CHANGED)
510
+ return False
511
+
512
+ # Final cancel check after all awaited preparation; task creation
513
+ # below is synchronous, so a cancel can no longer interleave.
514
+ if await self._abandon_cancelled_injection_submit(event):
515
+ if preparation is not None:
516
+ await preparation.finished(outcome=PreparationOutcome.CANCELLED)
517
+ return False
518
+ if not self._fresh_prompt_fsm_accepts():
519
+ await self._publish_prompt_admission_conflict()
520
+ if preparation is not None:
521
+ await preparation.finished(outcome=PreparationOutcome.CONFLICT)
522
+ return False
523
+ if self._turn_state.lease.run_task is not None and not self._turn_state.lease.run_task.done():
524
+ await self._publish_prompt_admission_conflict()
525
+ if preparation is not None:
526
+ await preparation.finished(outcome=PreparationOutcome.CONFLICT)
527
+ return False
528
+ _expire_current_run_scope(self._turn_state, self._current, self._turn_state.lease.current_run_scope)
529
+
530
+ # After a failed/interrupted run, remove status markers (interrupted,
531
+ # error, turn_marker) so the new message appends cleanly. Then remove
532
+ # any orphaned user message that got no model response (e.g. immediate
533
+ # 401 error) — this matches the live UX where the failed message
534
+ # disappeared when the user typed a new one. If the model DID produce
535
+ # output (tool calls, partial text) the user message is preserved.
536
+ #
537
+ # Also check the history itself for trailing error markers — after a
538
+ # session restore the FSM is reset to IDLE, but the saved history
539
+ # still carries the error state from the previous run.
540
+ if (
541
+ self._fsm.state in (EngineState.INTERRUPTED, EngineState.FAILED)
542
+ or self._history.has_trailing_error_markers()
543
+ ):
544
+ self._history.remove_trailing_markers()
545
+ self._history.remove_orphaned_user_message()
546
+
547
+ reminder_middleware = self._current.loaded.reminder_middleware if self._current.loaded is not None else None
548
+ if reminder_middleware is None:
549
+ self._turn_state.lease.release_prompt_admission(admission)
550
+ admission_released = True
551
+ self._fsm.try_transition(Trigger.USER_MESSAGE)
552
+ self._turn_state.lease.run_task = asyncio.create_task(
553
+ self.run_and_save(
554
+ event.text,
555
+ created_at=event.timestamp,
556
+ contents=contents,
557
+ admission_preparation=preparation,
558
+ )
559
+ )
560
+ # Record the opener synchronously with its task: a Stop followed by
561
+ # Retry must find it even before preparation has populated history.
562
+ self._turn_state.set_current_input(event.text, contents, event.timestamp)
563
+ self._queue_prompt_hook_reminders(decision, injected=False)
564
+ return True
565
+
566
+ reminder_scope = reminder_middleware.create_current_run_scope()
567
+ promotion = self._turn_state.lease.promote_fresh_admission_to_run(
568
+ admission,
569
+ reminder_scope=reminder_scope,
570
+ make_task=lambda scope, window: asyncio.create_task(
571
+ self.run_and_save(
572
+ event.text,
573
+ created_at=event.timestamp,
574
+ contents=contents,
575
+ run_scope=scope,
576
+ injection_window=window,
577
+ admission_preparation=preparation,
578
+ )
579
+ ),
580
+ )
581
+ if not promotion.promoted:
582
+ reminder_middleware.expire_current_run_scope(reminder_scope)
583
+ if promotion.conflict:
584
+ await self._publish_prompt_admission_conflict()
585
+ if preparation is not None:
586
+ await preparation.finished(
587
+ outcome=PreparationOutcome.CONFLICT if promotion.conflict else PreparationOutcome.OWNER_CHANGED
588
+ )
589
+ return False
590
+ admission_released = True
591
+ # Record the opener synchronously with its task: a Stop followed by
592
+ # Retry must find it even before preparation has populated history.
593
+ self._turn_state.set_current_input(event.text, contents, event.timestamp)
594
+ self._fsm.try_transition(Trigger.USER_MESSAGE)
595
+ self._queue_prompt_hook_reminders(decision, injected=False)
596
+ return True
597
+ finally:
598
+ if admission is not None and not admission_released:
599
+ self._turn_state.lease.release_prompt_admission(admission)
600
+
601
+ async def on_user_interrupt(self, _event: UserInterrupt) -> None:
602
+ """Handle user interrupt.
603
+
604
+ Cascades to every live sub-agent controller first so paused
605
+ sub-agents resolve via the cascade-abort branch (otherwise their
606
+ ``pending_decision`` future would keep their ``_invoke``
607
+ coroutine pinned forever and the subsequent task cancel below
608
+ would leak). Then sets the interrupt flag / cancels the parent
609
+ task. It then either binds cancellation to the exact pre-executor
610
+ run task or interrupts the active executor; ``run_and_save``
611
+ detects ``was_interrupted`` and rolls back history.
612
+ """
613
+ if self._permits.agent_loading:
614
+ await self._bus.publish(
615
+ Warning(
616
+ code="agent_loading_interrupt_ignored",
617
+ message="Interrupt ignored while agent infrastructure is loading.",
618
+ display_message=_COORDINATOR_INTERRUPT_IGNORED_LOADING.bind(),
619
+ session_id=self._session.session_id,
620
+ )
621
+ )
622
+ return
623
+ executor = self._current.loaded.bindings if self._current.loaded is not None else None
624
+ interrupt_task = self._turn_state.lease.run_task
625
+ self._trajectory_recorder.interrupt_requested_soon()
626
+ if executor is not None and not executor.state.running:
627
+ self._turn_state.lease.request_pre_executor_interrupt(interrupt_task)
628
+ if self._current.loaded is not None and self._current.loaded.sub_agent_tools is not None:
629
+ await self._current.loaded.sub_agent_tools.cascade_abort_all()
630
+ if executor is not None and interrupt_task is not None and self._turn_state.lease.run_task is interrupt_task:
631
+ if executor.state.running:
632
+ handle = executor.backend.active_handle
633
+ if handle is not None:
634
+ await executor.backend.abort(handle, AbortCause.USER_CANCEL)
635
+ else:
636
+ self._turn_state.lease.request_pre_executor_interrupt(interrupt_task)
637
+ self.schedule_user_interrupt_hook()
638
+
639
+ async def on_user_retry(self, event: UserRetry) -> None:
640
+ """Handle retry requests and route them to immediate or pending execution."""
641
+ await self._retry().handle_user_retry(event)
642
+
643
+ async def on_user_inject(self, event: UserInject) -> None:
644
+ """Handle user injection (prompt inserted before next model call)."""
645
+ self._turn_state.begin_inflight_injection(event.injection_id)
646
+ preparation_tracker = PreAdmissionPreparationTracker()
647
+ handed_off = False
648
+ try:
649
+ await self._start_pre_admission_preparation(preparation_tracker)
650
+ await self._wait_for_pre_admission_gate(self._permits.wait_for_agent_load_idle, preparation_tracker)
651
+ preparation = preparation_tracker.preparation
652
+ if self._current.loaded is None:
653
+ if preparation is not None:
654
+ await preparation.finished(outcome=PreparationOutcome.NOT_READY)
655
+ return
656
+ handed_off = await self._injector.inject(
657
+ event.text,
658
+ created_at=event.timestamp,
659
+ route="fsm_active",
660
+ reject_images_without_target=False,
661
+ injection_id=event.injection_id,
662
+ preparation=preparation,
663
+ preparation_tracker=preparation_tracker,
664
+ )
665
+ except asyncio.CancelledError:
666
+ preparation = preparation_tracker.preparation
667
+ if (
668
+ preparation is not None
669
+ and not preparation_tracker.preparation_handed_off
670
+ and not preparation.finished_state
671
+ ):
672
+ preparation.finished_soon(outcome=PreparationOutcome.CANCELLED)
673
+ raise
674
+ except BaseException:
675
+ preparation = preparation_tracker.preparation
676
+ if (
677
+ preparation is not None
678
+ and not preparation_tracker.preparation_handed_off
679
+ and not preparation.finished_state
680
+ ):
681
+ preparation.finished_soon(outcome=PreparationOutcome.PREPARATION_FAILED)
682
+ raise
683
+ finally:
684
+ self._turn_state.lease.deregister_pre_admission_preparation(preparation_tracker.current)
685
+ self._turn_state.finish_inflight_injection(event.injection_id)
686
+ preparation = preparation_tracker.preparation
687
+ if (
688
+ preparation is not None
689
+ and not handed_off
690
+ and not preparation_tracker.preparation_handed_off
691
+ and not preparation.finished_state
692
+ ):
693
+ await preparation.finished(outcome=PreparationOutcome.PREPARATION_FAILED)
694
+
695
+ async def on_user_inject_cancel(self, event: UserInjectCancel) -> None:
696
+ """Withdraw a queued mid-run injection before the model sees it.
697
+
698
+ Covers both pending phases: an injection already queued on the
699
+ middleware is removed here directly (including its pre-appended
700
+ approval judge context), and an injection still in awaited admission
701
+ aborts via a cancel mark it observes before committing. Everything
702
+ below is synchronous, so the injection cannot move between phases
703
+ mid-cancel. A cancel that matches neither phase is a no-op — the
704
+ injection already resolved (for a consumed one, the ``consumed=True``
705
+ result tells frontends the text reached the model) and recording a
706
+ mark would leave it dangling for the rest of the session.
707
+ """
708
+ injection_id = event.injection_id
709
+ if not injection_id:
710
+ return
711
+ executor = self._current.loaded.bindings if self._current.loaded is not None else None
712
+ if executor is None:
713
+ if self._turn_state.is_injection_inflight(injection_id):
714
+ self._turn_state.mark_injection_cancelled(injection_id)
715
+ return
716
+ removed = executor.cancel_injection(injection_id)
717
+ if removed is None:
718
+ if self._turn_state.is_injection_inflight(injection_id):
719
+ self._turn_state.mark_injection_cancelled(injection_id)
720
+ return
721
+ executor.approval.remove_user_message(removed.text)
722
+ if removed.preparation is not None:
723
+ removed.preparation.finished_soon(
724
+ outcome=PreparationOutcome.CANCELLED,
725
+ target_turn_id=removed.target_turn_id,
726
+ )
727
+ await self._bus.publish(
728
+ UserInjectResult(
729
+ text=removed.text,
730
+ consumed=False,
731
+ created_at=removed.created_at,
732
+ injection_id=injection_id,
733
+ session_id=self._session.session_id,
734
+ )
735
+ )
736
+
737
+ async def _abandon_cancelled_injection_submit(self, event: UserMessage) -> bool:
738
+ """Publish an abandoned result and return True for a cancelled locked submit."""
739
+ if not self._turn_state.discard_injection_cancellation(event.injection_id):
740
+ return False
741
+ await self._bus.publish(
742
+ UserInjectResult(
743
+ text=event.text,
744
+ consumed=False,
745
+ created_at=event.timestamp,
746
+ injection_id=event.injection_id,
747
+ session_id=self._session.session_id,
748
+ )
749
+ )
750
+ return True
751
+
752
+ def check_pending_retry(self) -> None:
753
+ """If a retry was queued while the executor was running, start it now."""
754
+ self._retry().start_pending_retry_if_due()
755
+
756
+ async def prepare_user_contents(
757
+ self,
758
+ text: str,
759
+ *,
760
+ discovered: AttachmentDiscoveryResult | None = None,
761
+ admission: PromptAdmissionScope | None = None,
762
+ event_session_id: str | None = None,
763
+ ) -> list[Any] | None:
764
+ """Return user-message contents or publish a recoverable attachment error."""
765
+ result = await self._content.prepare_fresh(
766
+ text,
767
+ discovered=discovered,
768
+ event_session_id=event_session_id,
769
+ should_publish=self._publication_guard_for_admission(admission),
770
+ )
771
+ return None if result is None else result.contents
772
+
773
+ async def reject_text_only_prepared_contents(
774
+ self,
775
+ text: str,
776
+ contents: list[Any],
777
+ *,
778
+ admission: PromptAdmissionScope,
779
+ event_session_id: str | None,
780
+ ) -> bool:
781
+ """Reject prebuilt image contents before a fresh prompt is promoted."""
782
+ return await self._content.reject_text_only_prepared_contents(
783
+ text,
784
+ contents,
785
+ event_session_id=event_session_id,
786
+ should_publish=self._publication_guard_for_admission(admission),
787
+ )
788
+
789
+ async def _wait_for_existing_run_task(self) -> None:
790
+ """Wait for current run-task cleanup, if any."""
791
+ task = self._turn_state.lease.run_task
792
+ if task is not None and not task.done():
793
+ await task
794
+
795
+ def _admission_owner_is_current(self, admission: PromptAdmissionScope) -> bool:
796
+ """Return whether *admission* still belongs to the live session/build owner."""
797
+ return (
798
+ self._permits.session_generation == admission.session_generation
799
+ and self._permits.build_generation == admission.build_generation
800
+ )
801
+
802
+ def _publication_guard_for_admission(
803
+ self,
804
+ admission: PromptAdmissionScope | None,
805
+ ) -> Callable[[], bool] | None:
806
+ """Return a prompt-preparation side-effect guard for an exact admission."""
807
+ if admission is None:
808
+ return None
809
+ return lambda: self._admission_owner_is_current(admission)
810
+
811
+ def _fresh_prompt_fsm_accepts(self) -> bool:
812
+ """Return whether the current FSM state can start a fresh user turn."""
813
+ return self._fsm.state in (
814
+ EngineState.UNINITIALIZED,
815
+ EngineState.IDLE,
816
+ EngineState.INTERRUPTED,
817
+ EngineState.FAILED,
818
+ )
819
+
820
+ async def _publish_prompt_admission_conflict(self) -> None:
821
+ """Publish a deterministic conflict when a fresh prompt cannot promote."""
822
+ await self._bus.publish(
823
+ Error(
824
+ code="prompt_admission_conflict",
825
+ message="Prompt could not be admitted because another turn started.",
826
+ display_message=_COORDINATOR_PROMPT_ADMISSION_CONFLICT.bind(),
827
+ session_id=self._session.session_id,
828
+ )
829
+ )
830
+
831
+ async def _evaluate_user_prompt_submit(
832
+ self,
833
+ text: str,
834
+ *,
835
+ injected: bool | None,
836
+ target_operation_id: str | None = None,
837
+ trajectory_context: TrajectoryContext | None = None,
838
+ ) -> HookDecision | None:
839
+ """Run ``user_prompt_submit`` hooks without applying reminder side effects."""
840
+ return await self._gate.evaluate(
841
+ text,
842
+ injected=injected,
843
+ target_operation_id=target_operation_id,
844
+ trajectory_context=trajectory_context,
845
+ )
846
+
847
+ def _open_pre_turn_preparation(self) -> PreparationTrace | None:
848
+ """Open a session-root preparation scope before prompt admission awaits."""
849
+ context = self._trajectory_recorder.context()
850
+ if context is not None:
851
+ context = context.with_turn(None).with_run(None)
852
+ return PreparationTrace.open(
853
+ scope=PreparationScope.PRE_TURN,
854
+ phase="input_admission",
855
+ context=context,
856
+ )
857
+
858
+ async def _start_pre_admission_preparation(self, tracker: PreAdmissionPreparationTracker) -> None:
859
+ """Start and register a preparation against the current session context."""
860
+ preparation = self._open_pre_turn_preparation()
861
+ if preparation is None:
862
+ tracker.current = None
863
+ return
864
+ entry = PreAdmissionPreparationEntry(preparation=preparation)
865
+ tracker.current = entry
866
+ await preparation.started()
867
+ self._turn_state.lease.register_pre_admission_preparation(entry)
868
+
869
+ async def _wait_for_pre_admission_gate(
870
+ self,
871
+ wait_for_gate: Callable[[], Awaitable[Any]],
872
+ tracker: PreAdmissionPreparationTracker,
873
+ ) -> None:
874
+ """Wait at one gate and rebind a preparation swept by a session boundary."""
875
+ entry = tracker.current
876
+ preparation = tracker.preparation
877
+ await input_admission_wait(wait_for_gate, preparation, entry)
878
+ if preparation is None or not preparation.finished_state:
879
+ return
880
+ self._turn_state.lease.deregister_pre_admission_preparation(entry)
881
+ await self._start_pre_admission_preparation(tracker)
882
+
883
+ async def _handle_user_prompt_submit_decision(
884
+ self,
885
+ decision: HookDecision | None,
886
+ *,
887
+ injected: bool | None,
888
+ session_id: str | None = None,
889
+ ) -> bool:
890
+ """Return True after publishing when a prompt-submit hook blocked."""
891
+ return await self._gate.handle_decision(
892
+ decision,
893
+ injected=injected,
894
+ session_id=session_id,
895
+ )
896
+
897
+ def _queue_prompt_hook_reminders(
898
+ self,
899
+ decision: HookDecision | None,
900
+ *,
901
+ injected: bool | None,
902
+ ) -> None:
903
+ """Apply non-blocking prompt-submit hook reminders after prompt validation passes."""
904
+ self._gate.queue_reminders(decision, injected=injected)
905
+
906
+ def schedule_user_interrupt_hook(self) -> None:
907
+ """Schedule the asynchronous user-interrupt lifecycle hook."""
908
+ self._hooks.schedule_user_interrupt()
909
+
910
+ @property
911
+ def conversation_revision(self) -> int:
912
+ """Monotonic fresh/retry lifecycle revision for stale projections."""
913
+ return self._turn_state.lease.conversation_revision
914
+
915
+ def execution(self) -> ExecutionSnapshot:
916
+ """What the execution lease is running right now."""
917
+ return self._turn_state.lease.execution()
918
+
919
+ def execution_busy(self) -> bool:
920
+ """Existing lifecycle busy predicate, including preparation and final save."""
921
+ return self._turn_state.lease.execution_busy()
922
+
923
+ def turn_accepts_injection(self) -> bool:
924
+ """Existing FSM is_running predicate, including pending retry and child waits."""
925
+ return self._fsm.is_running()
926
+
927
+ @property
928
+ def turn_lifecycle_task(self) -> asyncio.Task[None] | None:
929
+ """Return the currently owned execution/finalization task.
930
+
931
+ Callers that need a boundary for one captured turn should retain this
932
+ exact task instead of later awaiting the replaceable run-task chain.
933
+ """
934
+ return self.run_task
935
+
936
+ def was_turn_lifecycle_saved(self, task: asyncio.Task[None]) -> bool:
937
+ """Return whether *task* completed its final session save successfully."""
938
+ return self.was_run_task_finally_saved(task)
939
+
940
+ async def run_and_save(
941
+ self,
942
+ text: str,
943
+ created_at: datetime | str | None = None,
944
+ contents: list[Any] | None = None,
945
+ *,
946
+ run_scope: CurrentRunScope | None = None,
947
+ injection_window: CurrentRunInjectionWindow | None = None,
948
+ admission_preparation: PreparationTrace | None = None,
949
+ ) -> None:
950
+ """Execute the agent and auto-save the session afterward."""
951
+ task = asyncio.current_task()
952
+ unbind = None
953
+ binding_failure: Exception | None = None
954
+ try:
955
+ if self._current.loaded is not None and task is not None:
956
+ unbind = self._current.loaded.conversation.bind_operation(
957
+ TurnTaskBinding(
958
+ task,
959
+ self._current.loaded.bindings.backend.latch_abort if self._current.loaded is not None else None,
960
+ )
961
+ )
962
+ except Exception as exc:
963
+ # Promotion already owns the Turn. Let its ordinary admission
964
+ # failure path preserve input, finalize, save, and release it.
965
+ binding_failure = exc
966
+ try:
967
+ await self.run_fresh(
968
+ text,
969
+ created_at=created_at,
970
+ contents=contents,
971
+ run_scope=run_scope,
972
+ injection_window=injection_window,
973
+ admission_preparation=admission_preparation,
974
+ binding_failure=binding_failure,
975
+ )
976
+ finally:
977
+ if unbind is not None:
978
+ unbind()
979
+
980
+ async def retry_and_save(
981
+ self,
982
+ additional_text: str = "",
983
+ created_at: datetime | str | None = None,
984
+ *,
985
+ run_scope: CurrentRunScope | None = None,
986
+ injection_window: CurrentRunInjectionWindow | None = None,
987
+ admission_preparation: PreparationTrace | None = None,
988
+ ) -> None:
989
+ """Resume the agent from current state and auto-save afterward.
990
+
991
+ When *additional_text* is non-empty, the executor uses it as the
992
+ mid-turn continuation prompt (instead of the placeholder
993
+ ``"continue"``) and preserves it in history as a real user turn.
994
+ """
995
+ task = asyncio.current_task()
996
+ unbind = None
997
+ binding_failure: Exception | None = None
998
+ try:
999
+ if self._current.loaded is not None and task is not None:
1000
+ unbind = self._current.loaded.conversation.bind_operation(
1001
+ TurnTaskBinding(
1002
+ task,
1003
+ self._current.loaded.bindings.backend.latch_abort if self._current.loaded is not None else None,
1004
+ )
1005
+ )
1006
+ except Exception as exc:
1007
+ # Promotion already owns the Turn. Let its ordinary admission
1008
+ # failure path preserve input, finalize, save, and release it.
1009
+ binding_failure = exc
1010
+ try:
1011
+ await self.run_retry(
1012
+ additional_text,
1013
+ created_at=created_at,
1014
+ run_scope=run_scope,
1015
+ injection_window=injection_window,
1016
+ admission_preparation=admission_preparation,
1017
+ binding_failure=binding_failure,
1018
+ )
1019
+ finally:
1020
+ if unbind is not None:
1021
+ unbind()
1022
+
1023
+ async def on_sub_agent_retry(self, event: InvocationRetryRequested) -> None:
1024
+ """Route a user's per-card Retry click to the owning controller."""
1025
+ await sub_agent_coordination.on_sub_agent_retry(self._current, event)
1026
+
1027
+ async def on_sub_agent_abort(self, event: InvocationAbortRequested) -> None:
1028
+ """Route a user's per-card Abort click to the owning controller."""
1029
+ await sub_agent_coordination.on_sub_agent_abort(self._current, event)
1030
+
1031
+ async def on_sub_agent_paused(self, event: InvocationPaused) -> None:
1032
+ """Track a newly paused sub-agent and drive FSM / marker.
1033
+
1034
+ Idempotent on the paused set — if the same id arrives twice
1035
+ (controller re-publishes after retry exhaustion, for example)
1036
+ the FSM transition only fires on the 0→1 edge.
1037
+
1038
+ Defensive FSM guard: if the parent run has already terminated
1039
+ (e.g. the bus is dispatching a stale pause event that was queued
1040
+ before the parent's task was cancelled and ``_post_run`` ran),
1041
+ drop the event silently rather than re-inserting a marker on top
1042
+ of an already-terminal ``interrupted``/``error`` marker. The
1043
+ parent invariant ("parent run ends only after all sub-agents
1044
+ resolve") should make this unreachable, but defense in depth
1045
+ keeps history well-formed if that invariant ever breaks.
1046
+ """
1047
+ await sub_agent_coordination.on_sub_agent_paused(
1048
+ self._turn_state, self._history, self._fsm, self._trajectory_recorder, event
1049
+ )
1050
+
1051
+ async def on_sub_agent_unpaused(
1052
+ self,
1053
+ event: InvocationResumed | InvocationAborted | InvocationCascadeAborted,
1054
+ ) -> None:
1055
+ """Common handler — retry/abort/cascade all remove the invocation from the paused set.
1056
+
1057
+ FSM transitions only on the N→0 edge (last paused sub-agent
1058
+ resolved). Marker is updated or stripped accordingly.
1059
+ """
1060
+ await sub_agent_coordination.on_sub_agent_unpaused(
1061
+ self._turn_state, self._history, self._fsm, self._trajectory_recorder, event
1062
+ )