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,922 @@
1
+ # Copyright (c) the agent-client-protocol authors (Chojan Shang, Frost Ming and contributors)
2
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
3
+ # Contains code adapted from the Agent Client Protocol Python SDK (Apache License 2.0; see NOTICE).
4
+
5
+ """ACP transport framing, backpressure, observation, and tracking.
6
+
7
+ ``_TrackingStateStore`` and ``_BackpressureDispatcher`` are derived from
8
+ ``InMemoryMessageStateStore`` (``acp/task/state.py``) and
9
+ ``DefaultMessageDispatcher`` (``acp/task/dispatcher.py``) in the Agent Client
10
+ Protocol Python SDK (https://github.com/agentclientprotocol/python-sdk),
11
+ licensed under the Apache License, Version 2.0, and have been modified.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import contextlib
18
+ import contextvars
19
+ import logging
20
+ from collections import deque
21
+ from collections.abc import Callable
22
+ from dataclasses import dataclass
23
+ from typing import Any
24
+
25
+ from acp.connection import StreamDirection, StreamEvent
26
+ from acp.task import (
27
+ MessageQueue,
28
+ MessageStateStore,
29
+ NotificationRunner,
30
+ RequestRunner,
31
+ RpcTaskKind,
32
+ TaskSupervisor,
33
+ )
34
+ from acp.task.state import IncomingMessage
35
+
36
+ from . import protocol
37
+ from .errors import AcpConfigError, AcpTransportError, classify_protocol_frame_error
38
+ from .protocol import (
39
+ _validate_payload_caps,
40
+ _validate_update_payload_caps,
41
+ encode_protocol_json,
42
+ parse_protocol_json,
43
+ validate_json_rpc_envelope,
44
+ )
45
+ from .spec import AcpPromptUsage
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+ _MAX_ATTEMPT_FRAMES = 100_000
50
+ _MAX_ATTEMPT_BYTES = 256 * 1024 * 1024
51
+ _MAX_REQUEST_FRAMES = 8_192
52
+ _MAX_UPDATE_ITEMS = 4_096
53
+ _MAX_UPDATE_BYTES = 64 * 1024 * 1024
54
+ _MAX_HUMAN_WAITS = 8
55
+ _MAX_USAGE_VALUE = (1 << 63) - 1
56
+ _REQUEST_CONCURRENCY = 32
57
+ _CLOSE_STAGE_TIMEOUT_SECONDS = 2.0
58
+ _REQUEST_DRAIN_TIMEOUT_SECONDS = 1.0
59
+
60
+ _PROMPT_METHOD = "session/prompt"
61
+ _UPDATE_METHOD = "session/update"
62
+ _HUMAN_METHODS = frozenset({protocol._PERMISSION_METHOD, protocol._ASK_USER_METHOD})
63
+ _CURRENT_INBOUND_ID: contextvars.ContextVar[str | int | None] = contextvars.ContextVar(
64
+ "chrys_acp_inbound_request_id",
65
+ default=None,
66
+ )
67
+
68
+
69
+ @dataclass(slots=True)
70
+ class _OutgoingRecord:
71
+ request_id: int
72
+ method: str
73
+ future: asyncio.Future[Any]
74
+ write_committed: bool = False
75
+ response_observed: bool = False
76
+ caps_violated: bool = False
77
+ raw_response: dict[str, Any] | None = None
78
+ usage: AcpPromptUsage | None = None
79
+
80
+
81
+ class _TrackingStateStore(MessageStateStore):
82
+ """SDK state store with outgoing metadata and no inbound retention."""
83
+
84
+ def __init__(self) -> None:
85
+ self._outgoing: dict[int, _OutgoingRecord] = {}
86
+ self._completed: dict[int, _OutgoingRecord] = {}
87
+ self._prompt_id: int | None = None
88
+ self._prompt_registered = asyncio.Event()
89
+
90
+ @property
91
+ def prompt_id(self) -> int | None:
92
+ return self._prompt_id
93
+
94
+ async def wait_for_prompt_id(self) -> int:
95
+ await self._prompt_registered.wait()
96
+ if self._prompt_id is None:
97
+ raise RuntimeError("Prompt registration event had no request id.")
98
+ return self._prompt_id
99
+
100
+ def reset_prompt(self) -> None:
101
+ self._prompt_id = None
102
+ self._prompt_registered.clear()
103
+
104
+ def register_outgoing(self, request_id: int, method: str) -> asyncio.Future[Any]:
105
+ future: asyncio.Future[Any] = asyncio.get_running_loop().create_future()
106
+ future.add_done_callback(_consume_future_exception)
107
+ record = _OutgoingRecord(request_id=request_id, method=method, future=future)
108
+ self._outgoing[request_id] = record
109
+ if method == _PROMPT_METHOD:
110
+ self._prompt_id = request_id
111
+ self._prompt_registered.set()
112
+ return future
113
+
114
+ def mark_write_committed(self, request_id: int) -> None:
115
+ record = self._outgoing.get(request_id)
116
+ if record is not None:
117
+ record.write_committed = True
118
+
119
+ def observe_response(self, request_id: str | int, message: dict[str, Any]) -> _OutgoingRecord | None:
120
+ if type(request_id) is not int:
121
+ return None
122
+ record = self._outgoing.get(request_id)
123
+ if record is None:
124
+ return None
125
+ if not record.write_committed:
126
+ raise ValueError("ACP response arrived before the request write was committed.")
127
+ if record.response_observed or record.caps_violated:
128
+ # First-response-wins: the pump observes every frame, and two
129
+ # coalesced responses for one id both arrive before SDK routing
130
+ # pops the record. The SDK resolves only the first, so a later
131
+ # duplicate must not overwrite the retained response or usage,
132
+ # re-run the caps, or resurrect a caps-violated record.
133
+ return None
134
+ # Retained record fields ride the same §7.4 caps as permission and
135
+ # ask-user payloads: without this, a handful of oversized responses
136
+ # could park most of the attempt byte budget in ``_completed`` forever.
137
+ # The flag is load-bearing beyond the raise: the SDK receive loop still
138
+ # routes this same frame into resolve/reject afterwards (the observer
139
+ # cannot veto it), and those paths must not store the oversized payload
140
+ # in the completed record's future.
141
+ try:
142
+ _validate_payload_caps(message)
143
+ except ValueError as exc:
144
+ record.caps_violated = True
145
+ # §6.2: validly reported usage must still account even when the
146
+ # response itself is rejected — extract it before raising so every
147
+ # resulting AcpTransportError carries it.
148
+ if record.method == _PROMPT_METHOD:
149
+ record.usage = _extract_raw_usage(message)
150
+ raise _AcpProtocolLimitError(
151
+ "ACP response frame exceeds the retained-payload caps.", cause=exc, usage=record.usage
152
+ ) from exc
153
+ record.response_observed = True
154
+ record.raw_response = message
155
+ if record.method == _PROMPT_METHOD:
156
+ record.usage = _extract_raw_usage(message)
157
+ return record
158
+
159
+ def record_for(self, request_id: int) -> _OutgoingRecord | None:
160
+ return self._outgoing.get(request_id) or self._completed.get(request_id)
161
+
162
+ def outstanding_record(self, request_id: str | int) -> _OutgoingRecord | None:
163
+ """The in-flight record for a response id, or None once routed.
164
+
165
+ The observer keys seal/barrier off this instead of ``record_for`` so
166
+ a duplicate response frame arriving after SDK routing stays inert.
167
+ """
168
+ if type(request_id) is not int:
169
+ return None
170
+ return self._outgoing.get(request_id)
171
+
172
+ def latest_raw_result(self, method: str) -> dict[str, Any] | None:
173
+ records = [*self._outgoing.values(), *self._completed.values()]
174
+ for record in reversed(records):
175
+ if record.method != method or record.raw_response is None:
176
+ continue
177
+ result = record.raw_response.get("result")
178
+ return result if type(result) is dict else None
179
+ return None
180
+
181
+ def resolve_outgoing(self, request_id: int, result: Any) -> None:
182
+ record = self._outgoing.pop(request_id, None)
183
+ if record is not None:
184
+ self._completed[request_id] = record
185
+ if not record.future.done():
186
+ if record.caps_violated:
187
+ record.future.set_exception(
188
+ AcpTransportError("ACP response frame exceeds the retained-payload caps.", usage=record.usage)
189
+ )
190
+ else:
191
+ record.future.set_result(result)
192
+
193
+ def reject_outgoing(self, request_id: int, error: Any) -> None:
194
+ record = self._outgoing.pop(request_id, None)
195
+ if record is not None:
196
+ self._completed[request_id] = record
197
+ if not record.future.done():
198
+ if record.caps_violated:
199
+ record.future.set_exception(
200
+ AcpTransportError("ACP response frame exceeds the retained-payload caps.", usage=record.usage)
201
+ )
202
+ else:
203
+ record.future.set_exception(error)
204
+
205
+ def reject_all_outgoing(self, error: Any) -> None:
206
+ for request_id, record in tuple(self._outgoing.items()):
207
+ self._completed[request_id] = record
208
+ if not record.future.done():
209
+ record.future.set_exception(error)
210
+ self._outgoing.clear()
211
+
212
+ def begin_incoming(self, method: str, params: Any) -> IncomingMessage:
213
+ return IncomingMessage(method=method, params=None)
214
+
215
+ def complete_incoming(self, record: IncomingMessage, result: Any) -> None:
216
+ return
217
+
218
+ def fail_incoming(self, record: IncomingMessage, error: Any) -> None:
219
+ return
220
+
221
+
222
+ def _consume_future_exception(future: asyncio.Future[Any]) -> None:
223
+ """Mark background rejection exceptions retrieved without changing await semantics."""
224
+ if not future.cancelled():
225
+ future.exception()
226
+
227
+
228
+ @dataclass(slots=True)
229
+ class _PendingSend:
230
+ payload: dict[str, Any]
231
+ encoded: bytes
232
+ future: asyncio.Future[None]
233
+
234
+
235
+ class _RetainedSender:
236
+ """SDK sender replacement with strict encoding and total pending-send failure."""
237
+
238
+ def __init__(
239
+ self,
240
+ writer: asyncio.StreamWriter,
241
+ supervisor: TaskSupervisor,
242
+ *,
243
+ store: _TrackingStateStore,
244
+ before_response_send: Callable[[str | int], None],
245
+ on_failure: Callable[[BaseException], None],
246
+ ) -> None:
247
+ self._writer = writer
248
+ self._store = store
249
+ self._before_response_send = before_response_send
250
+ self._on_failure = on_failure
251
+ self._queue: asyncio.Queue[_PendingSend | None] = asyncio.Queue()
252
+ self._closing = False
253
+ self._active: _PendingSend | None = None
254
+ self._task = supervisor.create(self._run(), name="chrys.acp.sender", on_error=self._task_failed)
255
+
256
+ async def send(self, payload: dict[str, Any]) -> None:
257
+ is_response = "method" not in payload and ("result" in payload or "error" in payload)
258
+ if self._task.done():
259
+ raise ConnectionError("ACP sender has stopped.")
260
+ if self._closing and not is_response:
261
+ raise ConnectionError("ACP sender is closing.")
262
+ try:
263
+ encoded = encode_protocol_json(payload) + b"\n"
264
+ except ValueError as exc:
265
+ failure = AcpConfigError("A local ACP payload is not valid JSON.", cause=exc)
266
+ request_id = payload.get("id")
267
+ if "method" in payload and type(request_id) is int:
268
+ self._store.reject_outgoing(request_id, failure)
269
+ self._on_failure(failure)
270
+ raise failure from exc
271
+ future: asyncio.Future[None] = asyncio.get_running_loop().create_future()
272
+ await self._queue.put(_PendingSend(payload=payload, encoded=encoded, future=future))
273
+ await future
274
+
275
+ def begin_close(self) -> None:
276
+ self._closing = True
277
+
278
+ async def close(self) -> None:
279
+ if not self._closing:
280
+ self._closing = True
281
+ if self._task.done():
282
+ self._reject_pending(ConnectionError("ACP sender stopped."))
283
+ with contextlib.suppress(asyncio.CancelledError, Exception):
284
+ await self._task
285
+ return
286
+ await self._queue.put(None)
287
+ with contextlib.suppress(asyncio.CancelledError):
288
+ await self._task
289
+
290
+ async def emergency_stop(self) -> None:
291
+ self._closing = True
292
+ self._task.cancel()
293
+ # This runs only after the normal close already wedged, so the sender
294
+ # cannot be trusted to honor that single cancellation; a direct await
295
+ # would hand the close ladder to a task that may never finish, and
296
+ # queued sends must be rejected even when it never does.
297
+ await _reap_task(self._task)
298
+ self._reject_pending(ConnectionError("ACP sender stopped."))
299
+ # The item _run already popped is unreachable from the queue; if the
300
+ # task outlived both reap cancellations, its caller must still be
301
+ # released here (both sides guard with future.done()).
302
+ active = self._active
303
+ if active is not None and not active.future.done():
304
+ active.future.set_exception(ConnectionError("ACP sender stopped."))
305
+
306
+ async def _run(self) -> None:
307
+ failure: BaseException | None = None
308
+ try:
309
+ while True:
310
+ item = await self._queue.get()
311
+ if item is None:
312
+ return
313
+ if item.future.cancelled() and "method" in item.payload:
314
+ # Normal close appends its sentinel behind queued items,
315
+ # so a request whose caller already gave up (caller
316
+ # cancellation or a terminal wake) would still flush to
317
+ # the agent and execute remotely. Responses stay owed to
318
+ # the peer regardless of local cancellation, so only
319
+ # method-bearing frames are dropped.
320
+ continue
321
+ self._active = item
322
+ try:
323
+ payload = item.payload
324
+ if "method" in payload and "id" in payload and type(payload["id"]) is int:
325
+ self._store.mark_write_committed(payload["id"])
326
+ elif "method" not in payload and "id" in payload:
327
+ self._before_response_send(payload["id"])
328
+ self._writer.write(item.encoded)
329
+ await self._writer.drain()
330
+ except BaseException as exc:
331
+ failure = exc
332
+ if not item.future.done():
333
+ item.future.set_exception(exc)
334
+ raise
335
+ else:
336
+ if not item.future.done():
337
+ item.future.set_result(None)
338
+ self._active = None
339
+ except asyncio.CancelledError:
340
+ raise
341
+ finally:
342
+ self._reject_pending(failure or ConnectionError("ACP sender stopped."))
343
+
344
+ def _reject_pending(self, error: BaseException) -> None:
345
+ while True:
346
+ try:
347
+ item = self._queue.get_nowait()
348
+ except asyncio.QueueEmpty:
349
+ return
350
+ if item is not None and not item.future.done():
351
+ item.future.set_exception(error)
352
+
353
+ def _task_failed(self, _task: asyncio.Task[Any], error: BaseException) -> None:
354
+ self._store.reject_all_outgoing(ConnectionError("ACP sender failed."))
355
+ self._on_failure(error)
356
+
357
+
358
+ class _BackpressureDispatcher:
359
+ """Request-semaphore dispatcher that sinks every notification without tasks."""
360
+
361
+ def __init__(
362
+ self,
363
+ *,
364
+ queue: MessageQueue,
365
+ supervisor: TaskSupervisor,
366
+ store: MessageStateStore,
367
+ request_runner: RequestRunner,
368
+ notification_runner: NotificationRunner,
369
+ on_failure: Callable[[BaseException], None],
370
+ ) -> None:
371
+ self._queue = queue
372
+ self._supervisor = supervisor
373
+ self._store = store
374
+ self._request_runner = request_runner
375
+ self._on_failure = on_failure
376
+ self._semaphore = asyncio.Semaphore(_REQUEST_CONCURRENCY)
377
+ self._task: asyncio.Task[None] | None = None
378
+ self._runners: set[asyncio.Task[Any]] = set()
379
+ self._admitting = True
380
+
381
+ def start(self) -> None:
382
+ if self._task is not None:
383
+ raise RuntimeError("ACP dispatcher already started.")
384
+ self._task = self._supervisor.create(
385
+ self._run(),
386
+ name="chrys.acp.dispatcher",
387
+ on_error=self._dispatcher_failed,
388
+ )
389
+
390
+ async def _run(self) -> None:
391
+ async for task in self._queue:
392
+ try:
393
+ if task.kind is not RpcTaskKind.REQUEST or not self._admitting:
394
+ continue
395
+ await self._semaphore.acquire()
396
+ if not self._admitting:
397
+ self._semaphore.release()
398
+ continue
399
+ self._dispatch_request(task.message)
400
+ finally:
401
+ self._queue.task_done()
402
+
403
+ def _dispatch_request(self, message: dict[str, Any]) -> None:
404
+ record = self._store.begin_incoming(message["method"], message.get("params"))
405
+
406
+ async def runner() -> None:
407
+ token = _CURRENT_INBOUND_ID.set(message["id"])
408
+ try:
409
+ result = await self._request_runner(message)
410
+ except Exception as exc:
411
+ self._store.fail_incoming(record, exc)
412
+ raise
413
+ else:
414
+ self._store.complete_incoming(record, result)
415
+ finally:
416
+ _CURRENT_INBOUND_ID.reset(token)
417
+ self._semaphore.release()
418
+
419
+ task = self._supervisor.create(runner(), name="chrys.acp.request")
420
+ self._runners.add(task)
421
+ task.add_done_callback(self._runners.discard)
422
+
423
+ def close_admission(self) -> None:
424
+ self._admitting = False
425
+ if self._task is not None:
426
+ self._task.cancel()
427
+
428
+ async def drain_runners(self) -> None:
429
+ if not self._runners:
430
+ return
431
+ done, pending = await asyncio.wait(tuple(self._runners), timeout=_REQUEST_DRAIN_TIMEOUT_SECONDS)
432
+ for task in pending:
433
+ task.cancel()
434
+ cancelled_done: set[asyncio.Task[Any]] = set()
435
+ if pending:
436
+ cancelled_done, _still_pending = await asyncio.wait(
437
+ pending,
438
+ timeout=_REQUEST_DRAIN_TIMEOUT_SECONDS,
439
+ )
440
+ for task in (*done, *cancelled_done):
441
+ with contextlib.suppress(asyncio.CancelledError, Exception):
442
+ await task
443
+
444
+ async def stop(self) -> None:
445
+ self.close_admission()
446
+ if self._task is not None:
447
+ with contextlib.suppress(asyncio.CancelledError):
448
+ await self._task
449
+ self._task = None
450
+
451
+ async def cancel_runners(self) -> None:
452
+ """Cancel in-flight request callbacks without closing admission.
453
+
454
+ Admission stays open so requests arriving after the prompt seal are
455
+ still dispatched and politely auto-denied by the callbacks; the close
456
+ ladder remains the sole owner of dispatcher teardown. Supervisor error
457
+ hooks skip cancelled tasks, so this never feeds the failure path.
458
+ """
459
+ for task in tuple(self._runners):
460
+ task.cancel()
461
+ await self.drain_runners()
462
+
463
+ async def emergency_stop(self) -> None:
464
+ self.close_admission()
465
+ await self.cancel_runners()
466
+
467
+ def _dispatcher_failed(self, _task: asyncio.Task[Any], error: BaseException) -> None:
468
+ self._on_failure(error)
469
+
470
+
471
+ @dataclass(frozen=True, slots=True)
472
+ class _PromptBarrier:
473
+ request_id: int
474
+
475
+
476
+ class _AcpDisconnectError(AcpTransportError):
477
+ """EOF after the exact prompt response; drain the barrier before closing."""
478
+
479
+
480
+ class _AcpProtocolLimitError(AcpTransportError):
481
+ """Deterministic cap or budget violation; never retryable pre-stateful."""
482
+
483
+
484
+ class _ObserverBuffer:
485
+ """Bounded update lane plus an unmetered control-sentinel lane."""
486
+
487
+ def __init__(self) -> None:
488
+ self._items: deque[tuple[dict[str, Any] | _PromptBarrier, int]] = deque()
489
+ self._data_items = 0
490
+ self._data_bytes = 0
491
+ self._ready = asyncio.Event()
492
+ self._closed = False
493
+
494
+ def put_update(self, params: dict[str, Any]) -> None:
495
+ # Updates are retained (buffered) and then published, so they ride the
496
+ # same §7.4 caps as every other retained frame. Validated tool-image
497
+ # encodings are the sole exception because base64 expands the shared
498
+ # 3 MiB image limit beyond the generic string and payload ceilings.
499
+ try:
500
+ _validate_update_payload_caps(params)
501
+ except ValueError as exc:
502
+ raise AcpTransportError("An ACP update exceeds the retained-payload caps.", cause=exc) from exc
503
+ size = len(encode_protocol_json(params))
504
+ if self._data_items >= _MAX_UPDATE_ITEMS or self._data_bytes + size > _MAX_UPDATE_BYTES:
505
+ raise AcpTransportError("The ACP update buffer exceeded its safety budget.")
506
+ self._items.append((params, size))
507
+ self._data_items += 1
508
+ self._data_bytes += size
509
+ self._ready.set()
510
+
511
+ def put_barrier(self, request_id: int) -> None:
512
+ self._items.append((_PromptBarrier(request_id), 0))
513
+ self._ready.set()
514
+
515
+ async def get(self) -> dict[str, Any] | _PromptBarrier:
516
+ while not self._items:
517
+ if self._closed:
518
+ raise EOFError
519
+ self._ready.clear()
520
+ await self._ready.wait()
521
+ item, size = self._items.popleft()
522
+ if size:
523
+ self._data_items -= 1
524
+ self._data_bytes -= size
525
+ return item
526
+
527
+ def close(self) -> None:
528
+ self._closed = True
529
+ self._ready.set()
530
+
531
+
532
+ @dataclass(slots=True)
533
+ class _InboundRequest:
534
+ method: str
535
+ human_wait: bool
536
+
537
+
538
+ class _FrameObserver:
539
+ """Synchronous SDK observer retaining exact wire order without raising."""
540
+
541
+ def __init__(
542
+ self,
543
+ *,
544
+ store: _TrackingStateStore,
545
+ updates: _ObserverBuffer,
546
+ active_session: Callable[[], str | None],
547
+ fail: Callable[[BaseException], None],
548
+ activity: Callable[[], None],
549
+ ) -> None:
550
+ self._store = store
551
+ self._updates = updates
552
+ self._active_session = active_session
553
+ self._fail = fail
554
+ self._activity = activity
555
+ self._inbound: dict[str | int, _InboundRequest] = {}
556
+ self._human_waits = 0
557
+ self._sealed = False
558
+
559
+ @property
560
+ def human_wait_count(self) -> int:
561
+ return self._human_waits
562
+
563
+ @property
564
+ def prompt_sealed(self) -> bool:
565
+ """Whether the exact prompt response has been observed on the wire."""
566
+ return self._sealed
567
+
568
+ def human_wait_admitted(self, request_id: str | int | None) -> bool:
569
+ """Return whether this request holds one of the capped human-wait slots."""
570
+ if request_id is None:
571
+ return False
572
+ request = self._inbound.get(request_id)
573
+ return request is not None and request.human_wait
574
+
575
+ def __call__(self, event: StreamEvent) -> None:
576
+ try:
577
+ if event.direction is StreamDirection.INCOMING:
578
+ self._observe_incoming(event.message)
579
+ else:
580
+ self._observe_outgoing(event.message)
581
+ except BaseException as exc:
582
+ failure = (
583
+ exc
584
+ if isinstance(exc, AcpTransportError)
585
+ else AcpTransportError(
586
+ "The ACP frame observer failed.",
587
+ cause=exc,
588
+ )
589
+ )
590
+ self._fail(failure)
591
+
592
+ def _observe_incoming(self, message: dict[str, Any]) -> None:
593
+ if "method" in message and "id" in message:
594
+ request_id = message["id"]
595
+ if request_id in self._inbound:
596
+ raise ValueError("Duplicate outstanding inbound JSON-RPC id.")
597
+ method = message["method"]
598
+ active_session = self._is_active_session_frame(message)
599
+ # Registration is capped here, at arrival: an uncapped flag from a
600
+ # request the dispatcher never admits would suspend idle expiry
601
+ # forever. Unflagged requests are auto-rejected by the callbacks.
602
+ # A sealed attempt admits no new waits: the turn is already over.
603
+ human_wait = (
604
+ method in _HUMAN_METHODS
605
+ and active_session
606
+ and self._human_waits < _MAX_HUMAN_WAITS
607
+ and not self._sealed
608
+ )
609
+ self._inbound[request_id] = _InboundRequest(method=method, human_wait=human_wait)
610
+ if human_wait:
611
+ self._human_waits += 1
612
+ if active_session:
613
+ self._activity()
614
+ return
615
+
616
+ if "method" in message:
617
+ if message["method"] == _UPDATE_METHOD:
618
+ if self._sealed:
619
+ # Wire order is queue order, so anything observed after
620
+ # the exact prompt response would land behind the barrier
621
+ # and leak into the sink after prompt() returned.
622
+ logger.debug("Dropping an ACP session/update observed after the prompt response")
623
+ return
624
+ if not self._is_active_session_frame(message):
625
+ # Foreign traffic is inert by contract: it must not
626
+ # consume the active attempt's update budget, and a
627
+ # malformed or oversized foreign frame must not be able
628
+ # to fail the attempt from here or from the consumer.
629
+ logger.debug("Dropping an ACP session/update for a foreign session")
630
+ return
631
+ self._activity()
632
+ self._updates.put_update(message.get("params", {}))
633
+ elif self._is_active_session_frame(message):
634
+ self._activity()
635
+ return
636
+
637
+ request_id = message["id"]
638
+ # The pump already ran ``observe_response`` in frame order (a response
639
+ # coalesced with EOF must be tracked before the EOF classification);
640
+ # this branch only orders seal/barrier against the queued updates.
641
+ record = self._store.outstanding_record(request_id)
642
+ if record is None or not record.response_observed:
643
+ return
644
+ self._activity()
645
+ if record.method == _PROMPT_METHOD:
646
+ if record.request_id == self._store.prompt_id:
647
+ self._seal_prompt()
648
+ self._updates.put_barrier(record.request_id)
649
+
650
+ def _seal_prompt(self) -> None:
651
+ """Mark the attempt terminal at the exact prompt response.
652
+
653
+ Sealing happens synchronously in the pump's frame order, so every
654
+ later frame — updates, new human requests — sees it, and the idle
655
+ watchdog can never blame the agent for local post-response drains.
656
+ Outstanding human-wait slots are released here: their callbacks are
657
+ auto-denied (not yet started) or cancelled (in flight) by prompt()'s
658
+ completion path and must not suspend idle expiry meanwhile.
659
+ """
660
+ self._sealed = True
661
+ for request in self._inbound.values():
662
+ request.human_wait = False
663
+ self._human_waits = 0
664
+
665
+ def _observe_outgoing(self, message: dict[str, Any]) -> None:
666
+ # Inbound ids are retired at the response-write seam
667
+ # (before_response_send). This observer runs only after the SDK
668
+ # finishes its send, and the peer may legally reuse an id the moment
669
+ # it reads the response — popping here could retire the reused id's
670
+ # fresh registration.
671
+ return
672
+
673
+ def before_response_send(self, request_id: str | int) -> None:
674
+ # The write seam is the id's end of life: writer.write follows this
675
+ # call synchronously, and every response flows through the retained
676
+ # sender, so retirement here is total and the peer may reuse the id
677
+ # as soon as it reads the response.
678
+ request = self._inbound.pop(request_id, None)
679
+ if request is not None and request.human_wait:
680
+ request.human_wait = False
681
+ self._human_waits -= 1
682
+ self._activity()
683
+
684
+ def clear_human_wait(self, request_id: str | int | None) -> None:
685
+ if request_id is None:
686
+ return
687
+ request = self._inbound.get(request_id)
688
+ if request is not None and request.human_wait:
689
+ request.human_wait = False
690
+ self._human_waits -= 1
691
+ self._activity()
692
+
693
+ def clear(self) -> None:
694
+ self._inbound.clear()
695
+ self._human_waits = 0
696
+ self._activity()
697
+
698
+ def _is_active_session_frame(self, message: dict[str, Any]) -> bool:
699
+ return _is_active_session_payload(message, self._active_session())
700
+
701
+
702
+ def _is_active_session_payload(message: dict[str, Any], active: str | None) -> bool:
703
+ if active is None:
704
+ return False
705
+ params = message.get("params")
706
+ return type(params) is dict and type(params.get("sessionId")) is str and params["sessionId"] == active
707
+
708
+
709
+ class _PauseTransport:
710
+ """StreamReader pause/resume hook that backpressures the raw framing pump."""
711
+
712
+ def __init__(self) -> None:
713
+ self._reading = asyncio.Event()
714
+ self._reading.set()
715
+
716
+ async def wait_until_reading(self) -> None:
717
+ await self._reading.wait()
718
+
719
+ def pause_reading(self) -> None:
720
+ self._reading.clear()
721
+
722
+ def resume_reading(self) -> None:
723
+ self._reading.set()
724
+
725
+
726
+ class _FramingPump:
727
+ """Validate and meter raw stdout before forwarding frames to the SDK."""
728
+
729
+ def __init__(
730
+ self,
731
+ raw_reader: asyncio.StreamReader,
732
+ sdk_reader: asyncio.StreamReader,
733
+ pause_transport: _PauseTransport,
734
+ *,
735
+ stateful: Callable[[], bool],
736
+ closing: Callable[[], bool],
737
+ preflight: Callable[[dict[str, Any]], dict[str, Any]],
738
+ on_response: Callable[[dict[str, Any]], None],
739
+ active_session: Callable[[], str | None],
740
+ fail: Callable[[BaseException], None],
741
+ ) -> None:
742
+ self._raw_reader = raw_reader
743
+ self._sdk_reader = sdk_reader
744
+ self._pause_transport = pause_transport
745
+ self._stateful = stateful
746
+ self._closing = closing
747
+ self._preflight = preflight
748
+ self._on_response = on_response
749
+ self._active_session = active_session
750
+ self._fail = fail
751
+ self._frames = 0
752
+ self._bytes = 0
753
+ self._request_frames = 0
754
+ self._task: asyncio.Task[None] | None = None
755
+
756
+ @property
757
+ def frames(self) -> int:
758
+ return self._frames
759
+
760
+ @property
761
+ def bytes(self) -> int:
762
+ return self._bytes
763
+
764
+ def start(self) -> None:
765
+ self._task = asyncio.create_task(self._run(), name="chrys.acp.framing")
766
+
767
+ async def stop(self) -> None:
768
+ task = self._task
769
+ if task is None:
770
+ return
771
+ if task is asyncio.current_task():
772
+ return
773
+ if not task.done():
774
+ task.cancel()
775
+ with contextlib.suppress(asyncio.CancelledError):
776
+ await task
777
+ self._task = None
778
+ # The SDK receive loop blocks on this reader; without EOF it would
779
+ # outlive a close whose SDK-side shutdown never runs (wedged sender).
780
+ self._sdk_reader.feed_eof()
781
+
782
+ async def _run(self) -> None:
783
+ try:
784
+ while True:
785
+ await self._pause_transport.wait_until_reading()
786
+ try:
787
+ line = await self._raw_reader.readline()
788
+ except ValueError as exc:
789
+ raise _AcpProtocolLimitError("The ACP agent emitted an oversized stdout frame.", cause=exc) from exc
790
+ if not line:
791
+ if not self._closing():
792
+ failure = (
793
+ _AcpDisconnectError("The ACP process closed its protocol stream.")
794
+ if self._stateful()
795
+ else classify_protocol_frame_error(
796
+ EOFError("ACP stdout closed."),
797
+ stateful=False,
798
+ complete=False,
799
+ )
800
+ )
801
+ self._fail(failure)
802
+ self._sdk_reader.feed_eof()
803
+ return
804
+ self._frames += 1
805
+ self._bytes += len(line)
806
+ if self._frames > _MAX_ATTEMPT_FRAMES or self._bytes > _MAX_ATTEMPT_BYTES:
807
+ raise _AcpProtocolLimitError("The ACP inbound frame budget was exhausted.")
808
+
809
+ complete = line.endswith(b"\n")
810
+ raw = line[:-1]
811
+ if raw.endswith(b"\r"):
812
+ raw = raw[:-1]
813
+ if not complete:
814
+ raise classify_protocol_frame_error(
815
+ EOFError("ACP frame ended without a newline."),
816
+ stateful=self._stateful(),
817
+ complete=False,
818
+ )
819
+ try:
820
+ message = parse_protocol_json(raw)
821
+ kind = validate_json_rpc_envelope(message)
822
+ except BaseException as exc:
823
+ raise classify_protocol_frame_error(
824
+ exc,
825
+ stateful=self._stateful(),
826
+ complete=True,
827
+ ) from exc
828
+ if kind == "request":
829
+ self._request_frames += 1
830
+ if self._request_frames > _MAX_REQUEST_FRAMES:
831
+ raise _AcpProtocolLimitError("The ACP inbound request-frame budget was exhausted.")
832
+ message = self._preflight(message)
833
+ raw = encode_protocol_json(message)
834
+ elif kind == "notification":
835
+ if message.get("method") == _UPDATE_METHOD and not _is_active_session_payload(
836
+ message, self._active_session()
837
+ ):
838
+ # Foreign traffic is inert by contract even when
839
+ # oversized: it must be dropped before the caps can
840
+ # turn it transport-fatal. A flood still burns the
841
+ # attempt frame budget above, so it stays bounded.
842
+ logger.debug("Dropping an ACP session/update for a foreign session at the pump")
843
+ continue
844
+ # The SDK re-parses every forwarded notification and the
845
+ # dispatcher caps only session/update, so ext/unknown
846
+ # methods must ride the retained-payload caps here.
847
+ # Notifications owe no response, so a violation is
848
+ # transport-fatal (matching put_update) rather than
849
+ # substituted like request params.
850
+ try:
851
+ params = message.get("params")
852
+ if message.get("method") == _UPDATE_METHOD:
853
+ _validate_update_payload_caps(params)
854
+ else:
855
+ _validate_payload_caps(params)
856
+ except ValueError as exc:
857
+ raise _AcpProtocolLimitError(
858
+ "An ACP notification exceeds the retained-payload caps.",
859
+ cause=exc,
860
+ ) from exc
861
+ elif kind == "response":
862
+ # The pump is the single serial point ahead of SDK routing;
863
+ # announcing here is what keeps preflight/observer session
864
+ # checks race-free for frames written back-to-back with the
865
+ # session/new response.
866
+ self._on_response(message)
867
+ self._sdk_reader.feed_data(raw + b"\n")
868
+ except asyncio.CancelledError:
869
+ raise
870
+ except BaseException as exc:
871
+ self._fail(exc)
872
+ self._sdk_reader.feed_eof()
873
+
874
+
875
+ def _extract_raw_usage(message: dict[str, Any]) -> AcpPromptUsage | None:
876
+ result = message.get("result")
877
+ usage = result.get("usage") if type(result) is dict else None
878
+ if usage is None:
879
+ error = message.get("error")
880
+ data = error.get("data") if type(error) is dict else None
881
+ usage = data.get("usage") if type(data) is dict else None
882
+ if type(usage) is not dict:
883
+ return None
884
+ required = ("inputTokens", "outputTokens", "totalTokens")
885
+ optional = ("cachedReadTokens", "cachedWriteTokens", "thoughtTokens")
886
+ if any(not _valid_usage_int(usage.get(field)) for field in required):
887
+ return None
888
+ if any(field in usage and usage[field] is not None and not _valid_usage_int(usage[field]) for field in optional):
889
+ return None
890
+ return AcpPromptUsage(
891
+ input_tokens=usage["inputTokens"],
892
+ output_tokens=usage["outputTokens"],
893
+ total_tokens=usage["totalTokens"],
894
+ cached_read_tokens=usage.get("cachedReadTokens"),
895
+ cached_write_tokens=usage.get("cachedWriteTokens"),
896
+ thought_tokens=usage.get("thoughtTokens"),
897
+ )
898
+
899
+
900
+ def _valid_usage_int(value: Any) -> bool:
901
+ return type(value) is int and 0 <= value <= _MAX_USAGE_VALUE
902
+
903
+
904
+ async def _reap_task(
905
+ task: asyncio.Task[Any],
906
+ *,
907
+ timeout_seconds: float = _CLOSE_STAGE_TIMEOUT_SECONDS,
908
+ ) -> None:
909
+ """Bounded task reap that cannot be held hostage by swallowed cancellation.
910
+
911
+ ``await task`` would forward an outer cancellation into the awaited task
912
+ (``Task.cancel`` cancels ``_fut_waiter``); a task that swallows that single
913
+ CancelledError would strand this reaper after ``asyncio.timeout`` spent its
914
+ one shot. ``asyncio.wait`` never cancels the watched task and returns at
915
+ the deadline unconditionally, so the close ladder below stays reachable.
916
+ """
917
+ task.add_done_callback(_consume_future_exception)
918
+ pending: set[asyncio.Task[Any]] = {task}
919
+ with contextlib.suppress(asyncio.CancelledError, Exception):
920
+ _, pending = await asyncio.wait(pending, timeout=timeout_seconds)
921
+ for stuck in pending:
922
+ stuck.cancel()