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,1006 @@
1
+ # Workflow reference
2
+
3
+ This page provides workflow authors with the Python API, data and scheduling rules, execution environments, interaction behavior, and run records.
4
+
5
+ ## Workflow files
6
+
7
+ ### File definition and validation
8
+
9
+ Workflows are defined in `.py` files. This is a minimal complete example that returns its input unchanged:
10
+
11
+ ```python
12
+ from chrys.workflows import WorkflowBuilder, WorkflowValue
13
+
14
+ wf = WorkflowBuilder("Echo")
15
+
16
+
17
+ def echo(value: WorkflowValue) -> WorkflowValue:
18
+ return value
19
+
20
+
21
+ node = wf.python("echo", echo) # Register the node
22
+ wf.start(node) # Declare the single top-level start
23
+ wf.output(node) # Declare at least one output; it may also be the start
24
+ workflow = wf.build() # Validate, build, and assign to the module-level workflow variable
25
+ ```
26
+
27
+ The module must define a top-level variable named `workflow` containing the `Workflow` object returned by calling `build()` on a `WorkflowBuilder`, as in `workflow = wf.build()` above. You can choose a different name for the builder variable `wf`, but the entry variable must be named `workflow`. Do not place the `workflow` assignment inside a function or an `if __name__ == "__main__":` block.
28
+
29
+ Syntax errors, exceptions in top-level code, build validation failures, or a missing valid `workflow` object prevent loading. For structural validation rules, see [`WorkflowBuilder.build()`](#workflowbuilderbuild).
30
+
31
+ To check a workflow before running it, use [`icode workflow validate`](#icode-workflow-validate): it reports each problem with its file and line.
32
+
33
+ ### File discovery
34
+
35
+ A workflow is either a single `.py` file or a workflow folder: a folder holding an entry file with exactly the folder's name, such as `code-review/code-review.py`. A workflow's ID is its file name without `.py`, or its folder name, independent of the `WorkflowBuilder` title. iCode looks for workflows in these locations:
36
+
37
+ | Location | Source |
38
+ | --- | --- |
39
+ | `.chrys/workflows/` in the current working directory | `project` |
40
+ | Under the iCode configuration directory: `%APPDATA%\chrys\workflows\` on Windows; `~/.chrys/workflows/` on Linux and macOS | `global` |
41
+ | Built-in examples shipped with iCode | `builtin` |
42
+
43
+ When several workflows have the same ID, the first one that can be read wins, in this order:
44
+
45
+ | Priority | Workflow |
46
+ | --- | --- |
47
+ | 1 | Project workflow folder |
48
+ | 2 | Project `.py` file |
49
+ | 3 | Global workflow folder |
50
+ | 4 | Global `.py` file |
51
+ | 5 | Built-in workflow |
52
+
53
+ Project workflows are discovered only in `.chrys/workflows/` under the current working directory; parent directories are not searched. Each location is scanned only for the `.py` files and workflow folders directly inside it. Names starting with `.` or `_` are ignored. A folder without an entry file of exactly its name, such as `data/` or `venv/`, is not a workflow and is ignored, and so is any folder inside a workflow folder. Built-in workflows are single files only. The global location reserves the folder name `sdk` for iCode's own files.
54
+
55
+ User workflow files cannot be symbolic links, must pass ownership checks, and have a source size limit of 4 MiB. A workflow folder is skipped when the folder or anything in it is a symbolic link or a Windows directory junction, when it holds something other than regular files and folders (such as a named pipe), or when it holds more than 1000 files and folders, more than 512 MiB in total, or is nested more than 16 levels deep. Names starting with `.` (such as `.venv` and `.git`) and the compiled copies Python keeps in `__pycache__` folders (such as `helpers.cpython-312.pyc`) are not checked and do not count toward these limits; every other file does, binaries and data files included. Workflows that cannot be read are skipped with a reported reason, and the next workflow with the same ID is used instead.
56
+
57
+ ### Workflow folders
58
+
59
+ Split a workflow into several files by putting it in a folder:
60
+
61
+ ```text
62
+ .chrys/workflows/
63
+ code-review/
64
+ code-review.py # Entry file: same name as the folder
65
+ steps.py # Imported with `from steps import ...`
66
+ prompts/summary.md # Read relative to __file__
67
+ ```
68
+
69
+ - The folder comes first on `sys.path`, as a single file's directory does: `import steps`, `from steps import check`, and modules in subfolders (`from helpers.git import diff`, with or without `__init__.py`) all work. Relative imports such as `from .steps import check` do not, because the entry file is not part of a package.
70
+ - Read other files relative to `__file__`, for example `Path(__file__).parent / "prompts" / "summary.md"`. Relative paths such as `open("summary.md")` are relative to the workspace, not to the folder.
71
+ - Do not name the entry file or another file in the folder after a standard library or installed module, such as `json.py` or `pkgutil.py`. Python may then load the module instead of your file, or your file instead of the module, and processes the workflow starts with `multiprocessing` can fail to start.
72
+ - Do not import the entry file from another file by its name: that runs the entry file again as a separate module.
73
+ - iCode keeps the workflow's compiled Python files in its own folder under the configuration directory, so a workflow run creates no `__pycache__` folders next to your files and does not use the compiled copies of your files found in them. This also holds for Python processes the workflow starts, unless it starts them without its environment variables or with `-E` or `-I`: those use `__pycache__` folders as usual, including compiled copies that confirmation does not check.
74
+
75
+ Moving `review.py` to `review/review.py` changes the workflow's path, so it must be confirmed again, and workflow sessions created for the old file cannot run it; start a new session.
76
+
77
+ ### Trust confirmation
78
+
79
+ Loading a custom workflow for the first time requires trust confirmation:
80
+
81
+ - **TUI**: Selecting a workflow opens the “Trust workflow” dialog. Review the source and declared execution environment, then click “Trust” to continue or “Cancel” to leave it unloaded.
82
+ - **CLI**: Add `--trust` to `icode workflow run`. For example, run `.chrys/workflows/echo.py` from the project root with:
83
+
84
+ ```shell
85
+ icode workflow run echo --trust --input "Hello"
86
+ ```
87
+
88
+ The confirmation is saved, so later runs can omit `--trust`. Confirmation is required again if the workflow's source, the built workflow definition, or environment information changes, including the selected interpreter's path, version, platform, or the workflow SDK supplied by iCode. For a single `.py` file, the source is that file; other Python files it imports are not checked. For a workflow folder, the source is every file in the folder except names starting with `.` and the compiled copies Python keeps in `__pycache__` folders, so adding, changing, or removing any of them requires confirming again. The check does not cover changes to installed dependencies.
89
+
90
+ Files are checked when the workflow is previewed and when a run starts, and Python reads them again as it loads them. A change made in between, or to a file the workflow reads only while it runs, is not caught. After changing files in a workflow folder, reopen the workflow to preview and confirm it.
91
+
92
+ Review the source before confirming trust. Workflow code can read and write files or start programs with your user permissions. Previewing a trusted workflow executes its top-level code, and running it loads the module again. Top-level operations such as file writes or network requests can therefore occur before any node starts and can occur more than once. Put task operations inside node functions, leaving only imports, function definitions, and workflow construction at the top level.
93
+
94
+ ## Python API
95
+
96
+ `chrys.workflows` provides these APIs:
97
+
98
+ | Name | Purpose |
99
+ | --- | --- |
100
+ | `WorkflowBuilder` | Create top-level nodes, edges, loops, the start node, and outputs |
101
+ | `BuilderScope` | Create nodes and edges inside a loop body |
102
+ | `NodeHandle` | A node reference returned by builder methods; `node_id` is the node name |
103
+ | `Workflow` | The result of `build()`, assigned to the top-level `workflow` variable |
104
+ | `WorkflowValue` | Text and structured data passed between nodes |
105
+ | `SourceValue` | A source and its result, received by a combine function |
106
+ | `NodeContext` | Progress reporting and user questions for Python nodes |
107
+ | `Question` | A question for `NodeContext.ask`, with optional single- or multi-select options |
108
+ | `Option` | One option of a `Question` |
109
+ | `Answer` | The user's answer to one `Question` |
110
+ | `Retry` | Node retry count and interval |
111
+
112
+ ### `WorkflowBuilder`
113
+
114
+ Create a top-level workflow:
115
+
116
+ ```python
117
+ WorkflowBuilder(title: str, *, description: str | None = None)
118
+ ```
119
+
120
+ `title` must be a nonempty string; `description` is optional. Neither is passed as node input or agent instructions.
121
+
122
+ `WorkflowBuilder` inherits from `BuilderScope` and can directly call its [`python()`](#builderscopepython), [`agent()`](#builderscopeagent), [`edge()`](#builderscopeedge), [`chain()`](#builderscopechain), [`switch()`](#builderscopeswitch), and [`join()`](#builderscopejoin) methods.
123
+
124
+ #### `WorkflowBuilder.start`
125
+
126
+ ```python
127
+ start(node: NodeHandle) -> None
128
+ ```
129
+
130
+ Declare the single top-level start node. This can be called only once. `node` must be a top-level node handle created by this builder.
131
+
132
+ #### `WorkflowBuilder.output`
133
+
134
+ ```python
135
+ output(node: NodeHandle) -> None
136
+ ```
137
+
138
+ Collect the result of `node` as a workflow output. `node` must be a top-level node handle created by this builder. Call this method multiple times to collect results from different nodes; the same node cannot be declared twice.
139
+
140
+ Declaring a node as an output does not stop further execution. Its downstream nodes and other runnable branches continue, and the workflow waits for those paths to finish. For collection order and skipped nodes, see [Input scheduling and output collection](#input-scheduling-and-output-collection).
141
+
142
+ #### `WorkflowBuilder.build`
143
+
144
+ ```python
145
+ build() -> Workflow
146
+ ```
147
+
148
+ Validate and freeze the workflow, returning a `Workflow`. A builder can be successfully built only once.
149
+
150
+ Declare one top-level start node with `start()` and at least one output node with `output()`. Every top-level node must be reachable from the top-level start along the edges. Every node inside a loop body must likewise be reachable from that body's entry node.
151
+
152
+ An edge from the same source to the same destination cannot be declared twice. A node cannot connect to itself, and edges cannot directly connect nodes inside a loop body to nodes outside it. Edges cannot form cycles such as `A → B → A`. To repeat a group of nodes, define a loop with `loop()` instead of connecting a downstream node back to an upstream node.
153
+
154
+ #### `WorkflowBuilder.loop`
155
+
156
+ Wrap a group of nodes in a loop that repeats until a stop condition is met or the iteration limit is reached. The returned `NodeHandle` represents the whole loop node and connects it to upstream and downstream nodes at the top level.
157
+
158
+ ```python
159
+ loop(name, body, until, max_iterations, on_exhausted="continue") -> NodeHandle
160
+ ```
161
+
162
+ | Parameter | Rules |
163
+ | --- | --- |
164
+ | `name` | Loop node name, unique across the entire workflow; cannot be empty, start with `join:`, or contain `->` |
165
+ | `body` | Function that builds the loop body, with signature `(scope: BuilderScope) -> tuple[NodeHandle, NodeHandle]`. The builder supplies `scope` to create nodes and edges inside the body. Return `(entry, exit)`, the entry and exit node handles |
166
+ | `until` | Synchronous stop condition with signature `(value: WorkflowValue) -> bool`. `value` is the current iteration's exit output; `True` ends the loop and `False` means the stop condition has not been met |
167
+ | `max_iterations` | Required maximum iteration count; an integer of at least 1 |
168
+ | `on_exhausted` | What to do when the iteration limit is reached without satisfying the stop condition: `"continue"` (default) or `"fail"`, as described below |
169
+
170
+ `body` and `until` each take one positional argument. The names `scope` and `value` above are illustrative; you can name them differently.
171
+
172
+ **Building the loop body**
173
+
174
+ `body` runs once when `loop()` is called. The returned entry and exit must belong to that loop body and may be the same node.
175
+
176
+ `body` creates nodes and edges through the supplied [`BuilderScope`](#builderscope) instance. A loop body cannot contain another loop.
177
+
178
+ **Execution and stopping rules**
179
+
180
+ At runtime, each iteration executes the previously built body:
181
+
182
+ 1. The first iteration passes the loop node's input to `entry`. Later iterations pass the previous `exit` output to `entry`.
183
+ 2. Wait for the iteration's body to finish and obtain the `exit` output. If no branch passes a result to the exit in this iteration—for example, if the only edge to it has a `when` condition that returns `False`—the exit node is skipped and the loop fails with `loop_no_value`.
184
+ 3. Pass the exit value to `until`. If it returns `True`, end the loop with this value as the loop node's output. If it returns `False` and the iteration limit has not been reached, start the next iteration.
185
+
186
+ `until` is checked after each iteration, so the loop always runs at least once. If `max_iterations` is reached and `until` still returns `False`, `on_exhausted` determines the result:
187
+
188
+ | Value | Behavior |
189
+ | --- | --- |
190
+ | `"continue"` | End the loop, use the last iteration's exit value as its output, and continue downstream |
191
+ | `"fail"` | End the workflow run with `loop_exhausted` |
192
+
193
+ ### `BuilderScope`
194
+
195
+ `BuilderScope` provides instance methods for creating nodes and edges, inherited by `WorkflowBuilder`. At the top level, use the `WorkflowBuilder` instance. Inside a loop body, use the `BuilderScope` instance supplied to the `body` callback of `loop()`; you do not need to construct one yourself.
196
+
197
+ **Node naming rules**: Names must be unique across the entire workflow, cannot be empty, cannot start with `join:`, and cannot contain `->`.
198
+
199
+ #### `BuilderScope.python`
200
+
201
+ Define a node that executes a Python function.
202
+
203
+ ```python
204
+ python(name, fn, *, timeout=300.0, retry=None) -> NodeHandle
205
+ ```
206
+
207
+ | Parameter | Meaning |
208
+ | --- | --- |
209
+ | `name` | Node name |
210
+ | `fn` | Python function to execute; see signature and return value requirements below |
211
+ | `timeout` | Maximum seconds per attempt, default 300; a finite positive number or `None`, where `None` means no deadline |
212
+ | `retry` | A [`Retry`](#retry) instance; when omitted, the node runs only once |
213
+
214
+ `fn` can be synchronous or asynchronous, defined with `async def`.
215
+
216
+ `fn` takes one or two positional arguments. The first is the node input, a [`WorkflowValue`](#workflowvalue). The optional second argument is a [`NodeContext`](#nodecontext), used to report progress or ask the user a question. Examples of both signatures:
217
+
218
+ ```python
219
+ def transform(value: WorkflowValue) -> str:
220
+ return value.text.strip()
221
+
222
+
223
+ def report(value: WorkflowValue, ctx: NodeContext) -> WorkflowValue:
224
+ ctx.emit("Preparing result")
225
+ return value
226
+ ```
227
+
228
+ The positional parameters of `fn` cannot have default values. `fn` also cannot declare `*args`, `**kwargs`, or required keyword-only parameters.
229
+
230
+ `fn` must return a string or a `WorkflowValue`. iCode converts a returned string to `WorkflowValue(text=returned_string, data=None)`.
231
+
232
+ Relative file paths in Python nodes are resolved against the workflow session's working directory.
233
+
234
+ #### `BuilderScope.agent`
235
+
236
+ Define a node that executes an agent task.
237
+
238
+ ```python
239
+ agent(
240
+ name,
241
+ *,
242
+ profile,
243
+ model=None,
244
+ instructions_suffix=None,
245
+ timeout=None,
246
+ retry=None,
247
+ ) -> NodeHandle
248
+ ```
249
+
250
+ | Parameter | Meaning |
251
+ | --- | --- |
252
+ | `name` | Node name |
253
+ | `profile` | Required ID, name, or display name of an existing agent profile; using the ID is recommended. List profiles with `icode agents` |
254
+ | `model` | Optional iCode model profile ID or name, listed by `icode models`; when omitted, the model selection rules below apply |
255
+ | `instructions_suffix` | Optional string with additional instructions for this node |
256
+ | `timeout` | Maximum seconds per attempt, unlimited by default; a finite positive number or `None` |
257
+ | `retry` | When omitted, at most three attempts with zero backoff; not all errors are retried automatically |
258
+
259
+ ##### Agent profile matching
260
+
261
+ `profile` is matched in this order:
262
+
263
+ 1. ID.
264
+ 2. Exact name.
265
+ 3. Exact display name.
266
+ 4. Case-insensitive name.
267
+ 5. Case-insensitive display name.
268
+
269
+ A unique match at any step selects that profile. No match proceeds to the next step; multiple matches cause an immediate error. If all steps fail to match, an error is also reported.
270
+
271
+ ##### Model selection
272
+
273
+ `model` is matched against iCode model profiles in this order:
274
+
275
+ 1. Exact ID.
276
+ 2. Exact name.
277
+ 3. Case-insensitive name.
278
+
279
+ A unique match at any step selects that profile. No match proceeds to the next step; multiple matches cause an immediate error. If all steps fail to match, an error is also reported.
280
+
281
+ Regular iCode agents select a model in this priority order:
282
+
283
+ 1. The `model` argument to `agent()`.
284
+ 2. The model bound to the agent profile.
285
+ 3. The workflow's default model. In the TUI, select it under “Run Settings”. In the CLI, loading an existing workflow session with `icode workflow run <workflow-id> --session <session-id>` retains that session's saved model selection. If no workflow default has been selected, the [default model configured in iCode](settings.md#agents-models-and-requests) is used.
286
+
287
+ An explicit `model` that does not exist or is unavailable causes an error. If the agent's bound model does not exist, selection falls back to the workflow default. The workflow cannot start without an available model.
288
+
289
+ ##### Input and output
290
+
291
+ The agent uses the upstream `WorkflowValue.text` as its task input. `WorkflowValue.data` is not automatically added to the prompt.
292
+
293
+ The node returns a `WorkflowValue` with the agent's response text in `text` and `None` in `data`. Regular iCode agents prefer the final response text. If the final response has no text, the text emitted during this execution is concatenated in order as the node output, which may include commentary around tool calls.
294
+
295
+ ##### Agent profile support
296
+
297
+ Regular iCode agent nodes support profile settings as follows (✓ supported, ✗ unsupported). For field formats and configuration rules, see the [Agent profile reference](agent-profile.md).
298
+
299
+ | Setting | Supported | Behavior in a node |
300
+ | --- | :---: | --- |
301
+ | `instructions` | ✓ | Uses the profile's instructions, with the `instructions_suffix` argument to `agent()` appended |
302
+ | `model` | ✓ | Participates in model selection |
303
+ | `tools.builtins` | ✓ | Loads built-in tools; CLI runs do not provide the user-question tool |
304
+ | `tools.shell_filter` | ✓ | Applies Shell command filtering configuration |
305
+ | `tools.mcp` | ✓ | Loads MCP tools and applies allowed-tool scope, progressive disclosure, and result limits |
306
+ | `approval` | ✓ | Uses the same [tool approval rules](../guides/configuration/approval.md) as Chat mode |
307
+ | `compaction` | ✓ | Applies context compaction configuration |
308
+ | `sub_agents` | ✓ | Registers sub-agent tools. A failed sub-agent ends at once and returns its error to the node's model, without offering “Retry” or “Abort” |
309
+ | `skills` | ✓ | Discovers and loads skills as in Chat mode, and provides `load_skill` |
310
+ | `memory` | ✓ | Loads the configured memory files and directories |
311
+
312
+ Each agent node has its own context. Edges pass only node outputs, not the full conversation history or tool-call records.
313
+
314
+ ##### External ACP agents
315
+
316
+ `profile` can directly reference a configured ACP agent, even one marked as sub-agent-only. [Configure an external ACP agent in the TUI](../guides/extensions/external-acp-agents.md), or [create an agent YAML file containing an `acp` configuration](agent-profile.md#acp).
317
+
318
+ ACP nodes differ in these ways:
319
+
320
+ - Omitting `model` uses the remote model settings in the ACP profile, not the workflow default.
321
+ - An explicit `model` takes an iCode model profile ID or name. iCode uses only its `model_id` to request a remote model switch over ACP; the profile's other fields do not apply. If the remote agent does not list the model, or rejects the switch because the method is unsupported or the parameters are invalid, execution continues with the remote default model.
322
+ - `instructions_suffix` is appended to the user prompt sent to the external agent.
323
+ - The node output is determined by “Result” in the external agent profile ([`acp.result_mode`](agent-profile.md#acp)), with options “Last message segment” and “Full transcript”. See [Set the result and timeouts](../guides/extensions/external-acp-agents.md#set-the-result-and-timeouts).
324
+ - The external agent executes its own tools. Only operations for which it sends ACP permission requests enter iCode's approval process.
325
+
326
+ #### `BuilderScope.edge`
327
+
328
+ Connect a source node to a destination, optionally with a condition for passing the result.
329
+
330
+ ```python
331
+ edge(src: NodeHandle, dst: NodeHandle, *, when=None) -> None
332
+ ```
333
+
334
+ `src` is the source node and `dst` is the destination. Once the source finishes, its output is passed along the edge to the destination.
335
+
336
+ Omitting `when` passes the output unconditionally. If supplied, `when` is a synchronous function that receives the source output as a `WorkflowValue`: `True` passes it on, while `False` does not. A source can connect to multiple destinations. Edge conditions are evaluated independently, so multiple true conditions pass the result to multiple destinations.
337
+
338
+ A condition function must take exactly one positional argument with no default value. Prefer conditions based only on the input, without modifying external state. An exception during condition evaluation prevents the source node from completing normally; it is not treated as a false condition.
339
+
340
+ `src` and `dst` must be created by the instance on which `edge()` is called. For a `WorkflowBuilder`, both must be top-level nodes in that workflow; for a loop body's `BuilderScope`, both must belong to that body.
341
+
342
+ #### `BuilderScope.chain`
343
+
344
+ Connect multiple nodes in order.
345
+
346
+ ```python
347
+ chain(*nodes: NodeHandle) -> None
348
+ ```
349
+
350
+ Pass at least two nodes. `chain(a, b, c)` is equivalent to `edge(a, b)` followed by `edge(b, c)`.
351
+
352
+ Every node in `nodes` must be created by the instance on which `chain()` is called.
353
+
354
+ #### `BuilderScope.switch`
355
+
356
+ Select one downstream branch from a source node based on conditions.
357
+
358
+ ```python
359
+ switch(
360
+ src: NodeHandle,
361
+ cases: Sequence[tuple[Callable[[WorkflowValue], bool], NodeHandle]],
362
+ default: NodeHandle,
363
+ ) -> None
364
+ ```
365
+
366
+ `src` is the source node. `cases` is an ordered sequence whose entries must be `(condition_function, destination_node)` pairs, with each destination a `NodeHandle`. `default` is the required fallback destination.
367
+
368
+ Conditions are evaluated in `cases` order. The first condition that returns `True` selects its branch; later conditions are not evaluated. If none match, the default branch is selected.
369
+
370
+ Each condition must be a synchronous function taking one positional argument with no default value. It receives the source output as a `WorkflowValue` and returns a Boolean. Prefer conditions based only on the input, without modifying external state. An exception during condition evaluation prevents the source node from completing normally; it is not treated as a false condition.
371
+
372
+ A source node can have only one `switch()`. A `switch()` and ordinary `edge()` calls can coexist on the same source.
373
+
374
+ `src`, every destination in `cases`, and `default` must be created by the instance on which `switch()` is called.
375
+
376
+ #### `BuilderScope.join`
377
+
378
+ Combine results from multiple source nodes and pass them to a destination.
379
+
380
+ ```python
381
+ join(
382
+ sources: Sequence[NodeHandle],
383
+ dst: NodeHandle,
384
+ *,
385
+ combine=None,
386
+ ) -> None
387
+ ```
388
+
389
+ `sources` is a nonempty sequence of `NodeHandle` objects, and `dst` is the destination's `NodeHandle`. `join()` creates a join node named `join:<destination-name>` and connects it to `dst`. `dst` cannot have any other direct incoming edges.
390
+
391
+ Every node in `sources` and `dst` must be created by the instance on which `join()` is called.
392
+
393
+ Without `combine`, the [default merge rules](#input-scheduling-and-output-collection) apply.
394
+
395
+ If supplied, `combine` must be a synchronous single-argument function taking `list[SourceValue]` and returning a string or `WorkflowValue`. The list follows the declaration order of `sources`, not completion order. Skipped sources or sources not selected by a condition do not appear in it.
396
+
397
+ The supplied `combine` is called whenever at least one source provides input, including when there is only one input. If no source provides input, both the join node and the destination are skipped, and `combine` is not called.
398
+
399
+ ### `NodeHandle`
400
+
401
+ ```python
402
+ class NodeHandle:
403
+ node_id: str
404
+ ```
405
+
406
+ A node reference used to declare the start, outputs, and edges. Use handles returned by `python()`, `agent()`, or `loop()`. Instances created directly with `NodeHandle("name")` cannot be used to build a workflow.
407
+
408
+ The sole public attribute, `node_id`, is the node name and cannot be modified.
409
+
410
+ ### `Workflow`
411
+
412
+ ```python
413
+ class Workflow:
414
+ definition: WorkflowDefinition # Read-only property
415
+ ```
416
+
417
+ A built workflow returned by `WorkflowBuilder.build()`. Assign it to the workflow file's top-level `workflow` variable for iCode to load and run.
418
+
419
+ Its `definition` attribute holds the built workflow definition, including the title, description, start, output nodes, nodes, and edges. This `WorkflowDefinition` instance is created automatically by `build()`; you do not need to define it yourself.
420
+
421
+ ### `WorkflowValue`
422
+
423
+ ```python
424
+ class WorkflowValue:
425
+ text: str
426
+ data: dict | list | str | int | float | bool | None = None
427
+ ```
428
+
429
+ A value passed between nodes. `text` holds text content; `data` holds structured data, defaults to `None`, and supports only these JSON values:
430
+
431
+ - Strings (`str`), integers (`int`), finite floating-point numbers (`float`), Booleans (`bool`), and `None`.
432
+ - Lists (`list`) and dictionaries (`dict`) with string keys. Nesting is allowed, but list elements and dictionary values must also meet these type requirements.
433
+
434
+ Non-JSON types such as sets (`set`, `frozenset`), tuples (`tuple`), and bytes (`bytes`, `bytearray`) are unsupported, as are ordinary custom objects, non-string dictionary keys, `NaN`, and positive or negative infinity. For size limits, see [Data and output limits](#data-and-output-limits).
435
+
436
+ The framework supplies each node's input. When a Python node or `combine` returns a string, it is automatically converted to a `WorkflowValue`. To return structured data, construct `WorkflowValue(text="...", data=...)` yourself; `text` is required.
437
+
438
+ ### `SourceValue`
439
+
440
+ ```python
441
+ class SourceValue:
442
+ node_id: str
443
+ activation_id: str
444
+ value: WorkflowValue
445
+ ```
446
+
447
+ A source item received by the `combine` function. The framework creates it; you do not need to construct it yourself.
448
+
449
+ - `node_id`: The source node's name.
450
+ - `activation_id`: The identifier for this execution of the source node within the workflow run. A loop-body node has a different identifier in each iteration.
451
+ - `value`: The `WorkflowValue` produced by that execution.
452
+
453
+ ### `NodeContext`
454
+
455
+ A Python node function can receive a `NodeContext` as its second positional argument to send text messages or ask the user questions.
456
+
457
+ A new `NodeContext` is created for every function invocation, including each loop iteration and retry after failure. Do not store task data that needs to persist across invocations in `NodeContext`; pass data between nodes through return values.
458
+
459
+ #### `NodeContext.emit`
460
+
461
+ ```python
462
+ emit(text: str) -> None
463
+ ```
464
+
465
+ Send any text message, such as progress or a notice. The TUI shows it on the “Output” tab; the CLI writes it to stderr in text mode and suppresses it in `--json` mode. Messages do not affect the node output returned by the function.
466
+
467
+ #### `NodeContext.ask`
468
+
469
+ ```python
470
+ async ask(prompt: str) -> str
471
+ async ask(prompt: Question) -> Answer
472
+ async ask(prompt: list[Question] | tuple[Question, ...]) -> tuple[Answer, ...]
473
+ ```
474
+
475
+ Ask the user and wait for the answer. Call it only from an asynchronous node function, using `await ctx.ask(...)`. The return value follows the argument:
476
+
477
+ - A string, which must not be blank, asks one open question and returns the typed answer as text.
478
+ - A [`Question`](#question) can offer options to pick one or several from, and returns one [`Answer`](#answer).
479
+ - A list or tuple of 1 to 5 questions shows them together in one dialog, one tab per question, and returns a tuple of answers in question order. A one-element list also returns a tuple.
480
+
481
+ Any other argument raises `TypeError`; an empty list or more than five questions raise `ValueError`.
482
+
483
+ The TUI displays the questions in the same dialog as the agent question tool. The CLI does not support interaction; the call fails with `ask_unavailable`.
484
+
485
+ Time spent waiting for an answer counts toward the node timeout. `ask()` does not use the agent question tool's timeout setting. To wait indefinitely for the user, set `timeout=None` when registering the Python node to remove its deadline.
486
+
487
+ The run's `workflows/<run_id>/events.jsonl` under the [session directory](../guides/daily-use/sessions.md#find-the-session-id-and-storage-location) keeps a text summary of the questions and the answers, cut to 512 characters, rather than the full questions.
488
+
489
+ ### `Question`
490
+
491
+ ```python
492
+ Question(
493
+ question: str,
494
+ header: str = "",
495
+ options: list[Option | str] | tuple[Option | str, ...] = (),
496
+ multi_select: bool = False,
497
+ )
498
+ ```
499
+
500
+ One question for `ctx.ask()`. The field names match the agent question tool.
501
+
502
+ - `question`: The question text, rendered as Markdown. It must not be blank. Apart from the message size limit it has no length limit, so a question can carry a whole draft for review.
503
+ - `header`: A short label for the question's tab, at most 64 characters. Without one, the tab shows the question number. Tabs, and therefore headers, show only when several questions are asked together.
504
+ - `options`: Up to 8 options. A string is shorthand for `Option(label=...)`. Without options, the question takes a typed answer only.
505
+ - `multi_select`: `True` lets the user pick several options, and needs at least one option.
506
+
507
+ Option labels have surrounding whitespace removed and must be unique within a question. Every string must be valid Unicode, without unpaired surrogates. Wrong types raise `TypeError` and other violations raise `ValueError`, both when the `Question` or `Option` is created, so the traceback points at your code.
508
+
509
+ Whether or not a question has options, the user can type an answer of their own, or add a note to a selection.
510
+
511
+ ### `Option`
512
+
513
+ ```python
514
+ Option(label: str, description: str = "")
515
+ ```
516
+
517
+ - `label`: What the user picks, and what comes back in `Answer.selected`. At most 200 characters.
518
+ - `description`: One line shown under the label. At most 500 characters.
519
+
520
+ ### `Answer`
521
+
522
+ ```python
523
+ class Answer:
524
+ selected: tuple[str, ...] = ()
525
+ text: str = ""
526
+ answered: bool # read-only property
527
+ choice: str | None # read-only property
528
+ ```
529
+
530
+ The user's answer to one `Question`.
531
+
532
+ - `selected`: The offered labels the answer names, in option order. Clicking an option and typing its label exactly (case-sensitive) give the same answer.
533
+ - `text`: Everything else the user typed: an answer of their own that matches no label, or a note given with a selection.
534
+ - `answered`: `False` only when the user skipped the question.
535
+ - `choice`: The one selected label, or `None` when nothing is selected. It raises `ValueError` when several are selected; read `selected` for multi-select questions.
536
+
537
+ Every answer has one of three shapes:
538
+
539
+ | `selected` | `text` | Meaning |
540
+ | --- | --- | --- |
541
+ | Not empty | A note, or `""` | Offered labels picked or typed; at most one for a single-select question |
542
+ | `()` | Not empty | An answer of the user's own that matches no label |
543
+ | `()` | `""` | Skipped |
544
+
545
+ How the dialog behaves:
546
+
547
+ - With one single-select question, clicking an option submits at once, so type any note before clicking. Text submitted without clicking an option is the whole answer.
548
+ - With several questions, clicking an option of a single-select question moves on to the next unanswered question, or to the “Submit” tab. Multi-select and typed answers move on with “Answer & Next” (“Answer & Review” on the last question). The “Submit” tab lists all answers and sends them with “Submit answers”, or with “Submit anyway” while some questions are unanswered. Submitting with nothing answered needs a second, confirming press.
549
+ - The dialog cannot be dismissed with Esc. The question waits until it is answered, or until its attempt or run ends.
550
+
551
+ `Answer` is a local result, not a node output: return a string or a `WorkflowValue`. To pass a selection downstream, put it in both `text`, which agent nodes and the default join read, and `data`, for Python nodes:
552
+
553
+ ```python
554
+ async def pick_areas(value: WorkflowValue, ctx: NodeContext) -> WorkflowValue:
555
+ answer = await ctx.ask(Question("Which areas?", options=["API", "Storage", "UI"], multi_select=True))
556
+ text = "\n".join(part for part in (", ".join(answer.selected), answer.text) if part)
557
+ return WorkflowValue(text=text, data={"selected": list(answer.selected), "text": answer.text})
558
+ ```
559
+
560
+ ### `Retry`
561
+
562
+ ```python
563
+ Retry(max_attempts: int, backoff: float = 0.0)
564
+ ```
565
+
566
+ `max_attempts` is the total number of attempts including the first, and must be at least 1. `backoff` is a fixed, finite, nonnegative interval in seconds.
567
+
568
+ Pass it through the `retry` argument of `python()` or `agent()`. Whether an error is retried also depends on its type; see [Timeouts and retries](#timeouts-and-retries).
569
+
570
+ ## Data and scheduling rules
571
+
572
+ ### Data and output limits
573
+
574
+ Workflows are intended to pass task text and bounded JSON data. Exceeding the limits below causes failure; values are not automatically truncated before being passed downstream. Depth, value count, and string length limits apply to Python node and custom `combine` return values. Return values that cannot be serialized also cause failure.
575
+
576
+ | Limit | Maximum | Scope |
577
+ | --- | --- | --- |
578
+ | One message sent between processes | 16 MiB | The entire UTF-8-encoded message, including the value and information added by the framework |
579
+ | Nesting depth of `data` | 64 levels | `data` itself is level 0; each level of list elements or dictionary values adds 1 |
580
+ | Total values in `data` | 200,000 | Includes all nested content; each list and dictionary itself counts as 1. Dictionary keys do not count. See the example below |
581
+ | One string value in `text` or `data` | 4,194,304 characters | Measured in characters; the full transport message must still meet the byte limit |
582
+ | All source data passed to a custom `combine` | 12 MiB total | All sources before merging, including each source's `text`, `data`, and identifiers, measured as UTF-8-encoded JSON |
583
+
584
+ This `data` contains 7 values:
585
+
586
+ ```python
587
+ data = { # Outer dictionary: 1
588
+ "names": ["Alice", "Bob"], # List itself + two strings: 3
589
+ "scores": [90, 80], # List itself + two numbers: 3
590
+ }
591
+ ```
592
+
593
+ The keys `"names"` and `"scores"` do not count.
594
+
595
+ Progress messages and diagnostic output have separate limits. An “attempt” below is one execution of a node; counts reset on retry.
596
+
597
+ | Output method | Limit per attempt | Effect of exceeding the limit |
598
+ | --- | --- | --- |
599
+ | Messages sent by `ctx.emit()` | At most 200 in any rolling 1-second window; at most 10,000 total; at most 8 MiB of UTF-8-encoded message text in total. All three limits apply | The attempt fails |
600
+ | Direct writes to `sys.stdout` / `sys.stderr` in Python nodes, such as `print()` output, excluding `ctx.emit()` | Only the first 64 KiB combined, measured as UTF-8, is retained | Excess output is truncated; this alone does not fail the node or affect its return value |
601
+
602
+ CLI text mode displays received `ctx.emit()` messages on stderr, but these still count toward the separate `ctx.emit()` limits, not the node's 64 KiB output allowance.
603
+
604
+ ### Input scheduling and output collection
605
+
606
+ Multiple `wf.edge()` calls can connect multiple upstream nodes to the same node. The destination waits until every incoming edge has determined whether it will pass a result, then runs or skips based on the results actually received.
607
+
608
+ All node types use these default input merge rules, except join nodes with a custom `combine`:
609
+
610
+ | Number of sources that actually provide results | Behavior |
611
+ | --- | --- |
612
+ | Zero | Skip the node; successors that depend only on it are also skipped |
613
+ | One | Pass through that source's `WorkflowValue` unchanged, retaining `data` |
614
+ | Multiple | Merge their `text` in incoming-edge declaration order as the node input, with `data=None` |
615
+
616
+ When merging multiple sources by default, each source node's name becomes a heading:
617
+
618
+ ```text
619
+ ## first_node
620
+ First output
621
+
622
+ ## second_node
623
+ Second output
624
+ ```
625
+
626
+ Ready branches can be scheduled in parallel. Each run currently permits at most four concurrent agent attempts; further agent nodes wait for a slot.
627
+
628
+ The workflow collects output nodes in `output()` declaration order, including only completed nodes that have a result. Skipped outputs do not produce empty placeholders, so a workflow can complete without any final outputs.
629
+
630
+ ### Timeouts and retries
631
+
632
+ For Python and agent nodes, `timeout` limits each attempt's duration, including time spent waiting for questions or approvals. The timer resets for each retry.
633
+
634
+ The entire batch of outgoing-edge condition evaluations for a node, including `switch` checks, shares a 30-second limit. Each loop stop-condition evaluation and custom `combine` call has its own 30-second limit. These calculations are independent of the node's `timeout`. Loading and building the workflow source has a separate combined limit of 60 seconds.
635
+
636
+ Automatic retry after a node failure depends on the error type and the node's `Retry` configuration:
637
+
638
+ | Situation | Automatic retry condition |
639
+ | --- | --- |
640
+ | A Python node's function raises an exception or times out | `Retry.max_attempts` has not been reached |
641
+ | A non-ACP agent node's model request hits a temporary error, such as a dropped connection, a rate limit or a request timeout | The request is first retried within the attempt, as in chat. If it still fails, the node is retried while `Retry.max_attempts` has not been reached |
642
+ | A non-ACP agent node's model request is too long for the model's context window | As in chat, with context compaction on, the context is compacted and the request is sent once more within the attempt, unless the error names a smaller limit than the model profile's. If it still fails, the node is not retried automatically |
643
+ | A non-ACP agent node reaches its `timeout` | `Retry.max_attempts` has not been reached |
644
+ | An external ACP agent node loses its connection, stops responding, reports an error, or reaches its `timeout` | `Retry.max_attempts` has not been reached. iCode cannot tell which errors an external agent reports are permanent, so it retries them all. It does not retry an agent that cannot be started, one that still cannot be reached after the attempt retried the connection several times, a rejected configuration, or an answer the agent refuses, cuts short or leaves empty |
645
+ | Errors that retrying cannot fix, such as an exhausted quota or plan, or an external agent that needs you to log in; also failed condition or combine calculations, unserializable or oversized return values, user questions in an environment that does not support them, and similar errors | Not retried automatically |
646
+
647
+ If retrying could make the model provider run one of its own tools a second time, such as an MCP server or shell the provider runs, the node is not retried automatically. Provider-run search and code execution are safe to repeat and don't stop retries; neither do iCode's own file, Shell or MCP tools.
648
+
649
+ When a non-ACP agent's attempt fails or times out, the retry continues from its conversation so far: tool calls that already finished do not run again, and node details show the new attempt's transcript continuing the previous one. An external ACP agent starts a new session with the same input, and the new attempt's transcript shows only that session. If the agent finished and a later step of the node failed, such as an outgoing condition, a retry runs the agent again from its input.
650
+
651
+ `Retry(max_attempts=1)` turns off node retries only: a failed model request is still retried within the attempt.
652
+
653
+ In the TUI, a failed node that is not retried automatically, either because retries are exhausted or because they do not apply, enters `awaiting_retry`. The user can inspect diagnostics and retry, or cancel the entire run. Manual retry applies only to nodes in this state in the current run.
654
+
655
+ The CLI does not support manual retry. If a node fails and cannot retry automatically, the entire run ends. The workflow is considered failed even if some outputs already exist.
656
+
657
+ Retrying a Python node reruns the entire function, including when it failed while evaluating outgoing conditions after the function had completed. If the loop node itself fails while evaluating its stop condition or outgoing conditions, manual retry reevaluates only the failed condition, retaining completed loop progress.
658
+
659
+ Retries can repeat file writes, external requests, and other operations, so ensure these operations can safely be repeated. A timeout or cancellation does not undo operations that have already occurred, nor does it guarantee that a running synchronous Python function stops immediately.
660
+
661
+ ## Execution environment
662
+
663
+ By default, workflows use the Python interpreter running iCode. You can instead specify an existing virtual environment or Python executable in the workflow file. Python 3.9 and later are supported.
664
+
665
+ This example uses an existing `.venv` in the project root, with the workflow file in the project's `.chrys/workflows/` directory:
666
+
667
+ ```python
668
+ # /// script
669
+ # [tool.chrys]
670
+ # python = "../../.venv"
671
+ # ///
672
+ ```
673
+
674
+ Keep the `#` prefixes and `# ///` markers: iCode reads the configuration from these comments. `[tool.chrys]` is the iCode-specific configuration section. Relative `python` paths are resolved against the directory containing the workflow file. For a workflow folder, that is the folder itself: a `.venv` in the project root is `../../../.venv`, and one inside the folder is `.venv`.
675
+
676
+ `python` can also point directly to a Python executable, such as `"/opt/homebrew/bin/python3.12"`. iCode does not search `PATH` for commands. For an environment created with uv, point to its `.venv`.
677
+
678
+ iCode starts the interpreter with the `PYTHONPYCACHEPREFIX` environment variable set to its own folder for compiled files. A wrapper script that runs Python with `-E` or `-I` ignores it, and the workflow then fails to load.
679
+
680
+ iCode does not install dependencies automatically. Install the workflow's third-party packages in the selected environment beforehand. iCode supplies `chrys.workflows` at runtime, so it needs no separate installation.
681
+
682
+ Script metadata also supports these two top-level fields, placed before `[tool.chrys]`:
683
+
684
+ | Field | Rules |
685
+ | --- | --- |
686
+ | `requires-python` | Optional Python version constraint string, such as `">=3.10,<3.13"`. iCode checks the selected interpreter against it and refuses to load if it does not match; it does not find or install another version automatically. Only comma-separated `~=`, `==`, `!=`, `<=`, `>=`, `<`, and `>` clauses with numeric release versions are supported: `.*` works only with `==` and `!=`, and `~=` needs at least two version components. Any other clause also refuses loading |
687
+ | `dependencies` | Optional array of dependency strings. A nonempty array requires `[tool.chrys] python`, otherwise loading is refused even if the default environment already has those packages. With a user-supplied interpreter, iCode neither installs these dependencies nor verifies that installed versions match the declaration |
688
+
689
+ For example, this declaration requires a project virtual environment using Python 3.10 through 3.12, with `requests` installed beforehand by the author:
690
+
691
+ ```python
692
+ # /// script
693
+ # requires-python = ">=3.10,<3.13"
694
+ # dependencies = ["requests"]
695
+ # [tool.chrys]
696
+ # python = "../../.venv"
697
+ # ///
698
+ ```
699
+
700
+ ## Sessions, runs, and records
701
+
702
+ Workflow sessions are separate from chat sessions and save multiple runs of the same workflow in the same workspace. Each start creates a new `run_id` using the input supplied for that run; previous conversations are not automatically included.
703
+
704
+ ### Session settings
705
+
706
+ The TUI's “Start Workflow” dialog provides “Run Settings”. Whether the working directory can change depends on whether the session has any runs and on the workflow's source:
707
+
708
+ | Session and workflow source | Working directory rules |
709
+ | --- | --- |
710
+ | New session with no runs, using a built-in or global workflow | The working directory can be changed in Run Settings |
711
+ | New session with no runs, using a project workflow | Fixed to the project directory where the workflow was discovered: the directory containing `.chrys/workflows/` |
712
+ | Session with existing runs | The workspace is fixed; create a new session to change directories or workflows |
713
+
714
+ Run Settings also offers a default model selector when the workflow contains iCode agent nodes.
715
+
716
+ Workflow sessions do not save their own approval mode. They share the TUI's current approval mode with Chat mode, including sessions reopened later. CLI runs always use `bypass`.
717
+
718
+ ### Run outcomes and recovery
719
+
720
+ Reopening a session lets you view history or start a new run, but does not resume from the last interrupted node. Cancelled or interrupted runs must start over. For nodes awaiting manual retry in the current run, see [Timeouts and retries](#timeouts-and-retries).
721
+
722
+ In the result object from `icode workflow run --json`, `outcome` indicates the run outcome. Local records also store it in the run-end event in `workflows/<run_id>/events.jsonl` under the [session directory](../guides/daily-use/sessions.md#find-the-session-id-and-storage-location).
723
+
724
+ If the process terminates abnormally before finalizing a run, restoring the session records `outcome=orphaned` for that run. The workflow run view displays “orphaned”.
725
+
726
+ | Run `outcome` | Meaning |
727
+ | --- | --- |
728
+ | `completed` | Run completed |
729
+ | `node_failed` | A node failed |
730
+ | `loop_exhausted` | A loop exhausted its iterations with `on_exhausted="fail"` |
731
+ | `cancelled` | Run cancelled; `reason=deadline_exceeded` indicates the overall run timeout |
732
+ | `worker_lost` | The process executing the workflow was lost |
733
+ | `storage_failed` | Run records could not be written |
734
+ | `orphaned` | Recovery found that the original process had terminated without properly finalizing the run |
735
+
736
+ File changes made by tool calls are recorded against the run, but do not assume that all side effects of arbitrary Python code or external programs can be tracked or undone. Cancellation, node retries, and new runs do not automatically roll back files.
737
+
738
+ ### View and export records
739
+
740
+ Use the run tabs in the TUI to switch between records within a session; after quitting, switch to Workflow mode and reopen it from the session list. Node details retain separate loop iterations and retry attempts. Viewing history does not execute workflow source code.
741
+
742
+ The `workflows/<run_id>/` directory under the [session directory](../guides/daily-use/sessions.md#find-the-session-id-and-storage-location) stores the source snapshot, graph definition, input, final outputs, run events, and node records. Agent transcripts also belong to that run. These files preserve records; they do not support resuming a workflow run.
743
+
744
+ Run and node usage and elapsed time help assess costs; ACP usage depends on what the remote agent reports. To export analysis data, use:
745
+
746
+ ```shell
747
+ icode trajectory export --session <session-id> --format json --out workflow.json
748
+ icode trajectory export --session <session-id> --format perfetto --out workflow.perfetto.json
749
+ ```
750
+
751
+ Replace `<session-id>` with the workflow session ID. Analysis exports for workflow sessions support only JSON and Perfetto.
752
+
753
+ ## Lifecycle hooks
754
+
755
+ Workflows use configured external hooks; see the [Hooks reference](hooks.md) for the configuration format. A workflow session can contain multiple runs. `session_*` events mark the session's first use for execution, its first run after restoration, or the end of its use. `workflow_run_*` events mark the start and end of each run. Node executions do not independently trigger the session events below.
756
+
757
+ A run must first pass startup checks for source code, execution environment, agent profiles, and other prerequisites before triggering session-start or session-restored events and the run-start event. Start requests that fail these checks do not trigger these events.
758
+
759
+ | Event | When it fires | Additional fields |
760
+ | --- | --- | --- |
761
+ | `session_start` | When a new session starts its first run, before `workflow_run_start` | None |
762
+ | `session_restored` | When a session loaded from saved records starts its first new run in the current iCode process, before `workflow_run_start` | `restored_session_id`: ID of the restored session |
763
+ | `session_end` | When a session that has started a run in the current iCode process is deleted, iCode closes normally, or the CLI run command ends normally | None |
764
+ | `workflow_run_start` | After each run passes startup checks, before nodes execute | `run_id`, `input_text` |
765
+ | `workflow_run_end` | After each run is finalized and its final outcome is determined, including failure and cancellation | `run_id`, `outcome`, `reason` |
766
+
767
+ Within each iCode process, a session triggers `session_start` or `session_restored` only on its first run. Each accepted run triggers one `workflow_run_start` and one `workflow_run_end`; node retries do not trigger them again. End events are not guaranteed if the process crashes or is forcibly terminated.
768
+
769
+ All these events include `session_kind: "workflow"`, `workflow_id`, `session_id`, and `cwd`. `profile` is an empty string because a workflow session is not bound to a single agent. For other shared fields, see [Hook base fields](hooks.md#base-fields).
770
+
771
+ `workflow_run_start` and `workflow_run_end` are for notifications and recording. If a hook is configured as blocking, iCode waits for it to finish but does not use its returned action to prevent the run or grant tool permissions. `action: block` is ignored, and `on_error: block` is treated as a warning on hook failure.
772
+
773
+ ## Command line
774
+
775
+ ### `icode workflow list`
776
+
777
+ List workflows according to the [file discovery](#file-discovery) rules without executing workflow code. Custom workflow titles come from previously saved trust confirmations.
778
+
779
+ ```shell
780
+ icode workflow list [--json]
781
+ ```
782
+
783
+ Text mode shows `ID`, `Source`, `Title`, and `Path`, using `-` for an unknown title; for a workflow folder, `Path` is its entry file. With `--json`, the output is an object with a `workflows` array; each entry contains `id`, `source`, `layout` (`file` for a single `.py` file, `package` for a workflow folder), `title`, and `path` (the entry file), with an empty string for an unknown title. In either mode, warnings about skipped files go to stderr. Use `-h` / `--help` for help.
784
+
785
+ ### `icode workflow validate`
786
+
787
+ Check a workflow before running it. The command loads the workflow as a run would and reports each problem with its file, line, and column, like a compiler.
788
+
789
+ ```shell
790
+ icode workflow validate <path> [--json]
791
+ ```
792
+
793
+ `<path>` is a workflow `.py` file or a [workflow folder](#workflow-folders), relative to the current directory. It does not have to be in a location iCode searches. For a folder, `code-review`, `code-review/`, and `code-review/code-review.py` all check the folder.
794
+
795
+ Validation runs the workflow's top-level code, as loading it in the TUI does, but it runs no node and calls no model. It does not ask for or record trust confirmation, so a later `icode workflow run` still needs `--trust` when the workflow is new or has changed.
796
+
797
+ The checks run in this order and stop at the first one that fails:
798
+
799
+ | Stage | What it checks |
800
+ | --- | --- |
801
+ | `resolve` | The path names a workflow: a `.py` file or a folder holding an entry file with the folder's exact name, not a link, with a name iCode loads |
802
+ | `read` | Every file can be read, is within the size limits, and the entry file is UTF-8 |
803
+ | `metadata` | The `# /// script` block, if any, is valid (see [Execution environment](#execution-environment)) |
804
+ | `environment` | The Python interpreter the workflow asks for exists and starts |
805
+ | `load` | Every `.py` file of a folder compiles, the top-level code runs, and `build()` succeeds |
806
+ | `graph` | The built graph is valid; warns about suspicious structure |
807
+ | `bindings` | Every agent node's profile and model are available on this computer |
808
+
809
+ #### Text report
810
+
811
+ Each problem is shown as `file:line:column: error: message [code]`, followed by the source line with the place marked. `note:` lines show how the code was reached, and a `help:` line suggests a fix. Text the workflow printed while loading follows under `captured output (load):`. The last line is the result:
812
+
813
+ ```text
814
+ ./.chrys/workflows/code-review/steps.py:2:10: error: NameError: name 'summarise' is not defined [load_error]
815
+ 2 | PROMPT = summarise("diff")
816
+ | ^~~~~~~~~
817
+ note: imported from ./.chrys/workflows/code-review/code-review.py:3
818
+ captured output (load):
819
+ | loading steps
820
+ FAIL code-review (package): 1 error
821
+ ```
822
+
823
+ A workflow that passes prints one line, such as `PASS code-review (package) · 1 node · 0 edges`. Warnings are listed above it and do not fail validation. Columns appear only when the workflow's Python is 3.11 or later.
824
+
825
+ #### JSON report
826
+
827
+ With `--json`, stdout holds one JSON object. Every field is always present; unknown values are `null`.
828
+
829
+ | Field | Type and meaning |
830
+ | --- | --- |
831
+ | `version` | Integer: report format version, currently `1` |
832
+ | `status` | String: `pass` or `fail` |
833
+ | `target` | Object: what was checked: `path`, `layout` (`file` or `package`), `workflow_id`, `entry` (the entry file), `package_dir`, `source_digest`, and `files` (number of files) |
834
+ | `stages` | Array of objects: each stage's `name` and `status` (`pass`, `fail`, or `skipped`), in order |
835
+ | `diagnostics` | Array of objects: the problems found, described below |
836
+ | `diagnostics_truncated` | Boolean: some load problems were left out |
837
+ | `sites_truncated` | Boolean: the workflow has too many nodes to report where each was declared, so graph and binding problems may have no line |
838
+ | `workflow` | Object or `null`: once the workflow has loaded, its `title`, `node_count`, `edge_count`, and `outputs` (output node IDs) |
839
+ | `output` | Object: `text` printed while loading, and whether it was `truncated` |
840
+
841
+ Each item in `diagnostics` contains:
842
+
843
+ | Field | Type and meaning |
844
+ | --- | --- |
845
+ | `severity` | String: `error` or `warning` |
846
+ | `code` | String: problem code, listed below |
847
+ | `stage` | String: the stage that found it |
848
+ | `message` | String: what is wrong |
849
+ | `file` | String or `null`: absolute path of the file |
850
+ | `line`, `column`, `end_line`, `end_column` | Integer or `null`: position in the file, starting at 1 |
851
+ | `node` | String or `null`: the node ID the problem concerns, or an edge written `source->target` |
852
+ | `source_line` | String or `null`: the text of `line` |
853
+ | `notes` | Array of objects with `message`, `file`, and `line`: how the code was reached, innermost first |
854
+ | `hint` | String or `null`: a suggested fix |
855
+ | `traceback` | String or `null`: the Python traceback of a load failure, with only the frames of your own files (not the standard library, installed packages or iCode) |
856
+
857
+ | Code | Stage | Meaning |
858
+ | --- | --- | --- |
859
+ | `path_not_found` | `resolve` | Nothing exists at the path. If a workflow has that ID, the hint gives its path |
860
+ | `path_is_link` | `resolve` | The workflow, or a folder's entry file, is a link |
861
+ | `path_not_workflow` | `resolve` | The path names no single workflow: it is not a `.py` file or a folder, it is a directory that holds workflows (or the project or `.chrys` folder around one) or a file inside a workflow folder, the folder's entry file is not a regular file, or it is spelled differently from its folder listing |
862
+ | `name_ignored` | `resolve` | iCode never loads this name, for example one starting with `.` or `_` |
863
+ | `name_reserved` | `resolve` | `sdk` is reserved in the global workflow directory |
864
+ | `entry_missing` | `resolve` | The folder has no entry file with its exact name |
865
+ | `shadowed` (warning) | `resolve` | Another workflow with the same ID is found first, so runs use that one |
866
+ | `source_too_large`, `package_too_large` | `read` | The entry file or the folder is over the size limit |
867
+ | `source_unreadable`, `package_unreadable` | `read` | A file cannot be read |
868
+ | `package_link`, `package_unsupported_file` | `read` | The folder holds a link or a file that is neither a regular file nor a folder |
869
+ | `source_not_utf8` | `read` | The entry file is not UTF-8 |
870
+ | `metadata_invalid` | `metadata` | The `# /// script` block is invalid |
871
+ | `environment_invalid` | `environment` | The Python environment cannot be used |
872
+ | `syntax_error` | `load` | A `.py` file does not compile |
873
+ | `load_error` | `load` | Top-level code raised an exception |
874
+ | `sdk_validation_error` | `load` | `WorkflowBuilder` rejected a declaration or `build()` |
875
+ | `missing_workflow` | `load` | There is no module-level `workflow` built by `build()` |
876
+ | `load_timeout` | `load` | Loading did not finish in time |
877
+ | `worker_failed` | `load` | The process that loads the workflow failed |
878
+ | `manifest_invalid` | `graph` | The built graph is invalid |
879
+ | `loop_exit_all_conditional` (warning) | `graph` | A loop's exit node is reached only through conditional edges, so an iteration can end with no value and fail the run |
880
+ | `agent_profile_missing` | `bindings` | An agent node names a profile that is not available |
881
+ | `model_unresolvable` | `bindings` | An agent node has no model it can use |
882
+ | `internal_error` | any | iCode itself failed during the stage shown |
883
+
884
+ #### Exit codes
885
+
886
+ | Exit code | Meaning |
887
+ | --- | --- |
888
+ | `0` | PASS, possibly with warnings |
889
+ | `1` | FAIL |
890
+ | `2` | Argument parsing error |
891
+ | `130` | The CLI caught a keyboard interrupt |
892
+
893
+ ### `icode workflow run`
894
+
895
+ Run the specified workflow and output its results when it finishes.
896
+
897
+ #### Arguments
898
+
899
+ ```shell
900
+ icode workflow run <workflow-id> [--input TEXT] [-s SESSION] [--trust] [--timeout SECONDS] [--json] [-q]
901
+ ```
902
+
903
+ | Argument | Default and purpose |
904
+ | --- | --- |
905
+ | `<workflow-id>` | File name without `.py`, or the folder name of a workflow folder; find IDs with `icode workflow list` |
906
+ | `--input TEXT` | Defaults to an empty string, passed to the start node as `WorkflowValue.text` (see [multi-line input](#multi-line-input)) |
907
+ | `-s` / `--session` | Load an existing workflow session and start a new run of its bound workflow; does not resume an old run. The session must be a workflow session with at least one run. `<workflow-id>` must match the session's workflow; otherwise the run is rejected with `spec_changed` |
908
+ | `--trust` | Trust the current custom source and environment; unnecessary for built-in workflows |
909
+ | `--timeout SECONDS` | No overall limit by default; must be a finite positive number. Covers preview, workflow loading, and execution, but excludes session restoration, some initialization, and cleanup. After a timeout, the command still waits for cleanup to finish |
910
+ | `--json` | Use JSON output |
911
+ | `-q` / `--quiet` | Do not show progress on stderr; warnings, errors, load output, output outside nodes, and final outputs are still shown |
912
+ | `-h` / `--help` | Show help |
913
+
914
+ #### Multi-line input
915
+
916
+ How to write line breaks in `--input` depends on the shell. In bash and zsh, use `$'...'` quoting with `\n`:
917
+
918
+ ```shell
919
+ icode workflow run demo-workflow --input $'interactive: false\ndepth: deep\nhow are errors handled?'
920
+ ```
921
+
922
+ In PowerShell, write each line break as `` `n `` inside double quotes:
923
+
924
+ ```powershell
925
+ icode workflow run demo-workflow --input "interactive: false`ndepth: deep`nhow are errors handled?"
926
+ ```
927
+
928
+ PowerShell does not understand `$'...'`: it passes the text on as one line with a literal `\n`.
929
+
930
+ #### Text output
931
+
932
+ Without `--json`, text mode writes final outputs to stdout in declaration order, separated by a blank line. Progress goes to stderr, one line per step: node states, agent-node tool calls and the notes an agent node writes between them, `ctx.emit()` progress, loop iterations, and a closing `✓ Workflow completed` line when the run succeeds. Load output, output outside nodes, and other run diagnostics also go to stderr. `--quiet` hides the progress but keeps warnings, errors, load output, and output outside nodes.
933
+
934
+ #### JSON output
935
+
936
+ JSON mode suppresses node states, `ctx.emit()` progress, and loop iteration messages on stderr. Warnings and errors still go to stderr. Once a run result is available, it is written to stdout as a single-line JSON object. The table lists all top-level fields; the structures of `diagnostics` and `outputs` follow below.
937
+
938
+ | Field | Type and meaning |
939
+ | --- | --- |
940
+ | `session_id` | String or `null`: session identifier, or `null` if unavailable |
941
+ | `run_id` | String: identifier for this run |
942
+ | `outcome` | String: run outcome; see [Run outcomes and recovery](#run-outcomes-and-recovery) |
943
+ | `reason` | String: outcome reason code, or an empty string if none |
944
+ | `node_id` | String: relevant failed node, or an empty string if none |
945
+ | `error` | String: error description, or an empty string if none |
946
+ | `duration` | Number: elapsed seconds measured by the CLI, including preparation |
947
+ | `diagnostics` | Object or `null`: run diagnostics; may be `null` if unavailable |
948
+ | `outputs` | Array of objects: final outputs in output declaration order, or an empty array if none |
949
+
950
+ When `diagnostics` is an object, its structure is as follows. It does not include output from node diagnostic files. This example shows only this field's value; line breaks and comments are added for readability. Actual output is single-line JSON without comments.
951
+
952
+ ```jsonc
953
+ {
954
+ "load": {
955
+ // String: output captured while loading the workflow file, such as a top-level print()
956
+ "text": "Output during loading\n",
957
+ // Boolean: whether load output was truncated by the capture limit
958
+ "truncated": false
959
+ },
960
+ "native": {
961
+ // String: output that cannot be associated with a specific node attempt,
962
+ // such as direct writes from subprocesses or native extensions;
963
+ // only the trailing portion is retained if the capture limit is exceeded
964
+ "text": "",
965
+ // Integer: number of bytes discarded because of the capture limit
966
+ "dropped_bytes": 0
967
+ }
968
+ }
969
+ ```
970
+
971
+ If collecting or reading diagnostics fails, the object includes a string `error` field and may lack `load` or `native`. For example, a read failure may produce:
972
+
973
+ ```jsonc
974
+ {
975
+ "error": "Specific reason diagnostics could not be read"
976
+ }
977
+ ```
978
+
979
+ Each item in `outputs` is an object containing these fields:
980
+
981
+ | Field | Type and meaning |
982
+ | --- | --- |
983
+ | `node_id` | String: output node ID |
984
+ | `activation_id` | String: activation identifier of the node execution that produced this output |
985
+ | `text` | String: the node's returned `WorkflowValue.text` |
986
+ | `data` | JSON value: the node's returned `WorkflowValue.data`; may be an object, array, string, number, Boolean, or `null`. Its internal structure is defined by the workflow and has no fixed fields |
987
+
988
+ #### Node diagnostics
989
+
990
+ Both text and JSON modes save node diagnostic records. `print()` output inside a node goes only to those records, not to CLI stderr or the result JSON's `diagnostics` field.
991
+
992
+ Node diagnostic records are stored in `workflows/<run_id>/nodes/` under the [session directory](../guides/daily-use/sessions.md#find-the-session-id-and-storage-location). Filenames typically follow `<activation_id>.<attempt>.diagnostics.<hash>.json`, containing the activation identifier, attempt number, and an 8-character hash of the record identity that keeps names distinct after sanitizing. `phases[].stdout.text` holds the captured text; `phases[].stdout.truncated` indicates truncation due to the capture limit. No file is created if there is no diagnostic content. In the TUI, you can also inspect an attempt's diagnostics on the “Output” tab in node details.
993
+
994
+ #### Error handling and exit codes
995
+
996
+ Text and JSON modes use the same exit codes. On failure or cancellation, text mode writes error text to stderr, while JSON mode writes error JSON to stderr. Argument parsing errors are written to stderr as text in both modes.
997
+
998
+ If a run result has already been produced before failure or cancellation, stdout may still contain output: collected final-result text in text mode, or the result object in JSON mode. If failure occurs before a run result exists—for example, the workflow is missing or trust has not been confirmed—stdout output is not guaranteed. Scripts should check the exit code; content on stdout or a nonempty JSON `outputs` array alone does not indicate success.
999
+
1000
+ | Exit code | Meaning |
1001
+ | --- | --- |
1002
+ | `0` | Completed |
1003
+ | `1` | Errors such as run failure, rejection, or ordinary cancellation |
1004
+ | `2` | Argument parsing error |
1005
+ | `124` | Workflow run timed out |
1006
+ | `130` | The CLI caught a keyboard interrupt |