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,1474 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Three-stage trajectory aggregation for the P0 dashboard.
4
+
5
+ The loader performs one physical JSONL scan into compact, reversible facts.
6
+ Resolution happens only after the scan reaches EOF (or the complete live-tail
7
+ prefix), so rollback projection and forward references are settled before any
8
+ metric is published.
9
+
10
+ ``_resolve`` wires the stages: context evidence (``_context_evidence``) and
11
+ physical turn attempts (``_turns``) first, then actions and change
12
+ verification (``_actions``). Session totals and retry folding stay here:
13
+ retry amplification feeds validation, and the folded logical turns become the
14
+ published turns. The insight panels (``_insights``) read the physical attempts
15
+ and use the folded turns only to count skill use per logical turn.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from array import array
21
+ from collections import Counter, defaultdict
22
+ from dataclasses import replace
23
+ from io import BufferedReader
24
+ from os import fstat
25
+ from pathlib import Path
26
+ from threading import Event
27
+ from typing import Final, cast
28
+
29
+ import chrys.service.analytics._turns as _turns
30
+ from chrys.foundation.config.settings import DEFAULT_TRAJECTORY_VERIFY_COMMANDS
31
+ from chrys.foundation.platform.files import secure_open_owner_only_binary
32
+ from chrys.foundation.trajectory.fingerprint import FINGERPRINT_KEY_BYTES
33
+ from chrys.foundation.trajectory.keys import TRAJECTORY_KEY_FILE_NAME
34
+ from chrys.service.analytics._actions import (
35
+ _action_count_metrics,
36
+ _degrade_change_verification,
37
+ _degrade_validation_metrics,
38
+ _resolve_actions,
39
+ _resolve_change_verification,
40
+ _validation_metrics,
41
+ )
42
+ from chrys.service.analytics._context_evidence import _revision_memberships, _RevisionResolution
43
+ from chrys.service.analytics._facts import (
44
+ _active,
45
+ _closed_sequence_union,
46
+ _Endpoint,
47
+ _Intermediate,
48
+ _lifecycle_cut,
49
+ _LifecycleCut,
50
+ _Node,
51
+ _payload_str,
52
+ )
53
+ from chrys.service.analytics._insights import (
54
+ _context_carrying_load,
55
+ _degrade_insights,
56
+ _degrade_tool_usage_panel,
57
+ _insights_analysis,
58
+ _tool_usage_panel,
59
+ )
60
+ from chrys.service.analytics._metric_ops import (
61
+ _cap_session_metric,
62
+ _percentile_metric,
63
+ _sum_metrics,
64
+ _sum_optional_bucket_metrics,
65
+ )
66
+ from chrys.service.analytics._session_projection import _lazy_session_projection, _SessionProjectionCache
67
+ from chrys.service.analytics._timeline import (
68
+ _exclusively_inactive,
69
+ _merge_projection_memberships,
70
+ _node_owner_membership,
71
+ _timeline_diagnostics,
72
+ _turn_start_membership,
73
+ )
74
+ from chrys.service.analytics._workflows import include_workflow_overview, workflow_runs
75
+ from chrys.service.analytics.findings import evaluate_findings
76
+ from chrys.service.analytics.model import (
77
+ FLOW_TERMINAL_INDEX,
78
+ ActionClass,
79
+ ActionOperation,
80
+ AnalysisAvailability,
81
+ Metric,
82
+ Precision,
83
+ SequenceRangeDiagnostic,
84
+ SessionCounterSamples,
85
+ SessionSpan,
86
+ SubmissionLatencyBucket,
87
+ SubmissionLatencyOverview,
88
+ SubmissionLatencySample,
89
+ SubmissionLatencyStats,
90
+ TimelineOperation,
91
+ TimelineOperationDiagnostic,
92
+ TimeSlice,
93
+ TokenUsage,
94
+ TrajectoryAnalysis,
95
+ TrajectoryDiagnostics,
96
+ TrajectoryOverview,
97
+ TurnAnalysis,
98
+ TurnAttemptRef,
99
+ TurnFlow,
100
+ UsageBucket,
101
+ WallBucket,
102
+ least_precision,
103
+ )
104
+ from chrys.service.analytics.reader import (
105
+ ScanCursor,
106
+ TrajectoryScanCancelled,
107
+ _PrefixHasher,
108
+ scan_open_trajectory_batch,
109
+ verify_prefix,
110
+ )
111
+ from chrys.service.analytics.reader import raise_if_cancelled as _check_cancelled
112
+ from chrys.service.trajectory.preparation import PreparationOutcome
113
+
114
+ MAX_RESIDENT_MEMORY_BYTES: Final = 200 * 1024 * 1024
115
+ """P0 acceptance ceiling for a 200 MiB input fixture."""
116
+
117
+ _PREFIX_REVERIFY_FLOOR_BYTES: Final = 1 << 20
118
+ """Smallest growth that triggers a full prefix replay between doublings."""
119
+
120
+ _PREFIX_PROBE_BYTES: Final = 1 << 12
121
+ """Consumed-suffix window reread before trusting growth as an append."""
122
+
123
+
124
+ def _read_prefix_probe(handle: BufferedReader, byte_offset: int) -> bytes:
125
+ length = min(byte_offset, _PREFIX_PROBE_BYTES)
126
+ handle.seek(byte_offset - length)
127
+ return handle.read(length)
128
+
129
+
130
+ def _read_installed_fingerprint_key(path: Path) -> bytes | None:
131
+ if (
132
+ len(path.parents) < 4
133
+ or path.name != "events.jsonl"
134
+ or path.parent.name != "trajectory"
135
+ or path.parents[2].name != "sessions"
136
+ ):
137
+ return None
138
+ from chrys.foundation.platform import get_platform
139
+
140
+ # The recorder keeps the key in the platform config directory. Beside the
141
+ # session root covers the default layout (the root IS the config
142
+ # directory) and trees copied along with their key; the config directory
143
+ # is the fallback for a custom session root, whose sessions live
144
+ # elsewhere while the key stays put.
145
+ candidates = [path.parents[3] / TRAJECTORY_KEY_FILE_NAME]
146
+ config_key_path = get_platform().config_dir / TRAJECTORY_KEY_FILE_NAME
147
+ if config_key_path not in candidates:
148
+ candidates.append(config_key_path)
149
+ for key_path in candidates:
150
+ try:
151
+ with secure_open_owner_only_binary(key_path) as handle:
152
+ key = handle.read(FINGERPRINT_KEY_BYTES + 1)
153
+ except OSError:
154
+ continue
155
+ if len(key) == FINGERPRINT_KEY_BYTES:
156
+ return key
157
+ return None
158
+
159
+
160
+ class TrajectoryAnalyzer:
161
+ """Path-identity cache with append-only live-tail scans and dirty-turn resolves."""
162
+
163
+ def __init__(
164
+ self,
165
+ *,
166
+ fingerprint_key: bytes | None = None,
167
+ verify_commands: str = DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
168
+ ) -> None:
169
+ self._path: Path | None = None
170
+ self._identity: tuple[int, int] | None = None
171
+ self._size = 0
172
+ self._mtime_ns = 0
173
+ self._prefix_digest = b""
174
+ self._prefix_hasher: _PrefixHasher | None = None
175
+ self._prefix_probe = b""
176
+ self._verified_offset = 0
177
+ self._cursor = ScanCursor()
178
+ self._intermediate: _Intermediate | None = None
179
+ self._resolution_cache = _turns._ResolutionCache()
180
+ self._analysis: TrajectoryAnalysis | None = None
181
+ self._generation = 0
182
+ self._fingerprint_key = fingerprint_key
183
+ self._verify_commands = verify_commands
184
+ self._session_projection_cache = _SessionProjectionCache()
185
+
186
+ def load(self, path: Path, *, cancel_event: Event | None = None) -> TrajectoryAnalysis:
187
+ """Perform the one initial physical scan and resolve after EOF."""
188
+ self._path = path
189
+ self._generation += 1
190
+ try:
191
+ handle = path.open("rb")
192
+ except FileNotFoundError:
193
+ self._reset_cache()
194
+ self._path = path
195
+ self._analysis = TrajectoryAnalysis(
196
+ availability=AnalysisAvailability.UNAVAILABLE,
197
+ path=path,
198
+ generation=self._generation,
199
+ )
200
+ return self._analysis
201
+ except OSError as exc:
202
+ return self._read_error(path, exc)
203
+ with handle:
204
+ return self._load_opened(path, handle, cancel_event=cancel_event)
205
+
206
+ def _load_opened(
207
+ self,
208
+ path: Path,
209
+ handle: BufferedReader,
210
+ *,
211
+ cancel_event: Event | None,
212
+ ) -> TrajectoryAnalysis:
213
+ intermediate = _Intermediate(path)
214
+ cursor = ScanCursor()
215
+ try:
216
+ batch = scan_open_trajectory_batch(
217
+ handle,
218
+ intermediate.consume,
219
+ cursor=cursor,
220
+ cancel_event=cancel_event,
221
+ )
222
+ except TrajectoryScanCancelled:
223
+ self._reset_cache()
224
+ raise
225
+ except OSError as exc:
226
+ return self._read_error(path, exc)
227
+ if cancel_event is not None and cancel_event.is_set():
228
+ self._reset_cache()
229
+ raise TrajectoryScanCancelled
230
+ intermediate.absorb_batch(batch)
231
+ stat = fstat(handle.fileno())
232
+ self._cursor = cursor
233
+ self._intermediate = intermediate
234
+ self._identity = (stat.st_dev, stat.st_ino)
235
+ self._size = cursor.byte_offset + batch.torn_tail_bytes
236
+ self._mtime_ns = stat.st_mtime_ns
237
+ self._prefix_digest = batch.prefix_digest
238
+ self._prefix_hasher = batch.prefix_hasher
239
+ self._prefix_probe = _read_prefix_probe(handle, cursor.byte_offset)
240
+ self._verified_offset = cursor.byte_offset
241
+ self._resolution_cache = _turns._ResolutionCache()
242
+ try:
243
+ self._analysis = _resolve(
244
+ intermediate,
245
+ generation=self._generation,
246
+ resolution_cache=self._resolution_cache,
247
+ cancel_event=cancel_event,
248
+ fingerprint_key=self._fingerprint_key or _read_installed_fingerprint_key(path),
249
+ verify_commands=self._verify_commands,
250
+ session_cache=self._session_projection_cache,
251
+ )
252
+ except TrajectoryScanCancelled:
253
+ self._reset_cache()
254
+ raise
255
+ intermediate.mark_resolved()
256
+ return self._analysis
257
+
258
+ def refresh(self, *, cancel_event: Event | None = None) -> TrajectoryAnalysis:
259
+ """Consume one append batch; replacement or truncation performs a fresh load."""
260
+ path = self._path
261
+ if path is None:
262
+ raise RuntimeError("TrajectoryAnalyzer.refresh() requires load() first")
263
+ try:
264
+ handle = path.open("rb")
265
+ except OSError:
266
+ return self.load(path, cancel_event=cancel_event)
267
+ with handle:
268
+ stat = fstat(handle.fileno())
269
+ identity = (stat.st_dev, stat.st_ino)
270
+ intermediate = self._intermediate
271
+ self._generation += 1
272
+ if intermediate is None or identity != self._identity or stat.st_size < self._cursor.byte_offset:
273
+ return self._load_opened(path, handle, cancel_event=cancel_event)
274
+ if stat.st_size == self._size:
275
+ # An unchanged log still goes stale when the companion
276
+ # session document moved under it: carriers, commands, and
277
+ # mutation detail all fold from that document.
278
+ if (
279
+ stat.st_mtime_ns == self._mtime_ns
280
+ and self._analysis is not None
281
+ and not self._session_projection_cache.changed(path)
282
+ ):
283
+ self._generation -= 1
284
+ return self._analysis
285
+ return self._load_opened(path, handle, cancel_event=cancel_event)
286
+ retained = self._prefix_hasher
287
+ appended_since_verify = self._cursor.byte_offset - self._verified_offset
288
+ # Replaying the whole consumed prefix on every append batch is
289
+ # quadratic against a log polled twice a second, so the retained
290
+ # digest state continues across batches and the replay re-verifies
291
+ # only once the log outgrows the last verified length again:
292
+ # amortized linear, still fail-closed at every doubling and load.
293
+ if retained is not None and appended_since_verify < max(
294
+ self._verified_offset, _PREFIX_REVERIFY_FLOOR_BYTES
295
+ ):
296
+ # Growth alone is not append provenance: a same-inode
297
+ # replacement also grows the file. Rereading the consumed
298
+ # suffix catches a replaced prefix at the boundary before any
299
+ # stale state is combined with the new bytes; a replacement
300
+ # that forges the probe window still fails the next replay.
301
+ if _read_prefix_probe(handle, self._cursor.byte_offset) != self._prefix_probe:
302
+ return self._load_opened(path, handle, cancel_event=cancel_event)
303
+ prefix_hasher = retained
304
+ else:
305
+ try:
306
+ prefix_hasher = verify_prefix(
307
+ handle,
308
+ length=self._cursor.byte_offset,
309
+ expected_digest=self._prefix_digest,
310
+ cancel_event=cancel_event,
311
+ )
312
+ except TrajectoryScanCancelled:
313
+ self._reset_cache()
314
+ raise
315
+ if prefix_hasher is None:
316
+ return self._load_opened(path, handle, cancel_event=cancel_event)
317
+ self._verified_offset = self._cursor.byte_offset
318
+ try:
319
+ batch = scan_open_trajectory_batch(
320
+ handle,
321
+ intermediate.consume,
322
+ cursor=self._cursor,
323
+ cancel_event=cancel_event,
324
+ prefix_hasher=prefix_hasher,
325
+ )
326
+ except TrajectoryScanCancelled:
327
+ self._reset_cache()
328
+ raise
329
+ except OSError as exc:
330
+ return self._read_error(path, exc)
331
+ if cancel_event is not None and cancel_event.is_set():
332
+ self._reset_cache()
333
+ raise TrajectoryScanCancelled
334
+ intermediate.absorb_batch(batch)
335
+ final_stat = fstat(handle.fileno())
336
+ self._size = self._cursor.byte_offset + batch.torn_tail_bytes
337
+ self._mtime_ns = final_stat.st_mtime_ns
338
+ self._prefix_digest = batch.prefix_digest
339
+ self._prefix_hasher = batch.prefix_hasher
340
+ self._prefix_probe = _read_prefix_probe(handle, self._cursor.byte_offset)
341
+ try:
342
+ self._analysis = _resolve(
343
+ intermediate,
344
+ generation=self._generation,
345
+ resolution_cache=self._resolution_cache,
346
+ previous=self._analysis,
347
+ cancel_event=cancel_event,
348
+ fingerprint_key=self._fingerprint_key or _read_installed_fingerprint_key(path),
349
+ verify_commands=self._verify_commands,
350
+ session_cache=self._session_projection_cache,
351
+ )
352
+ except TrajectoryScanCancelled:
353
+ self._reset_cache()
354
+ raise
355
+ intermediate.mark_resolved()
356
+ return self._analysis
357
+
358
+ def release(self) -> None:
359
+ """Release all generation-bound state owned by the analyzer."""
360
+ self._path = None
361
+ self._analysis = None
362
+ self._session_projection_cache = _SessionProjectionCache()
363
+ self._reset_cache()
364
+
365
+ def counter_samples(self) -> SessionCounterSamples:
366
+ """Project per-turn counter samples from the retained scan facts.
367
+
368
+ Computed transiently for export; retaining timestamped samples on every
369
+ TurnAnalysis would scale the resident footprint with session length.
370
+ """
371
+ intermediate = self._intermediate
372
+ if intermediate is None:
373
+ return SessionCounterSamples(usage_by_turn={}, context_by_turn={})
374
+ inactive_ranges = _closed_sequence_union(intermediate.rollback_ranges)
375
+ return SessionCounterSamples(
376
+ usage_by_turn=_turns._usage_samples_by_turn(intermediate, inactive_ranges),
377
+ context_by_turn=_turns._context_samples_by_turn(intermediate, inactive_ranges),
378
+ )
379
+
380
+ def _read_error(self, path: Path, exc: OSError) -> TrajectoryAnalysis:
381
+ self._reset_cache()
382
+ self._path = path
383
+ self._analysis = TrajectoryAnalysis(
384
+ availability=AnalysisAvailability.READ_ERROR,
385
+ path=path,
386
+ generation=self._generation,
387
+ read_error=str(exc),
388
+ )
389
+ return self._analysis
390
+
391
+ def _reset_cache(self) -> None:
392
+ self._identity = None
393
+ self._size = 0
394
+ self._mtime_ns = 0
395
+ self._prefix_digest = b""
396
+ # A scan abandoned mid-batch leaves the retained digest state partially
397
+ # updated, so every reset path must also drop it.
398
+ self._prefix_hasher = None
399
+ self._prefix_probe = b""
400
+ self._verified_offset = 0
401
+ self._cursor = ScanCursor()
402
+ self._intermediate = None
403
+ self._resolution_cache = _turns._ResolutionCache()
404
+
405
+
406
+ def analyze_trajectory(
407
+ path: Path,
408
+ *,
409
+ fingerprint_key: bytes | None = None,
410
+ verify_commands: str = DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
411
+ ) -> TrajectoryAnalysis:
412
+ """Analyze *path* without retaining a live-tail cache."""
413
+ return TrajectoryAnalyzer(fingerprint_key=fingerprint_key, verify_commands=verify_commands).load(path)
414
+
415
+
416
+ def _resolve(
417
+ intermediate: _Intermediate,
418
+ *,
419
+ generation: int,
420
+ resolution_cache: _turns._ResolutionCache,
421
+ previous: TrajectoryAnalysis | None = None,
422
+ cancel_event: Event | None = None,
423
+ fingerprint_key: bytes | None = None,
424
+ session_cache: _SessionProjectionCache | None = None,
425
+ verify_commands: str = DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
426
+ ) -> TrajectoryAnalysis:
427
+ _check_cancelled(cancel_event)
428
+ active_ranges = _closed_sequence_union(intermediate.rollback_ranges)
429
+ rollback_projection_unresolved = _rollback_projection_unresolved(intermediate, active_ranges)
430
+ session_integrity_reason = _session_integrity_reason(
431
+ intermediate,
432
+ rollback_projection_unresolved=rollback_projection_unresolved,
433
+ )
434
+ active_turn_ids: set[str] = set()
435
+ for turn_id, turn in intermediate.turns.items():
436
+ _check_cancelled(cancel_event)
437
+ if any(_active(event.sequence, active_ranges) for event in turn.starts):
438
+ active_turn_ids.add(turn_id)
439
+ revisions = _revision_memberships(
440
+ intermediate,
441
+ active_ranges,
442
+ fingerprint_key=fingerprint_key,
443
+ cancel_event=cancel_event,
444
+ )
445
+ session_projection = (
446
+ session_cache.lazy(intermediate.path, cancel_event=cancel_event)
447
+ if session_cache is not None
448
+ else _lazy_session_projection(intermediate.path, cancel_event=cancel_event)
449
+ )
450
+
451
+ def mapped_carrier(item_id: str) -> str | None:
452
+ return session_projection().carriers.get(item_id)
453
+
454
+ # A folded logical turn is not a valid cache entry for any one of its
455
+ # physical attempts. Re-resolve those attempts and fold them again; regular
456
+ # one-attempt turns retain the live-tail fast path.
457
+ previous_turns = (
458
+ {turn.turn_id: turn for turn in previous.turns if len(turn.attempts) == 1} if previous is not None else {}
459
+ )
460
+ session_carrier_turn_ids = {
461
+ turn_id
462
+ for turn_id in active_turn_ids
463
+ if _turns._turn_uses_session_carrier_fallback(
464
+ intermediate,
465
+ turn_id,
466
+ active_ranges,
467
+ cancel_event=cancel_event,
468
+ )
469
+ }
470
+ resolved_turns: list[TurnAnalysis] = []
471
+ for turn_id in active_turn_ids:
472
+ _check_cancelled(cancel_event)
473
+ if (
474
+ not intermediate.dirty.full
475
+ and turn_id not in intermediate.dirty.turn_ids
476
+ and turn_id not in session_carrier_turn_ids
477
+ and turn_id in previous_turns
478
+ ):
479
+ resolved_turns.append(previous_turns[turn_id])
480
+ else:
481
+ resolved_turns.append(
482
+ _turns._resolve_turn(
483
+ intermediate,
484
+ intermediate.turns[turn_id],
485
+ active_ranges,
486
+ resolution_cache=resolution_cache,
487
+ revisions=revisions,
488
+ mapped_carrier=mapped_carrier,
489
+ rollback_projection_unresolved=rollback_projection_unresolved,
490
+ refresh_counter_axis=intermediate.dirty.full or turn_id in intermediate.dirty.turn_ids,
491
+ cancel_event=cancel_event,
492
+ )
493
+ )
494
+ resolved_turns.sort(key=lambda turn: turn.start_sequence)
495
+ actions = _resolve_actions(
496
+ intermediate,
497
+ active_ranges,
498
+ resolved_turns,
499
+ session_projection=session_projection,
500
+ verify_commands=verify_commands,
501
+ cancel_event=cancel_event,
502
+ )
503
+ actions_by_turn: dict[str, list[ActionOperation]] = defaultdict(list)
504
+ for action in actions:
505
+ actions_by_turn[action.turn_id].append(action)
506
+ resolved_turns = [
507
+ replace(
508
+ turn,
509
+ action_counts=_action_count_metrics(
510
+ tuple(actions_by_turn.get(turn.turn_id, ())),
511
+ projection_precision=turn.action_projection_precision,
512
+ projection_reason=turn.action_projection_reason,
513
+ ),
514
+ )
515
+ for turn in resolved_turns
516
+ ]
517
+ change_verification = _resolve_change_verification(
518
+ intermediate,
519
+ active_ranges,
520
+ resolved_turns,
521
+ actions,
522
+ session_projection(),
523
+ rollback_projection_unresolved=rollback_projection_unresolved,
524
+ cancel_event=cancel_event,
525
+ )
526
+ if session_integrity_reason is not None:
527
+ change_verification = _degrade_change_verification(change_verification, session_integrity_reason)
528
+ retry_amplification, retry_evidence, retry_target = _retry_amplification(
529
+ intermediate,
530
+ active_ranges,
531
+ revisions,
532
+ cancel_event=cancel_event,
533
+ )
534
+ validation = _validation_metrics(
535
+ actions,
536
+ resolved_turns,
537
+ change_verification,
538
+ retry_amplification,
539
+ cancel_event=cancel_event,
540
+ )
541
+ if session_integrity_reason is not None:
542
+ validation = _degrade_validation_metrics(
543
+ validation,
544
+ session_integrity_reason,
545
+ session_integrity_cap=True,
546
+ )
547
+ context_carrying_load = _context_carrying_load(
548
+ intermediate,
549
+ active_ranges,
550
+ resolved_turns,
551
+ revisions,
552
+ session_projection(),
553
+ cancel_event=cancel_event,
554
+ )
555
+ findings = evaluate_findings(
556
+ actions=actions,
557
+ turns=resolved_turns,
558
+ validation=validation,
559
+ change_verification=change_verification,
560
+ retry_amplification_evidence=retry_evidence,
561
+ retry_target=retry_target,
562
+ context_carrying_load=context_carrying_load,
563
+ cancel_event=cancel_event,
564
+ )
565
+ span_mismatches, containment_violations = _timeline_diagnostics(
566
+ intermediate,
567
+ active_ranges,
568
+ cancel_event=cancel_event,
569
+ )
570
+ diagnostics = TrajectoryDiagnostics(
571
+ line_count=intermediate.line_count,
572
+ byte_count=intermediate.byte_count,
573
+ torn_tail_bytes=intermediate.torn_tail_bytes,
574
+ corrupt_line_count=len(intermediate.corrupt_after_sequences),
575
+ unsupported_event_count=intermediate.unsupported_event_count,
576
+ accounted_prefix_violations=tuple(violation.message for violation in intermediate.prefix_violations),
577
+ accounted_prefix_violation_details=tuple(
578
+ SequenceRangeDiagnostic(violation.message, violation.first_sequence, violation.last_sequence)
579
+ for violation in intermediate.prefix_violations
580
+ ),
581
+ explicit_gap_count=len(intermediate.explicit_gaps),
582
+ explicit_gaps=tuple(
583
+ SequenceRangeDiagnostic("trajectory gap", first, last) for first, last in intermediate.explicit_gaps
584
+ ),
585
+ rollback_projection_unresolved=rollback_projection_unresolved,
586
+ span_duration_mismatch_count=len(span_mismatches),
587
+ span_duration_mismatches=span_mismatches,
588
+ containment_violation_count=len(containment_violations),
589
+ containment_violations=containment_violations,
590
+ malformed_hook_execution_mode_count=intermediate.malformed_hook_execution_mode_count,
591
+ side_call_empty_shell_revisions=revisions.side_call_empty_shell_revisions,
592
+ unidentified_membership_revision_count=revisions.unidentified_membership_revision_count,
593
+ corrupt_lines=tuple(intermediate.corrupt_lines),
594
+ unsupported_lines=tuple(intermediate.unsupported_lines),
595
+ )
596
+ overview = _overview(resolved_turns, cancel_event=cancel_event)
597
+ token_usage = _token_usage(intermediate, active_ranges, resolved_turns, cancel_event=cancel_event)
598
+ skill_usage = _tool_usage_panel(
599
+ actions,
600
+ resolved_turns,
601
+ tool_kind="skill",
602
+ display_name=lambda action: session_projection().skill_names.get(action.call_item_id or ""),
603
+ )
604
+ mcp_usage = _tool_usage_panel(
605
+ actions,
606
+ resolved_turns,
607
+ tool_kind="mcp",
608
+ display_name=lambda action: action.tool_name,
609
+ )
610
+ logical_turns = _fold_retry_turns(resolved_turns, cancel_event=cancel_event)
611
+ diagnostics = replace(
612
+ diagnostics,
613
+ timeline_operations=_timeline_operation_diagnostics(logical_turns),
614
+ )
615
+ insights = _insights_analysis(
616
+ intermediate,
617
+ active_ranges,
618
+ resolved_turns,
619
+ logical_turns,
620
+ actions,
621
+ context_carrying_load=context_carrying_load,
622
+ cancel_event=cancel_event,
623
+ )
624
+ submission_latency = _submission_latency(intermediate, active_ranges, cancel_event=cancel_event)
625
+ if session_integrity_reason is not None:
626
+ overview = _degrade_overview(overview, session_integrity_reason)
627
+ token_usage = _degrade_token_usage(token_usage, session_integrity_reason)
628
+ skill_usage = _degrade_tool_usage_panel(skill_usage, session_integrity_reason)
629
+ mcp_usage = _degrade_tool_usage_panel(mcp_usage, session_integrity_reason)
630
+ insights = _degrade_insights(insights, session_integrity_reason)
631
+ submission_latency = _degrade_submission_latency(submission_latency, session_integrity_reason)
632
+ runs = workflow_runs(
633
+ intermediate, active_ranges, integrity_reason=session_integrity_reason, cancel_event=cancel_event
634
+ )
635
+ if runs:
636
+ overview = include_workflow_overview(overview, runs)
637
+ return TrajectoryAnalysis(
638
+ availability=AnalysisAvailability.AVAILABLE,
639
+ path=intermediate.path,
640
+ generation=generation,
641
+ overview=overview,
642
+ workflow_runs=runs,
643
+ turns=tuple(logical_turns),
644
+ diagnostics=diagnostics,
645
+ actions=actions,
646
+ validation=None if runs else validation,
647
+ findings=findings,
648
+ submission_latency=None if runs else submission_latency,
649
+ change_verification=None if runs else change_verification,
650
+ token_usage=None if runs else token_usage,
651
+ skill_usage=None if runs else skill_usage,
652
+ mcp_usage=None if runs else mcp_usage,
653
+ insights=None if runs else insights,
654
+ session_span=SessionSpan(
655
+ first_turn_started_at=intermediate.first_turn_started_at,
656
+ last_turn_finished_at=intermediate.last_turn_finished_at,
657
+ runtime_count=len(intermediate.runtime_starts),
658
+ ),
659
+ )
660
+
661
+
662
+ def _session_integrity_reason(
663
+ intermediate: _Intermediate,
664
+ *,
665
+ rollback_projection_unresolved: bool,
666
+ ) -> str | None:
667
+ """Explain physical damage or an unresolvable live-history projection."""
668
+
669
+ problems: list[str] = []
670
+ if intermediate.line_count == 0 and intermediate.byte_count == 0 and intermediate.torn_tail_bytes == 0:
671
+ problems.append("empty log")
672
+ if intermediate.corrupt_after_sequences:
673
+ problems.append("corrupt lines")
674
+ if intermediate.unsupported_event_count:
675
+ problems.append("unsupported events")
676
+ if intermediate.prefix_violations:
677
+ problems.append("accounted-prefix violations")
678
+ if intermediate.explicit_gaps:
679
+ problems.append("explicit gaps")
680
+ if intermediate.torn_tail_bytes:
681
+ problems.append("torn tail")
682
+ if rollback_projection_unresolved:
683
+ problems.append("unresolved rollback projection")
684
+ if not problems:
685
+ return None
686
+ return f"session trajectory integrity is unresolved: {', '.join(problems)}"
687
+
688
+
689
+ def _rollback_projection_unresolved(
690
+ intermediate: _Intermediate,
691
+ inactive_ranges: tuple[tuple[int, int], ...],
692
+ ) -> bool:
693
+ if any(_active(sequence, inactive_ranges) for sequence in intermediate.rollback_errors):
694
+ return True
695
+ rollback_pairs = {
696
+ (old_branch, new_branch)
697
+ for sequence, old_branch, new_branch in intermediate.rollback_branch_pairs
698
+ if _active(sequence, inactive_ranges)
699
+ }
700
+ return any(
701
+ _active(sequence, inactive_ranges)
702
+ and (old_branch is None or new_branch is None or (old_branch, new_branch) not in rollback_pairs)
703
+ for sequence, old_branch, new_branch in intermediate.branch_supersessions
704
+ )
705
+
706
+
707
+ def _timeline_operation_diagnostics(
708
+ turns: list[TurnAnalysis],
709
+ ) -> tuple[TimelineOperationDiagnostic, ...]:
710
+ """Project sparse row diagnostics without losing physical retry ownership."""
711
+ diagnostics: list[TimelineOperationDiagnostic] = []
712
+ for turn in turns:
713
+ for attempt in turn.attempts:
714
+ for operation in turn.operations[attempt.operation_start_index : attempt.operation_end_index]:
715
+ code = operation.diagnostic_code
716
+ reason = operation.reason
717
+ if code is None or reason is None:
718
+ continue
719
+ diagnostics.append(
720
+ TimelineOperationDiagnostic(
721
+ turn_id=attempt.turn_id,
722
+ turn_number=turn.turn_number,
723
+ operation_id=operation.operation_id,
724
+ family=operation.family,
725
+ precision=operation.precision,
726
+ code=code,
727
+ reason=reason,
728
+ identity=operation.identity,
729
+ hook_id=operation.hook_id,
730
+ )
731
+ )
732
+ return tuple(diagnostics)
733
+
734
+
735
+ def _overview(
736
+ turns: list[TurnAnalysis],
737
+ *,
738
+ cancel_event: Event | None = None,
739
+ metric_subject: str = "selected turns",
740
+ ) -> TrajectoryOverview:
741
+ for _turn in turns:
742
+ _check_cancelled(cancel_event)
743
+ elapsed = _sum_metrics([turn.elapsed_ns for turn in turns], metric_subject=metric_subject)
744
+ compute_cp = _sum_metrics([turn.compute_cp_ns for turn in turns], metric_subject=metric_subject)
745
+ response_cp = _sum_metrics([turn.response_cp_ns for turn in turns], metric_subject=metric_subject)
746
+ exclusive = _sum_metrics([turn.exclusive_work_ns for turn in turns], metric_subject=metric_subject)
747
+ overlap = _sum_metrics([turn.overlap_gain_ns for turn in turns], metric_subject=metric_subject)
748
+ usage = _sum_metrics(
749
+ [turn.usage_tokens for turn in turns],
750
+ missing_is_missing=True,
751
+ metric_subject=metric_subject,
752
+ )
753
+ ratio_precision = (
754
+ Precision.EXACT
755
+ if elapsed.precision is Precision.EXACT and exclusive.precision is Precision.EXACT
756
+ else Precision.UNRESOLVED
757
+ )
758
+ elapsed_value = int(elapsed.value) if elapsed.value is not None else None
759
+ exclusive_value = int(exclusive.value) if exclusive.value is not None else None
760
+ wall = {
761
+ bucket: _sum_metrics(
762
+ [turn.wall_time_ns[bucket] for turn in turns],
763
+ metric_subject=metric_subject,
764
+ )
765
+ for bucket in WallBucket
766
+ }
767
+ utilization: dict[WallBucket, Metric] = {}
768
+ for bucket in (WallBucket.MODEL, WallBucket.TOOLS):
769
+ work_ns = 0
770
+ for turn in turns:
771
+ _check_cancelled(cancel_event)
772
+ work_ns += sum(
773
+ item.duration_ns for item in turn.slices if item.counts_as_work and item.wall_bucket is bucket
774
+ )
775
+ utilization[bucket] = Metric(
776
+ work_ns / elapsed_value
777
+ if elapsed_value is not None and elapsed_value > 0
778
+ else 0.0
779
+ if elapsed_value == 0
780
+ else None,
781
+ ratio_precision,
782
+ None if ratio_precision is Precision.EXACT else f"one or more {metric_subject} are unresolved",
783
+ )
784
+ return TrajectoryOverview(
785
+ elapsed_ns=elapsed,
786
+ compute_cp_ns=compute_cp,
787
+ response_cp_ns=response_cp,
788
+ exclusive_work_ns=exclusive,
789
+ parallelism=Metric(
790
+ exclusive_value / elapsed_value
791
+ if elapsed_value is not None and elapsed_value > 0 and exclusive_value is not None
792
+ else 0.0
793
+ if elapsed_value == 0 and exclusive_value is not None
794
+ else None,
795
+ ratio_precision,
796
+ None if ratio_precision is Precision.EXACT else f"one or more {metric_subject} are unresolved",
797
+ ),
798
+ overlap_gain_ns=overlap,
799
+ wall_time_ns=wall,
800
+ utilization=utilization,
801
+ usage_tokens=usage,
802
+ )
803
+
804
+
805
+ def _token_usage(
806
+ intermediate: _Intermediate,
807
+ inactive_ranges: tuple[tuple[int, int], ...],
808
+ turns: list[TurnAnalysis],
809
+ *,
810
+ cancel_event: Event | None,
811
+ ) -> TokenUsage:
812
+ per_turn: dict[UsageBucket, list[Metric]] = {bucket: [] for bucket in UsageBucket}
813
+ for turn in turns:
814
+ _check_cancelled(cancel_event)
815
+ usage = turn.token_usage
816
+ if usage is None:
817
+ usage = _turns._turn_token_usage(intermediate, turn.turn_id, inactive_ranges, turn.usage_tokens)
818
+ for bucket in UsageBucket:
819
+ per_turn[bucket].append(usage.buckets[bucket])
820
+ buckets = {
821
+ UsageBucket.INPUT: _sum_metrics(per_turn[UsageBucket.INPUT], missing_is_missing=True),
822
+ UsageBucket.OUTPUT: _sum_metrics(per_turn[UsageBucket.OUTPUT], missing_is_missing=True),
823
+ }
824
+ for bucket in _turns._OPTIONAL_USAGE_BUCKETS:
825
+ buckets[bucket] = _sum_optional_bucket_metrics(per_turn[bucket])
826
+ return TokenUsage(buckets=buckets)
827
+
828
+
829
+ def _fold_retry_turns(turns: list[TurnAnalysis], *, cancel_event: Event | None = None) -> list[TurnAnalysis]:
830
+ """Fold consecutive retry/resume attempts into their one user-level turn.
831
+
832
+ Producers keep a fresh physical ``turn_id`` per pass so every lifecycle can
833
+ still be validated independently. ``turn_number`` is the user-level
834
+ ordinal, and ``is_retry`` explicitly says that a later pass continues the
835
+ preceding ordinal rather than opening another turn.
836
+ """
837
+ groups: list[list[TurnAnalysis]] = []
838
+ for turn in turns:
839
+ _check_cancelled(cancel_event)
840
+ if (
841
+ turn.attempts[0].is_retry
842
+ and groups
843
+ and turn.turn_number is not None
844
+ and turn.turn_number == groups[-1][0].turn_number
845
+ ):
846
+ groups[-1].append(turn)
847
+ else:
848
+ groups.append([turn])
849
+ return [_fold_turn_group(group, cancel_event=cancel_event) for group in groups]
850
+
851
+
852
+ def _fold_turn_group(attempts: list[TurnAnalysis], *, cancel_event: Event | None) -> TurnAnalysis:
853
+ canonical = attempts[0]
854
+ if len(attempts) == 1:
855
+ attempt = attempts[0]
856
+ source = attempt.attempts[0]
857
+ diagnostics = list(attempt.diagnostics)
858
+ if source.physical_axis_end_ns < source.physical_axis_start_ns:
859
+ diagnostics.append("folded attempt axis end precedes its start")
860
+ normalized = replace(
861
+ source,
862
+ logical_axis_start_ns=attempt.axis_start_ns,
863
+ operation_start_index=0,
864
+ operation_end_index=len(attempt.operations),
865
+ slice_start_index=0,
866
+ slice_end_index=len(attempt.slices),
867
+ )
868
+ return replace(
869
+ attempt,
870
+ axis_end_ns=attempt.axis_start_ns + normalized.duration_ns,
871
+ diagnostics=tuple(dict.fromkeys(diagnostics)),
872
+ attempts=(normalized,),
873
+ )
874
+
875
+ overview = _overview(
876
+ attempts,
877
+ cancel_event=cancel_event,
878
+ metric_subject="attempts of this turn",
879
+ )
880
+ axis_start = canonical.axis_start_ns
881
+ cursor = axis_start
882
+ operations: list[TimelineOperation] = []
883
+ slices: list[TimeSlice] = []
884
+ attempt_refs: list[TurnAttemptRef] = []
885
+ fold_diagnostics: list[str] = []
886
+ parent_pairs = array("I")
887
+ causal_pairs = array("I")
888
+ root_index: int | None = None
889
+ flows_exact = all(attempt.flow is not None for attempt in attempts)
890
+ if len({attempt.runtime_id for attempt in attempts}) > 1:
891
+ fold_diagnostics.append("logical turn spans multiple trajectory runtimes")
892
+ for attempt_index, attempt in enumerate(attempts):
893
+ _check_cancelled(cancel_event)
894
+ source = attempt.attempts[0]
895
+ if len(attempt.attempts) != 1:
896
+ fold_diagnostics.append("fold input already contains multiple physical attempts")
897
+ if source.physical_axis_end_ns < source.physical_axis_start_ns:
898
+ fold_diagnostics.append("folded attempt axis end precedes its start")
899
+ shift = cursor - attempt.axis_start_ns
900
+ operation_offset = len(operations)
901
+ operations.extend(
902
+ replace(
903
+ operation,
904
+ start_ns=operation.start_ns + shift if operation.start_ns is not None else None,
905
+ end_ns=operation.end_ns + shift if operation.end_ns is not None else None,
906
+ )
907
+ for operation in attempt.operations
908
+ )
909
+ operation_end = len(operations)
910
+ slice_offset = len(slices)
911
+ for item in attempt.slices:
912
+ slices.append(
913
+ replace(
914
+ item,
915
+ slice_index=len(slices),
916
+ start_ns=item.start_ns + shift,
917
+ end_ns=item.end_ns + shift,
918
+ )
919
+ )
920
+ attempt_refs.append(
921
+ TurnAttemptRef(
922
+ turn_id=source.turn_id,
923
+ runtime_id=source.runtime_id,
924
+ is_retry=source.is_retry,
925
+ physical_axis_start_ns=source.physical_axis_start_ns,
926
+ physical_axis_end_ns=source.physical_axis_end_ns,
927
+ logical_axis_start_ns=cursor,
928
+ operation_start_index=operation_offset,
929
+ operation_end_index=operation_end,
930
+ slice_start_index=slice_offset,
931
+ slice_end_index=len(slices),
932
+ )
933
+ )
934
+ flow = attempt.flow
935
+ if flow is not None:
936
+ if root_index is None and flow.root_index is not None:
937
+ root_index = flow.root_index + operation_offset
938
+ # A cancelled/interrupted physical attempt may still have a typed
939
+ # response terminal. Only the final attempt owns the response of
940
+ # the stitched logical turn, so earlier terminal edges stop here.
941
+ for edge_source, target in flow.parent_edges():
942
+ if target == FLOW_TERMINAL_INDEX and attempt_index < len(attempts) - 1:
943
+ continue
944
+ # FLOW_TERMINAL_INDEX is producer-defined as a target-only
945
+ # response sentinel, so every source is a real operation index.
946
+ parent_pairs.extend(
947
+ (
948
+ edge_source + operation_offset,
949
+ FLOW_TERMINAL_INDEX if target == FLOW_TERMINAL_INDEX else target + operation_offset,
950
+ )
951
+ )
952
+ for edge_source, target in flow.causal_edges():
953
+ if target == FLOW_TERMINAL_INDEX and attempt_index < len(attempts) - 1:
954
+ continue
955
+ causal_pairs.extend(
956
+ (
957
+ edge_source + operation_offset,
958
+ FLOW_TERMINAL_INDEX if target == FLOW_TERMINAL_INDEX else target + operation_offset,
959
+ )
960
+ )
961
+ cursor += attempt_refs[-1].duration_ns
962
+
963
+ action_counts = {
964
+ action_class: _sum_metrics(
965
+ [attempt.action_counts.get(action_class, Metric(0, Precision.EXACT)) for attempt in attempts],
966
+ metric_subject="attempts of this turn",
967
+ )
968
+ for action_class in ActionClass
969
+ }
970
+ action_precision = least_precision(attempt.action_projection_precision for attempt in attempts)
971
+ action_reason = next(
972
+ (
973
+ attempt.action_projection_reason
974
+ for attempt in attempts
975
+ if attempt.action_projection_precision is not Precision.EXACT and attempt.action_projection_reason
976
+ ),
977
+ None,
978
+ )
979
+ token_usages = [attempt.token_usage for attempt in attempts]
980
+ token_usage = (
981
+ _merge_turn_token_usage([usage for usage in token_usages if usage is not None])
982
+ if all(usage is not None for usage in token_usages)
983
+ else None
984
+ )
985
+ flow = (
986
+ TurnFlow(
987
+ turn_id=canonical.turn_id,
988
+ root_index=root_index,
989
+ has_terminal=bool(attempts[-1].flow and attempts[-1].flow.has_terminal),
990
+ parent_pairs=parent_pairs.tobytes(),
991
+ causal_pairs=causal_pairs.tobytes(),
992
+ acyclic=all(attempt.flow is not None and attempt.flow.acyclic for attempt in attempts),
993
+ )
994
+ if flows_exact
995
+ else None
996
+ )
997
+ return replace(
998
+ canonical,
999
+ end_sequence=attempts[-1].end_sequence,
1000
+ elapsed_ns=overview.elapsed_ns,
1001
+ compute_cp_ns=overview.compute_cp_ns,
1002
+ response_cp_ns=overview.response_cp_ns,
1003
+ exclusive_work_ns=overview.exclusive_work_ns,
1004
+ parallelism=overview.parallelism,
1005
+ overlap_gain_ns=overview.overlap_gain_ns,
1006
+ wall_time_ns=overview.wall_time_ns,
1007
+ utilization=overview.utilization,
1008
+ usage_tokens=overview.usage_tokens,
1009
+ axis_end_ns=cursor,
1010
+ operations=tuple(operations),
1011
+ slices=tuple(slices),
1012
+ diagnostics=tuple(
1013
+ dict.fromkeys(
1014
+ (
1015
+ *(diagnostic for attempt in attempts for diagnostic in attempt.diagnostics),
1016
+ *fold_diagnostics,
1017
+ )
1018
+ )
1019
+ ),
1020
+ action_counts=action_counts,
1021
+ critical_tool_contributions_ns=dict(
1022
+ sum((Counter(attempt.critical_tool_contributions_ns) for attempt in attempts), Counter())
1023
+ ),
1024
+ server_critical_contributions_ns=dict(
1025
+ sum((Counter(attempt.server_critical_contributions_ns) for attempt in attempts), Counter())
1026
+ ),
1027
+ action_projection_precision=action_precision,
1028
+ action_projection_reason=action_reason,
1029
+ token_usage=token_usage,
1030
+ flow=flow,
1031
+ attempts=tuple(attempt_refs),
1032
+ )
1033
+
1034
+
1035
+ def _merge_turn_token_usage(usages: list[TokenUsage]) -> TokenUsage:
1036
+ per_bucket = {bucket: [usage.buckets[bucket] for usage in usages] for bucket in UsageBucket}
1037
+ buckets = {
1038
+ UsageBucket.INPUT: _sum_metrics(
1039
+ per_bucket[UsageBucket.INPUT],
1040
+ missing_is_missing=True,
1041
+ metric_subject="attempts of this turn",
1042
+ ),
1043
+ UsageBucket.OUTPUT: _sum_metrics(
1044
+ per_bucket[UsageBucket.OUTPUT],
1045
+ missing_is_missing=True,
1046
+ metric_subject="attempts of this turn",
1047
+ ),
1048
+ }
1049
+ for bucket in _turns._OPTIONAL_USAGE_BUCKETS:
1050
+ buckets[bucket] = _sum_optional_bucket_metrics(
1051
+ per_bucket[bucket],
1052
+ metric_subject="attempts of this turn",
1053
+ )
1054
+ return TokenUsage(buckets=buckets)
1055
+
1056
+
1057
+ def _retry_amplification(
1058
+ intermediate: _Intermediate,
1059
+ inactive_ranges: tuple[tuple[int, int], ...],
1060
+ revisions: _RevisionResolution,
1061
+ *,
1062
+ cancel_event: Event | None,
1063
+ ) -> tuple[Metric, tuple[str, ...], tuple[str | None, str | None, str | None]]:
1064
+ attribution_exact = True
1065
+ retry_ids: set[str] = set()
1066
+ for node in intermediate.nodes.get("retry", {}).values():
1067
+ _check_cancelled(cancel_event)
1068
+ if _all_side_call_lifecycle(node):
1069
+ continue
1070
+ cut = _lifecycle_cut(node, inactive_ranges)
1071
+ if cut is not _LifecycleCut.NONE:
1072
+ ownership = _node_owner_membership(
1073
+ intermediate,
1074
+ node,
1075
+ inactive_ranges,
1076
+ target_operation_id=node.starts[0].parent_operation_id,
1077
+ )
1078
+ if not _exclusively_inactive(ownership):
1079
+ attribution_exact = False
1080
+ continue
1081
+ starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
1082
+ finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
1083
+ endpoints = (*starts, *finishes)
1084
+ if not endpoints or all(event.side_call for event in endpoints):
1085
+ continue
1086
+ if any(event.side_call for event in endpoints):
1087
+ attribution_exact = False
1088
+ continue
1089
+ if len(starts) == 1 and not finishes:
1090
+ # A scheduled retry whose backoff never starts adds no retry usage.
1091
+ continue
1092
+ if (
1093
+ len(starts) != 1
1094
+ or len(finishes) != 1
1095
+ or starts[0].scope != finishes[0].scope
1096
+ or finishes[0].sequence <= starts[0].sequence
1097
+ ):
1098
+ attribution_exact = False
1099
+ continue
1100
+ retry_ids.add(node.operation_id)
1101
+ if not retry_ids:
1102
+ metric = (
1103
+ Metric(0, Precision.EXACT)
1104
+ if attribution_exact
1105
+ else Metric(None, Precision.UNRESOLVED, "retry lifecycle attribution is incomplete")
1106
+ )
1107
+ return metric, (), (None, None, None)
1108
+ parent_by_operation: dict[str, str | None] = {}
1109
+ for family in ("model.run", "model.cycle", "model.exchange"):
1110
+ for node in intermediate.nodes.get(family, {}).values():
1111
+ _check_cancelled(cancel_event)
1112
+ if _all_side_call_lifecycle(node):
1113
+ continue
1114
+ cut = _lifecycle_cut(node, inactive_ranges)
1115
+ if cut is not _LifecycleCut.NONE:
1116
+ ownership = _node_owner_membership(
1117
+ intermediate,
1118
+ node,
1119
+ inactive_ranges,
1120
+ target_operation_id=node.starts[0].parent_operation_id,
1121
+ )
1122
+ if not _exclusively_inactive(ownership):
1123
+ attribution_exact = False
1124
+ continue
1125
+ starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
1126
+ finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
1127
+ endpoints = (*starts, *finishes)
1128
+ if not endpoints or all(event.side_call for event in endpoints):
1129
+ continue
1130
+ if any(event.side_call for event in endpoints) or len(starts) != 1:
1131
+ attribution_exact = False
1132
+ else:
1133
+ parent_by_operation[node.operation_id] = starts[0].parent_operation_id
1134
+
1135
+ def retried(operation_id: str | None) -> bool:
1136
+ current = operation_id
1137
+ visited: set[str] = set()
1138
+ while current is not None and current not in visited:
1139
+ _check_cancelled(cancel_event)
1140
+ if current in retry_ids:
1141
+ return True
1142
+ visited.add(current)
1143
+ current = parent_by_operation.get(current)
1144
+ return False
1145
+
1146
+ retried_exchanges: dict[int, tuple[str, _Endpoint]] = {}
1147
+ for node in intermediate.nodes.get("model.exchange", {}).values():
1148
+ _check_cancelled(cancel_event)
1149
+ if _all_side_call_lifecycle(node):
1150
+ continue
1151
+ if not retried(node.operation_id):
1152
+ continue
1153
+ cut = _lifecycle_cut(node, inactive_ranges)
1154
+ if cut is not _LifecycleCut.NONE:
1155
+ ownership = _node_owner_membership(
1156
+ intermediate,
1157
+ node,
1158
+ inactive_ranges,
1159
+ target_operation_id=node.starts[0].parent_operation_id,
1160
+ )
1161
+ if not _exclusively_inactive(ownership):
1162
+ attribution_exact = False
1163
+ continue
1164
+ starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
1165
+ finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
1166
+ endpoints = (*starts, *finishes)
1167
+ if endpoints and all(event.side_call for event in endpoints):
1168
+ continue
1169
+ if (
1170
+ any(event.side_call for event in endpoints)
1171
+ or len(starts) != 1
1172
+ or len(finishes) != 1
1173
+ or starts[0].scope != finishes[0].scope
1174
+ or finishes[0].sequence <= starts[0].sequence
1175
+ ):
1176
+ attribution_exact = False
1177
+ else:
1178
+ retried_exchanges[finishes[0].sequence] = (node.operation_id, starts[0])
1179
+ amplified = [
1180
+ usage
1181
+ for usages in intermediate.usage_by_turn.values()
1182
+ for usage in usages
1183
+ if _active(usage.sequence, inactive_ranges) and usage.sequence in retried_exchanges
1184
+ ]
1185
+ if not amplified:
1186
+ metric = (
1187
+ Metric(0, Precision.EXACT)
1188
+ if attribution_exact
1189
+ else Metric(None, Precision.UNRESOLVED, "retry exchange attribution is incomplete")
1190
+ )
1191
+ return metric, (), (None, None, None)
1192
+ missing = any(
1193
+ usage.normalization_unavailable or usage.input_total is None or usage.output_total is None
1194
+ for usage in amplified
1195
+ )
1196
+ value = sum((usage.input_total or 0) + (usage.output_total or 0) for usage in amplified)
1197
+ provenance_exact = all(
1198
+ usage.bucket_has_provider_provenance(UsageBucket.INPUT)
1199
+ and usage.bucket_has_provider_provenance(UsageBucket.OUTPUT)
1200
+ for usage in amplified
1201
+ )
1202
+ precision = (
1203
+ Precision.MISSING
1204
+ if missing
1205
+ else Precision.EXACT
1206
+ if provenance_exact and attribution_exact
1207
+ else Precision.UNRESOLVED
1208
+ )
1209
+ reason = (
1210
+ "normalized usage is missing for one or more retried exchanges"
1211
+ if missing
1212
+ else None
1213
+ if provenance_exact and attribution_exact
1214
+ else "retry exchange attribution is incomplete"
1215
+ if not attribution_exact
1216
+ else "normalized retry usage provenance is incomplete"
1217
+ )
1218
+ stable_items: set[str] = set()
1219
+ target: tuple[str | None, str | None, str | None] = (None, None, None)
1220
+ for usage in amplified:
1221
+ _check_cancelled(cancel_event)
1222
+ operation_id, start = retried_exchanges[usage.sequence]
1223
+ revision_id = _payload_str(start.payload, "context_revision_id")
1224
+ stable_items.update(revisions.memberships.get(revision_id or "", ()))
1225
+ if target == (None, None, None):
1226
+ target = (start.turn_id, operation_id, str(usage.sequence))
1227
+ return Metric(value if not missing else None, precision, reason), tuple(sorted(stable_items)), target
1228
+
1229
+
1230
+ def _all_side_call_lifecycle(node: _Node) -> bool:
1231
+ """Whether every raw endpoint belongs to a non-main actor."""
1232
+ endpoints = (*node.starts, *node.finishes)
1233
+ return bool(endpoints) and all(endpoint.side_call for endpoint in endpoints)
1234
+
1235
+
1236
+ # The vocabulary this build's writer can emit; an outcome outside it is
1237
+ # damaged or from a different writer and cannot pass for read evidence.
1238
+ _RECOGNIZED_PREPARATION_OUTCOMES: Final = frozenset(
1239
+ value for name, value in vars(PreparationOutcome).items() if not name.startswith("_") and isinstance(value, str)
1240
+ )
1241
+ _PROMOTED_PREPARATION_OUTCOMES: Final = frozenset({PreparationOutcome.FRESH_TURN, PreparationOutcome.RETRY_TURN})
1242
+
1243
+
1244
+ def _submission_latency(
1245
+ intermediate: _Intermediate,
1246
+ inactive_ranges: tuple[tuple[int, int], ...],
1247
+ *,
1248
+ cancel_event: Event | None,
1249
+ ) -> SubmissionLatencyOverview:
1250
+ turn_by_scope: dict[str, list[_Endpoint]] = defaultdict(list)
1251
+ turn_scope_membership: dict[str, tuple[bool, bool]] = {}
1252
+ for turn in intermediate.turns.values():
1253
+ _check_cancelled(cancel_event)
1254
+ for start in turn.starts:
1255
+ scope_id = _payload_str(start.payload, "preparation_scope_operation_id")
1256
+ if scope_id is None:
1257
+ continue
1258
+ active = _active(start.sequence, inactive_ranges)
1259
+ turn_scope_membership[scope_id] = _merge_projection_memberships(
1260
+ turn_scope_membership.get(scope_id, (False, False)),
1261
+ (active, not active),
1262
+ )
1263
+ if active:
1264
+ turn_by_scope[scope_id].append(start)
1265
+ samples: list[SubmissionLatencySample] = []
1266
+ for node in intermediate.nodes.get("preparation", {}).values():
1267
+ _check_cancelled(cancel_event)
1268
+ starts = [event for event in node.starts if _active(event.sequence, inactive_ranges)]
1269
+ finishes = [event for event in node.finishes if _active(event.sequence, inactive_ranges)]
1270
+ endpoints = sorted((*starts, *finishes), key=lambda endpoint: endpoint.sequence)
1271
+ if not endpoints or _payload_str(endpoints[0].payload, "scope") != "pre_turn":
1272
+ continue
1273
+ if all(endpoint.side_call for endpoint in endpoints):
1274
+ continue
1275
+ if _drop_submission_lifecycle_cut(intermediate, node, inactive_ranges, turn_scope_membership):
1276
+ # The raw lifecycle is complete, but rollback cut its projection
1277
+ # away from every active owner. Its surviving endpoint is not a
1278
+ # live submission sample; ownership ambiguity remains diagnosable.
1279
+ continue
1280
+ start = starts[0] if len(starts) == 1 else None
1281
+ finish = finishes[0] if len(finishes) == 1 else None
1282
+ outcome = _payload_str(finish.payload, "outcome") if finish is not None else None
1283
+ bucket = _submission_bucket(outcome)
1284
+ turn_candidates = turn_by_scope.get(node.operation_id, ())
1285
+ if outcome in {"fresh_turn", "retry_turn"}:
1286
+ end = turn_candidates[0] if len(turn_candidates) == 1 else None
1287
+ turn_id = (
1288
+ end.turn_id if end is not None else _payload_str(finish.payload, "target_turn_id") if finish else None
1289
+ )
1290
+ else:
1291
+ end = finish
1292
+ turn_id = _payload_str(finish.payload, "target_turn_id") if finish is not None else None
1293
+ exact = (
1294
+ start is not None
1295
+ and finish is not None
1296
+ and end is not None
1297
+ and outcome in _RECOGNIZED_PREPARATION_OUTCOMES
1298
+ and not start.side_call
1299
+ and finish.monotonic_measurement
1300
+ and finish.scope == start.scope
1301
+ and finish.sequence > start.sequence
1302
+ and finish.monotonic_ns >= start.monotonic_ns
1303
+ and end.runtime_id == start.runtime_id
1304
+ and end.branch_id == start.branch_id
1305
+ and end.coverage_id == start.coverage_id
1306
+ and end.actor_id == start.actor_id
1307
+ and not end.side_call
1308
+ and end.sequence > start.sequence
1309
+ and end.monotonic_ns >= start.monotonic_ns
1310
+ )
1311
+ reason = None
1312
+ if len(starts) != 1:
1313
+ reason = "preparation scope does not have exactly one start"
1314
+ elif len(finishes) != 1:
1315
+ reason = "preparation scope does not have exactly one finished event"
1316
+ elif outcome is None:
1317
+ reason = "preparation terminal outcome is missing"
1318
+ elif outcome not in _RECOGNIZED_PREPARATION_OUTCOMES:
1319
+ reason = "preparation terminal outcome is unrecognized"
1320
+ elif end is None:
1321
+ reason = "promoted turn start cannot be resolved uniquely"
1322
+ elif start is not None and start.side_call:
1323
+ reason = "preparation scope belongs to a side-call actor"
1324
+ elif finish is not None and not finish.monotonic_measurement:
1325
+ reason = "preparation duration lacks monotonic provenance"
1326
+ elif start is not None and finish is not None and finish.scope != start.scope:
1327
+ reason = "preparation endpoints cross scope"
1328
+ elif (
1329
+ start is not None
1330
+ and finish is not None
1331
+ and (finish.sequence <= start.sequence or finish.monotonic_ns < start.monotonic_ns)
1332
+ ):
1333
+ reason = "preparation lifecycle endpoints are not ordered"
1334
+ elif (
1335
+ start is not None
1336
+ and end is not None
1337
+ and (
1338
+ end.runtime_id != start.runtime_id
1339
+ or end.branch_id != start.branch_id
1340
+ or end.coverage_id != start.coverage_id
1341
+ or end.actor_id != start.actor_id
1342
+ or end.side_call
1343
+ )
1344
+ ):
1345
+ reason = "submission endpoints cross scope"
1346
+ elif end is not None and start is not None and end.sequence <= start.sequence:
1347
+ reason = "submission endpoints are not ordered"
1348
+ elif end is not None and start is not None and end.monotonic_ns < start.monotonic_ns:
1349
+ reason = "submission latency has a negative endpoint interval"
1350
+ start_ns = start.monotonic_ns if start is not None else None
1351
+ raw_end_ns = end.monotonic_ns if end is not None else None
1352
+ end_ns = raw_end_ns if start_ns is None or raw_end_ns is None or raw_end_ns >= start_ns else None
1353
+ samples.append(
1354
+ SubmissionLatencySample(
1355
+ scope_operation_id=node.operation_id,
1356
+ outcome=outcome,
1357
+ bucket=bucket,
1358
+ start_sequence=start.sequence if start is not None else endpoints[0].sequence,
1359
+ start_ns=start_ns,
1360
+ end_ns=end_ns,
1361
+ duration_ns=Metric(
1362
+ end_ns - start_ns if exact and end_ns is not None and start_ns is not None else None,
1363
+ Precision.EXACT if exact else Precision.UNRESOLVED,
1364
+ reason,
1365
+ ),
1366
+ turn_id=turn_id,
1367
+ finished_count=len(finishes),
1368
+ )
1369
+ )
1370
+ ordered = tuple(sorted(samples, key=lambda sample: sample.start_sequence))
1371
+ stats: list[SubmissionLatencyStats] = []
1372
+ for bucket in SubmissionLatencyBucket:
1373
+ _check_cancelled(cancel_event)
1374
+ bucket_samples = tuple(sample for sample in ordered if sample.bucket is bucket)
1375
+ resolved = [
1376
+ cast("int", sample.duration_ns.value)
1377
+ for sample in bucket_samples
1378
+ if sample.duration_ns.precision is Precision.EXACT
1379
+ ]
1380
+ stats.append(
1381
+ SubmissionLatencyStats(
1382
+ bucket=bucket,
1383
+ sample_count=len(bucket_samples),
1384
+ unresolved_count=len(bucket_samples) - len(resolved),
1385
+ p50_ns=_percentile_metric(resolved, 0.50),
1386
+ p90_ns=_percentile_metric(resolved, 0.90),
1387
+ max_ns=Metric(max(resolved), Precision.EXACT)
1388
+ if resolved
1389
+ else Metric(None, Precision.MISSING, "no resolved samples"),
1390
+ samples=bucket_samples,
1391
+ )
1392
+ )
1393
+ return SubmissionLatencyOverview(tuple(stats))
1394
+
1395
+
1396
+ def _drop_submission_lifecycle_cut(
1397
+ intermediate: _Intermediate,
1398
+ node: _Node,
1399
+ inactive_ranges: tuple[tuple[int, int], ...],
1400
+ turn_scope_membership: dict[str, tuple[bool, bool]],
1401
+ ) -> bool:
1402
+ """Whether a complete cut lifecycle has no active submission owner."""
1403
+ if _lifecycle_cut(node, inactive_ranges) is _LifecycleCut.NONE:
1404
+ return False
1405
+ raw_finish = node.finishes[0]
1406
+ outcome = _payload_str(raw_finish.payload, "outcome")
1407
+ if outcome not in _RECOGNIZED_PREPARATION_OUTCOMES:
1408
+ return False
1409
+ scope_membership = turn_scope_membership.get(node.operation_id, (False, False))
1410
+ target_turn_id = _payload_str(raw_finish.payload, "target_turn_id")
1411
+ target_membership = _turn_start_membership(
1412
+ intermediate,
1413
+ {target_turn_id} if target_turn_id is not None else set(),
1414
+ inactive_ranges,
1415
+ )
1416
+ ownership = _merge_projection_memberships(scope_membership, target_membership)
1417
+ if ownership[0]:
1418
+ return False
1419
+ if outcome in _PROMOTED_PREPARATION_OUTCOMES:
1420
+ # A promoted turn must claim the preparation scope. Absence is damaged
1421
+ # evidence rather than proof that the lifecycle was superseded.
1422
+ return _exclusively_inactive(scope_membership)
1423
+ if outcome == PreparationOutcome.INJECTED:
1424
+ # Successful injection terminals explicitly identify their target turn.
1425
+ return target_turn_id is not None and _exclusively_inactive(target_membership)
1426
+ # Rejected/cancelled/failed admission normally creates no turn claim. For
1427
+ # these derived samples, a complete cut with no active owner is superseded.
1428
+ return True
1429
+
1430
+
1431
+ def _submission_bucket(outcome: str | None) -> SubmissionLatencyBucket:
1432
+ if outcome in {"fresh_turn", "retry_turn"}:
1433
+ return SubmissionLatencyBucket.BECAME_TURN
1434
+ if outcome == "injected":
1435
+ return SubmissionLatencyBucket.INJECTED
1436
+ return SubmissionLatencyBucket.DID_NOT_BECOME_TURN
1437
+
1438
+
1439
+ def _degrade_overview(overview: TrajectoryOverview, reason: str) -> TrajectoryOverview:
1440
+ """Cap selection-wide metrics without changing still-provable turn metrics."""
1441
+
1442
+ return TrajectoryOverview(
1443
+ elapsed_ns=_cap_session_metric(overview.elapsed_ns, reason),
1444
+ compute_cp_ns=_cap_session_metric(overview.compute_cp_ns, reason),
1445
+ response_cp_ns=_cap_session_metric(overview.response_cp_ns, reason),
1446
+ exclusive_work_ns=_cap_session_metric(overview.exclusive_work_ns, reason),
1447
+ parallelism=_cap_session_metric(overview.parallelism, reason),
1448
+ overlap_gain_ns=_cap_session_metric(overview.overlap_gain_ns, reason),
1449
+ wall_time_ns={bucket: _cap_session_metric(metric, reason) for bucket, metric in overview.wall_time_ns.items()},
1450
+ utilization={bucket: _cap_session_metric(metric, reason) for bucket, metric in overview.utilization.items()},
1451
+ usage_tokens=_cap_session_metric(overview.usage_tokens, reason),
1452
+ )
1453
+
1454
+
1455
+ def _degrade_token_usage(usage: TokenUsage, reason: str) -> TokenUsage:
1456
+ """Cap normalized session token totals when events may be missing."""
1457
+
1458
+ return TokenUsage(buckets={bucket: _cap_session_metric(metric, reason) for bucket, metric in usage.buckets.items()})
1459
+
1460
+
1461
+ def _degrade_submission_latency(latency: SubmissionLatencyOverview, reason: str) -> SubmissionLatencyOverview:
1462
+ """Cap session percentile summaries without degrading intact samples."""
1463
+
1464
+ return SubmissionLatencyOverview(
1465
+ buckets=tuple(
1466
+ replace(
1467
+ stats,
1468
+ p50_ns=_cap_session_metric(stats.p50_ns, reason),
1469
+ p90_ns=_cap_session_metric(stats.p90_ns, reason),
1470
+ max_ns=_cap_session_metric(stats.max_ns, reason),
1471
+ )
1472
+ for stats in latency.buckets
1473
+ )
1474
+ )