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,972 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Single-writer, write-through trajectory event log with written-ack.
4
+
5
+ Contract:
6
+
7
+ * **Write-through** — ``emit`` returns only after the whole line reached the
8
+ file (or after it is certain the event has no persistence guarantee). There
9
+ is no "returned but still queued" state. Normal events are not ``fsync``ed;
10
+ only checkpoints and the graceful-close tail are, and one of those answers
11
+ DEGRADED when its ``fsync`` fails even though its line was written.
12
+ * **Ordered hand-off for synchronous producers** — a call site that cannot
13
+ await (a ``finally`` in a sync function, a callback) uses ``submit`` +
14
+ ``wait`` instead: ``submit`` takes the sequence and hands the encoded line
15
+ to the worker before it returns, so ordering against every later ``emit``
16
+ holds, and only the ack is awaited elsewhere. Such a producer learns
17
+ nothing about persistence — but a later awaited ``WRITTEN`` still proves
18
+ its line is on disk or explained by a gap.
19
+ * **Accounted prefix** — the worker writes strictly in ``sequence`` order. A
20
+ ``WRITTEN`` ack for ``N`` proves every smaller sequence either has a line
21
+ or is covered by a ``trajectory.gap`` persisted before ``N``.
22
+ * **Bounded wait, terminal degrade** — a producer waits at most
23
+ ``write_ack_timeout``; a timeout or write failure flips the writer into
24
+ ``TERMINAL_DEGRADED`` (one-way). Queued-not-started events become virtual
25
+ slots with their payload released; the in-flight batch may finish and is
26
+ confirmed line by line; later emits only register slots. Only gap/closure
27
+ records may still be written, by the same worker, on the same descriptor.
28
+ * **Two-state result** — :class:`EmitResult.WRITTEN` carries the persistence
29
+ guarantee; :class:`EmitResult.DEGRADED` carries none.
30
+
31
+ The worker is a dedicated daemon thread (never a ``ThreadPoolExecutor``): a
32
+ thread stuck in ``write()`` on a network disk must not keep the interpreter
33
+ from exiting, and the writer never opens a second descriptor or thread for
34
+ the same session.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import asyncio
40
+ import concurrent.futures
41
+ import contextlib
42
+ import errno
43
+ import logging
44
+ import os
45
+ import threading
46
+ import time
47
+ from collections import deque
48
+ from collections.abc import Callable, Mapping
49
+ from dataclasses import dataclass, field
50
+ from enum import Enum
51
+ from typing import Any, Final, Protocol
52
+
53
+ from chrys.foundation.trajectory.envelope import (
54
+ INT64_MAX,
55
+ LINE_BUDGET_BYTES,
56
+ SYSTEM_ACTOR,
57
+ EventDraft,
58
+ TrajectoryEvent,
59
+ build_event,
60
+ check_int64_range,
61
+ encode_event_line,
62
+ malformed_id_pointers,
63
+ )
64
+ from chrys.foundation.trajectory.event_types import EventType, GapReason
65
+
66
+ logger = logging.getLogger(__name__)
67
+
68
+ DEFAULT_WRITE_ACK_TIMEOUT_SECONDS: Final = 3.0
69
+ DEFAULT_CLOSE_JOIN_TIMEOUT_SECONDS: Final = 2.0
70
+
71
+
72
+ class EmitResult(Enum):
73
+ """Outcome of one ``emit``; only ``WRITTEN`` carries a persistence guarantee."""
74
+
75
+ WRITTEN = "written"
76
+ DEGRADED = "degraded"
77
+
78
+
79
+ class WriterState(Enum):
80
+ """Writer lifecycle; ``TERMINAL_DEGRADED`` is one-way."""
81
+
82
+ ACTIVE = "active"
83
+ TERMINAL_DEGRADED = "terminal_degraded"
84
+ CLOSED = "closed"
85
+
86
+
87
+ class WriteBackend(Protocol):
88
+ """Minimal byte sink the worker thread drives; injectable for fault tests."""
89
+
90
+ def write(self, data: memoryview) -> int:
91
+ """Write some prefix of *data*; return the byte count (may be short)."""
92
+ ...
93
+
94
+ def fsync(self) -> None: ...
95
+
96
+ def truncate(self, size: int) -> None: ...
97
+
98
+ def close(self) -> None: ...
99
+
100
+
101
+ class FdWriteBackend:
102
+ """Backend over a raw descriptor from ``secure_open_owner_only_append``."""
103
+
104
+ def __init__(self, fd: int) -> None:
105
+ self._fd = fd
106
+
107
+ @property
108
+ def fd(self) -> int:
109
+ return self._fd
110
+
111
+ def write(self, data: memoryview) -> int:
112
+ return os.write(self._fd, data)
113
+
114
+ def fsync(self) -> None:
115
+ os.fsync(self._fd)
116
+
117
+ def truncate(self, size: int) -> None:
118
+ os.ftruncate(self._fd, size)
119
+
120
+ def close(self) -> None:
121
+ fd, self._fd = self._fd, -1
122
+ if fd >= 0:
123
+ os.close(fd)
124
+
125
+
126
+ @dataclass(frozen=True, slots=True)
127
+ class DroppedRange:
128
+ """One immutable range of confirmed-unwritten sequences."""
129
+
130
+ first_sequence: int
131
+ last_sequence: int
132
+ dropped_count: int
133
+
134
+
135
+ @dataclass(slots=True)
136
+ class _Pending:
137
+ sequence: int
138
+ data: bytes | None
139
+ future: concurrent.futures.Future[EmitResult]
140
+ fsync_after: bool = False
141
+ system: bool = False
142
+ covers: tuple[DroppedRange, ...] = ()
143
+ """For gap records: compact ranges this gap accounts for."""
144
+
145
+
146
+ @dataclass(slots=True)
147
+ class WriterSnapshot:
148
+ """Point-in-time view for diagnostics and tests."""
149
+
150
+ state: WriterState
151
+ next_sequence: int
152
+ last_written_sequence: int
153
+ last_durable_sequence: int
154
+ uncovered_slots: tuple[int, ...]
155
+ uncovered_ranges: tuple[DroppedRange, ...]
156
+ inflight_sequences: tuple[int, ...]
157
+ queued_sequences: tuple[int, ...]
158
+ degraded_reason: str | None
159
+ tail_torn: bool
160
+ runtime_closed: bool
161
+ written_count: int = 0
162
+ written_bytes: int = 0
163
+
164
+
165
+ @dataclass(slots=True)
166
+ class _GapBookkeeping:
167
+ pending_by_gap_sequence: dict[int, tuple[DroppedRange, ...]] = field(default_factory=dict)
168
+ attempt_failed: bool = False
169
+
170
+
171
+ @dataclass(frozen=True, slots=True)
172
+ class SubmittedEvent:
173
+ """A queued line or the immediate result when nothing was queued (degraded/gap)."""
174
+
175
+ result: _Pending | EmitResult
176
+
177
+
178
+ class TrajectoryWriter:
179
+ """Per-session writer: one thread, one descriptor, one sequence counter."""
180
+
181
+ def __init__(
182
+ self,
183
+ *,
184
+ backend: WriteBackend,
185
+ session_id: str,
186
+ runtime_id: str,
187
+ coverage_id: str,
188
+ branch_id: str,
189
+ initial_sequence: int = 0,
190
+ initial_offset: int = 0,
191
+ recovered_gap: tuple[int, int] | None = None,
192
+ write_ack_timeout: float = DEFAULT_WRITE_ACK_TIMEOUT_SECONDS,
193
+ on_degraded: Callable[[str], None] | None = None,
194
+ on_worker_exit: Callable[[], None] | None = None,
195
+ thread_name: str | None = None,
196
+ ) -> None:
197
+ if write_ack_timeout <= 0:
198
+ raise ValueError("write_ack_timeout must be positive.")
199
+ self._backend = backend
200
+ self._session_id = session_id
201
+ self._runtime_id = runtime_id
202
+ self._coverage_id = coverage_id
203
+ self._branch_id = branch_id
204
+ self._write_ack_timeout = write_ack_timeout
205
+ self._on_degraded = on_degraded
206
+ self._on_worker_exit = on_worker_exit
207
+
208
+ self._lock = threading.Lock()
209
+ self._cond = threading.Condition(self._lock)
210
+ self._queue: deque[_Pending] = deque()
211
+ self._inflight: list[_Pending] = []
212
+ self._state = WriterState.ACTIVE
213
+ self._frozen = False
214
+ self._stop_requested = False
215
+ self._next_sequence = initial_sequence + 1
216
+ self._last_written = initial_sequence
217
+ self._last_durable = 0
218
+ self._confirmed_offset = initial_offset
219
+ self._uncovered_slots: list[int] = []
220
+ self._uncovered_ranges: list[DroppedRange] = []
221
+ self._gap = _GapBookkeeping()
222
+ self._degraded_reason: str | None = None
223
+ self._tail_torn = False
224
+ self._runtime_closed = False
225
+ self._written_count = 0
226
+ self._written_bytes = 0
227
+ self._thread = threading.Thread(
228
+ target=self._run,
229
+ name=thread_name or f"chrys-trajectory-writer-{session_id[:8]}",
230
+ daemon=True,
231
+ )
232
+ self._started = False
233
+ self._resources_released = False
234
+ self._abort_truncate_to: int | None = None
235
+ self._abort_rewound = False
236
+ if recovered_gap is not None:
237
+ # Queued here rather than on ``start``: the first emit starts the
238
+ # worker too, so the only place this gap is guaranteed to precede
239
+ # every line of the resumed runtime is before anyone holds the
240
+ # writer at all.
241
+ first, last = recovered_gap
242
+ if 1 <= first <= last:
243
+ with self._lock:
244
+ recovered = DroppedRange(
245
+ first_sequence=first,
246
+ last_sequence=last,
247
+ dropped_count=last - first + 1,
248
+ )
249
+ self._queue_gap_locked(
250
+ first=first,
251
+ last=last,
252
+ reason=GapReason.RECOVERED_UNREADABLE,
253
+ covers=(recovered,),
254
+ )
255
+
256
+ # ------------------------------------------------------------------ props
257
+
258
+ @property
259
+ def state(self) -> WriterState:
260
+ return self._state
261
+
262
+ @property
263
+ def runtime_id(self) -> str:
264
+ return self._runtime_id
265
+
266
+ @property
267
+ def coverage_id(self) -> str:
268
+ return self._coverage_id
269
+
270
+ @property
271
+ def session_id(self) -> str:
272
+ return self._session_id
273
+
274
+ @property
275
+ def branch_id(self) -> str:
276
+ return self._branch_id
277
+
278
+ @property
279
+ def write_ack_timeout(self) -> float:
280
+ return self._write_ack_timeout
281
+
282
+ @property
283
+ def is_degraded(self) -> bool:
284
+ return self._state is WriterState.TERMINAL_DEGRADED
285
+
286
+ @property
287
+ def runtime_closed(self) -> bool:
288
+ return self._runtime_closed
289
+
290
+ @property
291
+ def thread_alive(self) -> bool:
292
+ return self._thread.is_alive()
293
+
294
+ @property
295
+ def last_assigned_sequence(self) -> int:
296
+ """The highest sequence handed out so far (0 before the first event)."""
297
+ with self._lock:
298
+ return self._next_sequence - 1
299
+
300
+ def snapshot(self) -> WriterSnapshot:
301
+ with self._lock:
302
+ return WriterSnapshot(
303
+ state=self._state,
304
+ next_sequence=self._next_sequence,
305
+ last_written_sequence=self._last_written,
306
+ last_durable_sequence=self._last_durable,
307
+ uncovered_slots=tuple(self._uncovered_slots),
308
+ uncovered_ranges=tuple(self._uncovered_ranges),
309
+ inflight_sequences=tuple(p.sequence for p in self._inflight),
310
+ queued_sequences=tuple(p.sequence for p in self._queue),
311
+ degraded_reason=self._degraded_reason,
312
+ tail_torn=self._tail_torn,
313
+ runtime_closed=self._runtime_closed,
314
+ written_count=self._written_count,
315
+ written_bytes=self._written_bytes,
316
+ )
317
+
318
+ def set_branch_id(self, branch_id: str) -> None:
319
+ """Switch the branch stamped on subsequent events (rollback)."""
320
+ with self._lock:
321
+ self._branch_id = branch_id
322
+
323
+ def start(self) -> None:
324
+ """Start the worker thread (idempotent; the first emit also starts it)."""
325
+ with self._lock:
326
+ if self._started:
327
+ return
328
+ # Only a thread that actually started may be joined later: claiming
329
+ # one that the system refused would make ``_stop_worker`` raise on
330
+ # the join and strand the descriptor and the session's lease.
331
+ self._thread.start()
332
+ self._started = True
333
+
334
+ def measure_line(self, draft: EventDraft) -> int:
335
+ """Return the encoded line length *draft* would have at the next sequence."""
336
+ event = build_event(
337
+ draft,
338
+ sequence=10**9,
339
+ runtime_id=self._runtime_id,
340
+ coverage_id=self._coverage_id,
341
+ session_id=self._session_id,
342
+ branch_id=self._branch_id,
343
+ )
344
+ return len(encode_event_line(event))
345
+
346
+ # ------------------------------------------------------------------ emit
347
+
348
+ def submit(
349
+ self, draft: EventDraft, *, payload_factory: Callable[[int], Mapping[str, Any]] | None = None
350
+ ) -> SubmittedEvent:
351
+ """Assign the sequence and queue the line now; the ack is awaited separately.
352
+
353
+ For producers that cannot await at the point the event happens (a
354
+ synchronous stream hook) but must keep write order: the sequence is
355
+ taken synchronously here, so a later ``emit`` from the same task
356
+ always lands after it. Pass the result to :meth:`wait` to observe
357
+ the ack and keep the bounded-wait contract.
358
+ """
359
+ return SubmittedEvent(self._prepare(draft, payload_factory=payload_factory))
360
+
361
+ async def wait(self, submitted: SubmittedEvent) -> EmitResult:
362
+ """Await the ack of a :meth:`submit` (timeout flips the writer degraded)."""
363
+ result = submitted.result
364
+ if isinstance(result, EmitResult):
365
+ return result
366
+ return await self._await_ack(result)
367
+
368
+ async def emit(
369
+ self, draft: EventDraft, *, payload_factory: Callable[[int], Mapping[str, Any]] | None = None
370
+ ) -> EmitResult:
371
+ """Write one event through; returns ``WRITTEN`` only once the line is in the file.
372
+
373
+ The wait is bounded by ``write_ack_timeout``; the write request is
374
+ shielded from the caller's cancellation (a cancelled producer's line is
375
+ still written). Timeout flips the writer into ``TERMINAL_DEGRADED``.
376
+
377
+ ``payload_factory`` builds the payload from the assigned sequence for
378
+ events whose payload refers to their own position (``session.rollback``
379
+ closes its superseded range at ``sequence - 1``).
380
+ """
381
+ prepared = self._prepare(draft, payload_factory=payload_factory)
382
+ if isinstance(prepared, EmitResult):
383
+ return prepared
384
+ return await self._await_ack(prepared)
385
+
386
+ def emit_blocking(
387
+ self, draft: EventDraft, *, payload_factory: Callable[[int], Mapping[str, Any]] | None = None
388
+ ) -> EmitResult:
389
+ """Synchronous ``emit`` for threads that do not run an event loop."""
390
+ prepared = self._prepare(draft, payload_factory=payload_factory)
391
+ if isinstance(prepared, EmitResult):
392
+ return prepared
393
+ return self._wait_ack_blocking(prepared)
394
+
395
+ async def checkpoint(self) -> EmitResult:
396
+ """Write ``trajectory.checkpoint`` and ``fsync``; ``last_durable`` advances here only."""
397
+
398
+ def payload(sequence: int) -> dict[str, Any]:
399
+ return {
400
+ "last_assigned": sequence,
401
+ "last_written": self._last_written,
402
+ "last_durable": self._last_durable,
403
+ }
404
+
405
+ prepared = self._prepare(
406
+ EventDraft(event_type=EventType.CHECKPOINT, actor=SYSTEM_ACTOR),
407
+ payload_factory=payload,
408
+ fsync_after=True,
409
+ )
410
+ if isinstance(prepared, EmitResult):
411
+ return prepared
412
+ return await self._await_ack(prepared)
413
+
414
+ async def close(self, *, reason: str, join_timeout: float = DEFAULT_CLOSE_JOIN_TIMEOUT_SECONDS) -> bool:
415
+ """Graceful close: freeze → (final gap) → coverage.ended → runtime.finished → fsync → close.
416
+
417
+ Returns ``True`` when the runtime was closed cleanly (both closure
418
+ markers ``WRITTEN`` and durable). Any failure leaves the runtime
419
+ unclosed on disk, which is exactly the evidence a reader needs.
420
+ """
421
+ with self._lock:
422
+ self._frozen = True
423
+ closed = False
424
+ try:
425
+ if self._state is WriterState.ACTIVE:
426
+ closed = await self._close_active(reason)
427
+ elif self._state is WriterState.TERMINAL_DEGRADED:
428
+ closed = await self._close_degraded(reason)
429
+ except Exception:
430
+ # Closing is the last thing this writer does, so a failure here has
431
+ # nowhere left to be handled — and an unclosed runtime on disk is
432
+ # already how that reads. What must not be lost is the handback
433
+ # below: the descriptor and the session's lease come back either
434
+ # way, or the session stays leased for the rest of the process.
435
+ logger.warning("Trajectory writer close failed for session %s", self._session_id, exc_info=True)
436
+ finally:
437
+ self._stop_worker(join_timeout)
438
+ return closed
439
+
440
+ def abort(self, *, join_timeout: float = DEFAULT_CLOSE_JOIN_TIMEOUT_SECONDS) -> bool:
441
+ """Erase and stop an unannounced runtime without writing closure markers.
442
+
443
+ Activation owns this path: if its prelude could not complete, emitting
444
+ ``coverage.ended`` or ``runtime.finished`` would create terminals for a
445
+ runtime readers never saw start. The worker rewinds every prelude byte
446
+ it may have written before releasing its descriptor and lease, allowing
447
+ activation to retry from the exact prefix it originally recovered.
448
+ """
449
+ with self._lock:
450
+ self._frozen = True
451
+ self._abort_truncate_to = self._confirmed_offset - self._written_bytes
452
+ self._stop_worker(join_timeout)
453
+ return not self.thread_alive and self._abort_rewound
454
+
455
+ # --------------------------------------------------------------- internals
456
+
457
+ def _prepare(
458
+ self,
459
+ draft: EventDraft,
460
+ *,
461
+ payload_factory: Callable[[int], Mapping[str, Any]] | None = None,
462
+ fsync_after: bool = False,
463
+ system: bool = False,
464
+ covers: tuple[DroppedRange, ...] = (),
465
+ ) -> _Pending | EmitResult:
466
+ with self._lock:
467
+ if (self._frozen and not system) or self._stop_requested:
468
+ return EmitResult.DEGRADED
469
+ sequence = self._next_sequence
470
+ self._next_sequence += 1
471
+ if self._state is not WriterState.ACTIVE and not system:
472
+ # Virtual slot only: no payload is retained, no wait is taken.
473
+ self._uncovered_slots.append(sequence)
474
+ return EmitResult.DEGRADED
475
+ try:
476
+ payload = payload_factory(sequence) if payload_factory is not None else None
477
+ event = build_event(
478
+ draft if payload is None else _with_payload(draft, payload),
479
+ sequence=sequence,
480
+ runtime_id=self._runtime_id,
481
+ coverage_id=self._coverage_id,
482
+ session_id=self._session_id,
483
+ branch_id=self._branch_id,
484
+ )
485
+ encoded = _encode_checked(event)
486
+ except Exception:
487
+ # The slot is spent the moment it is handed out. A factory that
488
+ # raises — or a draft that cannot be built into an event —
489
+ # leaves nothing to write, which is the encode failure below by
490
+ # another route: letting it out of here instead would drop the
491
+ # sequence from the prefix with no gap to explain it.
492
+ logger.warning(
493
+ "Trajectory event for sequence %d could not be built; recording a gap", sequence, exc_info=True
494
+ )
495
+ encoded = GapReason.ENCODE_FAILURE
496
+ if isinstance(encoded, str):
497
+ # The event itself cannot be written (over budget, out of range,
498
+ # unencodable). Its slot is accounted for by a gap that takes the
499
+ # next sequence, so the prefix stays explained.
500
+ self._uncovered_slots.append(sequence)
501
+ self._enqueue_gap_locked(reason=encoded)
502
+ return EmitResult.DEGRADED
503
+ pending = _Pending(
504
+ sequence=sequence,
505
+ data=encoded,
506
+ future=concurrent.futures.Future(),
507
+ fsync_after=fsync_after,
508
+ system=system,
509
+ covers=covers,
510
+ )
511
+ self._queue.append(pending)
512
+ self._cond.notify()
513
+ if not self._started:
514
+ self._thread.start()
515
+ self._started = True
516
+ return pending
517
+
518
+ async def _await_ack(self, pending: _Pending) -> EmitResult:
519
+ loop = asyncio.get_running_loop()
520
+ wrapped = asyncio.wrap_future(pending.future, loop=loop)
521
+ try:
522
+ return await asyncio.wait_for(asyncio.shield(wrapped), self._write_ack_timeout)
523
+ except TimeoutError:
524
+ # The worker resolves the concurrent future from its own thread;
525
+ # ``wrap_future`` then schedules delivery onto this event loop.
526
+ # Under scheduler pressure the timeout callback can run before
527
+ # that delivery callback even though the write already finished.
528
+ # Read the worker-owned result directly before declaring the
529
+ # writer degraded so an event-loop delay cannot turn a persisted
530
+ # lifecycle terminal into a virtual slot.
531
+ if pending.future.done():
532
+ return pending.future.result()
533
+ self._enter_terminal_degraded(GapReason.WRITE_TIMEOUT)
534
+ return EmitResult.DEGRADED
535
+
536
+ def _wait_ack_blocking(self, pending: _Pending) -> EmitResult:
537
+ try:
538
+ return pending.future.result(timeout=self._write_ack_timeout)
539
+ except concurrent.futures.TimeoutError:
540
+ self._enter_terminal_degraded(GapReason.WRITE_TIMEOUT)
541
+ return EmitResult.DEGRADED
542
+
543
+ def _enter_terminal_degraded(self, reason: str) -> None:
544
+ dropped: list[_Pending] = []
545
+ with self._lock:
546
+ if self._state is not WriterState.ACTIVE:
547
+ return
548
+ self._state = WriterState.TERMINAL_DEGRADED
549
+ self._degraded_reason = reason
550
+ # Queue classification at the transition: everything still in the
551
+ # queue is "queued, not started" — the worker moves a batch out of
552
+ # the queue under this lock before touching the descriptor, so the
553
+ # in-flight batch is never here. Those slots are confirmed
554
+ # unwritten; release their payload and answer their waiters now.
555
+ while self._queue:
556
+ item = self._queue.popleft()
557
+ item.data = None
558
+ if item.covers:
559
+ self._uncovered_ranges.extend(self._gap.pending_by_gap_sequence.pop(item.sequence, item.covers))
560
+ self._uncovered_slots.append(item.sequence)
561
+ dropped.append(item)
562
+ self._uncovered_slots.sort()
563
+ self._cond.notify_all()
564
+ for item in dropped:
565
+ _resolve(item.future, EmitResult.DEGRADED)
566
+ logger.warning(
567
+ "Trajectory writer for session %s entered TERMINAL_DEGRADED (%s); "
568
+ "later events carry no persistence guarantee.",
569
+ self._session_id,
570
+ reason,
571
+ )
572
+ if self._on_degraded is not None:
573
+ with contextlib.suppress(Exception):
574
+ self._on_degraded(reason)
575
+
576
+ def _enqueue_gap_locked(self, *, reason: str) -> list[_Pending]:
577
+ """Queue one gap per contiguous run of uncovered slots (lock held)."""
578
+ if self._tail_torn or not (self._uncovered_slots or self._uncovered_ranges):
579
+ return []
580
+ if self._next_sequence > INT64_MAX:
581
+ # The gap would have to carry a sequence no line may carry, and it
582
+ # is built past the check that refuses an ordinary event for that
583
+ # — it would land as a line no reader can decode. Nothing after
584
+ # this slot is writable either, so the file ends here: an unwritten
585
+ # slot at the end of a file reads as a runtime that stopped, which
586
+ # is a shape readers already handle.
587
+ return []
588
+ ranges = _coalesced_dropped_ranges(
589
+ [
590
+ *self._uncovered_ranges,
591
+ *(
592
+ DroppedRange(first_sequence=run[0], last_sequence=run[-1], dropped_count=len(run))
593
+ for run in _contiguous_runs(self._uncovered_slots)
594
+ ),
595
+ ]
596
+ )
597
+ self._uncovered_slots = []
598
+ self._uncovered_ranges = []
599
+ return [
600
+ self._queue_gap_locked(
601
+ first=dropped.first_sequence,
602
+ last=dropped.last_sequence,
603
+ reason=reason,
604
+ covers=(dropped,),
605
+ )
606
+ for dropped in ranges
607
+ ]
608
+
609
+ def _queue_gap_locked(
610
+ self,
611
+ *,
612
+ first: int,
613
+ last: int,
614
+ reason: str,
615
+ covers: tuple[DroppedRange, ...],
616
+ ) -> _Pending:
617
+ """Queue one gap over ``[first, last]`` (lock held).
618
+
619
+ ``covers`` keeps compact intervals to hand back if this gap is never
620
+ written, so the next one explains them again. Recovered corruption can
621
+ claim an enormous sequence range; retaining intervals here avoids the
622
+ unbounded ``range`` expansion that per-slot bookkeeping would require.
623
+ """
624
+ sequence = self._next_sequence
625
+ self._next_sequence += 1
626
+ draft = EventDraft(
627
+ event_type=EventType.GAP,
628
+ actor=SYSTEM_ACTOR,
629
+ payload={
630
+ "first_sequence": first,
631
+ "last_sequence": last,
632
+ "dropped_count": last - first + 1,
633
+ "reason": reason,
634
+ },
635
+ )
636
+ event = build_event(
637
+ draft,
638
+ sequence=sequence,
639
+ runtime_id=self._runtime_id,
640
+ coverage_id=self._coverage_id,
641
+ session_id=self._session_id,
642
+ branch_id=self._branch_id,
643
+ )
644
+ pending = _Pending(
645
+ sequence=sequence,
646
+ data=encode_event_line(event),
647
+ future=concurrent.futures.Future(),
648
+ system=True,
649
+ covers=covers,
650
+ )
651
+ if covers:
652
+ self._gap.pending_by_gap_sequence[sequence] = covers
653
+ self._queue.append(pending)
654
+ self._cond.notify()
655
+ return pending
656
+
657
+ # --------------------------------------------------------------- worker
658
+
659
+ def _run(self) -> None:
660
+ try:
661
+ self._drain()
662
+ finally:
663
+ # The descriptor and the session's writer lease belong to this
664
+ # thread for as long as it may still write, so this thread is what
665
+ # hands them back — on request, or whenever a blocked ``write()``
666
+ # finally lets go. Nothing outside can observe that moment without
667
+ # a thread of its own, and a thread is exactly what a system out
668
+ # of them cannot give.
669
+ self._release_worker_resources()
670
+
671
+ def _drain(self) -> None:
672
+ while True:
673
+ with self._cond:
674
+ while not self._queue and not self._stop_requested:
675
+ self._cond.wait()
676
+ if not self._queue and self._stop_requested:
677
+ return
678
+ batch = list(self._queue)
679
+ self._queue.clear()
680
+ self._inflight = batch
681
+ self._write_batch(batch)
682
+ with self._lock:
683
+ self._inflight = []
684
+ # ``CLOSED`` counts too: a close that timed out on this thread
685
+ # moved the state on and left, and the slots it could not cover
686
+ # are no less lost for that. This thread still holds the file,
687
+ # so it is the only place left that can say so — without it the
688
+ # next runtime resumes on a slot that was handed out already
689
+ # and nothing on disk records that an event went missing.
690
+ settling = self._state in (WriterState.TERMINAL_DEGRADED, WriterState.CLOSED)
691
+ if settling and not self._gap.attempt_failed and (self._uncovered_slots or self._uncovered_ranges):
692
+ self._enqueue_gap_locked(reason=self._degraded_reason or GapReason.WRITE_FAILURE)
693
+
694
+ def _write_batch(self, batch: list[_Pending]) -> None:
695
+ payload = b"".join(item.data or b"" for item in batch)
696
+ view = memoryview(payload)
697
+ written = 0
698
+ failure: BaseException | None = None
699
+ try:
700
+ while written < len(payload):
701
+ count = self._backend.write(view[written:])
702
+ if not isinstance(count, int) or count <= 0:
703
+ raise OSError(errno.EIO, "Trajectory backend wrote nothing.")
704
+ written += count
705
+ except BaseException as exc: # a stuck/failing backend must never kill the thread
706
+ failure = exc
707
+ if failure is None:
708
+ self._complete_batch(batch, written=written)
709
+ return
710
+ self._fail_batch(batch, written=written, failure=failure)
711
+
712
+ def _complete_batch(self, batch: list[_Pending], *, written: int) -> None:
713
+ fsync_failure: BaseException | None = None
714
+ needs_fsync = any(item.fsync_after for item in batch)
715
+ if needs_fsync:
716
+ try:
717
+ self._backend.fsync()
718
+ except BaseException as exc:
719
+ fsync_failure = exc
720
+ to_resolve: list[tuple[_Pending, EmitResult]] = []
721
+ with self._lock:
722
+ self._confirmed_offset += written
723
+ for item in batch:
724
+ self._last_written = item.sequence
725
+ self._written_count += 1
726
+ self._written_bytes += len(item.data or b"")
727
+ if item.covers:
728
+ self._gap.pending_by_gap_sequence.pop(item.sequence, None)
729
+ item.data = None
730
+ # Every line reached the file. An event that asked to be made
731
+ # durable asked for more than that, so a failed ``fsync`` is
732
+ # answered with DEGRADED: the only callers that set the flag
733
+ # are the checkpoint and the closure tail, and both must be
734
+ # able to tell "written" from "written and durable".
735
+ durable_denied = fsync_failure is not None and item.fsync_after
736
+ to_resolve.append((item, EmitResult.DEGRADED if durable_denied else EmitResult.WRITTEN))
737
+ if needs_fsync and fsync_failure is None:
738
+ self._last_durable = batch[-1].sequence
739
+ for item, result in to_resolve:
740
+ _resolve(item.future, result)
741
+ if fsync_failure is not None:
742
+ logger.warning("Trajectory checkpoint fsync failed for session %s: %s", self._session_id, fsync_failure)
743
+
744
+ def _fail_batch(self, batch: list[_Pending], *, written: int, failure: BaseException) -> None:
745
+ to_resolve: list[tuple[_Pending, EmitResult]] = []
746
+ truncate_to: int | None = None
747
+ with self._lock:
748
+ offset = 0
749
+ confirmed = 0
750
+ torn = False
751
+ for item in batch:
752
+ size = len(item.data or b"")
753
+ end = offset + size
754
+ if end <= written:
755
+ confirmed = end
756
+ self._last_written = item.sequence
757
+ self._written_count += 1
758
+ self._written_bytes += size
759
+ if item.covers:
760
+ self._gap.pending_by_gap_sequence.pop(item.sequence, None)
761
+ to_resolve.append((item, EmitResult.WRITTEN))
762
+ else:
763
+ if offset < written:
764
+ torn = True
765
+ if item.covers:
766
+ self._uncovered_ranges.extend(self._gap.pending_by_gap_sequence.pop(item.sequence, ()))
767
+ self._gap.attempt_failed = True
768
+ self._uncovered_slots.append(item.sequence)
769
+ to_resolve.append((item, EmitResult.DEGRADED))
770
+ item.data = None
771
+ offset = end
772
+ self._uncovered_slots.sort()
773
+ self._confirmed_offset += confirmed
774
+ if torn:
775
+ truncate_to = self._confirmed_offset
776
+ # Transition first so a producer that observes DEGRADED already sees the
777
+ # terminal state (and the on_degraded callback has fired).
778
+ self._enter_terminal_degraded(GapReason.WRITE_FAILURE)
779
+ logger.warning("Trajectory write failed for session %s: %s", self._session_id, failure)
780
+ for item, result in to_resolve:
781
+ _resolve(item.future, result)
782
+ if truncate_to is not None:
783
+ # Repair our own torn tail so a later gap/closure line does not land
784
+ # after a half-written line (which would be mid-file corruption).
785
+ try:
786
+ self._backend.truncate(truncate_to)
787
+ except BaseException as exc:
788
+ with self._lock:
789
+ self._tail_torn = True
790
+ logger.warning("Trajectory torn-tail repair failed for session %s: %s", self._session_id, exc)
791
+
792
+ # --------------------------------------------------------------- closing
793
+
794
+ async def _close_active(self, reason: str) -> bool:
795
+ ended = await self._emit_system(
796
+ EventType.COVERAGE_ENDED,
797
+ payload_factory=lambda seq: {"coverage_id": self._coverage_id, "last_sequence": seq - 1},
798
+ )
799
+ if ended is not EmitResult.WRITTEN:
800
+ return False
801
+ finished = await self._emit_system(
802
+ EventType.RUNTIME_FINISHED,
803
+ payload_factory=lambda _seq: {"reason": reason},
804
+ fsync_after=True,
805
+ )
806
+ if finished is not EmitResult.WRITTEN:
807
+ return False
808
+ with self._lock:
809
+ self._runtime_closed = True
810
+ return True
811
+
812
+ async def _close_degraded(self, reason: str) -> bool:
813
+ # ① emit is frozen (done by close()). ② wait for the in-flight batch to
814
+ # settle — a batch that never returns leaves slots neither written nor
815
+ # dropped, so no final gap can be constructed and the runtime stays open.
816
+ deadline = time.monotonic() + self._write_ack_timeout
817
+ while True:
818
+ with self._lock:
819
+ inflight = bool(self._inflight)
820
+ torn = self._tail_torn
821
+ if not inflight:
822
+ break
823
+ if time.monotonic() >= deadline:
824
+ return False
825
+ await asyncio.sleep(0.01)
826
+ if torn:
827
+ return False
828
+ with self._lock:
829
+ self._gap.attempt_failed = False
830
+ gaps = self._enqueue_gap_locked(reason=GapReason.DEGRADED_CLOSE)
831
+ for pending in gaps:
832
+ if await self._await_ack(pending) is not EmitResult.WRITTEN:
833
+ return False
834
+ with self._lock:
835
+ if self._uncovered_slots or self._uncovered_ranges:
836
+ return False
837
+ return await self._close_active(reason)
838
+
839
+ async def _emit_system(
840
+ self,
841
+ event_type: str,
842
+ *,
843
+ payload_factory: Callable[[int], dict[str, Any]],
844
+ fsync_after: bool = False,
845
+ ) -> EmitResult:
846
+ prepared = self._prepare(
847
+ EventDraft(event_type=event_type, actor=SYSTEM_ACTOR),
848
+ payload_factory=payload_factory,
849
+ fsync_after=fsync_after,
850
+ system=True,
851
+ )
852
+ if isinstance(prepared, EmitResult):
853
+ return prepared
854
+ return await self._await_ack(prepared)
855
+
856
+ def _stop_worker(self, join_timeout: float) -> None:
857
+ with self._lock:
858
+ self._stop_requested = True
859
+ self._cond.notify_all()
860
+ if self._started:
861
+ self._thread.join(timeout=max(0.0, join_timeout))
862
+ if self._thread.is_alive():
863
+ # A stuck worker keeps its descriptor: closing it underneath a
864
+ # blocked write() could let the number be reused by another file.
865
+ logger.warning(
866
+ "Trajectory writer thread for session %s did not exit within %.1fs; abandoning it.",
867
+ self._session_id,
868
+ join_timeout,
869
+ )
870
+ with self._lock:
871
+ self._state = WriterState.CLOSED
872
+ # The worker hands its own resources back when it returns.
873
+ return
874
+ with self._lock:
875
+ self._state = WriterState.CLOSED
876
+ self._release_worker_resources()
877
+
878
+ def _release_worker_resources(self) -> None:
879
+ """Close the descriptor and hand the lease back; runs at most once.
880
+
881
+ Reached from the worker's own exit and from ``_stop_worker`` (which
882
+ covers a worker that never started), so it has to settle who does it.
883
+ """
884
+ with self._lock:
885
+ if self._resources_released:
886
+ return
887
+ self._resources_released = True
888
+ callback = self._on_worker_exit
889
+ self._on_worker_exit = None
890
+ truncate_to = self._abort_truncate_to
891
+ if truncate_to is not None:
892
+ try:
893
+ self._backend.truncate(truncate_to)
894
+ self._backend.fsync()
895
+ self._abort_rewound = True
896
+ except Exception:
897
+ logger.warning("Trajectory activation rollback failed for session %s", self._session_id, exc_info=True)
898
+ with contextlib.suppress(Exception):
899
+ self._backend.close()
900
+ if callback is not None:
901
+ try:
902
+ callback()
903
+ except Exception:
904
+ logger.debug("Trajectory writer exit callback failed", exc_info=True)
905
+
906
+
907
+ def _with_payload(draft: EventDraft, payload: Mapping[str, Any]) -> EventDraft:
908
+ return EventDraft(
909
+ event_type=draft.event_type,
910
+ event_id=draft.event_id,
911
+ actor=draft.actor,
912
+ payload=payload,
913
+ measurements=draft.measurements,
914
+ turn_id=draft.turn_id,
915
+ operation_id=draft.operation_id,
916
+ parent_operation_id=draft.parent_operation_id,
917
+ links=draft.links,
918
+ segmented_fields=draft.segmented_fields,
919
+ occurred_at=draft.occurred_at,
920
+ monotonic_ns=draft.monotonic_ns,
921
+ )
922
+
923
+
924
+ def _encode_checked(event: TrajectoryEvent) -> bytes | str:
925
+ """Encode *event*; return the line bytes or a gap reason code on failure."""
926
+ data = event.to_dict()
927
+ if check_int64_range(data):
928
+ return GapReason.VALUE_OUT_OF_RANGE
929
+ if malformed_id_pointers(data):
930
+ # A line no reader can decode would read as corruption; a gap says
931
+ # exactly what happened and keeps the accounted prefix honest.
932
+ return GapReason.ENCODE_FAILURE
933
+ try:
934
+ line = encode_event_line(event)
935
+ except TypeError, ValueError:
936
+ return GapReason.ENCODE_FAILURE
937
+ if len(line) > LINE_BUDGET_BYTES:
938
+ return GapReason.LINE_BUDGET_EXCEEDED
939
+ return line
940
+
941
+
942
+ def _contiguous_runs(values: list[int]) -> list[list[int]]:
943
+ runs: list[list[int]] = []
944
+ for value in sorted(set(values)):
945
+ if runs and runs[-1][-1] + 1 == value:
946
+ runs[-1].append(value)
947
+ else:
948
+ runs.append([value])
949
+ return runs
950
+
951
+
952
+ def _coalesced_dropped_ranges(ranges: list[DroppedRange]) -> list[DroppedRange]:
953
+ """Merge overlapping/adjacent dropped intervals without expanding them."""
954
+ merged: list[DroppedRange] = []
955
+ for dropped in sorted(ranges, key=lambda item: (item.first_sequence, item.last_sequence)):
956
+ if merged and dropped.first_sequence <= merged[-1].last_sequence + 1:
957
+ first = merged[-1].first_sequence
958
+ last = max(merged[-1].last_sequence, dropped.last_sequence)
959
+ merged[-1] = DroppedRange(
960
+ first_sequence=first,
961
+ last_sequence=last,
962
+ dropped_count=last - first + 1,
963
+ )
964
+ else:
965
+ merged.append(dropped)
966
+ return merged
967
+
968
+
969
+ def _resolve(future: concurrent.futures.Future[EmitResult], result: EmitResult) -> None:
970
+ with contextlib.suppress(Exception):
971
+ if not future.done():
972
+ future.set_result(result)