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,1176 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """External ACP agent configuration panel."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import contextlib
9
+ import math
10
+ import os
11
+ import tempfile
12
+ from collections.abc import Callable, Sequence
13
+ from pathlib import Path
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from acp import RequestError
17
+ from acp import schema as acp_schema
18
+ from rich.markup import escape
19
+ from textual import on, work
20
+ from textual.containers import Horizontal, Vertical, VerticalScroll
21
+ from textual.widgets import Button, Label, Static
22
+
23
+ from chrys.app.tui.i18n import render_str, widget_localizer
24
+ from chrys.app.tui.screens.agents.panels.markdown_report import (
25
+ NOT_ADVERTISED as _NOT_ADVERTISED,
26
+ )
27
+ from chrys.app.tui.screens.agents.panels.markdown_report import (
28
+ SUPPORTED_MARK as _SUPPORTED_MARK,
29
+ )
30
+ from chrys.app.tui.screens.agents.panels.markdown_report import (
31
+ UNSUPPORTED_MARK as _UNSUPPORTED_MARK,
32
+ )
33
+ from chrys.app.tui.screens.agents.panels.markdown_report import (
34
+ code_span as _code_span,
35
+ )
36
+ from chrys.app.tui.screens.agents.panels.markdown_report import (
37
+ definition_bullets as _definition_bullets,
38
+ )
39
+ from chrys.app.tui.screens.agents.panels.markdown_report import (
40
+ inline_text as _inline_text,
41
+ )
42
+ from chrys.app.tui.screens.agents.panels.markdown_report import (
43
+ md_table as _md_table,
44
+ )
45
+ from chrys.app.tui.screens.agents.validation_messages import (
46
+ ACP_ALLOW_EXTERNAL_CWD as _ALLOW_EXTERNAL_CWD,
47
+ )
48
+ from chrys.app.tui.screens.agents.validation_messages import (
49
+ ACP_CONFIG_OPTION_ROW_LABEL,
50
+ ACP_CWD_OUTSIDE,
51
+ ACP_ENVIRONMENT_ROW_LABEL,
52
+ ACP_EXECUTABLE_REQUIRED,
53
+ ACP_EXPANDED_LAUNCH_NUL,
54
+ ACP_HANDSHAKE_TIMEOUT_LABEL,
55
+ ACP_IDLE_TIMEOUT_LABEL,
56
+ ACP_LAUNCH_NUL,
57
+ ACP_TIMEOUT_NUMBER,
58
+ ACP_TIMEOUT_RANGE,
59
+ DUPLICATE_KEY_ROW,
60
+ GREATER_THAN_ZERO,
61
+ KEY_REQUIRED_ROW,
62
+ ZERO_OR_GREATER,
63
+ )
64
+ from chrys.app.tui.screens.dialogs.connection_test import ConnectionTestDialog
65
+ from chrys.app.tui.widgets import Checkbox, ConfigAddButton, Select
66
+ from chrys.app.tui.widgets import EnhancedInput as Input
67
+ from chrys.foundation.i18n import DisplayBlock, MessageDef, MessageRef, msg
68
+ from chrys.foundation.i18n.formatting import format_message
69
+ from chrys.foundation.util.env_templates import resolve_env_templates
70
+ from chrys.service.acp_client import AcpAgentClient, AcpAgentSpec, PermissionDecision
71
+ from chrys.service.profiles.agents.schema import AcpAgentConfig
72
+
73
+ if TYPE_CHECKING:
74
+ from textual.app import ComposeResult
75
+
76
+ _TEST_TIMEOUT_SECONDS = 15.0
77
+
78
+ _ACP_AGENT_SUBJECT = msg("tui.acp.connection_test.subject", fallback="ACP Agent")
79
+ _STDERR_TAIL_LABEL = msg("tui.acp.connection_test.stderr_tail", fallback="stderr tail (UI only):")
80
+ _CAPABILITIES_HEADING = msg("tui.acp.connection_report.heading.capabilities", fallback="Capabilities")
81
+ _MODES_HEADING = msg("tui.acp.connection_report.heading.modes", fallback="Modes")
82
+ _MODELS_HEADING = msg("tui.acp.connection_report.heading.models", fallback="Models")
83
+ _CONFIG_OPTIONS_HEADING = msg("tui.acp.connection_report.heading.config_options", fallback="Config Options")
84
+ _AUTH_METHODS_HEADING = msg("tui.acp.connection_report.heading.auth_methods", fallback="Auth Methods")
85
+ _CAPABILITY_HEADER = msg("tui.acp.connection_report.header.capability", fallback="Capability")
86
+ _SUPPORTED_HEADER = msg("tui.acp.connection_report.header.supported", fallback="Supported")
87
+ _MODEL_HEADER = msg("tui.acp.connection_report.header.model", fallback="Model")
88
+ _ID_HEADER = msg("tui.acp.connection_report.header.id", fallback="ID")
89
+ _DESCRIPTION_HEADER = msg("tui.acp.connection_report.header.description", fallback="Description")
90
+ _CURRENT_CAPTION = msg("tui.acp.connection_report.current", fallback="Current: {label}")
91
+ _CURRENT_VALUE = msg("tui.acp.connection_report.current_value", fallback="current: {value}")
92
+ _ENV_LABEL = msg("tui.acp.connection_report.env", fallback="Env: {names}")
93
+ _IDENTITY_NOT_REPORTED = msg(
94
+ "tui.acp.connection_report.identity_not_reported",
95
+ fallback="*The agent did not report its identity.*",
96
+ )
97
+ _ACP_AGENT_FALLBACK_TITLE = msg("tui.acp.connection_report.fallback_title", fallback="ACP Agent")
98
+ _LAST_MESSAGE_SEGMENT = msg("tui.acp.result.last_segment", fallback="Last message segment")
99
+ _FULL_TRANSCRIPT = msg("tui.acp.result.full_transcript", fallback="Full transcript")
100
+ _RESULT_MODE_OPTIONS = [
101
+ (_LAST_MESSAGE_SEGMENT, "last_segment"),
102
+ (_FULL_TRANSCRIPT, "transcript"),
103
+ ]
104
+ _STRING = msg("tui.acp.option_type.string", fallback="String")
105
+ _BOOLEAN = msg("tui.acp.option_type.boolean", fallback="Boolean")
106
+ _OPTION_TYPE_OPTIONS = [
107
+ (_STRING, "string"),
108
+ (_BOOLEAN, "boolean"),
109
+ ]
110
+ _ARGUMENT_PLACEHOLDER = msg("tui.acp.placeholder.argument", fallback="Argument")
111
+ _VARIABLE_NAME_PLACEHOLDER = msg("tui.acp.placeholder.variable_name", fallback="Variable name")
112
+ _OPTION_ID_PLACEHOLDER = msg("tui.acp.placeholder.option_id", fallback="Option id")
113
+ _VALUE_PLACEHOLDER = msg("tui.acp.placeholder.value", fallback="Value")
114
+ _ENABLED = msg("tui.acp.enabled", fallback="Enabled")
115
+ _EXTERNAL_AGENT = msg("tui.acp.title", fallback="External ACP Agent")
116
+ _DESCRIPTION = msg(
117
+ "tui.acp.description",
118
+ fallback="Launch one stdio ACP process for each delegated invocation.",
119
+ )
120
+ _EXECUTABLE = msg("tui.acp.executable", fallback="Executable")
121
+ _EXECUTABLE_PLACEHOLDER = msg(
122
+ "tui.acp.placeholder.executable",
123
+ fallback="Executable path or command",
124
+ )
125
+ _ARGUMENTS = msg("tui.acp.arguments", fallback="Arguments")
126
+ _ARGUMENT = msg("tui.acp.argument", fallback="Argument")
127
+ _ENVIRONMENT_VARIABLES = msg("tui.acp.environment_variables", fallback="Environment Variables")
128
+ _VARIABLE = msg("tui.acp.variable", fallback="Variable")
129
+ _WORKING_DIRECTORY = msg("tui.acp.working_directory", fallback="Working Directory")
130
+ _WORKING_DIRECTORY_PLACEHOLDER = msg(
131
+ "tui.acp.placeholder.working_directory",
132
+ fallback="Blank uses workspace primary directory",
133
+ )
134
+ _ALLOW_EXTERNAL_CWD_DESCRIPTION = msg(
135
+ "tui.acp.allow_external_cwd_description",
136
+ fallback="Permit a working directory that resolves outside the workspace roots",
137
+ )
138
+ _SESSION_MODE = msg("tui.acp.session_mode", fallback="Session Mode")
139
+ _SESSION_MODE_PLACEHOLDER = msg("tui.acp.placeholder.session_mode", fallback="Optional mode id")
140
+ _UNSAFE_MODE_CAUTION = msg(
141
+ "tui.acp.unsafe_mode_caution",
142
+ fallback="Caution: this mode may bypass the remote agent's permission prompts.",
143
+ )
144
+ _MODEL_ID = msg("tui.acp.model_id", fallback="Model ID")
145
+ _MODEL_ID_PLACEHOLDER = msg("tui.acp.placeholder.model_id", fallback="Optional remote model id")
146
+ _CONFIG_OPTIONS = msg("tui.acp.config_options", fallback="Config Options")
147
+ _OPTION = msg("tui.acp.option", fallback="Option")
148
+ _BEST_EFFORT = msg("tui.acp.best_effort", fallback="Best effort mode and config options")
149
+ _BEST_EFFORT_DESCRIPTION = msg(
150
+ "tui.acp.best_effort_description",
151
+ fallback=(
152
+ "Continue the launch when the agent rejects the session mode or config options above "
153
+ "(the Model ID is always best-effort)"
154
+ ),
155
+ )
156
+ _RESULT = msg("tui.acp.result", fallback="Result")
157
+ _HANDSHAKE_TIMEOUT = msg("tui.acp.handshake_timeout", fallback="Handshake Timeout (seconds)")
158
+ _IDLE_TIMEOUT = msg("tui.acp.idle_timeout", fallback="Idle Timeout (seconds; 0 disables)")
159
+ _TEST = msg("tui.acp.test", fallback="Test")
160
+ _TEST_NOTE = msg(
161
+ "tui.acp.test_note",
162
+ fallback=(
163
+ "The test launches an ephemeral process, opens one session, reports advertised capabilities, then "
164
+ "force-closes it. No prompt is sent."
165
+ ),
166
+ )
167
+ _ADD_ITEM = msg("tui.acp.add_item", fallback="+ {label}")
168
+
169
+
170
+ class _AcpArgumentRow(Horizontal):
171
+ def __init__(self, index: int, value: str, *, read_only: bool) -> None:
172
+ self.index = index
173
+ self.initial_value = value
174
+ self.read_only = read_only
175
+ super().__init__(classes="acp-list-row acp-argument-row")
176
+
177
+ def compose(self) -> ComposeResult:
178
+ value = Input(
179
+ value=self.initial_value,
180
+ placeholder=render_str(widget_localizer(self), _ARGUMENT_PLACEHOLDER.bind()),
181
+ classes="acp-row-value",
182
+ )
183
+ value.disabled = self.read_only
184
+ yield value
185
+ remove = Button("✕", id=f"acp-row-delete-{self.index}", classes="acp-row-delete")
186
+ remove.disabled = self.read_only
187
+ remove.display = not self.read_only
188
+ yield remove
189
+
190
+ def value(self) -> str:
191
+ return self.query_one(Input).value
192
+
193
+
194
+ class _AcpKeyValueRow(Horizontal):
195
+ def __init__(
196
+ self,
197
+ index: int,
198
+ key: str,
199
+ value: str,
200
+ *,
201
+ kind: str,
202
+ read_only: bool,
203
+ ) -> None:
204
+ self.index = index
205
+ self.initial_key = key
206
+ self.initial_value = value
207
+ self.kind = kind
208
+ self.read_only = read_only
209
+ super().__init__(classes=f"acp-list-row acp-{kind}-row")
210
+
211
+ def compose(self) -> ComposeResult:
212
+ localizer = widget_localizer(self)
213
+ key = Input(
214
+ value=self.initial_key,
215
+ placeholder=render_str(
216
+ localizer,
217
+ (_VARIABLE_NAME_PLACEHOLDER if self.kind == "env" else _OPTION_ID_PLACEHOLDER).bind(),
218
+ ),
219
+ classes="acp-row-key",
220
+ )
221
+ key.disabled = self.read_only
222
+ yield key
223
+ value = Input(
224
+ value=self.initial_value,
225
+ placeholder=render_str(localizer, _VALUE_PLACEHOLDER.bind()),
226
+ classes="acp-row-value",
227
+ )
228
+ value.disabled = self.read_only
229
+ yield value
230
+ remove = Button("✕", id=f"acp-row-delete-{self.index}", classes="acp-row-delete")
231
+ remove.disabled = self.read_only
232
+ remove.display = not self.read_only
233
+ yield remove
234
+
235
+ def pair(self) -> tuple[str, str]:
236
+ inputs = list(self.query(Input))
237
+ return inputs[0].value.strip(), inputs[1].value
238
+
239
+
240
+ class _AcpConfigOptionRow(Horizontal):
241
+ def __init__(
242
+ self,
243
+ index: int,
244
+ key: str,
245
+ value: str | bool,
246
+ *,
247
+ read_only: bool,
248
+ ) -> None:
249
+ self.index = index
250
+ self.initial_key = key
251
+ self.initial_value = value
252
+ self.read_only = read_only
253
+ super().__init__(classes="acp-list-row acp-option-row")
254
+
255
+ def compose(self) -> ComposeResult:
256
+ localizer = widget_localizer(self)
257
+ key = Input(
258
+ value=self.initial_key,
259
+ placeholder=render_str(localizer, _OPTION_ID_PLACEHOLDER.bind()),
260
+ classes="acp-row-key",
261
+ )
262
+ key.disabled = self.read_only
263
+ yield key
264
+ value_type = "boolean" if type(self.initial_value) is bool else "string"
265
+ type_select = Select(
266
+ [(render_str(localizer, label.bind()), value) for label, value in _OPTION_TYPE_OPTIONS],
267
+ value=value_type,
268
+ allow_blank=False,
269
+ classes="acp-option-type",
270
+ )
271
+ type_select.disabled = self.read_only
272
+ yield type_select
273
+ string_value = Input(
274
+ value=self.initial_value if isinstance(self.initial_value, str) else "",
275
+ placeholder=render_str(localizer, _VALUE_PLACEHOLDER.bind()),
276
+ classes="acp-option-string",
277
+ )
278
+ string_value.disabled = self.read_only
279
+ string_value.display = value_type == "string"
280
+ yield string_value
281
+ bool_value = Checkbox(
282
+ render_str(localizer, _ENABLED.bind()),
283
+ value=bool(self.initial_value) if type(self.initial_value) is bool else False,
284
+ classes="acp-option-bool",
285
+ )
286
+ bool_value.disabled = self.read_only
287
+ bool_value.display = value_type == "boolean"
288
+ yield bool_value
289
+ remove = Button("✕", id=f"acp-row-delete-{self.index}", classes="acp-row-delete")
290
+ remove.disabled = self.read_only
291
+ remove.display = not self.read_only
292
+ yield remove
293
+
294
+ @on(Select.Changed, ".acp-option-type")
295
+ def _on_type_changed(self, event: Select.Changed) -> None:
296
+ is_bool = event.value == "boolean"
297
+ self.query_one(".acp-option-string", Input).display = not is_bool
298
+ self.query_one(".acp-option-bool", Checkbox).display = is_bool
299
+
300
+ def pair(self) -> tuple[str, str | bool]:
301
+ key = self.query_one(".acp-row-key", Input).value.strip()
302
+ if self.query_one(".acp-option-type", Select).value == "boolean":
303
+ return key, bool(self.query_one(".acp-option-bool", Checkbox).value)
304
+ return key, self.query_one(".acp-option-string", Input).value
305
+
306
+
307
+ class _AcpTestCallbacks:
308
+ async def on_update(self, seq: int, notification: acp_schema.SessionNotification) -> None:
309
+ _ = seq, notification
310
+ return
311
+
312
+ async def on_permission_request(
313
+ self,
314
+ tool_call: acp_schema.ToolCallUpdate,
315
+ options: Sequence[acp_schema.PermissionOption],
316
+ ) -> PermissionDecision:
317
+ _ = tool_call, options
318
+ return PermissionDecision.cancelled()
319
+
320
+ async def on_ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
321
+ _ = params
322
+ raise RequestError.method_not_found(method)
323
+
324
+
325
+ class AcpConfigPanel(VerticalScroll):
326
+ """Edit and test the single ACP launch configuration on an agent profile."""
327
+
328
+ DEFAULT_CSS = """
329
+ AcpConfigPanel {
330
+ height: 1fr;
331
+ /* Left inset only: Textual carves the vertical scrollbar out of the
332
+ content box INSIDE the padding, so right padding would strand dead
333
+ columns between the scrollbar and the pane edge. */
334
+ padding: 0 0 0 2;
335
+ scrollbar-size-vertical: 1;
336
+ scrollbar-gutter: stable;
337
+ }
338
+ /* The right inset rides child margins instead of panel padding for the
339
+ same reason: full-width children would otherwise touch the scrollbar.
340
+ Full-width child rules that set a `margin:` shorthand must keep
341
+ right = 1 themselves — the shorthand outranks this rule. */
342
+ AcpConfigPanel > * {
343
+ margin-right: 1;
344
+ }
345
+ AcpConfigPanel .acp-section-title {
346
+ color: $secondary;
347
+ text-style: bold;
348
+ height: 1;
349
+ }
350
+ AcpConfigPanel .acp-section-desc,
351
+ AcpConfigPanel .acp-note {
352
+ color: $text-muted;
353
+ height: auto;
354
+ margin: 0 1 1 0;
355
+ }
356
+ AcpConfigPanel .acp-label {
357
+ height: 1;
358
+ color: $text-muted;
359
+ margin: 1 0 0 0;
360
+ }
361
+ AcpConfigPanel Checkbox {
362
+ height: 1;
363
+ padding: 0;
364
+ border: none;
365
+ background: transparent;
366
+ /* Align the [ ] glyph with the inputs' 1-cell text inset. */
367
+ margin: 0 0 0 1;
368
+ }
369
+ /* Blank line between the Working Directory input and its checkbox —
370
+ the Best-effort checkbox already gets one from the add-row above.
371
+ Full margin: a single-side override here beats the Checkbox rule
372
+ above for EVERY side, so the left inset must be restated. */
373
+ AcpConfigPanel #acp-allow-external-cwd {
374
+ margin: 1 0 0 1;
375
+ }
376
+ AcpConfigPanel Checkbox > .toggle--button {
377
+ color: $foreground 35%;
378
+ background: $foreground 6%;
379
+ }
380
+ AcpConfigPanel Checkbox.-on > .toggle--button {
381
+ color: $success;
382
+ background: $secondary 12%;
383
+ }
384
+ AcpConfigPanel .acp-option-desc {
385
+ color: $text-muted;
386
+ height: auto;
387
+ width: 1fr;
388
+ margin: 0 1 1 4;
389
+ text-wrap: wrap;
390
+ text-overflow: fold;
391
+ }
392
+ AcpConfigPanel Input {
393
+ height: 1;
394
+ border: none;
395
+ background: $foreground 8%;
396
+ padding: 0 1;
397
+ }
398
+ AcpConfigPanel Input:focus {
399
+ border: none;
400
+ background: $foreground 12%;
401
+ }
402
+ AcpConfigPanel Select {
403
+ height: auto;
404
+ }
405
+ AcpConfigPanel SelectCurrent {
406
+ height: 1;
407
+ border: none;
408
+ background: $foreground 8%;
409
+ padding: 0 1;
410
+ }
411
+ AcpConfigPanel SelectOverlay {
412
+ height: auto;
413
+ max-height: 12;
414
+ border: round $tui-border-primary $border-opacity;
415
+ background: $surface;
416
+ }
417
+ AcpConfigPanel .acp-list {
418
+ height: auto;
419
+ }
420
+ AcpConfigPanel .acp-list-row {
421
+ height: auto;
422
+ margin: 0 0 1 0;
423
+ }
424
+ AcpConfigPanel .acp-row-key {
425
+ width: 2fr;
426
+ margin-right: 1;
427
+ }
428
+ AcpConfigPanel .acp-row-value,
429
+ AcpConfigPanel .acp-option-string {
430
+ width: 3fr;
431
+ }
432
+ AcpConfigPanel .acp-option-type {
433
+ width: 14;
434
+ margin-right: 1;
435
+ }
436
+ AcpConfigPanel .acp-option-bool {
437
+ width: 3fr;
438
+ }
439
+ AcpConfigPanel .acp-row-delete {
440
+ width: 3;
441
+ min-width: 3;
442
+ height: 1;
443
+ border: none;
444
+ background: transparent;
445
+ color: $error;
446
+ /* Must stay 0: config.tcss re-asserts this at #ac-tabs specificity —
447
+ a squeezed content box makes Button's line-pad corrupt strip
448
+ widths (see the note beside `#ac-tabs Button`). */
449
+ padding: 0;
450
+ content-align: center middle;
451
+ text-align: center;
452
+ }
453
+ AcpConfigPanel .acp-add-row,
454
+ AcpConfigPanel #acp-test {
455
+ width: auto;
456
+ min-width: 10;
457
+ margin: 0 0 1 0;
458
+ }
459
+ AcpConfigPanel #acp-test {
460
+ margin: 1 0 1 0;
461
+ }
462
+ AcpConfigPanel .acp-caution {
463
+ color: $warning;
464
+ background: $warning 12%;
465
+ height: auto;
466
+ margin: 1 1 1 0;
467
+ padding: 0 1;
468
+ }
469
+ AcpConfigPanel .acp-timeout-row {
470
+ height: auto;
471
+ }
472
+ AcpConfigPanel .acp-timeout-field {
473
+ width: 1fr;
474
+ height: auto;
475
+ }
476
+ /* Gap between the two timeout fields only — a trailing margin on the
477
+ last field would pull it out of line with the inputs above. */
478
+ AcpConfigPanel .acp-timeout-gap {
479
+ margin-right: 1;
480
+ }
481
+ """
482
+
483
+ def __init__(
484
+ self,
485
+ config: AcpAgentConfig,
486
+ *,
487
+ workspace_cwd: str | None = None,
488
+ workspace_roots: list[str] | None = None,
489
+ read_only: bool = False,
490
+ ) -> None:
491
+ self._config = config
492
+ self._workspace_cwd = workspace_cwd or ""
493
+ self._workspace_roots = [root for root in workspace_roots or [] if root]
494
+ if not self._workspace_roots and self._workspace_cwd:
495
+ self._workspace_roots = [self._workspace_cwd]
496
+ self._read_only = read_only
497
+ self._next_index = 0
498
+ self._testing = False
499
+ super().__init__()
500
+
501
+ def _render_message(self, reference: MessageRef) -> str:
502
+ return render_str(widget_localizer(self), reference)
503
+
504
+ def _index(self) -> int:
505
+ index = self._next_index
506
+ self._next_index += 1
507
+ return index
508
+
509
+ def compose(self) -> ComposeResult:
510
+ config = self._config
511
+ localizer = widget_localizer(self)
512
+ yield Label(render_str(localizer, _EXTERNAL_AGENT.bind()), classes="acp-section-title")
513
+ yield Label(
514
+ render_str(localizer, _DESCRIPTION.bind()),
515
+ classes="acp-section-desc",
516
+ )
517
+ yield Label(f"[red]*[/red] {escape(render_str(localizer, _EXECUTABLE.bind()))}", classes="acp-label")
518
+ command = Input(
519
+ value=config.command,
520
+ placeholder=render_str(localizer, _EXECUTABLE_PLACEHOLDER.bind()),
521
+ id="acp-command",
522
+ )
523
+ command.disabled = self._read_only
524
+ yield command
525
+
526
+ yield Label(render_str(localizer, _ARGUMENTS.bind()), classes="acp-label")
527
+ yield Vertical(
528
+ *(_AcpArgumentRow(self._index(), value, read_only=self._read_only) for value in config.args),
529
+ id="acp-args",
530
+ classes="acp-list",
531
+ )
532
+ yield self._add_button(_ARGUMENT, "acp-add-arg")
533
+
534
+ yield Label(render_str(localizer, _ENVIRONMENT_VARIABLES.bind()), classes="acp-label")
535
+ yield Vertical(
536
+ *(
537
+ _AcpKeyValueRow(self._index(), key, value, kind="env", read_only=self._read_only)
538
+ for key, value in config.env.items()
539
+ ),
540
+ id="acp-env",
541
+ classes="acp-list",
542
+ )
543
+ yield self._add_button(_VARIABLE, "acp-add-env")
544
+
545
+ yield Label(render_str(localizer, _WORKING_DIRECTORY.bind()), classes="acp-label")
546
+ cwd = Input(
547
+ value=config.cwd,
548
+ placeholder=render_str(localizer, _WORKING_DIRECTORY_PLACEHOLDER.bind()),
549
+ id="acp-cwd",
550
+ )
551
+ cwd.disabled = self._read_only
552
+ yield cwd
553
+ allow_external = Checkbox(
554
+ render_str(localizer, _ALLOW_EXTERNAL_CWD.bind()),
555
+ value=config.allow_external_cwd,
556
+ id="acp-allow-external-cwd",
557
+ )
558
+ allow_external.disabled = self._read_only
559
+ yield allow_external
560
+ yield Label(
561
+ render_str(localizer, _ALLOW_EXTERNAL_CWD_DESCRIPTION.bind()),
562
+ classes="acp-option-desc",
563
+ )
564
+
565
+ yield Label(render_str(localizer, _SESSION_MODE.bind()), classes="acp-label")
566
+ mode = Input(
567
+ value=config.session_mode,
568
+ placeholder=render_str(localizer, _SESSION_MODE_PLACEHOLDER.bind()),
569
+ id="acp-session-mode",
570
+ )
571
+ mode.disabled = self._read_only
572
+ yield mode
573
+ caution = Static(
574
+ render_str(localizer, _UNSAFE_MODE_CAUTION.bind()),
575
+ id="acp-mode-caution",
576
+ classes="acp-caution",
577
+ )
578
+ caution.display = self._unsafe_mode(config.session_mode)
579
+ yield caution
580
+
581
+ yield Label(render_str(localizer, _MODEL_ID.bind()), classes="acp-label")
582
+ model = Input(
583
+ value=config.model_id,
584
+ placeholder=render_str(localizer, _MODEL_ID_PLACEHOLDER.bind()),
585
+ id="acp-model-id",
586
+ )
587
+ model.disabled = self._read_only
588
+ yield model
589
+
590
+ yield Label(render_str(localizer, _CONFIG_OPTIONS.bind()), classes="acp-label")
591
+ yield Vertical(
592
+ *(
593
+ _AcpConfigOptionRow(self._index(), key, value, read_only=self._read_only)
594
+ for key, value in config.config_options.items()
595
+ ),
596
+ id="acp-options",
597
+ classes="acp-list",
598
+ )
599
+ yield self._add_button(_OPTION, "acp-add-option")
600
+ best_effort = Checkbox(
601
+ render_str(localizer, _BEST_EFFORT.bind()),
602
+ value=config.best_effort_options,
603
+ id="acp-best-effort",
604
+ )
605
+ best_effort.disabled = self._read_only
606
+ yield best_effort
607
+ yield Label(
608
+ render_str(localizer, _BEST_EFFORT_DESCRIPTION.bind()),
609
+ classes="acp-option-desc",
610
+ )
611
+
612
+ yield Label(render_str(localizer, _RESULT.bind()), classes="acp-label")
613
+ result_mode = Select(
614
+ [(render_str(localizer, label.bind()), value) for label, value in _RESULT_MODE_OPTIONS],
615
+ value=config.result_mode,
616
+ allow_blank=False,
617
+ id="acp-result-mode",
618
+ )
619
+ result_mode.disabled = self._read_only
620
+ yield result_mode
621
+
622
+ with Horizontal(classes="acp-timeout-row"):
623
+ with Vertical(classes="acp-timeout-field acp-timeout-gap"):
624
+ yield Label(render_str(localizer, _HANDSHAKE_TIMEOUT.bind()), classes="acp-label")
625
+ handshake = Input(value=str(config.handshake_timeout_seconds), id="acp-handshake-timeout")
626
+ handshake.disabled = self._read_only
627
+ yield handshake
628
+ with Vertical(classes="acp-timeout-field"):
629
+ yield Label(render_str(localizer, _IDLE_TIMEOUT.bind()), classes="acp-label")
630
+ idle = Input(value=str(config.idle_timeout_seconds), id="acp-idle-timeout")
631
+ idle.disabled = self._read_only
632
+ yield idle
633
+
634
+ test = ConfigAddButton(render_str(localizer, _TEST.bind()), id="acp-test")
635
+ test.disabled = self._read_only
636
+ test.display = not self._read_only
637
+ yield test
638
+ yield Static(
639
+ render_str(localizer, _TEST_NOTE.bind()),
640
+ classes="acp-note",
641
+ )
642
+
643
+ def _add_button(self, label: MessageDef, button_id: str) -> Button:
644
+ localizer = widget_localizer(self)
645
+ button = ConfigAddButton(
646
+ render_str(localizer, _ADD_ITEM.bind(label=render_str(localizer, label.bind()))),
647
+ id=button_id,
648
+ classes="acp-add-row",
649
+ )
650
+ button.disabled = self._read_only
651
+ button.display = not self._read_only
652
+ return button
653
+
654
+ @staticmethod
655
+ def _unsafe_mode(value: str) -> bool:
656
+ # Known prompt-skipping mode families: Claude Code acceptEdits /
657
+ # bypassPermissions, Codex full-access, Gemini CLI yolo. Separator
658
+ # stripping catches full_access/fullAccess spellings too.
659
+ folded = value.casefold()
660
+ compact = folded.replace("-", "").replace("_", "").replace(" ", "")
661
+ return "accept" in folded or "bypass" in folded or "fullaccess" in compact or "yolo" in folded
662
+
663
+ @on(Input.Changed, "#acp-session-mode")
664
+ def _on_mode_changed(self, event: Input.Changed) -> None:
665
+ self.query_one("#acp-mode-caution", Static).display = self._unsafe_mode(event.value)
666
+
667
+ @on(Button.Pressed, "#acp-add-arg")
668
+ async def _on_add_arg(self, _event: Button.Pressed) -> None:
669
+ await self.query_one("#acp-args", Vertical).mount(_AcpArgumentRow(self._index(), "", read_only=self._read_only))
670
+
671
+ @on(Button.Pressed, "#acp-add-env")
672
+ async def _on_add_env(self, _event: Button.Pressed) -> None:
673
+ await self.query_one("#acp-env", Vertical).mount(
674
+ _AcpKeyValueRow(self._index(), "", "", kind="env", read_only=self._read_only)
675
+ )
676
+
677
+ @on(Button.Pressed, "#acp-add-option")
678
+ async def _on_add_option(self, _event: Button.Pressed) -> None:
679
+ await self.query_one("#acp-options", Vertical).mount(
680
+ _AcpConfigOptionRow(self._index(), "", "", read_only=self._read_only)
681
+ )
682
+
683
+ @on(Button.Pressed, ".acp-row-delete")
684
+ async def _on_delete_row(self, event: Button.Pressed) -> None:
685
+ parent = event.button.parent
686
+ if isinstance(parent, _AcpArgumentRow | _AcpKeyValueRow | _AcpConfigOptionRow):
687
+ await parent.remove()
688
+
689
+ def get_config(self) -> AcpAgentConfig:
690
+ """Harvest separate executable, argument, environment, and option fields."""
691
+ arguments = [row.value() for row in self.query(_AcpArgumentRow)]
692
+ env = {
693
+ key: value for row in self.query(_AcpKeyValueRow) if row.kind == "env" for key, value in [row.pair()] if key
694
+ }
695
+ options = {key: value for row in self.query(_AcpConfigOptionRow) for key, value in [row.pair()] if key}
696
+ result_value = self.query_one("#acp-result-mode", Select).value
697
+ return AcpAgentConfig(
698
+ command=self.query_one("#acp-command", Input).value.strip(),
699
+ args=arguments,
700
+ env=env,
701
+ cwd=self.query_one("#acp-cwd", Input).value.strip(),
702
+ allow_external_cwd=bool(self.query_one("#acp-allow-external-cwd", Checkbox).value),
703
+ session_mode=self.query_one("#acp-session-mode", Input).value.strip(),
704
+ model_id=self.query_one("#acp-model-id", Input).value.strip(),
705
+ config_options=options,
706
+ best_effort_options=bool(self.query_one("#acp-best-effort", Checkbox).value),
707
+ result_mode="transcript" if result_value == "transcript" else "last_segment",
708
+ handshake_timeout_seconds=self._float_value(
709
+ "#acp-handshake-timeout",
710
+ self._config.handshake_timeout_seconds,
711
+ ),
712
+ idle_timeout_seconds=self._float_value(
713
+ "#acp-idle-timeout",
714
+ self._config.idle_timeout_seconds,
715
+ ),
716
+ )
717
+
718
+ def _float_value(self, selector: str, fallback: float) -> float:
719
+ with contextlib.suppress(ValueError):
720
+ return float(self.query_one(selector, Input).value.strip())
721
+ return fallback
722
+
723
+ def validate(self) -> list[str]:
724
+ localizer = widget_localizer(self)
725
+ errors: list[str] = []
726
+ if not self.query_one("#acp-command", Input).value.strip():
727
+ errors.append(render_str(localizer, ACP_EXECUTABLE_REQUIRED.bind()))
728
+ errors.extend(self._validate_key_value_rows())
729
+ errors.extend(
730
+ self._validate_timeout(
731
+ "#acp-handshake-timeout",
732
+ ACP_HANDSHAKE_TIMEOUT_LABEL,
733
+ allow_zero=False,
734
+ )
735
+ )
736
+ errors.extend(self._validate_timeout("#acp-idle-timeout", ACP_IDLE_TIMEOUT_LABEL, allow_zero=True))
737
+ config = self.get_config()
738
+ if any("\0" in value for value in (config.command, config.cwd, *config.args, *config.env.values())):
739
+ errors.append(render_str(localizer, ACP_LAUNCH_NUL.bind()))
740
+ expanded, template_errors = self._expanded_launch_values(config)
741
+ errors.extend(template_errors)
742
+ if expanded is not None:
743
+ if any(
744
+ "\0" in value
745
+ for value in (expanded["command"], expanded["cwd"], *expanded["args"], *expanded["env"].values())
746
+ ):
747
+ errors.append(render_str(localizer, ACP_EXPANDED_LAUNCH_NUL.bind()))
748
+ errors.extend(self._validate_cwd(config, expanded["cwd"]))
749
+ return errors
750
+
751
+ def _expanded_launch_values(self, config: AcpAgentConfig) -> tuple[dict[str, Any] | None, list[str]]:
752
+ """Expand ``{{ENV}}`` templates exactly like real invocation does.
753
+
754
+ Validation and the Test spawn must both operate on the EXPANDED
755
+ values — validating the literal template while launching the expanded
756
+ one lets a template that expands outside the workspace bypass the
757
+ containment check.
758
+ """
759
+ try:
760
+ expanded: dict[str, Any] = {
761
+ "command": resolve_env_templates(config.command, location="ACP command"),
762
+ "args": tuple(resolve_env_templates(value, location="ACP argument") for value in config.args),
763
+ "env": {
764
+ key: resolve_env_templates(value, location=f"ACP environment {key!r}")
765
+ for key, value in config.env.items()
766
+ },
767
+ "cwd": resolve_env_templates(config.cwd, location="ACP cwd") if config.cwd else self._workspace_cwd,
768
+ }
769
+ except ValueError as exc:
770
+ return None, [str(exc)]
771
+ return expanded, []
772
+
773
+ def _validate_key_value_rows(self) -> list[str]:
774
+ localizer = widget_localizer(self)
775
+ errors: list[str] = []
776
+ for case_insensitive, label_definition, rows in (
777
+ (True, ACP_ENVIRONMENT_ROW_LABEL, list(self.query(_AcpKeyValueRow))),
778
+ (False, ACP_CONFIG_OPTION_ROW_LABEL, list(self.query(_AcpConfigOptionRow))),
779
+ ):
780
+ label = render_str(localizer, label_definition.bind())
781
+ seen: set[str] = set()
782
+ for index, row in enumerate(rows, start=1):
783
+ key, value = row.pair()
784
+ has_value = bool(value) if isinstance(value, str) else True
785
+ if not key and has_value:
786
+ errors.append(render_str(localizer, KEY_REQUIRED_ROW.bind(label=label, row=index)))
787
+ compare = key.casefold() if case_insensitive else key
788
+ if key and compare in seen:
789
+ errors.append(
790
+ render_str(
791
+ localizer,
792
+ DUPLICATE_KEY_ROW.bind(label=label, row=index, key=DisplayBlock(key)),
793
+ )
794
+ )
795
+ if key:
796
+ seen.add(compare)
797
+ return errors
798
+
799
+ def _validate_timeout(self, selector: str, label_definition: MessageDef, *, allow_zero: bool) -> list[str]:
800
+ localizer = widget_localizer(self)
801
+ label = render_str(localizer, label_definition.bind())
802
+ raw = self.query_one(selector, Input).value.strip()
803
+ try:
804
+ value = float(raw)
805
+ except ValueError:
806
+ return [render_str(localizer, ACP_TIMEOUT_NUMBER.bind(label=label))]
807
+ if not math.isfinite(value) or value < 0 or (not allow_zero and value == 0):
808
+ qualifier_definition = ZERO_OR_GREATER if allow_zero else GREATER_THAN_ZERO
809
+ qualifier = render_str(localizer, qualifier_definition.bind())
810
+ return [render_str(localizer, ACP_TIMEOUT_RANGE.bind(label=label, qualifier=qualifier))]
811
+ return []
812
+
813
+ def _validate_cwd(self, config: AcpAgentConfig, expanded_cwd: str) -> list[str]:
814
+ if not config.cwd or config.allow_external_cwd or not self._workspace_roots:
815
+ return []
816
+ candidate = Path(expanded_cwd)
817
+ if not candidate.is_absolute():
818
+ candidate = Path(self._workspace_cwd) / candidate
819
+ resolved = os.path.realpath(candidate)
820
+ for root in self._workspace_roots:
821
+ try:
822
+ if os.path.commonpath(
823
+ [os.path.normcase(resolved), os.path.normcase(os.path.realpath(root))]
824
+ ) == os.path.normcase(os.path.realpath(root)):
825
+ return []
826
+ except ValueError:
827
+ continue
828
+ return [render_str(widget_localizer(self), ACP_CWD_OUTSIDE.bind())]
829
+
830
+ @on(Button.Pressed, "#acp-test")
831
+ def _on_test(self, _event: Button.Pressed) -> None:
832
+ if self._testing or self._read_only:
833
+ return
834
+ self._testing = True
835
+ self.query_one("#acp-test", Button).disabled = True
836
+ self._run_test()
837
+
838
+ @work(thread=False)
839
+ async def _run_test(self) -> None:
840
+ dialog = ConnectionTestDialog(
841
+ server_name=self.query_one("#acp-command", Input).value.strip(),
842
+ subject_label=_ACP_AGENT_SUBJECT.bind(),
843
+ )
844
+ self.app.push_screen(dialog)
845
+ client: AcpAgentClient | None = None
846
+ try:
847
+ errors = self.validate()
848
+ if errors:
849
+ dialog.set_result(False, "\n".join(errors))
850
+ return
851
+ config = self.get_config()
852
+ # ignore_cleanup_errors: on Windows the killed agent process can
853
+ # release its stderr.log handle a beat after force_close returns;
854
+ # a leaked test tempdir beats crashing the worker.
855
+ with tempfile.TemporaryDirectory(prefix="chrys-acp-test-", ignore_cleanup_errors=True) as temp_dir:
856
+ # realpath: macOS $TMPDIR lives under the /var -> private/var
857
+ # symlink, and the secure stderr-log open refuses symlinked
858
+ # parent components.
859
+ temp_root = Path(os.path.realpath(temp_dir))
860
+ # Spec construction stays inside the try: a template that no
861
+ # longer resolves must land in the dialog, not leave it
862
+ # spinning on an unhandled worker exception.
863
+ try:
864
+ spec = self._test_spec(config, temp_root / "stderr.log")
865
+ client = AcpAgentClient(spec, _AcpTestCallbacks())
866
+ async with asyncio.timeout(_TEST_TIMEOUT_SECONDS):
867
+ await client.connect()
868
+ handshake = await client.open_session()
869
+ dialog.set_result(
870
+ True,
871
+ _format_handshake(handshake, render_message=self._render_message),
872
+ markdown=True,
873
+ )
874
+ except Exception as exc:
875
+ message = _sanitized_test_error(exc)
876
+ if client is not None and (tail := client.stderr_tail.strip()):
877
+ message += f"\n\n{self._render_message(_STDERR_TAIL_LABEL.bind())}\n{tail[-4096:]}"
878
+ dialog.set_result(False, message, allow_esc=isinstance(exc, TimeoutError))
879
+ finally:
880
+ if client is not None:
881
+ await client.force_close()
882
+ client = None
883
+ finally:
884
+ if client is not None:
885
+ await client.force_close()
886
+ self._testing = False
887
+ with contextlib.suppress(Exception):
888
+ self.query_one("#acp-test", Button).disabled = self._read_only
889
+
890
+ def _test_spec(self, config: AcpAgentConfig, stderr_path: Path) -> AcpAgentSpec:
891
+ expanded, errors = self._expanded_launch_values(config)
892
+ if expanded is None:
893
+ raise ValueError(errors[0])
894
+ cwd_path = Path(expanded["cwd"])
895
+ if not cwd_path.is_absolute():
896
+ cwd_path = Path(self._workspace_cwd) / cwd_path
897
+ cwd = os.path.realpath(cwd_path)
898
+ roots = tuple(dict.fromkeys(os.path.realpath(root) for root in self._workspace_roots))
899
+ return AcpAgentSpec(
900
+ command=expanded["command"],
901
+ args=expanded["args"],
902
+ env=expanded["env"],
903
+ cwd=cwd,
904
+ stderr_log_path=stderr_path,
905
+ additional_directories=tuple(root for root in roots if root != cwd),
906
+ session_mode=config.session_mode,
907
+ model_id=config.model_id,
908
+ config_options=config.config_options,
909
+ best_effort_options=config.best_effort_options,
910
+ handshake_timeout_seconds=config.handshake_timeout_seconds,
911
+ idle_timeout_seconds=config.idle_timeout_seconds,
912
+ )
913
+
914
+
915
+ def _format_handshake(
916
+ handshake: Any,
917
+ *,
918
+ render_message: Callable[[MessageRef], str] = format_message,
919
+ ) -> str:
920
+ """Render the handshake as the Markdown report shown by the test dialog."""
921
+ return _handshake_markdown(
922
+ agent=_handshake_payload(handshake.agent_info),
923
+ capabilities=_handshake_payload(handshake.capabilities),
924
+ modes=_handshake_payload(handshake.modes),
925
+ models=_handshake_payload(handshake.models),
926
+ config_options=_handshake_payload(handshake.config_options),
927
+ auth_methods=_handshake_payload(handshake.auth_methods),
928
+ render_message=render_message,
929
+ )
930
+
931
+
932
+ def _handshake_payload(value: Any) -> Any:
933
+ """Dump an ACP schema object (or tuple of them) to plain wire-shaped data."""
934
+ if value is None:
935
+ return None
936
+ if isinstance(value, list | tuple):
937
+ return [_handshake_payload(item) for item in value]
938
+ # agent-client-protocol 0.10.1 declares ``auth`` as
939
+ # ``AgentAuthCapabilities | None`` but gives it a bare-dict default. An
940
+ # omitted field therefore reaches Pydantic's serializer as the wrong
941
+ # runtime type and emits a warning. An explicitly supplied ``auth={}``
942
+ # is validated into AgentAuthCapabilities, so normalizing only the raw
943
+ # dict preserves that meaningful distinction at the SDK boundary.
944
+ if (
945
+ isinstance(value, acp_schema.AgentCapabilities)
946
+ and "auth" not in value.model_fields_set
947
+ and isinstance(value.auth, dict)
948
+ ):
949
+ value = value.model_copy(update={"auth": None})
950
+ return value.model_dump(by_alias=True, exclude_none=True)
951
+
952
+
953
+ def _handshake_markdown(
954
+ *,
955
+ agent: Any,
956
+ capabilities: Any,
957
+ modes: Any,
958
+ models: Any,
959
+ config_options: Any,
960
+ auth_methods: Any,
961
+ render_message: Callable[[MessageRef], str] = format_message,
962
+ ) -> str:
963
+ sections = [
964
+ _agent_heading(agent, render_message),
965
+ f"### {render_message(_CAPABILITIES_HEADING.bind())}",
966
+ _capabilities_section(capabilities, render_message),
967
+ f"### {render_message(_MODES_HEADING.bind())}",
968
+ _modes_section(modes, render_message),
969
+ f"### {render_message(_MODELS_HEADING.bind())}",
970
+ _models_section(models, render_message),
971
+ f"### {render_message(_CONFIG_OPTIONS_HEADING.bind())}",
972
+ *(_config_options_blocks(config_options, render_message) or [render_message(_NOT_ADVERTISED.bind())]),
973
+ f"### {render_message(_AUTH_METHODS_HEADING.bind())}",
974
+ _auth_methods_section(auth_methods, render_message),
975
+ ]
976
+ return "\n\n".join(sections)
977
+
978
+
979
+ def _agent_heading(agent: Any, render_message: Callable[[MessageRef], str] = format_message) -> str:
980
+ if not isinstance(agent, dict):
981
+ return render_message(_IDENTITY_NOT_REPORTED.bind())
982
+ name = _inline_text(agent.get("name") or "")
983
+ title = _inline_text(agent.get("title") or "") or name or render_message(_ACP_AGENT_FALLBACK_TITLE.bind())
984
+ parts = [f"**{title}**"]
985
+ if name and name != title:
986
+ parts.append(_code_span(name))
987
+ if version := _inline_text(agent.get("version") or ""):
988
+ parts.append(f"v{version}")
989
+ return " · ".join(parts)
990
+
991
+
992
+ def _capability_rows(payload: Any, prefix: str = "") -> list[tuple[str, str]]:
993
+ """Flatten the capability tree into (dotted key, mark) rows.
994
+
995
+ Booleans map to supported/unsupported marks; nested empty objects are
996
+ presence markers (e.g. ``sessionCapabilities.fork``); other scalars are
997
+ shown verbatim. Unknown agents stay renderable because nothing here is
998
+ schema-specific beyond stripping the ``Capabilities`` key suffix.
999
+ """
1000
+ if not isinstance(payload, dict):
1001
+ return []
1002
+ rows: list[tuple[str, str]] = []
1003
+ for key, value in payload.items():
1004
+ if key.startswith("_"):
1005
+ continue
1006
+ label = key.removesuffix("Capabilities") or key
1007
+ path = f"{prefix}.{label}" if prefix else label
1008
+ if isinstance(value, bool):
1009
+ rows.append((path, _SUPPORTED_MARK if value else _UNSUPPORTED_MARK))
1010
+ elif isinstance(value, dict):
1011
+ if value:
1012
+ rows.extend(_capability_rows(value, path))
1013
+ elif prefix:
1014
+ rows.append((path, _SUPPORTED_MARK))
1015
+ elif value is not None:
1016
+ rows.append((path, _code_span(value)))
1017
+ return rows
1018
+
1019
+
1020
+ def _capabilities_section(
1021
+ capabilities: Any,
1022
+ render_message: Callable[[MessageRef], str] = format_message,
1023
+ ) -> str:
1024
+ rows = sorted(_capability_rows(capabilities), key=lambda row: row[0].casefold())
1025
+ if not rows:
1026
+ return render_message(_NOT_ADVERTISED.bind())
1027
+ return _md_table(
1028
+ (render_message(_CAPABILITY_HEADER.bind()), render_message(_SUPPORTED_HEADER.bind())),
1029
+ [(_code_span(path), mark) for path, mark in rows],
1030
+ )
1031
+
1032
+
1033
+ def _current_caption(
1034
+ items: Sequence[dict],
1035
+ current: Any,
1036
+ id_key: str,
1037
+ render_message: Callable[[MessageRef], str] = format_message,
1038
+ ) -> str:
1039
+ """One-line "Current: …" caption naming the active mode/model."""
1040
+ if current is None:
1041
+ return ""
1042
+ name = next((str(item.get("name") or "") for item in items if item.get(id_key) == current), "")
1043
+ label = f"**{_inline_text(name)}** ({_code_span(current)})" if name else _code_span(current)
1044
+ return render_message(_CURRENT_CAPTION.bind(label=label))
1045
+
1046
+
1047
+ def _modes_section(modes: Any, render_message: Callable[[MessageRef], str] = format_message) -> str:
1048
+ if not isinstance(modes, dict):
1049
+ return render_message(_NOT_ADVERTISED.bind())
1050
+ items = [item for item in modes.get("availableModes") or [] if isinstance(item, dict)]
1051
+ if not items:
1052
+ return render_message(_NOT_ADVERTISED.bind())
1053
+ entries = [
1054
+ (
1055
+ str(item.get("name") or item.get("id") or ""),
1056
+ _code_span(item.get("id") or ""),
1057
+ str(item.get("description") or ""),
1058
+ )
1059
+ for item in items
1060
+ ]
1061
+ blocks = []
1062
+ if caption := _current_caption(items, modes.get("currentModeId"), "id", render_message):
1063
+ blocks.append(caption)
1064
+ blocks.append(_definition_bullets(entries))
1065
+ return "\n\n".join(blocks)
1066
+
1067
+
1068
+ def _models_section(models: Any, render_message: Callable[[MessageRef], str] = format_message) -> str:
1069
+ if not isinstance(models, dict):
1070
+ return render_message(_NOT_ADVERTISED.bind())
1071
+ items = [item for item in models.get("availableModels") or [] if isinstance(item, dict)]
1072
+ rows = [
1073
+ (
1074
+ str(item.get("name") or item.get("modelId") or ""),
1075
+ _code_span(item.get("modelId") or ""),
1076
+ str(item.get("description") or ""),
1077
+ )
1078
+ for item in items
1079
+ ]
1080
+ if not rows:
1081
+ return render_message(_NOT_ADVERTISED.bind())
1082
+ blocks = []
1083
+ if caption := _current_caption(items, models.get("currentModelId"), "modelId", render_message):
1084
+ blocks.append(caption)
1085
+ blocks.append(
1086
+ _md_table(
1087
+ (
1088
+ render_message(_MODEL_HEADER.bind()),
1089
+ render_message(_ID_HEADER.bind()),
1090
+ render_message(_DESCRIPTION_HEADER.bind()),
1091
+ ),
1092
+ rows,
1093
+ )
1094
+ )
1095
+ return "\n\n".join(blocks)
1096
+
1097
+
1098
+ def _config_options_blocks(
1099
+ options: Any,
1100
+ render_message: Callable[[MessageRef], str] = format_message,
1101
+ ) -> list[str]:
1102
+ blocks: list[str] = []
1103
+ for option in options or []:
1104
+ if not isinstance(option, dict):
1105
+ continue
1106
+ name = str(option.get("name") or option.get("id") or "")
1107
+ header = [f"**{_inline_text(name)}**", _code_span(option.get("id") or "")]
1108
+ if kind := _inline_text(option.get("type") or ""):
1109
+ header.append(kind)
1110
+ current = option.get("currentValue")
1111
+ if current is not None:
1112
+ header.append(render_message(_CURRENT_VALUE.bind(value=_code_span(_scalar_code(current)))))
1113
+ blocks.append(" · ".join(header))
1114
+ if description := _inline_text(option.get("description") or ""):
1115
+ blocks.append(f"*{description}*")
1116
+ if choices := _select_choice_entries(option.get("options") or [], current):
1117
+ blocks.append(_definition_bullets(choices))
1118
+ return blocks
1119
+
1120
+
1121
+ def _select_choice_entries(items: Any, current: Any, group: str = "") -> list[tuple[str, str, str]]:
1122
+ entries: list[tuple[str, str, str]] = []
1123
+ for item in items or []:
1124
+ if not isinstance(item, dict):
1125
+ continue
1126
+ if "options" in item:
1127
+ # SessionConfigSelectGroup: flatten with a "group / choice" label.
1128
+ entries.extend(_select_choice_entries(item.get("options"), current, group=str(item.get("name") or "")))
1129
+ continue
1130
+ value = item.get("value")
1131
+ name = str(item.get("name") or value or "")
1132
+ if group:
1133
+ name = f"{group} / {name}"
1134
+ entries.append((name, _code_span(_scalar_code(value)), str(item.get("description") or "")))
1135
+ return entries
1136
+
1137
+
1138
+ def _auth_methods_section(
1139
+ auth_methods: Any,
1140
+ render_message: Callable[[MessageRef], str] = format_message,
1141
+ ) -> str:
1142
+ entries: list[tuple[str, str, str]] = []
1143
+ for method in auth_methods or []:
1144
+ if not isinstance(method, dict):
1145
+ continue
1146
+ detail = _inline_text(method.get("description") or "")
1147
+ env_names = [
1148
+ str(var.get("name")) for var in method.get("vars") or [] if isinstance(var, dict) and var.get("name")
1149
+ ]
1150
+ if env_names:
1151
+ joined = ", ".join(_code_span(name) for name in env_names)
1152
+ detail = f"{detail} {render_message(_ENV_LABEL.bind(names=joined))}".strip()
1153
+ entries.append(
1154
+ (
1155
+ str(method.get("name") or method.get("id") or ""),
1156
+ _code_span(method.get("id") or ""),
1157
+ detail,
1158
+ )
1159
+ )
1160
+ if not entries:
1161
+ return render_message(_NOT_ADVERTISED.bind())
1162
+ return _definition_bullets(entries)
1163
+
1164
+
1165
+ def _scalar_code(value: Any) -> str:
1166
+ if isinstance(value, bool):
1167
+ return "true" if value else "false"
1168
+ return str(value)
1169
+
1170
+
1171
+ def _sanitized_test_error(exc: BaseException) -> str:
1172
+ detail = getattr(exc, "detail", "")
1173
+ if not isinstance(detail, str) or not detail:
1174
+ detail = str(exc)
1175
+ line = detail.replace("\r", " ").replace("\n", " ").strip()
1176
+ return (line or type(exc).__name__)[:500]