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,1095 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Main Turn middleware, presentation callbacks, and backend construction."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import logging
9
+ import traceback
10
+ from dataclasses import dataclass
11
+ from datetime import datetime
12
+ from typing import TYPE_CHECKING, Any, Literal, TypedDict
13
+
14
+ from chrys.foundation.errors import clean_error_message
15
+ from chrys.foundation.errors.display import display_fields
16
+ from chrys.foundation.events.types import (
17
+ AgentThinking,
18
+ Error,
19
+ InvocationMessage,
20
+ InvocationPresentationAttemptAccepted,
21
+ InvocationPresentationAttemptRejected,
22
+ InvocationRetryAttempt,
23
+ InvocationToolCallArgsUpdated,
24
+ InvocationToolCallProgress,
25
+ InvocationToolCallResult,
26
+ InvocationToolCallStart,
27
+ InvocationToolCallStatusUpdated,
28
+ ProvisionalPresentation,
29
+ )
30
+ from chrys.foundation.hosted_tools import HOSTED_TOOL_DEFAULT_KIND_BY_FAMILY, HostedToolStatus
31
+ from chrys.foundation.retry import (
32
+ TRANSIENT_RETRY_BACKOFF_SECONDS,
33
+ StreamStall,
34
+ )
35
+ from chrys.foundation.trajectory.envelope import Link, LinkRelation
36
+ from chrys.foundation.util.time import parse_created_at
37
+ from chrys.kernel import (
38
+ Agent,
39
+ AgentSession,
40
+ ConsumedInjectionMessageProbe,
41
+ LoopRecorderSnapshot,
42
+ StallExhaustedAction,
43
+ WireRetryPolicy,
44
+ resolve_storage_mode_and_handles,
45
+ )
46
+ from chrys.orchestration.engine.run.resume import TurnPassState, TurnResumePolicy
47
+ from chrys.orchestration.invoker.attempts import (
48
+ AgentRunKwargs,
49
+ AttemptRecipe,
50
+ AttemptRunner,
51
+ AttemptTaskHandle,
52
+ BlockingCallTiming,
53
+ HistoryRollback,
54
+ ModelRunTrace,
55
+ RestoreAndFallback,
56
+ RetryBoundaryPolicy,
57
+ WireRecipe,
58
+ continuation_token_observer_for,
59
+ is_string_keyed_dict,
60
+ )
61
+ from chrys.orchestration.invoker.contracts import (
62
+ AbortCause,
63
+ FailureDisposition,
64
+ InvocationOutcome,
65
+ RunIntent,
66
+ RunRequest,
67
+ )
68
+ from chrys.orchestration.invoker.kernel import KernelConversation
69
+ from chrys.orchestration.invoker.origin import BoundEmitter, InvocationPublishers
70
+ from chrys.orchestration.invoker.resources import Conversation, PassResources
71
+ from chrys.service.agent_middleware import (
72
+ ApprovalMiddleware,
73
+ AskUserMiddleware,
74
+ IntermediateTextBuffer,
75
+ InterruptMiddleware,
76
+ SleepMiddleware,
77
+ ToolEventMiddleware,
78
+ )
79
+ from chrys.service.agent_middleware.control.approval import ApprovalRetrySnapshot
80
+ from chrys.service.agent_middleware.events.hosted_tools import (
81
+ FinalTextOp,
82
+ HostedPresentationBridge,
83
+ HostedToolArgsOp,
84
+ HostedToolProgressOp,
85
+ HostedToolResultOp,
86
+ HostedToolStartOp,
87
+ HostedToolStatusOp,
88
+ IntermediateTextOp,
89
+ PresentationAttemptAcceptedOp,
90
+ PresentationAttemptRejectedOp,
91
+ PresentationSinkOperation,
92
+ next_hosted_run_generation,
93
+ )
94
+ from chrys.service.agent_middleware.events.tool_events import ToolEventRetrySnapshot
95
+ from chrys.service.agent_middleware.response_validation import (
96
+ ResponseValidationMiddleware,
97
+ )
98
+ from chrys.service.context.providers.history import PRE_OUTPUT_HISTORY_LEN_STATE_KEY
99
+ from chrys.service.session.message_metadata import (
100
+ LAST_ASSISTANT_CREATED_AT_STATE_KEY,
101
+ )
102
+
103
+ if TYPE_CHECKING:
104
+ from collections.abc import Awaitable, Callable, Sequence
105
+
106
+ from chrys.foundation.events.bus import EventBus
107
+ from chrys.foundation.trajectory.context import TrajectoryContext
108
+ from chrys.kernel import AgentResponse, AgentResponseUpdate, LoopRecorder
109
+ from chrys.kernel.middleware import FunctionMiddleware
110
+ from chrys.service.agent_middleware.injection import InjectionMiddleware, QueuedInjection
111
+ from chrys.service.context.compaction import UnifiedContextStrategy
112
+ from chrys.service.context.compaction.strategy import CompactionRetrySnapshot
113
+ from chrys.service.hooks.manager import HookManager
114
+ from chrys.service.mutations.coordination import MutationCoordinator
115
+ from chrys.service.mutations.tracker import MutationTracker
116
+ from chrys.service.trajectory.preparation import PreparationTrace
117
+
118
+
119
+ logger = logging.getLogger(__name__)
120
+
121
+
122
+ class _AssistantMessageEventKwargs(TypedDict, total=False):
123
+ """Optional timestamp forwarded to ``InvocationMessage``."""
124
+
125
+ timestamp: datetime
126
+
127
+
128
+ @dataclass(frozen=True, slots=True)
129
+ class _TurnRetryState:
130
+ """Auxiliary per-run buffers rolled back with framework history."""
131
+
132
+ loop_recorder: LoopRecorderSnapshot | None
133
+ tool_events: ToolEventRetrySnapshot
134
+ approval: ApprovalRetrySnapshot
135
+ compaction: CompactionRetrySnapshot | None
136
+
137
+
138
+ class TurnBindings:
139
+ """Compose main-pass callbacks and middleware; owns no execution facade.
140
+
141
+ KernelConversation runs the pass; TurnRunner owns the user turn.
142
+ Resource references here are borrowed from the installed PreparedAgent owner.
143
+ """
144
+
145
+ # Resilience: retry on transient network errors (proxy drops,
146
+ # connection resets, timeouts). Applies to both blocking and
147
+ # streaming paths. The classifier lives in :mod:`chrys.foundation.errors`
148
+ # and the loop lives in :mod:`chrys.foundation.retry` so sub-agents share
149
+ # the same policy without reaching back into this class.
150
+ _MAX_RETRIES = 5
151
+ _BACKOFF_SCHEDULE = TRANSIENT_RETRY_BACKOFF_SECONDS
152
+ # Fallback when no per-run stream_attempt_timeout is supplied (e.g. tests).
153
+ # Production callers pass ModelProfile.http_read_timeout so stall detection
154
+ # matches the HTTP client's read timeout.
155
+ _DEFAULT_STREAM_ATTEMPT_TIMEOUT = 300.0 # seconds
156
+ # Class-level fallbacks so partially-constructed bindings (tests build
157
+ # them via ``object.__new__``) read "no probe wired" instead of raising.
158
+ _hosted_commits_probe: Callable[[], tuple[str, ...]] | None = None
159
+ _hosted_commits_in_flight_probe: Callable[[], tuple[str, ...]] | None = None
160
+ _max_retries_override: int | None = None
161
+ _hosted_bridge: HostedPresentationBridge | None = None
162
+
163
+ def __init__(
164
+ self,
165
+ *,
166
+ conversation: Conversation,
167
+ agent: Agent,
168
+ session: AgentSession,
169
+ event_bus: EventBus,
170
+ approval_middleware: ApprovalMiddleware,
171
+ ask_user_middleware: AskUserMiddleware,
172
+ injection_middleware: InjectionMiddleware,
173
+ loop_recorder: LoopRecorder | None = None,
174
+ session_id: str | None = None,
175
+ compaction_strategy: UnifiedContextStrategy | None = None,
176
+ stream: bool = False,
177
+ intermediate_buffer: IntermediateTextBuffer | None = None,
178
+ chat_options: dict[str, Any] | None = None,
179
+ mutation_tracker: MutationTracker | None = None,
180
+ mutation_coordinator: MutationCoordinator | None = None,
181
+ stream_attempt_timeout: float | None = None,
182
+ max_transient_retries: int | None = None,
183
+ hook_manager: HookManager | None = None,
184
+ profile_name: str = "",
185
+ workspace_cwd: str = "",
186
+ serialize_implicit_windows: bool = False,
187
+ extra_function_middleware: Sequence[FunctionMiddleware] = (),
188
+ run_cycle_start_hooks: Sequence[Callable[[], None]] = (),
189
+ hosted_commits_probe: Callable[[], tuple[str, ...]] | None = None,
190
+ hosted_commits_in_flight_probe: Callable[[], tuple[str, ...]] | None = None,
191
+ response_validation_middleware: ResponseValidationMiddleware | None = None,
192
+ publish_intermediate_text: Callable[[str], Awaitable[None]] | None = None,
193
+ invocation_publishers: InvocationPublishers | None = None,
194
+ commit_intermediate_text: Callable[[str, int], None] | None = None,
195
+ tool_result_ceiling_tokens: int | None = None,
196
+ ) -> None:
197
+ self.resource_scope = conversation
198
+ self._agent = agent
199
+ self._session = session
200
+ self._bus = event_bus
201
+ self.approval = approval_middleware
202
+ self._ask_user = ask_user_middleware
203
+ self._sleep = SleepMiddleware(event_bus, session_id=session_id)
204
+ self._injection = injection_middleware
205
+ self._loop_recorder = loop_recorder
206
+ self._session_id = session_id
207
+ self._compaction_strategy = compaction_strategy
208
+ self._stream = stream
209
+ self._chat_options = chat_options
210
+ self._stream_attempt_timeout = (
211
+ stream_attempt_timeout if stream_attempt_timeout is not None else self._DEFAULT_STREAM_ATTEMPT_TIMEOUT
212
+ )
213
+ self._max_retries_override = max_transient_retries
214
+
215
+ self._intermediate_buffer = intermediate_buffer
216
+ self._response_validation = response_validation_middleware
217
+ self._publish_intermediate_text = publish_intermediate_text
218
+ self._commit_intermediate_text = commit_intermediate_text
219
+ self._provisional_intermediate_batches: dict[tuple[str, str], int] = {}
220
+ self._hosted_run_generation = 0
221
+ self._hosted_bridge = None
222
+ self._interrupt = InterruptMiddleware()
223
+ self._extra_function_middleware = tuple(extra_function_middleware)
224
+ # Fired once at the start of every user-initiated run cycle, before
225
+ # the first attempt. Components that carry state across the cycle's
226
+ # whole-run retry attempts (e.g. the validation middleware's retry
227
+ # budget) register here so an aborted cycle — interrupt during outer
228
+ # backoff, unrelated exception — cannot leak state into the next
229
+ # independent run.
230
+ self._attempt_handle = AttemptTaskHandle()
231
+ self.state = TurnPassState()
232
+ # Validation-middleware probes for provider-hosted tool executions the
233
+ # loop recorder cannot see (the rejected/aborted exchange never reaches
234
+ # it): run-scoped for the whole-run retry gate, wire-attempt-scoped for
235
+ # the kernel's per-wire replay veto.
236
+ self._hosted_commits_probe = hosted_commits_probe
237
+ self._hosted_commits_in_flight_probe = hosted_commits_in_flight_probe
238
+ # Last continuation token announced by the kernel and not yet
239
+ # resolved to a terminal response. Run kwargs are rebuilt per run,
240
+ # so without this carry-over a user retry after a gate-blocked poll
241
+ # failure would CREATE a second background response while the
242
+ # announced one keeps running remotely (duplicate hosted work).
243
+ # Crash-recovery channel to the orchestration turn state, wired by the
244
+ # engine after build (``TurnRuntimeState.set_current_input``). The
245
+ # bare-resume replay branch pops the anchor user message out of state
246
+ # and must register it as recovery current input at pop time — the
247
+ # component that destructively reads is the component that makes it
248
+ # durable.
249
+ # Trajectory recording for the next pass, set by the turn runner:
250
+ # the ambient context every model run of the pass binds (None = not
251
+ # recording) and the pre-minted item id of the message opening it.
252
+ self._attempt_trace = ModelRunTrace(
253
+ interrupted=lambda: self._interrupt.is_interrupted,
254
+ service_side=lambda: self.backend.service_session_storage_enabled,
255
+ committed=lambda: self._loop_recorder is not None and self._loop_recorder.committed_count > 0,
256
+ )
257
+ attempt_recipe = AttemptRecipe(
258
+ stall_exhaustion=RestoreAndFallback(self._reject_stream),
259
+ stall_error=lambda _timeout: StreamStall(0),
260
+ retry_boundary=RetryBoundaryPolicy.SKIP,
261
+ blocking_call_timing=BlockingCallTiming.IN_ATTEMPT_TASK,
262
+ history_state=lambda: session.state.get("chrys_history", {}),
263
+ before_attempt=None,
264
+ )
265
+ self._rollback = HistoryRollback(
266
+ session,
267
+ snapshot_caller=self._snapshot_retry_state,
268
+ restore_caller=self._restore_retry_state,
269
+ history_state=attempt_recipe.history_state,
270
+ )
271
+ self._attempts = AttemptRunner(
272
+ agent=agent,
273
+ session=session,
274
+ handle=self._attempt_handle,
275
+ rollback=self._rollback,
276
+ retry_participant=self._injection,
277
+ interrupt=self._interrupt,
278
+ trace=self._attempt_trace,
279
+ stream_observer=self._make_stream_observer,
280
+ publish_retry=self._publish_retry_notice,
281
+ interruptible_sleep=self._interruptible_sleep,
282
+ max_retries=self._effective_max_retries,
283
+ backoff_schedule=lambda: self._BACKOFF_SCHEDULE,
284
+ stream_timeout=lambda: self._stream_attempt_timeout,
285
+ committed_count=lambda: self._loop_recorder.committed_count if self._loop_recorder is not None else 0,
286
+ hosted_commits=lambda: self._hosted_commits_probe() if self._hosted_commits_probe is not None else (),
287
+ recipe=attempt_recipe,
288
+ )
289
+
290
+ self.backend = KernelConversation(
291
+ owner=conversation,
292
+ session=session,
293
+ attempts=self._attempts,
294
+ attempt_handle=self._attempt_handle,
295
+ observer=self,
296
+ run_kwargs=lambda: self._build_run_kwargs(
297
+ None if self.backend.service_session_storage_enabled else self._build_wire_retry_policy()
298
+ ),
299
+ stream=lambda: self._stream,
300
+ service_side=self._resolve_service_storage,
301
+ compaction_strategy=compaction_strategy,
302
+ recorder=loop_recorder,
303
+ hosted_observed=hosted_commits_probe,
304
+ start_hooks=(*run_cycle_start_hooks, self._begin_hosted_baseline),
305
+ failure_disposition=FailureDisposition.CALLER_DECISION,
306
+ )
307
+
308
+ self.inputs = TurnResumePolicy(self.backend, session_id, self.state)
309
+ self._invocation_publishers = invocation_publishers or InvocationPublishers(event_bus)
310
+ self._bound_emitter: BoundEmitter | None = None
311
+ self.tool_events = ToolEventMiddleware(
312
+ event_bus,
313
+ origin=self.inputs.origin,
314
+ session_id=session_id,
315
+ intermediate_buffer=intermediate_buffer,
316
+ mutation_tracker=mutation_tracker,
317
+ hook_manager=hook_manager,
318
+ profile_name=profile_name,
319
+ workspace_cwd=workspace_cwd,
320
+ serialize_implicit_windows=serialize_implicit_windows,
321
+ mutation_coordinator=mutation_coordinator,
322
+ on_start_published=self._local_call_start_published,
323
+ tool_result_ceiling_tokens=tool_result_ceiling_tokens,
324
+ )
325
+
326
+ @property
327
+ def trajectory_context(self) -> TrajectoryContext | None:
328
+ return self._attempt_trace.context
329
+
330
+ @trajectory_context.setter
331
+ def trajectory_context(self, context: TrajectoryContext | None) -> None:
332
+ self._attempt_trace.context = context
333
+ self._attempt_trace.first_run_links = (
334
+ (Link(relation=LinkRelation.CAUSED_BY, target_operation_id=context.turn_preamble_operation_id),)
335
+ if context is not None and context.turn_preamble_operation_id is not None
336
+ else ()
337
+ )
338
+
339
+ def record_pre_run_interrupt(self) -> None:
340
+ """Record a task-scoped interrupt that arrived before execution began."""
341
+ self.state.was_interrupted = True
342
+ self.state.run_failed = False
343
+ self.state.last_error = ""
344
+ # A cancelled Turn pass abandons any incomplete provider-side
345
+ # response even when Stop prevents ``run()``/``resume()`` from
346
+ # reaching their normal reset paths.
347
+ self.inputs.pending_continuation_token = None
348
+ self._interrupt.reset()
349
+
350
+ def _begin_hosted_baseline(self) -> None:
351
+ if self._response_validation is not None:
352
+ self._response_validation.begin_pass_hosted_baseline(
353
+ resumes_background_response=self.inputs.pending_continuation_token is not None
354
+ )
355
+
356
+ def _resolve_service_storage(self) -> bool:
357
+ """Return whether the first request uses provider-side history."""
358
+ try:
359
+ stores_by_default = bool(self._agent.client.STORES_BY_DEFAULT)
360
+ except AttributeError:
361
+ stores_by_default = False
362
+ try:
363
+ force_stateless = bool(self._agent.client.FORCES_STATELESS)
364
+ except AttributeError:
365
+ force_stateless = False
366
+ return resolve_storage_mode_and_handles(
367
+ self._chat_options,
368
+ stores_by_default=stores_by_default,
369
+ force_stateless=force_stateless,
370
+ ).service_side
371
+
372
+ async def _local_call_start_published(self, provider_call_id: str) -> None:
373
+ """Release hosted presentation ordered behind a local tool call."""
374
+ if self._hosted_bridge is not None:
375
+ await self._hosted_bridge.local_call_start_published(provider_call_id)
376
+
377
+ @staticmethod
378
+ def _artifact_descriptors(operation: HostedToolResultOp) -> list[dict[str, Any]]:
379
+ """Build bounded JSON-safe descriptors for hosted result artifacts."""
380
+ descriptors: list[dict[str, Any]] = []
381
+ for artifact in operation.view.artifacts:
382
+ # "path" must stay a real URI: consumers turn it into links, and a
383
+ # bare hosted filename (OpenAI hosted_file) is not addressable.
384
+ descriptor = {
385
+ "id": artifact.file_id or artifact.vector_store_id or artifact.id or "",
386
+ "name": artifact.name or "",
387
+ "path": artifact.uri or "",
388
+ "mime": artifact.media_type or "",
389
+ }
390
+ size = artifact.additional_properties.get("size")
391
+ if isinstance(size, int) and not isinstance(size, bool):
392
+ descriptor["size"] = size
393
+ descriptors.append({key: value for key, value in descriptor.items() if value != ""})
394
+ return descriptors
395
+
396
+ async def _publish_hosted_operation(self, operation: PresentationSinkOperation, *, emitter: BoundEmitter) -> None:
397
+ """Map one provider-neutral presentation operation onto EventBus events."""
398
+ if isinstance(operation, IntermediateTextOp):
399
+ if operation.provisional:
400
+ segment_id = operation.segment_ids[0] if operation.segment_ids else ""
401
+ if self._intermediate_buffer is not None:
402
+ self._intermediate_buffer.new_batch()
403
+ self._provisional_intermediate_batches[(operation.attempt_id, segment_id)] = (
404
+ self._intermediate_buffer.batch_id
405
+ )
406
+ await emitter.publish(
407
+ InvocationMessage(
408
+ origin=emitter.origin,
409
+ text=operation.text,
410
+ is_final=False,
411
+ is_intermediate=True,
412
+ presentation=ProvisionalPresentation(operation.attempt_id, segment_id),
413
+ session_id=self._session_id,
414
+ )
415
+ )
416
+ return
417
+ if self._publish_intermediate_text is not None:
418
+ await self._publish_intermediate_text(operation.text)
419
+ else:
420
+ await emitter.publish(
421
+ InvocationMessage(
422
+ origin=emitter.origin,
423
+ text=operation.text,
424
+ is_final=False,
425
+ is_intermediate=True,
426
+ session_id=self._session_id,
427
+ )
428
+ )
429
+ return
430
+ if isinstance(operation, PresentationAttemptAcceptedOp):
431
+ accepted_ids = tuple(segment_id for segment in operation.segments for segment_id in segment.segment_ids)
432
+ if self._commit_intermediate_text is not None:
433
+ for segment in operation.segments:
434
+ segment_id = segment.segment_ids[0] if segment.segment_ids else ""
435
+ batch_id = self._provisional_intermediate_batches.get((operation.attempt_id, segment_id))
436
+ if batch_id is not None:
437
+ self._commit_intermediate_text(segment.text, batch_id)
438
+ self._drop_provisional_batches(operation.attempt_id)
439
+ await emitter.publish(
440
+ InvocationPresentationAttemptAccepted(
441
+ origin=emitter.origin,
442
+ attempt_id=operation.attempt_id,
443
+ segment_ids=accepted_ids,
444
+ session_id=self._session_id,
445
+ )
446
+ )
447
+ return
448
+ if isinstance(operation, PresentationAttemptRejectedOp):
449
+ self._drop_provisional_batches(operation.attempt_id)
450
+ await emitter.publish(
451
+ InvocationPresentationAttemptRejected(
452
+ origin=emitter.origin, attempt_id=operation.attempt_id, session_id=self._session_id
453
+ )
454
+ )
455
+ return
456
+ if isinstance(operation, FinalTextOp):
457
+ await emitter.publish(
458
+ InvocationMessage(
459
+ origin=emitter.origin,
460
+ text=operation.text,
461
+ is_final=True,
462
+ structured_output_completed=operation.structured_output_completed,
463
+ **self._assistant_message_event_kwargs(),
464
+ session_id=self._session_id,
465
+ )
466
+ )
467
+ return
468
+
469
+ view = operation.view
470
+ if isinstance(operation, HostedToolStartOp):
471
+ await emitter.publish(
472
+ InvocationToolCallStart(
473
+ origin=emitter.origin,
474
+ tool_name=view.tool_name,
475
+ call_id=operation.presentation_id,
476
+ provider_hosted=True,
477
+ hosted_family=view.family,
478
+ provider=view.provider,
479
+ provider_item_type=view.provider_item_type,
480
+ provider_call_id=view.provider_call_id,
481
+ provider_status=view.provider_status,
482
+ tool_kind=HOSTED_TOOL_DEFAULT_KIND_BY_FAMILY.get(view.family, ""),
483
+ args=view.arguments,
484
+ session_id=self._session_id,
485
+ )
486
+ )
487
+
488
+ elif isinstance(operation, HostedToolArgsOp):
489
+ await emitter.publish(
490
+ InvocationToolCallArgsUpdated(
491
+ origin=emitter.origin,
492
+ tool_name=view.tool_name,
493
+ call_id=operation.presentation_id,
494
+ provider_hosted=True,
495
+ hosted_family=view.family,
496
+ provider=view.provider,
497
+ provider_item_type=view.provider_item_type,
498
+ provider_call_id=view.provider_call_id,
499
+ provider_status=view.provider_status,
500
+ tool_kind=HOSTED_TOOL_DEFAULT_KIND_BY_FAMILY.get(view.family, ""),
501
+ args=view.arguments,
502
+ session_id=self._session_id,
503
+ )
504
+ )
505
+ elif isinstance(operation, HostedToolProgressOp):
506
+ await emitter.publish(
507
+ InvocationToolCallProgress(
508
+ origin=emitter.origin,
509
+ tool_name=view.tool_name,
510
+ call_id=operation.presentation_id,
511
+ provider_hosted=True,
512
+ hosted_family=view.family,
513
+ provider=view.provider,
514
+ provider_item_type=view.provider_item_type,
515
+ provider_call_id=view.provider_call_id,
516
+ provider_status=view.provider_status,
517
+ lines=view.result_text.splitlines(),
518
+ image_contents=view.image_contents,
519
+ snapshot_metadata=view.metadata,
520
+ session_id=self._session_id,
521
+ )
522
+ )
523
+ elif isinstance(operation, HostedToolStatusOp):
524
+ metadata = dict(view.metadata)
525
+ if view.result_text:
526
+ metadata["result_text"] = view.result_text
527
+ if view.provider_item_type:
528
+ metadata["provider_item_type"] = view.provider_item_type
529
+ if view.provider_call_id:
530
+ metadata["provider_call_id"] = view.provider_call_id
531
+ await emitter.publish(
532
+ InvocationToolCallStatusUpdated(
533
+ origin=emitter.origin,
534
+ tool_name=view.tool_name,
535
+ call_id=operation.presentation_id,
536
+ provider_hosted=True,
537
+ hosted_family=view.family,
538
+ provider=view.provider,
539
+ status=view.status,
540
+ provider_status=view.provider_status,
541
+ metadata=metadata,
542
+ session_id=self._session_id,
543
+ )
544
+ )
545
+ elif isinstance(operation, HostedToolResultOp):
546
+ await emitter.publish(
547
+ InvocationToolCallResult(
548
+ origin=emitter.origin,
549
+ tool_name=view.tool_name,
550
+ call_id=operation.presentation_id,
551
+ provider_hosted=True,
552
+ hosted_family=view.family,
553
+ provider=view.provider,
554
+ provider_item_type=view.provider_item_type,
555
+ provider_call_id=view.provider_call_id,
556
+ provider_status=view.provider_status,
557
+ result=view.result_text,
558
+ image_contents=view.image_contents,
559
+ metadata=view.metadata,
560
+ artifacts=self._artifact_descriptors(operation),
561
+ session_id=self._session_id,
562
+ )
563
+ )
564
+
565
+ def _drop_provisional_batches(self, attempt_id: str) -> None:
566
+ stale = [key for key in self._provisional_intermediate_batches if key[0] == attempt_id]
567
+ for key in stale:
568
+ self._provisional_intermediate_batches.pop(key, None)
569
+
570
+ def _build_run_kwargs(self, wire_retry_policy: WireRetryPolicy | None = None) -> AgentRunKwargs:
571
+ """Build common kwargs for agent.run()."""
572
+ # ToolEventMiddleware owns hook dispatch too, so hook-modified args
573
+ # are reflected consistently in UI events, mutation tracking, and
574
+ # approval requests.
575
+ # Per-run channel consumed by the chrys ToolLoopLayer (fresh dict per
576
+ # call; the agent merges the session into the same mapping). A None
577
+ # recorder is not injected — absent key and None mean the same thing
578
+ # to the loop, and an absent key keeps the mapping honest.
579
+ client_kwargs: dict[str, object] = {}
580
+ if self._loop_recorder is not None:
581
+ client_kwargs["loop_recorder"] = self._loop_recorder
582
+ if wire_retry_policy is not None:
583
+ client_kwargs["wire_retry_policy"] = wire_retry_policy
584
+ client_kwargs["consumed_injection_message_probe"] = ConsumedInjectionMessageProbe(
585
+ drain_consumed_injection_messages=self._injection.drain_consumed_injection_messages,
586
+ commit_consumed_injections=self._injection.commit_logical_call,
587
+ )
588
+ # Chain order is pinned (first = outermost): extras sit INSIDE
589
+ # tool_events (a short-circuiting extra must still publish
590
+ # InvocationToolCallStart/InvocationToolCallResult) and INSIDE approval (no extra can
591
+ # bypass approval on gated kinds).
592
+ kwargs: AgentRunKwargs = {
593
+ "session": self._session,
594
+ "middleware": [
595
+ self.tool_events,
596
+ self._ask_user,
597
+ self.approval,
598
+ *self._extra_function_middleware,
599
+ self._sleep,
600
+ self._interrupt,
601
+ ],
602
+ "client_kwargs": client_kwargs,
603
+ }
604
+ if self._compaction_strategy is not None:
605
+ kwargs["compaction_strategy"] = self._compaction_strategy
606
+ # The raw client needs the same tokenizer to estimate live tool
607
+ # definitions before the first calibrated provider response. The
608
+ # strategy remains the single owner of the tokenizer instance.
609
+ kwargs["tokenizer"] = self._compaction_strategy.tokenizer
610
+ if self._chat_options:
611
+ options_copy = dict(self._chat_options)
612
+ if is_string_keyed_dict(extra_body := options_copy.get("extra_body")):
613
+ options_copy["extra_body"] = dict(extra_body)
614
+ kwargs["options"] = options_copy
615
+ if self.inputs.pending_continuation_token is not None:
616
+ # A previous run of this turn left a background response in
617
+ # flight (poll failed, gate blocked the auto-retry). Resume it
618
+ # instead of creating a duplicate; the kernel clears the token
619
+ # on any terminal judgment.
620
+ seeded = kwargs.get("options")
621
+ if not is_string_keyed_dict(seeded):
622
+ seeded = {}
623
+ kwargs["options"] = seeded
624
+ seeded["continuation_token"] = self.inputs.pending_continuation_token
625
+ mirror_to_run_kwargs = continuation_token_observer_for(kwargs)
626
+
627
+ def _observe_continuation_token(token: Any) -> None:
628
+ self.inputs.pending_continuation_token = token
629
+ mirror_to_run_kwargs(token)
630
+
631
+ client_kwargs["continuation_token_observer"] = _observe_continuation_token
632
+ return kwargs
633
+
634
+ def _build_wire_retry_policy(self) -> WireRetryPolicy:
635
+ max_retries = self._effective_max_retries()
636
+ return WireRecipe(
637
+ max_retries=max_retries,
638
+ stall_timeout_seconds=self._stream_attempt_timeout,
639
+ stall_max_retries=max_retries,
640
+ stall_exhausted_action=StallExhaustedAction.BLOCKING_FALLBACK,
641
+ backoff_schedule=self._BACKOFF_SCHEDULE,
642
+ interrupted=lambda: self._interrupt.is_interrupted,
643
+ interruptible_sleep=self._interruptible_sleep,
644
+ publish_retry=self._publish_wire_retry_attempt,
645
+ prepare_retry=self._injection.restore_for_retry,
646
+ hosted_commits_in_flight=self._hosted_commits_in_flight_probe,
647
+ ).build()
648
+
649
+ def _effective_max_retries(self) -> int:
650
+ """Return the injected budget or the lazily resolved class fallback."""
651
+ if self._max_retries_override is not None:
652
+ return self._max_retries_override
653
+ return self._MAX_RETRIES
654
+
655
+ def record_outcome(self, outcome: InvocationOutcome) -> None:
656
+ self.inputs.evidence = self.inputs.evidence.add(outcome.effects)
657
+ self.inputs.outcome = outcome
658
+
659
+ @property
660
+ def _emitter(self) -> BoundEmitter:
661
+ if self._bound_emitter is None:
662
+ raise ValueError("Cannot publish outside an active turn pass")
663
+ return self._bound_emitter
664
+
665
+ async def begin(self, request: RunRequest, resources: PassResources) -> None:
666
+ """Start presentation and validation inside the backend pass try."""
667
+ self._bound_emitter = self._invocation_publishers.bind(request.origin)
668
+ self.approval.bind_publisher(self._emitter)
669
+ self.tool_events.bind_origin(request.origin)
670
+ if request.intent is RunIntent.FRESH:
671
+ self.inputs.pending_continuation_token = None
672
+ self.state.running = True
673
+ self.state.was_interrupted = False
674
+ self.state.run_failed = False
675
+ self.state.last_error = ""
676
+ self._interrupt.reset()
677
+ self._attempt_trace.reset()
678
+ self._hosted_run_generation = next_hosted_run_generation()
679
+ emitter = self._emitter
680
+ self._hosted_bridge = HostedPresentationBridge(
681
+ lambda operation: self._publish_hosted_operation(operation, emitter=emitter),
682
+ run_generation=self._hosted_run_generation,
683
+ batch_id=self._intermediate_buffer.batch_id if self._intermediate_buffer is not None else 0,
684
+ before_response=self.tool_events.release_intermediate_text,
685
+ )
686
+ if self._response_validation is not None:
687
+ self._response_validation.set_observation_hook(self._hosted_bridge)
688
+ else:
689
+ await self._hosted_bridge.begin_response(response_index=0)
690
+ resources.begin()
691
+ await self._emitter.publish(AgentThinking(session_id=self._session_id))
692
+ await self._flush_carried_compressions()
693
+
694
+ async def succeeded(self, result: AgentResponse[Any]) -> None:
695
+ if not self._interrupt.is_interrupted:
696
+ await self._publish_response_text(result)
697
+
698
+ def cancelled(self) -> None:
699
+ self.state.was_interrupted = True
700
+
701
+ def abort_cause(self) -> AbortCause | None:
702
+ if self._interrupt.is_interrupted or self.state.was_interrupted:
703
+ return AbortCause.USER_CANCEL
704
+ return None
705
+
706
+ async def failed(self, e: Exception) -> None:
707
+ if self._interrupt.is_interrupted:
708
+ self.state.was_interrupted = True
709
+ return
710
+ self.state.run_failed = True
711
+ self.state.last_error = clean_error_message(e)
712
+ tb = traceback.format_exc()
713
+ # Text left buffered by a call that started no tool precedes the error.
714
+ await self.tool_events.release_intermediate_text()
715
+ err_msg = self.state.last_error
716
+ logger.error("Turn error: %s", err_msg)
717
+ if self._hosted_bridge is not None:
718
+ await self._hosted_bridge.attempt_rejected(err_msg)
719
+ strategy = self._compaction_strategy
720
+ display_message, display_hint = display_fields(
721
+ e, max_context_tokens=strategy.max_context_tokens if strategy is not None else None
722
+ )
723
+ await self._emitter.publish(
724
+ Error(
725
+ code="executor_error",
726
+ message=err_msg,
727
+ recoverable=True,
728
+ session_id=self._session_id,
729
+ display_message=display_message,
730
+ display_hint=display_hint,
731
+ )
732
+ )
733
+ logger.debug("Turn traceback:\n%s", tb)
734
+
735
+ async def finished(self) -> None:
736
+ try:
737
+ # An interrupted or cancelled pass publishes no outcome, and the
738
+ # buffer outlives the pass: text still waiting for a tool start
739
+ # would otherwise surface at a tool start in a later turn.
740
+ await self.tool_events.finish_intermediate_text()
741
+ if self._interrupt.is_interrupted and self._hosted_bridge is not None:
742
+ await self._hosted_bridge.attempt_rejected(
743
+ "Execution interrupted",
744
+ status=HostedToolStatus.INTERRUPTED,
745
+ preserve_provisional=True,
746
+ )
747
+ finally:
748
+ # Close can cancel the awaits above. The reset must not erase an
749
+ # interrupt that arrived while they ran.
750
+ if self._interrupt.is_interrupted:
751
+ self.state.was_interrupted = True
752
+ if self._response_validation is not None:
753
+ self._response_validation.set_observation_hook(None)
754
+ self._interrupt.reset()
755
+ self.state.running = False
756
+ if self._bound_emitter is not None:
757
+ self._invocation_publishers.unbind(self._bound_emitter.origin)
758
+ self._bound_emitter = None
759
+ self.approval.bind_publisher(None)
760
+
761
+ async def _flush_carried_compressions(self) -> None:
762
+ """Commit a prior failed pass's queued folds before retry snapshots.
763
+
764
+ A ``compress_context`` call can validate and queue a fold, then the
765
+ pass can terminate before the history provider reaches its normal
766
+ ``after_run`` flush. The next user retry must treat that request as
767
+ pre-existing committed work. If it first runs inside
768
+ ``StreamRetryLoop`` instead, a transient failure restores the
769
+ pre-attempt history (removing the new ``CompressedBlock``) after the
770
+ queue has already been consumed. The live ``ContextCompressed``
771
+ event then has no durable counterpart and disappears on replay.
772
+
773
+ Bind the current provider state explicitly and flush before
774
+ the L0 runner captures its attempt
775
+ snapshot. The history provider binds the same state again inside
776
+ ``agent.run()``; that later bind is intentionally idempotent.
777
+ """
778
+ strategy = self._compaction_strategy
779
+ if strategy is None:
780
+ return
781
+ history_state = self.backend.history_state
782
+ strategy.bind_state(history_state)
783
+ if not await strategy.flush_pending_compressions():
784
+ return
785
+ messages = history_state.get("messages", [])
786
+ if isinstance(messages, list):
787
+ # TurnRunner.pre_run captured its metadata floor before this
788
+ # carried fold shortened history. Finalization must start at the
789
+ # post-fold boundary so retry output still receives approval,
790
+ # modified-argument, and timestamp annotations.
791
+ history_state[PRE_OUTPUT_HISTORY_LEN_STATE_KEY] = len(messages)
792
+
793
+ # ── main presentation ────────────────────────────────────────────
794
+
795
+ async def _publish_wire_retry_attempt(
796
+ self,
797
+ message: str,
798
+ attempt: int,
799
+ max_attempts: int,
800
+ delay_seconds: int,
801
+ exc: BaseException,
802
+ ) -> None:
803
+ """The wire retry policy's notifier: UI only.
804
+
805
+ A wire retry is recorded by the loop that performs it, as a new
806
+ exchange under the same run (``retry.scheduled{retry_mode: wire}``).
807
+ Recording a run-level retry here as well would count one retry twice
808
+ and leave a scheduled marker that no ``retry.started`` ever answers.
809
+ """
810
+ await self._publish_retry_notice(message, attempt, max_attempts, delay_seconds, exc, scope="wire")
811
+
812
+ async def _publish_retry_notice(
813
+ self,
814
+ message: str,
815
+ attempt: int,
816
+ max_attempts: int,
817
+ delay_seconds: int,
818
+ exc: BaseException,
819
+ *,
820
+ scope: Literal["wire", "run"] = "run",
821
+ ) -> None:
822
+ if self._hosted_bridge is not None and self.inputs.pending_continuation_token is None:
823
+ await self._hosted_bridge.attempt_rejected(message)
824
+ display_message, display_hint = display_fields(exc, retry_notice=True)
825
+ await self._emitter.publish(
826
+ InvocationRetryAttempt(
827
+ origin=self._emitter.origin,
828
+ message=message,
829
+ attempt=attempt,
830
+ scope=scope,
831
+ max_attempts=max_attempts,
832
+ delay_seconds=delay_seconds,
833
+ session_id=self._session_id,
834
+ display_message=display_message,
835
+ display_hint=display_hint,
836
+ )
837
+ )
838
+
839
+ async def _publish_response_text(self, result: AgentResponse[Any]) -> None:
840
+ """Publish the final text from a completed AgentResponse.
841
+
842
+ Only publishes the *last* message — intermediate text is handled
843
+ by ``IntermediateTextBuffer`` + ``ToolEventMiddleware``.
844
+
845
+ The final event is also the frontend's turn-complete signal. It
846
+ must be emitted even when the final text is empty, for example
847
+ after response validation exhausts retries and drops an empty /
848
+ whitespace-only assistant message.
849
+ """
850
+ if self._response_has_hosted_contents(result) and self._hosted_bridge is not None:
851
+ await self._hosted_bridge.reconcile_accepted(result.messages, final=True)
852
+ return
853
+ final_text = self._extract_final_text(result)
854
+ event_kwargs = self._assistant_message_event_kwargs()
855
+ await self._emitter.publish(
856
+ InvocationMessage(
857
+ origin=self._emitter.origin,
858
+ text=final_text,
859
+ is_final=True,
860
+ **event_kwargs,
861
+ session_id=self._session_id,
862
+ )
863
+ )
864
+
865
+ # ── control ──────────────────────────────────────────────────────
866
+
867
+ async def interrupt(self) -> None:
868
+ """Signal the Turn to stop and cancel any running tool.
869
+
870
+ Sets the interrupt flag for the middleware boundary check AND
871
+ cancels the current ``agent.run()`` child task so long-running
872
+ tools (sub-agents, scripts) are actually terminated instead of
873
+ completing in the background.
874
+ """
875
+ generation = self._hosted_run_generation
876
+ self._interrupt.set_interrupted()
877
+ if self._hosted_bridge is not None:
878
+ await self._hosted_bridge.attempt_rejected(
879
+ "Execution interrupted",
880
+ status=HostedToolStatus.INTERRUPTED,
881
+ preserve_provisional=True,
882
+ )
883
+ if self._hosted_run_generation != generation:
884
+ return
885
+ if self._attempt_handle.active:
886
+ sleep_call_ids = self._sleep.active_call_ids
887
+ if sleep_call_ids:
888
+ await self._interrupt_active_sleep(set(sleep_call_ids))
889
+ # A wire retry may replace the attempt inside this pass; still
890
+ # cancel it. A successor pass must never inherit this interrupt.
891
+ if self._hosted_run_generation == generation:
892
+ self._attempt_handle.cancel()
893
+
894
+ async def _interrupt_active_sleep(self, call_ids: set[str]) -> None:
895
+ """Let an active sleep publish its interrupted InvocationToolCallResult before cancellation."""
896
+ if not call_ids:
897
+ return
898
+ pending = set(call_ids)
899
+ loop = asyncio.get_running_loop()
900
+ completed: asyncio.Future[None] = loop.create_future()
901
+
902
+ origin = self._emitter.origin
903
+
904
+ async def _on_tool_result(event: InvocationToolCallResult) -> None:
905
+ if event.origin != origin:
906
+ return
907
+ pending.discard(event.call_id)
908
+ if not pending and not completed.done():
909
+ completed.set_result(None)
910
+
911
+ await self._bus.subscribe(InvocationToolCallResult, _on_tool_result)
912
+ try:
913
+ interrupted = set(self._sleep.interrupt_active())
914
+ pending.intersection_update(interrupted)
915
+ if not pending:
916
+ return
917
+ # Keep Esc responsive: give the interrupted sleep a small
918
+ # writeback window, then let task cancellation win.
919
+ await asyncio.wait_for(completed, timeout=0.5)
920
+ except TimeoutError:
921
+ return
922
+ finally:
923
+ await self._bus.unsubscribe(InvocationToolCallResult, _on_tool_result)
924
+
925
+ async def _interruptible_sleep(self, seconds: int) -> bool:
926
+ """Sleep in 1-second ticks, returning True if interrupted."""
927
+ for _ in range(seconds):
928
+ if self._interrupt.is_interrupted:
929
+ return True
930
+ await asyncio.sleep(1)
931
+ return self._interrupt.is_interrupted
932
+
933
+ def inject(
934
+ self,
935
+ text: str,
936
+ created_at: datetime | str | None = None,
937
+ injection_id: str | None = None,
938
+ reminders: tuple[str, ...] = (),
939
+ preparation: PreparationTrace | None = None,
940
+ target_turn_id: str | None = None,
941
+ ) -> None:
942
+ """Queue text for injection before the next model call."""
943
+ self._injection.queue(
944
+ text,
945
+ created_at=created_at,
946
+ injection_id=injection_id,
947
+ reminders=reminders,
948
+ preparation=preparation,
949
+ target_turn_id=target_turn_id,
950
+ )
951
+
952
+ def cancel_injection(self, injection_id: str) -> QueuedInjection | None:
953
+ """Remove a still-pending injection; returns it, or None when too late."""
954
+ return self._injection.cancel(injection_id)
955
+
956
+ def reset_counters(self, *, reset_batch_id: bool = True) -> None:
957
+ """Reset per-run counters (called by engine before each run).
958
+
959
+ Args:
960
+ reset_batch_id: If True, reset batch_id to 0 (for fresh runs).
961
+ On resume, pass False to continue numbering from the
962
+ interrupted run so batch_ids don't collide.
963
+ """
964
+ self.approval.reset()
965
+ self.tool_events.reset_invocation_order()
966
+ if reset_batch_id and self._intermediate_buffer is not None:
967
+ self._intermediate_buffer.batch_id = 0
968
+
969
+ def _get_history_messages(self) -> list:
970
+ """Return the message list from the history provider state."""
971
+ history_state = self._session.state.get("chrys_history", {})
972
+ return history_state.get("messages", [])
973
+
974
+ # ── helpers ───────────────────────────────────────────────────────
975
+
976
+ @staticmethod
977
+ def _response_has_hosted_contents(result: AgentResponse[Any]) -> bool:
978
+ """Return whether the accepted response contains provider-hosted content."""
979
+ return any(content.provider_hosted for message in result.messages for content in message.contents)
980
+
981
+ @staticmethod
982
+ def _extract_final_text(result: AgentResponse[Any]) -> str:
983
+ """Extract only the *last* message's text from an AgentResponse.
984
+
985
+ ``AgentResponse.text`` concatenates ALL messages from the tool loop.
986
+ This method returns only the final LLM output after all tool calls.
987
+ """
988
+ if not result.messages:
989
+ return ""
990
+ last_msg = result.messages[-1]
991
+ return "".join(c.text for c in last_msg.contents if c.type == "text" and c.text)
992
+
993
+ def _assistant_message_event_kwargs(self) -> _AssistantMessageEventKwargs:
994
+ """Return event kwargs matching the timestamp persisted for assistant output."""
995
+ parsed = parse_created_at(self._session.state.get(LAST_ASSISTANT_CREATED_AT_STATE_KEY))
996
+ return {"timestamp": parsed} if parsed is not None else {}
997
+
998
+ def _snapshot_retry_state(self) -> _TurnRetryState:
999
+ """Capture the four caller-owned retry buffers synchronously."""
1000
+ return _TurnRetryState(
1001
+ loop_recorder=self._loop_recorder.snapshot() if self._loop_recorder is not None else None,
1002
+ tool_events=self.tool_events.snapshot_retry_state(),
1003
+ approval=self.approval.snapshot_retry_state(),
1004
+ compaction=self._compaction_strategy.snapshot_retry_state()
1005
+ if self._compaction_strategy is not None
1006
+ else None,
1007
+ )
1008
+
1009
+ def _restore_retry_state(self, retry_state: object) -> None:
1010
+ """Restore caller buffers inline; a failure stops the retry transaction."""
1011
+ if not isinstance(retry_state, _TurnRetryState):
1012
+ raise TypeError("Turn retry snapshot has invalid caller state.")
1013
+ if self._loop_recorder is not None and retry_state.loop_recorder is not None:
1014
+ self._loop_recorder.restore(retry_state.loop_recorder)
1015
+ self.tool_events.restore_retry_state(retry_state.tool_events)
1016
+ self.approval.restore_retry_state(retry_state.approval)
1017
+ if self._compaction_strategy is not None and retry_state.compaction is not None:
1018
+ self._compaction_strategy.restore_retry_state(retry_state.compaction)
1019
+
1020
+ async def _reject_stream(self, reason: str) -> None:
1021
+ if self._hosted_bridge is not None:
1022
+ await self._hosted_bridge.attempt_rejected(reason)
1023
+
1024
+ def _make_stream_observer(self) -> _MainStreamObserver:
1025
+ return _MainStreamObserver(self)
1026
+
1027
+
1028
+ class _MainStreamObserver:
1029
+ """The main shell's final-response text projection for one stream attempt.
1030
+
1031
+ ``on_update`` accumulates text, discarding intermediate tool-response
1032
+ text that the wire client's ``result_hook`` publishes separately.
1033
+ Two signals discard the buffer: a non-informational ``function_call``
1034
+ in the update, or a change in ``IntermediateTextBuffer.batch_id`` between
1035
+ updates. The hook advances the batch at the end of a tool-calling response;
1036
+ text/tool order within the same update therefore does not matter.
1037
+
1038
+ ``on_retry_boundary`` discards text from the rejected provider attempt.
1039
+ ``before_finalize`` first publishes intermediate text no tool start or
1040
+ response start has released, then checks the batch once more after
1041
+ iteration as a safety net for a last tool response with no following
1042
+ update. It emits the final
1043
+ response's remaining text as one cumulative ``InvocationMessage(is_final=False)``
1044
+ snapshot before ``_publish_response_text`` emits ``is_final=True``. The text
1045
+ was buffered whole, so replaying it line by line only fakes streaming: every
1046
+ line would publish the whole text so far through the bus and every subscriber.
1047
+ When the hosted bridge owns the text, this observer discards its buffer and
1048
+ emits no text; interruption also suppresses emission.
1049
+ """
1050
+
1051
+ def __init__(self, executor: TurnBindings) -> None:
1052
+ self._executor = executor
1053
+ self._emitter = executor._emitter
1054
+ self._buffer = ""
1055
+ self._bridge_owns_text = False
1056
+ self._ibuf = executor._intermediate_buffer
1057
+ self._last_batch_id = self._ibuf.batch_id if self._ibuf is not None else 0
1058
+
1059
+ async def on_retry_boundary(self) -> None:
1060
+ self._buffer = ""
1061
+ executor = self._executor
1062
+ if executor._hosted_bridge is not None and executor.inputs.pending_continuation_token is None:
1063
+ await executor._hosted_bridge.attempt_rejected("Provider response attempt retried")
1064
+
1065
+ def on_update(self, update: AgentResponseUpdate) -> None:
1066
+ if self._ibuf is not None and self._ibuf.batch_id != self._last_batch_id:
1067
+ self._buffer = ""
1068
+ self._last_batch_id = self._ibuf.batch_id
1069
+ text_chunk = ""
1070
+ has_function_call = False
1071
+ for content in update.contents or []:
1072
+ if content.type == "text" and content.text:
1073
+ text_chunk += content.text
1074
+ elif content.type == "function_call" and not content.informational_only:
1075
+ has_function_call = True
1076
+ if content.provider_hosted:
1077
+ self._bridge_owns_text = True
1078
+ self._buffer += text_chunk
1079
+ if has_function_call or self._bridge_owns_text:
1080
+ self._buffer = ""
1081
+
1082
+ async def before_finalize(self) -> None:
1083
+ if self._ibuf is not None and self._ibuf.batch_id != self._last_batch_id:
1084
+ self._buffer = ""
1085
+ executor = self._executor
1086
+ # Text streamed with a call that started no tool precedes the final
1087
+ # text. The next response's start releases it, and a run that ends
1088
+ # without one releases it here.
1089
+ await executor.tool_events.release_intermediate_text()
1090
+ if self._buffer and not self._bridge_owns_text and not executor._interrupt.is_interrupted:
1091
+ await self._emitter.publish(
1092
+ InvocationMessage(
1093
+ origin=self._emitter.origin, text=self._buffer, is_final=False, session_id=executor._session_id
1094
+ )
1095
+ )