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,808 @@
1
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
2
+
3
+ """Agent profile loader — load agent profile definitions from YAML files and directories."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ import math
9
+ import ntpath
10
+ import re
11
+ from dataclasses import dataclass
12
+ from pathlib import PurePath, PureWindowsPath
13
+ from typing import TYPE_CHECKING, Any, Literal, cast
14
+
15
+ import yaml
16
+
17
+ from chrys.foundation.branding import APP_DISPLAY_NAME
18
+ from chrys.foundation.text.encoding import decode_bytes
19
+ from chrys.foundation.tool_kinds import strip_legacy_kind_prefix
20
+ from chrys.service.mcp.validation import (
21
+ MCP_PROGRESSIVE_CONTROL_TOOL_NAMES,
22
+ validate_mcp_tool_loading_policy,
23
+ validate_mcp_tool_name_prefix,
24
+ )
25
+ from chrys.service.profiles.agents.schema import (
26
+ DEFAULT_SUB_AGENT_CONCURRENCY,
27
+ AcpAgentConfig,
28
+ AgentProfile,
29
+ ApprovalConfig,
30
+ CompactionConfig,
31
+ MCPServerConfig,
32
+ MemoryConfig,
33
+ ModelConfig,
34
+ ShellFilterConfig,
35
+ SkillConfig,
36
+ SkillResourceConfig,
37
+ SkillsConfig,
38
+ SkillScriptConfig,
39
+ SubAgentRef,
40
+ SubAgentsConfig,
41
+ ToolsConfig,
42
+ )
43
+ from chrys.service.profiles.coercion import coerce_bool
44
+
45
+ if TYPE_CHECKING:
46
+ from pathlib import Path
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+
51
+ class AgentProfileLoadError(Exception):
52
+ """Raised when an agent profile YAML file cannot be loaded or validated."""
53
+
54
+
55
+ _VALID_TRANSPORTS = {"stdio", "http"}
56
+ _VALID_APPROVAL_RULES = frozenset({"auto", "require", "skip"})
57
+ _SKILL_NAME_RE = re.compile(r"^[a-z0-9](?:[a-z0-9]|-(?=[a-z0-9])){0,63}$")
58
+ _MAX_SKILL_DESCRIPTION_LENGTH = 1024
59
+ _ENV_NAME_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
60
+ _ACP_DEPTH_ENV = "CHRYS_ACP_SUBAGENT_DEPTH"
61
+ _ACP_RESULT_MODES = frozenset({"last_segment", "transcript"})
62
+
63
+
64
+ def _mapping_section(raw: object, section: str) -> dict[str, Any]:
65
+ """Return a YAML section mapping or raise a profile load error."""
66
+ if raw is None:
67
+ return {}
68
+ if not isinstance(raw, dict):
69
+ msg = f"Agent profile section '{section}' must be a mapping, got {type(raw).__name__}"
70
+ raise AgentProfileLoadError(msg)
71
+ return cast("dict[str, Any]", raw)
72
+
73
+
74
+ def _list_section(raw: object, section: str) -> list[Any]:
75
+ """Return a YAML section list or raise a profile load error."""
76
+ if raw is None:
77
+ return []
78
+ if not isinstance(raw, list):
79
+ msg = f"Agent profile section '{section}' must be a list, got {type(raw).__name__}"
80
+ raise AgentProfileLoadError(msg)
81
+ return raw
82
+
83
+
84
+ def _mapping_entry(raw: object, section: str) -> dict[str, Any]:
85
+ """Return a list entry mapping or raise a profile load error."""
86
+ if not isinstance(raw, dict):
87
+ msg = f"Agent profile section '{section}' entries must be mappings, got {type(raw).__name__}"
88
+ raise AgentProfileLoadError(msg)
89
+ return cast("dict[str, Any]", raw)
90
+
91
+
92
+ def _string_list_field(raw: object, field: str) -> list[str]:
93
+ """Parse a list-of-strings field without accepting scalar iterables."""
94
+ if raw is None:
95
+ return []
96
+ if not isinstance(raw, list) or not all(isinstance(item, str) for item in raw):
97
+ msg = f"Agent profile field '{field}' must be a list of strings; got {raw!r}"
98
+ raise AgentProfileLoadError(msg)
99
+ return list(cast("list[str]", raw))
100
+
101
+
102
+ def _instructions_field(raw: object, field: str) -> str:
103
+ """Parse instructions text; a list of strings joins one item per line.
104
+
105
+ Any other value would reach the prompt as its Python repr.
106
+ """
107
+ if raw is None:
108
+ return ""
109
+ if isinstance(raw, str):
110
+ return raw
111
+ if not isinstance(raw, list):
112
+ msg = f"Agent profile field '{field}' must be a string or a list of strings; got {type(raw).__name__}"
113
+ raise AgentProfileLoadError(msg)
114
+ for index, item in enumerate(raw, start=1):
115
+ if not isinstance(item, str):
116
+ # YAML reads an unquoted `- Note: text` or `- Heading:` item as a mapping.
117
+ msg = (
118
+ f"Agent profile field '{field}' must be a string or a list of strings; item {index} is a "
119
+ f"{type(item).__name__} (quote list items that contain ': ' or end with ':')"
120
+ )
121
+ raise AgentProfileLoadError(msg)
122
+ return "\n".join(cast("list[str]", raw))
123
+
124
+
125
+ def _approval_rule(raw: object, field: str) -> Literal["auto", "require", "skip"]:
126
+ """Parse an approval rule without YAML truthiness coercion."""
127
+ if not isinstance(raw, str) or raw not in _VALID_APPROVAL_RULES:
128
+ allowed = ", ".join(sorted(_VALID_APPROVAL_RULES))
129
+ msg = f"Agent profile field '{field}' must be one of: {allowed}; got {raw!r}"
130
+ raise AgentProfileLoadError(msg)
131
+ return cast("Literal['auto', 'require', 'skip']", raw)
132
+
133
+
134
+ def _acp_string(raw: object, field: str, *, reject_nul: bool = False) -> str:
135
+ if type(raw) is not str:
136
+ msg = f"Agent profile field 'acp.{field}' must be a string; got {raw!r}"
137
+ raise AgentProfileLoadError(msg)
138
+ if reject_nul and "\0" in raw:
139
+ msg = f"Agent profile field 'acp.{field}' must not contain an embedded NUL"
140
+ raise AgentProfileLoadError(msg)
141
+ return raw
142
+
143
+
144
+ def _acp_timeout(
145
+ raw: object,
146
+ field: str,
147
+ *,
148
+ default: float,
149
+ allow_zero: bool,
150
+ ) -> float:
151
+ if type(raw) not in {int, float}:
152
+ logger.warning("Invalid acp.%s %r; using default %s", field, raw, default)
153
+ return default
154
+ try:
155
+ value = float(cast("int | float", raw))
156
+ except OverflowError:
157
+ # YAML ints are arbitrary-precision; one absurd timeout must not
158
+ # abort loading the whole profile registry.
159
+ logger.warning("Invalid acp.%s %r; using default %s", field, raw, default)
160
+ return default
161
+ if not math.isfinite(value) or value < 0 or (not allow_zero and value == 0):
162
+ logger.warning("Invalid acp.%s %r; using default %s", field, raw, default)
163
+ return default
164
+ return value
165
+
166
+
167
+ def _parse_acp(raw: object) -> AcpAgentConfig:
168
+ """Parse the ACP discriminator with strict per-field type enforcement."""
169
+ raw_map = _mapping_section(raw, "acp")
170
+ defaults = AcpAgentConfig()
171
+
172
+ command = _acp_string(raw_map.get("command", defaults.command), "command", reject_nul=True)
173
+ cwd = _acp_string(raw_map.get("cwd", defaults.cwd), "cwd", reject_nul=True)
174
+ session_mode = _acp_string(raw_map.get("session_mode", defaults.session_mode), "session_mode")
175
+ model_id = _acp_string(raw_map.get("model_id", defaults.model_id), "model_id")
176
+
177
+ raw_args = raw_map.get("args", defaults.args)
178
+ if type(raw_args) is not list:
179
+ msg = f"Agent profile field 'acp.args' must be a list of strings; got {raw_args!r}"
180
+ raise AgentProfileLoadError(msg)
181
+ args = [_acp_string(value, f"args[{index}]", reject_nul=True) for index, value in enumerate(raw_args)]
182
+
183
+ raw_env = raw_map.get("env", defaults.env)
184
+ if type(raw_env) is not dict:
185
+ msg = f"Agent profile field 'acp.env' must be a mapping of strings; got {raw_env!r}"
186
+ raise AgentProfileLoadError(msg)
187
+ env: dict[str, str] = {}
188
+ env_keys: set[str] = set()
189
+ for key, value in raw_env.items():
190
+ if type(key) is not str or _ENV_NAME_RE.fullmatch(key) is None:
191
+ msg = f"Agent profile field 'acp.env' has invalid environment variable name {key!r}"
192
+ raise AgentProfileLoadError(msg)
193
+ folded = key.casefold()
194
+ if folded == _ACP_DEPTH_ENV.casefold():
195
+ msg = f"Agent profile field 'acp.env' reserves {_ACP_DEPTH_ENV!r} for {APP_DISPLAY_NAME}"
196
+ raise AgentProfileLoadError(msg)
197
+ if folded in env_keys:
198
+ msg = f"Agent profile field 'acp.env' has case-insensitive duplicate key {key!r}"
199
+ raise AgentProfileLoadError(msg)
200
+ env_keys.add(folded)
201
+ env[key] = _acp_string(value, f"env.{key}", reject_nul=True)
202
+
203
+ raw_options = raw_map.get("config_options", defaults.config_options)
204
+ if type(raw_options) is not dict:
205
+ msg = f"Agent profile field 'acp.config_options' must be a mapping; got {raw_options!r}"
206
+ raise AgentProfileLoadError(msg)
207
+ config_options: dict[str, str | bool] = {}
208
+ for key, value in raw_options.items():
209
+ if type(key) is not str or not key:
210
+ msg = f"Agent profile field 'acp.config_options' keys must be non-empty strings; got {key!r}"
211
+ raise AgentProfileLoadError(msg)
212
+ if type(value) not in {str, bool}:
213
+ msg = (
214
+ "Agent profile field 'acp.config_options' values must be strings or booleans; "
215
+ f"got {value!r} for {key!r}"
216
+ )
217
+ raise AgentProfileLoadError(msg)
218
+ config_options[key] = value
219
+
220
+ allow_external_cwd = raw_map.get("allow_external_cwd", defaults.allow_external_cwd)
221
+ if type(allow_external_cwd) is not bool:
222
+ msg = f"Agent profile field 'acp.allow_external_cwd' must be a boolean; got {allow_external_cwd!r}"
223
+ raise AgentProfileLoadError(msg)
224
+ best_effort_options = raw_map.get("best_effort_options", defaults.best_effort_options)
225
+ if type(best_effort_options) is not bool:
226
+ msg = f"Agent profile field 'acp.best_effort_options' must be a boolean; got {best_effort_options!r}"
227
+ raise AgentProfileLoadError(msg)
228
+ result_mode = raw_map.get("result_mode", defaults.result_mode)
229
+ if type(result_mode) is not str or result_mode not in _ACP_RESULT_MODES:
230
+ msg = f"Agent profile field 'acp.result_mode' must be one of: last_segment, transcript; got {result_mode!r}"
231
+ raise AgentProfileLoadError(msg)
232
+
233
+ return AcpAgentConfig(
234
+ command=command,
235
+ args=args,
236
+ env=env,
237
+ cwd=cwd,
238
+ allow_external_cwd=allow_external_cwd,
239
+ session_mode=session_mode,
240
+ model_id=model_id,
241
+ config_options=config_options,
242
+ best_effort_options=best_effort_options,
243
+ result_mode=result_mode,
244
+ handshake_timeout_seconds=_acp_timeout(
245
+ raw_map.get("handshake_timeout_seconds", defaults.handshake_timeout_seconds),
246
+ "handshake_timeout_seconds",
247
+ default=defaults.handshake_timeout_seconds,
248
+ allow_zero=False,
249
+ ),
250
+ idle_timeout_seconds=_acp_timeout(
251
+ raw_map.get("idle_timeout_seconds", defaults.idle_timeout_seconds),
252
+ "idle_timeout_seconds",
253
+ default=defaults.idle_timeout_seconds,
254
+ allow_zero=True,
255
+ ),
256
+ )
257
+
258
+
259
+ def _parse_mcp_servers(raw: object) -> list[MCPServerConfig]:
260
+ configs: list[MCPServerConfig] = []
261
+ for raw_entry in _list_section(raw, "tools.mcp"):
262
+ entry = _mapping_entry(raw_entry, "tools.mcp")
263
+ if "name" not in entry:
264
+ msg = "MCP server missing required 'name' field"
265
+ raise AgentProfileLoadError(msg)
266
+ if "transport" not in entry:
267
+ msg = f"MCP server '{entry.get('name', '?')}' missing required 'transport' field"
268
+ raise AgentProfileLoadError(msg)
269
+ transport = entry["transport"]
270
+ if transport not in _VALID_TRANSPORTS:
271
+ msg = f"MCP server '{entry.get('name', '?')}' has invalid transport '{transport}', must be one of {_VALID_TRANSPORTS}"
272
+ raise AgentProfileLoadError(msg)
273
+ allowed_tools = (
274
+ None
275
+ if entry.get("allowed_tools") is None
276
+ else _string_list_field(entry.get("allowed_tools"), "tools.mcp.allowed_tools")
277
+ )
278
+ use_progressive_disclosure = coerce_bool(entry.get("use_progressive_disclosure"), default=False)
279
+ tool_name_prefix = entry.get("tool_name_prefix", "")
280
+ prefix_error = validate_mcp_tool_name_prefix(
281
+ tool_name_prefix,
282
+ generated_suffixes=MCP_PROGRESSIVE_CONTROL_TOOL_NAMES if use_progressive_disclosure else (),
283
+ )
284
+ if prefix_error is not None:
285
+ msg = f"MCP server '{entry.get('name', '?')}' has invalid tool name prefix: {prefix_error}"
286
+ raise AgentProfileLoadError(msg)
287
+ always_load = _string_list_field(entry.get("always_load"), "tools.mcp.always_load")
288
+ policy_errors = validate_mcp_tool_loading_policy(
289
+ allowed_tools=allowed_tools,
290
+ use_progressive_disclosure=use_progressive_disclosure,
291
+ always_load=always_load,
292
+ )
293
+ if policy_errors:
294
+ msg = f"MCP server '{entry.get('name', '?')}' has invalid tool loading policy: {' '.join(policy_errors)}"
295
+ raise AgentProfileLoadError(msg)
296
+ max_tool_result_tokens = entry.get("max_tool_result_tokens")
297
+ if not (
298
+ max_tool_result_tokens is None
299
+ or (type(max_tool_result_tokens) is int and (max_tool_result_tokens == 0 or max_tool_result_tokens >= 100))
300
+ ):
301
+ msg = (
302
+ f"MCP server '{entry.get('name', '?')}' has invalid max_tool_result_tokens; "
303
+ "expected null, 0, or an integer of at least 100"
304
+ )
305
+ raise AgentProfileLoadError(msg)
306
+ configs.append(
307
+ MCPServerConfig(
308
+ name=entry["name"],
309
+ transport=transport,
310
+ # stdio
311
+ command=entry.get("command", ""),
312
+ args=entry.get("args", []),
313
+ env=entry.get("env", {}) if transport == "stdio" else {},
314
+ encoding=entry.get("encoding"),
315
+ # http
316
+ url=entry.get("url", ""),
317
+ headers=entry.get("headers", {}),
318
+ resolve_header_templates=coerce_bool(entry.get("resolve_header_templates"), default=True),
319
+ terminate_on_close=entry.get("terminate_on_close"),
320
+ verify_ssl=coerce_bool(entry.get("verify_ssl"), default=True),
321
+ bypass_proxy=coerce_bool(entry.get("bypass_proxy"), default=False),
322
+ # common
323
+ enabled=entry.get("enabled", entry.get("enable", True)),
324
+ description=entry.get("description", ""),
325
+ tool_name_prefix=tool_name_prefix,
326
+ allowed_tools=allowed_tools,
327
+ use_progressive_disclosure=use_progressive_disclosure,
328
+ always_load=always_load,
329
+ request_timeout=entry.get("timeout", entry.get("request_timeout")),
330
+ max_tool_result_tokens=max_tool_result_tokens,
331
+ load_prompts=entry.get("load_prompts", True),
332
+ expose_instructions=coerce_bool(entry.get("expose_instructions"), default=True),
333
+ )
334
+ )
335
+ return configs
336
+
337
+
338
+ def _parse_skill_resources(raw: object, section: str) -> list[SkillResourceConfig]:
339
+ resources: list[SkillResourceConfig] = []
340
+ for raw_entry in _list_section(raw, section):
341
+ entry = _mapping_entry(raw_entry, section)
342
+ if "name" not in entry:
343
+ msg = f"Skill resource in '{section}' missing required 'name' field"
344
+ raise AgentProfileLoadError(msg)
345
+ resources.append(
346
+ SkillResourceConfig(
347
+ name=entry["name"],
348
+ description=entry.get("description", ""),
349
+ content=entry.get("content", ""),
350
+ path=entry.get("path", ""),
351
+ )
352
+ )
353
+ return resources
354
+
355
+
356
+ def _parse_skill_scripts(raw: object, section: str) -> list[SkillScriptConfig]:
357
+ scripts: list[SkillScriptConfig] = []
358
+ for raw_entry in _list_section(raw, section):
359
+ entry = _mapping_entry(raw_entry, section)
360
+ if "name" not in entry:
361
+ msg = f"Skill script in '{section}' missing required 'name' field"
362
+ raise AgentProfileLoadError(msg)
363
+ scripts.append(
364
+ SkillScriptConfig(
365
+ name=entry["name"],
366
+ description=entry.get("description", ""),
367
+ path=entry.get("path", ""),
368
+ )
369
+ )
370
+ return scripts
371
+
372
+
373
+ def _validate_inline_skill_metadata(entry: dict[str, Any], index: int) -> None:
374
+ """Validate inline profile skill metadata against the Agent Skills specification rules."""
375
+ location = f"skills.inline[{index}]"
376
+ name = entry["name"]
377
+ if not isinstance(name, str) or not name.strip():
378
+ msg = f"{location}.name must be a non-empty string"
379
+ raise AgentProfileLoadError(msg)
380
+ if not _SKILL_NAME_RE.fullmatch(name):
381
+ msg = (
382
+ f"{location}.name '{name}' is not a valid skill name; "
383
+ "use 1-64 lowercase letters, numbers, and single hyphens, with no leading or trailing hyphen."
384
+ )
385
+ raise AgentProfileLoadError(msg)
386
+
387
+ description = entry.get("description", "")
388
+ if not isinstance(description, str):
389
+ msg = f"{location}.description must be a string"
390
+ raise AgentProfileLoadError(msg)
391
+ if not description.strip():
392
+ msg = f"{location}.description is required for inline skills"
393
+ raise AgentProfileLoadError(msg)
394
+ if len(description) > _MAX_SKILL_DESCRIPTION_LENGTH:
395
+ msg = f"{location}.description must be {_MAX_SKILL_DESCRIPTION_LENGTH} characters or fewer"
396
+ raise AgentProfileLoadError(msg)
397
+
398
+
399
+ def _parse_skills(raw: object) -> SkillsConfig:
400
+ if raw is None:
401
+ return SkillsConfig()
402
+ raw_map = _mapping_section(raw, "skills")
403
+ inline: list[SkillConfig] = []
404
+ for index, raw_entry in enumerate(_list_section(raw_map.get("inline", []), "skills.inline"), start=1):
405
+ entry = _mapping_entry(raw_entry, "skills.inline")
406
+ if "name" not in entry:
407
+ msg = "Inline skill missing required 'name' field"
408
+ raise AgentProfileLoadError(msg)
409
+ _validate_inline_skill_metadata(entry, index)
410
+ inline.append(
411
+ SkillConfig(
412
+ name=entry["name"],
413
+ description=entry.get("description", ""),
414
+ instructions=_instructions_field(entry.get("instructions"), f"skills.inline[{index}].instructions"),
415
+ resources=_parse_skill_resources(entry.get("resources", []), "skills.inline.resources"),
416
+ scripts=_parse_skill_scripts(entry.get("scripts", []), "skills.inline.scripts"),
417
+ )
418
+ )
419
+ return SkillsConfig(
420
+ paths=raw_map.get("paths", []),
421
+ inline=inline,
422
+ script_timeout=raw_map.get("script_timeout", 300),
423
+ script_extensions=raw_map.get("script_extensions", [".py", ".sh", ".ps1"]),
424
+ auto_load_user_agents_skills=raw_map.get("auto_load_user_agents_skills", True),
425
+ auto_load_cwd_agents_skills=raw_map.get("auto_load_cwd_agents_skills", True),
426
+ )
427
+
428
+
429
+ def _parse_shell_filter(raw: object) -> ShellFilterConfig | None:
430
+ if raw is None:
431
+ return None
432
+ if isinstance(raw, str):
433
+ # Shorthand: shell_filter: read_only
434
+ return ShellFilterConfig(preset=raw)
435
+ raw_map = _mapping_section(raw, "tools.shell_filter")
436
+ return ShellFilterConfig(
437
+ preset=raw_map.get("preset", ""),
438
+ mode=raw_map.get("mode", "whitelist"),
439
+ commands=raw_map.get("commands", []),
440
+ allow_redirections=raw_map.get("allow_redirections", True),
441
+ allow_subshells=raw_map.get("allow_subshells", True),
442
+ )
443
+
444
+
445
+ def _parse_tools(raw: object) -> ToolsConfig:
446
+ if raw is None:
447
+ return ToolsConfig()
448
+ from chrys.service.tools.builtins.web.config import mapping, parse_fetch, parse_search
449
+
450
+ known_keys = {"builtins", "custom", "mcp", "shell_filter", "web_search", "web_fetch"}
451
+ # YAML comments routinely leave sections behind as null. Older versions also
452
+ # ignored unknown tool keys and categories, so retain the understood subset
453
+ # instead of dropping an otherwise usable user override during an upgrade.
454
+ if isinstance(raw, dict):
455
+ unknown_keys = sorted(key for key in raw if isinstance(key, str) and key not in known_keys)
456
+ if unknown_keys:
457
+ logger.warning("Ignoring unknown agent tool keys: %s", ", ".join(unknown_keys))
458
+ raw = {
459
+ key: value
460
+ for key, value in raw.items()
461
+ if value is not None and (not isinstance(key, str) or key in known_keys)
462
+ }
463
+ try:
464
+ raw_map = mapping(
465
+ raw,
466
+ known_keys,
467
+ "tools",
468
+ nullable=frozenset({"shell_filter"}),
469
+ )
470
+ builtins = raw_map.get("builtins", [])
471
+ if not isinstance(builtins, list):
472
+ raise ValueError("tools.builtins must be a list")
473
+ # A stray "-" loads as null. Unknown names stay: the registry ignores them, and
474
+ # keeping them means a save from this version never deletes a newer version's tool.
475
+ if any(not isinstance(category, str) for category in builtins):
476
+ logger.warning("Ignoring non-string entries in tools.builtins")
477
+ raw_map["builtins"] = [category for category in builtins if isinstance(category, str)]
478
+
479
+ search_raw = raw_map.get("web_search")
480
+ if isinstance(search_raw, dict) and isinstance(search_raw.get("mode"), bool):
481
+ search_raw = {**search_raw, "mode": "auto" if search_raw["mode"] else "off"}
482
+ fetch_raw = raw_map.get("web_fetch")
483
+ if isinstance(fetch_raw, dict) and isinstance(fetch_raw.get("mode"), bool):
484
+ fetch_raw = {**fetch_raw, "mode": "on" if fetch_raw["mode"] else "off"}
485
+ web_search = parse_search(search_raw) if "web_search" in raw_map else None
486
+ web_fetch = parse_fetch(fetch_raw) if "web_fetch" in raw_map else None
487
+ except ValueError as err:
488
+ raise AgentProfileLoadError(str(err)) from err
489
+ return ToolsConfig(
490
+ builtins=raw_map.get("builtins", []),
491
+ custom=raw_map.get("custom", []),
492
+ mcp=_parse_mcp_servers(raw_map.get("mcp", [])),
493
+ shell_filter=_parse_shell_filter(raw_map.get("shell_filter")),
494
+ web_search=web_search,
495
+ web_fetch=web_fetch,
496
+ )
497
+
498
+
499
+ def _parse_approval(raw: object) -> ApprovalConfig:
500
+ if raw is None:
501
+ return ApprovalConfig()
502
+ raw_map = _mapping_section(raw, "approval")
503
+ defaults = ApprovalConfig()
504
+ raw_overrides = raw_map.get("overrides")
505
+ if raw_overrides is None:
506
+ overrides = dict(defaults.overrides)
507
+ else:
508
+ if not isinstance(raw_overrides, dict):
509
+ msg = f"Agent profile section 'approval.overrides' must be a mapping, got {type(raw_overrides).__name__}"
510
+ raise AgentProfileLoadError(msg)
511
+ overrides = {}
512
+ for key, value in raw_overrides.items():
513
+ if not isinstance(key, str) or not key:
514
+ msg = f"Agent profile section 'approval.overrides' keys must be non-empty strings, got {key!r}"
515
+ raise AgentProfileLoadError(msg)
516
+ # Keys are bare kinds, ``kind.tool_name`` compounds, or bare tool
517
+ # names; legacy pre-P1 keys carried a ``chrys.`` prefix.
518
+ normalized_key = strip_legacy_kind_prefix(key, allow_tool_suffix=True, source="approval.overrides")
519
+ overrides[normalized_key] = _approval_rule(value, f"approval.overrides.{normalized_key}")
520
+ return ApprovalConfig(
521
+ default=_approval_rule(raw_map.get("default", defaults.default), "approval.default"),
522
+ overrides=overrides,
523
+ user_can_override=raw_map.get("user_can_override", defaults.user_can_override),
524
+ )
525
+
526
+
527
+ def _parse_model(raw: object, source: str = "") -> ModelConfig:
528
+ if raw is None:
529
+ return ModelConfig()
530
+ raw_map = _mapping_section(raw, "model")
531
+ profile_id = (raw_map.get("profile_id") or "").strip()
532
+ # Legacy key ``profile`` (previously stored a model-profile *name*) is
533
+ # ignored — the new schema is id-only. Emit a one-line warning so
534
+ # users know to re-select their model profile via the TUI.
535
+ if not profile_id and raw_map.get("profile"):
536
+ logger.warning(
537
+ "Ignoring legacy `model.profile` field in %s — replace with `model.profile_id: <id>` "
538
+ "or re-select the model profile in the agent configuration screen.",
539
+ source or "agent profile YAML",
540
+ )
541
+ return ModelConfig(profile_id=profile_id)
542
+
543
+
544
+ def _parse_compaction(raw: object) -> CompactionConfig:
545
+ if raw is None:
546
+ return CompactionConfig()
547
+ raw_map = _mapping_section(raw, "compaction")
548
+ defaults = CompactionConfig()
549
+ legacy_keys = {"reserved_context_pct", "compaction_target_pct", "force_compress_pct"}
550
+ reserved = raw_map.get("reserved_context_pct")
551
+ legacy_disabled = isinstance(reserved, int | float) and not isinstance(reserved, bool) and reserved >= 1.0
552
+ if legacy_disabled:
553
+ logger.warning(
554
+ "Compaction thresholds are now derived from the model profile; this profile stays disabled "
555
+ "via the legacy reserved_context_pct >= 1.0 switch for this load"
556
+ )
557
+ ignored_legacy = (legacy_keys & raw_map.keys()) - ({"reserved_context_pct"} if legacy_disabled else set())
558
+ if ignored_legacy:
559
+ logger.info(
560
+ "Ignoring obsolete compaction threshold field(s): %s; thresholds are derived from the model profile",
561
+ ", ".join(sorted(ignored_legacy)),
562
+ )
563
+ last_words_template = raw_map.get("last_words_template", defaults.last_words_template)
564
+ if not isinstance(last_words_template, str):
565
+ msg = f"Agent profile field 'compaction.last_words_template' must be a string; got {last_words_template!r}"
566
+ raise AgentProfileLoadError(msg)
567
+ phase4_side_call_token_budget = raw_map.get(
568
+ "phase4_side_call_token_budget",
569
+ defaults.phase4_side_call_token_budget,
570
+ )
571
+ if phase4_side_call_token_budget is None:
572
+ # Explicit ``null`` keeps its pre--1 meaning: use the default.
573
+ phase4_side_call_token_budget = defaults.phase4_side_call_token_budget
574
+ if (
575
+ not isinstance(phase4_side_call_token_budget, int)
576
+ or isinstance(phase4_side_call_token_budget, bool)
577
+ or phase4_side_call_token_budget < -1
578
+ ):
579
+ msg = (
580
+ "Agent profile field 'compaction.phase4_side_call_token_budget' must be "
581
+ f"an integer >= -1 (-1 means unlimited) or null; got {phase4_side_call_token_budget!r}"
582
+ )
583
+ raise AgentProfileLoadError(msg)
584
+ return CompactionConfig(
585
+ enabled=not legacy_disabled,
586
+ last_words_template=last_words_template,
587
+ last_words_max_output_tokens=raw_map.get(
588
+ "last_words_max_output_tokens",
589
+ defaults.last_words_max_output_tokens,
590
+ ),
591
+ phase4_side_call_token_budget=phase4_side_call_token_budget,
592
+ )
593
+
594
+
595
+ def _parse_memory(raw: object) -> MemoryConfig:
596
+ if raw is None:
597
+ return MemoryConfig()
598
+ raw_map = _mapping_section(raw, "memory")
599
+ return MemoryConfig(
600
+ files=list(raw_map.get("files", [])),
601
+ folders=list(raw_map.get("folders", [])),
602
+ )
603
+
604
+
605
+ def _parse_sub_agents(raw: object) -> SubAgentsConfig:
606
+ if raw is None:
607
+ return SubAgentsConfig()
608
+ raw_map = _mapping_section(raw, "sub_agents")
609
+ agents = [
610
+ SubAgentRef(
611
+ profile=entry["profile"],
612
+ tool_name=entry.get("tool_name", ""),
613
+ tool_description=entry.get("tool_description", ""),
614
+ max_concurrency=entry.get("max_concurrency", DEFAULT_SUB_AGENT_CONCURRENCY),
615
+ )
616
+ for entry in (
617
+ _mapping_entry(raw_entry, "sub_agents.agents")
618
+ for raw_entry in _list_section(raw_map.get("agents", []), "sub_agents.agents")
619
+ )
620
+ if "profile" in entry
621
+ ]
622
+ return SubAgentsConfig(
623
+ max_total_concurrency=raw_map.get("max_total_concurrency", DEFAULT_SUB_AGENT_CONCURRENCY),
624
+ agents=agents,
625
+ )
626
+
627
+
628
+ def is_filename_safe_profile_name(name: str) -> bool:
629
+ """Return True when ``name`` can serve as a bare ``<name>.yaml`` stem.
630
+
631
+ Every persistence path resolves a user profile as ``<name>.yaml`` under
632
+ the profiles directory, so a name carrying separators, drive or root
633
+ prefixes, ``.``/``..``, or NUL would escape that directory. The
634
+ resulting filename must also be legal on every platform a config
635
+ directory can be shared with: Windows device names (``CON``, ``NUL``,
636
+ ``COM1`` …), NTFS stream syntax (``foo:bar``), wildcard and control
637
+ characters are rejected regardless of the current OS. The ACP write
638
+ path uses the same predicate.
639
+ """
640
+ if not name or name != name.strip() or name in {".", ".."} or "\0" in name:
641
+ return False
642
+ posix_path = PurePath(name)
643
+ windows_path = PureWindowsPath(name)
644
+ if (
645
+ posix_path.is_absolute()
646
+ or windows_path.is_absolute()
647
+ or windows_path.drive
648
+ or posix_path.name != name
649
+ or windows_path.name != name
650
+ ):
651
+ return False
652
+ return not ntpath.isreserved(f"{name}.yaml")
653
+
654
+
655
+ def load_profile_from_yaml(path: Path) -> AgentProfile:
656
+ """Load an agent profile from a YAML file.
657
+
658
+ Raises:
659
+ AgentProfileLoadError: If the file cannot be read or is missing required fields.
660
+ """
661
+ try:
662
+ text = decode_bytes(path.read_bytes())
663
+ except FileNotFoundError as e:
664
+ raise AgentProfileLoadError(f"Agent profile file not found: {path}") from e
665
+ except OSError as e:
666
+ raise AgentProfileLoadError(f"Cannot read agent profile file {path}: {e}") from e
667
+
668
+ try:
669
+ data = yaml.safe_load(text)
670
+ except (yaml.YAMLError, AttributeError, LookupError, TypeError, ValueError) as e:
671
+ # PyYAML scalar constructors can raise built-in errors for invalid values.
672
+ raise AgentProfileLoadError(f"Invalid YAML in {path}: {e}") from e
673
+
674
+ if not isinstance(data, dict):
675
+ raise AgentProfileLoadError(f"Agent profile YAML must be a mapping, got {type(data).__name__} in {path}")
676
+
677
+ if "name" not in data:
678
+ raise AgentProfileLoadError(f"Agent profile YAML missing required 'name' field in {path}")
679
+ raw_name = data["name"]
680
+ if not isinstance(raw_name, str) or not raw_name.strip():
681
+ raise AgentProfileLoadError(f"Agent profile 'name' must be a non-empty string in {path}")
682
+ name = raw_name.strip()
683
+ if not is_filename_safe_profile_name(name):
684
+ raise AgentProfileLoadError(
685
+ f"Agent profile name {name!r} in {path} is not filename-safe; use a bare name, not a path"
686
+ )
687
+
688
+ if "hooks" in data:
689
+ logger.warning(
690
+ "Ignoring legacy `hooks` section in %s. Configure lifecycle hooks in the global hooks config instead.",
691
+ path,
692
+ )
693
+
694
+ raw_id = data.get("id")
695
+ if raw_id is not None and not isinstance(raw_id, str):
696
+ raise AgentProfileLoadError(f"Agent profile 'id' must be a string or null in {path}")
697
+ profile_id = (raw_id or "").strip()
698
+ acp_present = "acp" in data
699
+
700
+ try:
701
+ profile = AgentProfile(
702
+ name=name,
703
+ id=profile_id,
704
+ display_name=data.get("display_name", ""),
705
+ description=data.get("description", ""),
706
+ sub_agent_only=bool(data.get("sub_agent_only", False)),
707
+ acp=_parse_acp(data.get("acp")) if acp_present else None,
708
+ instructions=_instructions_field(data.get("instructions"), "instructions"),
709
+ tools=_parse_tools(data.get("tools")),
710
+ skills=_parse_skills(data.get("skills")),
711
+ approval=_parse_approval(data.get("approval")),
712
+ sub_agents=_parse_sub_agents(data.get("sub_agents")),
713
+ model=_parse_model(data.get("model"), source=str(path)),
714
+ compaction=_parse_compaction(data.get("compaction")),
715
+ memory=_parse_memory(data.get("memory")),
716
+ metadata=data.get("metadata", {}),
717
+ )
718
+ if not acp_present:
719
+ return profile
720
+ if not profile.sub_agent_only:
721
+ logger.warning("ACP profile %s must be sub-agent-only; forcing sub_agent_only=true", profile.name)
722
+ profile.sub_agent_only = True
723
+
724
+ conflicting: list[str] = []
725
+ if profile.tools != ToolsConfig():
726
+ conflicting.append("tools")
727
+ profile.tools = ToolsConfig()
728
+ if profile.skills != SkillsConfig():
729
+ conflicting.append("skills")
730
+ profile.skills = SkillsConfig()
731
+ if profile.sub_agents.agents:
732
+ conflicting.append("sub_agents.agents")
733
+ profile.sub_agents = SubAgentsConfig()
734
+ if profile.memory != MemoryConfig():
735
+ conflicting.append("memory")
736
+ profile.memory = MemoryConfig()
737
+ if profile.compaction != CompactionConfig():
738
+ conflicting.append("compaction")
739
+ profile.compaction = CompactionConfig()
740
+ if profile.instructions:
741
+ conflicting.append("instructions")
742
+ profile.instructions = ""
743
+ if profile.approval != ApprovalConfig():
744
+ conflicting.append("approval")
745
+ profile.approval = ApprovalConfig()
746
+ if profile.model.profile_id:
747
+ conflicting.append("model.profile_id")
748
+ profile.model = ModelConfig()
749
+ for section in conflicting:
750
+ logger.warning("ACP profile %s ignores conflicting section %s; reset to defaults", profile.name, section)
751
+ return profile
752
+ except AgentProfileLoadError:
753
+ raise
754
+ except (AttributeError, KeyError, TypeError, ValueError) as e:
755
+ raise AgentProfileLoadError(f"Invalid agent profile structure in {path}: {e}") from e
756
+
757
+
758
+ @dataclass(frozen=True)
759
+ class ProfileDirScan:
760
+ """The profiles parsed from one directory, with their source paths, and the files skipped as invalid."""
761
+
762
+ loaded: list[tuple[Path, AgentProfile]]
763
+ invalid: list[Path]
764
+
765
+
766
+ def scan_profile_dir(directory: Path) -> ProfileDirScan:
767
+ """Load all agent profile YAML files from a directory, recording the ones that fail to parse.
768
+
769
+ Non-YAML files are ignored, and so are dotfiles even when their suffix is
770
+ YAML. Files that fail to parse are skipped with a warning and listed in
771
+ ``invalid``. ``*.yaml`` files come before ``*.yml`` files, each group in
772
+ sorted order.
773
+ """
774
+ loaded: list[tuple[Path, AgentProfile]] = []
775
+ invalid: list[Path] = []
776
+ if not directory.is_dir():
777
+ return ProfileDirScan(loaded, invalid)
778
+ for pattern in ("*.yaml", "*.yml"):
779
+ for path in sorted(directory.glob(pattern)):
780
+ if path.name.startswith("."):
781
+ continue
782
+ try:
783
+ loaded.append((path, load_profile_from_yaml(path)))
784
+ except AgentProfileLoadError as exc:
785
+ # Skip invalid files — caller can use load_profile_from_yaml for strict loading.
786
+ # A dropped user file silently falls back to a builtin of the same name, so
787
+ # this must be loud enough to be noticed in a support log.
788
+ logger.error(
789
+ "Skipping invalid agent profile %s: %s (any builtin with this name stays active)", path, exc
790
+ )
791
+ invalid.append(path)
792
+ return ProfileDirScan(loaded, invalid)
793
+
794
+
795
+ def load_profile_files_from_dir(directory: Path) -> list[tuple[Path, AgentProfile]]:
796
+ """Load all agent profile YAML files from a directory, keeping their source paths.
797
+
798
+ See :func:`scan_profile_dir` for the scan rules.
799
+ """
800
+ return scan_profile_dir(directory).loaded
801
+
802
+
803
+ def load_profiles_from_dir(directory: Path) -> list[AgentProfile]:
804
+ """Load all agent profile YAML files from a directory.
805
+
806
+ See :func:`scan_profile_dir` for the scan rules.
807
+ """
808
+ return [profile for _path, profile in load_profile_files_from_dir(directory)]