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,1114 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Event-driven GC freeze coordination for the Textual UI."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import gc
8
+ import logging
9
+ import weakref
10
+ from collections import deque
11
+ from collections.abc import Callable, Iterable
12
+ from dataclasses import dataclass
13
+ from enum import Enum
14
+ from functools import cache, partial
15
+ from itertools import pairwise
16
+ from time import perf_counter
17
+ from typing import Any, Protocol, cast
18
+
19
+ from textual import _time as textual_time
20
+ from textual.cache import FIFOCache, LRUCache
21
+ from textual.message import Message
22
+ from textual.screen import Screen
23
+
24
+ from chrys.foundation.patches.textual_removed_node_caches import removal_clears_node_caches
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ GC_FREEZE_ENABLED = True
29
+ """Whether a ``ChrysApp`` freezes the permanent generation by default.
30
+
31
+ Deliberately not a setting: the optimization is invisible when it works and
32
+ the failure modes it guards against are ours to fix, not a user's to
33
+ configure. ``ChrysApp(gc_freeze_enabled=...)`` overrides it for the
34
+ acceptance tests and the calibration harness, which is the whole audience.
35
+ """
36
+
37
+ _WATCHDOG_PERIOD_SECONDS = 1.0
38
+ GC_FREEZE_WATCHDOG_PERIOD_SECONDS = _WATCHDOG_PERIOD_SECONDS
39
+ _SCHEDULE_RECOVERY_SECONDS = 3.0
40
+ _ABSORB_INPUT_IDLE_SECONDS = 0.75
41
+ _RECLAIM_INPUT_IDLE_SECONDS = 3.0
42
+ _RECLAIM_SOFT_ABSORBS = 12
43
+ _RECLAIM_HARD_ABSORBS = 16
44
+ _FROZEN_DEAD_BUDGET_BYTES = 32 * 1024 * 1024
45
+ _PREPARE_MAX_FAILURES = 3
46
+ _PREPARE_RETRY_BASE_SECONDS = 1.0
47
+ _DEFERRAL_LOG_INTERVAL_SECONDS = 30.0
48
+ _PENDING_WARNING_SECONDS = 30.0
49
+ _ABSORB_WARNING_SECONDS = 0.050
50
+ _FULL_RECLAIM_WARNING_SECONDS = 0.750
51
+ _ABSORB_TREND_WINDOW = 4
52
+ _ABSORB_TREND_GROWTH_RATIO = 1.5
53
+ _ABSORB_TREND_MIN_DELTA_SECONDS = 0.005
54
+
55
+
56
+ class GcAbsorbReason(Enum):
57
+ """Stable UI mutation that should be added to the permanent generation."""
58
+
59
+ TURN_TERMINAL = "turn_terminal"
60
+ STABLE_CONTENT_MOUNTED = "stable_content_mounted"
61
+ PROFILE_UI_UPDATED = "profile_ui_updated"
62
+ WORKSPACE_UI_UPDATED = "workspace_ui_updated"
63
+ VIEWPORT_UPDATED = "viewport_updated"
64
+
65
+
66
+ class GcReclaimReason(Enum):
67
+ """Lifecycle boundary that may leave unreachable frozen cycles."""
68
+
69
+ SESSION_READY = "session_ready"
70
+ SESSION_RESTORED = "session_restored"
71
+ ROLLBACK_WELCOME = "rollback_welcome"
72
+ AGENT_REBUILT = "agent_rebuilt"
73
+ AGENT_REBUILD_FAILED = "agent_rebuild_failed"
74
+ STABLE_CONTENT_REMOVED = "stable_content_removed"
75
+ THEME_UPDATED = "theme_updated"
76
+ PERIODIC = "periodic"
77
+
78
+
79
+ class GcFreezeBlockReason(Enum):
80
+ """Hard gate that makes a GC freeze action unsafe right now."""
81
+
82
+ TOP_SCREEN = "top_screen"
83
+ BACKEND_TURN_LIFECYCLE = "backend_turn_lifecycle"
84
+ AGENT_LOADING = "agent_loading"
85
+ AGENT_RUNNING = "agent_running"
86
+ SCROLL_GC_PAUSED = "scroll_gc_paused"
87
+ POINTER_BUTTON_HELD = "pointer_button_held"
88
+ SESSION_JSON_VISIBLE = "session_json_visible"
89
+ TRAJECTORY_DASHBOARD_VISIBLE = "trajectory_dashboard_visible"
90
+ SHELL_VISIBLE = "shell_visible"
91
+ SUGGESTIONS_VISIBLE = "suggestions_visible"
92
+ PARTICIPANT = "participant"
93
+
94
+
95
+ @dataclass
96
+ class GcAbsorbRequested(Message):
97
+ """Request an additive freeze after a stable UI mutation."""
98
+
99
+ reason: GcAbsorbReason
100
+ terminal_boundary: bool = False
101
+
102
+
103
+ @dataclass(frozen=True, slots=True)
104
+ class GcRemovedContent:
105
+ """Weak handles on a removed subtree that no freeze had captured when it was removed.
106
+
107
+ ``built_at_epoch`` is :func:`current_gc_freeze_epoch` read before the subtree's first
108
+ node was built. While the epoch is unchanged the subtree is young, so instead of an
109
+ idle full reclaim the coordinator watches ``nodes`` and requests one only if a node
110
+ is still alive after the next freezing action's own collection, the one case where a
111
+ freeze would capture removed content.
112
+ """
113
+
114
+ built_at_epoch: int
115
+ nodes: tuple[weakref.ref[Any], ...]
116
+
117
+ @classmethod
118
+ def watch(cls, *, built_at_epoch: int, nodes: Iterable[object]) -> GcRemovedContent:
119
+ """Hold *nodes* weakly so the request never keeps the removed subtree alive."""
120
+ return cls(built_at_epoch=built_at_epoch, nodes=tuple(weakref.ref(node) for node in nodes))
121
+
122
+
123
+ @dataclass
124
+ class GcReclaimRequested(Message):
125
+ """Request a full reclaim at a prompt or idle lifecycle boundary.
126
+
127
+ An idle request that carries ``removed`` content still young when the coordinator
128
+ handles it becomes a watch on that content instead of a pending reclaim.
129
+ """
130
+
131
+ reason: GcReclaimReason
132
+ prompt: bool
133
+ removed: GcRemovedContent | None = None
134
+
135
+
136
+ @dataclass(frozen=True, slots=True)
137
+ class GcFreezeActionMetrics:
138
+ """Content-free measurements for one completed coordinator action."""
139
+
140
+ action: str
141
+ absorb_reasons: tuple[str, ...]
142
+ prompt_reclaim_reasons: tuple[str, ...]
143
+ idle_reclaim_reasons: tuple[str, ...]
144
+ terminal: bool
145
+ collected: int
146
+ duration_ms: float
147
+ freeze_count_before: int | None
148
+ freeze_count_after: int | None
149
+ absorbs_since_reclaim: int
150
+ refresh_recovery_count: int
151
+
152
+
153
+ @dataclass(frozen=True, slots=True)
154
+ class DetachedLruCache:
155
+ """Acyclic capacity token held only across a synchronous GC freeze action."""
156
+
157
+ maxsize: int
158
+
159
+
160
+ @dataclass(frozen=True, slots=True)
161
+ class DetachedFifoCache:
162
+ """Acyclic capacity token for a Textual arrangement FIFO."""
163
+
164
+ maxsize: int
165
+
166
+
167
+ def detach_lru_cache(cache: LRUCache[Any, Any] | DetachedLruCache) -> DetachedLruCache:
168
+ """Drop a cyclic LRU before collection without freezing a replacement head."""
169
+ if isinstance(cache, DetachedLruCache):
170
+ return cache
171
+ return DetachedLruCache(maxsize=cache.maxsize)
172
+
173
+
174
+ def renew_lru_cache(detached: DetachedLruCache | LRUCache[Any, Any]) -> LRUCache[Any, Any]:
175
+ """Create the mutable LRU only after the permanent generation changes."""
176
+ if isinstance(detached, LRUCache):
177
+ return detached
178
+ return LRUCache(maxsize=detached.maxsize)
179
+
180
+
181
+ def detach_fifo_cache(cache: FIFOCache[Any, Any] | DetachedFifoCache) -> DetachedFifoCache:
182
+ """Drop an arrangement FIFO before freezing its empty dictionary shell."""
183
+ if isinstance(cache, DetachedFifoCache):
184
+ return cache
185
+ return DetachedFifoCache(maxsize=cache._maxsize)
186
+
187
+
188
+ def renew_fifo_cache(detached: DetachedFifoCache | FIFOCache[Any, Any]) -> FIFOCache[Any, Any]:
189
+ """Recreate an arrangement FIFO after the permanent generation changes."""
190
+ if isinstance(detached, FIFOCache):
191
+ return detached
192
+ return FIFOCache(maxsize=detached.maxsize)
193
+
194
+
195
+ def raise_gc_freeze_hook_errors(message: str, errors: list[Exception]) -> None:
196
+ """Raise every independently observed hook failure as one diagnostic."""
197
+ if errors:
198
+ raise ExceptionGroup(message, errors)
199
+
200
+
201
+ class GcFreezeHost(Protocol):
202
+ """Narrow App adapter used by :class:`GcFreezeCoordinator`."""
203
+
204
+ def call_after_refresh(self, callback: Callable[[], None]) -> bool:
205
+ """Schedule ``callback`` after the App's next refresh."""
206
+
207
+ def freeze_block_reason(self) -> GcFreezeBlockReason | None:
208
+ """Return the first active hard gate, if any."""
209
+
210
+ def prepare_for_gc_freeze(self) -> None:
211
+ """Prepare registered surfaces immediately before a GC action."""
212
+
213
+ def after_gc_freeze(self) -> None:
214
+ """Renew registered mutable cyclic surfaces after a freeze."""
215
+
216
+ def abort_gc_freeze(self) -> None:
217
+ """Best-effort restore every cache detached by an incomplete hook sequence."""
218
+
219
+
220
+ class GcFreezeParticipant(Protocol):
221
+ """Contract for a permanently mounted mutable or cache-heavy UI surface."""
222
+
223
+ def gc_freeze_block_reason(self) -> GcFreezeBlockReason | None:
224
+ """Return the participant's current hard gate, if any."""
225
+
226
+ def prepare_for_gc_freeze(self) -> None:
227
+ """Release young cyclic cache state before collection and freeze."""
228
+
229
+ def after_gc_freeze(self) -> None:
230
+ """Replace mutable cyclic containers after the permanent generation changes."""
231
+
232
+ def abort_gc_freeze(self) -> None:
233
+ """Idempotently restore detached caches after any hook failure."""
234
+
235
+
236
+ class _CacheProbeEntry:
237
+ """Weakly referenceable value the Textual cache probe watches die."""
238
+
239
+ __slots__ = ("__weakref__",)
240
+
241
+
242
+ def _cache_frees_entries_by_refcount(
243
+ probe_cache: LRUCache[int, _CacheProbeEntry] | FIFOCache[int, _CacheProbeEntry],
244
+ ) -> bool:
245
+ """Whether *probe_cache* frees an evicted and a cleared entry by their last reference.
246
+
247
+ Upstream ``LRUCache`` links its entries into a ring, so ``clear()`` drops the index but
248
+ leaves the ring, and its entries, to a cyclic collection. The caller disables automatic
249
+ collection so a young ring cannot be collected before the check and pass as acyclic.
250
+ """
251
+ evicted = _CacheProbeEntry()
252
+ cleared = _CacheProbeEntry()
253
+ evicted_ref = weakref.ref(evicted)
254
+ cleared_ref = weakref.ref(cleared)
255
+ probe_cache[0] = evicted
256
+ probe_cache[1] = cleared
257
+ probe_cache.get(1)
258
+ # A capacity of two evicts key 0 under both the LRU and the FIFO policy.
259
+ probe_cache[2] = _CacheProbeEntry()
260
+ del evicted, cleared
261
+ evicted_freed = evicted_ref() is None
262
+ probe_cache.clear()
263
+ return evicted_freed and cleared_ref() is None
264
+
265
+
266
+ @cache
267
+ def textual_screen_caches_acyclic() -> bool:
268
+ """Whether Textual's per-widget caches free their entries without a cyclic collection.
269
+
270
+ Each widget keeps a box-model and a query-one ``LRUCache`` and an arrangement
271
+ ``FIFOCache``. Once ``textual_lru_acyclic`` stores LRU entries in an ``OrderedDict``,
272
+ neither cache holds a reference cycle: an entry evicted or cleared after a freeze is
273
+ freed by its last reference even though the cache itself is frozen, and the compositor
274
+ maps are plain containers a reflow replaces.
275
+
276
+ The answer comes from exercising both cache classes through their public API once per
277
+ process, after the runtime patch is installed. Anything unexpected, including an
278
+ upstream ring cache when the patch is skipped, answers ``False`` and keeps the detach
279
+ path.
280
+ """
281
+ gc_was_enabled = gc.isenabled()
282
+ gc.disable()
283
+ try:
284
+ acyclic = _cache_frees_entries_by_refcount(LRUCache(maxsize=2)) and _cache_frees_entries_by_refcount(
285
+ FIFOCache(maxsize=2)
286
+ )
287
+ except Exception:
288
+ logger.warning("Textual cache probe failed; GC freeze keeps detaching screen caches", exc_info=True)
289
+ return False
290
+ finally:
291
+ if gc_was_enabled:
292
+ gc.enable()
293
+ logger.debug("Textual screen caches are %s", "acyclic" if acyclic else "cyclic; GC freeze detaches them")
294
+ return acyclic
295
+
296
+
297
+ def textual_screen_caches_freeze_in_place() -> bool:
298
+ """Whether a GC action may freeze a screen's Textual caches as they are.
299
+
300
+ Their entries must be freed without a cyclic collection
301
+ (:func:`textual_screen_caches_acyclic`), and a removal must clear every entry that could
302
+ still hold the removed node (``textual_removed_node_caches``); otherwise a lookup cached
303
+ before a removal keeps the removed subtree alive until the detach path drops the cache.
304
+ When both hold, freezing the caches in place strands nothing, so a GC action need not
305
+ detach them, renew them and reflow the whole screen.
306
+ """
307
+ return removal_clears_node_caches() and textual_screen_caches_acyclic()
308
+
309
+
310
+ def prepare_textual_screen_for_gc(screen: Screen) -> None:
311
+ """Release screen/compositor and detach standard cyclic LRUs before freezing.
312
+
313
+ When :func:`textual_screen_caches_freeze_in_place` holds the caches stay installed. GC actions
314
+ run only while *screen* is on top (a covered MainScreen blocks them with ``TOP_SCREEN``), and
315
+ outside ``batch_update()`` the top screen lays out before the refresh callbacks that run them,
316
+ so its layout state names no widget removed from it.
317
+ """
318
+ if textual_screen_caches_freeze_in_place():
319
+ return
320
+ screen._compositor.clear()
321
+ for widget in screen.walk_children(with_self=True):
322
+ widget._box_model_cache = cast(Any, detach_lru_cache(widget._box_model_cache))
323
+ widget._query_one_cache = cast(Any, detach_lru_cache(widget._query_one_cache))
324
+ widget._arrangement_cache = cast(Any, detach_fifo_cache(widget._arrangement_cache))
325
+
326
+
327
+ def after_textual_screen_gc_freeze(screen: Screen) -> None:
328
+ """Renew every standard cache, continuing past individual failures."""
329
+ errors = _normalize_textual_screen_gc_caches(screen)
330
+ raise_gc_freeze_hook_errors("Textual screen cache renewal failed", errors)
331
+
332
+
333
+ def abort_textual_screen_gc_freeze(screen: Screen) -> None:
334
+ """Best-effort normalize every screen cache after an incomplete hook sequence."""
335
+ for error in _normalize_textual_screen_gc_caches(screen):
336
+ logger.error("Failed to restore a detached Textual cache", exc_info=error)
337
+
338
+
339
+ def _normalize_textual_screen_gc_caches(screen: Screen) -> list[Exception]:
340
+ """Restore detached screen caches without stopping at the first failure.
341
+
342
+ Caches frozen in place were never detached, so there is nothing to renew and no reflow
343
+ to force.
344
+ """
345
+ errors: list[Exception] = []
346
+ if textual_screen_caches_freeze_in_place():
347
+ return errors
348
+ for widget in screen.walk_children(with_self=True):
349
+ try:
350
+ widget._box_model_cache = renew_lru_cache(widget._box_model_cache)
351
+ except Exception as error:
352
+ errors.append(error)
353
+ try:
354
+ widget._query_one_cache = renew_lru_cache(widget._query_one_cache)
355
+ except Exception as error:
356
+ errors.append(error)
357
+ try:
358
+ widget._arrangement_cache = renew_fifo_cache(widget._arrangement_cache)
359
+ except Exception as error:
360
+ errors.append(error)
361
+ try:
362
+ screen.refresh(layout=True)
363
+ except Exception as error:
364
+ errors.append(error)
365
+ return errors
366
+
367
+
368
+ _enabled_owner: GcFreezeCoordinator | None = None
369
+ _freeze_epoch = 0
370
+
371
+
372
+ def current_gc_freeze_epoch() -> int:
373
+ """Return how many times a coordinator has frozen the permanent generation in this process.
374
+
375
+ ``gc.freeze()`` captures only objects alive when it runs, so content built after the epoch
376
+ was read stays out of the permanent generation until the epoch changes. Content removed
377
+ under the epoch it was built in is young garbage: unless something still holds it when
378
+ the next freeze runs, that action's own collection frees it without a full reclaim
379
+ (:class:`GcRemovedContent`).
380
+ """
381
+ return _freeze_epoch
382
+
383
+
384
+ def _freeze_permanent_generation() -> None:
385
+ """Freeze every live tracked object, advancing the epoch first so no reader misses it."""
386
+ global _freeze_epoch
387
+
388
+ _freeze_epoch += 1
389
+ gc.freeze()
390
+
391
+
392
+ class GcFreezeCoordinator:
393
+ """Place explicit GC freeze/reclaim intents at safe Textual boundaries."""
394
+
395
+ def __init__(
396
+ self,
397
+ host: GcFreezeHost,
398
+ *,
399
+ enabled: bool,
400
+ clock: Callable[[], float] | None = None,
401
+ measure_freeze_counts: bool = False,
402
+ ) -> None:
403
+ if not 0 < _RECLAIM_SOFT_ABSORBS <= _RECLAIM_HARD_ABSORBS:
404
+ msg = "GC freeze reclaim thresholds must be positive and ordered"
405
+ raise ValueError(msg)
406
+
407
+ self.enabled = enabled
408
+ self._host = host
409
+ self._clock = textual_time.get_time if clock is None else clock
410
+ self._measure_freeze_counts = measure_freeze_counts
411
+ self._started = False
412
+ self._closed = False
413
+ self._faulted = False
414
+ self._startup_warning: str | None = None
415
+ self._frozen = False
416
+ self._scheduled = False
417
+ self._schedule_generation = 0
418
+ self._scheduled_hop = 0
419
+ self._scheduled_at = 0.0
420
+
421
+ self._absorb_pending = False
422
+ self._terminal_boundary_at: float | None = None
423
+ self._prompt_reclaim_pending = False
424
+ self._idle_reclaim_pending = False
425
+
426
+ self._absorb_reasons: set[GcAbsorbReason] = set()
427
+ self._prompt_reclaim_reasons: set[GcReclaimReason] = set()
428
+ self._idle_reclaim_reasons: set[GcReclaimReason] = set()
429
+ self._removal_watch: dict[GcReclaimReason, list[weakref.ref[Any]]] = {}
430
+ """Young removed nodes the next freezing action checks after its collection."""
431
+
432
+ self._absorbs_since_reclaim = 0
433
+ self._last_input_at = self._clock()
434
+ self._last_absorbing_action_at = float("-inf")
435
+ self._last_full_action_at = float("-inf")
436
+ self._prepare_failures = 0
437
+ self._prepare_retry_not_before = float("-inf")
438
+
439
+ self._last_deferral_reason: GcFreezeBlockReason | None = None
440
+ self._last_deferral_logged_at = float("-inf")
441
+ self._refresh_recovery_count = 0
442
+ self._pending_since: float | None = None
443
+ self._pending_warning_emitted = False
444
+ self._absorb_warning_emitted = False
445
+ self._absorb_trend_warning_emitted = False
446
+ self._full_warning_emitted = False
447
+ self._recent_absorb_durations: deque[float] = deque(maxlen=_ABSORB_TREND_WINDOW)
448
+ self._last_action_metrics: GcFreezeActionMetrics | None = None
449
+
450
+ @property
451
+ def started(self) -> bool:
452
+ """Whether this coordinator acquired the process-global lifecycle."""
453
+ return self._started
454
+
455
+ @property
456
+ def frozen(self) -> bool:
457
+ """Whether this coordinator currently owns frozen GC state."""
458
+ return self._frozen
459
+
460
+ @property
461
+ def startup_warning(self) -> str | None:
462
+ """User-facing reason the requested optimization degraded to disabled."""
463
+ return self._startup_warning
464
+
465
+ @property
466
+ def last_action_metrics(self) -> GcFreezeActionMetrics | None:
467
+ """Return the most recent completed action measurement, if any."""
468
+ return self._last_action_metrics
469
+
470
+ def start(self) -> None:
471
+ """Acquire process ownership and queue the initial freeze."""
472
+ global _enabled_owner
473
+
474
+ if not self.enabled or self._closed or self._faulted or self._started:
475
+ return
476
+ if _enabled_owner is not None:
477
+ msg = "Only one enabled Chrys GC freeze coordinator may run in a process"
478
+ raise RuntimeError(msg)
479
+ foreign_freeze_count = gc.get_freeze_count()
480
+ if foreign_freeze_count:
481
+ self.enabled = False
482
+ self._startup_warning = (
483
+ "GC freeze optimization was disabled because this process already contains "
484
+ f"{foreign_freeze_count:,} externally frozen objects."
485
+ )
486
+ logger.warning(
487
+ "Chrys GC freeze optimization disabled because the process already has %d frozen objects",
488
+ foreign_freeze_count,
489
+ )
490
+ return
491
+
492
+ _enabled_owner = self
493
+ self._started = True
494
+ self._mark_pending()
495
+ self._schedule()
496
+
497
+ def request_absorb(
498
+ self,
499
+ *,
500
+ reason: GcAbsorbReason,
501
+ terminal_boundary: bool,
502
+ requested_at: float | None = None,
503
+ ) -> None:
504
+ """Coalesce an additive freeze request and schedule its settle barrier."""
505
+ if self._inert() or not self._frozen:
506
+ return
507
+ source_time = self._clock() if requested_at is None else requested_at
508
+ if source_time < max(self._last_absorbing_action_at, self._last_full_action_at):
509
+ return
510
+
511
+ self._absorb_pending = True
512
+ self._absorb_reasons.add(reason)
513
+ self._mark_pending()
514
+ if terminal_boundary and source_time >= self._last_input_at:
515
+ if self._terminal_boundary_at is None:
516
+ self._terminal_boundary_at = source_time
517
+ else:
518
+ self._terminal_boundary_at = max(self._terminal_boundary_at, source_time)
519
+ self._schedule()
520
+
521
+ def request_reclaim(
522
+ self,
523
+ *,
524
+ reason: GcReclaimReason,
525
+ prompt: bool,
526
+ requested_at: float | None = None,
527
+ removed: GcRemovedContent | None = None,
528
+ ) -> None:
529
+ """Coalesce a full-reclaim request, or watch removed content that is still young.
530
+
531
+ Removed content is young only while the epoch it was built under is current: no
532
+ freeze has run since, so none of it is frozen and the ordinary collector can free
533
+ it. Once the epoch has moved on, part of it may be frozen garbage, and the request
534
+ falls back to the idle reclaim.
535
+ """
536
+ if self._inert():
537
+ return
538
+ source_time = self._clock() if requested_at is None else requested_at
539
+ if source_time < self._last_full_action_at:
540
+ return
541
+
542
+ if prompt:
543
+ self._prompt_reclaim_pending = True
544
+ self._prompt_reclaim_reasons.add(reason)
545
+ self._mark_pending()
546
+ self._schedule()
547
+ elif removed is not None and removed.built_at_epoch == _freeze_epoch:
548
+ watched = [node for node in self._removal_watch.get(reason, ()) if node() is not None]
549
+ watched.extend(removed.nodes)
550
+ self._removal_watch[reason] = watched
551
+ else:
552
+ self._request_idle_reclaim({reason})
553
+
554
+ def note_input(self, *, occurred_at: float) -> None:
555
+ """Record source-time activity and demote older terminal privilege."""
556
+ if self._inert():
557
+ return
558
+ self._last_input_at = max(self._last_input_at, occurred_at)
559
+ if self._terminal_boundary_at is not None and occurred_at > self._terminal_boundary_at:
560
+ self._terminal_boundary_at = None
561
+
562
+ def on_tick(self) -> None:
563
+ """Retry pending work, idle reclaim, or an expired refresh chain once."""
564
+ if self._inert():
565
+ return
566
+ if self._scheduled:
567
+ if self._clock() - self._scheduled_at < _SCHEDULE_RECOVERY_SECONDS:
568
+ self._warn_if_pending_too_long()
569
+ return
570
+ if (block := self._host.freeze_block_reason()) is not None:
571
+ self._record_deferral(block)
572
+ return
573
+ expired_generation = self._schedule_generation
574
+ expired_hop = self._scheduled_hop
575
+ self._schedule_generation += 1
576
+ self._scheduled = False
577
+ self._scheduled_hop = 0
578
+ self._refresh_recovery_count += 1
579
+ self._record_recovery(expired_generation=expired_generation, expired_hop=expired_hop)
580
+ self._schedule()
581
+ return
582
+ if not (not self._frozen or self._prompt_reclaim_pending or self._idle_reclaim_pending or self._absorb_pending):
583
+ return
584
+ if self._clock() < self._prepare_retry_not_before:
585
+ return
586
+ if (block := self._host.freeze_block_reason()) is not None:
587
+ self._record_deferral(block)
588
+ return
589
+ self._warn_if_pending_too_long()
590
+ if (
591
+ not self._frozen
592
+ or self._prompt_reclaim_pending
593
+ or (self._idle_reclaim_pending and self._reclaim_input_idle())
594
+ or (
595
+ self._absorb_pending
596
+ and (
597
+ self._terminal_privileged()
598
+ or (self._absorbs_since_reclaim < _RECLAIM_HARD_ABSORBS and self._absorb_input_idle())
599
+ )
600
+ )
601
+ ):
602
+ self._schedule()
603
+
604
+ def close(self) -> None:
605
+ """Make callbacks inert, restore an unfrozen baseline, and release ownership."""
606
+ global _enabled_owner
607
+
608
+ was_started = self._started and not self._closed
609
+ reason_snapshot = self._reason_snapshot()
610
+ terminal = self._terminal_privileged()
611
+ freeze_count_before = self._action_freeze_count()
612
+ started_at = perf_counter()
613
+ collected = 0
614
+ self._closed = True
615
+ self._schedule_generation += 1
616
+ self._scheduled = False
617
+ self._scheduled_hop = 0
618
+ self._clear_all_intents()
619
+ try:
620
+ if self._frozen:
621
+ gc.unfreeze()
622
+ collected = gc.collect()
623
+ except Exception:
624
+ logger.exception("Failed to return GC to an unfrozen baseline during coordinator close")
625
+ finally:
626
+ self._frozen = False
627
+ if _enabled_owner is self:
628
+ _enabled_owner = None
629
+ if was_started:
630
+ self._record_action(
631
+ "close",
632
+ collected=collected,
633
+ started_at=started_at,
634
+ freeze_count_before=freeze_count_before,
635
+ terminal=terminal,
636
+ reason_snapshot=reason_snapshot,
637
+ )
638
+
639
+ def _inert(self) -> bool:
640
+ return not self.enabled or not self._started or self._closed or self._faulted
641
+
642
+ def _schedule(self) -> None:
643
+ if self._inert() or self._scheduled:
644
+ return
645
+ if self._clock() < self._prepare_retry_not_before:
646
+ return
647
+ self._schedule_generation += 1
648
+ generation = self._schedule_generation
649
+ self._scheduled = True
650
+ self._scheduled_hop = 1
651
+ self._scheduled_at = self._clock()
652
+ callback = partial(self._after_first_refresh, generation)
653
+ if not self._host.call_after_refresh(callback):
654
+ self._clear_schedule(generation)
655
+
656
+ def _clear_schedule(self, generation: int) -> None:
657
+ if generation == self._schedule_generation:
658
+ self._scheduled = False
659
+ self._scheduled_hop = 0
660
+
661
+ def _after_first_refresh(self, generation: int) -> None:
662
+ if generation != self._schedule_generation:
663
+ return
664
+ if self._inert():
665
+ self._clear_schedule(generation)
666
+ return
667
+ self._scheduled_hop = 2
668
+ self._scheduled_at = self._clock()
669
+ callback = partial(self._run, generation)
670
+ if not self._host.call_after_refresh(callback):
671
+ self._clear_schedule(generation)
672
+
673
+ def _run(self, generation: int) -> None:
674
+ if generation != self._schedule_generation:
675
+ return
676
+ self._clear_schedule(generation)
677
+ if self._inert():
678
+ return
679
+ if (block := self._host.freeze_block_reason()) is not None:
680
+ self._record_deferral(block)
681
+ return
682
+
683
+ terminal = self._terminal_privileged()
684
+ reclaim_idle = self._reclaim_input_idle()
685
+ absorb_idle = self._absorb_input_idle()
686
+ hard_reclaim_waiting = self._absorbs_since_reclaim >= _RECLAIM_HARD_ABSORBS
687
+ full_due = (
688
+ self._prompt_reclaim_pending
689
+ or (self._idle_reclaim_pending and reclaim_idle)
690
+ or (hard_reclaim_waiting and (reclaim_idle or terminal))
691
+ or (terminal and self._absorb_pending and self._absorbs_since_reclaim + 1 >= _RECLAIM_HARD_ABSORBS)
692
+ )
693
+
694
+ if not self._frozen:
695
+ self._run_initial_freeze()
696
+ return
697
+ if full_due:
698
+ self._run_full_reclaim()
699
+ return
700
+ if self._absorb_pending and not hard_reclaim_waiting and (terminal or absorb_idle):
701
+ self._run_absorb()
702
+
703
+ def _run_initial_freeze(self) -> None:
704
+ reason_snapshot = self._reason_snapshot()
705
+ terminal = self._terminal_privileged()
706
+ freeze_count_before = self._action_freeze_count()
707
+ started_at = perf_counter()
708
+ if not self._prepare_for_freeze():
709
+ return
710
+ collected = gc.collect()
711
+ removal_survivors = self._take_removal_survivors()
712
+ _freeze_permanent_generation()
713
+ if not self._finish_freeze():
714
+ return
715
+ self._frozen = True
716
+ action_at = self._clock()
717
+ self._last_absorbing_action_at = action_at
718
+ self._last_full_action_at = action_at
719
+ self._absorbs_since_reclaim = 0
720
+ self._recent_absorb_durations.clear()
721
+ self._record_action(
722
+ "initial",
723
+ collected=collected,
724
+ started_at=started_at,
725
+ freeze_count_before=freeze_count_before,
726
+ terminal=terminal,
727
+ reason_snapshot=reason_snapshot,
728
+ )
729
+ self._clear_all_intents()
730
+ self._request_idle_reclaim(removal_survivors, at=action_at)
731
+
732
+ def _run_full_reclaim(self) -> None:
733
+ reason_snapshot = self._reason_snapshot()
734
+ terminal = self._terminal_privileged()
735
+ freeze_count_before = self._action_freeze_count()
736
+ started_at = perf_counter()
737
+ if not self._prepare_for_freeze():
738
+ return
739
+ gc.unfreeze()
740
+ collected = gc.collect()
741
+ removal_survivors = self._take_removal_survivors()
742
+ _freeze_permanent_generation()
743
+ if not self._finish_freeze():
744
+ return
745
+ self._frozen = True
746
+ action_at = self._clock()
747
+ self._last_absorbing_action_at = action_at
748
+ self._last_full_action_at = action_at
749
+ self._absorbs_since_reclaim = 0
750
+ self._recent_absorb_durations.clear()
751
+ self._record_action(
752
+ "full",
753
+ collected=collected,
754
+ started_at=started_at,
755
+ freeze_count_before=freeze_count_before,
756
+ terminal=terminal,
757
+ reason_snapshot=reason_snapshot,
758
+ )
759
+ self._clear_all_intents()
760
+ self._request_idle_reclaim(removal_survivors, at=action_at)
761
+
762
+ def _run_absorb(self) -> None:
763
+ reason_snapshot = self._reason_snapshot()
764
+ terminal = self._terminal_privileged()
765
+ freeze_count_before = self._action_freeze_count()
766
+ started_at = perf_counter()
767
+ idle_reclaim_was_pending = self._idle_reclaim_pending
768
+ if not self._prepare_for_freeze():
769
+ return
770
+ collected = gc.collect()
771
+ removal_survivors = self._take_removal_survivors()
772
+ _freeze_permanent_generation()
773
+ if not self._finish_freeze():
774
+ return
775
+ action_at = self._clock()
776
+ self._last_absorbing_action_at = action_at
777
+ self._absorb_pending = False
778
+ self._terminal_boundary_at = None
779
+ self._absorb_reasons.clear()
780
+ self._absorbs_since_reclaim += 1
781
+ if not idle_reclaim_was_pending:
782
+ self._reset_pending_diagnostics()
783
+ if self._absorbs_since_reclaim >= _RECLAIM_SOFT_ABSORBS:
784
+ removal_survivors.add(GcReclaimReason.PERIODIC)
785
+ self._request_idle_reclaim(removal_survivors, at=action_at)
786
+ self._record_action(
787
+ "absorb",
788
+ collected=collected,
789
+ started_at=started_at,
790
+ freeze_count_before=freeze_count_before,
791
+ terminal=terminal,
792
+ reason_snapshot=reason_snapshot,
793
+ )
794
+
795
+ def _prepare_for_freeze(self) -> bool:
796
+ try:
797
+ self._host.prepare_for_gc_freeze()
798
+ except Exception:
799
+ self._abort_gc_freeze_hooks()
800
+ self._prepare_failures += 1
801
+ logger.exception(
802
+ "GC freeze participant preparation failed (attempt %d/%d)",
803
+ self._prepare_failures,
804
+ _PREPARE_MAX_FAILURES,
805
+ )
806
+ if self._prepare_failures >= _PREPARE_MAX_FAILURES:
807
+ self._fail_open(permanent_generation_changed=False)
808
+ else:
809
+ delay = _PREPARE_RETRY_BASE_SECONDS * (2 ** (self._prepare_failures - 1))
810
+ self._prepare_retry_not_before = self._clock() + delay
811
+ return False
812
+ self._prepare_failures = 0
813
+ self._prepare_retry_not_before = float("-inf")
814
+ return True
815
+
816
+ def _finish_freeze(self) -> bool:
817
+ try:
818
+ self._host.after_gc_freeze()
819
+ except Exception:
820
+ self._abort_gc_freeze_hooks()
821
+ logger.exception("GC freeze participant after-hook failed")
822
+ self._fail_open(permanent_generation_changed=True)
823
+ return False
824
+ return True
825
+
826
+ def _abort_gc_freeze_hooks(self) -> None:
827
+ """Best-effort normalize live UI caches after an incomplete hook pass."""
828
+ try:
829
+ self._host.abort_gc_freeze()
830
+ except Exception:
831
+ logger.exception("GC freeze hook rollback failed")
832
+
833
+ def _fail_open(self, *, permanent_generation_changed: bool) -> None:
834
+ reason_snapshot = self._reason_snapshot()
835
+ terminal = self._terminal_privileged()
836
+ freeze_count_before = self._action_freeze_count()
837
+ started_at = perf_counter()
838
+ collected = 0
839
+ should_unfreeze = permanent_generation_changed or self._frozen
840
+ try:
841
+ if should_unfreeze:
842
+ gc.unfreeze()
843
+ collected = gc.collect()
844
+ except Exception:
845
+ logger.exception("Failed to restore GC baseline while faulting freeze optimization")
846
+ finally:
847
+ self._frozen = False
848
+ self._faulted = True
849
+ self._clear_all_intents()
850
+ self._record_action(
851
+ "fail-open",
852
+ collected=collected,
853
+ started_at=started_at,
854
+ freeze_count_before=freeze_count_before,
855
+ terminal=terminal,
856
+ reason_snapshot=reason_snapshot,
857
+ )
858
+
859
+ def _clear_all_intents(self) -> None:
860
+ self._absorb_pending = False
861
+ self._terminal_boundary_at = None
862
+ self._prompt_reclaim_pending = False
863
+ self._idle_reclaim_pending = False
864
+ self._absorb_reasons.clear()
865
+ self._prompt_reclaim_reasons.clear()
866
+ self._idle_reclaim_reasons.clear()
867
+ self._removal_watch.clear()
868
+ self._reset_pending_diagnostics()
869
+
870
+ def _request_idle_reclaim(self, reasons: set[GcReclaimReason], *, at: float | None = None) -> None:
871
+ if not reasons:
872
+ return
873
+ self._idle_reclaim_pending = True
874
+ self._idle_reclaim_reasons.update(reasons)
875
+ self._mark_pending(at=at)
876
+
877
+ def _take_removal_survivors(self) -> set[GcReclaimReason]:
878
+ """Consume the removal watch; return the reasons whose removed content outlived collection.
879
+
880
+ Called between an action's ``gc.collect()`` and its freeze, where a live node is
881
+ exactly one the freeze is about to capture (or, under an absorb, one a previous
882
+ freeze already captured). It will be frozen garbage once released, so its reason
883
+ becomes an idle reclaim; content the collection freed needs none.
884
+ """
885
+ survivors = {
886
+ reason for reason, nodes in self._removal_watch.items() if any(node() is not None for node in nodes)
887
+ }
888
+ self._removal_watch.clear()
889
+ return survivors
890
+
891
+ def _action_freeze_count(self) -> int | None:
892
+ """Return a permanent-generation count only for offline calibration."""
893
+ return gc.get_freeze_count() if self._measure_freeze_counts else None
894
+
895
+ def _terminal_privileged(self) -> bool:
896
+ return self._terminal_boundary_at is not None and self._terminal_boundary_at >= self._last_input_at
897
+
898
+ def _absorb_input_idle(self) -> bool:
899
+ return self._clock() - self._last_input_at >= _ABSORB_INPUT_IDLE_SECONDS
900
+
901
+ def _reclaim_input_idle(self) -> bool:
902
+ return self._clock() - self._last_input_at >= _RECLAIM_INPUT_IDLE_SECONDS
903
+
904
+ def _record_deferral(self, reason: GcFreezeBlockReason) -> None:
905
+ now = self._clock()
906
+ self._warn_if_pending_too_long(now=now)
907
+ if (
908
+ reason is self._last_deferral_reason
909
+ and now - self._last_deferral_logged_at < _DEFERRAL_LOG_INTERVAL_SECONDS
910
+ ):
911
+ return
912
+ self._last_deferral_reason = reason
913
+ self._last_deferral_logged_at = now
914
+ logger.debug(
915
+ "GC freeze action deferred: %s",
916
+ reason.value,
917
+ extra={
918
+ "event_name": "chrys.gc.freeze.deferral",
919
+ "gc_deferral_reason": reason.value,
920
+ "gc_pending_seconds": self._pending_seconds(now),
921
+ "gc_absorb_pending": self._absorb_pending,
922
+ "gc_prompt_reclaim_pending": self._prompt_reclaim_pending,
923
+ "gc_idle_reclaim_pending": self._idle_reclaim_pending,
924
+ "gc_schedule_generation": self._schedule_generation,
925
+ "gc_scheduled_hop": self._scheduled_hop,
926
+ "gc_refresh_recovery_count": self._refresh_recovery_count,
927
+ },
928
+ )
929
+
930
+ def _record_recovery(self, *, expired_generation: int, expired_hop: int) -> None:
931
+ logger.debug(
932
+ "GC freeze refresh chain recovered: generation=%d hop=%d count=%d",
933
+ expired_generation,
934
+ expired_hop,
935
+ self._refresh_recovery_count,
936
+ extra={
937
+ "event_name": "chrys.gc.freeze.recovery",
938
+ "gc_expired_generation": expired_generation,
939
+ "gc_expired_hop": expired_hop,
940
+ "gc_refresh_recovery_count": self._refresh_recovery_count,
941
+ },
942
+ )
943
+
944
+ def _record_action(
945
+ self,
946
+ action: str,
947
+ *,
948
+ collected: int,
949
+ started_at: float,
950
+ freeze_count_before: int | None,
951
+ terminal: bool,
952
+ reason_snapshot: tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]],
953
+ ) -> None:
954
+ duration_seconds = max(0.0, perf_counter() - started_at)
955
+ absorb_reasons, prompt_reclaim_reasons, idle_reclaim_reasons = reason_snapshot
956
+ metrics = GcFreezeActionMetrics(
957
+ action=action,
958
+ absorb_reasons=absorb_reasons,
959
+ prompt_reclaim_reasons=prompt_reclaim_reasons,
960
+ idle_reclaim_reasons=idle_reclaim_reasons,
961
+ terminal=terminal,
962
+ collected=collected,
963
+ duration_ms=duration_seconds * 1_000,
964
+ freeze_count_before=freeze_count_before,
965
+ freeze_count_after=self._action_freeze_count(),
966
+ absorbs_since_reclaim=self._absorbs_since_reclaim,
967
+ refresh_recovery_count=self._refresh_recovery_count,
968
+ )
969
+ self._last_action_metrics = metrics
970
+ self._log_action(action)
971
+
972
+ def _log_action(self, action: str) -> None:
973
+ """Emit structured diagnostics for the most recently recorded action."""
974
+ metrics = self._last_action_metrics
975
+ if metrics is None or metrics.action != action:
976
+ return
977
+ extra: dict[str, object] = {
978
+ "event_name": "chrys.gc.freeze.action",
979
+ "gc_action": metrics.action,
980
+ "gc_absorb_reasons": metrics.absorb_reasons,
981
+ "gc_prompt_reclaim_reasons": metrics.prompt_reclaim_reasons,
982
+ "gc_idle_reclaim_reasons": metrics.idle_reclaim_reasons,
983
+ "gc_terminal": metrics.terminal,
984
+ "gc_collected": metrics.collected,
985
+ "gc_duration_ms": metrics.duration_ms,
986
+ "gc_freeze_count_before": metrics.freeze_count_before,
987
+ "gc_freeze_count_after": metrics.freeze_count_after,
988
+ "gc_absorbs_since_reclaim": metrics.absorbs_since_reclaim,
989
+ "gc_refresh_recovery_count": metrics.refresh_recovery_count,
990
+ }
991
+ logger.debug(
992
+ "GC freeze action=%s duration_ms=%.3f collected=%d freeze_count=%s->%s "
993
+ "absorb_reasons=%s prompt_reclaim_reasons=%s idle_reclaim_reasons=%s "
994
+ "absorbs_since_reclaim=%d",
995
+ action,
996
+ extra["gc_duration_ms"],
997
+ metrics.collected,
998
+ metrics.freeze_count_before,
999
+ extra["gc_freeze_count_after"],
1000
+ metrics.absorb_reasons,
1001
+ metrics.prompt_reclaim_reasons,
1002
+ metrics.idle_reclaim_reasons,
1003
+ metrics.absorbs_since_reclaim,
1004
+ extra=extra,
1005
+ )
1006
+ duration_seconds = metrics.duration_ms / 1_000
1007
+ if action == "absorb":
1008
+ self._record_absorb_latency(duration_seconds, extra=extra)
1009
+ elif (
1010
+ action in {"initial", "full"}
1011
+ and duration_seconds > _FULL_RECLAIM_WARNING_SECONDS
1012
+ and not self._full_warning_emitted
1013
+ ):
1014
+ self._full_warning_emitted = True
1015
+ logger.warning("GC freeze full action exceeded the calibrated latency threshold", extra=extra)
1016
+
1017
+ def _record_absorb_latency(self, duration_seconds: float, *, extra: dict[str, object]) -> None:
1018
+ """Warn once for an absolute overrun and once for sustained sub-threshold growth."""
1019
+ self._recent_absorb_durations.append(duration_seconds)
1020
+ if duration_seconds > _ABSORB_WARNING_SECONDS and not self._absorb_warning_emitted:
1021
+ self._absorb_warning_emitted = True
1022
+ logger.warning(
1023
+ "GC freeze absorb exceeded the calibrated latency threshold",
1024
+ extra={**extra, "gc_latency_warning": "absolute_threshold"},
1025
+ )
1026
+ if self._absorb_trend_warning_emitted or len(self._recent_absorb_durations) < _ABSORB_TREND_WINDOW:
1027
+ return
1028
+ durations = tuple(self._recent_absorb_durations)
1029
+ if not all(earlier < later for earlier, later in pairwise(durations)):
1030
+ return
1031
+ if durations[-1] - durations[0] < _ABSORB_TREND_MIN_DELTA_SECONDS:
1032
+ return
1033
+ if durations[-1] < durations[0] * _ABSORB_TREND_GROWTH_RATIO:
1034
+ return
1035
+ self._absorb_trend_warning_emitted = True
1036
+ logger.warning(
1037
+ "GC freeze absorb latency is trending upward",
1038
+ extra={
1039
+ **extra,
1040
+ "gc_latency_warning": "upward_trend",
1041
+ "gc_absorb_trend_durations_ms": tuple(duration * 1_000 for duration in durations),
1042
+ },
1043
+ )
1044
+
1045
+ def _reason_snapshot(self) -> tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]]:
1046
+ return (
1047
+ tuple(sorted(reason.value for reason in self._absorb_reasons)),
1048
+ tuple(sorted(reason.value for reason in self._prompt_reclaim_reasons)),
1049
+ tuple(sorted(reason.value for reason in self._idle_reclaim_reasons)),
1050
+ )
1051
+
1052
+ def _mark_pending(self, *, at: float | None = None) -> None:
1053
+ if self._pending_since is None:
1054
+ self._pending_since = self._clock() if at is None else at
1055
+ self._pending_warning_emitted = False
1056
+
1057
+ def _reset_pending_diagnostics(self) -> None:
1058
+ self._pending_since = None
1059
+ self._pending_warning_emitted = False
1060
+
1061
+ def _pending_seconds(self, now: float) -> float:
1062
+ return 0.0 if self._pending_since is None else max(0.0, now - self._pending_since)
1063
+
1064
+ def _warn_if_pending_too_long(self, *, now: float | None = None) -> None:
1065
+ if self._pending_since is None or self._pending_warning_emitted:
1066
+ return
1067
+ current = self._clock() if now is None else now
1068
+ pending_seconds = self._pending_seconds(current)
1069
+ if pending_seconds < _PENDING_WARNING_SECONDS:
1070
+ return
1071
+ self._pending_warning_emitted = True
1072
+ logger.warning(
1073
+ "GC freeze request remains pending after %.1f seconds",
1074
+ pending_seconds,
1075
+ extra={
1076
+ "event_name": "chrys.gc.freeze.pending",
1077
+ "gc_pending_seconds": pending_seconds,
1078
+ "gc_absorb_pending": self._absorb_pending,
1079
+ "gc_prompt_reclaim_pending": self._prompt_reclaim_pending,
1080
+ "gc_idle_reclaim_pending": self._idle_reclaim_pending,
1081
+ "gc_schedule_generation": self._schedule_generation,
1082
+ "gc_scheduled_hop": self._scheduled_hop,
1083
+ "gc_refresh_recovery_count": self._refresh_recovery_count,
1084
+ },
1085
+ )
1086
+
1087
+
1088
+ __all__ = [
1089
+ "GC_FREEZE_ENABLED",
1090
+ "GC_FREEZE_WATCHDOG_PERIOD_SECONDS",
1091
+ "DetachedFifoCache",
1092
+ "DetachedLruCache",
1093
+ "GcAbsorbReason",
1094
+ "GcAbsorbRequested",
1095
+ "GcFreezeActionMetrics",
1096
+ "GcFreezeBlockReason",
1097
+ "GcFreezeCoordinator",
1098
+ "GcFreezeHost",
1099
+ "GcFreezeParticipant",
1100
+ "GcReclaimReason",
1101
+ "GcReclaimRequested",
1102
+ "GcRemovedContent",
1103
+ "abort_textual_screen_gc_freeze",
1104
+ "after_textual_screen_gc_freeze",
1105
+ "current_gc_freeze_epoch",
1106
+ "detach_fifo_cache",
1107
+ "detach_lru_cache",
1108
+ "prepare_textual_screen_for_gc",
1109
+ "raise_gc_freeze_hook_errors",
1110
+ "renew_fifo_cache",
1111
+ "renew_lru_cache",
1112
+ "textual_screen_caches_acyclic",
1113
+ "textual_screen_caches_freeze_in_place",
1114
+ ]