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,756 @@
1
+ # Demo workflow: a runnable example of every workflow feature, meant to be copied and adapted.
2
+
3
+ # /// script
4
+ # requires-python = ">=3.9"
5
+ # ///
6
+
7
+ r"""Workflow Demo · Project Tour: every workflow feature in one read-only, runnable example.
8
+
9
+ WHAT IT DOES
10
+ It writes a guided tour of the project in the current workspace for someone new to it. Python
11
+ nodes take a bounded inventory of the tree, agent nodes read the code, and you review the draft
12
+ before it becomes the output. Copy this file to `.chrys/workflows/` and edit it to start your own.
13
+
14
+ HOW TO RUN IT
15
+ In the TUI, open Workflows, pick this one and press Start. The input is optional free text naming
16
+ what you want to learn ("how does authentication work?"). The run asks you two things: how deep to
17
+ go, and whether the draft is good.
18
+
19
+ `icode workflow run` is headless: nobody can answer there, so `ctx.ask` fails the node. This demo
20
+ therefore defines its own input lines that settle the questions up front:
21
+
22
+ icode workflow run demo-workflow --input $'interactive: false\ndepth: deep\nhow are errors handled?'
23
+
24
+ interactive: false never ask; the tour is labelled "not reviewed by a human"
25
+ depth: quick | deep skip the depth question (unattended runs default to quick)
26
+
27
+ `$'...'` is bash and zsh quoting. In PowerShell, write each line break as `n inside double quotes:
28
+
29
+ icode workflow run demo-workflow --input "interactive: false`ndepth: deep`nhow are errors handled?"
30
+
31
+ These lines are a convention of this file (see `read_request`), not a workflow feature. The file
32
+ deliberately does not catch the failure of `ctx.ask` to fall back on a default answer: that would
33
+ turn every real fault into "carry on as if the user had agreed".
34
+
35
+ READ-ONLY SCOPE
36
+ Nothing in the project is modified. The Python nodes only list directories and read the head of a
37
+ README; they never import or run project code, run tests, install anything or write a file. The
38
+ walk skips VCS, dependency, cache and virtual-environment directories, never enters a symlinked
39
+ directory, and is bounded in depth and entry count. iCode itself still records the run (session,
40
+ transcripts, outputs) as it does for every workflow.
41
+
42
+ Every agent uses the builtin QA profile, which is read-only by its instructions and has no
43
+ file-writing tools, and each node repeats the rule in `instructions_suffix`. Be precise about what
44
+ that guarantees: QA still carries the `shell` tool (behind an approval the user cannot switch off),
45
+ so the read-only promise rests on instructions and approval, not on the absence of a tool. A
46
+ workflow that needs a tool-level guarantee has to name a profile without `shell`.
47
+
48
+ THE GRAPH
49
+ read_request -> scan_tree -> read_key_files --[must_ask_depth]--> choose_depth (ask) --+
50
+ | v
51
+ +-----------[depth_is_known]--------------------> plan
52
+
53
+ plan --switch--+-- wants_deep --> fan_out --+--> architecture --+
54
+ | +--> entry_points --+--join--> merge_findings --+
55
+ | +--> conventions ---+ |
56
+ +-- default ----> hand_over ----> quick_scan --------------------------------+
57
+ plan ---------------- side edge: carries the run state past the agents --------------------+
58
+ |
59
+ join(combine=carry_state)
60
+ v
61
+ refine (loop, at most MAX_ROUNDS): open_round --> brief --> write_tour --+
62
+ | +--join(combine=keep_round_state)
63
+ +---------- side edge -----------+ |
64
+ v
65
+ review (ask)
66
+ |
67
+ render_tour (output) <---------------------------------------+
68
+ next_steps (output) <---- tour_text <--------------[wants_deep]---------------+
69
+
70
+ WHERE EACH FEATURE IS USED
71
+ WorkflowBuilder, build(), Workflow ...... bottom of the file; the module-level `workflow` is what iCode loads
72
+ wf.python, sync fn(value) ............... read_request, read_key_files, merge_findings, brief, tour_text,
73
+ render_tour
74
+ wf.python, async fn(value) ............... scan_tree
75
+ wf.python, sync fn(value, ctx) .......... plan, fan_out, hand_over, open_round
76
+ wf.python, async fn(value, ctx) .......... choose_depth, review
77
+ returning a WorkflowValue(text, data) .... read_request, plan, open_round, review
78
+ returning a plain str ................... fan_out, hand_over, merge_findings, brief, tour_text, render_tour
79
+ ctx.emit ................................ plan, fan_out, hand_over, choose_depth, open_round, review
80
+ await ctx.ask(Question), Answer ......... choose_depth (one choice between two Options)
81
+ await ctx.ask(str) ...................... review (free text)
82
+ wf.agent, instructions_suffix ........... all six agents: the three readers, quick_scan, write_tour, next_steps
83
+ timeout= ................................ conventions (agent), choose_depth and review (None)
84
+ Retry on a Python node .................. read_key_files
85
+ Retry on an agent node .................. write_tour
86
+ model= (commented example) .............. architecture
87
+ wf.chain ................................ read_request -> scan_tree -> read_key_files, and inside the loop
88
+ wf.edge, plain .......................... choose_depth -> plan, the fan-out, refine -> render_tour
89
+ wf.edge(when=...) ....................... the bypass around choose_depth; refine -> tour_text
90
+ wf.switch(cases, default) ............... plan -> fan_out | hand_over
91
+ wf.join, default combine ................ the three readers into merge_findings
92
+ wf.join(combine=...), SourceValue ....... carry_state (into the loop), keep_round_state (inside it)
93
+ wf.loop, BuilderScope, NodeHandle ....... refine / refine_round
94
+ until=, max_iterations=, on_exhausted= .. refine
95
+ wf.start, wf.output (more than one) ..... bottom of the file
96
+ script metadata block ................... top of the file (`requires-python`)
97
+
98
+ RULES WORTH REMEMBERING
99
+ * A node body is `fn(value)` or `fn(value, ctx)`, sync or async, and returns a WorkflowValue or a
100
+ str. A sync body runs on a worker thread, so blocking calls are fine in it. An async body runs on
101
+ the worker's event loop, so it must not block (see `scan_tree`), and only an async body can
102
+ `await ctx.ask`.
103
+ * Edge predicates, switch cases, a loop's `until` and a join's `combine` must be plain sync
104
+ functions of one argument. There is no async form of any of them; `build()` rejects one.
105
+ * `value.text` reaches everyone. `value.data` (JSON only) reaches Python nodes and predicates, and
106
+ is lost in two places: an agent reads and writes text only, and the default join concatenates
107
+ text only. State that must outlive either travels on a side edge into a `join(combine=...)`,
108
+ which is what `carry_state` and `keep_round_state` do.
109
+ * Handing `data` to an agent is not an error, but the run raises a notice about it, because it is
110
+ usually a mistake. So the node in front of an agent returns a plain str, the prompt and nothing
111
+ else, and the state leaves on the side edge from the node before that one. This file never
112
+ triggers the notice.
113
+ * A node runs once all its in-edges are settled. Sources that were skipped, or whose condition was
114
+ false, are simply absent. With one source present the value passes through unchanged, data
115
+ included; with none present the node is skipped, and so is everything that depends only on it.
116
+ * A timeout is wall-clock per attempt and includes the time spent waiting for a person, so a node
117
+ that asks needs `timeout=None`. Python nodes default to 300 seconds, agents to no deadline.
118
+ * An agent node retries up to 3 attempts by default, a Python node runs once. A retry runs the
119
+ whole body again, so give `Retry` only to a body that is safe to repeat.
120
+
121
+ ENVIRONMENT
122
+ The block at the top of this file is standard inline script metadata. `requires-python` is checked
123
+ against the interpreter that runs the file. iCode does not install `dependencies`; a workflow that
124
+ needs third-party packages points `[tool.chrys] python` at an interpreter or virtual environment
125
+ of its own, relative to the workflow file. A workspace copy could start like this (indented here
126
+ so that it stays an example):
127
+
128
+ # /// script
129
+ # requires-python = ">=3.11"
130
+ # dependencies = ["pyyaml"]
131
+ #
132
+ # [tool.chrys]
133
+ # python = "../../.venv"
134
+ # ///
135
+ """
136
+
137
+ import asyncio
138
+ import os
139
+ from typing import Any
140
+
141
+ from chrys.workflows import (
142
+ BuilderScope,
143
+ NodeContext,
144
+ NodeHandle,
145
+ Option,
146
+ Question,
147
+ Retry,
148
+ SourceValue,
149
+ Workflow,
150
+ WorkflowBuilder,
151
+ WorkflowValue,
152
+ )
153
+
154
+ wf = WorkflowBuilder(
155
+ "Workflow Demo · Project Tour",
156
+ description="A read-only guided tour of this project: quick or deep, reviewed by you for up to 2 rounds",
157
+ )
158
+
159
+ # ---------------------------------------------------------------------------------------------------
160
+ # Constants
161
+ # ---------------------------------------------------------------------------------------------------
162
+
163
+ QUICK = "quick"
164
+ DEEP = "deep"
165
+ MAX_ROUNDS = 2 # The draft is written once and revised at most once.
166
+
167
+ # Node names that code refers to by id: a custom combine finds its sources by `SourceValue.node_id`.
168
+ PLAN_NODE = "plan"
169
+ ROUND_NODE = "open_round"
170
+ WRITER_NODE = "write_tour"
171
+
172
+ # Bounds of the inventory. Depth and entry count bound the walk whatever the file system looks like.
173
+ MAX_DEPTH = 3
174
+ MAX_ENTRIES = 250
175
+ README_BYTES = 6000
176
+ README_LINES = 60
177
+ SKIPPED_DIRECTORIES = frozenset(
178
+ {
179
+ ".git", ".hg", ".svn",
180
+ "node_modules", "bower_components", "vendor", "site-packages",
181
+ ".venv", "venv", "env",
182
+ "__pycache__", ".tox", ".nox", ".cache", ".mypy_cache", ".pytest_cache", ".ruff_cache", ".gradle",
183
+ ".idea", ".vscode",
184
+ "build", "dist", "target",
185
+ }
186
+ ) # fmt: skip
187
+ README_NAMES = ("readme.md", "readme.rst", "readme.txt", "readme")
188
+ PROJECT_FILES = (
189
+ "pyproject.toml", "setup.py", "requirements.txt", "package.json", "tsconfig.json", "Cargo.toml", "go.mod",
190
+ "pom.xml", "build.gradle", "CMakeLists.txt", "Makefile", "Dockerfile",
191
+ ) # fmt: skip
192
+
193
+ TRUE_WORDS = ("true", "yes", "on", "1")
194
+ FALSE_WORDS = ("false", "no", "off", "0")
195
+ ACCEPT_WORDS = ("ok", "okay", "yes", "y", "accept", "accepted", "approve", "approved", "lgtm", "looks good")
196
+
197
+ DEFAULT_FOCUS = "a general orientation for someone who is new to this project"
198
+
199
+ # The state every Python node and predicate can rely on. It rides on `WorkflowValue.data`, so it has to
200
+ # be plain JSON: dicts with string keys, lists, strings, numbers, booleans and None.
201
+ DEFAULT_STATE = {
202
+ "focus": DEFAULT_FOCUS,
203
+ "interactive": True, # False: an unattended run that must never call ctx.ask
204
+ "depth": None, # QUICK, DEEP, or None while it is still undecided
205
+ "round": 0, # refine rounds started so far
206
+ "reviewed": False, # a person looked at the draft
207
+ "accepted": False, # that person accepted it
208
+ "feedback": "", # what they asked to change, when they did not
209
+ }
210
+
211
+ READ_ONLY_RULES = (
212
+ " This is a read-only tour: only read and search the workspace. Never create, edit, move or delete"
213
+ " anything, never run a command that changes something, and do not run tests, builds or installers."
214
+ " Cite files as path:line."
215
+ )
216
+
217
+ READER_TASK = (
218
+ "Help a newcomer understand the project in the current workspace.\n"
219
+ "What they want to learn: {focus}\n\n"
220
+ "A bounded inventory of the project, to get you started (read further yourself):\n\n{inventory}"
221
+ )
222
+ FAN_OUT_NOTE = (
223
+ "\n\nYou are one of three readers working in parallel. Cover only the dimension named in your"
224
+ " instructions; the other two are covered by someone else."
225
+ )
226
+ WRITE_TASK = (
227
+ "Write a guided tour of this project for a newcomer.\n"
228
+ "What they want to learn: {focus}\n\n"
229
+ "Notes from reading the project:\n\n{findings}"
230
+ )
231
+ REVISE_TASK = (
232
+ "Revise the project tour below. The reader reviewed it and asked for this change:\n\n{feedback}\n\n"
233
+ "Return the complete revised tour, not a list of edits.\n\n--- current tour ---\n{draft}"
234
+ )
235
+ # A question is shown as Markdown in a dialog that stays open until it is answered, so the person
236
+ # cannot look anything up in the run meanwhile: put what they need to decide into the question and
237
+ # its option descriptions.
238
+ DEPTH_QUESTION = Question(
239
+ "How deep should the tour go?",
240
+ options=[
241
+ Option(QUICK, "One reader skims the project (the default)"),
242
+ Option(
243
+ DEEP,
244
+ "Three readers study architecture, entry points and conventions in parallel, and the run adds"
245
+ " a list of next steps",
246
+ ),
247
+ ],
248
+ )
249
+ REVIEW_QUESTION = (
250
+ "{draft}\n\n---\n\n"
251
+ "**Review, round {round} of {rounds}.** Reply `ok` to accept this tour, or describe what to change."
252
+ )
253
+
254
+
255
+ # ---------------------------------------------------------------------------------------------------
256
+ # Helpers (plain functions; only the functions handed to the builder below become nodes)
257
+ # ---------------------------------------------------------------------------------------------------
258
+
259
+
260
+ def _state(value: WorkflowValue) -> dict[str, Any]:
261
+ """The run state a value carries on its data channel, with defaults for whatever is missing."""
262
+ state: dict[str, Any] = dict(DEFAULT_STATE)
263
+ if isinstance(value.data, dict):
264
+ state.update(value.data)
265
+ return state
266
+
267
+
268
+ def _list_directory(directory: str, depth: int, lines: list[str], counts: dict[str, int]) -> None:
269
+ """Append an indented listing of *directory* to *lines*; stops at MAX_DEPTH and MAX_ENTRIES."""
270
+ try:
271
+ with os.scandir(directory) as scanner:
272
+ entries = sorted(scanner, key=lambda entry: entry.name)
273
+ except OSError:
274
+ return # An unreadable directory is left out; it is no reason to fail the tour.
275
+ for entry in entries:
276
+ if len(lines) >= MAX_ENTRIES:
277
+ return
278
+ indent = " " * depth
279
+ # follow_symlinks=False: a link to a directory is listed like a file and never entered.
280
+ if entry.is_dir(follow_symlinks=False):
281
+ is_virtualenv = os.path.isfile(os.path.join(entry.path, "pyvenv.cfg"))
282
+ if entry.name in SKIPPED_DIRECTORIES or is_virtualenv:
283
+ continue
284
+ lines.append(f"{indent}{entry.name}/")
285
+ if depth + 1 < MAX_DEPTH:
286
+ _list_directory(entry.path, depth + 1, lines, counts)
287
+ else:
288
+ lines.append(f"{indent}{entry.name}")
289
+ suffix = os.path.splitext(entry.name)[1].lower() or "(no extension)"
290
+ counts[suffix] = counts.get(suffix, 0) + 1
291
+
292
+
293
+ def _inventory(root: str) -> str:
294
+ """The tree listing and a file-type tally of *root*, as text."""
295
+ lines: list[str] = []
296
+ counts: dict[str, int] = {}
297
+ _list_directory(root, 0, lines, counts)
298
+ most_common = sorted(counts.items(), key=lambda item: (-item[1], item[0]))[:8]
299
+ tally = ", ".join(f"{suffix} {count}" for suffix, count in most_common) or "no files"
300
+ limit = f"; stopped at {MAX_ENTRIES} entries" if len(lines) >= MAX_ENTRIES else ""
301
+ return (
302
+ f"Project: {os.path.basename(root) or root}\n"
303
+ f"File types: {tally}\n\n"
304
+ f"Tree ({MAX_DEPTH} levels; VCS, dependency, cache and virtual-environment directories skipped{limit}):\n"
305
+ + "\n".join(lines)
306
+ )
307
+
308
+
309
+ # ---------------------------------------------------------------------------------------------------
310
+ # Python node bodies, in graph order
311
+ # ---------------------------------------------------------------------------------------------------
312
+
313
+
314
+ def read_request(value: WorkflowValue) -> WorkflowValue:
315
+ """Start node. SYNC `fn(value)`, returning a WorkflowValue with `data`.
316
+
317
+ The run's input text arrives as `value.text` (its `data` is None). This body separates the two
318
+ option lines described in the module docstring from the free text, and from here on the options
319
+ travel as structured state on `data` while `text` stays readable prose.
320
+ """
321
+ lines = value.text.splitlines()
322
+ if len(lines) == 1 and "\\n" in lines[0]:
323
+ # The bash command pasted into PowerShell arrives as one line with literal `\n`s, after the `$` of
324
+ # `$'...'`. Read as written, its options would be ignored without a word.
325
+ first_key = lines[0].partition(":")[0].strip().lstrip("$").strip().lower()
326
+ if first_key in ("interactive", "depth"):
327
+ raise ValueError(
328
+ "The input is one line with a literal \\n in it: $'...' is bash quoting, and PowerShell passes "
329
+ "it on as written. In PowerShell, write each line break as `n inside double quotes: --input "
330
+ '"interactive: false`ndepth: deep`nhow are errors handled?"'
331
+ )
332
+ options: dict[str, str] = {}
333
+ focus_lines: list[str] = []
334
+ for line in lines:
335
+ key, separator, raw = line.partition(":")
336
+ name = key.strip().lower()
337
+ if separator and name in ("interactive", "depth"):
338
+ options[name] = raw.strip().lower()
339
+ else:
340
+ focus_lines.append(line)
341
+
342
+ # A value this demo cannot interpret is an error, not a reason to guess: raising fails the node,
343
+ # and the message is what the user sees.
344
+ switch = options.get("interactive", "true")
345
+ if switch not in TRUE_WORDS + FALSE_WORDS:
346
+ raise ValueError(f"'interactive: {switch}' is not understood; write true or false.")
347
+ depth = options.get("depth") or None
348
+ if depth not in (None, QUICK, DEEP):
349
+ raise ValueError(f"'depth: {depth}' is not understood; write {QUICK} or {DEEP}.")
350
+
351
+ interactive = switch in TRUE_WORDS
352
+ if not interactive and depth is None:
353
+ depth = QUICK # Nobody to ask, nothing requested: the cheaper tour.
354
+ focus = "\n".join(focus_lines).strip() or DEFAULT_FOCUS
355
+ return WorkflowValue(text=focus, data=dict(DEFAULT_STATE, focus=focus, interactive=interactive, depth=depth))
356
+
357
+
358
+ async def scan_tree(value: WorkflowValue) -> WorkflowValue:
359
+ """ASYNC `fn(value)`.
360
+
361
+ An async body runs on the worker's own event loop, next to the machinery that talks to iCode, so
362
+ blocking in it stalls everything else in the run. The directory walk blocks, hence `to_thread`.
363
+ `read_key_files` below does the same kind of file access as a SYNC body and needs no such care:
364
+ the worker already runs sync bodies on a thread. Prefer sync unless the body awaits something.
365
+
366
+ The worker's current directory is the run's workspace, so `os.getcwd()` is the project root.
367
+ """
368
+ inventory = await asyncio.to_thread(_inventory, os.getcwd())
369
+ # `data` is passed along untouched. Returning only a str here would silently drop it.
370
+ return WorkflowValue(text=inventory, data=value.data)
371
+
372
+
373
+ def read_key_files(value: WorkflowValue) -> WorkflowValue:
374
+ """SYNC `fn(value)` with a Retry policy (see where the node is declared).
375
+
376
+ Adds the project files found at the top level and the head of the README. A file can change or
377
+ vanish between the listing and the read; the OSError is not caught here, so it fails the attempt
378
+ and the node's Retry runs the body once more. That is only sound because this body is safe to
379
+ repeat: it reads, and nothing else.
380
+ """
381
+ names = {name.lower(): name for name in os.listdir(".")}
382
+ present = [name for name in PROJECT_FILES if name.lower() in names]
383
+ sections = [value.text, "Project files at the top level: " + (", ".join(present) or "none of the usual ones")]
384
+ for candidate in README_NAMES:
385
+ readme = names.get(candidate)
386
+ if readme is None or not os.path.isfile(readme):
387
+ continue
388
+ with open(readme, "rb") as handle:
389
+ head = handle.read(README_BYTES).decode("utf-8", errors="replace")
390
+ excerpt = "\n".join(head.splitlines()[:README_LINES])
391
+ sections.append(f"{readme} (first {README_LINES} lines at most):\n{excerpt}")
392
+ break
393
+ return WorkflowValue(text="\n\n".join(sections), data=value.data)
394
+
395
+
396
+ def must_ask_depth(value: WorkflowValue) -> bool:
397
+ """Edge predicate. Predicates are SYNC, take the source's value, and should be cheap and pure."""
398
+ state = _state(value)
399
+ return bool(state["interactive"]) and state["depth"] is None
400
+
401
+
402
+ def depth_is_known(value: WorkflowValue) -> bool:
403
+ """The complement of `must_ask_depth`: exactly one of the two edges out of read_key_files fires."""
404
+ return not must_ask_depth(value)
405
+
406
+
407
+ async def choose_depth(value: WorkflowValue, ctx: NodeContext) -> WorkflowValue:
408
+ """ASYNC `fn(value, ctx)`: the only shape that can `await ctx.ask`.
409
+
410
+ `ctx.ask` suspends this body until the person driving the run answers. Asked a `Question`, it
411
+ returns an `Answer`: `answer.choice` is the option label they picked (clicking it and typing it
412
+ exactly are the same answer), or None when they typed something else or nothing; `answer.text`
413
+ holds what they typed besides. A list of up to five Questions shows them in one dialog, one tab each
414
+ labelled by its `header`, and returns a tuple of Answers. The node is declared with `timeout=None`
415
+ because a timeout counts the waiting too.
416
+
417
+ In an unattended run this node is never activated: the conditional edges route around it, which
418
+ shows up as "skipped" in the graph. That is one of two ways to keep `ctx.ask` out of a headless
419
+ run; `review` shows the other.
420
+ """
421
+ answer = await ctx.ask(DEPTH_QUESTION)
422
+ depth = DEEP if answer.choice == DEEP else QUICK # Anything that is not "deep" is the default.
423
+ ctx.emit(f"Depth chosen: {depth}.")
424
+ return WorkflowValue(text=value.text, data=dict(_state(value), depth=depth))
425
+
426
+
427
+ def plan(value: WorkflowValue, ctx: NodeContext) -> WorkflowValue:
428
+ """SYNC `fn(value, ctx)`. `ctx.emit` works in every body; only `ctx.ask` needs async.
429
+
430
+ This node has two in-edges, from choose_depth and from the bypass, and exactly one of them carries
431
+ a value in any run. A single present source passes through unchanged, `data` included, so the body
432
+ does not care which way the value came.
433
+
434
+ Its value goes two ways: the switch judges it and hands it to one branch, and a side edge takes
435
+ the same value, state and all, straight to the join in front of the loop. Emitted lines appear
436
+ live under the node and are kept with the run.
437
+ """
438
+ state = _state(value)
439
+ depth = state["depth"] or QUICK
440
+ readers = "three readers in parallel" if depth == DEEP else "one reader"
441
+ asked = "asked interactively" if state["interactive"] else "unattended run"
442
+ ctx.emit(f"Tour depth: {depth} ({readers}; {asked}).")
443
+ return WorkflowValue(text=value.text, data=dict(state, depth=depth))
444
+
445
+
446
+ def wants_deep(value: WorkflowValue) -> bool:
447
+ """Used twice: as the switch case after `plan`, and as the condition on the edge out of the loop.
448
+
449
+ It reads `data`, so it only works where the state has been carried along. Both call sites are
450
+ downstream of Python nodes or a custom combine, never directly downstream of an agent.
451
+ """
452
+ return _state(value)["depth"] == DEEP
453
+
454
+
455
+ def _reader_task(value: WorkflowValue) -> str:
456
+ return READER_TASK.format(focus=_state(value)["focus"], inventory=value.text)
457
+
458
+
459
+ def fan_out(value: WorkflowValue, ctx: NodeContext) -> str:
460
+ """SYNC `fn(value, ctx)` returning a plain str, which is shorthand for `WorkflowValue(text=...)`.
461
+
462
+ The shorthand carries no `data`, and that is the point: this is the node in front of agents, so
463
+ it returns their prompt and nothing else. An agent's prompt is the `text` of its input. The state
464
+ is not lost, it left `plan` on the side edge.
465
+
466
+ A switch picks ONE target, so fanning out to three agents needs this node in between: the switch
467
+ routes to it, and three plain edges leave it.
468
+ """
469
+ ctx.emit("Deep tour: three readers start in parallel.")
470
+ return _reader_task(value) + FAN_OUT_NOTE
471
+
472
+
473
+ def hand_over(value: WorkflowValue, ctx: NodeContext) -> str:
474
+ """The quick branch of the switch: the same prompt for a single reader, again as a plain str."""
475
+ ctx.emit("Quick tour: one reader skims the project.")
476
+ return _reader_task(value)
477
+
478
+
479
+ def merge_findings(value: WorkflowValue) -> str:
480
+ """Target of the DEFAULT join over the three readers.
481
+
482
+ Without `combine=`, a join concatenates its present sources in declared order as
483
+ "## <node id>" sections of text, and the result has no `data`. A reader that failed its way to
484
+ "skipped" would simply be missing from the text.
485
+ """
486
+ return "Notes from three readers, one section each.\n\n" + value.text
487
+
488
+
489
+ def carry_state(sources: list[SourceValue]) -> WorkflowValue:
490
+ """CUSTOM combine of the join in front of the loop. SYNC, one argument, at most 30 seconds.
491
+
492
+ `sources` holds only the sources that produced a value, in declared order. Each is a SourceValue:
493
+ `node_id`, `activation_id` and `value`. Here that is always `plan` (the side edge) plus whichever
494
+ branch of the switch ran; the other branch was skipped and is absent.
495
+
496
+ This is the reunion the side edge exists for: the findings are text that came through agents, and
497
+ the state comes from `plan`, where it last existed.
498
+ """
499
+ by_node = {source.node_id: source.value for source in sources}
500
+ findings = [source.value.text for source in sources if source.node_id != PLAN_NODE]
501
+ return WorkflowValue(text="\n\n".join(findings), data=_state(by_node[PLAN_NODE]))
502
+
503
+
504
+ def open_round(value: WorkflowValue, ctx: NodeContext) -> WorkflowValue:
505
+ """Loop-body ENTRY. Round 1 receives the loop's input; every later round the previous EXIT value.
506
+
507
+ So in round 1 `value.text` is the findings, and in round 2 it is the draft that `review` returned,
508
+ with the reviewer's feedback on `data`. The round counter lives on `data` as well: a loop keeps no
509
+ variables between rounds other than the value it hands from exit to entry.
510
+ """
511
+ state = _state(value)
512
+ round_number = state["round"] + 1
513
+ ctx.emit("Round 1: writing the tour." if round_number == 1 else f"Round {round_number}: revising the tour.")
514
+ return WorkflowValue(text=value.text, data=dict(state, round=round_number))
515
+
516
+
517
+ def brief(value: WorkflowValue) -> str:
518
+ """The node in front of the writer: it turns the round's value into a prompt, a plain str.
519
+
520
+ The same split as `plan` and `fan_out` above. `open_round` owns the state and sends it around the
521
+ agent; this node only writes the prompt.
522
+ """
523
+ state = _state(value)
524
+ if state["round"] == 1:
525
+ return WRITE_TASK.format(focus=state["focus"], findings=value.text)
526
+ return REVISE_TASK.format(feedback=state["feedback"], draft=value.text)
527
+
528
+
529
+ def keep_round_state(sources: list[SourceValue]) -> WorkflowValue:
530
+ """CUSTOM combine inside the loop: the writer's text, with the state `open_round` sent around it."""
531
+ by_node = {source.node_id: source.value for source in sources}
532
+ return WorkflowValue(text=by_node[WRITER_NODE].text, data=_state(by_node[ROUND_NODE]))
533
+
534
+
535
+ async def review(value: WorkflowValue, ctx: NodeContext) -> WorkflowValue:
536
+ """Loop-body EXIT, and the second `ctx.ask`. Its return value is what `until` judges.
537
+
538
+ The other way to keep `ctx.ask` out of an unattended run: branch inside the body. Routing around
539
+ the node, as choose_depth does, is not an option for a loop's exit, which must produce a value in
540
+ every round (a round without one fails the run).
541
+
542
+ An unattended run is recorded as NOT reviewed. It does not pretend that somebody approved.
543
+ """
544
+ state = _state(value)
545
+ if not state["interactive"]:
546
+ ctx.emit("Unattended run: nobody reviews the draft.")
547
+ return WorkflowValue(text=value.text, data=dict(state, reviewed=False, accepted=False))
548
+
549
+ question = REVIEW_QUESTION.format(draft=value.text, round=state["round"], rounds=MAX_ROUNDS)
550
+ answer = (await ctx.ask(question)).strip()
551
+ accepted = answer.lower().rstrip(".!") in ACCEPT_WORDS
552
+ ctx.emit("Accepted." if accepted else "Changes requested.")
553
+ # Only an explicit word of acceptance counts; an empty answer is not one.
554
+ feedback = "" if accepted else answer or "(no details given)"
555
+ return WorkflowValue(text=value.text, data=dict(state, reviewed=True, accepted=accepted, feedback=feedback))
556
+
557
+
558
+ def tour_settled(value: WorkflowValue) -> bool:
559
+ """The loop's `until`: SYNC, judged on the exit value after every round. True ends the loop.
560
+
561
+ An unattended run is settled after one round, because another round has nothing new to go on.
562
+ """
563
+ state = _state(value)
564
+ return bool(state["accepted"]) or not state["interactive"]
565
+
566
+
567
+ def tour_text(value: WorkflowValue) -> str:
568
+ """The node in front of next_steps: the tour as a plain str, the state left behind.
569
+
570
+ The condition on the edge into this node reads `data`, so the loop's value has to carry it; the
571
+ agent after this node must not receive it. One line of Python separates the two.
572
+ """
573
+ return value.text
574
+
575
+
576
+ def render_tour(value: WorkflowValue) -> str:
577
+ """Output node. Its value appears on the Output tab and is what `icode workflow run` prints.
578
+
579
+ The status line says honestly how the draft got here, including the case where the loop ran out
580
+ of rounds and handed over a draft that nobody accepted.
581
+ """
582
+ state = _state(value)
583
+ if state["accepted"]:
584
+ status = f"accepted by the reader in round {state['round']}"
585
+ elif not state["reviewed"]:
586
+ status = "NOT reviewed by a human (unattended run)"
587
+ else:
588
+ status = f"DRAFT, not accepted after {state['round']} rounds. Still open: {state['feedback']}"
589
+ return f"# Project tour\n\nFocus: {state['focus']}\nDepth: {state['depth']}\nStatus: {status}\n\n{value.text}"
590
+
591
+
592
+ # ---------------------------------------------------------------------------------------------------
593
+ # The refine loop
594
+ # ---------------------------------------------------------------------------------------------------
595
+
596
+
597
+ def refine_round(scope: BuilderScope) -> tuple[NodeHandle, NodeHandle]:
598
+ """Loop body. Called ONCE, at build time, to declare the nodes of a round; it is not run per round.
599
+
600
+ `scope` offers python/agent/edge/switch/join/chain, but no loop (loops do not nest), no start and
601
+ no output. Edges cannot cross the loop boundary: a body node connects only to body nodes, and the
602
+ outside connects to the loop node as a whole. Return the (entry, exit) pair.
603
+ """
604
+ entry: NodeHandle = scope.python(ROUND_NODE, open_round)
605
+ prompt: NodeHandle = scope.python("brief", brief)
606
+ writer: NodeHandle = scope.agent(
607
+ WRITER_NODE,
608
+ profile="QA",
609
+ # Agents already retry up to 3 attempts, and only failures the backend marks as transient.
610
+ # Retry replaces that default: 2 attempts, and a 5 second pause before the second.
611
+ retry=Retry(max_attempts=2, backoff=5),
612
+ instructions_suffix=(
613
+ "You are writing a guided tour of this project for a newcomer. Keep it under 500 words: what the"
614
+ " project is, how it is laid out, where execution starts, and what to read first. When asked to"
615
+ " revise, return the whole tour again." + READ_ONLY_RULES
616
+ ),
617
+ )
618
+ # timeout=None: this node waits for a person, and a timeout would count that wait.
619
+ exit_: NodeHandle = scope.python("review", review, timeout=None)
620
+
621
+ scope.chain(entry, prompt, writer)
622
+ # The side edge again: `open_round` feeds the join directly, so the round's state reaches
623
+ # `review` although the prompt and the writer in between pass text only.
624
+ scope.join([entry, writer], exit_, combine=keep_round_state)
625
+ return entry, exit_
626
+
627
+
628
+ # ---------------------------------------------------------------------------------------------------
629
+ # Nodes
630
+ # ---------------------------------------------------------------------------------------------------
631
+
632
+ request: NodeHandle = wf.python("read_request", read_request)
633
+ tree: NodeHandle = wf.python("scan_tree", scan_tree)
634
+ # A Python node runs once unless told otherwise. Retry counts the first execution: 2 attempts means
635
+ # one retry, after an exception or a timeout, 0.5 seconds later.
636
+ key_files: NodeHandle = wf.python("read_key_files", read_key_files, retry=Retry(max_attempts=2, backoff=0.5))
637
+ depth_choice: NodeHandle = wf.python("choose_depth", choose_depth, timeout=None)
638
+ tour_plan: NodeHandle = wf.python(PLAN_NODE, plan)
639
+
640
+ dispatch: NodeHandle = wf.python("fan_out", fan_out)
641
+ architecture: NodeHandle = wf.agent(
642
+ "architecture",
643
+ # `profile` names an agent profile: a builtin one, or one of your own. An unknown name rejects the
644
+ # workflow when it is opened, before anything runs.
645
+ profile="QA",
646
+ # `model` pins this node to one model profile; without it the node uses the run's active model.
647
+ # A name that does not resolve rejects the workflow just like an unknown profile, which is why
648
+ # this shipped file names none. In your own copy:
649
+ # model="my-fast-model",
650
+ # `instructions_suffix` is appended to the profile's own instructions, for this node only.
651
+ instructions_suffix=(
652
+ "Your dimension is architecture: the main components, what each is responsible for, and how they"
653
+ " depend on each other." + READ_ONLY_RULES
654
+ ),
655
+ )
656
+ entry_points: NodeHandle = wf.agent(
657
+ "entry_points",
658
+ profile="QA",
659
+ instructions_suffix=(
660
+ "Your dimension is entry points: where execution starts (commands, mains, servers, handlers) and the"
661
+ " path a typical request or command takes through the code." + READ_ONLY_RULES
662
+ ),
663
+ )
664
+ conventions: NodeHandle = wf.agent(
665
+ "conventions",
666
+ profile="QA",
667
+ # Agents have no deadline by default. This one gets 10 minutes per attempt.
668
+ timeout=600,
669
+ instructions_suffix=(
670
+ "Your dimension is conventions: how the code is organised and named, how it is tested, and the rules"
671
+ " a contributor is expected to follow." + READ_ONLY_RULES
672
+ ),
673
+ )
674
+ merged: NodeHandle = wf.python("merge_findings", merge_findings)
675
+ solo: NodeHandle = wf.python("hand_over", hand_over)
676
+ quick: NodeHandle = wf.agent(
677
+ "quick_scan",
678
+ profile="QA",
679
+ instructions_suffix=(
680
+ "Skim the project and note what a newcomer needs first: its purpose, its layout, where execution"
681
+ " starts, and how it is tested. Be brief." + READ_ONLY_RULES
682
+ ),
683
+ )
684
+
685
+ refine: NodeHandle = wf.loop(
686
+ "refine",
687
+ body=refine_round,
688
+ until=tour_settled,
689
+ max_iterations=MAX_ROUNDS,
690
+ # "continue" (the default): when the rounds run out without `until` turning true, the last exit
691
+ # value becomes the loop's output all the same, and render_tour labels it as a draft.
692
+ # The alternative is on_exhausted="fail": the run then ends as failed (loop_exhausted) and no
693
+ # output is produced. Choose it when an unaccepted result must never look like a result.
694
+ on_exhausted="continue",
695
+ )
696
+
697
+ tour: NodeHandle = wf.python("render_tour", render_tour)
698
+ draft_text: NodeHandle = wf.python("tour_text", tour_text)
699
+ steps: NodeHandle = wf.agent(
700
+ "next_steps",
701
+ profile="QA",
702
+ instructions_suffix=(
703
+ "The message is a finished tour of this project. Suggest what the newcomer should do next: up to five"
704
+ " files to read, each with a one-line reason, and up to three small, safe first tasks. Only suggest;"
705
+ " do not carry out any of it." + READ_ONLY_RULES
706
+ ),
707
+ )
708
+
709
+ # ---------------------------------------------------------------------------------------------------
710
+ # Edges
711
+ # ---------------------------------------------------------------------------------------------------
712
+
713
+ wf.start(request) # Exactly one start node; it receives the run's input text.
714
+
715
+ # chain(a, b, c) is edge(a, b) plus edge(b, c).
716
+ wf.chain(request, tree, key_files)
717
+
718
+ # Conditional edges. Each `when` is judged on its own against the source's value, so any number of
719
+ # them may fire: none, one or all. These two are complements, which makes them an either/or that
720
+ # routes around the asking node when there is nothing to ask.
721
+ wf.edge(key_files, depth_choice, when=must_ask_depth)
722
+ wf.edge(key_files, tour_plan, when=depth_is_known)
723
+ wf.edge(depth_choice, tour_plan)
724
+
725
+ # A switch is the other either/or: cases are tried in order, the first true one wins, and `default`
726
+ # (required) takes the rest. Exactly one target runs; the others are skipped. One switch per node.
727
+ wf.switch(tour_plan, cases=[(wants_deep, dispatch)], default=solo)
728
+ wf.edge(solo, quick)
729
+
730
+ # Fan-out is nothing special: several plain edges out of one node, and the targets run in parallel.
731
+ wf.edge(dispatch, architecture)
732
+ wf.edge(dispatch, entry_points)
733
+ wf.edge(dispatch, conventions)
734
+
735
+ # Fan-in with the default combine. A join synthesises a node of its own, "join:merge_findings", in
736
+ # front of the target, and the target may have no other in-edge.
737
+ wf.join([architecture, entry_points, conventions], merged)
738
+
739
+ # Fan-in with a custom combine, straight into the loop. `tour_plan` is the side edge that carries
740
+ # the state; of `merged` and `quick` only the branch the switch chose is present.
741
+ wf.join([tour_plan, merged, quick], refine, combine=carry_state)
742
+
743
+ # The loop node stands for its whole body: its value is the last exit value.
744
+ wf.edge(refine, tour)
745
+ wf.edge(refine, draft_text, when=wants_deep) # Fires next to the plain edge above, not instead of it.
746
+ wf.edge(draft_text, steps)
747
+
748
+ # Every output a run produced is reported, in this order. An output that was skipped is left out,
749
+ # so a quick tour has one output and a deep tour two. Outputs must be top-level nodes.
750
+ wf.output(tour)
751
+ wf.output(steps)
752
+
753
+ # iCode loads the module-level name `workflow`. build() validates the whole graph (names, cycles,
754
+ # reachability, callable shapes) and freezes the builder; a mistake surfaces here, when the file is
755
+ # opened, not in the middle of a run.
756
+ workflow: Workflow = wf.build()