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,1385 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Renderer for sub-agent tool calls with a reusable nested transcript."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import re
9
+ import time
10
+ from collections.abc import Awaitable, Callable
11
+ from contextlib import suppress
12
+ from dataclasses import dataclass
13
+ from typing import TYPE_CHECKING, Any, ClassVar, Literal
14
+
15
+ from rich.text import Text
16
+ from textual.containers import Horizontal, Vertical
17
+ from textual.message import Message
18
+ from textual.timer import Timer
19
+ from textual.widget import Widget
20
+ from textual.widgets import Button, Static
21
+
22
+ from chrys.app.tui.i18n import render_str, render_text, widget_localizer
23
+ from chrys.app.tui.util.invocation_progress import invocation_progress_parts
24
+ from chrys.app.tui.util.visibility import is_widget_shown_on_active_screen
25
+ from chrys.app.tui.widgets.chat.agent_transcript_surface import (
26
+ AgentTranscriptJournal,
27
+ AgentTranscriptSurface,
28
+ TranscriptAssistantOp,
29
+ TranscriptCompactionFinishedOp,
30
+ TranscriptCompactionStartOp,
31
+ TranscriptPresentationAcceptedOp,
32
+ TranscriptPresentationRejectedOp,
33
+ TranscriptToolArgsOp,
34
+ TranscriptToolProgressOp,
35
+ TranscriptToolResultOp,
36
+ TranscriptToolStartOp,
37
+ TranscriptToolStatusOp,
38
+ )
39
+ from chrys.app.tui.widgets.chat.messages import process_think_tags
40
+ from chrys.app.tui.widgets.chat.renderers.sleep import SleepSkipClicked
41
+ from chrys.app.tui.widgets.chat.tool_call import (
42
+ TOOL_CARD_COMPLETED,
43
+ TOOL_CARD_ERRORED,
44
+ TOOL_CARD_INTERRUPTED,
45
+ TOOL_CARD_REJECTED,
46
+ BaseToolCard,
47
+ ToolCardHeader,
48
+ fmt_duration,
49
+ tool_activity_reference,
50
+ tool_result_render_status,
51
+ )
52
+ from chrys.app.tui.widgets.chat.tool_view_builders import (
53
+ TOOL_VIEW_EMPTY,
54
+ TOOL_VIEW_OUTPUT,
55
+ build_code_view,
56
+ build_params_view,
57
+ )
58
+ from chrys.app.tui.widgets.loading import ChrysLoadingIndicator
59
+ from chrys.app.tui.widgets.markdown import VirtualizedMarkdown
60
+ from chrys.foundation.i18n import MessageDef, MessageRef, msg
61
+ from chrys.foundation.i18n.formatting import sanitize_legacy_scalar
62
+ from chrys.foundation.platform.files import surrogate_safe_text
63
+ from chrys.foundation.tool_kinds import KIND_SLEEP, KIND_SUB_AGENT
64
+ from chrys.foundation.tool_result_metadata import TOOL_INTERRUPTED_METADATA_KEY
65
+ from chrys.foundation.util.sub_agent_context import SUB_AGENT_TRANSCRIPT_FINAL_TEXT_METADATA_KEY
66
+
67
+ if TYPE_CHECKING:
68
+ from textual.app import ComposeResult
69
+
70
+ from chrys.foundation.events.types import ProvisionalPresentation
71
+ from chrys.service.session.sub_agent_transcript import PersistedSubAgentTranscript
72
+
73
+ _SUB_AGENT_TASK = msg("tui.tool_card.sub_agent.task", fallback="Task")
74
+ _SUB_AGENT_TASK_PROMPT = msg("tui.tool_card.sub_agent.task_prompt", fallback="Task Prompt")
75
+ _SUB_AGENT_DURATION = msg("tui.tool_card.sub_agent.duration", fallback="Duration: {duration}")
76
+ _SUB_AGENT_RETRYING = msg(
77
+ "tui.tool_card.sub_agent.retrying",
78
+ fallback="↻ Retrying in {delay_seconds}s ({attempt}/{max_attempts}): {message}",
79
+ )
80
+ _SUB_AGENT_REASON_STREAM_STALLED = msg(
81
+ "tui.tool_card.sub_agent.reason.stream_stalled",
82
+ fallback="Stream stalled",
83
+ )
84
+ _SUB_AGENT_REASON_COMPACTION_FAILED = msg(
85
+ "tui.tool_card.sub_agent.reason.compaction_failed",
86
+ fallback="Compaction failed",
87
+ )
88
+ _SUB_AGENT_REASON_FAILED = msg("tui.tool_card.sub_agent.reason.failed", fallback="Sub-agent failed")
89
+ _SUB_AGENT_REASON_ACP_INTERRUPTED = msg(
90
+ "tui.tool_card.sub_agent.reason.acp_interrupted",
91
+ fallback="External ACP transport interrupted",
92
+ )
93
+ _SUB_AGENT_REASON_PAUSED = msg("tui.tool_card.sub_agent.reason.paused", fallback="Sub-agent paused")
94
+ _SUB_AGENT_AFTER_RETRIES = msg(
95
+ "tui.tool_card.sub_agent.after_retries",
96
+ fallback="(after {count} auto-retry attempt)",
97
+ plural_fallback="(after {count} auto-retry attempts)",
98
+ )
99
+ _SUB_AGENT_DIAGNOSTICS = msg("tui.tool_card.sub_agent.diagnostics", fallback="Diagnostics: {path}")
100
+ _SUB_AGENT_ERRORED_WITH_REASON = msg("tui.tool_card.sub_agent.errored_with_reason", fallback="Errored: {reason}")
101
+ _SUB_AGENT_PAUSED = msg(
102
+ "tui.tool_card.sub_agent.paused",
103
+ fallback="Paused — awaiting user",
104
+ )
105
+ _SUB_AGENT_SKIP_SLEEP = msg("tui.tool_card.sub_agent.button.skip_sleep", fallback="Skip sleep")
106
+ _SUB_AGENT_RETRY = msg("tui.tool_card.sub_agent.button.retry", fallback="Retry")
107
+ _SUB_AGENT_ABORT = msg("tui.tool_card.sub_agent.button.abort", fallback="Abort")
108
+ _SUB_AGENT_VIEW_DETAILS = msg("tui.tool_card.sub_agent.button.view_details", fallback="View details")
109
+ _SUB_AGENT_SKIP_SLEEP_TOOLTIP = msg(
110
+ "tui.tool_card.sub_agent.button.skip_sleep_tooltip",
111
+ fallback="Skip the active sleep in this sub-agent",
112
+ )
113
+
114
+ _SUB_AGENT_REASON_MESSAGES: dict[str, MessageDef] = {
115
+ "stream_stall": _SUB_AGENT_REASON_STREAM_STALLED,
116
+ "last_words": _SUB_AGENT_REASON_COMPACTION_FAILED,
117
+ "framework_exc": _SUB_AGENT_REASON_FAILED,
118
+ "acp_transport": _SUB_AGENT_REASON_ACP_INTERRUPTED,
119
+ }
120
+
121
+ _COMPACTION_ENTRY_KIND = "compaction"
122
+ """Synthetic ``tool_kind`` for the Phase-4 compaction progress line."""
123
+
124
+ # The failure results the sub-agent policies write name the card's own agent
125
+ # ("sub-agent 'X' failed — <cause>"); the card line keeps only what the card
126
+ # does not already say.
127
+ _OWN_AGENT_PREFIX = re.compile(r"sub-agent '[^']*' (?:failed —\s*)?")
128
+ # Far wider than any card: the activity line ellipsizes to its own width.
129
+ _ERROR_REASON_MAX_CHARS = 400
130
+
131
+
132
+ def _error_reason(result: str) -> str:
133
+ """One display line naming why the sub-agent call failed, or ``""``."""
134
+ text = result.strip().removeprefix("Error:").lstrip()
135
+ text = _OWN_AGENT_PREFIX.sub("", text, count=1) if text.startswith("sub-agent '") else text
136
+ reason = sanitize_legacy_scalar(surrogate_safe_text(" ".join(text.split())))
137
+ if len(reason) > _ERROR_REASON_MAX_CHARS:
138
+ reason = reason[: _ERROR_REASON_MAX_CHARS - 1] + "…"
139
+ return reason
140
+
141
+
142
+ @dataclass
143
+ class _InnerToolEntry:
144
+ """State for a single inner tool call."""
145
+
146
+ call_id: str
147
+ tool_name: str
148
+ tool_kind: str
149
+ args: dict[str, Any]
150
+ status: str = "running"
151
+ completion_counted: bool = False
152
+
153
+
154
+ @dataclass
155
+ class _ActivityHistoryEntry:
156
+ """One rollback-aware candidate for the compact latest-activity line."""
157
+
158
+ activity: MessageRef | str
159
+ presentation: ProvisionalPresentation | None = None
160
+
161
+
162
+ class _SubAgentActivityText(Static):
163
+ """Single-line latest activity with a real narrow-width ellipsis."""
164
+
165
+ def __init__(self, text: Text) -> None:
166
+ super().__init__(text, markup=False, id="sa-activity-text")
167
+ self._full_text = text.copy()
168
+
169
+ def set_text(self, text: Text) -> None:
170
+ self._full_text = text.copy()
171
+ self.refresh(layout=False)
172
+
173
+ def render(self) -> Text:
174
+ text = self._full_text.copy()
175
+ available = self.content_size.width or self.size.width
176
+ if available > 0:
177
+ text.truncate(available, overflow="ellipsis")
178
+ return text
179
+
180
+
181
+ # Custom Textual messages — bubble up to MainScreen so it can publish the
182
+ # corresponding bus event. Keeping a translation layer between widget
183
+ # clicks and event bus events means the widget doesn't need to hold a
184
+ # reference to the bus.
185
+
186
+
187
+ class SubAgentRetryClicked(Message):
188
+ """User clicked Retry on a paused :class:`SubAgentToolCall` card."""
189
+
190
+ def __init__(self, invocation_id: str) -> None:
191
+ super().__init__()
192
+ self.invocation_id = invocation_id
193
+
194
+
195
+ class SubAgentAbortClicked(Message):
196
+ """User clicked Abort on a paused :class:`SubAgentToolCall` card."""
197
+
198
+ def __init__(self, invocation_id: str) -> None:
199
+ super().__init__()
200
+ self.invocation_id = invocation_id
201
+
202
+
203
+ class SubAgentToolCall(BaseToolCard):
204
+ """Renderer for sub-agent tool invocations.
205
+
206
+ Implements the ToolCall protocol so it can be used in ToolGroup.
207
+ A dedicated :class:`VirtualizedMarkdown` renders the original prompt in a
208
+ ``Task`` panel. The second panel keeps only the newest assistant/tool
209
+ activity; a dedicated details action opens :class:`AgentTranscriptSurface`
210
+ in the existing modal, reusing the main-agent transcript renderers.
211
+ """
212
+
213
+ DEFAULT_CSS = """
214
+ SubAgentToolCall {
215
+ padding: 0 0 0 2;
216
+ height: auto;
217
+ margin: 0 0 1 0;
218
+ }
219
+ SubAgentToolCall #sa-label {
220
+ height: auto;
221
+ }
222
+ SubAgentToolCall > #sa-task-panel {
223
+ height: auto;
224
+ margin: 0 0 0 2;
225
+ border: round $tui-border-neutral-128 $border-opacity;
226
+ border-title-color: $text-muted;
227
+ border-title-style: not bold;
228
+ padding: 0 1 0 1;
229
+ }
230
+ SubAgentToolCall > #sa-panel {
231
+ height: auto;
232
+ margin: 0 0 0 2;
233
+ border: round $tui-border-warning 50%;
234
+ border-title-color: $warning;
235
+ border-title-style: bold;
236
+ border-title-align: left;
237
+ border-subtitle-color: $warning;
238
+ padding: 0 1 0 1;
239
+ }
240
+ SubAgentToolCall.-complete > #sa-panel {
241
+ border: round $tui-border-success 30%;
242
+ border-title-color: $success;
243
+ border-title-style: not bold;
244
+ border-subtitle-color: $success;
245
+ }
246
+ SubAgentToolCall.-error > #sa-panel {
247
+ border: round $tui-border-error 50%;
248
+ border-title-color: $error;
249
+ border-title-style: not bold;
250
+ border-subtitle-color: $error;
251
+ }
252
+ SubAgentToolCall.-rejected > #sa-panel {
253
+ border: round $tui-border-warning 50%;
254
+ border-title-color: $warning;
255
+ border-title-style: not bold;
256
+ border-subtitle-color: $warning;
257
+ }
258
+ SubAgentToolCall.-paused > #sa-panel {
259
+ border: round $tui-border-warning $border-opacity;
260
+ border-title-color: $warning;
261
+ border-title-style: bold;
262
+ border-subtitle-color: $warning;
263
+ }
264
+ SubAgentToolCall #sa-latest {
265
+ height: 1;
266
+ width: 100%;
267
+ }
268
+ SubAgentToolCall #sa-latest > ChrysLoadingIndicator {
269
+ color: $tui-tool-group-title;
270
+ height: 1;
271
+ width: 6;
272
+ min-width: 6;
273
+ max-width: 6;
274
+ }
275
+ SubAgentToolCall #sa-activity-text {
276
+ color: $text-muted;
277
+ height: 1;
278
+ width: 1fr;
279
+ overflow: hidden;
280
+ }
281
+ SubAgentToolCall #sa-task {
282
+ height: auto;
283
+ min-height: 1;
284
+ padding: 0;
285
+ background: transparent;
286
+ }
287
+ SubAgentToolCall #sa-retry-banner {
288
+ height: auto;
289
+ display: none;
290
+ color: $warning;
291
+ text-style: italic;
292
+ }
293
+ SubAgentToolCall.-retrying #sa-retry-banner {
294
+ display: block;
295
+ }
296
+ SubAgentToolCall #sa-pause-info {
297
+ height: auto;
298
+ display: none;
299
+ color: $error;
300
+ }
301
+ SubAgentToolCall.-paused #sa-pause-info {
302
+ display: block;
303
+ }
304
+ SubAgentToolCall #sa-actions {
305
+ height: auto;
306
+ display: none;
307
+ margin: 1 0 0 0;
308
+ }
309
+ SubAgentToolCall #sa-sleep-actions {
310
+ height: auto;
311
+ display: none;
312
+ margin: 1 0 0 0;
313
+ }
314
+ SubAgentToolCall.-sleeping #sa-sleep-actions {
315
+ display: block;
316
+ }
317
+ SubAgentToolCall.-done #sa-sleep-actions {
318
+ display: none;
319
+ }
320
+ SubAgentToolCall.-paused #sa-actions {
321
+ display: block;
322
+ }
323
+ SubAgentToolCall #sa-actions Button {
324
+ margin: 0 1 0 0;
325
+ min-width: 10;
326
+ height: 1;
327
+ border: none;
328
+ }
329
+ SubAgentToolCall #sa-retry-btn {
330
+ background: $warning;
331
+ color: $text;
332
+ text-style: bold;
333
+ }
334
+ SubAgentToolCall #sa-abort-btn {
335
+ background: $error;
336
+ color: $text;
337
+ text-style: bold;
338
+ }
339
+ SubAgentToolCall #sa-skip-sleep-btn {
340
+ min-width: 12;
341
+ height: 1;
342
+ border: none;
343
+ background: $warning;
344
+ color: $text;
345
+ text-style: bold;
346
+ }
347
+ """
348
+
349
+ _SPINNERS: ClassVar[str] = "\u25d0\u25d3\u25d1\u25d2"
350
+
351
+ def __init__(
352
+ self,
353
+ call_id: str,
354
+ tool_name: str,
355
+ args_summary: str = "",
356
+ args: dict[str, Any] | None = None,
357
+ ) -> None:
358
+ super().__init__(call_id, tool_name, args_summary, args=args)
359
+ self._spin_idx = 0
360
+ self._transcript_journal = AgentTranscriptJournal()
361
+ self._final_transcript_recorded = False
362
+ self._transcript_loader: Callable[[str], Awaitable[PersistedSubAgentTranscript | None]] | None = None
363
+ self._transcript_log_file = ""
364
+ self._transcript_profile_name = tool_name
365
+ self._linked_display_name = ""
366
+ initial_activity = tool_activity_reference(tool_name, KIND_SUB_AGENT, {"agent_name": tool_name})
367
+ self._activity_history = [_ActivityHistoryEntry(initial_activity)]
368
+ self._latest_activity: MessageRef | str = initial_activity
369
+
370
+ self._invocation_id: str | None = None
371
+ self._inner_tools: dict[str, _InnerToolEntry] = {}
372
+ self._seen_inner_call_ids: set[str] = set()
373
+ self._terminal_inner_call_ids: set[str] = set()
374
+ self._total_inner_calls = 0
375
+ self._completed_inner_calls = 0
376
+ self._progress_tool_calls = 0
377
+ self._progress_ctx_tokens = 0
378
+ self._progress_total_usage_tokens = 0
379
+ self._usage_unreported_attempts = 0
380
+ # Committed Phase-4 compactions — persistent, unlike the compaction
381
+ # entries in ``_inner_tools`` which the visibility cap can evict.
382
+ # Driven by the committed signal, not finished(ok): a generated note
383
+ # whose spill write fails is abandoned and must not count.
384
+ self._compaction_count = 0
385
+
386
+ self._start_time = time.monotonic()
387
+ self._timer: Timer | None = None
388
+ # NOTE: name must avoid ``_task`` — that attribute is owned by
389
+ # :class:`textual.message_pump.MessagePump`, which assigns the
390
+ # widget's running ``asyncio.Task`` to ``self._task`` before
391
+ # :meth:`compose` runs. Storing the prompt under that name makes
392
+ # it look like a string in ``__init__`` and an ``asyncio.Task`` by
393
+ # the time ``compose()`` reads it, which crashes ``Text(...)``.
394
+ self._task_prompt = self._extract_prompt(self.args_summary, self.args)
395
+
396
+ def _render_message(self, reference: MessageRef) -> str:
397
+ return render_str(widget_localizer(self), reference)
398
+
399
+ def _activity_text(self) -> Text:
400
+ activity = self._latest_activity
401
+ plain = activity if isinstance(activity, str) else self._render_message(activity)
402
+ return Text(plain, no_wrap=True, overflow="ellipsis")
403
+
404
+ def _remember_activity(
405
+ self,
406
+ activity: MessageRef | str,
407
+ *,
408
+ presentation: ProvisionalPresentation | None = None,
409
+ ) -> MessageRef | str:
410
+ if presentation is None:
411
+ self._activity_history = [_ActivityHistoryEntry(activity)]
412
+ else:
413
+ self._activity_history.append(_ActivityHistoryEntry(activity, presentation))
414
+ self._latest_activity = activity
415
+ self._sync_activity_summary()
416
+ return activity
417
+
418
+ def _resolve_activity_presentation(
419
+ self,
420
+ attempt_id: str,
421
+ accepted_segment_ids: set[str],
422
+ ) -> MessageRef | str:
423
+ resolved: list[_ActivityHistoryEntry] = []
424
+ for entry in self._activity_history:
425
+ presentation = entry.presentation
426
+ if presentation is None or presentation.attempt_id != attempt_id:
427
+ resolved.append(entry)
428
+ elif presentation.segment_id in accepted_segment_ids:
429
+ resolved.append(_ActivityHistoryEntry(entry.activity))
430
+ if not resolved:
431
+ fallback = tool_activity_reference(
432
+ self.tool_name,
433
+ KIND_SUB_AGENT,
434
+ {"agent_name": self.tool_name},
435
+ )
436
+ resolved = [_ActivityHistoryEntry(fallback)]
437
+ if not any(entry.presentation is not None for entry in resolved):
438
+ resolved = [resolved[-1]]
439
+ self._activity_history = resolved
440
+ self._latest_activity = resolved[-1].activity
441
+ self._sync_activity_summary()
442
+ return self._latest_activity
443
+
444
+ def _sync_activity_summary(self) -> None:
445
+ with suppress(Exception):
446
+ self.query_one("#sa-activity-text", _SubAgentActivityText).set_text(self._activity_text())
447
+ with suppress(Exception):
448
+ indicator = self.query_one("#sa-activity-indicator", ChrysLoadingIndicator)
449
+ active = self.status == "running"
450
+ indicator.display = active
451
+ if active:
452
+ indicator.resume_animation()
453
+ else:
454
+ indicator.pause_animation()
455
+
456
+ def _label_text(self, duration_ms: int = 0) -> Text:
457
+ t = Text()
458
+ t.append("• ", style="bold")
459
+ t.append("SubAgent", style="bold")
460
+ if duration_ms:
461
+ t.append(f" ({fmt_duration(duration_ms)})", style="dim")
462
+ return t
463
+
464
+ def _running_label_text(self) -> Text:
465
+ """Label with live elapsed time while running."""
466
+ t = Text()
467
+ t.append("• ", style="bold")
468
+ t.append("SubAgent", style="bold")
469
+ elapsed_ms = int((time.monotonic() - self._start_time) * 1000)
470
+ if elapsed_ms >= 1000:
471
+ t.append(f" ({fmt_duration(elapsed_ms)})", style="dim")
472
+ return t
473
+
474
+ def compose(self) -> ComposeResult:
475
+ header = ToolCardHeader(
476
+ self._running_label_text(),
477
+ id="sa-label",
478
+ view_only_label=_SUB_AGENT_VIEW_DETAILS.bind() if self.status in {"running", "paused"} else None,
479
+ )
480
+ if self.status not in {"running", "paused"}:
481
+ header.show_actions()
482
+ yield header
483
+ if self._task_prompt:
484
+ yield self._build_task_panel()
485
+ with Vertical(id="sa-panel") as panel:
486
+ panel.border_title = Text(self._render_title())
487
+ with Horizontal(id="sa-latest"):
488
+ yield ChrysLoadingIndicator(id="sa-activity-indicator")
489
+ yield _SubAgentActivityText(self._activity_text())
490
+ with Horizontal(id="sa-sleep-actions"):
491
+ skip = Button(
492
+ render_text(widget_localizer(self), _SUB_AGENT_SKIP_SLEEP.bind()),
493
+ id="sa-skip-sleep-btn",
494
+ compact=True,
495
+ )
496
+ skip.tooltip = render_text(widget_localizer(self), _SUB_AGENT_SKIP_SLEEP_TOOLTIP.bind())
497
+ yield skip
498
+ yield Static("", id="sa-retry-banner")
499
+ yield Static("", id="sa-pause-info")
500
+ with Horizontal(id="sa-actions"):
501
+ yield Button(
502
+ render_text(widget_localizer(self), _SUB_AGENT_RETRY.bind()),
503
+ id="sa-retry-btn",
504
+ compact=True,
505
+ )
506
+ yield Button(
507
+ render_text(widget_localizer(self), _SUB_AGENT_ABORT.bind()),
508
+ id="sa-abort-btn",
509
+ compact=True,
510
+ )
511
+
512
+ def on_mount(self) -> None:
513
+ self._timer = self.set_interval(0.12, self._spin)
514
+ self._sync_activity_summary()
515
+
516
+ def on_unmount(self) -> None:
517
+ if self._timer is not None:
518
+ self._timer.stop()
519
+ self._timer = None
520
+
521
+ @staticmethod
522
+ def _extract_prompt(args_summary: str, args: dict[str, Any]) -> str:
523
+ """Extract prompt string safely from args_summary or args dict."""
524
+ if args_summary:
525
+ try:
526
+ parsed = json.loads(args_summary)
527
+ except Exception:
528
+ parsed = None
529
+ if isinstance(parsed, dict):
530
+ val = parsed.get("prompt", "")
531
+ if isinstance(val, str):
532
+ return val
533
+ val = args.get("prompt")
534
+ if isinstance(val, str):
535
+ return val
536
+ return ""
537
+
538
+ def _build_task_panel(self) -> Widget:
539
+ task_panel = Widget(VirtualizedMarkdown(self._task_prompt, id="sa-task"), id="sa-task-panel")
540
+ task_panel.border_title = render_text(widget_localizer(self), _SUB_AGENT_TASK.bind())
541
+ return task_panel
542
+
543
+ def update_args(self, args: dict[str, Any]) -> None:
544
+ """Refresh the task prompt after approval edits the sub-agent handoff."""
545
+ self.args = args
546
+ self.args_summary = json.dumps(args, ensure_ascii=False) if args else ""
547
+ self._task_prompt = self._extract_prompt(self.args_summary, self.args)
548
+ if self._task_prompt:
549
+ if self.query("#sa-task-panel"):
550
+ with suppress(Exception):
551
+ self.query_one("#sa-task", VirtualizedMarkdown).update(self._task_prompt)
552
+ else:
553
+ with suppress(Exception):
554
+ self.mount(self._build_task_panel(), before=self.query_one("#sa-panel", Vertical))
555
+ return
556
+ with suppress(Exception):
557
+ self.query_one("#sa-task-panel", Widget).remove()
558
+
559
+ def _spin(self) -> None:
560
+ if self.status == "running":
561
+ self._spin_idx = (self._spin_idx + 1) % len(self._SPINNERS)
562
+ # Only a shown card repaints (see ``ToolCall._spin``); the first tick after it shows paints it.
563
+ if not is_widget_shown_on_active_screen(self):
564
+ return
565
+ with suppress(Exception):
566
+ # The label shows whole seconds: most ticks leave it as it is, and
567
+ # rewriting an equal label would only repaint the header.
568
+ label = self._running_label_text()
569
+ header = self.query_one("#sa-label", ToolCardHeader)
570
+ if header.content != label:
571
+ header.update(label)
572
+ self._update_title()
573
+
574
+ def _update_title(self) -> None:
575
+ """Update border title (top-left) and subtitle (bottom-right)."""
576
+ panel = self.query_one("#sa-panel", Vertical)
577
+ panel.border_title = Text(self._render_title())
578
+ panel.border_subtitle = Text(self._render_subtitle())
579
+
580
+ @staticmethod
581
+ def _fmt_duration(ms: int) -> str:
582
+ return fmt_duration(ms)
583
+
584
+ def _render_title(self) -> str:
585
+ if self.status == "running":
586
+ return f"{self._SPINNERS[self._spin_idx]} {self.tool_name}"
587
+ if self.status == "complete":
588
+ return f"\u2713 {self.tool_name}"
589
+ return f"\u2717 {self.tool_name}"
590
+
591
+ def _render_subtitle(self) -> str:
592
+ tool_calls = self._total_inner_calls if self.status != "running" else self._progress_tool_calls
593
+ parts = invocation_progress_parts(
594
+ tool_calls=tool_calls,
595
+ context_tokens=self._progress_ctx_tokens,
596
+ usage_tokens=self._progress_total_usage_tokens,
597
+ unreported_attempts=self._usage_unreported_attempts,
598
+ compactions=self._compaction_count,
599
+ render=self._render_message,
600
+ )
601
+ duration_ms = self.duration_ms
602
+ if self.status == "running":
603
+ duration_ms = int((time.monotonic() - self._start_time) * 1000)
604
+ if duration_ms < 1000:
605
+ duration_ms = 0
606
+ if duration_ms:
607
+ parts.append(self._render_message(_SUB_AGENT_DURATION.bind(duration=self._fmt_duration(duration_ms))))
608
+ return " \u00b7 ".join(parts)
609
+
610
+ def _running_sleep_call_id(self) -> str:
611
+ """Return the newest running inner sleep call id, if one is visible."""
612
+ for entry in reversed(self._inner_tools.values()):
613
+ is_sleep = entry.tool_kind == KIND_SLEEP or (not entry.tool_kind and entry.tool_name == "sleep")
614
+ if entry.status == "running" and is_sleep:
615
+ return entry.call_id
616
+ return ""
617
+
618
+ def _refresh_sleep_action_state(self) -> None:
619
+ if self.status == "running" and self._running_sleep_call_id():
620
+ self.add_class("-sleeping")
621
+ else:
622
+ self.remove_class("-sleeping")
623
+
624
+ def _show_completion_controls(self) -> None:
625
+ """Expose copy/details affordances without mounting the final answer inline."""
626
+ self.add_class("-done")
627
+ self._show_tool_copy_button()
628
+
629
+ async def mount_pending_content(self) -> bool:
630
+ """The compact activity line has no deferred inline content."""
631
+ return False
632
+
633
+ def release_collapsed_content(self) -> None:
634
+ """The one-line activity summary remains mounted when its parent collapses."""
635
+ return
636
+
637
+ def _tool_copy_input(self) -> tuple[str, str]:
638
+ """Copy the sub-agent prompt without duplicating the full args dict."""
639
+ return "markdown", self._task_prompt or self._render_message(TOOL_VIEW_EMPTY.bind())
640
+
641
+ def _tool_view_input_widgets(self) -> list[Widget]:
642
+ """Render the sub-agent prompt as markdown in the detail modal."""
643
+ dark = self._view_dark()
644
+ label = Static(
645
+ Text(render_str(widget_localizer(self), _SUB_AGENT_TASK_PROMPT.bind()), style="bold"),
646
+ classes="tool-view-section-title tool-view-section-title-first",
647
+ )
648
+ widgets: list[Widget] = [
649
+ label,
650
+ build_code_view(
651
+ "markdown",
652
+ self._task_prompt or self._render_message(TOOL_VIEW_EMPTY.bind()),
653
+ dark=dark,
654
+ render_message=self._render_message,
655
+ ),
656
+ ]
657
+
658
+ args = self._tool_input_args()
659
+ extra = {key: value for key, value in args.items() if key != "prompt"}
660
+ if extra:
661
+ widgets.extend(build_params_view(extra, dark=dark, render_message=self._render_message))
662
+ return widgets
663
+
664
+ def _tool_copy_sections(self) -> list[tuple[str, str, str]]:
665
+ """Preserve the sub-agent's markdown final answer in the copy payload."""
666
+ return [
667
+ (
668
+ self._render_message(TOOL_VIEW_OUTPUT.bind()),
669
+ "markdown",
670
+ self.result_text or self._render_message(TOOL_VIEW_EMPTY.bind()),
671
+ )
672
+ ]
673
+
674
+ def _tool_view_copy_enabled(self) -> bool:
675
+ """Disable stale whole-output copying while the sub-agent is live."""
676
+ return self.status not in {"running", "paused"}
677
+
678
+ def _tool_view_output_widgets(self) -> list[Widget]:
679
+ """Mount a full live transcript projection in the existing detail modal."""
680
+ persisted_replay_loader: Callable[[], Awaitable[PersistedSubAgentTranscript | None]] | None = None
681
+ metadata = self.metadata or {}
682
+ transcript_final = metadata.get(SUB_AGENT_TRANSCRIPT_FINAL_TEXT_METADATA_KEY)
683
+ fallback_final_text = self.result_text
684
+ if transcript_final == "" and self._transcript_journal.has_activity:
685
+ # The empty override means the parent result was already emitted
686
+ # as an intermediate segment. Suppress fallback only while that
687
+ # live evidence is retained; a failed durable replay must still
688
+ # fall back to the authoritative parent result.
689
+ fallback_final_text = ""
690
+ metadata_log_file = metadata.get("sub_agent_log_file")
691
+ log_file = (
692
+ metadata_log_file if isinstance(metadata_log_file, str) and metadata_log_file else self._transcript_log_file
693
+ )
694
+ # A running/paused card's journal is the live authority. A terminal
695
+ # audit may be written just before the parent result is published; if
696
+ # a live modal consumed that audit, it could seal the journal before
697
+ # the authoritative result arrived. Persisted replay is therefore a
698
+ # terminal-card restoration path only.
699
+ if (
700
+ self.status in {"complete", "error", "rejected", "interrupted"}
701
+ and log_file
702
+ and self._transcript_loader is not None
703
+ ):
704
+ transcript_loader = self._transcript_loader
705
+
706
+ async def load_persisted_replay() -> PersistedSubAgentTranscript | None:
707
+ return await transcript_loader(log_file)
708
+
709
+ persisted_replay_loader = load_persisted_replay
710
+ return [
711
+ AgentTranscriptSurface(
712
+ self._transcript_journal,
713
+ profile_name=self._transcript_profile_name,
714
+ opening_prompt=self._task_prompt,
715
+ fallback_final_text=fallback_final_text,
716
+ persisted_replay_loader=persisted_replay_loader,
717
+ )
718
+ ]
719
+
720
+ def configure_transcript_loader(
721
+ self,
722
+ loader: Callable[[str], Awaitable[PersistedSubAgentTranscript | None]],
723
+ ) -> None:
724
+ """Install the screen-owned safe persisted-transcript loader."""
725
+ self._transcript_loader = loader
726
+
727
+ def add_assistant_message(
728
+ self,
729
+ text: str,
730
+ *,
731
+ presentation: ProvisionalPresentation | None = None,
732
+ profile_name: str = "",
733
+ ) -> MessageRef | str:
734
+ """Append an invocation-scoped intermediate assistant message."""
735
+ if profile_name and not self._linked_display_name:
736
+ self._transcript_profile_name = profile_name
737
+ self._transcript_journal.record(TranscriptAssistantOp(text=text, final=False, presentation=presentation))
738
+ visible = process_think_tags(text, intermediate=True)
739
+ if not visible:
740
+ return self._latest_activity
741
+ single_line = sanitize_legacy_scalar(" ".join(visible.splitlines()).strip())
742
+ return self._remember_activity(single_line, presentation=presentation)
743
+
744
+ def accept_presentation_attempt(self, attempt_id: str, segment_ids: tuple[str, ...]) -> MessageRef | str:
745
+ """Commit provisional transcript messages for one provider attempt."""
746
+ self._transcript_journal.record(TranscriptPresentationAcceptedOp(attempt_id, segment_ids))
747
+ return self._resolve_activity_presentation(attempt_id, set(segment_ids))
748
+
749
+ def reject_presentation_attempt(self, attempt_id: str) -> MessageRef | str:
750
+ """Retract provisional transcript messages for one provider attempt."""
751
+ self._transcript_journal.record(TranscriptPresentationRejectedOp(attempt_id))
752
+ return self._resolve_activity_presentation(attempt_id, set())
753
+
754
+ def _record_final_transcript(self, text: str) -> None:
755
+ if self._final_transcript_recorded:
756
+ return
757
+ self._final_transcript_recorded = True
758
+ self._transcript_journal.record(TranscriptAssistantOp(text=text, final=True))
759
+
760
+ # --- Invocation linking ---
761
+
762
+ def claim_invocation(self, invocation_id: str, agent_name: str = "", sub_agent_log_file: str = "") -> None:
763
+ """Link this widget and apply the invocation's display name."""
764
+ self._invocation_id = invocation_id
765
+ if sub_agent_log_file:
766
+ self._transcript_log_file = sub_agent_log_file
767
+ if not agent_name:
768
+ return
769
+ self._linked_display_name = agent_name
770
+ self._transcript_profile_name = agent_name
771
+ self._remember_activity(
772
+ tool_activity_reference(
773
+ self.tool_name,
774
+ KIND_SUB_AGENT,
775
+ {"agent_name": agent_name},
776
+ )
777
+ )
778
+
779
+ def update_progress(
780
+ self,
781
+ tool_call_count: int,
782
+ total_tokens: int,
783
+ total_usage_tokens: int = 0,
784
+ usage_unreported_attempts: int = 0,
785
+ ) -> None:
786
+ """Update cumulative progress stats and refresh the border title."""
787
+ self._progress_tool_calls = tool_call_count
788
+ self._progress_ctx_tokens = total_tokens
789
+ self._progress_total_usage_tokens = total_usage_tokens
790
+ self._usage_unreported_attempts = usage_unreported_attempts
791
+ if self.status == "running":
792
+ self._update_title()
793
+
794
+ # --- Inner tool management ---
795
+
796
+ async def add_inner_tool_start(
797
+ self,
798
+ call_id: str,
799
+ tool_name: str,
800
+ args: dict[str, Any],
801
+ *,
802
+ tool_kind: str = "",
803
+ provider_hosted: bool = False,
804
+ hosted_family: str = "",
805
+ provider: str = "",
806
+ provider_item_type: str = "",
807
+ provider_status: str = "",
808
+ provider_call_id: str = "",
809
+ ) -> None:
810
+ """Insert or refresh a running inner tool call by its lifetime id."""
811
+ if call_id in self._terminal_inner_call_ids:
812
+ return
813
+ self._transcript_journal.record(
814
+ TranscriptToolStartOp(
815
+ call_id=call_id,
816
+ tool_name=tool_name,
817
+ tool_kind=tool_kind,
818
+ args=dict(args),
819
+ provider_hosted=provider_hosted,
820
+ hosted_family=hosted_family,
821
+ provider=provider,
822
+ provider_item_type=provider_item_type,
823
+ provider_status=provider_status,
824
+ provider_call_id=provider_call_id,
825
+ )
826
+ )
827
+ self._remember_activity(tool_activity_reference(tool_name, tool_kind, args))
828
+ # A new inner tool call means the sub-agent is making forward
829
+ # progress — if a retry banner is still visible from a prior
830
+ # transient failure, the retry has clearly succeeded. Drop it
831
+ # now instead of letting it linger until the parent tool call
832
+ # finally resolves.
833
+ if self.has_class("-retrying"):
834
+ self.remove_class("-retrying")
835
+ with suppress(Exception):
836
+ self.query_one("#sa-retry-banner", Static).update("")
837
+ entry = self._inner_tools.get(call_id)
838
+ if entry is None:
839
+ entry = _InnerToolEntry(
840
+ call_id=call_id,
841
+ tool_name=tool_name,
842
+ tool_kind=tool_kind,
843
+ args=dict(args),
844
+ )
845
+ self._inner_tools[call_id] = entry
846
+ else:
847
+ entry.tool_name = tool_name
848
+ entry.tool_kind = tool_kind
849
+ entry.args = dict(args)
850
+ if call_id not in self._seen_inner_call_ids:
851
+ self._seen_inner_call_ids.add(call_id)
852
+ self._total_inner_calls += 1
853
+ self._refresh_sleep_action_state()
854
+
855
+ def update_inner_tool_args(self, call_id: str, args: dict[str, Any]) -> None:
856
+ """Refresh arguments on a running nested tool entry."""
857
+ entry = self._inner_tools.get(call_id)
858
+ if entry is None or call_id in self._terminal_inner_call_ids:
859
+ return
860
+ self._transcript_journal.record(TranscriptToolArgsOp(call_id, dict(args)))
861
+ entry.args = dict(args)
862
+ self._remember_activity(tool_activity_reference(entry.tool_name, entry.tool_kind, entry.args))
863
+
864
+ def update_inner_tool_progress(
865
+ self,
866
+ call_id: str,
867
+ lines: list[str],
868
+ *,
869
+ image_contents: list[Any] | None = None,
870
+ snapshot_metadata: dict[str, Any] | None = None,
871
+ provider_status: str = "",
872
+ ) -> None:
873
+ """Apply the latest bounded progress snapshot to a nested entry."""
874
+ entry = self._inner_tools.get(call_id)
875
+ if entry is None or call_id in self._terminal_inner_call_ids:
876
+ return
877
+ self._transcript_journal.record(
878
+ TranscriptToolProgressOp(
879
+ call_id,
880
+ list(lines),
881
+ list(image_contents or []),
882
+ dict(snapshot_metadata or {}),
883
+ provider_status,
884
+ )
885
+ )
886
+
887
+ def update_inner_tool_status(
888
+ self,
889
+ call_id: str,
890
+ status: str,
891
+ *,
892
+ metadata: dict[str, Any] | None = None,
893
+ provider_status: str = "",
894
+ ) -> None:
895
+ """Apply one canonical lifecycle status to a nested entry."""
896
+ entry = self._inner_tools.get(call_id)
897
+ if entry is None or call_id in self._terminal_inner_call_ids:
898
+ return
899
+ if status not in {"completed", "failed", "interrupted"}:
900
+ return
901
+ self._transcript_journal.record(TranscriptToolStatusOp(call_id, status, provider_status, dict(metadata or {})))
902
+ if status == "completed":
903
+ # A completed provider snapshot can precede output_item.done. Show
904
+ # it as complete now, but leave the id open for one authoritative
905
+ # result carrying final images, duration, and metadata.
906
+ self._apply_inner_tool_completion(
907
+ call_id,
908
+ entry,
909
+ str((metadata or {}).get("result_text", "")),
910
+ 0,
911
+ metadata=metadata,
912
+ authoritative_result=False,
913
+ )
914
+ return
915
+ entry.status = "interrupted" if status == "interrupted" else "error"
916
+ if not entry.completion_counted:
917
+ entry.completion_counted = True
918
+ self._completed_inner_calls += 1
919
+ self._refresh_sleep_action_state()
920
+
921
+ def add_compaction_start(self, compaction_id: str) -> None:
922
+ """Show a live "Compacting conversation..." line in the inner feed.
923
+
924
+ Phase-4 compaction is not a tool call, so this lifecycle entry is not
925
+ counted in ``_total_inner_calls``. The visible card lives in the
926
+ shared transcript journal.
927
+ """
928
+ self._transcript_journal.record(TranscriptCompactionStartOp(compaction_id))
929
+ entry = _InnerToolEntry(
930
+ call_id=f"compaction:{compaction_id}",
931
+ tool_name="compaction",
932
+ tool_kind=_COMPACTION_ENTRY_KIND,
933
+ args={},
934
+ )
935
+ self._inner_tools[entry.call_id] = entry
936
+
937
+ def complete_compaction(
938
+ self,
939
+ compaction_id: str,
940
+ *,
941
+ outcome: str,
942
+ duration_ms: int = 0,
943
+ format_violation: str = "",
944
+ failure_reason: str = "",
945
+ ) -> None:
946
+ """Flip the compaction line to its terminal state."""
947
+ entry = self._inner_tools.get(f"compaction:{compaction_id}")
948
+ if entry is None:
949
+ return
950
+ self._transcript_journal.record(
951
+ TranscriptCompactionFinishedOp(
952
+ compaction_id,
953
+ outcome,
954
+ duration_ms,
955
+ format_violation,
956
+ failure_reason,
957
+ )
958
+ )
959
+ if outcome == "ok":
960
+ entry.status = "complete"
961
+ # A successful finish proves the LAST_WORDS retry loop
962
+ # recovered — drop a lingering "Retrying in …" banner, same
963
+ # forward-progress rule as add_inner_tool_start. (Terminal
964
+ # failure pauses the sub-agent, whose flow clears it instead.)
965
+ if self.has_class("-retrying"):
966
+ self.remove_class("-retrying")
967
+ with suppress(Exception):
968
+ self.query_one("#sa-retry-banner", Static).update("")
969
+ elif outcome == "canceled":
970
+ entry.status = "interrupted"
971
+ else:
972
+ entry.status = "error"
973
+ self._inner_tools.pop(entry.call_id, None)
974
+
975
+ def record_compaction_committed(self, compaction_id: str) -> None:
976
+ """Count a durably committed compaction round in the subtitle.
977
+
978
+ Fires on the committed signal, which trails ``complete_compaction``'s
979
+ finished(ok) — a successful note generation whose spill write later
980
+ failed never commits, so counting here (not on finished-ok) keeps
981
+ "Compactions: N" honest. Independent of the feed entry: the line
982
+ may already be evicted by the visibility cap.
983
+ """
984
+ self._compaction_count += 1
985
+ with suppress(Exception):
986
+ self._update_title()
987
+
988
+ def complete_inner_tool(
989
+ self,
990
+ call_id: str,
991
+ result: str,
992
+ duration_ms: int,
993
+ image_contents: list[Any] | None = None,
994
+ artifacts: list[dict[str, Any]] | None = None,
995
+ approval: str | None = None,
996
+ metadata: dict[str, Any] | None = None,
997
+ provider_status: str = "",
998
+ canonical_status: str = "completed",
999
+ ) -> None:
1000
+ """Mark an inner tool call as complete."""
1001
+ entry = self._inner_tools.get(call_id)
1002
+ if not entry or call_id in self._terminal_inner_call_ids:
1003
+ return
1004
+ self._transcript_journal.record(
1005
+ TranscriptToolResultOp(
1006
+ call_id=call_id,
1007
+ tool_name=entry.tool_name,
1008
+ result=result,
1009
+ duration_ms=duration_ms,
1010
+ image_contents=list(image_contents or []),
1011
+ artifacts=[dict(artifact) for artifact in artifacts or []],
1012
+ approval=approval,
1013
+ metadata=dict(metadata or {}),
1014
+ provider_status=provider_status,
1015
+ canonical_status=canonical_status,
1016
+ )
1017
+ )
1018
+ self._apply_inner_tool_completion(
1019
+ call_id,
1020
+ entry,
1021
+ result,
1022
+ duration_ms,
1023
+ image_contents=image_contents,
1024
+ artifacts=artifacts,
1025
+ approval=approval,
1026
+ metadata=metadata,
1027
+ authoritative_result=True,
1028
+ )
1029
+
1030
+ def _apply_inner_tool_completion(
1031
+ self,
1032
+ call_id: str,
1033
+ entry: _InnerToolEntry,
1034
+ result: str,
1035
+ duration_ms: int,
1036
+ *,
1037
+ image_contents: list[Any] | None = None,
1038
+ artifacts: list[dict[str, Any]] | None = None,
1039
+ approval: str | None = None,
1040
+ metadata: dict[str, Any] | None = None,
1041
+ authoritative_result: bool,
1042
+ ) -> None:
1043
+ """Render completion while keeping status-only snapshots replaceable."""
1044
+ if authoritative_result:
1045
+ self._terminal_inner_call_ids.add(call_id)
1046
+ # A terminal id short-circuits add_inner_tool_start before the
1047
+ # seen-check, so its _seen membership is dead weight — drop it to
1048
+ # keep the dedupe set bounded by in-flight calls.
1049
+ self._seen_inner_call_ids.discard(call_id)
1050
+ render_status = tool_result_render_status(result, approval, entry.tool_kind, metadata, entry.tool_name)
1051
+ if render_status == "rejected":
1052
+ entry.status = "rejected"
1053
+ elif isinstance(metadata, dict) and metadata.get("sleep_interrupted") is True:
1054
+ entry.status = "interrupted"
1055
+ elif isinstance(metadata, dict) and metadata.get("sleep_skipped") is True:
1056
+ entry.status = "skipped"
1057
+ elif render_status == "error":
1058
+ entry.status = "error"
1059
+ else:
1060
+ entry.status = "complete"
1061
+ if not entry.completion_counted:
1062
+ entry.completion_counted = True
1063
+ self._completed_inner_calls += 1
1064
+ if authoritative_result:
1065
+ self._inner_tools.pop(call_id, None)
1066
+ self._refresh_sleep_action_state()
1067
+
1068
+ # --- ToolCall protocol ---
1069
+
1070
+ def _settle_running_inner_tools(self, *, completed: bool) -> None:
1071
+ """Close transcript cards whose terminal event lost the race with the parent result."""
1072
+ canonical_status = "completed" if completed else "interrupted"
1073
+ for entry in tuple(self._inner_tools.values()):
1074
+ if entry.status != "running":
1075
+ continue
1076
+ if entry.tool_kind == _COMPACTION_ENTRY_KIND:
1077
+ self._transcript_journal.record(
1078
+ TranscriptCompactionFinishedOp(
1079
+ entry.call_id.removeprefix("compaction:"),
1080
+ "canceled",
1081
+ )
1082
+ )
1083
+ else:
1084
+ self._transcript_journal.record(TranscriptToolStatusOp(entry.call_id, canonical_status))
1085
+ entry.status = "complete" if completed else "interrupted"
1086
+ if not entry.completion_counted:
1087
+ entry.completion_counted = True
1088
+ self._completed_inner_calls += 1
1089
+
1090
+ def _finish_transcript(
1091
+ self,
1092
+ result: str,
1093
+ status: Literal["complete", "error", "rejected", "interrupted"],
1094
+ ) -> None:
1095
+ """Finalize active nested cards, append the answer, and stop compact activity UI."""
1096
+ self._settle_running_inner_tools(completed=status == "complete")
1097
+ metadata = self.metadata or {}
1098
+ transcript_final = metadata.get(SUB_AGENT_TRANSCRIPT_FINAL_TEXT_METADATA_KEY)
1099
+ has_transcript_override = status == "complete" and isinstance(transcript_final, str)
1100
+ if not has_transcript_override or transcript_final:
1101
+ self._record_final_transcript(transcript_final if has_transcript_override else result)
1102
+ self._transcript_journal.finalize_retention(
1103
+ durable_replay_available=(
1104
+ bool(metadata.get("sub_agent_log_file")) and metadata.get("sub_agent_audit_complete") is True
1105
+ )
1106
+ )
1107
+ status_message = {
1108
+ "complete": TOOL_CARD_COMPLETED,
1109
+ "error": TOOL_CARD_ERRORED,
1110
+ "rejected": TOOL_CARD_REJECTED,
1111
+ "interrupted": TOOL_CARD_INTERRUPTED,
1112
+ }[status]
1113
+ reason = _error_reason(result) if status == "error" else ""
1114
+ self._remember_activity(_SUB_AGENT_ERRORED_WITH_REASON.bind(reason=reason) if reason else status_message.bind())
1115
+
1116
+ def _drop_inner_call_tracking(self) -> None:
1117
+ """Free per-call id bookkeeping once the parent call is terminal.
1118
+
1119
+ Completed cards stay mounted for the session; keeping every inner
1120
+ call id (up to thousands per ACP attempt, fresh ids per retry)
1121
+ would grow the seen/terminal sets without bound across a long
1122
+ session. Late inner events are already inert after a parent
1123
+ terminal: completions need a live ``_inner_tools`` entry, and
1124
+ session replay rebuilds a fresh card from scratch.
1125
+ """
1126
+ self._inner_tools.clear()
1127
+ self._seen_inner_call_ids.clear()
1128
+ self._terminal_inner_call_ids.clear()
1129
+
1130
+ def _clear_transient_state(self) -> None:
1131
+ """Drop stale retry/pause CSS classes + banner text before a terminal transition.
1132
+
1133
+ Both ``-retrying`` and ``-paused`` belong to *in-flight* states.
1134
+ When the sub-agent finally completes or errors out, the banners
1135
+ those classes reveal should not remain visible — otherwise the
1136
+ card shows a "Retrying in 7s…" line *after* a successful
1137
+ "Recovered" result, or a "Stream stalled" pause-info line *after*
1138
+ the user has already aborted.
1139
+ """
1140
+ self.remove_class("-retrying")
1141
+ self.remove_class("-paused")
1142
+ self.remove_class("-sleeping")
1143
+ with suppress(Exception):
1144
+ self.query_one("#sa-retry-banner", Static).update("")
1145
+ self.query_one("#sa-pause-info", Static).update("")
1146
+
1147
+ def set_complete(self, result: str, duration_ms: int = 0, **kwargs: Any) -> None:
1148
+ """Mark this sub-agent call as complete (parent tool call finished).
1149
+
1150
+ If structured metadata or legacy result text indicates a sub-agent
1151
+ error, redirect to ``set_error()`` so the widget renders with the
1152
+ error style.
1153
+ Rejected tools (approval declined) get a separate warning style.
1154
+ """
1155
+ self.approval = kwargs.get("approval")
1156
+ metadata = kwargs.get("metadata")
1157
+ self.metadata = metadata if isinstance(metadata, dict) else {}
1158
+ if self.metadata.get(TOOL_INTERRUPTED_METADATA_KEY) is True:
1159
+ self.result_text = result
1160
+ self.duration_ms = duration_ms
1161
+ self.status = "interrupted"
1162
+ self._finish_transcript(result, "interrupted")
1163
+ self._drop_inner_call_tracking()
1164
+ self._clear_transient_state()
1165
+ self.add_class("-rejected")
1166
+ if self._timer is not None:
1167
+ self._timer.stop()
1168
+ with suppress(Exception):
1169
+ panel = self.query_one("#sa-panel")
1170
+ panel.border_title = Text(self.tool_name)
1171
+ panel.border_subtitle = render_text(widget_localizer(self), TOOL_CARD_INTERRUPTED.bind())
1172
+ self.query_one("#sa-label", Static).update(self._label_text(duration_ms))
1173
+ self._show_completion_controls()
1174
+ return
1175
+ render_status = tool_result_render_status(
1176
+ result,
1177
+ self.approval,
1178
+ self.tool_kind or KIND_SUB_AGENT,
1179
+ self.metadata,
1180
+ self.tool_name,
1181
+ )
1182
+ if render_status == "rejected":
1183
+ self.result_text = result
1184
+ self.duration_ms = duration_ms
1185
+ self.status = "rejected"
1186
+ self._finish_transcript(result, "rejected")
1187
+ self._drop_inner_call_tracking()
1188
+ self._clear_transient_state()
1189
+ self.add_class("-rejected")
1190
+ if self._timer is not None:
1191
+ self._timer.stop()
1192
+ with suppress(Exception):
1193
+ panel = self.query_one("#sa-panel")
1194
+ panel.border_title = Text(self.tool_name)
1195
+ panel.border_subtitle = render_text(widget_localizer(self), TOOL_CARD_REJECTED.bind())
1196
+ self.query_one("#sa-label", Static).update(self._label_text(duration_ms))
1197
+ self._show_completion_controls()
1198
+ return
1199
+ if render_status == "error":
1200
+ self._set_error(result, duration_ms)
1201
+ return
1202
+ self.result_text = result
1203
+ self.duration_ms = duration_ms
1204
+ self.status = "complete"
1205
+ self._finish_transcript(result, "complete")
1206
+ self._drop_inner_call_tracking()
1207
+ self._clear_transient_state()
1208
+ self.add_class("-complete")
1209
+ if self._timer is not None:
1210
+ self._timer.stop()
1211
+ with suppress(Exception):
1212
+ self.query_one("#sa-label", Static).update(self._label_text(duration_ms))
1213
+ self._update_title()
1214
+ self._show_completion_controls()
1215
+
1216
+ def set_error(self, error: str) -> None:
1217
+ """Mark this sub-agent call as failed."""
1218
+ self._set_error(error, int((time.monotonic() - self._start_time) * 1000))
1219
+
1220
+ def _set_error(self, error: str, duration_ms: int) -> None:
1221
+ """Apply an error with either live elapsed or persisted duration."""
1222
+ self.result_text = error
1223
+ self.duration_ms = duration_ms
1224
+ self.status = "error"
1225
+ self._finish_transcript(error, "error")
1226
+ self._drop_inner_call_tracking()
1227
+ self._clear_transient_state()
1228
+ self.add_class("-error")
1229
+ if self._timer is not None:
1230
+ self._timer.stop()
1231
+ with suppress(Exception):
1232
+ self.query_one("#sa-label", Static).update(self._label_text(duration_ms))
1233
+ self._update_title()
1234
+ self._show_completion_controls()
1235
+
1236
+ # --- Paused / retry / resumed state ---
1237
+
1238
+ def set_retry_attempt(self, message: str, attempt: int, max_attempts: int, delay_seconds: int) -> None:
1239
+ """Show an inline retry banner for an auto-retry attempt.
1240
+
1241
+ Called for :class:`InvocationRetryAttempt` events — the controller
1242
+ is retrying transient failures without user intervention. The
1243
+ banner gives the user visibility into the retry cadence. The
1244
+ ``↻`` prefix visually marks the line as a retry state sub-entry,
1245
+ distinct from the result line that follows it on recovery.
1246
+ """
1247
+ self.add_class("-retrying")
1248
+ retry_message = self._render_message(
1249
+ _SUB_AGENT_RETRYING.bind(
1250
+ delay_seconds=delay_seconds,
1251
+ attempt=attempt,
1252
+ max_attempts=max_attempts,
1253
+ message=message,
1254
+ )
1255
+ )
1256
+ with suppress(Exception):
1257
+ self.query_one("#sa-retry-banner", Static).update(Text(retry_message, style="italic"))
1258
+
1259
+ def set_paused(
1260
+ self,
1261
+ reason: str,
1262
+ last_error: str,
1263
+ retry_attempts: int,
1264
+ diagnostic_path: str | None = None,
1265
+ last_error_display: str | None = None,
1266
+ ) -> None:
1267
+ """Transition the card to a paused state with Retry/Abort buttons.
1268
+
1269
+ Called when :class:`InvocationPaused` arrives. Auto-retry (if any
1270
+ happened first) is now finished; the banner is replaced with the
1271
+ pause-info block and the action row is revealed.
1272
+ ``last_error_display`` is the rendered meaning of ``last_error``,
1273
+ shown above the raw text.
1274
+ """
1275
+ self.status = "paused"
1276
+ # Paused supersedes the retrying banner — the retry run completed
1277
+ # (unsuccessfully). Keep visual history of the last error only.
1278
+ self.remove_class("-retrying")
1279
+ self.remove_class("-sleeping")
1280
+ self.add_class("-paused")
1281
+ self.remove_class("-complete")
1282
+ self.remove_class("-error")
1283
+ self._sync_activity_summary()
1284
+ if self._timer is not None:
1285
+ self._timer.stop()
1286
+ elapsed_ms = int((time.monotonic() - self._start_time) * 1000)
1287
+ reason_definition = _SUB_AGENT_REASON_MESSAGES.get(reason, _SUB_AGENT_REASON_PAUSED)
1288
+ reason_label = self._render_message(reason_definition.bind())
1289
+ info_lines = [reason_label]
1290
+ if retry_attempts:
1291
+ info_lines.append(self._render_message(_SUB_AGENT_AFTER_RETRIES.bind(count=retry_attempts)))
1292
+ if last_error_display:
1293
+ info_lines.append(last_error_display)
1294
+ if last_error:
1295
+ info_lines.append(last_error)
1296
+ if diagnostic_path:
1297
+ # Display copy only — the operational path stays raw on the event.
1298
+ info_lines.append(
1299
+ self._render_message(_SUB_AGENT_DIAGNOSTICS.bind(path=surrogate_safe_text(diagnostic_path)))
1300
+ )
1301
+ info = "\n".join(info_lines)
1302
+ with suppress(Exception):
1303
+ self.query_one("#sa-label", Static).update(self._label_text(elapsed_ms))
1304
+ panel = self.query_one("#sa-panel", Vertical)
1305
+ panel.border_title = Text(f"\u25aa {self.tool_name}") # small square = paused
1306
+ panel.border_subtitle = render_text(widget_localizer(self), _SUB_AGENT_PAUSED.bind())
1307
+ self.query_one("#sa-pause-info", Static).update(Text(info, style="red"))
1308
+ self.query_one("#sa-retry-banner", Static).update("")
1309
+
1310
+ def set_resumed_after_pause(self) -> None:
1311
+ """Clear the paused state and reset the card to a live running view.
1312
+
1313
+ Called when :class:`InvocationResumed` arrives — the user
1314
+ clicked Retry and the controller has re-entered running state.
1315
+ """
1316
+ if self.status != "paused":
1317
+ return
1318
+ self.status = "running"
1319
+ self.remove_class("-paused")
1320
+ self._sync_activity_summary()
1321
+ self._refresh_sleep_action_state()
1322
+ self._start_time = time.monotonic() # reset elapsed clock for the new attempt
1323
+ self._spin_idx = 0
1324
+ # Inner-call tracking is attempt-local: the dead attempt's stream is
1325
+ # fully drained (the bus delivers synchronously), and a fresh attempt
1326
+ # may legitimately reuse raw call ids (the ACP translator scopes ids
1327
+ # with an attempt prefix for exactly that reason). Keeping the old
1328
+ # feed would pin its ids forever across unlimited manual retries and
1329
+ # make the stale terminal-guard swallow a reused id's new call.
1330
+ self._drop_inner_call_tracking()
1331
+ with suppress(Exception):
1332
+ self.query_one("#sa-pause-info", Static).update("")
1333
+ self.query_one("#sa-retry-banner", Static).update("")
1334
+ self.query_one("#sa-label", Static).update(self._running_label_text())
1335
+ self._update_title()
1336
+ self.remove_class("-retrying")
1337
+ # Restart the spinner timer.
1338
+ if self._timer is not None:
1339
+ self._timer.stop()
1340
+ self._timer = self.set_interval(0.12, self._spin)
1341
+
1342
+ def set_cascade_aborted(self) -> None:
1343
+ """Mark the card as cascade-aborted by a global interrupt."""
1344
+ self.remove_class("-paused")
1345
+ self.remove_class("-retrying")
1346
+ self.set_error("Error: cancelled (global interrupt)")
1347
+
1348
+ def set_aborted(self, last_error: str) -> None:
1349
+ """Mark the card as aborted by the user after a pause.
1350
+
1351
+ Fired on :class:`InvocationAborted`. Clears the paused banner (so
1352
+ the Retry/Abort buttons and pause-info disappear immediately) and
1353
+ routes through :meth:`set_error` so the card gets the standard
1354
+ error styling + a message indicating the abort was user-driven.
1355
+ """
1356
+ self.remove_class("-paused")
1357
+ self.remove_class("-retrying")
1358
+ error_text = (
1359
+ f"Error: sub-agent aborted by user after failure — {last_error}"
1360
+ if last_error
1361
+ else "Error: sub-agent aborted by user"
1362
+ )
1363
+ self.set_error(error_text)
1364
+
1365
+ # --- Button actions ---
1366
+
1367
+ def on_button_pressed(self, event: Button.Pressed) -> None: # type: ignore[name-defined]
1368
+ """Translate a Retry/Abort click into a bubbled message.
1369
+
1370
+ Guarded by ``_invocation_id`` being set — we should never
1371
+ dispatch a retry/abort for a card that hasn't been linked yet
1372
+ (that would mean no controller exists to receive it).
1373
+ """
1374
+ if event.button.id == "sa-skip-sleep-btn":
1375
+ if call_id := self._running_sleep_call_id():
1376
+ self.post_message(SleepSkipClicked(call_id))
1377
+ event.stop()
1378
+ return
1379
+ if self._invocation_id is None:
1380
+ return
1381
+ if event.button.id == "sa-retry-btn":
1382
+ self.post_message(SubAgentRetryClicked(self._invocation_id))
1383
+ elif event.button.id == "sa-abort-btn":
1384
+ self.post_message(SubAgentAbortClicked(self._invocation_id))
1385
+ event.stop()