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,986 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """One workflow run: the pure scheduler's decisions executed against a worker, agent shells, and the journal.
4
+
5
+ The scheduler decides, the runner acts. Every scheduler input goes through
6
+ one lock, and the decisions it returns are executed, journal writes
7
+ included, before the next input is taken: the run log's sequence order is
8
+ the scheduler's decision order. Node bodies, evaluations, and backoffs run
9
+ as tasks outside that lock and feed their results back through it.
10
+
11
+ Faults converge the same way user actions do: a lost worker or a failed
12
+ store becomes a scheduler input whose terminal decisions cancel what is
13
+ still running. The runner then drains its tasks, closes every agent shell,
14
+ waits for the worker to exit, checkpoints the session, and records the terminal
15
+ last. A failed checkpoint makes the run ``storage_failed`` even if every node
16
+ completed successfully.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import contextlib
23
+ import logging
24
+ import time
25
+ from collections.abc import Awaitable, Callable
26
+ from dataclasses import dataclass, field
27
+ from typing import TYPE_CHECKING, Any, Final
28
+
29
+ from chrys.foundation.events.types import (
30
+ WORKFLOW_NOTICE_DATA_DROPPED,
31
+ WORKFLOW_OUTPUT_EMIT,
32
+ WORKFLOW_OUTPUT_FINAL,
33
+ WorkflowOutputSummary,
34
+ )
35
+ from chrys.foundation.models.ask_user import AskUserAnswer, AskUserQuestion, validate_ask_user_answers
36
+ from chrys.foundation.trajectory.ids import new_analytics_id
37
+ from chrys.foundation.util.once_close import finish_close
38
+ from chrys.orchestration.invoker.contracts import AbortCause
39
+ from chrys.orchestration.workflows.agent_archive import AgentNodeArchive
40
+ from chrys.orchestration.workflows.agent_node import WorkflowAgentShell
41
+ from chrys.orchestration.workflows.agent_node_build import AgentNodeResources
42
+ from chrys.orchestration.workflows.worker_client import (
43
+ AskUnavailable,
44
+ AttemptTimeout,
45
+ CapturedOutput,
46
+ WorkerLostError,
47
+ WorkerRpcError,
48
+ WorkflowWorkerClient,
49
+ )
50
+ from chrys.service.trajectory.workflow import WorkflowTrace
51
+ from chrys.service.workflows.admission import AdmittedManifest
52
+ from chrys.service.workflows.graph import KIND_AGENT, KIND_JOIN, KIND_PYTHON, ON_EXHAUSTED_CONTINUE
53
+ from chrys.service.workflows.journal import WorkflowJournal, summarize
54
+ from chrys.service.workflows.outcomes import (
55
+ REASON_DEADLINE_EXCEEDED,
56
+ REASON_INTERNAL_ERROR,
57
+ REASON_SHUTDOWN,
58
+ RunOutcome,
59
+ )
60
+ from chrys.service.workflows.protocol import ErrorCode, ProtocolError
61
+ from chrys.service.workflows.scheduler import (
62
+ Activate,
63
+ ActivationState,
64
+ AttemptRef,
65
+ CancelActivation,
66
+ Decision,
67
+ ErrorClass,
68
+ EvaluateLoopUntil,
69
+ EvaluateOutgoing,
70
+ FailureReport,
71
+ LoopActivated,
72
+ LoopIteration,
73
+ LoopVerdict,
74
+ NodeStateChanged,
75
+ PersistRetryKey,
76
+ RetryRejected,
77
+ RunFinished,
78
+ RunMode,
79
+ RunOutput,
80
+ ScheduleRetry,
81
+ WorkflowScheduler,
82
+ )
83
+ from chrys.service.workflows.sdk import SourceValue, WorkflowValue
84
+ from chrys.service.workflows.store import (
85
+ DATA_DROPPED_KEY,
86
+ NODE_RECORD_INPUT,
87
+ NODE_RECORD_OUTPUT,
88
+ WorkflowStorageFailed,
89
+ )
90
+ from chrys.service.workflows.values import ValueShapeError, source_to_wire, value_to_wire
91
+
92
+ if TYPE_CHECKING:
93
+ from chrys.service.approval.policy import ApprovalMode
94
+
95
+ logger = logging.getLogger(__name__)
96
+
97
+ MAX_CONCURRENT_AGENT_ATTEMPTS: Final = 4
98
+ """Concurrent agent attempts per run, including shell setup and execution but excluding retry backoff."""
99
+
100
+ _CANCEL_CAUSES: Final = {
101
+ REASON_DEADLINE_EXCEEDED: AbortCause.CALLER_TIMEOUT,
102
+ REASON_SHUTDOWN: AbortCause.OWNER_CLOSE,
103
+ REASON_INTERNAL_ERROR: AbortCause.OWNER_CLOSE,
104
+ }
105
+ _DURABLE_STATES: Final = frozenset(
106
+ {ActivationState.COMPLETED, ActivationState.FAILED, ActivationState.SKIPPED, ActivationState.CANCELLED}
107
+ )
108
+ _PYTHON_FAILURE_CLASSES: Final = {
109
+ ErrorCode.USER_EXCEPTION: ErrorClass.PYTHON_EXCEPTION,
110
+ ErrorCode.VALUE_TOO_LARGE: ErrorClass.VALUE_TOO_LARGE,
111
+ ErrorCode.PROTOCOL_LIMIT: ErrorClass.PROTOCOL_LIMIT,
112
+ ErrorCode.VALUE_NOT_SERIALIZABLE: ErrorClass.VALUE_NOT_SERIALIZABLE,
113
+ ErrorCode.ASK_UNAVAILABLE: ErrorClass.ASK_UNAVAILABLE,
114
+ }
115
+
116
+
117
+ @dataclass(frozen=True, slots=True)
118
+ class WorkflowRunResult:
119
+ """How a run ended; ``outputs`` follows the workflow's ``output()`` declaration order."""
120
+
121
+ run_id: str
122
+ outcome: RunOutcome
123
+ outputs: tuple[RunOutput, ...] = ()
124
+ node_id: str = ""
125
+ error: str = ""
126
+ reason: str = ""
127
+ duration: float = 0.0 # seconds
128
+
129
+
130
+ class WorkerCallbacks:
131
+ """The ask/emit handlers a worker is launched with, bound to the runner once it exists.
132
+
133
+ The worker is launched (and the workflow file loaded) before the runner
134
+ is built; no node body runs until the runner starts the scheduler, so
135
+ nothing reaches these handlers unbound.
136
+ """
137
+
138
+ def __init__(self) -> None:
139
+ self._runner: WorkflowRunner | None = None
140
+
141
+ def bind(self, runner: WorkflowRunner) -> None:
142
+ self._runner = runner
143
+
144
+ async def ask(self, ref: AttemptRef, questions: tuple[AskUserQuestion, ...]) -> tuple[AskUserAnswer, ...]:
145
+ if self._runner is None:
146
+ raise AskUnavailable("no run is active.")
147
+ return await self._runner.on_ask(ref, questions)
148
+
149
+ async def emit(self, ref: AttemptRef, ordinal: int, text: str) -> None:
150
+ if self._runner is not None:
151
+ await self._runner.on_emit(ref, ordinal, text)
152
+
153
+
154
+ @dataclass(slots=True)
155
+ class _PendingAsk:
156
+ ref: AttemptRef
157
+ questions: tuple[AskUserQuestion, ...]
158
+ answering: bool = False
159
+ future: asyncio.Future[tuple[AskUserAnswer, ...]] = field(
160
+ default_factory=lambda: asyncio.get_running_loop().create_future()
161
+ )
162
+
163
+
164
+ class WorkflowRunner:
165
+ """Executes one run of an admitted workflow; see the module docstring for the ordering contract."""
166
+
167
+ def __init__(
168
+ self,
169
+ *,
170
+ admitted: AdmittedManifest,
171
+ journal: WorkflowJournal,
172
+ worker: WorkflowWorkerClient,
173
+ resources: AgentNodeResources,
174
+ checkpoint: Callable[[], Awaitable[None]],
175
+ mode: RunMode,
176
+ timeout: float | None = None,
177
+ trace: WorkflowTrace | None = None,
178
+ load_stdout: CapturedOutput | None = None,
179
+ ) -> None:
180
+ self._admitted = admitted
181
+ self._graph = admitted.graph
182
+ self._journal = journal
183
+ self._worker = worker
184
+ self._resources = resources
185
+ self._checkpoint = checkpoint
186
+ self._mode = mode
187
+ self._timeout = timeout
188
+ self._trace = trace
189
+ self._load_stdout = load_stdout or CapturedOutput("", False)
190
+ self._output_collected = False
191
+ self._output_attempts: dict[str, int] = {}
192
+ self._record_end: Callable[[WorkflowRunResult], Awaitable[None]] | None = None
193
+ self._run_id = journal.run_id
194
+ self._scheduler = WorkflowScheduler(admitted.graph, run_id=self._run_id, mode=mode)
195
+ self._lock = asyncio.Lock()
196
+ self._agent_slots = asyncio.BoundedSemaphore(MAX_CONCURRENT_AGENT_ATTEMPTS)
197
+ self._lock_holder: asyncio.Task[Any] | None = None
198
+ self._tasks: set[asyncio.Task[None]] = set()
199
+ self._aborts: set[asyncio.Task[None]] = set()
200
+ self._invocations: dict[str, str] = {}
201
+ self._shells: dict[str, WorkflowAgentShell] = {}
202
+ self._started = 0.0
203
+ self._asks: dict[str, _PendingAsk] = {}
204
+ self._terminal: RunFinished | None = None
205
+ self._terminal_event = asyncio.Event()
206
+ self._deferred_fault: Callable[[], tuple[Decision, ...]] | None = None
207
+ self._record_error: str | None = None
208
+ self._cause: AbortCause | None = None
209
+ self._reason = ""
210
+ self._cancel_task: asyncio.Task[None] | None = None
211
+ self._worker_error = ""
212
+ self._internal_error = ""
213
+ self._data_dropped_noticed = False
214
+ self._result: WorkflowRunResult | None = None
215
+ self._deadline_task: asyncio.Task[None] | None = None
216
+ self._worker_watch: asyncio.Task[None] | None = None
217
+
218
+ @property
219
+ def run_id(self) -> str:
220
+ return self._run_id
221
+
222
+ @property
223
+ def result(self) -> WorkflowRunResult | None:
224
+ """The terminal result once :meth:`run` returned it."""
225
+ return self._result
226
+
227
+ @property
228
+ def finished(self) -> bool:
229
+ return self._terminal is not None
230
+
231
+ def holds(self, task: asyncio.Task[Any] | None) -> bool:
232
+ """True when *task* is one the run waits for: a subscriber to one of the run's events runs on one.
233
+
234
+ Scheduler events go out under the runner's lock, from the run task or one of its node tasks;
235
+ outputs and asks under the journal's lock alone, from a node's or the worker's task; an agent
236
+ node's invocation events from the tasks of its shell's pass, which the teardown waits out.
237
+ Waiting for the run from any of them would wait on itself.
238
+ """
239
+ if task is None:
240
+ return False
241
+ return (
242
+ task is self._lock_holder
243
+ or task in self._tasks
244
+ or task in self._aborts
245
+ or self._journal.holds(task)
246
+ or any(shell.holds(task) for shell in self._shells.values())
247
+ )
248
+
249
+ # -- driving ---------------------------------------------------------------
250
+
251
+ async def run(
252
+ self,
253
+ input_text: str,
254
+ *,
255
+ startup: tuple[Callable[[], Awaitable[None]], ...],
256
+ record_end: Callable[[WorkflowRunResult], Awaitable[None]],
257
+ ) -> WorkflowRunResult:
258
+ """Start the scheduler with *input_text* and return once the run is terminal and torn down."""
259
+ self._started = time.monotonic()
260
+ self._record_end = record_end
261
+ if self._timeout is not None and self._terminal is None:
262
+ self._deadline_task = self._spawn(self._deadline(self._timeout))
263
+ self._worker_watch = self._spawn(self._watch_worker())
264
+ try:
265
+ # Acceptance owns these facts even if cancellation already settled
266
+ # the scheduler. Only hook execution and node work are cancellable.
267
+ if self._trace is not None:
268
+ await self._trace.started()
269
+ await self._record(self._journal.run_started())
270
+ for step in startup:
271
+ await self._startup_step(step)
272
+ if self._terminal is None:
273
+ await self._feed(lambda: self._scheduler.start(WorkflowValue(text=input_text)))
274
+ await self._terminal_event.wait()
275
+ await self._teardown()
276
+ except asyncio.CancelledError:
277
+ await asyncio.shield(self._abandon(REASON_SHUTDOWN))
278
+ raise
279
+ except Exception as exc:
280
+ # Even a failure before a scheduler input must leave a terminal record and a closed worker.
281
+ logger.exception("workflow run %s: the runner failed", self.run_id)
282
+ reason = REASON_INTERNAL_ERROR
283
+ if isinstance(exc, WorkflowStorageFailed):
284
+ self._storage_failed(str(exc))
285
+ reason = ""
286
+ else:
287
+ self._unexpected_failure(f"{type(exc).__name__}: {exc}")
288
+ result = await self._abandon(reason)
289
+ if result is None:
290
+ raise
291
+ return result
292
+ return await self._finish()
293
+
294
+ async def _startup_step(self, step: Callable[[], Awaitable[None]]) -> None:
295
+ """Dispatch each lifecycle hook once, cancelling its waits when the run ends."""
296
+
297
+ async def invoke() -> None:
298
+ task = asyncio.current_task()
299
+ if task is None:
300
+ raise RuntimeError("Dispatching workflow hooks requires an asyncio task.")
301
+ self._tasks.add(task) # inline subscribers must recognize their run owner
302
+ await step()
303
+
304
+ # Enter the dispatch before cancellation can prevent the coroutine from
305
+ # ever running. Already-cancelled runs still announce start/end; blocking
306
+ # startup work stops at its first suspension and drains before proceeding.
307
+ task = asyncio.create_task(invoke(), eager_start=True)
308
+ task.add_done_callback(self._forget)
309
+ if self._terminal is not None:
310
+ task.cancel()
311
+ (result,) = await asyncio.gather(task, return_exceptions=True)
312
+ if isinstance(result, asyncio.CancelledError):
313
+ if self._terminal is None:
314
+ raise result
315
+ elif isinstance(result, Exception):
316
+ cancelled = self.cancel(reason=REASON_INTERNAL_ERROR)
317
+ if cancelled is not None:
318
+ await cancelled
319
+ elif isinstance(result, BaseException):
320
+ raise result
321
+
322
+ def cancel(self, *, reason: str = "") -> asyncio.Task[None] | None:
323
+ """Cancel the run; ``reason`` refines the terminal and picks the abort cause agent passes see.
324
+
325
+ The scheduler input is queued, never awaited here: a caller reacting inline to one of the run's
326
+ own events (published under the runner lock) must not wait for that lock. The queued input is
327
+ returned for a caller outside that lock which needs it applied; ``None`` once the run is terminal.
328
+ """
329
+ if self._terminal is not None:
330
+ return None
331
+ if self._cause is None:
332
+ self._reason = reason
333
+ self._cause = _CANCEL_CAUSES.get(reason, AbortCause.USER_CANCEL)
334
+ self._cancel_task = self._spawn(self._feed(self._scheduler.cancel))
335
+ return self._cancel_task
336
+
337
+ def retry(self, node_id: str, activation_id: str, request_id: str, expected_failed_attempt: int) -> None:
338
+ """A manual retry request, queued like :meth:`cancel`; rejections are logged (replays are harmless)."""
339
+ if self._terminal is not None:
340
+ return
341
+ self._spawn(
342
+ self._feed(
343
+ lambda: self._scheduler.manual_retry(node_id, activation_id, request_id, expected_failed_attempt)
344
+ )
345
+ )
346
+
347
+ def answer(self, node_id: str, activation_id: str, request_id: str, answers: tuple[AskUserAnswer, ...]) -> bool:
348
+ """Queue one answer, including from an inline ask subscriber; reject duplicate or invalid admissions.
349
+
350
+ An invalid answer leaves the ask open, so a corrected one can still follow.
351
+ """
352
+ pending = self._asks.get(request_id)
353
+ if (
354
+ self._terminal is not None
355
+ or pending is None
356
+ or pending.future.done()
357
+ or pending.answering
358
+ or (pending.ref.node_id, pending.ref.activation_id) != (node_id, activation_id)
359
+ ):
360
+ return False
361
+ validated = validate_ask_user_answers(answers, questions=pending.questions)
362
+ if validated is None:
363
+ return False
364
+ pending.answering = True
365
+ self._spawn(self._answer(pending, request_id, validated))
366
+ return True
367
+
368
+ async def _answer(self, pending: _PendingAsk, request_id: str, answers: tuple[AskUserAnswer, ...]) -> None:
369
+ async def commit() -> None:
370
+ if (
371
+ await self._record(self._journal.node_answer(pending.ref, request_id, pending.questions, answers))
372
+ is None
373
+ ):
374
+ return
375
+ if not pending.future.done():
376
+ pending.future.set_result(answers)
377
+
378
+ # Once admitted, the answer owns its durable write through cancellation.
379
+ await finish_close(asyncio.create_task(commit()))
380
+
381
+ # -- worker callbacks ----------------------------------------------------------
382
+
383
+ async def on_ask(self, ref: AttemptRef, questions: tuple[AskUserQuestion, ...]) -> tuple[AskUserAnswer, ...]:
384
+ if self._mode is RunMode.HEADLESS:
385
+ raise AskUnavailable("nobody can answer in a headless run.")
386
+ if self._terminal is not None:
387
+ raise AskUnavailable("the run is over.")
388
+ request_id = new_analytics_id()
389
+ pending = _PendingAsk(ref, questions)
390
+ self._asks[request_id] = pending
391
+ try:
392
+ if await self._record(self._journal.node_ask(ref, request_id, questions)) is None:
393
+ raise AskUnavailable("the run record failed.")
394
+ return await pending.future
395
+ finally:
396
+ self._asks.pop(request_id, None)
397
+
398
+ async def on_emit(self, ref: AttemptRef, ordinal: int, text: str) -> None:
399
+ if not await self._write_emit(ref, ordinal, text):
400
+ return
401
+ await self._record(self._journal.node_output(ref, WORKFLOW_OUTPUT_EMIT, ordinal, text))
402
+
403
+ # -- scheduler loop ------------------------------------------------------------
404
+
405
+ async def _feed(self, produce: Callable[[], tuple[Decision, ...]]) -> None:
406
+ """One scheduler input and every decision it caused, executed before the next input."""
407
+ async with self._lock:
408
+ self._lock_holder = asyncio.current_task()
409
+ try:
410
+ if self._terminal is not None:
411
+ return
412
+ await self._apply(produce())
413
+ if self._terminal is None and self._scheduler.finished is not None:
414
+ # A prior decision publisher may have failed after the scheduler reached its terminal.
415
+ self._on_terminal(self._scheduler.finished)
416
+ while self._deferred_fault is not None and self._terminal is None:
417
+ fault, self._deferred_fault = self._deferred_fault, None
418
+ await self._apply(fault())
419
+ finally:
420
+ self._lock_holder = None
421
+
422
+ async def _apply(self, decisions: tuple[Decision, ...]) -> None:
423
+ for decision in decisions:
424
+ match decision:
425
+ case NodeStateChanged():
426
+ await self._on_state(decision)
427
+ case Activate():
428
+ self._spawn(self._activate(decision))
429
+ case EvaluateOutgoing():
430
+ self._spawn(self._evaluate_outgoing(decision))
431
+ case EvaluateLoopUntil():
432
+ self._spawn(self._evaluate_until(decision))
433
+ case ScheduleRetry():
434
+ self._spawn(self._backoff(decision))
435
+ case PersistRetryKey():
436
+ await self._record(self._journal.retry_key(decision.request_id, decision.ref))
437
+ case RetryRejected():
438
+ logger.info(
439
+ "workflow run %s rejected retry %s of %s: %s",
440
+ self._run_id,
441
+ decision.request_id,
442
+ decision.activation_id,
443
+ decision.reason.value,
444
+ )
445
+ case CancelActivation():
446
+ self._cancel_activation(decision.ref)
447
+ case LoopActivated():
448
+ await self._write_input(decision.ref, {"value": value_to_wire(decision.value)})
449
+ case LoopIteration():
450
+ await self._record(
451
+ self._journal.loop_iteration(decision.ref, decision.iteration, decision.verdict.value)
452
+ )
453
+ if self._loop_settles(decision):
454
+ await self._settle_loop(decision.ref, decision.value)
455
+ case RunFinished():
456
+ self._on_terminal(decision)
457
+
458
+ async def _on_state(self, decision: NodeStateChanged) -> None:
459
+ ref = decision.ref
460
+ invocation_id = ""
461
+ kind = self._graph.nodes[ref.node_id].kind
462
+ if kind == KIND_AGENT and ref.attempt > 0:
463
+ invocation_id = self._invocation_id(ref)
464
+ failure = decision.error
465
+ if self._trace is not None:
466
+ await self._trace.node_state(decision, kind=kind)
467
+ await self._record(
468
+ self._journal.node_state(
469
+ ref,
470
+ decision.state.value,
471
+ invocation_id=invocation_id,
472
+ error=failure.message if failure is not None else "",
473
+ error_class=failure.error_class.value if failure is not None else "",
474
+ durable=decision.state in _DURABLE_STATES,
475
+ iteration=decision.iteration,
476
+ failure_phase=decision.phase.value if decision.phase is not None else "",
477
+ )
478
+ )
479
+ if decision.state is ActivationState.COMPLETED and ref.activation_id in self._shells:
480
+ # Body success alone is not terminal: outgoing conditions can still
481
+ # fail and retry the same activation. Release only after settlement,
482
+ # outside the scheduler lock because close drains event publishers.
483
+ self._spawn(self._close_shell(ref.activation_id))
484
+
485
+ def _on_terminal(self, decision: RunFinished) -> None:
486
+ self._terminal = decision
487
+ if self._cause is None:
488
+ self._cause = AbortCause.RUN_TERMINAL
489
+ current = asyncio.current_task()
490
+ for task in self._tasks:
491
+ if task is not current:
492
+ task.cancel()
493
+ for pending in self._asks.values():
494
+ pending.future.cancel()
495
+ self._terminal_event.set()
496
+
497
+ def _cancel_activation(self, ref: AttemptRef) -> None:
498
+ """Only ever issued by a terminal: the local task is cancelled here, the worker at teardown."""
499
+ shell = self._shells.get(ref.activation_id)
500
+ if shell is not None:
501
+ cause = self._cause or AbortCause.RUN_TERMINAL
502
+ task = asyncio.create_task(shell.abort(cause), name=f"chrys.workflow.abort.{ref.activation_id}")
503
+ self._aborts.add(task)
504
+ task.add_done_callback(self._aborts.discard)
505
+
506
+ # -- attempts --------------------------------------------------------------------
507
+
508
+ async def _activate(self, decision: Activate) -> None:
509
+ ref = decision.ref
510
+ try:
511
+ if decision.kind == KIND_PYTHON:
512
+ if decision.value is None:
513
+ raise RuntimeError("Activating a Python or agent node requires an input value.")
514
+ await self._run_python(ref, decision.value)
515
+ elif decision.kind == KIND_AGENT:
516
+ if decision.value is None:
517
+ raise RuntimeError("Activating a Python or agent node requires an input value.")
518
+ async with self._agent_slots:
519
+ await self._run_agent(ref, decision.value)
520
+ elif decision.kind == KIND_JOIN:
521
+ await self._run_join(ref, decision.value, decision.sources)
522
+ else:
523
+ await self._fail(
524
+ ref, FailureReport(ErrorClass.PYTHON_EXCEPTION, f"unknown node kind {decision.kind!r}")
525
+ )
526
+ except WorkerLostError as exc:
527
+ await self._worker_lost(str(exc))
528
+ except (ProtocolError, ValueShapeError) as exc:
529
+ await self._worker_lost(f"worker protocol violation at {ref.activation_id}: {exc}")
530
+ except WorkflowStorageFailed as exc:
531
+ self._storage_failed(str(exc))
532
+
533
+ async def _run_python(self, ref: AttemptRef, value: WorkflowValue) -> None:
534
+ if not await self._write_input(ref, {"value": value_to_wire(value)}):
535
+ return
536
+ node = self._graph.nodes[ref.node_id]
537
+ try:
538
+ result = await self._worker.run_python(ref, value, blocking=not node.fn_is_async, timeout=node.timeout)
539
+ except WorkerRpcError as exc:
540
+ await self._write_diagnostics(ref, "body", exc.stdout, exc.traceback)
541
+ if exc.code == ErrorCode.ATTEMPT_TERMINATED and self._terminal is not None:
542
+ return
543
+ error_class = _PYTHON_FAILURE_CLASSES.get(exc.code, ErrorClass.PYTHON_EXCEPTION)
544
+ message = exc.message if exc.code in _PYTHON_FAILURE_CLASSES else f"{exc.code}: {exc.message}"
545
+ await self._fail(ref, FailureReport(error_class, message))
546
+ except AttemptTimeout as exc:
547
+ await self._write_diagnostics(ref, "body", exc.stdout, exc.traceback)
548
+ message = f"no result within {node.timeout:g}s"
549
+ if exc.leaked_thread:
550
+ message += "; its thread is still running"
551
+ await self._fail(ref, FailureReport(ErrorClass.PYTHON_TIMEOUT, message))
552
+ else:
553
+ await self._write_diagnostics(ref, "body", result.stdout)
554
+ await self._complete(ref, result.value, last_emit_ordinal=result.last_emit_ordinal)
555
+
556
+ async def _run_agent(self, ref: AttemptRef, value: WorkflowValue) -> None:
557
+ record: dict[str, Any] = {"value": value_to_wire(value)}
558
+ if value.data is not None:
559
+ record[DATA_DROPPED_KEY] = True
560
+ if not await self._write_input(ref, record):
561
+ return
562
+ if value.data is not None and not self._data_dropped_noticed:
563
+ self._data_dropped_noticed = True
564
+ await self._record(
565
+ self._journal.run_notice(
566
+ ref.node_id,
567
+ ref.activation_id,
568
+ WORKFLOW_NOTICE_DATA_DROPPED,
569
+ f"Node {ref.node_id!r} received structured data; an agent node consumes only the text.",
570
+ )
571
+ )
572
+ shell = self._shell_for(ref)
573
+ if not shell.is_prepared:
574
+ try:
575
+ await shell.open(value.text, attempt=ref.attempt)
576
+ except WorkflowStorageFailed as exc:
577
+ self._storage_failed(str(exc))
578
+ return
579
+ except Exception as exc:
580
+ logger.warning(
581
+ "workflow run %s: agent node %s could not open", self._run_id, ref.node_id, exc_info=True
582
+ )
583
+ with contextlib.suppress(Exception):
584
+ await shell.close()
585
+ del self._shells[ref.activation_id] # the next attempt builds a fresh shell under the same identity
586
+ await self._fail(ref, FailureReport(ErrorClass.AGENT_NON_TRANSIENT, f"{type(exc).__name__}: {exc}"))
587
+ return
588
+ try:
589
+ result = await shell.run(
590
+ value.text,
591
+ timeout=self._graph.nodes[ref.node_id].timeout,
592
+ trajectory_context=self._trace.node_context(ref.activation_id, ref.attempt) if self._trace else None,
593
+ attempt=ref.attempt,
594
+ )
595
+ except WorkflowStorageFailed as exc:
596
+ self._storage_failed(str(exc))
597
+ return
598
+ if result.kind == "completed":
599
+ await self._complete(ref, WorkflowValue(text=result.text))
600
+ elif result.kind == "failed":
601
+ if result.failure is None:
602
+ raise RuntimeError("A failed agent attempt requires a failure report.")
603
+ await self._fail(ref, result.failure)
604
+ elif result.kind == "cancelled" and self._terminal is None:
605
+ await self._fail(ref, FailureReport(ErrorClass.AGENT_NON_TRANSIENT, "agent pass was cancelled"))
606
+
607
+ async def _run_join(self, ref: AttemptRef, value: WorkflowValue | None, sources: tuple[SourceValue, ...]) -> None:
608
+ if not await self._write_input(ref, {"sources": [source_to_wire(source) for source in sources]}):
609
+ return
610
+ if value is not None: # no user combine: the scheduler already folded the sources
611
+ await self._complete(ref, value)
612
+ return
613
+ timeout = self._graph.nodes[ref.node_id].timeout
614
+ try:
615
+ combined = await self._worker.combine(ref, sources, timeout=timeout)
616
+ except WorkerRpcError as exc:
617
+ await self._write_diagnostics(ref, "combine", exc.stdout, exc.traceback)
618
+ error_class = (
619
+ ErrorClass.VALUE_TOO_LARGE if exc.code == ErrorCode.VALUE_TOO_LARGE else ErrorClass.EVALUATION_ERROR
620
+ )
621
+ await self._fail(ref, FailureReport(error_class, f"combine failed: {exc.message}"))
622
+ except AttemptTimeout as exc:
623
+ await self._write_diagnostics(ref, "combine", exc.stdout, exc.traceback)
624
+ await self._fail(ref, FailureReport(ErrorClass.EVALUATION_ERROR, "combine did not finish in time."))
625
+ else:
626
+ await self._write_diagnostics(ref, "combine", combined.stdout)
627
+ await self._complete(ref, combined.value)
628
+
629
+ async def _evaluate_outgoing(self, decision: EvaluateOutgoing) -> None:
630
+ ref = decision.ref
631
+ try:
632
+ decided = await self._worker.eval_outgoing(ref, decision.value, decision.edge_ids)
633
+ except WorkerLostError as exc:
634
+ await self._worker_lost(str(exc))
635
+ return
636
+ except WorkerRpcError as exc:
637
+ await self._write_diagnostics(ref, "outgoing", exc.stdout, exc.traceback)
638
+ message = f"{exc.code}: {exc.message}"
639
+ except AttemptTimeout as exc:
640
+ await self._write_diagnostics(ref, "outgoing", exc.stdout, exc.traceback)
641
+ message = "edge condition did not finish in time."
642
+ except (ProtocolError, ValueShapeError) as exc:
643
+ await self._worker_lost(f"edge decisions are malformed: {exc}")
644
+ return
645
+ else:
646
+ await self._write_diagnostics(ref, "outgoing", decided.stdout)
647
+ await self._feed(lambda: self._scheduler.outgoing_evaluated(ref, decided.value))
648
+ return
649
+ await self._feed(lambda: self._scheduler.outgoing_failed(ref, message))
650
+
651
+ async def _evaluate_until(self, decision: EvaluateLoopUntil) -> None:
652
+ ref, iteration = decision.ref, decision.iteration
653
+ try:
654
+ verdict = await self._worker.eval_loop_until(ref, iteration, decision.value)
655
+ except WorkerLostError as exc:
656
+ await self._worker_lost(str(exc))
657
+ return
658
+ except WorkerRpcError as exc:
659
+ await self._write_diagnostics(ref, "until", exc.stdout, exc.traceback, iteration=iteration)
660
+ message = f"{exc.code}: {exc.message}"
661
+ except AttemptTimeout as exc:
662
+ await self._write_diagnostics(ref, "until", exc.stdout, exc.traceback, iteration=iteration)
663
+ message = "loop condition did not finish in time."
664
+ except (ProtocolError, ValueShapeError) as exc:
665
+ await self._worker_lost(f"loop verdict is malformed: {exc}")
666
+ return
667
+ else:
668
+ await self._write_diagnostics(ref, "until", verdict.stdout, iteration=iteration)
669
+ await self._feed(lambda: self._scheduler.loop_until_evaluated(ref, iteration, verdict.value))
670
+ return
671
+ await self._feed(lambda: self._scheduler.loop_until_failed(ref, iteration, message))
672
+
673
+ async def _backoff(self, decision: ScheduleRetry) -> None:
674
+ await asyncio.sleep(decision.backoff)
675
+ await self._feed(lambda: self._scheduler.backoff_elapsed(decision.ref))
676
+
677
+ async def _deadline(self, timeout: float) -> None:
678
+ await asyncio.sleep(timeout)
679
+ self.cancel(reason=REASON_DEADLINE_EXCEEDED)
680
+
681
+ async def _watch_worker(self) -> None:
682
+ """A worker lost while no call is outstanding (a node awaiting retry, an agent-only stretch) still ends the run."""
683
+ error = await self._worker.wait_lost()
684
+ await self._worker_lost(str(error))
685
+
686
+ async def _worker_lost(self, message: str) -> None:
687
+ if not self._worker_error:
688
+ self._worker_error = message
689
+ await self._feed(self._scheduler.worker_lost)
690
+
691
+ def _loop_settles(self, decision: LoopIteration) -> bool:
692
+ """Whether this verdict makes the iteration's exit value the loop activation's output."""
693
+ if decision.verdict is LoopVerdict.EXIT:
694
+ return True
695
+ loop = self._graph.nodes[decision.ref.node_id].loop
696
+ return (
697
+ decision.verdict is LoopVerdict.EXHAUSTED
698
+ and loop is not None
699
+ and loop.on_exhausted == ON_EXHAUSTED_CONTINUE
700
+ )
701
+
702
+ async def _settle_loop(self, ref: AttemptRef, value: WorkflowValue) -> None:
703
+ """The loop activation's output, recorded like any node's: the value in full, then its final event."""
704
+ if await self._write_output(ref, value):
705
+ await self._record(self._journal.node_output(ref, WORKFLOW_OUTPUT_FINAL, 1, value.text))
706
+
707
+ async def _complete(self, ref: AttemptRef, value: WorkflowValue, *, last_emit_ordinal: int = 0) -> None:
708
+ if not await self._write_output(ref, value):
709
+ return
710
+ if (
711
+ await self._record(self._journal.node_output(ref, WORKFLOW_OUTPUT_FINAL, last_emit_ordinal + 1, value.text))
712
+ is None
713
+ ):
714
+ return
715
+ await self._feed(lambda: self._scheduler.activation_completed(ref, value))
716
+
717
+ async def _fail(self, ref: AttemptRef, failure: FailureReport) -> None:
718
+ await self._feed(lambda: self._scheduler.activation_failed(ref, failure))
719
+
720
+ # -- records ---------------------------------------------------------------------
721
+
722
+ async def _record(self, write: Awaitable[int]) -> int | None:
723
+ """One journal write; a storage failure becomes the run's fault instead of an exception."""
724
+ try:
725
+ return await write
726
+ except WorkflowStorageFailed:
727
+ self._storage_failed()
728
+ return None
729
+
730
+ async def _write_diagnostics(
731
+ self, ref: AttemptRef, phase: str, stdout: CapturedOutput, traceback: str = "", *, iteration: int | None = None
732
+ ) -> None:
733
+ if iteration is None:
734
+ snapshot = self._scheduler.snapshot(ref.activation_id)
735
+ iteration = snapshot.iteration if snapshot is not None else 0
736
+ await self._journal.store.offload(
737
+ self._journal.store.write_node_diagnostics,
738
+ ref.activation_id,
739
+ ref.attempt,
740
+ phase=phase,
741
+ iteration=iteration,
742
+ stdout=stdout.text,
743
+ truncated=stdout.truncated,
744
+ traceback=traceback,
745
+ )
746
+
747
+ async def _write_input(self, ref: AttemptRef, record: dict[str, Any]) -> bool:
748
+ return await self._write_node_record(ref, NODE_RECORD_INPUT, record)
749
+
750
+ async def _write_output(self, ref: AttemptRef, value: WorkflowValue) -> bool:
751
+ # Retrying a loop's outgoing condition advances its attempt without rewriting this value.
752
+ self._output_attempts[ref.activation_id] = ref.attempt
753
+ return await self._write_node_record(ref, NODE_RECORD_OUTPUT, {"value": value_to_wire(value)})
754
+
755
+ async def _write_emit(self, ref: AttemptRef, ordinal: int, text: str) -> bool:
756
+ return await self._store(self._journal.store.append_node_emit, ref.activation_id, ref.attempt, ordinal, text)
757
+
758
+ async def _write_node_record(self, ref: AttemptRef, kind: str, record: dict[str, Any]) -> bool:
759
+ return await self._store(self._journal.store.write_node_value, ref.activation_id, ref.attempt, kind, record)
760
+
761
+ async def _store[**P](self, write: Callable[P, object], *args: P.args, **kwargs: P.kwargs) -> bool:
762
+ try:
763
+ await self._journal.store.offload(write, *args, **kwargs)
764
+ except WorkflowStorageFailed:
765
+ self._storage_failed()
766
+ return False
767
+ return True
768
+
769
+ def _storage_failed(self, error: str = "A node record could not be written.") -> None:
770
+ """Queue the fault; the lock holder applies it after the current decisions, anyone else feeds it now.
771
+
772
+ The loss is remembered either way: a terminal settled in the same batch (or already settled) no
773
+ longer takes the fault as a scheduler input, and the run still finishes as ``storage_failed``.
774
+ """
775
+ if self._record_error is None:
776
+ self._record_error = error
777
+ if self._terminal is not None or self._deferred_fault is not None:
778
+ return
779
+ if self._lock.locked():
780
+ self._deferred_fault = self._scheduler.storage_failed
781
+ else:
782
+ self._spawn(self._feed(self._scheduler.storage_failed))
783
+
784
+ # -- shells and tasks --------------------------------------------------------------
785
+
786
+ def _invocation_id(self, ref: AttemptRef) -> str:
787
+ """The invocation identity of an agent activation: allocated once, kept across attempts."""
788
+ invocation_id = self._invocations.get(ref.activation_id)
789
+ if invocation_id is None:
790
+ invocation_id = new_analytics_id()
791
+ self._invocations[ref.activation_id] = invocation_id
792
+ return invocation_id
793
+
794
+ def set_approval_mode(self, mode: ApprovalMode) -> None:
795
+ """Update live agent shells; shells opened later read the engine's launch policy."""
796
+ for shell in list(self._shells.values()):
797
+ shell.set_approval_mode(mode)
798
+
799
+ def _shell_for(self, ref: AttemptRef) -> WorkflowAgentShell:
800
+ shell = self._shells.get(ref.activation_id)
801
+ if shell is None:
802
+ binding = self._admitted.binding(ref.node_id)
803
+ invocation_id = self._invocation_id(ref)
804
+ shell = WorkflowAgentShell(
805
+ binding=binding,
806
+ node_id=ref.node_id,
807
+ invocation_id=invocation_id,
808
+ resources=self._resources,
809
+ archive=AgentNodeArchive(
810
+ self._journal.store,
811
+ activation_id=ref.activation_id,
812
+ invocation_id=invocation_id,
813
+ profile_name=binding.agent.display_name or binding.agent.name,
814
+ ),
815
+ )
816
+ self._shells[ref.activation_id] = shell
817
+ return shell
818
+
819
+ async def _close_shell(self, activation_id: str) -> None:
820
+ await self._shells[activation_id].close()
821
+ del self._shells[activation_id]
822
+
823
+ def _spawn(self, coro: Awaitable[None]) -> asyncio.Task[None]:
824
+ task = asyncio.ensure_future(coro)
825
+ self._tasks.add(task)
826
+ task.add_done_callback(self._forget)
827
+ return task
828
+
829
+ def _forget(self, task: asyncio.Task[None]) -> None:
830
+ self._tasks.discard(task)
831
+ if task.cancelled():
832
+ if self._terminal is None:
833
+ self._unexpected_failure("run task was unexpectedly cancelled")
834
+ return
835
+ exc = task.exception()
836
+ if isinstance(exc, WorkflowStorageFailed):
837
+ self._storage_failed(str(exc))
838
+ elif exc is not None:
839
+ logger.error("workflow run %s: task failed", self._run_id, exc_info=exc)
840
+ self._unexpected_failure(f"{type(exc).__name__}: {exc}")
841
+
842
+ def _unexpected_failure(self, message: str) -> None:
843
+ if not self._internal_error:
844
+ self._internal_error = message
845
+ if self._terminal is None:
846
+ if self._cancel_task is not None and self._cancel_task.done():
847
+ # Cancellation's own publisher failed. Stop work without publishing more node decisions.
848
+ self._on_terminal(self._scheduler.finished or RunFinished(RunOutcome.CANCELLED, ()))
849
+ else:
850
+ self.cancel(reason=REASON_INTERNAL_ERROR)
851
+ elif self._terminal.outcome is RunOutcome.COMPLETED:
852
+ # A close task can fail after the last scheduler decision, before the terminal commit.
853
+ self._terminal = RunFinished(RunOutcome.CANCELLED, self._terminal.outputs)
854
+ self._reason = REASON_INTERNAL_ERROR
855
+
856
+ # -- terminal ------------------------------------------------------------------------
857
+
858
+ async def _teardown(self) -> None:
859
+ """Drain tasks, close shells, and wait for the worker to exit; the terminal is recorded after."""
860
+ current = asyncio.current_task()
861
+ for watch in (self._deadline_task, self._worker_watch):
862
+ if watch is not None:
863
+ watch.cancel() # the terminal is settled: nothing waits out the timer, and the close below is no fault
864
+ live = [task for task in [*self._tasks, *self._aborts] if task is not current]
865
+ if live:
866
+ await asyncio.gather(*live, return_exceptions=True)
867
+ # Every shell, opened or interrupted while opening: close() tolerates both.
868
+ for activation_id, shell in list(self._shells.items()):
869
+ try:
870
+ await shell.close()
871
+ except WorkflowStorageFailed as exc:
872
+ self._storage_failed(str(exc))
873
+ except Exception as exc:
874
+ self._unexpected_failure(f"{type(exc).__name__}: {exc}")
875
+ logger.warning(
876
+ "workflow run %s: agent shell %s did not close cleanly",
877
+ self._run_id,
878
+ activation_id,
879
+ exc_info=True,
880
+ )
881
+ self._shells.clear()
882
+ await self._collect_run_output()
883
+ await self._worker.close()
884
+
885
+ async def _collect_run_output(self) -> None:
886
+ if self._output_collected:
887
+ return
888
+ self._output_collected = True
889
+ payload: dict[str, Any] = {"load": {"text": self._load_stdout.text, "truncated": self._load_stdout.truncated}}
890
+ if self._worker.lost is None:
891
+ try:
892
+ async with asyncio.timeout(3.0):
893
+ native = await self._worker.native_output()
894
+ payload["native"] = {"text": native.text, "dropped_bytes": native.dropped_bytes}
895
+ except Exception as exc:
896
+ payload["error"] = f"Native output could not be collected: {exc}"
897
+ logger.warning("workflow run %s: native output unavailable", self._run_id, exc_info=True)
898
+ else:
899
+ payload["error"] = "Native output unavailable because the worker was lost."
900
+ try:
901
+ await self._journal.store.offload(self._journal.store.write_run_output, payload)
902
+ except OSError, ValueError, TypeError:
903
+ logger.warning("workflow run %s: run output could not be written", self._run_id, exc_info=True)
904
+
905
+ async def _abandon(self, reason: str) -> WorkflowRunResult | None:
906
+ """The run cannot continue on its own terms: converge as a cancel for *reason* and still tear down."""
907
+ if self._terminal is None:
908
+ if self._cause is None:
909
+ self._reason = reason
910
+ self._cause = _CANCEL_CAUSES.get(reason, AbortCause.OWNER_CLOSE)
911
+ with contextlib.suppress(Exception):
912
+ await self._feed(self._scheduler.cancel)
913
+ if self._terminal is None:
914
+ self._on_terminal(self._scheduler.finished or RunFinished(RunOutcome.CANCELLED, ()))
915
+ with contextlib.suppress(Exception):
916
+ await self._teardown()
917
+ if self._terminal is None:
918
+ return None
919
+ with contextlib.suppress(Exception):
920
+ return await self._finish()
921
+ return None
922
+
923
+ async def _finish(self) -> WorkflowRunResult:
924
+ if self._result is not None:
925
+ return self._result
926
+ terminal = self._terminal
927
+ if terminal is None:
928
+ raise RuntimeError("Finishing a workflow run requires a terminal decision.")
929
+ outputs = terminal.outputs
930
+ duration = time.monotonic() - self._started
931
+ summaries = [
932
+ WorkflowOutputSummary(
933
+ item.node_id,
934
+ item.activation_id,
935
+ summarize(item.value.text),
936
+ attempt=self._output_attempts[item.activation_id],
937
+ )
938
+ for item in outputs
939
+ ]
940
+ error = terminal.error.message if terminal.error is not None else ""
941
+ node_id = terminal.node_id or ""
942
+ outcome = terminal.outcome
943
+ if outcome is RunOutcome.WORKER_LOST:
944
+ error = self._worker_error or str(self._worker.lost or "worker lost")
945
+ elif self._reason == REASON_INTERNAL_ERROR:
946
+ error = self._internal_error or error
947
+ if outcome is RunOutcome.COMPLETED:
948
+ outcome = RunOutcome.CANCELLED
949
+ if self._record_error is not None:
950
+ # A node record that failed to write after (or in the very batch of) the terminal: the run
951
+ # is not the completed one its outputs claim, so the record, the event and the result all say so.
952
+ outcome = RunOutcome.STORAGE_FAILED
953
+ error = f"{self._record_error}\n{error}" if error else self._record_error
954
+ try:
955
+ await self._checkpoint()
956
+ except Exception as exc:
957
+ logger.exception("workflow run %s: final session checkpoint failed", self._run_id)
958
+ outcome = RunOutcome.STORAGE_FAILED
959
+ checkpoint_error = f"Session checkpoint could not be saved: {type(exc).__name__}: {exc}"
960
+ error = f"{checkpoint_error}\n{error}" if error else checkpoint_error
961
+ await self._journal.finish(
962
+ outcome,
963
+ outputs=summaries,
964
+ duration=duration,
965
+ node_id=node_id,
966
+ error=error,
967
+ reason=self._reason,
968
+ )
969
+ if self._journal.storage_failed:
970
+ outcome = RunOutcome.STORAGE_FAILED
971
+ self._result = WorkflowRunResult(
972
+ run_id=self._run_id,
973
+ outcome=outcome,
974
+ outputs=outputs,
975
+ node_id=node_id,
976
+ error=error,
977
+ reason=self._reason,
978
+ duration=duration,
979
+ )
980
+ try:
981
+ if self._record_end is not None:
982
+ await self._record_end(self._result)
983
+ finally:
984
+ if self._trace is not None:
985
+ self._trace.finished(outcome.value)
986
+ return self._result