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,983 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """The trajectory dashboard widget: tabs, loading and live refresh, and the
4
+ presentation cache that feeds the text view the pages render into."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ from dataclasses import replace
10
+ from enum import StrEnum
11
+ from functools import partial
12
+ from pathlib import Path
13
+ from threading import Event
14
+ from time import monotonic
15
+ from typing import TYPE_CHECKING, Any, ClassVar
16
+
17
+ from rich.style import Style
18
+ from rich.text import Text
19
+ from textual import events, on
20
+ from textual.cache import LRUCache
21
+ from textual.containers import Container, VerticalGroup
22
+ from textual.css.query import NoMatches
23
+ from textual.geometry import Size
24
+ from textual.message import Message
25
+ from textual.widgets import Tab, Tabs
26
+
27
+ from chrys.app.tui.binding_display import localized_binding
28
+ from chrys.app.tui.clipboard import copy_text_to_clipboards
29
+ from chrys.app.tui.copy_messages import COPIED_TITLE
30
+ from chrys.app.tui.support.file_manager import can_open_in_file_manager, open_in_file_manager
31
+ from chrys.app.tui.support.gc_freeze import (
32
+ DetachedLruCache,
33
+ GcFreezeBlockReason,
34
+ detach_lru_cache,
35
+ renew_lru_cache,
36
+ )
37
+ from chrys.app.tui.widgets.chat.session_json import SessionJsonPanel
38
+ from chrys.app.tui.widgets.hatch import hatch_text_style, hatched_text_line
39
+ from chrys.app.tui.widgets.loading import ChrysLoadingIndicator
40
+ from chrys.app.tui.widgets.trajectory.insights import has_diagnostic_content, insights_lines
41
+ from chrys.app.tui.widgets.trajectory.overview import overview_lines
42
+ from chrys.app.tui.widgets.trajectory.presentation import (
43
+ NO_TURNS,
44
+ PRECISION_SYMBOLS,
45
+ TURN_LABEL,
46
+ UNAVAILABLE,
47
+ DashboardLook,
48
+ RenderContext,
49
+ ResponsiveTier,
50
+ precision_label,
51
+ precision_style,
52
+ render_message,
53
+ )
54
+ from chrys.app.tui.widgets.trajectory.session_info import SessionStorage, collect_session_storage
55
+ from chrys.app.tui.widgets.trajectory.text_view import TrajectoryTextView
56
+ from chrys.app.tui.widgets.trajectory.timeline import dependency_graph_lines, timeline_lines
57
+ from chrys.foundation.config.settings import DEFAULT_TRAJECTORY_VERIFY_COMMANDS
58
+ from chrys.foundation.i18n import Localizer, MessageDef, MessageRef, msg
59
+ from chrys.foundation.platform.files import surrogate_safe_text
60
+ from chrys.foundation.util.session_ids import session_short_id
61
+ from chrys.service.analytics import (
62
+ AnalysisAvailability,
63
+ TrajectoryAnalysis,
64
+ TrajectoryAnalyzer,
65
+ TrajectoryScanCancelled,
66
+ TurnAnalysis,
67
+ )
68
+
69
+ if TYPE_CHECKING:
70
+ from collections.abc import Callable
71
+
72
+ from textual.app import ComposeResult
73
+ from textual.timer import Timer
74
+ from textual.worker import Worker
75
+
76
+ from chrys.app.tui.i18n import LocaleController
77
+
78
+ # The session folder's sizes live outside the events log, so the live refresh
79
+ # recollects them on this coarser clock instead of walking the tree every tick.
80
+ _STORAGE_REFRESH_INTERVAL_S = 5.0
81
+
82
+ _DASHBOARD_TITLE = msg("tui.trajectory.title", fallback="Trajectory")
83
+ _OVERVIEW_TAB = msg("tui.trajectory.tab.overview", fallback="Overview")
84
+ _TIMELINE_TAB = msg("tui.trajectory.tab.timeline", fallback="Timeline")
85
+ _INSIGHTS_TAB = msg("tui.trajectory.tab.insights", fallback="Insights")
86
+ _SESSION_DATA_TAB = msg("tui.trajectory.tab.session_data", fallback="Session data")
87
+ _TOGGLE_GRAPH_BINDING = msg("tui.binding.toggle_graph", fallback="Dependency graph")
88
+ _READ_ERROR = msg("tui.trajectory.read_error", fallback="Trajectory read error: {error}")
89
+ _NO_ACTIVE_SESSION = msg("tui.main.session_json.no_active_session", fallback="No active session.")
90
+ _SESSION_INFO_PATH_COPIED = msg("tui.trajectory.session_info.path_copied", fallback="Path copied")
91
+ _SESSION_INFO_OPEN_FAILED = msg(
92
+ "tui.trajectory.session_info.open_failed",
93
+ fallback="Could not open the session folder: {error}",
94
+ )
95
+ _SESSION_INFO_OPEN_UNAVAILABLE = msg(
96
+ "tui.trajectory.session_info.open_unavailable",
97
+ fallback="Opening the session folder is not available in the current environment.",
98
+ )
99
+
100
+ _TABS_HEIGHT = 2
101
+
102
+
103
+ class DashboardTab(StrEnum):
104
+ """The frozen four-tab dashboard information architecture."""
105
+
106
+ OVERVIEW = "overview"
107
+ TIMELINE = "timeline"
108
+ INSIGHTS = "insights"
109
+ SESSION_DATA = "session-data"
110
+
111
+
112
+ class TrajectoryDashboard(Container):
113
+ """Own dashboard foreground and active-tab state as one atomic pair."""
114
+
115
+ can_focus = True
116
+
117
+ BINDINGS: ClassVar[list] = [
118
+ localized_binding("space", "toggle_timeline_dependencies", _TOGGLE_GRAPH_BINDING, show=False),
119
+ ]
120
+
121
+ COMPONENT_CLASSES: ClassVar[set[str]] = {"trajectory-dashboard--muted-label", "hatch--pattern"}
122
+
123
+ DEFAULT_CSS = """
124
+ TrajectoryDashboard > .hatch--pattern {
125
+ color: $hatch-color;
126
+ }
127
+ TrajectoryDashboard > .trajectory-dashboard--muted-label {
128
+ color: $text-muted;
129
+ }
130
+ TrajectoryDashboard {
131
+ height: 1fr;
132
+ display: none;
133
+ layer: overlay;
134
+ layers: default loading;
135
+ background: $background;
136
+ padding: 0 1;
137
+ border: round $tui-border-accent $border-opacity;
138
+ border-title-align: left;
139
+ border-title-color: $tui-border-title-accent;
140
+ border-subtitle-align: right;
141
+ border-subtitle-color: $tui-border-title-accent;
142
+ }
143
+ TrajectoryDashboard > Tabs {
144
+ height: 2;
145
+ }
146
+ TrajectoryDashboard > #timeline-turn-tabs {
147
+ display: none;
148
+ }
149
+ /* The loading state floats on its own layer below the tab strip, so the
150
+ same indicator covers either the hidden text view or the Session Data
151
+ viewer while that viewer's background load is still running. */
152
+ TrajectoryDashboard > #trajectory-loading-state {
153
+ display: none;
154
+ layer: loading;
155
+ margin-top: 2;
156
+ width: 100%;
157
+ height: 1fr;
158
+ min-height: 1;
159
+ align: center middle;
160
+ background: $background;
161
+ }
162
+ TrajectoryDashboard > #trajectory-loading-state > ChrysLoadingIndicator {
163
+ width: 12;
164
+ height: 1;
165
+ color: $primary;
166
+ }
167
+ TrajectoryDashboard > SessionJsonPanel {
168
+ height: 1fr;
169
+ border: none;
170
+ }
171
+ """
172
+
173
+ class StateChanged(Message):
174
+ """The foreground derivations need synchronization by MainScreen."""
175
+
176
+ def __init__(
177
+ self,
178
+ *,
179
+ locale_controller: LocaleController | None = None,
180
+ verify_commands: str = DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
181
+ ) -> None:
182
+ super().__init__()
183
+ self._locale_controller = locale_controller
184
+ self._verify_commands = verify_commands
185
+ self.foreground = False
186
+ self.active_tab = DashboardTab.OVERVIEW
187
+ self._timeline_dependencies = False
188
+ self._selected_turn_id: str | None = None
189
+ self._session_id = ""
190
+ self._path: Path | None = None
191
+ self._session_json_loaded = False
192
+ self._turn_tab_key: tuple[tuple[str, str], ...] = ()
193
+ self._analyzer: TrajectoryAnalyzer | None = None
194
+ self._analysis: TrajectoryAnalysis | None = None
195
+ self._session_storage: SessionStorage | None = None
196
+ self._can_open_session_folder = can_open_in_file_manager()
197
+ self._storage_collected_at = -_STORAGE_REFRESH_INTERVAL_S
198
+ self._load_generation = 0
199
+ self._load_pending = False
200
+ self._worker: Worker[Any] | None = None
201
+ self._scan_cancel_event: Event | None = None
202
+ self._live_timer: Timer | None = None
203
+ self._shell_restore_foreground = False
204
+ self._available_size = Size(0, 0)
205
+ self._presentation_key: tuple[int, int, int] | None = None
206
+ self._presentation_revision = 0
207
+ self._render_identity: tuple[DashboardTab, str | None] | None = None
208
+ self._presentation_cache: LRUCache[tuple[object, ...], tuple[Text, ...]] | DetachedLruCache = LRUCache(
209
+ maxsize=64
210
+ )
211
+ self._update_border_labels()
212
+
213
+ @property
214
+ def chat_foreground(self) -> bool:
215
+ return not self.foreground
216
+
217
+ @property
218
+ def session_json_visible(self) -> bool:
219
+ return self.foreground and self.active_tab is DashboardTab.SESSION_DATA
220
+
221
+ @property
222
+ def responsive_tier(self) -> ResponsiveTier:
223
+ return ResponsiveTier.for_width(self._available_width())
224
+
225
+ def compose(self) -> ComposeResult:
226
+ yield Tabs(
227
+ Tab(Text(self._render_message(_OVERVIEW_TAB.bind())), id=DashboardTab.OVERVIEW),
228
+ Tab(Text(self._render_message(_TIMELINE_TAB.bind())), id=DashboardTab.TIMELINE),
229
+ Tab(Text(self._render_message(_INSIGHTS_TAB.bind())), id=DashboardTab.INSIGHTS),
230
+ Tab(Text(self._render_message(_SESSION_DATA_TAB.bind())), id=DashboardTab.SESSION_DATA),
231
+ active=self.active_tab,
232
+ id="trajectory-tabs",
233
+ )
234
+ yield Tabs(id="timeline-turn-tabs")
235
+ with VerticalGroup(id="trajectory-loading-state"):
236
+ yield ChrysLoadingIndicator(id="trajectory-loading")
237
+ yield TrajectoryTextView(
238
+ on_resized=self._render_for_settled_view,
239
+ open_session_folder=self.open_session_folder,
240
+ copy_session_path=self.copy_session_path,
241
+ )
242
+ yield SessionJsonPanel(locale_controller=self._locale_controller)
243
+
244
+ def on_mount(self) -> None:
245
+ if self._locale_controller is not None:
246
+ self._locale_controller.register_surface(self)
247
+ self._live_timer = self.set_interval(0.5, self._refresh_live)
248
+ self._live_timer.pause()
249
+ self._sync_content_visibility()
250
+
251
+ def on_unmount(self) -> None:
252
+ if self._locale_controller is not None:
253
+ self._locale_controller.unregister_surface(self)
254
+ self._cancel_worker()
255
+ analyzer = self._analyzer
256
+ if analyzer is not None:
257
+ analyzer.release()
258
+ self._analyzer = None
259
+ self._analysis = None
260
+
261
+ def on_resize(self, event: events.Resize) -> None:
262
+ """Invalidate presentation only; trajectory analysis never depends on size."""
263
+ content_size = self.size
264
+ if content_size == self._available_size:
265
+ return
266
+ self._available_size = content_size
267
+ if self.foreground and not self.session_json_visible:
268
+ self._render_active_view()
269
+
270
+ def _render_for_settled_view(self) -> None:
271
+ """Re-render after the text view's own size settles asynchronously."""
272
+ if self.is_mounted and self.foreground and not self.session_json_visible:
273
+ self._render_active_view()
274
+
275
+ def notify_style_update(self) -> None:
276
+ """Rebuild presentation lines that contain resolved theme colors."""
277
+ super().notify_style_update()
278
+ self._presentation_revision += 1
279
+ self._update_border_labels()
280
+ self._clear_presentation_cache()
281
+ self._render_active_view()
282
+
283
+ def refresh_localization(self) -> None:
284
+ self._presentation_revision += 1
285
+ self._update_border_labels()
286
+ for tab, label in (
287
+ (DashboardTab.OVERVIEW, _OVERVIEW_TAB),
288
+ (DashboardTab.TIMELINE, _TIMELINE_TAB),
289
+ (DashboardTab.INSIGHTS, _INSIGHTS_TAB),
290
+ (DashboardTab.SESSION_DATA, _SESSION_DATA_TAB),
291
+ ):
292
+ self.query_one(f"#{tab}", Tab).label = Text(self._render_message(label.bind()))
293
+ self._clear_presentation_cache()
294
+ self._render_active_view()
295
+
296
+ def show_session(self, session_id: str, path: Path | None) -> None:
297
+ changed = session_id != self._session_id or path != self._path
298
+ self.foreground = True
299
+ self.display = True
300
+ self._session_id = session_id
301
+ self._path = path
302
+ self._update_border_labels()
303
+ if changed:
304
+ self._session_json_loaded = False
305
+ if changed or self._analysis is None:
306
+ self._release_analysis()
307
+ self._start_load()
308
+ self._sync_live_timer()
309
+ self._sync_content_visibility()
310
+ self.query_one("#trajectory-tabs", Tabs).focus()
311
+
312
+ def set_verify_commands(self, value: str) -> None:
313
+ """Apply the live classification word list and rebuild the current projection."""
314
+ if value == self._verify_commands:
315
+ return
316
+ self._verify_commands = value
317
+ if self.foreground and self._path is not None:
318
+ self._release_analysis()
319
+ self._start_load()
320
+
321
+ def hide_dashboard(self) -> None:
322
+ self.foreground = False
323
+ self.display = False
324
+ self._sync_live_timer()
325
+ self._sync_content_visibility()
326
+ self._release_analysis()
327
+
328
+ def select_turn(self, turn_id: str) -> None:
329
+ turn = self._analysis.turn(turn_id) if self._analysis is not None else None
330
+ self._selected_turn_id = turn.turn_id if turn is not None else turn_id
331
+ self._timeline_dependencies = False
332
+ self.active_tab = DashboardTab.TIMELINE
333
+ self.query_one("#trajectory-tabs", Tabs).active = DashboardTab.TIMELINE
334
+ self._update_border_labels()
335
+ self._sync_live_timer()
336
+ self._sync_content_visibility()
337
+ self._render_active_view()
338
+ self.post_message(self.StateChanged())
339
+
340
+ def suspend_for_shell_mode(self) -> bool:
341
+ self._shell_restore_foreground = self.foreground
342
+ restore_json = self.session_json_visible
343
+ self.foreground = False
344
+ self.display = False
345
+ self._sync_live_timer()
346
+ self._release_analysis()
347
+ return restore_json
348
+
349
+ def finish_shell_mode(self) -> bool:
350
+ restore = self._shell_restore_foreground
351
+ self._shell_restore_foreground = False
352
+ if restore:
353
+ self.show_session(self._session_id, self._path)
354
+ return restore
355
+
356
+ def gc_freeze_block_reason(self) -> GcFreezeBlockReason | None:
357
+ if self.foreground and not self.session_json_visible:
358
+ return GcFreezeBlockReason.TRAJECTORY_DASHBOARD_VISIBLE
359
+ return None
360
+
361
+ def prepare_for_gc_freeze(self) -> None:
362
+ if self.foreground and not self.session_json_visible:
363
+ return
364
+ self.query_one(TrajectoryTextView).detach_cache()
365
+ self._presentation_cache = detach_lru_cache(self._presentation_cache)
366
+
367
+ def after_gc_freeze(self) -> None:
368
+ self.query_one(TrajectoryTextView).renew_cache()
369
+ self._presentation_cache = renew_lru_cache(self._presentation_cache)
370
+
371
+ def abort_gc_freeze(self) -> None:
372
+ self.after_gc_freeze()
373
+
374
+ @on(Tabs.TabActivated, "#trajectory-tabs")
375
+ def _on_tab_activated(self, event: Tabs.TabActivated) -> None:
376
+ if event.tab.id is None:
377
+ return
378
+ self.active_tab = DashboardTab(event.tab.id)
379
+ self._update_border_labels()
380
+ self._sync_live_timer()
381
+ self._sync_content_visibility()
382
+ self._render_active_view()
383
+ if self.foreground:
384
+ if self.session_json_visible:
385
+ self.query_one(SessionJsonPanel).focus()
386
+ else:
387
+ self.query_one(TrajectoryTextView).focus()
388
+ self.post_message(self.StateChanged())
389
+
390
+ @on(Tabs.TabActivated, "#timeline-turn-tabs")
391
+ def _on_turn_tab_activated(self, event: Tabs.TabActivated) -> None:
392
+ event.stop()
393
+ if event.tabs is not self._query_turn_tabs() or event.tab.id is None:
394
+ return
395
+ index = int(event.tab.id.removeprefix("turn-"))
396
+ if index >= len(self._turn_tab_key):
397
+ return
398
+ turn_id = self._turn_tab_key[index][0]
399
+ if turn_id == self._selected_turn_id:
400
+ return
401
+ self._selected_turn_id = turn_id
402
+ self._render_active_view()
403
+
404
+ def action_toggle_timeline_dependencies(self) -> None:
405
+ """Flip the timeline tab between the time axis and the dependency graph."""
406
+ if not self.foreground or self.active_tab is not DashboardTab.TIMELINE or self.session_json_visible:
407
+ return
408
+ self._timeline_dependencies = not self._timeline_dependencies
409
+ self._render_active_view()
410
+
411
+ @on(SessionJsonPanel.LoadStateChanged)
412
+ def _on_session_json_load_state_changed(self, event: SessionJsonPanel.LoadStateChanged) -> None:
413
+ event.stop()
414
+ self._sync_content_visibility()
415
+
416
+ def _sync_content_visibility(self) -> None:
417
+ if not self.is_mounted:
418
+ return
419
+ session_json = self.query_one(SessionJsonPanel)
420
+ text_view = self.query_one(TrajectoryTextView)
421
+ show_json = self.session_json_visible
422
+ if show_json:
423
+ session_json.display = True
424
+ if not self._session_json_loaded:
425
+ if self._session_id:
426
+ session_json.load_session(self._session_id)
427
+ else:
428
+ session_json.set_status(self._render_message(_NO_ACTIVE_SESSION.bind()))
429
+ self._session_json_loaded = True
430
+ else:
431
+ session_json.hide_session_json()
432
+ self._session_json_loaded = False
433
+ # One indicator for both slow paths: the analysis scan behind the
434
+ # hidden text view, and the Session Data viewer's background load
435
+ # (which must stay displayed so its worker can commit).
436
+ show_loading = self.foreground and (session_json.is_loading if show_json else self._load_pending)
437
+ text_view.set_class(self.active_tab is DashboardTab.OVERVIEW, "-overview")
438
+ text_view.set_class(self.active_tab is DashboardTab.TIMELINE, "-timeline")
439
+ text_view.display = self.foreground and not show_json and not show_loading
440
+ self.query_one("#trajectory-loading-state").display = show_loading
441
+ turn_tabs = self._query_turn_tabs()
442
+ if turn_tabs is not None:
443
+ turn_tabs.display = self._turn_tabs_visible()
444
+
445
+ def _turn_tabs_visible(self) -> bool:
446
+ return (
447
+ self.foreground
448
+ and not self.session_json_visible
449
+ and bool(self._turn_tab_key)
450
+ and self.active_tab is DashboardTab.TIMELINE
451
+ )
452
+
453
+ def _sync_turn_tabs(self, analysis: TrajectoryAnalysis | None) -> None:
454
+ turns = (
455
+ analysis.turns if analysis is not None and analysis.availability is AnalysisAvailability.AVAILABLE else ()
456
+ )
457
+ key = tuple(
458
+ (turn.turn_id, self._render_message(TURN_LABEL.bind(turn=turn.turn_number or "—"))) for turn in turns
459
+ )
460
+ if key != self._turn_tab_key:
461
+ self._turn_tab_key = key
462
+ # Rebuild by replacement: remove-then-mount runs atomically inside
463
+ # one callback, and building at execution time keeps the active tab
464
+ # aligned with whatever the selection is once the callback runs.
465
+ self.call_next(self._replace_turn_tabs)
466
+ return
467
+ tabs = self._query_turn_tabs()
468
+ if tabs is None:
469
+ return
470
+ active = self._active_turn_tab_id()
471
+ if active is not None and tabs.active != active and tabs.query(f"#{active}"):
472
+ tabs.active = active
473
+ tabs.display = self._turn_tabs_visible()
474
+
475
+ def _query_turn_tabs(self) -> Tabs | None:
476
+ # A pending replacement leaves a gap between remove and mount; callers
477
+ # skip the sync then because the mount applies the fresh state itself.
478
+ try:
479
+ return self.query_one("#timeline-turn-tabs", Tabs)
480
+ except NoMatches:
481
+ return None
482
+
483
+ def _active_turn_tab_id(self) -> str | None:
484
+ return next(
485
+ (
486
+ f"turn-{index}"
487
+ for index, (turn_id, _) in enumerate(self._turn_tab_key)
488
+ if turn_id == self._selected_turn_id
489
+ ),
490
+ None,
491
+ )
492
+
493
+ async def _replace_turn_tabs(self) -> None:
494
+ old = self.query_one("#timeline-turn-tabs", Tabs)
495
+ anchor = self.query_one("#trajectory-tabs", Tabs)
496
+ replacement = Tabs(
497
+ *(Tab(Text(label), id=f"turn-{index}") for index, (_, label) in enumerate(self._turn_tab_key)),
498
+ active=self._active_turn_tab_id(),
499
+ id="timeline-turn-tabs",
500
+ )
501
+ await old.remove()
502
+ replacement.display = self._turn_tabs_visible()
503
+ await self.mount(replacement, after=anchor)
504
+
505
+ def _update_border_labels(self) -> None:
506
+ self.border_title = Text(self._render_message(_DASHBOARD_TITLE.bind()))
507
+ if not self._session_id:
508
+ self.border_subtitle = Text("")
509
+ return
510
+ if self.active_tab is DashboardTab.SESSION_DATA and self._path is not None:
511
+ self.border_subtitle = Text(surrogate_safe_text(str(self._path.parents[1] / "session.json")))
512
+ return
513
+ subtitle = self._precision_legend()
514
+ subtitle.append(" · ")
515
+ subtitle.append(session_short_id(self._session_id))
516
+ self.border_subtitle = subtitle
517
+
518
+ def _sync_live_timer(self) -> None:
519
+ timer = self._live_timer
520
+ if timer is None:
521
+ return
522
+ if self.foreground and self.active_tab in {DashboardTab.OVERVIEW, DashboardTab.TIMELINE}:
523
+ timer.resume()
524
+ else:
525
+ timer.pause()
526
+
527
+ def _start_load(self) -> None:
528
+ self._cancel_worker()
529
+ self._load_generation += 1
530
+ generation = self._load_generation
531
+ analyzer = TrajectoryAnalyzer(verify_commands=self._verify_commands)
532
+ path = self._path
533
+ cancel_event = Event()
534
+ self._analyzer = analyzer
535
+ self._scan_cancel_event = cancel_event
536
+ self.query_one(TrajectoryTextView).clear_lines()
537
+ # The text view stays hidden behind the loading indicator until the
538
+ # first analysis lands; a long scan must not read as "no data".
539
+ self._load_pending = True
540
+ self._worker = self.run_worker(
541
+ partial(self._load, generation, analyzer, path, cancel_event),
542
+ group="trajectory-analysis",
543
+ exclusive=True,
544
+ )
545
+ self._sync_turn_tabs(None)
546
+ self._sync_content_visibility()
547
+
548
+ async def _load(
549
+ self,
550
+ generation: int,
551
+ analyzer: TrajectoryAnalyzer,
552
+ path: Path | None,
553
+ cancel_event: Event,
554
+ ) -> None:
555
+ storage: SessionStorage | None = None
556
+ if path is None:
557
+ analysis = None
558
+ else:
559
+ try:
560
+ analysis, storage = await asyncio.to_thread(
561
+ partial(_load_with_storage, analyzer, path, cancel_event=cancel_event)
562
+ )
563
+ except TrajectoryScanCancelled:
564
+ return
565
+ if generation != self._load_generation or analyzer is not self._analyzer or not self.foreground:
566
+ return
567
+ self._analysis = analysis
568
+ self._session_storage = storage
569
+ if storage is not None:
570
+ self._storage_collected_at = monotonic()
571
+ self._load_pending = False
572
+ self._presentation_revision += 1
573
+ self._sync_content_visibility()
574
+ self._render_active_view()
575
+ # The display flip above lands in the next layout pass, so the text
576
+ # view's region is still the placeholder's here; render again once the
577
+ # view settles or the first frame keeps the stale measurements.
578
+ self.call_after_refresh(self._render_for_settled_view)
579
+
580
+ async def _refresh_live(self) -> None:
581
+ analyzer = self._analyzer
582
+ worker = self._worker
583
+ if (
584
+ not self.foreground
585
+ or analyzer is None
586
+ or self._analysis is None
587
+ or (worker is not None and worker.is_running)
588
+ ):
589
+ return
590
+ self._load_generation += 1
591
+ generation = self._load_generation
592
+ cancel_event = Event()
593
+ self._scan_cancel_event = cancel_event
594
+ self._worker = self.run_worker(
595
+ partial(self._refresh, generation, analyzer, cancel_event),
596
+ group="trajectory-analysis",
597
+ exclusive=True,
598
+ )
599
+
600
+ async def _refresh(self, generation: int, analyzer: TrajectoryAnalyzer, cancel_event: Event) -> None:
601
+ # Storage lives outside the events log, so a quiet log must not pin it
602
+ # forever: recollect on a coarse clock even when the analysis is unchanged.
603
+ collect_storage = monotonic() - self._storage_collected_at >= _STORAGE_REFRESH_INTERVAL_S
604
+ try:
605
+ analysis, storage = await asyncio.to_thread(
606
+ partial(
607
+ _refresh_with_storage,
608
+ analyzer,
609
+ collect_storage=collect_storage,
610
+ cancel_event=cancel_event,
611
+ )
612
+ )
613
+ except TrajectoryScanCancelled:
614
+ return
615
+ if generation != self._load_generation or analyzer is not self._analyzer or not self.foreground:
616
+ return
617
+ changed = analysis is not self._analysis
618
+ if storage is not None:
619
+ self._storage_collected_at = monotonic()
620
+ if storage != self._session_storage:
621
+ self._session_storage = storage
622
+ changed = True
623
+ if not changed:
624
+ return
625
+ self._analysis = analysis
626
+ self._presentation_revision += 1
627
+ self._render_active_view()
628
+
629
+ def _cancel_worker(self) -> None:
630
+ self._load_generation += 1
631
+ self._load_pending = False
632
+ cancel_event = self._scan_cancel_event
633
+ if cancel_event is not None:
634
+ cancel_event.set()
635
+ self._scan_cancel_event = None
636
+ worker = self._worker
637
+ if worker is not None:
638
+ worker.cancel()
639
+ self._worker = None
640
+
641
+ def _release_analysis(self) -> None:
642
+ self._cancel_worker()
643
+ analyzer = self._analyzer
644
+ if analyzer is not None:
645
+ analyzer.release()
646
+ self._analyzer = None
647
+ self._analysis = None
648
+ self._session_storage = None
649
+ self._storage_collected_at = -_STORAGE_REFRESH_INTERVAL_S
650
+ self._presentation_key = None
651
+ self._render_identity = None
652
+ self._clear_presentation_cache()
653
+ if self.is_mounted:
654
+ self.query_one(TrajectoryTextView).release()
655
+
656
+ def _clear_presentation_cache(self) -> None:
657
+ if not isinstance(self._presentation_cache, DetachedLruCache):
658
+ self._presentation_cache.clear()
659
+
660
+ def _render_active_view(self) -> None:
661
+ if not self.is_mounted or self.session_json_visible or self._load_pending:
662
+ return
663
+ analysis = self._analysis
664
+ generation = analysis.generation if analysis is not None else -1
665
+ width = self._available_width()
666
+ height = self._available_height()
667
+ self._presentation_key = (generation, width, height)
668
+ turn = self._resolve_selected_turn(analysis) if self.active_tab is DashboardTab.TIMELINE else None
669
+ selected = self._selected_turn_id if self.active_tab is DashboardTab.TIMELINE else None
670
+ # The empty state and the hatch fill below short content are built for
671
+ # the text view's own region, which settles a beat after this widget
672
+ # resizes; the region (and the scrollbar state feeding the settle math)
673
+ # must key the cache so a build for a transitional layout is missed --
674
+ # not hit -- by the re-render that follows the settled view.
675
+ text_view = self.query_one(TrajectoryTextView)
676
+ region = text_view.scrollable_content_region
677
+ cache_key = (
678
+ self.active_tab,
679
+ self._timeline_dependencies,
680
+ selected,
681
+ generation,
682
+ width,
683
+ height,
684
+ region.width,
685
+ region.height,
686
+ text_view.show_vertical_scrollbar,
687
+ text_view.show_horizontal_scrollbar,
688
+ self._verify_commands,
689
+ self._presentation_revision,
690
+ )
691
+ cache = None if isinstance(self._presentation_cache, DetachedLruCache) else self._presentation_cache
692
+ if analysis is None or analysis.availability is not AnalysisAvailability.AVAILABLE or not analysis.turns:
693
+ cache = None
694
+ if not region.width or not region.height:
695
+ cache = None
696
+ cached = cache.get(cache_key) if cache is not None else None
697
+ if cached is not None:
698
+ self._commit_lines(list(cached))
699
+ else:
700
+ lines = self._settled_lines(partial(self._active_lines, analysis, turn))
701
+ if cache is not None:
702
+ cache[cache_key] = tuple(lines)
703
+ self._commit_lines(lines)
704
+ if self.active_tab is DashboardTab.TIMELINE:
705
+ self._sync_turn_tabs(analysis)
706
+
707
+ def _resolve_selected_turn(self, analysis: TrajectoryAnalysis | None) -> TurnAnalysis | None:
708
+ """Settle which turn the Timeline shows before anything keys on it.
709
+
710
+ A selection the analysis still has stays; otherwise (a first visit, or
711
+ the turn is gone) the newest turn is picked. The pick is written back,
712
+ so turns a live session adds later don't move the reader off it.
713
+ """
714
+ if analysis is None or analysis.availability is not AnalysisAvailability.AVAILABLE or not analysis.turns:
715
+ return None
716
+ turn = analysis.turn(self._selected_turn_id) if self._selected_turn_id is not None else None
717
+ turn = turn or analysis.turns[-1]
718
+ self._selected_turn_id = turn.turn_id
719
+ return turn
720
+
721
+ def _active_lines(
722
+ self,
723
+ analysis: TrajectoryAnalysis | None,
724
+ turn: TurnAnalysis | None,
725
+ context: RenderContext,
726
+ height: int,
727
+ ) -> list[Text]:
728
+ """The active tab's lines for *context*; *height* only sizes the empty state."""
729
+ if analysis is None or analysis.availability is AnalysisAvailability.UNAVAILABLE:
730
+ return self._empty_state_lines(UNAVAILABLE, width=context.width, height=height)
731
+ if analysis.availability is AnalysisAvailability.READ_ERROR:
732
+ return [Text(self._render_message(_READ_ERROR.bind(error=analysis.read_error or "")))]
733
+ if self.active_tab is DashboardTab.OVERVIEW:
734
+ if not analysis.turns:
735
+ return self._empty_state_lines(NO_TURNS, width=context.width, height=height)
736
+ return overview_lines(
737
+ self._look(),
738
+ context,
739
+ analysis,
740
+ folder=self._displayed_folder(),
741
+ storage=self._session_storage,
742
+ can_open_folder=self._can_open_session_folder,
743
+ )
744
+ if self.active_tab is DashboardTab.INSIGHTS:
745
+ # Diagnostics alone (a log of only corrupt lines) still make a page.
746
+ if not analysis.turns and not has_diagnostic_content(analysis.diagnostics):
747
+ return self._empty_state_lines(NO_TURNS, width=context.width, height=height)
748
+ return insights_lines(self._look(), context, analysis)
749
+ if self.active_tab is DashboardTab.TIMELINE:
750
+ # The selection is resolved before rendering: no turn means the
751
+ # session has none yet.
752
+ if turn is None:
753
+ return self._empty_state_lines(NO_TURNS, width=context.width, height=height)
754
+ if self._timeline_dependencies:
755
+ return dependency_graph_lines(self._look(), turn)
756
+ return timeline_lines(self._look(), context, turn)
757
+ return []
758
+
759
+ def _settled_lines(self, render: Callable[[RenderContext, int], list[Text]]) -> list[Text]:
760
+ """Build for the region the asynchronous scrollbars settle on.
761
+
762
+ The scrollbars this commit provokes (or retires) resize the region one
763
+ cell after the fact; building for the settled region keeps every right
764
+ edge real instead of cropping it off. *render* takes the layout and the
765
+ height the empty state fills.
766
+ """
767
+ context = self._render_context()
768
+ lines = render(context, self._content_height())
769
+ text_view = self.query_one(TrajectoryTextView)
770
+ region = text_view.scrollable_content_region
771
+ if not region.width or not region.height:
772
+ return lines
773
+ region_width = self._effective_region_width()
774
+ available = self._available_width()
775
+ base_width = region_width + (1 if text_view.show_vertical_scrollbar else 0)
776
+ if available:
777
+ base_width = min(base_width, available)
778
+ base_height = region.height + (1 if text_view.show_horizontal_scrollbar else 0)
779
+ overflows_y = len(lines) > base_height
780
+ settled_width = base_width - (1 if overflows_y else 0)
781
+ overflows_x = any(line.cell_len > settled_width for line in lines)
782
+ settled_height = base_height - (1 if overflows_x else 0)
783
+ if settled_width != region_width or settled_height != region.height:
784
+ lines = render(replace(context, width=max(1, settled_width)), max(1, settled_height))
785
+ return self._hatch_filled(lines, width=settled_width, height=settled_height)
786
+
787
+ def _hatch_filled(self, lines: list[Text], *, width: int, height: int) -> list[Text]:
788
+ """Fill the viewport rows below short content with the hatch pattern."""
789
+ if len(lines) >= height:
790
+ return lines
791
+ hatch_style = self._hatch_style()
792
+ label_style = self._muted_label_style()
793
+ return [
794
+ *lines,
795
+ *(
796
+ hatched_text_line(width, hatch_style=hatch_style, label_style=label_style)
797
+ for _ in range(height - len(lines))
798
+ ),
799
+ ]
800
+
801
+ def _effective_region_width(self) -> int:
802
+ """The text view's region width, bounded by the dashboard's own size.
803
+
804
+ The view's region lags a display flip by one layout pass, so right
805
+ after a load it can still report the width of a previous layout; the
806
+ dashboard's content size is current and caps it.
807
+ """
808
+ width = self.query_one(TrajectoryTextView).scrollable_content_region.width
809
+ available = self._available_width()
810
+ return min(width, available) if width and available else width
811
+
812
+ def _content_width(self) -> int:
813
+ """The width pages lay out in; the dashboard's own before the view has a region."""
814
+ width = self._effective_region_width()
815
+ return max(1, width) if width else max(1, self._available_width())
816
+
817
+ def _content_height(self) -> int:
818
+ """The height the empty state fills; the dashboard's own before the view has a region."""
819
+ height = self.query_one(TrajectoryTextView).scrollable_content_region.height
820
+ return max(1, height) if height else self._available_height()
821
+
822
+ def _render_context(self) -> RenderContext:
823
+ """The layout a page renders for before the scrollbars settle."""
824
+ return RenderContext(width=self._content_width(), tier=self.responsive_tier)
825
+
826
+ def _empty_state_lines(self, message: MessageDef, *, width: int, height: int) -> list[Text]:
827
+ hatch_style = self._hatch_style()
828
+ label_style = self._muted_label_style()
829
+ label = self._render_message(message.bind())
830
+ return [
831
+ hatched_text_line(
832
+ width, label if y == height // 2 else None, hatch_style=hatch_style, label_style=label_style
833
+ )
834
+ for y in range(height)
835
+ ]
836
+
837
+ def _commit_lines(self, lines: list[Text]) -> None:
838
+ # The identity deliberately excludes the analysis generation: a live
839
+ # session bumps it on every appended event, and a refresh of the view
840
+ # the user is already reading must keep their scroll position. Session
841
+ # switches reset the identity through ``_release_analysis``.
842
+ identity = (
843
+ self.active_tab,
844
+ self._selected_turn_id if self.active_tab is DashboardTab.TIMELINE else None,
845
+ )
846
+ reset_scroll = identity != self._render_identity
847
+ self._render_identity = identity
848
+ self.query_one(TrajectoryTextView).set_lines(lines, reset_scroll=reset_scroll)
849
+
850
+ def _displayed_folder(self) -> Path | None:
851
+ """The folder the session info section names: the session directory when
852
+ the events log sits in the store layout, otherwise the log's own parent."""
853
+ path = self._path
854
+ if path is None:
855
+ return None
856
+ session_dir = _session_directory(path)
857
+ return session_dir if session_dir is not None else path.parent
858
+
859
+ def open_session_folder(self) -> None:
860
+ """Reveal the displayed session folder in the desktop file manager."""
861
+ folder = self._displayed_folder()
862
+ if folder is None:
863
+ return
864
+ if not can_open_in_file_manager():
865
+ self.notify(self._render_message(_SESSION_INFO_OPEN_UNAVAILABLE.bind()), severity="warning", markup=False)
866
+ return
867
+ try:
868
+ open_in_file_manager(folder)
869
+ except OSError as error:
870
+ # The OS error text is data, not markup: "[Errno 2] ..." would
871
+ # otherwise be parsed as a content tag.
872
+ self.notify(
873
+ self._render_message(_SESSION_INFO_OPEN_FAILED.bind(error=surrogate_safe_text(str(error)))),
874
+ severity="error",
875
+ markup=False,
876
+ )
877
+
878
+ def copy_session_path(self) -> None:
879
+ """Copy the displayed session folder path to the available clipboards."""
880
+ folder = self._displayed_folder()
881
+ if folder is None:
882
+ return
883
+ copy_text_to_clipboards(self.app, str(folder))
884
+ self.notify(
885
+ self._render_message(_SESSION_INFO_PATH_COPIED.bind()),
886
+ title=self._render_message(COPIED_TITLE.bind()),
887
+ timeout=1,
888
+ markup=False,
889
+ )
890
+
891
+ def _precision_legend(self) -> Text:
892
+ # Only the glyphs carry semantic colour; the labels inherit the border
893
+ # subtitle's own colour so they read like the session id beside them.
894
+ look = self._look()
895
+ legend = Text()
896
+ for index, precision in enumerate(PRECISION_SYMBOLS):
897
+ if index:
898
+ legend.append(" ")
899
+ legend.append(PRECISION_SYMBOLS[precision], precision_style(look, precision))
900
+ legend.append(" ")
901
+ legend.append(precision_label(look, precision))
902
+ return legend
903
+
904
+ def _hatch_style(self) -> Style:
905
+ return hatch_text_style(self)
906
+
907
+ def _muted_label_style(self) -> Style:
908
+ # $text-muted is a composite value on every theme ("auto 60%",
909
+ # "ansi_white 40%"), which a plain color parse silently drops; only
910
+ # the stylesheet can resolve the alpha blend. Strip the stamped
911
+ # background so the label stays transparent over the hatch.
912
+ full = self.get_component_rich_style("trajectory-dashboard--muted-label")
913
+ return full.without_color + Style.from_color(full.color)
914
+
915
+ def _available_width(self) -> int:
916
+ return self._available_size.width or self.size.width
917
+
918
+ def _available_height(self) -> int:
919
+ height = self._available_size.height or self.size.height
920
+ return max(1, height - _TABS_HEIGHT)
921
+
922
+ def _look(self) -> DashboardLook:
923
+ return DashboardLook(
924
+ console=self.app.console,
925
+ theme_variables=self.app.theme_variables,
926
+ localizer=self._localizer(),
927
+ )
928
+
929
+ def _localizer(self) -> Localizer | None:
930
+ return None if self._locale_controller is None else self._locale_controller.localizer
931
+
932
+ def _render_message(self, reference: MessageRef) -> str:
933
+ # Takes only the localizer, never _look(): the constructor sets the
934
+ # border title before any App runs.
935
+ return render_message(self._localizer(), reference)
936
+
937
+
938
+ def _session_directory(path: Path) -> Path | None:
939
+ """The session folder owning *path*, only when it sits in the store layout.
940
+
941
+ Anything else (a loose events file under a scratch directory) has no
942
+ session folder to size or open; walking its parent would measure an
943
+ unrelated tree.
944
+ """
945
+ if (
946
+ len(path.parents) < 3
947
+ or path.name != "events.jsonl"
948
+ or path.parent.name != "trajectory"
949
+ or path.parents[2].name != "sessions"
950
+ ):
951
+ return None
952
+ return path.parents[1]
953
+
954
+
955
+ def _load_with_storage(
956
+ analyzer: TrajectoryAnalyzer,
957
+ path: Path,
958
+ *,
959
+ cancel_event: Event,
960
+ ) -> tuple[TrajectoryAnalysis, SessionStorage | None]:
961
+ analysis = analyzer.load(path, cancel_event=cancel_event)
962
+ session_dir = _session_directory(path)
963
+ if session_dir is None:
964
+ return analysis, None
965
+ return analysis, collect_session_storage(session_dir, cancel_event=cancel_event)
966
+
967
+
968
+ def _refresh_with_storage(
969
+ analyzer: TrajectoryAnalyzer,
970
+ *,
971
+ collect_storage: bool,
972
+ cancel_event: Event,
973
+ ) -> tuple[TrajectoryAnalysis, SessionStorage | None]:
974
+ analysis = analyzer.refresh(cancel_event=cancel_event)
975
+ if not collect_storage:
976
+ # Storage recollection stays on the caller's coarse clock even while
977
+ # the log is appending, or a busy session walks the directory tree on
978
+ # every poll tick; the panel keeps the previous figures meanwhile.
979
+ return analysis, None
980
+ session_dir = _session_directory(analysis.path)
981
+ if session_dir is None:
982
+ return analysis, None
983
+ return analysis, collect_session_storage(session_dir, cancel_event=cancel_event)