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,896 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Tool action projection, validation metrics and per-file change verification.
4
+
5
+ Actions are projected from tool lifecycles; action counts, edit/verify
6
+ cycles, validation metrics and file verification states are derived from them
7
+ here. Action classes and tool-outcome predicates come from ``classification``.
8
+ ``findings``' unverified-change rule repeats this module's unverified-edit
9
+ selection (``model.verify_covers_edit`` against the last successful verify)
10
+ and takes its precision from the count here, so a change to which edits count
11
+ as verified must change both. File verification states order differently: a
12
+ verify in the file's last-change turn must start after every potential mutator
13
+ in that turn.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from collections import defaultdict
19
+ from collections.abc import Callable
20
+ from dataclasses import replace
21
+ from statistics import median
22
+ from threading import Event
23
+ from typing import Final, cast
24
+
25
+ from chrys.foundation.trajectory.event_types import SourceRefKind, ToolOutcome
26
+ from chrys.foundation.trajectory.ids import is_valid_analytics_id
27
+ from chrys.service.analytics._facts import (
28
+ _active,
29
+ _Endpoint,
30
+ _EventScope,
31
+ _Intermediate,
32
+ _Node,
33
+ _payload_int,
34
+ _payload_str,
35
+ _payload_value,
36
+ _ToolPayloadExtras,
37
+ )
38
+ from chrys.service.analytics._metric_ops import _cap_session_metric, _with_unresolved_precision
39
+ from chrys.service.analytics._session_projection import _SessionProjection
40
+ from chrys.service.analytics._timeline import _tool_context_for_start
41
+ from chrys.service.analytics.classification import (
42
+ KNOWN_TOOL_OUTCOMES,
43
+ classification_evidence_key,
44
+ classify_action,
45
+ parse_verify_commands,
46
+ tool_failed,
47
+ tool_succeeded,
48
+ )
49
+ from chrys.service.analytics.model import (
50
+ ActionClass,
51
+ ActionFunnel,
52
+ ActionOperation,
53
+ ChangeVerification,
54
+ ChangeVerificationRow,
55
+ ChangeVerificationState,
56
+ Metric,
57
+ Precision,
58
+ TurnAnalysis,
59
+ ValidationMetrics,
60
+ least_precision,
61
+ verify_covers_edit,
62
+ )
63
+ from chrys.service.analytics.reader import raise_if_cancelled as _check_cancelled
64
+ from chrys.service.mutations.types import FileHashDiff, parse_skip_reason
65
+
66
+ _WORD_LIST_HEURISTIC_REASON: Final = "one or more shell actions use the verification word-list heuristic"
67
+
68
+
69
+ def _resolve_actions(
70
+ intermediate: _Intermediate,
71
+ inactive_ranges: tuple[tuple[int, int], ...],
72
+ turns: list[TurnAnalysis],
73
+ *,
74
+ session_projection: Callable[[], _SessionProjection],
75
+ verify_commands: str,
76
+ cancel_event: Event | None,
77
+ ) -> tuple[ActionOperation, ...]:
78
+ turns_by_id = {turn.turn_id: turn for turn in turns}
79
+ commands = parse_verify_commands(verify_commands)
80
+ actions: list[ActionOperation] = []
81
+ for node in intermediate.nodes.get("tool.operation", {}).values():
82
+ _check_cancelled(cancel_event)
83
+ starts = [event for event in node.starts if _active(event.sequence, inactive_ranges) and not event.side_call]
84
+ finishes = [
85
+ event for event in node.finishes if _active(event.sequence, inactive_ranges) and not event.side_call
86
+ ]
87
+ if len(starts) != 1 or starts[0].side_call or starts[0].turn_id not in turns_by_id:
88
+ continue
89
+ start = starts[0]
90
+ call_item_id = _payload_str(start.payload, "call_item_id")
91
+ argument_fingerprint = _payload_str(start.payload, "argument_fingerprint")
92
+ tool_kind = _payload_str(start.payload, "tool_kind")
93
+ tool_name = _payload_str(start.payload, "tool_name")
94
+ stable_key = classification_evidence_key(
95
+ call_item_id=call_item_id,
96
+ argument_fingerprint=argument_fingerprint,
97
+ )
98
+ command = session_projection().commands.get(call_item_id or "") if tool_kind == "shell" else None
99
+ classification, classification_precision, classification_reason = classify_action(
100
+ tool_kind,
101
+ command=command,
102
+ verify_commands=commands,
103
+ )
104
+ outcome, outcome_precision, outcome_reason, finish = _action_outcome(start, finishes)
105
+ context = _tool_context_for_start(node, start)
106
+ payload = _tool_payload_for_node(node, inactive_ranges, start.scope)
107
+ turn = turns_by_id[cast("str", start.turn_id)]
108
+ if turn.action_projection_precision is not Precision.EXACT:
109
+ outcome = None
110
+ outcome_precision = Precision.UNRESOLVED
111
+ outcome_reason = turn.action_projection_reason or "tool action projection is incomplete"
112
+ finish = None
113
+ actions.append(
114
+ ActionOperation(
115
+ evidence_key=stable_key,
116
+ occurrence_id=f"tool:{start.sequence}:{node.operation_id}",
117
+ operation_id=node.operation_id,
118
+ turn_id=turn.turn_id,
119
+ turn_number=turn.turn_number,
120
+ tool_name=tool_name,
121
+ tool_kind=tool_kind,
122
+ call_item_id=call_item_id,
123
+ argument_fingerprint=argument_fingerprint,
124
+ classification=classification,
125
+ classification_precision=classification_precision,
126
+ classification_reason=classification_reason,
127
+ outcome=outcome,
128
+ outcome_precision=outcome_precision,
129
+ start_sequence=start.sequence,
130
+ start_ns=start.monotonic_ns,
131
+ end_ns=finish.monotonic_ns if finish is not None and finish.monotonic_measurement else None,
132
+ end_sequence=finish.sequence if finish is not None else None,
133
+ outcome_reason=outcome_reason,
134
+ server_name=context.server_name if context is not None else None,
135
+ remote_name=context.remote_name if context is not None else None,
136
+ skill_name=context.skill_name if context is not None else None,
137
+ skill_revision=context.skill_revision if context is not None else None,
138
+ script_name=context.script_name if context is not None else None,
139
+ resource_name=context.resource_name if context is not None else None,
140
+ exit_code=_payload_int(finish.payload, "exit_code") if finish is not None else None,
141
+ payload_observed=payload is not None,
142
+ payload_bytes=payload.model_visible_bytes if payload is not None else None,
143
+ payload_token_estimate=payload.local_token_estimate if payload is not None else None,
144
+ payload_original_bytes=payload.original_bytes if payload is not None else None,
145
+ payload_truncated=payload.truncated if payload is not None else None,
146
+ payload_spilled=payload.spilled if payload is not None else None,
147
+ )
148
+ )
149
+ return tuple(sorted(actions, key=lambda action: (action.start_sequence, action.operation_id)))
150
+
151
+
152
+ def _action_outcome(
153
+ start: _Endpoint,
154
+ finishes: list[_Endpoint],
155
+ ) -> tuple[str | None, Precision, str | None, _Endpoint | None]:
156
+ if not finishes:
157
+ return None, Precision.MISSING, "tool operation has no terminal event", None
158
+ if len(finishes) != 1:
159
+ return None, Precision.UNRESOLVED, "tool operation has more than one terminal event", None
160
+ finish = finishes[0]
161
+ if finish.scope != start.scope:
162
+ return None, Precision.UNRESOLVED, "tool lifecycle endpoints cross scope", None
163
+ if finish.sequence <= start.sequence or finish.monotonic_ns < start.monotonic_ns:
164
+ return None, Precision.UNRESOLVED, "tool lifecycle endpoints are not ordered", None
165
+ outcome = _payload_str(finish.payload, "outcome")
166
+ if outcome is None:
167
+ return None, Precision.MISSING, "tool terminal outcome is missing", finish
168
+ if outcome not in KNOWN_TOOL_OUTCOMES:
169
+ return outcome, Precision.UNRESOLVED, "tool terminal outcome is invalid", finish
170
+ return outcome, Precision.EXACT, None, finish
171
+
172
+
173
+ def _scope_matches(observed_scope: _EventScope, node_scope: _EventScope) -> bool:
174
+ return (
175
+ observed_scope.runtime_id == node_scope.runtime_id
176
+ and observed_scope.branch_id == node_scope.branch_id
177
+ and observed_scope.coverage_id == node_scope.coverage_id
178
+ and observed_scope.actor_id == node_scope.actor_id
179
+ and observed_scope.turn_id == node_scope.turn_id
180
+ )
181
+
182
+
183
+ def _tool_payload_for_node(
184
+ node: _Node,
185
+ inactive_ranges: tuple[tuple[int, int], ...],
186
+ scope: _EventScope,
187
+ ) -> _ToolPayloadExtras | None:
188
+ extras = node.extras
189
+ if extras is None:
190
+ return None
191
+ active = [
192
+ item
193
+ for item in extras.payloads
194
+ if _active(item.sequence, inactive_ranges) and _scope_matches(item.scope, scope)
195
+ ]
196
+ return active[0] if len(active) == 1 else None
197
+
198
+
199
+ def _action_count_metrics(
200
+ actions: tuple[ActionOperation, ...],
201
+ *,
202
+ projection_precision: Precision = Precision.EXACT,
203
+ projection_reason: str | None = None,
204
+ ) -> dict[ActionClass, Metric]:
205
+ degraded_shell = any(
206
+ action.tool_kind == "shell" and action.classification_precision is Precision.UNRESOLVED for action in actions
207
+ )
208
+ estimated_shell = any(
209
+ action.tool_kind == "shell" and action.classification_precision is Precision.ESTIMATED for action in actions
210
+ )
211
+ metrics: dict[ActionClass, Metric] = {}
212
+ for action_class in ActionClass:
213
+ matching = [action for action in actions if action.classification is action_class]
214
+ precision = least_precision((projection_precision, *(action.classification_precision for action in matching)))
215
+ reason = None
216
+ if projection_precision is not Precision.EXACT:
217
+ reason = projection_reason or "tool action projection is incomplete"
218
+ elif action_class in {ActionClass.VERIFY, ActionClass.OTHER} and degraded_shell:
219
+ precision = Precision.UNRESOLVED
220
+ reason = "one or more shell command carriers are unavailable"
221
+ elif (
222
+ action_class in {ActionClass.VERIFY, ActionClass.OTHER} and estimated_shell and precision is Precision.EXACT
223
+ ):
224
+ # The word-list heuristic decides between the verify and other
225
+ # buckets, so either count is only as precise as the heuristic
226
+ # even when every shell action landed in the opposite bucket.
227
+ precision = Precision.ESTIMATED
228
+ reason = _WORD_LIST_HEURISTIC_REASON
229
+ elif precision is Precision.ESTIMATED:
230
+ reason = _WORD_LIST_HEURISTIC_REASON
231
+ elif precision is Precision.UNRESOLVED:
232
+ reason = "one or more action classifications are unresolved"
233
+ metrics[action_class] = Metric(len(matching), precision, reason)
234
+ return metrics
235
+
236
+
237
+ def _validation_metrics(
238
+ actions: tuple[ActionOperation, ...],
239
+ turns: list[TurnAnalysis],
240
+ change_verification: ChangeVerification,
241
+ retry_amplification: Metric,
242
+ *,
243
+ cancel_event: Event | None = None,
244
+ ) -> ValidationMetrics:
245
+ _check_cancelled(cancel_event)
246
+ projection_precision = least_precision(turn.action_projection_precision for turn in turns)
247
+ projection_reason = next(
248
+ (turn.action_projection_reason for turn in turns if turn.action_projection_reason is not None),
249
+ None,
250
+ )
251
+ counts = _action_count_metrics(
252
+ actions,
253
+ projection_precision=projection_precision,
254
+ projection_reason=projection_reason,
255
+ )
256
+ degraded_shell = any(
257
+ action.tool_kind == "shell" and action.classification_precision is Precision.UNRESOLVED for action in actions
258
+ )
259
+ estimated_shell = any(
260
+ action.tool_kind == "shell" and action.classification_precision is Precision.ESTIMATED for action in actions
261
+ )
262
+ verification_reason = "one or more shell command carriers are unavailable"
263
+ edits = [action for action in actions if action.classification is ActionClass.EDIT]
264
+ verifies = [action for action in actions if action.classification is ActionClass.VERIFY]
265
+ incomplete_outcomes = any(action.outcome_precision is not Precision.EXACT for action in actions)
266
+ unknown_outcomes = any(action.outcome == ToolOutcome.UNKNOWN for action in actions)
267
+ outcome_precision = Precision.UNRESOLVED if incomplete_outcomes or unknown_outcomes else Precision.EXACT
268
+ outcome_reason = (
269
+ next((action.outcome_reason for action in actions if action.outcome_reason is not None), None)
270
+ if incomplete_outcomes
271
+ else "one or more tool outcomes are unknown"
272
+ if unknown_outcomes
273
+ else None
274
+ )
275
+ verify_outcomes_unresolved = any(
276
+ action.outcome_precision is not Precision.EXACT or action.outcome == ToolOutcome.UNKNOWN for action in verifies
277
+ )
278
+ first_edit = edits[0] if edits else None
279
+ time_to_edit = _time_to_action(turns, first_edit)
280
+ first_verify = next(
281
+ (action for action in verifies if first_edit is not None and verify_covers_edit(first_edit, action)),
282
+ None,
283
+ )
284
+ if first_edit is not None and first_verify is not None:
285
+ edit_to_verify = _duration_between_actions(
286
+ turns,
287
+ first_edit,
288
+ first_verify,
289
+ precisions=(first_edit.classification_precision, first_verify.classification_precision),
290
+ )
291
+ if degraded_shell:
292
+ edit_to_verify = _with_unresolved_precision(edit_to_verify, verification_reason)
293
+ elif degraded_shell and first_edit is not None:
294
+ edit_to_verify = Metric(None, Precision.UNRESOLVED, verification_reason)
295
+ elif estimated_shell and first_edit is not None:
296
+ # An estimated other-classified shell action may have been the
297
+ # first verify, so "no verify followed" cannot be stated as fact.
298
+ edit_to_verify = Metric(None, Precision.UNRESOLVED, _WORD_LIST_HEURISTIC_REASON)
299
+ else:
300
+ edit_to_verify = Metric(None, Precision.MISSING, "no verify action followed the first edit")
301
+ cycle_metrics: list[Metric] = []
302
+ pending_edits: list[ActionOperation] = []
303
+ for action in actions:
304
+ _check_cancelled(cancel_event)
305
+ if action.classification is ActionClass.EDIT:
306
+ pending_edits.append(action)
307
+ # A verify closes the open iteration only when it began after every
308
+ # pending edit's terminal landed; one overlapping any of them
309
+ # leaves the whole batch pending for a later ordered verify.
310
+ elif (
311
+ action.classification is ActionClass.VERIFY
312
+ and tool_succeeded(action.outcome)
313
+ and pending_edits
314
+ and all(verify_covers_edit(edit, action) for edit in pending_edits)
315
+ ):
316
+ cycle_metrics.append(
317
+ _duration_between_actions(
318
+ turns,
319
+ pending_edits[0],
320
+ action,
321
+ precisions=(
322
+ *(edit.classification_precision for edit in pending_edits),
323
+ action.classification_precision,
324
+ action.outcome_precision,
325
+ ),
326
+ )
327
+ )
328
+ pending_edits = []
329
+ cycle_values = [metric.value for metric in cycle_metrics if isinstance(metric.value, int)]
330
+ cycle_precisions = [metric.precision for metric in cycle_metrics]
331
+ if degraded_shell or verify_outcomes_unresolved:
332
+ cycle_precisions.append(Precision.UNRESOLVED)
333
+ elif estimated_shell:
334
+ # An unrecognized shell verify could have opened or closed more
335
+ # cycles than the word list identified.
336
+ cycle_precisions.append(Precision.ESTIMATED)
337
+ cycle_precision = least_precision(cycle_precisions)
338
+ cycle_reason = (
339
+ verification_reason
340
+ if degraded_shell
341
+ else outcome_reason
342
+ if verify_outcomes_unresolved
343
+ else _WORD_LIST_HEURISTIC_REASON
344
+ if estimated_shell
345
+ else next((metric.reason for metric in cycle_metrics if metric.reason is not None), None)
346
+ )
347
+ successful_verifies = [action for action in verifies if tool_succeeded(action.outcome)]
348
+ last_successful_verify = successful_verifies[-1] if successful_verifies else None
349
+ unverified = [
350
+ action
351
+ for action in edits
352
+ if last_successful_verify is None or not verify_covers_edit(action, last_successful_verify)
353
+ ]
354
+ unverified_precision = least_precision(action.classification_precision for action in (*edits, *verifies))
355
+ unverified_reason = None
356
+ if degraded_shell:
357
+ unverified_precision = Precision.UNRESOLVED
358
+ unverified_reason = verification_reason
359
+ elif verify_outcomes_unresolved:
360
+ unverified_precision = Precision.UNRESOLVED
361
+ unverified_reason = outcome_reason
362
+ elif estimated_shell and unverified_precision is Precision.EXACT:
363
+ # An estimated other-classified shell action may really have been
364
+ # verification work, so the absence this count rests on is only as
365
+ # good as the word-list heuristic.
366
+ unverified_precision = Precision.ESTIMATED
367
+ unverified_reason = _WORD_LIST_HEURISTIC_REASON
368
+ elif unverified_precision is Precision.ESTIMATED:
369
+ unverified_reason = _WORD_LIST_HEURISTIC_REASON
370
+ failures = [action for action in actions if tool_failed(action.outcome)]
371
+ failure_groups: dict[tuple[str | None, str | None], list[ActionOperation]] = defaultdict(list)
372
+ for action in failures:
373
+ _check_cancelled(cancel_event)
374
+ if action.tool_name is not None and action.argument_fingerprint is not None:
375
+ failure_groups[(action.tool_name, action.argument_fingerprint)].append(action)
376
+ repeated_signatures = sum(len(group) >= 2 for group in failure_groups.values())
377
+ recovery_metrics: list[Metric] = []
378
+ for failure in failures:
379
+ _check_cancelled(cancel_event)
380
+ # A recovery responds to an observed failure, so candidates must
381
+ # begin after the failure's terminal — a concurrent same-tool
382
+ # success is not a retry, and without the terminal nothing orders
383
+ # any later success against the failure. A failure with no tool
384
+ # name cannot be paired at all: matching nameless calls would
385
+ # marry unrelated tools.
386
+ if failure.tool_name is None:
387
+ recovery_metrics.append(Metric(None, Precision.UNRESOLVED, "the failed call lacks a tool identity"))
388
+ continue
389
+ if failure.end_sequence is None:
390
+ recovery_metrics.append(Metric(None, Precision.UNRESOLVED, "the failed call's terminal never landed"))
391
+ continue
392
+ recovered: ActionOperation | None = None
393
+ # A same-tool action with an unknown or imprecise outcome between
394
+ # the failure and the recognized success may itself have been the
395
+ # recovery, so the measured latency cannot claim exactness past it.
396
+ passed_unknown_outcome = False
397
+ for action in actions:
398
+ if action.start_sequence <= failure.end_sequence or action.tool_name != failure.tool_name:
399
+ continue
400
+ if tool_succeeded(action.outcome):
401
+ recovered = action
402
+ break
403
+ if action.outcome == ToolOutcome.UNKNOWN or action.outcome_precision is not Precision.EXACT:
404
+ passed_unknown_outcome = True
405
+ if recovered is not None:
406
+ metric = _duration_between_actions(
407
+ turns,
408
+ failure,
409
+ recovered,
410
+ from_end=True,
411
+ precisions=(failure.outcome_precision, recovered.outcome_precision),
412
+ )
413
+ if passed_unknown_outcome:
414
+ metric = _with_unresolved_precision(
415
+ metric, "a same-tool call with an unknown outcome preceded the recognized recovery"
416
+ )
417
+ recovery_metrics.append(metric)
418
+ if any(action.tool_name is None or action.argument_fingerprint is None for action in failures):
419
+ signature_precision = Precision.UNRESOLVED
420
+ signature_reason = "one or more failed tools lack a name or argument fingerprint"
421
+ else:
422
+ signature_precision = outcome_precision
423
+ signature_reason = outcome_reason
424
+ recovery_values = [metric.value for metric in recovery_metrics if isinstance(metric.value, int)]
425
+ recovery_precision = least_precision(metric.precision for metric in recovery_metrics)
426
+ recovery_reason = next((metric.reason for metric in recovery_metrics if metric.reason is not None), outcome_reason)
427
+ validation = ValidationMetrics(
428
+ funnel=ActionFunnel(
429
+ search=counts[ActionClass.SEARCH],
430
+ read=counts[ActionClass.READ],
431
+ edit=counts[ActionClass.EDIT],
432
+ verify=counts[ActionClass.VERIFY],
433
+ ),
434
+ time_to_first_edit_ns=time_to_edit,
435
+ first_edit_to_first_verify_ns=edit_to_verify,
436
+ edit_verify_cycle_count=Metric(len(cycle_metrics), cycle_precision, cycle_reason),
437
+ edit_verify_cycle_median_ns=(
438
+ Metric(int(median(cycle_values)), cycle_precision, cycle_reason)
439
+ if cycle_values and len(cycle_values) == len(cycle_metrics)
440
+ else Metric(None, Precision.UNRESOLVED, cycle_reason)
441
+ if cycle_metrics
442
+ else Metric(None, Precision.UNRESOLVED, verification_reason)
443
+ if degraded_shell
444
+ else Metric(None, Precision.UNRESOLVED, outcome_reason)
445
+ if verify_outcomes_unresolved and first_edit is not None
446
+ else Metric(None, Precision.UNRESOLVED, _WORD_LIST_HEURISTIC_REASON)
447
+ if estimated_shell and first_edit is not None
448
+ else Metric(None, Precision.MISSING, "no completed edit-to-verify cycle was recorded")
449
+ ),
450
+ unverified_change_count=Metric(len(unverified), unverified_precision, unverified_reason),
451
+ net_zero_churn_count=change_verification.net_zero,
452
+ repeated_failure_signature_count=Metric(repeated_signatures, signature_precision, signature_reason),
453
+ failure_recovery_median_ns=(
454
+ Metric(int(median(recovery_values)), recovery_precision, recovery_reason)
455
+ if recovery_values and len(recovery_values) == len(recovery_metrics)
456
+ else Metric(None, Precision.UNRESOLVED, recovery_reason)
457
+ if recovery_metrics
458
+ else Metric(None, Precision.UNRESOLVED, outcome_reason)
459
+ if outcome_precision is Precision.UNRESOLVED
460
+ else Metric(None, Precision.MISSING, "no failed tool call was followed by a same-tool success")
461
+ ),
462
+ tool_failure_count=Metric(len(failures), outcome_precision, outcome_reason),
463
+ tool_count=Metric(
464
+ len(actions),
465
+ projection_precision,
466
+ None if projection_precision is Precision.EXACT else projection_reason,
467
+ ),
468
+ retry_amplification_tokens=retry_amplification,
469
+ )
470
+ if projection_precision is Precision.EXACT:
471
+ return validation
472
+ reason = projection_reason or "tool action projection is incomplete"
473
+ return _degrade_validation_metrics(validation, reason)
474
+
475
+
476
+ def _time_to_action(turns: list[TurnAnalysis], action: ActionOperation | None) -> Metric:
477
+ if action is None:
478
+ return Metric(None, Precision.MISSING, "no edit action was recorded")
479
+ elapsed = 0
480
+ precisions = [action.classification_precision]
481
+ for turn in turns:
482
+ if turn.turn_id == action.turn_id:
483
+ offset = action.start_ns - turn.axis_start_ns
484
+ if offset < 0:
485
+ return Metric(None, Precision.UNRESOLVED, "action precedes its owning turn")
486
+ return Metric(elapsed + offset, least_precision(precisions), action.classification_reason)
487
+ if not isinstance(turn.elapsed_ns.value, int):
488
+ return Metric(None, Precision.UNRESOLVED, "an earlier turn duration is unresolved")
489
+ elapsed += turn.elapsed_ns.value
490
+ precisions.append(turn.elapsed_ns.precision)
491
+ return Metric(None, Precision.UNRESOLVED, "action turn is absent from the active projection")
492
+
493
+
494
+ def _duration_between_actions(
495
+ turns: list[TurnAnalysis],
496
+ first: ActionOperation,
497
+ second: ActionOperation,
498
+ *,
499
+ from_end: bool = False,
500
+ precisions: tuple[Precision, ...],
501
+ ) -> Metric:
502
+ start_ns = first.end_ns if from_end else first.start_ns
503
+ if start_ns is None:
504
+ return Metric(None, Precision.MISSING, "the starting tool outcome has no terminal timestamp")
505
+ if first.turn_id == second.turn_id:
506
+ duration = second.start_ns - start_ns
507
+ if duration < 0:
508
+ return Metric(None, Precision.UNRESOLVED, "action order yields a negative duration")
509
+ return Metric(duration, least_precision(precisions))
510
+ turns_by_id = {turn.turn_id: index for index, turn in enumerate(turns)}
511
+ first_index = turns_by_id.get(first.turn_id)
512
+ second_index = turns_by_id.get(second.turn_id)
513
+ if first_index is None or second_index is None or second_index <= first_index:
514
+ return Metric(None, Precision.UNRESOLVED, "action turns are not ordered in the active projection")
515
+ first_turn = turns[first_index]
516
+ second_turn = turns[second_index]
517
+ if not isinstance(first_turn.elapsed_ns.value, int):
518
+ return Metric(None, Precision.UNRESOLVED, "the starting turn duration is unresolved")
519
+ start_offset = start_ns - first_turn.axis_start_ns
520
+ end_offset = second.start_ns - second_turn.axis_start_ns
521
+ duration = first_turn.elapsed_ns.value - start_offset + end_offset
522
+ metric_precisions = [*precisions, first_turn.elapsed_ns.precision]
523
+ for turn in turns[first_index + 1 : second_index]:
524
+ if not isinstance(turn.elapsed_ns.value, int):
525
+ return Metric(None, Precision.UNRESOLVED, "an intervening turn duration is unresolved")
526
+ duration += turn.elapsed_ns.value
527
+ metric_precisions.append(turn.elapsed_ns.precision)
528
+ if duration < 0:
529
+ return Metric(None, Precision.UNRESOLVED, "action order yields a negative duration")
530
+ return Metric(duration, least_precision(metric_precisions))
531
+
532
+
533
+ def _degrade_validation_metrics(
534
+ validation: ValidationMetrics,
535
+ reason: str,
536
+ *,
537
+ session_integrity_cap: bool = False,
538
+ ) -> ValidationMetrics:
539
+ def degrade(metric: Metric) -> Metric:
540
+ return (
541
+ _cap_session_metric(metric, reason) if session_integrity_cap else _with_unresolved_precision(metric, reason)
542
+ )
543
+
544
+ return ValidationMetrics(
545
+ funnel=ActionFunnel(
546
+ search=degrade(validation.funnel.search),
547
+ read=degrade(validation.funnel.read),
548
+ edit=degrade(validation.funnel.edit),
549
+ verify=degrade(validation.funnel.verify),
550
+ ),
551
+ time_to_first_edit_ns=degrade(validation.time_to_first_edit_ns),
552
+ first_edit_to_first_verify_ns=degrade(validation.first_edit_to_first_verify_ns),
553
+ edit_verify_cycle_count=degrade(validation.edit_verify_cycle_count),
554
+ edit_verify_cycle_median_ns=degrade(validation.edit_verify_cycle_median_ns),
555
+ unverified_change_count=degrade(validation.unverified_change_count),
556
+ net_zero_churn_count=degrade(validation.net_zero_churn_count),
557
+ repeated_failure_signature_count=degrade(validation.repeated_failure_signature_count),
558
+ failure_recovery_median_ns=degrade(validation.failure_recovery_median_ns),
559
+ tool_failure_count=degrade(validation.tool_failure_count),
560
+ tool_count=degrade(validation.tool_count),
561
+ retry_amplification_tokens=degrade(validation.retry_amplification_tokens),
562
+ )
563
+
564
+
565
+ def _resolve_change_verification(
566
+ intermediate: _Intermediate,
567
+ inactive_ranges: tuple[tuple[int, int], ...],
568
+ turns: list[TurnAnalysis],
569
+ actions: tuple[ActionOperation, ...],
570
+ projection: _SessionProjection,
571
+ *,
572
+ rollback_projection_unresolved: bool,
573
+ cancel_event: Event | None,
574
+ ) -> ChangeVerification:
575
+ _check_cancelled(cancel_event)
576
+ active_turn_numbers = {turn.turn_number for turn in turns if turn.turn_number is not None}
577
+ active_turn_ids = {turn.turn_id for turn in turns}
578
+ # The detailed mutation state joins on turn numbers; a missing,
579
+ # damaged, or duplicated number silently drops or misattributes that
580
+ # turn's rows, so the join cannot claim the detailed counts.
581
+ seen_turn_numbers: set[int] = set()
582
+ previous_turn_number: int | None = None
583
+ turn_numbers_joinable = True
584
+ for turn in turns:
585
+ number = turn.turn_number
586
+ repeated_without_contiguous_retry = number in seen_turn_numbers and (
587
+ not turn.attempts[0].is_retry or number != previous_turn_number
588
+ )
589
+ if number is None or number <= 0 or repeated_without_contiguous_retry:
590
+ turn_numbers_joinable = False
591
+ break
592
+ seen_turn_numbers.add(number)
593
+ previous_turn_number = number
594
+ summaries = [
595
+ endpoint
596
+ for endpoint in intermediate.mutation_summaries
597
+ if _active(endpoint.sequence, inactive_ranges) and endpoint.turn_id in active_turn_ids
598
+ ]
599
+ # A summary that names no session checkpoint recorded mutations whose
600
+ # save never completed: the readable document predates them, so its
601
+ # detail cannot be read as the current truth. A later successful save
602
+ # re-serializes the whole mutation log, so only the newest summary
603
+ # decides freshness. Only a ref of the session-checkpoint kind carrying
604
+ # an id the save path could have minted vouches for the document; any
605
+ # other shape proves nothing about it.
606
+ latest_summary = max(summaries, key=lambda endpoint: endpoint.sequence, default=None)
607
+ source_ref = None if latest_summary is None else _payload_value(latest_summary.payload, "source_ref")
608
+ detail_current = latest_summary is None or (
609
+ isinstance(source_ref, dict)
610
+ and source_ref.get("kind") == SourceRefKind.SESSION_CHECKPOINT
611
+ and is_valid_analytics_id(source_ref.get("id"))
612
+ )
613
+ if (
614
+ not projection.available
615
+ or not projection.mutation_detail_available
616
+ or not detail_current
617
+ or not turn_numbers_joinable
618
+ ):
619
+ # Per-turn summaries carry counts but no file identities, so a file
620
+ # touched in several turns counts once per turn, and a create followed
621
+ # by a later modify never folds into one created file — the sums are
622
+ # honest per-turn totals, never the session-wide folded counts the
623
+ # detailed projection reports.
624
+ precision = Precision.UNRESOLVED if rollback_projection_unresolved else Precision.ESTIMATED
625
+ reason = (
626
+ "recorded summary counts only; rollback projection and session.json detail are unavailable"
627
+ if precision is Precision.UNRESOLVED
628
+ else "summed per-turn summary counts; the active turns' numbers cannot join the session.json file detail"
629
+ if not turn_numbers_joinable
630
+ else "summed per-turn summary counts; usable session.json file detail is unavailable to fold repeat touches"
631
+ )
632
+
633
+ def summary(key: str) -> Metric:
634
+ values = [_payload_int(endpoint.payload, key) for endpoint in summaries]
635
+ if any(value is None or value < 0 for value in values):
636
+ return Metric(None, Precision.MISSING, f"recorded mutation summary is missing or invalid for {key}")
637
+ return Metric(sum(cast("int", value) for value in values), precision, reason)
638
+
639
+ return ChangeVerification(
640
+ detail_available=False,
641
+ detection_truncated=False,
642
+ files_touched=summary("files_touched"),
643
+ created=summary("create"),
644
+ modified=summary("modify"),
645
+ deleted=summary("delete"),
646
+ net_zero=summary("net_zero_count"),
647
+ )
648
+ selected = sorted(
649
+ (
650
+ mutation
651
+ for mutation in projection.mutations
652
+ if mutation.turn_number in active_turn_numbers and mutation.provenance != "foreign"
653
+ ),
654
+ key=lambda mutation: mutation.turn_number,
655
+ )
656
+ folded: dict[str, tuple[FileHashDiff, int]] = {}
657
+
658
+ def fold(
659
+ path: str,
660
+ turn_number: int,
661
+ *,
662
+ before_hash: str | None,
663
+ before_skip: str | None,
664
+ after_hash: str | None,
665
+ after_skip: str | None,
666
+ inferred: bool,
667
+ contested: bool,
668
+ ) -> None:
669
+ previous = folded.get(path)
670
+ before = previous[0].before if previous is not None else before_hash
671
+ folded_before_skip = previous[0].before_skip if previous is not None else parse_skip_reason(before_skip)
672
+ folded[path] = (
673
+ FileHashDiff(
674
+ before=before,
675
+ after=after_hash,
676
+ before_skip=folded_before_skip,
677
+ after_skip=parse_skip_reason(after_skip),
678
+ contested=(previous is not None and previous[0].contested) or contested,
679
+ inferred=(previous is not None and previous[0].inferred) or inferred,
680
+ ),
681
+ turn_number,
682
+ )
683
+
684
+ for mutation in selected:
685
+ _check_cancelled(cancel_event)
686
+ # Only a proven row attests that this session's own write produced
687
+ # the change; anything else folded from a window diff may be a
688
+ # concurrent third party's, so the folded diff keeps the badge.
689
+ inferred = mutation.provenance != "proven"
690
+ fold(
691
+ mutation.path,
692
+ mutation.turn_number,
693
+ before_hash=mutation.before_hash,
694
+ before_skip=mutation.before_skip,
695
+ after_hash=mutation.after_hash,
696
+ after_skip=mutation.after_skip,
697
+ inferred=inferred,
698
+ contested=mutation.contested,
699
+ )
700
+ if mutation.old_path is not None:
701
+ # The move's own before_hash describes the destination; the
702
+ # source folds as a delete from its snapshotted pre-state.
703
+ fold(
704
+ mutation.old_path,
705
+ mutation.turn_number,
706
+ before_hash=mutation.old_before_hash,
707
+ before_skip=mutation.old_before_skip,
708
+ after_hash=None,
709
+ after_skip=None,
710
+ inferred=inferred,
711
+ contested=mutation.contested,
712
+ )
713
+ detection_truncated = bool(projection.detection_truncated_turns & active_turn_numbers)
714
+ metric_precision = Precision.UNRESOLVED if detection_truncated else Precision.EXACT
715
+ metric_reason = "recorded/observed file counts; mutation detection was truncated" if detection_truncated else None
716
+ created = modified = deleted = net_zero = 0
717
+ rows: list[ChangeVerificationRow] = []
718
+ successful_verifies = [
719
+ action for action in actions if action.classification is ActionClass.VERIFY and tool_succeeded(action.outcome)
720
+ ]
721
+ # Batched tool calls run concurrently, so an action having *started*
722
+ # before a verify proves nothing about what the verify observed; the
723
+ # verify vouches for a same-turn change only when it began after every
724
+ # action that could have produced the change finished landing. Mutation
725
+ # rows carry no operation attribution, so the ordering must clear every
726
+ # potential mutator — edits, the other-classified actions (shell
727
+ # commands and the like), and the verify-classified peers of the
728
+ # candidate verify (a fixer like `ruff --fix` matches the word list
729
+ # yet mutates) — while the row's evidence and the "orderable at all"
730
+ # question stay with the edit-classified actions alone.
731
+ edit_terminals_by_turn: dict[int, list[int | None]] = {turn_number: [] for turn_number in active_turn_numbers}
732
+ mutator_terminals_by_turn: dict[int, list[int | None]] = {turn_number: [] for turn_number in active_turn_numbers}
733
+ verify_terminals_by_turn: dict[int, list[tuple[str, int | None]]] = {
734
+ turn_number: [] for turn_number in active_turn_numbers
735
+ }
736
+ for action in actions:
737
+ if action.turn_number not in active_turn_numbers:
738
+ continue
739
+ if action.classification is ActionClass.EDIT:
740
+ edit_terminals_by_turn[action.turn_number].append(action.end_sequence)
741
+ if action.classification in (ActionClass.EDIT, ActionClass.OTHER):
742
+ mutator_terminals_by_turn[action.turn_number].append(action.end_sequence)
743
+ if action.classification is ActionClass.VERIFY:
744
+ verify_terminals_by_turn[action.turn_number].append((action.operation_id, action.end_sequence))
745
+ # A shell action whose command carrier is unavailable could have been
746
+ # the verify an unverified row claims never happened, and a recognized
747
+ # verify whose terminal outcome never resolved could have been the
748
+ # success; an estimated shell action may have been verification work
749
+ # the word list missed.
750
+ verify_absence_precision = (
751
+ Precision.UNRESOLVED
752
+ if any(
753
+ (action.tool_kind == "shell" and action.classification_precision is Precision.UNRESOLVED)
754
+ or (
755
+ action.classification is ActionClass.VERIFY
756
+ and (action.outcome_precision is not Precision.EXACT or action.outcome == ToolOutcome.UNKNOWN)
757
+ )
758
+ for action in actions
759
+ )
760
+ else Precision.ESTIMATED
761
+ if any(
762
+ action.tool_kind == "shell" and action.classification_precision is Precision.ESTIMATED for action in actions
763
+ )
764
+ else Precision.EXACT
765
+ )
766
+ edit_evidence_by_turn = {
767
+ turn_number: tuple(
768
+ sorted(
769
+ action.evidence_key
770
+ for action in actions
771
+ if action.turn_number == turn_number
772
+ and action.classification is ActionClass.EDIT
773
+ and action.evidence_key is not None
774
+ )
775
+ )
776
+ for turn_number in active_turn_numbers
777
+ }
778
+ unprovable_net_zero = False
779
+ for path, (diff, last_turn) in folded.items():
780
+ _check_cancelled(cancel_event)
781
+ if not diff.before_exists and diff.after_exists:
782
+ created += 1
783
+ elif diff.before_exists and not diff.after_exists:
784
+ deleted += 1
785
+ else:
786
+ modified += 1
787
+ # A window-inferred or peer-contested fold may describe another
788
+ # writer's change, so no row built from one can claim exactness.
789
+ provenance_precision = Precision.ESTIMATED if diff.inferred or diff.contested else Precision.EXACT
790
+ if diff.is_net_zero:
791
+ net_zero += 1
792
+ state = ChangeVerificationState.NET_ZERO
793
+ if diff.content_unavailable:
794
+ # Both content backups were withheld: existence never
795
+ # flipped, but a return to the original bytes cannot be
796
+ # proven either way.
797
+ unprovable_net_zero = True
798
+ row_precision = Precision.UNRESOLVED
799
+ else:
800
+ row_precision = least_precision((metric_precision, provenance_precision))
801
+ else:
802
+ latest_verify = successful_verifies[-1] if successful_verifies else None
803
+ edit_terminals = edit_terminals_by_turn.get(last_turn, [])
804
+ mutator_terminals = list(mutator_terminals_by_turn.get(last_turn, []))
805
+ if latest_verify is not None:
806
+ mutator_terminals.extend(
807
+ end_sequence
808
+ for operation_id, end_sequence in verify_terminals_by_turn.get(last_turn, [])
809
+ if operation_id != latest_verify.operation_id
810
+ )
811
+ orderable = bool(edit_terminals) and None not in mutator_terminals
812
+ if latest_verify is None or latest_verify.turn_number is None:
813
+ state = ChangeVerificationState.UNVERIFIED
814
+ row_precision = least_precision((metric_precision, provenance_precision, verify_absence_precision))
815
+ elif latest_verify.turn_number == last_turn and not orderable:
816
+ # The turn's changes came from actions that never classify
817
+ # as edits (shell commands classify as verify or other) or
818
+ # from a potential mutator whose terminal never landed, so
819
+ # the verify cannot be ordered against the change it would
820
+ # need to follow.
821
+ state = ChangeVerificationState.UNVERIFIED
822
+ row_precision = Precision.UNRESOLVED
823
+ elif latest_verify.turn_number > last_turn or (
824
+ latest_verify.turn_number == last_turn
825
+ and latest_verify.start_sequence > max(cast("list[int]", mutator_terminals))
826
+ ):
827
+ state = ChangeVerificationState.VERIFIED
828
+ row_precision = least_precision(
829
+ (metric_precision, provenance_precision, latest_verify.classification_precision)
830
+ )
831
+ else:
832
+ # The state leans on having identified the candidate as a
833
+ # verify AND on no unclassifiable shell action having been
834
+ # a later verify that would upgrade the row, so both
835
+ # precisions travel with it.
836
+ state = ChangeVerificationState.AFTER_VERIFY
837
+ row_precision = least_precision(
838
+ (
839
+ metric_precision,
840
+ provenance_precision,
841
+ latest_verify.classification_precision,
842
+ verify_absence_precision,
843
+ )
844
+ )
845
+ rows.append(
846
+ ChangeVerificationRow(
847
+ path,
848
+ state,
849
+ last_turn,
850
+ row_precision,
851
+ edit_evidence_by_turn.get(last_turn, ()),
852
+ )
853
+ )
854
+ diagnostics = [metric_reason] if metric_reason is not None else []
855
+ count_precision = metric_precision
856
+ if any(diff.inferred or diff.contested for diff, _ in folded.values()):
857
+ # The counts then include folds that may describe another writer's
858
+ # change, so they cannot pass for exact per-session totals.
859
+ count_precision = least_precision((count_precision, Precision.ESTIMATED))
860
+ diagnostics.append("counts include window-inferred or peer-contested mutations")
861
+ count_reason = "; ".join(diagnostics) if diagnostics else None
862
+ net_zero_precision = count_precision
863
+ net_zero_reason = count_reason
864
+ if unprovable_net_zero:
865
+ net_zero_precision = least_precision((net_zero_precision, Precision.UNRESOLVED))
866
+ net_zero_reason = "; ".join(
867
+ (*diagnostics, "count includes files whose withheld content backups leave the net change unprovable")
868
+ )
869
+ return ChangeVerification(
870
+ detail_available=True,
871
+ detection_truncated=detection_truncated,
872
+ files_touched=Metric(len(folded), count_precision, count_reason),
873
+ created=Metric(created, count_precision, count_reason),
874
+ modified=Metric(modified, count_precision, count_reason),
875
+ deleted=Metric(deleted, count_precision, count_reason),
876
+ net_zero=Metric(net_zero, net_zero_precision, net_zero_reason),
877
+ rows=tuple(sorted(rows, key=lambda row: row.path)),
878
+ )
879
+
880
+
881
+ def _degrade_change_verification(change: ChangeVerification, reason: str) -> ChangeVerification:
882
+ """Cap every session-wide mutation result when the source log is incomplete."""
883
+
884
+ return ChangeVerification(
885
+ detail_available=change.detail_available,
886
+ detection_truncated=change.detection_truncated,
887
+ files_touched=_cap_session_metric(change.files_touched, reason),
888
+ created=_cap_session_metric(change.created, reason),
889
+ modified=_cap_session_metric(change.modified, reason),
890
+ deleted=_cap_session_metric(change.deleted, reason),
891
+ net_zero=_cap_session_metric(change.net_zero, reason),
892
+ rows=tuple(
893
+ row if row.precision is Precision.MISSING else replace(row, precision=Precision.UNRESOLVED)
894
+ for row in change.rows
895
+ ),
896
+ )