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.
- chrys/__init__.py +16 -0
- chrys/app/__init__.py +3 -0
- chrys/app/acp/__init__.py +3 -0
- chrys/app/acp/bridge.py +664 -0
- chrys/app/acp/content.py +132 -0
- chrys/app/acp/doc/frontend-api.md +425 -0
- chrys/app/acp/history.py +585 -0
- chrys/app/acp/server.py +1796 -0
- chrys/app/acp/session_manager.py +1815 -0
- chrys/app/acp/tool_status.py +45 -0
- chrys/app/cli/__init__.py +3 -0
- chrys/app/cli/acp.py +178 -0
- chrys/app/cli/app.py +165 -0
- chrys/app/cli/approvals.py +101 -0
- chrys/app/cli/headless.py +223 -0
- chrys/app/cli/launch_cwd.py +42 -0
- chrys/app/cli/profiles.py +298 -0
- chrys/app/cli/progress.py +857 -0
- chrys/app/cli/run.py +425 -0
- chrys/app/cli/serve.py +1593 -0
- chrys/app/cli/tool_summary.py +225 -0
- chrys/app/cli/trajectory.py +201 -0
- chrys/app/cli/workflow.py +601 -0
- chrys/app/features/__init__.py +3 -0
- chrys/app/features/buddy/__init__.py +3 -0
- chrys/app/features/buddy/actions.py +68 -0
- chrys/app/features/buddy/animation.py +76 -0
- chrys/app/features/buddy/commands.py +196 -0
- chrys/app/features/buddy/docs/design.md +187 -0
- chrys/app/features/buddy/hatchery.py +80 -0
- chrys/app/features/buddy/lifecycle.py +41 -0
- chrys/app/features/buddy/model.py +265 -0
- chrys/app/features/buddy/pixel_renderer.py +113 -0
- chrys/app/features/buddy/pixel_sprites.py +279 -0
- chrys/app/features/buddy/portrait.py +76 -0
- chrys/app/features/buddy/progression.py +58 -0
- chrys/app/features/buddy/replies.py +191 -0
- chrys/app/features/buddy/sprites/alien.toml +70 -0
- chrys/app/features/buddy/sprites/axolotl.toml +71 -0
- chrys/app/features/buddy/sprites/bat.toml +69 -0
- chrys/app/features/buddy/sprites/bee.toml +69 -0
- chrys/app/features/buddy/sprites/cactus.toml +73 -0
- chrys/app/features/buddy/sprites/capybara.toml +70 -0
- chrys/app/features/buddy/sprites/cat.toml +69 -0
- chrys/app/features/buddy/sprites/crab.toml +70 -0
- chrys/app/features/buddy/sprites/dragon.toml +71 -0
- chrys/app/features/buddy/sprites/duck.toml +69 -0
- chrys/app/features/buddy/sprites/fox.toml +69 -0
- chrys/app/features/buddy/sprites/frog.toml +70 -0
- chrys/app/features/buddy/sprites/ghost.toml +70 -0
- chrys/app/features/buddy/sprites/goose.toml +69 -0
- chrys/app/features/buddy/sprites/jelly.toml +70 -0
- chrys/app/features/buddy/sprites/llama.toml +70 -0
- chrys/app/features/buddy/sprites/mushroom.toml +69 -0
- chrys/app/features/buddy/sprites/octopus.toml +70 -0
- chrys/app/features/buddy/sprites/owl.toml +70 -0
- chrys/app/features/buddy/sprites/panda.toml +71 -0
- chrys/app/features/buddy/sprites/penguin.toml +69 -0
- chrys/app/features/buddy/sprites/rabbit.toml +70 -0
- chrys/app/features/buddy/sprites/robot.toml +72 -0
- chrys/app/features/buddy/sprites/shark.toml +70 -0
- chrys/app/features/buddy/sprites/snail.toml +69 -0
- chrys/app/features/buddy/sprites/snake.toml +69 -0
- chrys/app/features/buddy/sprites/tabby.toml +69 -0
- chrys/app/features/buddy/sprites/turtle.toml +69 -0
- chrys/app/features/buddy/store.py +143 -0
- chrys/app/features/session_title/__init__.py +7 -0
- chrys/app/features/session_title/updater.py +582 -0
- chrys/app/installer.py +651 -0
- chrys/app/parsing.py +17 -0
- chrys/app/tui/__init__.py +3 -0
- chrys/app/tui/app.py +1287 -0
- chrys/app/tui/behaviors/__init__.py +3 -0
- chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
- chrys/app/tui/behaviors/insert_clipboard.py +73 -0
- chrys/app/tui/behaviors/right_click_copy.py +146 -0
- chrys/app/tui/binding_display.py +139 -0
- chrys/app/tui/buddy_messages.py +17 -0
- chrys/app/tui/buddy_reply.py +86 -0
- chrys/app/tui/chrys.tcss +549 -0
- chrys/app/tui/clipboard.py +109 -0
- chrys/app/tui/copy_messages.py +10 -0
- chrys/app/tui/i18n.py +230 -0
- chrys/app/tui/language.py +58 -0
- chrys/app/tui/notifications/__init__.py +12 -0
- chrys/app/tui/notifications/drivers.py +180 -0
- chrys/app/tui/notifications/service.py +126 -0
- chrys/app/tui/notifications/settings.py +141 -0
- chrys/app/tui/screens/__init__.py +3 -0
- chrys/app/tui/screens/agents/__init__.py +8 -0
- chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
- chrys/app/tui/screens/agents/config.py +2142 -0
- chrys/app/tui/screens/agents/config.tcss +169 -0
- chrys/app/tui/screens/agents/panels/__init__.py +3 -0
- chrys/app/tui/screens/agents/panels/acp.py +1176 -0
- chrys/app/tui/screens/agents/panels/basic.py +497 -0
- chrys/app/tui/screens/agents/panels/compaction.py +231 -0
- chrys/app/tui/screens/agents/panels/config_card.py +218 -0
- chrys/app/tui/screens/agents/panels/instructions.py +91 -0
- chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
- chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
- chrys/app/tui/screens/agents/panels/memory.py +684 -0
- chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
- chrys/app/tui/screens/agents/panels/skills.py +806 -0
- chrys/app/tui/screens/agents/panels/subagents.py +652 -0
- chrys/app/tui/screens/agents/panels/tools.py +205 -0
- chrys/app/tui/screens/agents/picker.py +103 -0
- chrys/app/tui/screens/agents/skill_paths.py +38 -0
- chrys/app/tui/screens/agents/validation_messages.py +452 -0
- chrys/app/tui/screens/dialogs/__init__.py +35 -0
- chrys/app/tui/screens/dialogs/agent_load.py +834 -0
- chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
- chrys/app/tui/screens/dialogs/app_mode.py +55 -0
- chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
- chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
- chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
- chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
- chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
- chrys/app/tui/screens/dialogs/approval/body.py +110 -0
- chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
- chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
- chrys/app/tui/screens/dialogs/ask_user.py +148 -0
- chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
- chrys/app/tui/screens/dialogs/base.py +84 -0
- chrys/app/tui/screens/dialogs/confirm.py +184 -0
- chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
- chrys/app/tui/screens/dialogs/connection_test.py +183 -0
- chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
- chrys/app/tui/screens/dialogs/diagram.py +102 -0
- chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
- chrys/app/tui/screens/dialogs/editor.py +378 -0
- chrys/app/tui/screens/dialogs/editor.tcss +132 -0
- chrys/app/tui/screens/dialogs/file_picker.py +582 -0
- chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
- chrys/app/tui/screens/dialogs/fork_session.py +219 -0
- chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
- chrys/app/tui/screens/dialogs/image_compression.py +102 -0
- chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
- chrys/app/tui/screens/dialogs/man_page.py +172 -0
- chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
- chrys/app/tui/screens/dialogs/new_folder.py +277 -0
- chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
- chrys/app/tui/screens/dialogs/notice.tcss +14 -0
- chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
- chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
- chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
- chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
- chrys/app/tui/screens/dialogs/session_title.py +110 -0
- chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
- chrys/app/tui/screens/dialogs/tool_view.py +207 -0
- chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
- chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
- chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
- chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
- chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
- chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
- chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
- chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
- chrys/app/tui/screens/diff/__init__.py +22 -0
- chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
- chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
- chrys/app/tui/screens/diff/screen.py +1532 -0
- chrys/app/tui/screens/diff/screen.tcss +104 -0
- chrys/app/tui/screens/guides/__init__.py +7 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
- chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/index.py +215 -0
- chrys/app/tui/screens/guides/reader.py +173 -0
- chrys/app/tui/screens/guides/screen.py +442 -0
- chrys/app/tui/screens/guides/screen.tcss +52 -0
- chrys/app/tui/screens/logs/__init__.py +7 -0
- chrys/app/tui/screens/logs/screen.py +504 -0
- chrys/app/tui/screens/logs/screen.tcss +45 -0
- chrys/app/tui/screens/main/__init__.py +7 -0
- chrys/app/tui/screens/main/action_availability.py +30 -0
- chrys/app/tui/screens/main/buddy_command.py +143 -0
- chrys/app/tui/screens/main/commands.py +1073 -0
- chrys/app/tui/screens/main/config_actions.py +464 -0
- chrys/app/tui/screens/main/copy_actions.py +117 -0
- chrys/app/tui/screens/main/dialog_controllers.py +826 -0
- chrys/app/tui/screens/main/dialog_gateway.py +166 -0
- chrys/app/tui/screens/main/diff_controller.py +478 -0
- chrys/app/tui/screens/main/engine_read_model.py +184 -0
- chrys/app/tui/screens/main/event_handlers.py +2014 -0
- chrys/app/tui/screens/main/input_flow.py +241 -0
- chrys/app/tui/screens/main/live_diff.py +244 -0
- chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
- chrys/app/tui/screens/main/model_indicator.py +217 -0
- chrys/app/tui/screens/main/navigation.py +343 -0
- chrys/app/tui/screens/main/ports.py +571 -0
- chrys/app/tui/screens/main/recent_dirs.py +123 -0
- chrys/app/tui/screens/main/rollback_controller.py +586 -0
- chrys/app/tui/screens/main/runtime_info.py +71 -0
- chrys/app/tui/screens/main/screen.py +2265 -0
- chrys/app/tui/screens/main/screen.tcss +21 -0
- chrys/app/tui/screens/main/session_handlers.py +1127 -0
- chrys/app/tui/screens/main/session_title.py +239 -0
- chrys/app/tui/screens/main/settings_coordinator.py +365 -0
- chrys/app/tui/screens/main/settings_persistence.py +184 -0
- chrys/app/tui/screens/main/shell_mode.py +112 -0
- chrys/app/tui/screens/main/state.py +289 -0
- chrys/app/tui/screens/main/submit_state.py +11 -0
- chrys/app/tui/screens/main/subscriptions.py +177 -0
- chrys/app/tui/screens/main/suggestions.py +982 -0
- chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
- chrys/app/tui/screens/main/view_adapter.py +1567 -0
- chrys/app/tui/screens/main/workflow_browser.py +747 -0
- chrys/app/tui/screens/main/workflow_content.py +248 -0
- chrys/app/tui/screens/main/workflow_controller.py +240 -0
- chrys/app/tui/screens/main/workflow_feedback.py +137 -0
- chrys/app/tui/screens/main/workflow_flow.py +49 -0
- chrys/app/tui/screens/main/workflow_run_control.py +456 -0
- chrys/app/tui/screens/main/workflow_session_view.py +316 -0
- chrys/app/tui/screens/main/workspace_actions.py +272 -0
- chrys/app/tui/screens/main/workspace_branch.py +194 -0
- chrys/app/tui/screens/menus/__init__.py +8 -0
- chrys/app/tui/screens/menus/languages.py +114 -0
- chrys/app/tui/screens/models/__init__.py +7 -0
- chrys/app/tui/screens/models/picker.py +147 -0
- chrys/app/tui/screens/models/screen.py +2043 -0
- chrys/app/tui/screens/models/screen.tcss +255 -0
- chrys/app/tui/screens/sessions/__init__.py +8 -0
- chrys/app/tui/screens/sessions/presenter.py +586 -0
- chrys/app/tui/screens/sessions/screen.py +779 -0
- chrys/app/tui/screens/sessions/screen.tcss +127 -0
- chrys/app/tui/screens/settings/__init__.py +29 -0
- chrys/app/tui/screens/settings/danger.py +26 -0
- chrys/app/tui/screens/settings/dialog.py +281 -0
- chrys/app/tui/screens/settings/layout.py +519 -0
- chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
- chrys/app/tui/screens/settings/panes/__init__.py +3 -0
- chrys/app/tui/screens/settings/panes/notifications.py +248 -0
- chrys/app/tui/screens/settings/panes/sessions.py +148 -0
- chrys/app/tui/screens/settings/ports.py +55 -0
- chrys/app/tui/screens/settings/provenance.py +102 -0
- chrys/app/tui/screens/settings/rows.py +546 -0
- chrys/app/tui/screens/settings/settings.tcss +406 -0
- chrys/app/tui/screens/themes/__init__.py +8 -0
- chrys/app/tui/screens/themes/dialogs.py +330 -0
- chrys/app/tui/screens/themes/editor.py +632 -0
- chrys/app/tui/screens/themes/editor.tcss +268 -0
- chrys/app/tui/screens/themes/messages.py +95 -0
- chrys/app/tui/screens/themes/palette.py +694 -0
- chrys/app/tui/screens/themes/panel.py +113 -0
- chrys/app/tui/screens/themes/picker.py +203 -0
- chrys/app/tui/screens/themes/save.py +191 -0
- chrys/app/tui/support/__init__.py +3 -0
- chrys/app/tui/support/file_manager.py +64 -0
- chrys/app/tui/support/gc_freeze.py +1114 -0
- chrys/app/tui/support/path_shortcuts.py +50 -0
- chrys/app/tui/support/prompt_history.py +298 -0
- chrys/app/tui/support/workspace_mru.py +476 -0
- chrys/app/tui/terminal/__init__.py +6 -0
- chrys/app/tui/terminal/_pty_backend.py +114 -0
- chrys/app/tui/terminal/emulator/__init__.py +24 -0
- chrys/app/tui/terminal/emulator/buffer.py +456 -0
- chrys/app/tui/terminal/emulator/charsets.py +113 -0
- chrys/app/tui/terminal/emulator/core.py +963 -0
- chrys/app/tui/terminal/emulator/input.py +342 -0
- chrys/app/tui/terminal/emulator/parser.py +227 -0
- chrys/app/tui/terminal/emulator/pen.py +158 -0
- chrys/app/tui/terminal/launcher.py +455 -0
- chrys/app/tui/terminal/leaked_reports.py +44 -0
- chrys/app/tui/terminal/panel.py +323 -0
- chrys/app/tui/terminal/pty_process.py +405 -0
- chrys/app/tui/terminal/rendering.py +151 -0
- chrys/app/tui/terminal/shell.py +273 -0
- chrys/app/tui/terminal/shell_integration.py +199 -0
- chrys/app/tui/terminal/title.py +224 -0
- chrys/app/tui/terminal/widget.py +654 -0
- chrys/app/tui/theme.py +422 -0
- chrys/app/tui/theme_loader.py +201 -0
- chrys/app/tui/theme_stylesheet.py +49 -0
- chrys/app/tui/themes/__init__.py +3 -0
- chrys/app/tui/themes/document.py +120 -0
- chrys/app/tui/themes/preview.py +170 -0
- chrys/app/tui/themes/store.py +176 -0
- chrys/app/tui/toasts.py +69 -0
- chrys/app/tui/util/__init__.py +3 -0
- chrys/app/tui/util/context_pressure.py +146 -0
- chrys/app/tui/util/debug_log.py +292 -0
- chrys/app/tui/util/diff_entries.py +64 -0
- chrys/app/tui/util/formatting.py +53 -0
- chrys/app/tui/util/git_branch.py +326 -0
- chrys/app/tui/util/invocation_progress.py +57 -0
- chrys/app/tui/util/logo.py +32 -0
- chrys/app/tui/util/message_gate.py +35 -0
- chrys/app/tui/util/mutation_sources.py +14 -0
- chrys/app/tui/util/picker_text.py +27 -0
- chrys/app/tui/util/removal.py +47 -0
- chrys/app/tui/util/rich_style.py +36 -0
- chrys/app/tui/util/source_text.py +12 -0
- chrys/app/tui/util/static_update.py +88 -0
- chrys/app/tui/util/todo_format.py +51 -0
- chrys/app/tui/util/visibility.py +214 -0
- chrys/app/tui/widgets/__init__.py +60 -0
- chrys/app/tui/widgets/ask_user_controls.py +652 -0
- chrys/app/tui/widgets/ask_user_markdown.py +17 -0
- chrys/app/tui/widgets/ask_user_prompt.py +606 -0
- chrys/app/tui/widgets/buttons.py +52 -0
- chrys/app/tui/widgets/chat/__init__.py +3 -0
- chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
- chrys/app/tui/widgets/chat/chrome.py +296 -0
- chrys/app/tui/widgets/chat/compaction_card.py +353 -0
- chrys/app/tui/widgets/chat/context_fold.py +77 -0
- chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
- chrys/app/tui/widgets/chat/image_preview.py +214 -0
- chrys/app/tui/widgets/chat/live_renderer.py +383 -0
- chrys/app/tui/widgets/chat/messages.py +1039 -0
- chrys/app/tui/widgets/chat/panel.py +1769 -0
- chrys/app/tui/widgets/chat/ports.py +245 -0
- chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
- chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
- chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
- chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
- chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
- chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
- chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
- chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
- chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
- chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
- chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
- chrys/app/tui/widgets/chat/renderers/search.py +348 -0
- chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
- chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
- chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
- chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
- chrys/app/tui/widgets/chat/renderers/web.py +69 -0
- chrys/app/tui/widgets/chat/replay.py +1605 -0
- chrys/app/tui/widgets/chat/replay_mount.py +322 -0
- chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
- chrys/app/tui/widgets/chat/selection_controller.py +435 -0
- chrys/app/tui/widgets/chat/session_json.py +508 -0
- chrys/app/tui/widgets/chat/toc_model.py +156 -0
- chrys/app/tui/widgets/chat/tool_call.py +2478 -0
- chrys/app/tui/widgets/chat/tool_registry.py +753 -0
- chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
- chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
- chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
- chrys/app/tui/widgets/checkbox.py +24 -0
- chrys/app/tui/widgets/chrome/__init__.py +3 -0
- chrys/app/tui/widgets/chrome/app_header.py +283 -0
- chrys/app/tui/widgets/chrome/commands.py +95 -0
- chrys/app/tui/widgets/chrome/file_index.py +68 -0
- chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
- chrys/app/tui/widgets/chrome/footer.py +284 -0
- chrys/app/tui/widgets/chrome/image_paste.py +351 -0
- chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
- chrys/app/tui/widgets/chrome/status_bar.py +911 -0
- chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
- chrys/app/tui/widgets/click_affordance.py +24 -0
- chrys/app/tui/widgets/color_picker/__init__.py +254 -0
- chrys/app/tui/widgets/color_picker/controls.py +171 -0
- chrys/app/tui/widgets/color_picker/model.py +89 -0
- chrys/app/tui/widgets/dialog_buttons.py +58 -0
- chrys/app/tui/widgets/dialog_scroll.py +24 -0
- chrys/app/tui/widgets/diff_view/__init__.py +20 -0
- chrys/app/tui/widgets/diff_view/cells.py +59 -0
- chrys/app/tui/widgets/diff_view/code.py +280 -0
- chrys/app/tui/widgets/diff_view/compute.py +286 -0
- chrys/app/tui/widgets/diff_view/gutter.py +161 -0
- chrys/app/tui/widgets/diff_view/inline.py +77 -0
- chrys/app/tui/widgets/diff_view/palette.py +87 -0
- chrys/app/tui/widgets/diff_view/protocols.py +24 -0
- chrys/app/tui/widgets/diff_view/rows.py +138 -0
- chrys/app/tui/widgets/diff_view/unified.py +267 -0
- chrys/app/tui/widgets/diff_view/widget.py +209 -0
- chrys/app/tui/widgets/editor/__init__.py +79 -0
- chrys/app/tui/widgets/editor/coordinates.py +30 -0
- chrys/app/tui/widgets/editor/highlighter.py +139 -0
- chrys/app/tui/widgets/editor/keymaps.py +357 -0
- chrys/app/tui/widgets/editor/types.py +216 -0
- chrys/app/tui/widgets/editor/vim.py +1195 -0
- chrys/app/tui/widgets/editor/widget.py +401 -0
- chrys/app/tui/widgets/file_preview.py +58 -0
- chrys/app/tui/widgets/hatch.py +138 -0
- chrys/app/tui/widgets/input.py +66 -0
- chrys/app/tui/widgets/loading.py +125 -0
- chrys/app/tui/widgets/markdown/__init__.py +25 -0
- chrys/app/tui/widgets/markdown/_utils.py +21 -0
- chrys/app/tui/widgets/markdown/anchors.py +71 -0
- chrys/app/tui/widgets/markdown/blocks.py +115 -0
- chrys/app/tui/widgets/markdown/copyable.py +163 -0
- chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
- chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
- chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
- chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
- chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
- chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
- chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
- chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
- chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
- chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
- chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
- chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
- chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
- chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
- chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
- chrys/app/tui/widgets/markdown/links.py +47 -0
- chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
- chrys/app/tui/widgets/markdown/math/layout.py +739 -0
- chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
- chrys/app/tui/widgets/markdown/math/parser.py +325 -0
- chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
- chrys/app/tui/widgets/markdown/navigator.py +80 -0
- chrys/app/tui/widgets/markdown/parser.py +1057 -0
- chrys/app/tui/widgets/markdown/stream.py +73 -0
- chrys/app/tui/widgets/markdown/toc.py +98 -0
- chrys/app/tui/widgets/markdown/viewer.py +146 -0
- chrys/app/tui/widgets/markdown/widget.py +2046 -0
- chrys/app/tui/widgets/marquee.py +101 -0
- chrys/app/tui/widgets/option_menu.py +82 -0
- chrys/app/tui/widgets/page_navigator.py +118 -0
- chrys/app/tui/widgets/select.py +196 -0
- chrys/app/tui/widgets/selection.py +59 -0
- chrys/app/tui/widgets/sidebar/__init__.py +3 -0
- chrys/app/tui/widgets/sidebar/buddy.py +367 -0
- chrys/app/tui/widgets/sidebar/context.py +482 -0
- chrys/app/tui/widgets/sidebar/debug.py +195 -0
- chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
- chrys/app/tui/widgets/sidebar/panel.py +198 -0
- chrys/app/tui/widgets/sidebar/tasks.py +198 -0
- chrys/app/tui/widgets/sidebar/toc.py +166 -0
- chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
- chrys/app/tui/widgets/syntax_theme.py +85 -0
- chrys/app/tui/widgets/text_area.py +258 -0
- chrys/app/tui/widgets/trajectory/__init__.py +8 -0
- chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
- chrys/app/tui/widgets/trajectory/insights.py +1266 -0
- chrys/app/tui/widgets/trajectory/overview.py +1101 -0
- chrys/app/tui/widgets/trajectory/panel.py +983 -0
- chrys/app/tui/widgets/trajectory/presentation.py +311 -0
- chrys/app/tui/widgets/trajectory/session_info.py +124 -0
- chrys/app/tui/widgets/trajectory/text_view.py +124 -0
- chrys/app/tui/widgets/trajectory/timeline.py +380 -0
- chrys/app/tui/widgets/welcome.py +126 -0
- chrys/app/tui/widgets/workflow/__init__.py +5 -0
- chrys/app/tui/widgets/workflow/edges.py +89 -0
- chrys/app/tui/widgets/workflow/graph.py +869 -0
- chrys/app/tui/widgets/workflow/info.py +310 -0
- chrys/app/tui/widgets/workflow/node_view.py +27 -0
- chrys/app/tui/widgets/workflow/output.py +225 -0
- chrys/app/tui/widgets/workflow/panel.py +658 -0
- chrys/app/tui/widgets/workflow/projector.py +324 -0
- chrys/app/tui/widgets/workflow/records.py +69 -0
- chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
- chrys/app/tui/widgets/workflow/selection.py +151 -0
- chrys/app/tui/widgets/workflow/source.py +65 -0
- chrys/app/tui/widgets/workflow/text.py +257 -0
- chrys/app/tui/widgets/workflow/values.py +317 -0
- chrys/foundation/__init__.py +0 -0
- chrys/foundation/branding.py +15 -0
- chrys/foundation/config/__init__.py +7 -0
- chrys/foundation/config/coercion.py +374 -0
- chrys/foundation/config/context.py +34 -0
- chrys/foundation/config/env_file.py +141 -0
- chrys/foundation/config/env_layers.py +370 -0
- chrys/foundation/config/migrations.py +521 -0
- chrys/foundation/config/notification_events.py +22 -0
- chrys/foundation/config/process_settings.py +332 -0
- chrys/foundation/config/runtime_pointer.py +133 -0
- chrys/foundation/config/settings.py +1952 -0
- chrys/foundation/config/settings_store.py +996 -0
- chrys/foundation/config/spec.py +396 -0
- chrys/foundation/config/user_settings.py +140 -0
- chrys/foundation/config/warnings.py +262 -0
- chrys/foundation/config/web_values.py +135 -0
- chrys/foundation/config/yaml_store.py +487 -0
- chrys/foundation/errors/__init__.py +68 -0
- chrys/foundation/errors/_legacy.py +117 -0
- chrys/foundation/errors/_text.py +13 -0
- chrys/foundation/errors/_walk.py +85 -0
- chrys/foundation/errors/classify.py +278 -0
- chrys/foundation/errors/display.py +274 -0
- chrys/foundation/errors/formatting.py +283 -0
- chrys/foundation/errors/kinds.py +67 -0
- chrys/foundation/errors/network.py +497 -0
- chrys/foundation/errors/provider.py +445 -0
- chrys/foundation/errors/route.py +85 -0
- chrys/foundation/events/__init__.py +8 -0
- chrys/foundation/events/bus.py +143 -0
- chrys/foundation/events/types.py +1629 -0
- chrys/foundation/events/workflow.py +38 -0
- chrys/foundation/hosted_tools.py +142 -0
- chrys/foundation/i18n/__init__.py +32 -0
- chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
- chrys/foundation/i18n/catalogs.py +42 -0
- chrys/foundation/i18n/formatting.py +202 -0
- chrys/foundation/i18n/locale.py +92 -0
- chrys/foundation/i18n/localizer.py +187 -0
- chrys/foundation/i18n/messages.py +268 -0
- chrys/foundation/io/__init__.py +3 -0
- chrys/foundation/io/result_content.py +237 -0
- chrys/foundation/models/__init__.py +3 -0
- chrys/foundation/models/approval_reuse.py +23 -0
- chrys/foundation/models/ask_user.py +468 -0
- chrys/foundation/models/execution.py +18 -0
- chrys/foundation/models/history_markers.py +115 -0
- chrys/foundation/models/invocations.py +63 -0
- chrys/foundation/models/mutation_scope.py +28 -0
- chrys/foundation/models/session_env.py +87 -0
- chrys/foundation/models/session_surface.py +29 -0
- chrys/foundation/models/todos.py +74 -0
- chrys/foundation/models/turns.py +125 -0
- chrys/foundation/models/workflow_session.py +89 -0
- chrys/foundation/models/workspace.py +59 -0
- chrys/foundation/net/__init__.py +3 -0
- chrys/foundation/net/address_class.py +57 -0
- chrys/foundation/net/route_probe.py +94 -0
- chrys/foundation/net/url.py +107 -0
- chrys/foundation/observability/__init__.py +3 -0
- chrys/foundation/observability/exporters.py +174 -0
- chrys/foundation/observability/gate.py +31 -0
- chrys/foundation/observability/setup.py +497 -0
- chrys/foundation/observability/sink.py +228 -0
- chrys/foundation/patches/__init__.py +19 -0
- chrys/foundation/patches/mdurl_cache.py +59 -0
- chrys/foundation/patches/patcher.py +287 -0
- chrys/foundation/patches/staged_members.py +205 -0
- chrys/foundation/patches/textual_block_border.py +105 -0
- chrys/foundation/patches/textual_button_ansi.py +65 -0
- chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
- chrys/foundation/patches/textual_callback_cache.py +248 -0
- chrys/foundation/patches/textual_callback_dispatch.py +109 -0
- chrys/foundation/patches/textual_compositor_cjk.py +566 -0
- chrys/foundation/patches/textual_dispatch_cache.py +150 -0
- chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
- chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
- chrys/foundation/patches/textual_lru_acyclic.py +241 -0
- chrys/foundation/patches/textual_message_pump.py +136 -0
- chrys/foundation/patches/textual_node_diet.py +875 -0
- chrys/foundation/patches/textual_one_shot_timer.py +48 -0
- chrys/foundation/patches/textual_option_list.py +90 -0
- chrys/foundation/patches/textual_precompose.py +89 -0
- chrys/foundation/patches/textual_pruned_tabs.py +69 -0
- chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
- chrys/foundation/patches/textual_removed_node_caches.py +86 -0
- chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
- chrys/foundation/patches/textual_selection_extract.py +48 -0
- chrys/foundation/patches/textual_strip_cycles.py +119 -0
- chrys/foundation/patches/textual_tab_selection.py +1611 -0
- chrys/foundation/patches/textual_timer_skip.py +70 -0
- chrys/foundation/patches/textual_utf8_decoder.py +106 -0
- chrys/foundation/patches/textual_win_sleep.py +167 -0
- chrys/foundation/patches/watchdog_fsevents.py +70 -0
- chrys/foundation/patches/watchdog_windows.py +52 -0
- chrys/foundation/platform/__init__.py +556 -0
- chrys/foundation/platform/c_api.py +32 -0
- chrys/foundation/platform/child_reap.py +107 -0
- chrys/foundation/platform/command_line.py +95 -0
- chrys/foundation/platform/files.py +1470 -0
- chrys/foundation/platform/output_capture.py +113 -0
- chrys/foundation/platform/paths.py +193 -0
- chrys/foundation/platform/process.py +1468 -0
- chrys/foundation/platform/pty_output.py +19 -0
- chrys/foundation/platform/runtime_paths.py +281 -0
- chrys/foundation/platform/win_console.py +129 -0
- chrys/foundation/reasoning_origin.py +67 -0
- chrys/foundation/recovery.py +16 -0
- chrys/foundation/retry.py +278 -0
- chrys/foundation/text/__init__.py +3 -0
- chrys/foundation/text/encoding.py +603 -0
- chrys/foundation/text/images.py +304 -0
- chrys/foundation/text/lines.py +24 -0
- chrys/foundation/text/mentions.py +90 -0
- chrys/foundation/text/model_json.py +25 -0
- chrys/foundation/text/reading_order.py +20 -0
- chrys/foundation/text/tokenizer.py +25 -0
- chrys/foundation/text/tool_output.py +287 -0
- chrys/foundation/text/yaml_io.py +62 -0
- chrys/foundation/tool_call_context.py +202 -0
- chrys/foundation/tool_execution_stamp.py +184 -0
- chrys/foundation/tool_invocation_order.py +48 -0
- chrys/foundation/tool_kinds.py +102 -0
- chrys/foundation/tool_result_metadata.py +287 -0
- chrys/foundation/trajectory/__init__.py +10 -0
- chrys/foundation/trajectory/context.py +481 -0
- chrys/foundation/trajectory/envelope.py +571 -0
- chrys/foundation/trajectory/event_types.py +294 -0
- chrys/foundation/trajectory/finalizers.py +46 -0
- chrys/foundation/trajectory/fingerprint.py +62 -0
- chrys/foundation/trajectory/ids.py +155 -0
- chrys/foundation/trajectory/keys.py +99 -0
- chrys/foundation/trajectory/lease.py +173 -0
- chrys/foundation/trajectory/metadata.py +72 -0
- chrys/foundation/trajectory/reader.py +271 -0
- chrys/foundation/trajectory/recovery.py +225 -0
- chrys/foundation/trajectory/revisions.py +152 -0
- chrys/foundation/trajectory/segments.py +225 -0
- chrys/foundation/trajectory/tools.py +117 -0
- chrys/foundation/trajectory/usage.py +151 -0
- chrys/foundation/trajectory/writer.py +972 -0
- chrys/foundation/trajectory_timing.py +97 -0
- chrys/foundation/util/__init__.py +3 -0
- chrys/foundation/util/async_tasks.py +29 -0
- chrys/foundation/util/chrys_headers.py +23 -0
- chrys/foundation/util/env_templates.py +104 -0
- chrys/foundation/util/filenames.py +96 -0
- chrys/foundation/util/header_charset.py +138 -0
- chrys/foundation/util/httpx_helpers.py +12 -0
- chrys/foundation/util/lock.py +236 -0
- chrys/foundation/util/once_close.py +46 -0
- chrys/foundation/util/session_ids.py +29 -0
- chrys/foundation/util/shell_tokens.py +88 -0
- chrys/foundation/util/sub_agent_context.py +38 -0
- chrys/foundation/util/time.py +35 -0
- chrys/foundation/util/unicode_scalars.py +36 -0
- chrys/foundation/vendor/__init__.py +43 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
- chrys/kernel/__init__.py +256 -0
- chrys/kernel/_content.py +1866 -0
- chrys/kernel/_loop_recorder.py +631 -0
- chrys/kernel/_null_overlay.py +1026 -0
- chrys/kernel/_result_ceiling.py +216 -0
- chrys/kernel/_serialization.py +431 -0
- chrys/kernel/_tool_arg_errors.py +1629 -0
- chrys/kernel/_tool_execution.py +1172 -0
- chrys/kernel/_tool_expansion.py +26 -0
- chrys/kernel/_types.py +2598 -0
- chrys/kernel/agent.py +994 -0
- chrys/kernel/client.py +1084 -0
- chrys/kernel/compaction.py +1315 -0
- chrys/kernel/exceptions.py +211 -0
- chrys/kernel/exchanges.py +683 -0
- chrys/kernel/identity.py +95 -0
- chrys/kernel/images.py +36 -0
- chrys/kernel/instrumentation.py +1618 -0
- chrys/kernel/loop.py +2308 -0
- chrys/kernel/middleware.py +616 -0
- chrys/kernel/sessions.py +604 -0
- chrys/kernel/tools.py +1061 -0
- chrys/kernel/types.py +126 -0
- chrys/orchestration/__init__.py +3 -0
- chrys/orchestration/engine/__init__.py +3 -0
- chrys/orchestration/engine/assembly.py +236 -0
- chrys/orchestration/engine/build/__init__.py +3 -0
- chrys/orchestration/engine/build/builder.py +1061 -0
- chrys/orchestration/engine/build/construction.py +408 -0
- chrys/orchestration/engine/build/loaded.py +139 -0
- chrys/orchestration/engine/engine.py +789 -0
- chrys/orchestration/engine/execution.py +771 -0
- chrys/orchestration/engine/loader.py +865 -0
- chrys/orchestration/engine/rollback.py +918 -0
- chrys/orchestration/engine/run/__init__.py +3 -0
- chrys/orchestration/engine/run/active_injection.py +419 -0
- chrys/orchestration/engine/run/attachments.py +519 -0
- chrys/orchestration/engine/run/bindings.py +1095 -0
- chrys/orchestration/engine/run/coordinator.py +1062 -0
- chrys/orchestration/engine/run/finalizer.py +417 -0
- chrys/orchestration/engine/run/input_refs.py +60 -0
- chrys/orchestration/engine/run/prompt_content.py +457 -0
- chrys/orchestration/engine/run/resume.py +299 -0
- chrys/orchestration/engine/run/retry.py +948 -0
- chrys/orchestration/engine/run/runner.py +625 -0
- chrys/orchestration/engine/run/runtime_skills.py +146 -0
- chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
- chrys/orchestration/engine/run/turn_hooks.py +258 -0
- chrys/orchestration/engine/run/turn_state.py +149 -0
- chrys/orchestration/engine/run/working_dir.py +50 -0
- chrys/orchestration/engine/session_lifecycle.py +2013 -0
- chrys/orchestration/engine/state/__init__.py +3 -0
- chrys/orchestration/engine/state/active_session.py +183 -0
- chrys/orchestration/engine/state/controls.py +639 -0
- chrys/orchestration/engine/state/current_agent.py +26 -0
- chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
- chrys/orchestration/engine/state/machine.py +193 -0
- chrys/orchestration/engine/state/session_writer.py +434 -0
- chrys/orchestration/engine/trajectory.py +714 -0
- chrys/orchestration/engine/usage.py +118 -0
- chrys/orchestration/invoker/__init__.py +3 -0
- chrys/orchestration/invoker/acp.py +727 -0
- chrys/orchestration/invoker/acp_protocol.py +1115 -0
- chrys/orchestration/invoker/acp_spec.py +79 -0
- chrys/orchestration/invoker/attempts.py +994 -0
- chrys/orchestration/invoker/child_compaction.py +131 -0
- chrys/orchestration/invoker/child_history.py +99 -0
- chrys/orchestration/invoker/contracts.py +253 -0
- chrys/orchestration/invoker/evidence.py +94 -0
- chrys/orchestration/invoker/kernel.py +343 -0
- chrys/orchestration/invoker/origin.py +79 -0
- chrys/orchestration/invoker/resources.py +343 -0
- chrys/orchestration/invoker/runtime.py +299 -0
- chrys/orchestration/invoker/usage.py +80 -0
- chrys/orchestration/session_hooks.py +117 -0
- chrys/orchestration/session_host.py +1023 -0
- chrys/orchestration/session_resources.py +95 -0
- chrys/orchestration/session_usage.py +219 -0
- chrys/orchestration/startup.py +255 -0
- chrys/orchestration/sub_agents/__init__.py +3 -0
- chrys/orchestration/sub_agents/acp_policy.py +269 -0
- chrys/orchestration/sub_agents/kernel_policy.py +848 -0
- chrys/orchestration/sub_agents/registration.py +44 -0
- chrys/orchestration/sub_agents/shell.py +337 -0
- chrys/orchestration/sub_agents/tools.py +2228 -0
- chrys/orchestration/workflows/__init__.py +3 -0
- chrys/orchestration/workflows/agent_archive.py +156 -0
- chrys/orchestration/workflows/agent_node.py +894 -0
- chrys/orchestration/workflows/agent_node_build.py +687 -0
- chrys/orchestration/workflows/catalog.py +332 -0
- chrys/orchestration/workflows/coordinator.py +771 -0
- chrys/orchestration/workflows/hooks.py +176 -0
- chrys/orchestration/workflows/preview.py +322 -0
- chrys/orchestration/workflows/rollback.py +151 -0
- chrys/orchestration/workflows/runner.py +986 -0
- chrys/orchestration/workflows/session.py +390 -0
- chrys/orchestration/workflows/settings.py +97 -0
- chrys/orchestration/workflows/validation.py +674 -0
- chrys/orchestration/workflows/worker_client.py +1002 -0
- chrys/service/__init__.py +3 -0
- chrys/service/acp_client/__init__.py +65 -0
- chrys/service/acp_client/_transport.py +922 -0
- chrys/service/acp_client/client.py +1311 -0
- chrys/service/acp_client/errors.py +224 -0
- chrys/service/acp_client/protocol.py +220 -0
- chrys/service/acp_client/spawn.py +239 -0
- chrys/service/acp_client/spec.py +100 -0
- chrys/service/agent_middleware/__init__.py +60 -0
- chrys/service/agent_middleware/_metadata_keys.py +52 -0
- chrys/service/agent_middleware/control/__init__.py +3 -0
- chrys/service/agent_middleware/control/approval.py +769 -0
- chrys/service/agent_middleware/control/approval_hooks.py +62 -0
- chrys/service/agent_middleware/control/ask_user.py +207 -0
- chrys/service/agent_middleware/control/interrupt.py +50 -0
- chrys/service/agent_middleware/control/sleep.py +118 -0
- chrys/service/agent_middleware/control/todo.py +117 -0
- chrys/service/agent_middleware/events/__init__.py +3 -0
- chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
- chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
- chrys/service/agent_middleware/events/intermediate_text.py +128 -0
- chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
- chrys/service/agent_middleware/events/result_persistence.py +98 -0
- chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
- chrys/service/agent_middleware/events/tool_error_text.py +22 -0
- chrys/service/agent_middleware/events/tool_events.py +779 -0
- chrys/service/agent_middleware/injection.py +510 -0
- chrys/service/agent_middleware/reminders/__init__.py +91 -0
- chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
- chrys/service/agent_middleware/reminders/base.py +25 -0
- chrys/service/agent_middleware/reminders/context_usage.py +74 -0
- chrys/service/agent_middleware/reminders/file_change.py +63 -0
- chrys/service/agent_middleware/reminders/mcp.py +39 -0
- chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
- chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
- chrys/service/agent_middleware/reminders/skills.py +44 -0
- chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
- chrys/service/agent_middleware/reminders/todo.py +35 -0
- chrys/service/agent_middleware/reminders/turn_line.py +48 -0
- chrys/service/agent_middleware/response_validation.py +1380 -0
- chrys/service/agent_middleware/system_reminder.py +1107 -0
- chrys/service/agent_middleware/validators.py +396 -0
- chrys/service/analytics/__init__.py +124 -0
- chrys/service/analytics/_actions.py +896 -0
- chrys/service/analytics/_context_evidence.py +357 -0
- chrys/service/analytics/_critical_path.py +179 -0
- chrys/service/analytics/_facts.py +919 -0
- chrys/service/analytics/_insights.py +769 -0
- chrys/service/analytics/_metric_ops.py +83 -0
- chrys/service/analytics/_session_projection.py +460 -0
- chrys/service/analytics/_timeline.py +434 -0
- chrys/service/analytics/_turn_graph.py +1237 -0
- chrys/service/analytics/_turns.py +1377 -0
- chrys/service/analytics/_workflows.py +171 -0
- chrys/service/analytics/aggregation.py +1474 -0
- chrys/service/analytics/classification.py +128 -0
- chrys/service/analytics/export.py +803 -0
- chrys/service/analytics/findings.py +282 -0
- chrys/service/analytics/math.py +79 -0
- chrys/service/analytics/model.py +886 -0
- chrys/service/analytics/reader.py +321 -0
- chrys/service/approval/__init__.py +3 -0
- chrys/service/approval/arbitration.py +102 -0
- chrys/service/approval/command_identity.py +109 -0
- chrys/service/approval/correlation.py +57 -0
- chrys/service/approval/grant_store.py +108 -0
- chrys/service/approval/judge.py +589 -0
- chrys/service/approval/level.py +15 -0
- chrys/service/approval/policy.py +209 -0
- chrys/service/approval/reuse.py +223 -0
- chrys/service/approval/reuse_binding.py +133 -0
- chrys/service/approval/safety_classifier.py +536 -0
- chrys/service/approval/turn_context.py +49 -0
- chrys/service/context/__init__.py +3 -0
- chrys/service/context/compaction/__init__.py +38 -0
- chrys/service/context/compaction/breaker.py +156 -0
- chrys/service/context/compaction/budgets.py +93 -0
- chrys/service/context/compaction/calibration.py +77 -0
- chrys/service/context/compaction/compression.py +690 -0
- chrys/service/context/compaction/current_turn_drop.py +458 -0
- chrys/service/context/compaction/events.py +201 -0
- chrys/service/context/compaction/exclusions.py +261 -0
- chrys/service/context/compaction/groups.py +361 -0
- chrys/service/context/compaction/last_words.py +1996 -0
- chrys/service/context/compaction/last_words_state.py +752 -0
- chrys/service/context/compaction/scoped.py +472 -0
- chrys/service/context/compaction/spill.py +1947 -0
- chrys/service/context/compaction/strategy.py +1116 -0
- chrys/service/context/compaction/summaries.py +319 -0
- chrys/service/context/compaction/turns.py +375 -0
- chrys/service/context/manager.py +174 -0
- chrys/service/context/memory_loader.py +792 -0
- chrys/service/context/middleware/__init__.py +3 -0
- chrys/service/context/middleware/usage.py +332 -0
- chrys/service/context/providers/__init__.py +3 -0
- chrys/service/context/providers/context_management.py +432 -0
- chrys/service/context/providers/history.py +647 -0
- chrys/service/context/providers/memory.py +50 -0
- chrys/service/context/tool_content.py +22 -0
- chrys/service/hooks/__init__.py +37 -0
- chrys/service/hooks/detached_worker.py +118 -0
- chrys/service/hooks/docs/README.md +14 -0
- chrys/service/hooks/docs/authoring.md +684 -0
- chrys/service/hooks/docs/configuration.md +499 -0
- chrys/service/hooks/docs/design.md +359 -0
- chrys/service/hooks/docs/project-hooks.md +348 -0
- chrys/service/hooks/events.py +157 -0
- chrys/service/hooks/loader.py +665 -0
- chrys/service/hooks/manager.py +807 -0
- chrys/service/hooks/matcher.py +111 -0
- chrys/service/hooks/outbox.py +294 -0
- chrys/service/hooks/runner.py +572 -0
- chrys/service/hooks/schema.py +407 -0
- chrys/service/llm/__init__.py +7 -0
- chrys/service/llm/_structured_outputs.py +192 -0
- chrys/service/llm/anthropic_messages/__init__.py +9 -0
- chrys/service/llm/anthropic_messages/client.py +254 -0
- chrys/service/llm/anthropic_messages/decode.py +331 -0
- chrys/service/llm/anthropic_messages/history.py +279 -0
- chrys/service/llm/anthropic_messages/request.py +323 -0
- chrys/service/llm/anthropic_messages/server_tools.py +319 -0
- chrys/service/llm/anthropic_messages/stream.py +297 -0
- chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
- chrys/service/llm/chat_completions/__init__.py +9 -0
- chrys/service/llm/chat_completions/client.py +369 -0
- chrys/service/llm/chat_completions/decode.py +338 -0
- chrys/service/llm/chat_completions/history.py +657 -0
- chrys/service/llm/chat_completions/reasoning.py +244 -0
- chrys/service/llm/chat_completions/request.py +192 -0
- chrys/service/llm/chat_completions/stream.py +546 -0
- chrys/service/llm/chat_completions/validation.py +308 -0
- chrys/service/llm/clients.py +613 -0
- chrys/service/llm/images.py +65 -0
- chrys/service/llm/mock.py +413 -0
- chrys/service/llm/observer.py +446 -0
- chrys/service/llm/one_shot.py +106 -0
- chrys/service/llm/openai_exceptions.py +93 -0
- chrys/service/llm/openai_responses/__init__.py +10 -0
- chrys/service/llm/openai_responses/client.py +323 -0
- chrys/service/llm/openai_responses/decode.py +432 -0
- chrys/service/llm/openai_responses/history.py +519 -0
- chrys/service/llm/openai_responses/hosted.py +625 -0
- chrys/service/llm/openai_responses/replay.py +629 -0
- chrys/service/llm/openai_responses/request.py +342 -0
- chrys/service/llm/openai_responses/stream.py +847 -0
- chrys/service/llm/openai_timestamps.py +57 -0
- chrys/service/llm/providers.py +107 -0
- chrys/service/llm/proxy_route.py +54 -0
- chrys/service/llm/raw_http_log.py +309 -0
- chrys/service/llm/route_facts.py +63 -0
- chrys/service/llm/route_sessions.py +49 -0
- chrys/service/llm/sdk_anthropic.py +18 -0
- chrys/service/llm/sdk_headers.py +47 -0
- chrys/service/llm/sdk_openai.py +18 -0
- chrys/service/llm/sdk_retry.py +59 -0
- chrys/service/llm/wire_client.py +246 -0
- chrys/service/mcp/__init__.py +3 -0
- chrys/service/mcp/_connection.py +235 -0
- chrys/service/mcp/_http_transport.py +350 -0
- chrys/service/mcp/_progressive.py +459 -0
- chrys/service/mcp/_stdio_transport.py +614 -0
- chrys/service/mcp/_tool_mixins.py +96 -0
- chrys/service/mcp/adapter.py +1031 -0
- chrys/service/mcp/cache.py +476 -0
- chrys/service/mcp/content.py +32 -0
- chrys/service/mcp/errors.py +169 -0
- chrys/service/mcp/owned.py +1330 -0
- chrys/service/mcp/result_limits.py +168 -0
- chrys/service/mcp/thinking_warning.py +95 -0
- chrys/service/mcp/validation.py +74 -0
- chrys/service/mutations/__init__.py +84 -0
- chrys/service/mutations/coordination.py +894 -0
- chrys/service/mutations/detector.py +458 -0
- chrys/service/mutations/git_calibrator.py +397 -0
- chrys/service/mutations/git_state.py +647 -0
- chrys/service/mutations/pipeline.py +779 -0
- chrys/service/mutations/scanner.py +466 -0
- chrys/service/mutations/snapshot_files.py +233 -0
- chrys/service/mutations/store.py +625 -0
- chrys/service/mutations/tool_names.py +15 -0
- chrys/service/mutations/trace.py +246 -0
- chrys/service/mutations/tracker.py +1289 -0
- chrys/service/mutations/types.py +559 -0
- chrys/service/mutations/workspace_changes.py +1583 -0
- chrys/service/profiles/__init__.py +2 -0
- chrys/service/profiles/agents/__init__.py +3 -0
- chrys/service/profiles/agents/builtins/Code.yaml +199 -0
- chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
- chrys/service/profiles/agents/builtins/General.yaml +114 -0
- chrys/service/profiles/agents/builtins/QA.yaml +128 -0
- chrys/service/profiles/agents/loader.py +808 -0
- chrys/service/profiles/agents/registry.py +727 -0
- chrys/service/profiles/agents/schema.py +371 -0
- chrys/service/profiles/agents/serializer.py +196 -0
- chrys/service/profiles/coercion.py +22 -0
- chrys/service/profiles/models/__init__.py +3 -0
- chrys/service/profiles/models/env_bridge.py +157 -0
- chrys/service/profiles/models/loader.py +282 -0
- chrys/service/profiles/models/options.py +438 -0
- chrys/service/profiles/models/registry.py +104 -0
- chrys/service/profiles/models/resolver.py +286 -0
- chrys/service/profiles/models/schema.py +91 -0
- chrys/service/profiles/models/serializer.py +76 -0
- chrys/service/session/__init__.py +3 -0
- chrys/service/session/checkpoint.py +156 -0
- chrys/service/session/history.py +1616 -0
- chrys/service/session/message_metadata.py +151 -0
- chrys/service/session/persistence.py +435 -0
- chrys/service/session/runtime_metadata.py +210 -0
- chrys/service/session/sub_agent_logs.py +1069 -0
- chrys/service/session/sub_agent_transcript.py +654 -0
- chrys/service/skills/__init__.py +3 -0
- chrys/service/skills/adapter.py +408 -0
- chrys/service/skills/constants.py +8 -0
- chrys/service/skills/loader.py +575 -0
- chrys/service/skills/model.py +130 -0
- chrys/service/skills/provider.py +841 -0
- chrys/service/skills/runner.py +390 -0
- chrys/service/state/__init__.py +3 -0
- chrys/service/state/_fork.py +710 -0
- chrys/service/state/_session_files.py +175 -0
- chrys/service/state/_session_meta.py +552 -0
- chrys/service/state/locks.py +154 -0
- chrys/service/state/serializers.py +156 -0
- chrys/service/state/session_catalog.py +361 -0
- chrys/service/state/session_listing.py +84 -0
- chrys/service/state/session_migration.py +446 -0
- chrys/service/state/session_mru.py +315 -0
- chrys/service/state/store.py +2132 -0
- chrys/service/state/workflow.py +158 -0
- chrys/service/text_blocks.py +47 -0
- chrys/service/todos/__init__.py +3 -0
- chrys/service/todos/reminder.py +31 -0
- chrys/service/todos/tracker.py +58 -0
- chrys/service/tools/__init__.py +3 -0
- chrys/service/tools/approval_targets.py +92 -0
- chrys/service/tools/builtins/__init__.py +3 -0
- chrys/service/tools/builtins/ask_user.py +326 -0
- chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
- chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
- chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
- chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
- chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
- chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
- chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
- chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
- chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
- chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
- chrys/service/tools/builtins/doc_converter/registry.py +55 -0
- chrys/service/tools/builtins/filesystem.py +1071 -0
- chrys/service/tools/builtins/search.py +1265 -0
- chrys/service/tools/builtins/shell.py +967 -0
- chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
- chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
- chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
- chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
- chrys/service/tools/builtins/shell_filter.py +24 -0
- chrys/service/tools/builtins/sleep.py +91 -0
- chrys/service/tools/builtins/todo.py +64 -0
- chrys/service/tools/builtins/web/__init__.py +6 -0
- chrys/service/tools/builtins/web/build.py +268 -0
- chrys/service/tools/builtins/web/config.py +338 -0
- chrys/service/tools/builtins/web/credentials.py +37 -0
- chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
- chrys/service/tools/builtins/web/fetch/cache.py +68 -0
- chrys/service/tools/builtins/web/fetch/convert.py +56 -0
- chrys/service/tools/builtins/web/fetch/tool.py +223 -0
- chrys/service/tools/builtins/web/hosted.py +61 -0
- chrys/service/tools/builtins/web/http.py +330 -0
- chrys/service/tools/builtins/web/progress.py +18 -0
- chrys/service/tools/builtins/web/search/__init__.py +3 -0
- chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
- chrys/service/tools/builtins/web/search/html.py +152 -0
- chrys/service/tools/builtins/web/search/json_api.py +227 -0
- chrys/service/tools/builtins/web/search/providers.py +77 -0
- chrys/service/tools/builtins/web/search/tool.py +218 -0
- chrys/service/tools/builtins/web/search/types.py +28 -0
- chrys/service/tools/builtins/web/tls_identity.py +66 -0
- chrys/service/tools/kinds.py +115 -0
- chrys/service/tools/names.py +47 -0
- chrys/service/tools/registry.py +242 -0
- chrys/service/tools/result_metadata.py +103 -0
- chrys/service/tools/session_artifacts.py +196 -0
- chrys/service/tools/spill.py +218 -0
- chrys/service/tools/workspace_paths.py +81 -0
- chrys/service/trajectory/__init__.py +11 -0
- chrys/service/trajectory/approvals.py +201 -0
- chrys/service/trajectory/compaction.py +420 -0
- chrys/service/trajectory/fork.py +75 -0
- chrys/service/trajectory/hooks.py +217 -0
- chrys/service/trajectory/items.py +41 -0
- chrys/service/trajectory/preparation.py +374 -0
- chrys/service/trajectory/retries.py +95 -0
- chrys/service/trajectory/revisions.py +129 -0
- chrys/service/trajectory/segmented.py +163 -0
- chrys/service/trajectory/session.py +762 -0
- chrys/service/trajectory/state.py +83 -0
- chrys/service/trajectory/sub_agents.py +169 -0
- chrys/service/trajectory/tombstone.py +262 -0
- chrys/service/trajectory/tools.py +323 -0
- chrys/service/trajectory/validation.py +130 -0
- chrys/service/trajectory/waits.py +161 -0
- chrys/service/trajectory/workflow.py +148 -0
- chrys/service/vision.py +128 -0
- chrys/service/workflows/__init__.py +3 -0
- chrys/service/workflows/admission.py +151 -0
- chrys/service/workflows/artifacts.py +109 -0
- chrys/service/workflows/asks.py +130 -0
- chrys/service/workflows/builtins/__init__.py +12 -0
- chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
- chrys/service/workflows/builtins/demo-workflow.py +756 -0
- chrys/service/workflows/discovery.py +554 -0
- chrys/service/workflows/environment.py +284 -0
- chrys/service/workflows/graph.py +430 -0
- chrys/service/workflows/history.py +82 -0
- chrys/service/workflows/interpreter.py +107 -0
- chrys/service/workflows/journal.py +334 -0
- chrys/service/workflows/layout.py +56 -0
- chrys/service/workflows/ledger.py +149 -0
- chrys/service/workflows/model_selection.py +18 -0
- chrys/service/workflows/orphans.py +209 -0
- chrys/service/workflows/outcomes.py +46 -0
- chrys/service/workflows/protocol.py +210 -0
- chrys/service/workflows/records.py +142 -0
- chrys/service/workflows/scheduler.py +770 -0
- chrys/service/workflows/sdk/__init__.py +51 -0
- chrys/service/workflows/sdk/_ask.py +167 -0
- chrys/service/workflows/sdk/_builder.py +781 -0
- chrys/service/workflows/sdk/_values.py +92 -0
- chrys/service/workflows/sdk_artifact.py +74 -0
- chrys/service/workflows/store.py +494 -0
- chrys/service/workflows/transcript.py +102 -0
- chrys/service/workflows/values.py +73 -0
- chrys/service/workflows/worker_host.py +1820 -0
- chrys/workflows.py +38 -0
- icode_tui-0.29.1.dist-info/METADATA +104 -0
- icode_tui-0.29.1.dist-info/RECORD +1138 -0
- icode_tui-0.29.1.dist-info/WHEEL +4 -0
- icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
- icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
- icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""One workflow run: the pure scheduler's decisions executed against a worker, agent shells, and the journal.
|
|
4
|
+
|
|
5
|
+
The scheduler decides, the runner acts. Every scheduler input goes through
|
|
6
|
+
one lock, and the decisions it returns are executed, journal writes
|
|
7
|
+
included, before the next input is taken: the run log's sequence order is
|
|
8
|
+
the scheduler's decision order. Node bodies, evaluations, and backoffs run
|
|
9
|
+
as tasks outside that lock and feed their results back through it.
|
|
10
|
+
|
|
11
|
+
Faults converge the same way user actions do: a lost worker or a failed
|
|
12
|
+
store becomes a scheduler input whose terminal decisions cancel what is
|
|
13
|
+
still running. The runner then drains its tasks, closes every agent shell,
|
|
14
|
+
waits for the worker to exit, checkpoints the session, and records the terminal
|
|
15
|
+
last. A failed checkpoint makes the run ``storage_failed`` even if every node
|
|
16
|
+
completed successfully.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
import contextlib
|
|
23
|
+
import logging
|
|
24
|
+
import time
|
|
25
|
+
from collections.abc import Awaitable, Callable
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from typing import TYPE_CHECKING, Any, Final
|
|
28
|
+
|
|
29
|
+
from chrys.foundation.events.types import (
|
|
30
|
+
WORKFLOW_NOTICE_DATA_DROPPED,
|
|
31
|
+
WORKFLOW_OUTPUT_EMIT,
|
|
32
|
+
WORKFLOW_OUTPUT_FINAL,
|
|
33
|
+
WorkflowOutputSummary,
|
|
34
|
+
)
|
|
35
|
+
from chrys.foundation.models.ask_user import AskUserAnswer, AskUserQuestion, validate_ask_user_answers
|
|
36
|
+
from chrys.foundation.trajectory.ids import new_analytics_id
|
|
37
|
+
from chrys.foundation.util.once_close import finish_close
|
|
38
|
+
from chrys.orchestration.invoker.contracts import AbortCause
|
|
39
|
+
from chrys.orchestration.workflows.agent_archive import AgentNodeArchive
|
|
40
|
+
from chrys.orchestration.workflows.agent_node import WorkflowAgentShell
|
|
41
|
+
from chrys.orchestration.workflows.agent_node_build import AgentNodeResources
|
|
42
|
+
from chrys.orchestration.workflows.worker_client import (
|
|
43
|
+
AskUnavailable,
|
|
44
|
+
AttemptTimeout,
|
|
45
|
+
CapturedOutput,
|
|
46
|
+
WorkerLostError,
|
|
47
|
+
WorkerRpcError,
|
|
48
|
+
WorkflowWorkerClient,
|
|
49
|
+
)
|
|
50
|
+
from chrys.service.trajectory.workflow import WorkflowTrace
|
|
51
|
+
from chrys.service.workflows.admission import AdmittedManifest
|
|
52
|
+
from chrys.service.workflows.graph import KIND_AGENT, KIND_JOIN, KIND_PYTHON, ON_EXHAUSTED_CONTINUE
|
|
53
|
+
from chrys.service.workflows.journal import WorkflowJournal, summarize
|
|
54
|
+
from chrys.service.workflows.outcomes import (
|
|
55
|
+
REASON_DEADLINE_EXCEEDED,
|
|
56
|
+
REASON_INTERNAL_ERROR,
|
|
57
|
+
REASON_SHUTDOWN,
|
|
58
|
+
RunOutcome,
|
|
59
|
+
)
|
|
60
|
+
from chrys.service.workflows.protocol import ErrorCode, ProtocolError
|
|
61
|
+
from chrys.service.workflows.scheduler import (
|
|
62
|
+
Activate,
|
|
63
|
+
ActivationState,
|
|
64
|
+
AttemptRef,
|
|
65
|
+
CancelActivation,
|
|
66
|
+
Decision,
|
|
67
|
+
ErrorClass,
|
|
68
|
+
EvaluateLoopUntil,
|
|
69
|
+
EvaluateOutgoing,
|
|
70
|
+
FailureReport,
|
|
71
|
+
LoopActivated,
|
|
72
|
+
LoopIteration,
|
|
73
|
+
LoopVerdict,
|
|
74
|
+
NodeStateChanged,
|
|
75
|
+
PersistRetryKey,
|
|
76
|
+
RetryRejected,
|
|
77
|
+
RunFinished,
|
|
78
|
+
RunMode,
|
|
79
|
+
RunOutput,
|
|
80
|
+
ScheduleRetry,
|
|
81
|
+
WorkflowScheduler,
|
|
82
|
+
)
|
|
83
|
+
from chrys.service.workflows.sdk import SourceValue, WorkflowValue
|
|
84
|
+
from chrys.service.workflows.store import (
|
|
85
|
+
DATA_DROPPED_KEY,
|
|
86
|
+
NODE_RECORD_INPUT,
|
|
87
|
+
NODE_RECORD_OUTPUT,
|
|
88
|
+
WorkflowStorageFailed,
|
|
89
|
+
)
|
|
90
|
+
from chrys.service.workflows.values import ValueShapeError, source_to_wire, value_to_wire
|
|
91
|
+
|
|
92
|
+
if TYPE_CHECKING:
|
|
93
|
+
from chrys.service.approval.policy import ApprovalMode
|
|
94
|
+
|
|
95
|
+
logger = logging.getLogger(__name__)
|
|
96
|
+
|
|
97
|
+
MAX_CONCURRENT_AGENT_ATTEMPTS: Final = 4
|
|
98
|
+
"""Concurrent agent attempts per run, including shell setup and execution but excluding retry backoff."""
|
|
99
|
+
|
|
100
|
+
_CANCEL_CAUSES: Final = {
|
|
101
|
+
REASON_DEADLINE_EXCEEDED: AbortCause.CALLER_TIMEOUT,
|
|
102
|
+
REASON_SHUTDOWN: AbortCause.OWNER_CLOSE,
|
|
103
|
+
REASON_INTERNAL_ERROR: AbortCause.OWNER_CLOSE,
|
|
104
|
+
}
|
|
105
|
+
_DURABLE_STATES: Final = frozenset(
|
|
106
|
+
{ActivationState.COMPLETED, ActivationState.FAILED, ActivationState.SKIPPED, ActivationState.CANCELLED}
|
|
107
|
+
)
|
|
108
|
+
_PYTHON_FAILURE_CLASSES: Final = {
|
|
109
|
+
ErrorCode.USER_EXCEPTION: ErrorClass.PYTHON_EXCEPTION,
|
|
110
|
+
ErrorCode.VALUE_TOO_LARGE: ErrorClass.VALUE_TOO_LARGE,
|
|
111
|
+
ErrorCode.PROTOCOL_LIMIT: ErrorClass.PROTOCOL_LIMIT,
|
|
112
|
+
ErrorCode.VALUE_NOT_SERIALIZABLE: ErrorClass.VALUE_NOT_SERIALIZABLE,
|
|
113
|
+
ErrorCode.ASK_UNAVAILABLE: ErrorClass.ASK_UNAVAILABLE,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True, slots=True)
|
|
118
|
+
class WorkflowRunResult:
|
|
119
|
+
"""How a run ended; ``outputs`` follows the workflow's ``output()`` declaration order."""
|
|
120
|
+
|
|
121
|
+
run_id: str
|
|
122
|
+
outcome: RunOutcome
|
|
123
|
+
outputs: tuple[RunOutput, ...] = ()
|
|
124
|
+
node_id: str = ""
|
|
125
|
+
error: str = ""
|
|
126
|
+
reason: str = ""
|
|
127
|
+
duration: float = 0.0 # seconds
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class WorkerCallbacks:
|
|
131
|
+
"""The ask/emit handlers a worker is launched with, bound to the runner once it exists.
|
|
132
|
+
|
|
133
|
+
The worker is launched (and the workflow file loaded) before the runner
|
|
134
|
+
is built; no node body runs until the runner starts the scheduler, so
|
|
135
|
+
nothing reaches these handlers unbound.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(self) -> None:
|
|
139
|
+
self._runner: WorkflowRunner | None = None
|
|
140
|
+
|
|
141
|
+
def bind(self, runner: WorkflowRunner) -> None:
|
|
142
|
+
self._runner = runner
|
|
143
|
+
|
|
144
|
+
async def ask(self, ref: AttemptRef, questions: tuple[AskUserQuestion, ...]) -> tuple[AskUserAnswer, ...]:
|
|
145
|
+
if self._runner is None:
|
|
146
|
+
raise AskUnavailable("no run is active.")
|
|
147
|
+
return await self._runner.on_ask(ref, questions)
|
|
148
|
+
|
|
149
|
+
async def emit(self, ref: AttemptRef, ordinal: int, text: str) -> None:
|
|
150
|
+
if self._runner is not None:
|
|
151
|
+
await self._runner.on_emit(ref, ordinal, text)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass(slots=True)
|
|
155
|
+
class _PendingAsk:
|
|
156
|
+
ref: AttemptRef
|
|
157
|
+
questions: tuple[AskUserQuestion, ...]
|
|
158
|
+
answering: bool = False
|
|
159
|
+
future: asyncio.Future[tuple[AskUserAnswer, ...]] = field(
|
|
160
|
+
default_factory=lambda: asyncio.get_running_loop().create_future()
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class WorkflowRunner:
|
|
165
|
+
"""Executes one run of an admitted workflow; see the module docstring for the ordering contract."""
|
|
166
|
+
|
|
167
|
+
def __init__(
|
|
168
|
+
self,
|
|
169
|
+
*,
|
|
170
|
+
admitted: AdmittedManifest,
|
|
171
|
+
journal: WorkflowJournal,
|
|
172
|
+
worker: WorkflowWorkerClient,
|
|
173
|
+
resources: AgentNodeResources,
|
|
174
|
+
checkpoint: Callable[[], Awaitable[None]],
|
|
175
|
+
mode: RunMode,
|
|
176
|
+
timeout: float | None = None,
|
|
177
|
+
trace: WorkflowTrace | None = None,
|
|
178
|
+
load_stdout: CapturedOutput | None = None,
|
|
179
|
+
) -> None:
|
|
180
|
+
self._admitted = admitted
|
|
181
|
+
self._graph = admitted.graph
|
|
182
|
+
self._journal = journal
|
|
183
|
+
self._worker = worker
|
|
184
|
+
self._resources = resources
|
|
185
|
+
self._checkpoint = checkpoint
|
|
186
|
+
self._mode = mode
|
|
187
|
+
self._timeout = timeout
|
|
188
|
+
self._trace = trace
|
|
189
|
+
self._load_stdout = load_stdout or CapturedOutput("", False)
|
|
190
|
+
self._output_collected = False
|
|
191
|
+
self._output_attempts: dict[str, int] = {}
|
|
192
|
+
self._record_end: Callable[[WorkflowRunResult], Awaitable[None]] | None = None
|
|
193
|
+
self._run_id = journal.run_id
|
|
194
|
+
self._scheduler = WorkflowScheduler(admitted.graph, run_id=self._run_id, mode=mode)
|
|
195
|
+
self._lock = asyncio.Lock()
|
|
196
|
+
self._agent_slots = asyncio.BoundedSemaphore(MAX_CONCURRENT_AGENT_ATTEMPTS)
|
|
197
|
+
self._lock_holder: asyncio.Task[Any] | None = None
|
|
198
|
+
self._tasks: set[asyncio.Task[None]] = set()
|
|
199
|
+
self._aborts: set[asyncio.Task[None]] = set()
|
|
200
|
+
self._invocations: dict[str, str] = {}
|
|
201
|
+
self._shells: dict[str, WorkflowAgentShell] = {}
|
|
202
|
+
self._started = 0.0
|
|
203
|
+
self._asks: dict[str, _PendingAsk] = {}
|
|
204
|
+
self._terminal: RunFinished | None = None
|
|
205
|
+
self._terminal_event = asyncio.Event()
|
|
206
|
+
self._deferred_fault: Callable[[], tuple[Decision, ...]] | None = None
|
|
207
|
+
self._record_error: str | None = None
|
|
208
|
+
self._cause: AbortCause | None = None
|
|
209
|
+
self._reason = ""
|
|
210
|
+
self._cancel_task: asyncio.Task[None] | None = None
|
|
211
|
+
self._worker_error = ""
|
|
212
|
+
self._internal_error = ""
|
|
213
|
+
self._data_dropped_noticed = False
|
|
214
|
+
self._result: WorkflowRunResult | None = None
|
|
215
|
+
self._deadline_task: asyncio.Task[None] | None = None
|
|
216
|
+
self._worker_watch: asyncio.Task[None] | None = None
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def run_id(self) -> str:
|
|
220
|
+
return self._run_id
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def result(self) -> WorkflowRunResult | None:
|
|
224
|
+
"""The terminal result once :meth:`run` returned it."""
|
|
225
|
+
return self._result
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def finished(self) -> bool:
|
|
229
|
+
return self._terminal is not None
|
|
230
|
+
|
|
231
|
+
def holds(self, task: asyncio.Task[Any] | None) -> bool:
|
|
232
|
+
"""True when *task* is one the run waits for: a subscriber to one of the run's events runs on one.
|
|
233
|
+
|
|
234
|
+
Scheduler events go out under the runner's lock, from the run task or one of its node tasks;
|
|
235
|
+
outputs and asks under the journal's lock alone, from a node's or the worker's task; an agent
|
|
236
|
+
node's invocation events from the tasks of its shell's pass, which the teardown waits out.
|
|
237
|
+
Waiting for the run from any of them would wait on itself.
|
|
238
|
+
"""
|
|
239
|
+
if task is None:
|
|
240
|
+
return False
|
|
241
|
+
return (
|
|
242
|
+
task is self._lock_holder
|
|
243
|
+
or task in self._tasks
|
|
244
|
+
or task in self._aborts
|
|
245
|
+
or self._journal.holds(task)
|
|
246
|
+
or any(shell.holds(task) for shell in self._shells.values())
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# -- driving ---------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
async def run(
|
|
252
|
+
self,
|
|
253
|
+
input_text: str,
|
|
254
|
+
*,
|
|
255
|
+
startup: tuple[Callable[[], Awaitable[None]], ...],
|
|
256
|
+
record_end: Callable[[WorkflowRunResult], Awaitable[None]],
|
|
257
|
+
) -> WorkflowRunResult:
|
|
258
|
+
"""Start the scheduler with *input_text* and return once the run is terminal and torn down."""
|
|
259
|
+
self._started = time.monotonic()
|
|
260
|
+
self._record_end = record_end
|
|
261
|
+
if self._timeout is not None and self._terminal is None:
|
|
262
|
+
self._deadline_task = self._spawn(self._deadline(self._timeout))
|
|
263
|
+
self._worker_watch = self._spawn(self._watch_worker())
|
|
264
|
+
try:
|
|
265
|
+
# Acceptance owns these facts even if cancellation already settled
|
|
266
|
+
# the scheduler. Only hook execution and node work are cancellable.
|
|
267
|
+
if self._trace is not None:
|
|
268
|
+
await self._trace.started()
|
|
269
|
+
await self._record(self._journal.run_started())
|
|
270
|
+
for step in startup:
|
|
271
|
+
await self._startup_step(step)
|
|
272
|
+
if self._terminal is None:
|
|
273
|
+
await self._feed(lambda: self._scheduler.start(WorkflowValue(text=input_text)))
|
|
274
|
+
await self._terminal_event.wait()
|
|
275
|
+
await self._teardown()
|
|
276
|
+
except asyncio.CancelledError:
|
|
277
|
+
await asyncio.shield(self._abandon(REASON_SHUTDOWN))
|
|
278
|
+
raise
|
|
279
|
+
except Exception as exc:
|
|
280
|
+
# Even a failure before a scheduler input must leave a terminal record and a closed worker.
|
|
281
|
+
logger.exception("workflow run %s: the runner failed", self.run_id)
|
|
282
|
+
reason = REASON_INTERNAL_ERROR
|
|
283
|
+
if isinstance(exc, WorkflowStorageFailed):
|
|
284
|
+
self._storage_failed(str(exc))
|
|
285
|
+
reason = ""
|
|
286
|
+
else:
|
|
287
|
+
self._unexpected_failure(f"{type(exc).__name__}: {exc}")
|
|
288
|
+
result = await self._abandon(reason)
|
|
289
|
+
if result is None:
|
|
290
|
+
raise
|
|
291
|
+
return result
|
|
292
|
+
return await self._finish()
|
|
293
|
+
|
|
294
|
+
async def _startup_step(self, step: Callable[[], Awaitable[None]]) -> None:
|
|
295
|
+
"""Dispatch each lifecycle hook once, cancelling its waits when the run ends."""
|
|
296
|
+
|
|
297
|
+
async def invoke() -> None:
|
|
298
|
+
task = asyncio.current_task()
|
|
299
|
+
if task is None:
|
|
300
|
+
raise RuntimeError("Dispatching workflow hooks requires an asyncio task.")
|
|
301
|
+
self._tasks.add(task) # inline subscribers must recognize their run owner
|
|
302
|
+
await step()
|
|
303
|
+
|
|
304
|
+
# Enter the dispatch before cancellation can prevent the coroutine from
|
|
305
|
+
# ever running. Already-cancelled runs still announce start/end; blocking
|
|
306
|
+
# startup work stops at its first suspension and drains before proceeding.
|
|
307
|
+
task = asyncio.create_task(invoke(), eager_start=True)
|
|
308
|
+
task.add_done_callback(self._forget)
|
|
309
|
+
if self._terminal is not None:
|
|
310
|
+
task.cancel()
|
|
311
|
+
(result,) = await asyncio.gather(task, return_exceptions=True)
|
|
312
|
+
if isinstance(result, asyncio.CancelledError):
|
|
313
|
+
if self._terminal is None:
|
|
314
|
+
raise result
|
|
315
|
+
elif isinstance(result, Exception):
|
|
316
|
+
cancelled = self.cancel(reason=REASON_INTERNAL_ERROR)
|
|
317
|
+
if cancelled is not None:
|
|
318
|
+
await cancelled
|
|
319
|
+
elif isinstance(result, BaseException):
|
|
320
|
+
raise result
|
|
321
|
+
|
|
322
|
+
def cancel(self, *, reason: str = "") -> asyncio.Task[None] | None:
|
|
323
|
+
"""Cancel the run; ``reason`` refines the terminal and picks the abort cause agent passes see.
|
|
324
|
+
|
|
325
|
+
The scheduler input is queued, never awaited here: a caller reacting inline to one of the run's
|
|
326
|
+
own events (published under the runner lock) must not wait for that lock. The queued input is
|
|
327
|
+
returned for a caller outside that lock which needs it applied; ``None`` once the run is terminal.
|
|
328
|
+
"""
|
|
329
|
+
if self._terminal is not None:
|
|
330
|
+
return None
|
|
331
|
+
if self._cause is None:
|
|
332
|
+
self._reason = reason
|
|
333
|
+
self._cause = _CANCEL_CAUSES.get(reason, AbortCause.USER_CANCEL)
|
|
334
|
+
self._cancel_task = self._spawn(self._feed(self._scheduler.cancel))
|
|
335
|
+
return self._cancel_task
|
|
336
|
+
|
|
337
|
+
def retry(self, node_id: str, activation_id: str, request_id: str, expected_failed_attempt: int) -> None:
|
|
338
|
+
"""A manual retry request, queued like :meth:`cancel`; rejections are logged (replays are harmless)."""
|
|
339
|
+
if self._terminal is not None:
|
|
340
|
+
return
|
|
341
|
+
self._spawn(
|
|
342
|
+
self._feed(
|
|
343
|
+
lambda: self._scheduler.manual_retry(node_id, activation_id, request_id, expected_failed_attempt)
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def answer(self, node_id: str, activation_id: str, request_id: str, answers: tuple[AskUserAnswer, ...]) -> bool:
|
|
348
|
+
"""Queue one answer, including from an inline ask subscriber; reject duplicate or invalid admissions.
|
|
349
|
+
|
|
350
|
+
An invalid answer leaves the ask open, so a corrected one can still follow.
|
|
351
|
+
"""
|
|
352
|
+
pending = self._asks.get(request_id)
|
|
353
|
+
if (
|
|
354
|
+
self._terminal is not None
|
|
355
|
+
or pending is None
|
|
356
|
+
or pending.future.done()
|
|
357
|
+
or pending.answering
|
|
358
|
+
or (pending.ref.node_id, pending.ref.activation_id) != (node_id, activation_id)
|
|
359
|
+
):
|
|
360
|
+
return False
|
|
361
|
+
validated = validate_ask_user_answers(answers, questions=pending.questions)
|
|
362
|
+
if validated is None:
|
|
363
|
+
return False
|
|
364
|
+
pending.answering = True
|
|
365
|
+
self._spawn(self._answer(pending, request_id, validated))
|
|
366
|
+
return True
|
|
367
|
+
|
|
368
|
+
async def _answer(self, pending: _PendingAsk, request_id: str, answers: tuple[AskUserAnswer, ...]) -> None:
|
|
369
|
+
async def commit() -> None:
|
|
370
|
+
if (
|
|
371
|
+
await self._record(self._journal.node_answer(pending.ref, request_id, pending.questions, answers))
|
|
372
|
+
is None
|
|
373
|
+
):
|
|
374
|
+
return
|
|
375
|
+
if not pending.future.done():
|
|
376
|
+
pending.future.set_result(answers)
|
|
377
|
+
|
|
378
|
+
# Once admitted, the answer owns its durable write through cancellation.
|
|
379
|
+
await finish_close(asyncio.create_task(commit()))
|
|
380
|
+
|
|
381
|
+
# -- worker callbacks ----------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
async def on_ask(self, ref: AttemptRef, questions: tuple[AskUserQuestion, ...]) -> tuple[AskUserAnswer, ...]:
|
|
384
|
+
if self._mode is RunMode.HEADLESS:
|
|
385
|
+
raise AskUnavailable("nobody can answer in a headless run.")
|
|
386
|
+
if self._terminal is not None:
|
|
387
|
+
raise AskUnavailable("the run is over.")
|
|
388
|
+
request_id = new_analytics_id()
|
|
389
|
+
pending = _PendingAsk(ref, questions)
|
|
390
|
+
self._asks[request_id] = pending
|
|
391
|
+
try:
|
|
392
|
+
if await self._record(self._journal.node_ask(ref, request_id, questions)) is None:
|
|
393
|
+
raise AskUnavailable("the run record failed.")
|
|
394
|
+
return await pending.future
|
|
395
|
+
finally:
|
|
396
|
+
self._asks.pop(request_id, None)
|
|
397
|
+
|
|
398
|
+
async def on_emit(self, ref: AttemptRef, ordinal: int, text: str) -> None:
|
|
399
|
+
if not await self._write_emit(ref, ordinal, text):
|
|
400
|
+
return
|
|
401
|
+
await self._record(self._journal.node_output(ref, WORKFLOW_OUTPUT_EMIT, ordinal, text))
|
|
402
|
+
|
|
403
|
+
# -- scheduler loop ------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
async def _feed(self, produce: Callable[[], tuple[Decision, ...]]) -> None:
|
|
406
|
+
"""One scheduler input and every decision it caused, executed before the next input."""
|
|
407
|
+
async with self._lock:
|
|
408
|
+
self._lock_holder = asyncio.current_task()
|
|
409
|
+
try:
|
|
410
|
+
if self._terminal is not None:
|
|
411
|
+
return
|
|
412
|
+
await self._apply(produce())
|
|
413
|
+
if self._terminal is None and self._scheduler.finished is not None:
|
|
414
|
+
# A prior decision publisher may have failed after the scheduler reached its terminal.
|
|
415
|
+
self._on_terminal(self._scheduler.finished)
|
|
416
|
+
while self._deferred_fault is not None and self._terminal is None:
|
|
417
|
+
fault, self._deferred_fault = self._deferred_fault, None
|
|
418
|
+
await self._apply(fault())
|
|
419
|
+
finally:
|
|
420
|
+
self._lock_holder = None
|
|
421
|
+
|
|
422
|
+
async def _apply(self, decisions: tuple[Decision, ...]) -> None:
|
|
423
|
+
for decision in decisions:
|
|
424
|
+
match decision:
|
|
425
|
+
case NodeStateChanged():
|
|
426
|
+
await self._on_state(decision)
|
|
427
|
+
case Activate():
|
|
428
|
+
self._spawn(self._activate(decision))
|
|
429
|
+
case EvaluateOutgoing():
|
|
430
|
+
self._spawn(self._evaluate_outgoing(decision))
|
|
431
|
+
case EvaluateLoopUntil():
|
|
432
|
+
self._spawn(self._evaluate_until(decision))
|
|
433
|
+
case ScheduleRetry():
|
|
434
|
+
self._spawn(self._backoff(decision))
|
|
435
|
+
case PersistRetryKey():
|
|
436
|
+
await self._record(self._journal.retry_key(decision.request_id, decision.ref))
|
|
437
|
+
case RetryRejected():
|
|
438
|
+
logger.info(
|
|
439
|
+
"workflow run %s rejected retry %s of %s: %s",
|
|
440
|
+
self._run_id,
|
|
441
|
+
decision.request_id,
|
|
442
|
+
decision.activation_id,
|
|
443
|
+
decision.reason.value,
|
|
444
|
+
)
|
|
445
|
+
case CancelActivation():
|
|
446
|
+
self._cancel_activation(decision.ref)
|
|
447
|
+
case LoopActivated():
|
|
448
|
+
await self._write_input(decision.ref, {"value": value_to_wire(decision.value)})
|
|
449
|
+
case LoopIteration():
|
|
450
|
+
await self._record(
|
|
451
|
+
self._journal.loop_iteration(decision.ref, decision.iteration, decision.verdict.value)
|
|
452
|
+
)
|
|
453
|
+
if self._loop_settles(decision):
|
|
454
|
+
await self._settle_loop(decision.ref, decision.value)
|
|
455
|
+
case RunFinished():
|
|
456
|
+
self._on_terminal(decision)
|
|
457
|
+
|
|
458
|
+
async def _on_state(self, decision: NodeStateChanged) -> None:
|
|
459
|
+
ref = decision.ref
|
|
460
|
+
invocation_id = ""
|
|
461
|
+
kind = self._graph.nodes[ref.node_id].kind
|
|
462
|
+
if kind == KIND_AGENT and ref.attempt > 0:
|
|
463
|
+
invocation_id = self._invocation_id(ref)
|
|
464
|
+
failure = decision.error
|
|
465
|
+
if self._trace is not None:
|
|
466
|
+
await self._trace.node_state(decision, kind=kind)
|
|
467
|
+
await self._record(
|
|
468
|
+
self._journal.node_state(
|
|
469
|
+
ref,
|
|
470
|
+
decision.state.value,
|
|
471
|
+
invocation_id=invocation_id,
|
|
472
|
+
error=failure.message if failure is not None else "",
|
|
473
|
+
error_class=failure.error_class.value if failure is not None else "",
|
|
474
|
+
durable=decision.state in _DURABLE_STATES,
|
|
475
|
+
iteration=decision.iteration,
|
|
476
|
+
failure_phase=decision.phase.value if decision.phase is not None else "",
|
|
477
|
+
)
|
|
478
|
+
)
|
|
479
|
+
if decision.state is ActivationState.COMPLETED and ref.activation_id in self._shells:
|
|
480
|
+
# Body success alone is not terminal: outgoing conditions can still
|
|
481
|
+
# fail and retry the same activation. Release only after settlement,
|
|
482
|
+
# outside the scheduler lock because close drains event publishers.
|
|
483
|
+
self._spawn(self._close_shell(ref.activation_id))
|
|
484
|
+
|
|
485
|
+
def _on_terminal(self, decision: RunFinished) -> None:
|
|
486
|
+
self._terminal = decision
|
|
487
|
+
if self._cause is None:
|
|
488
|
+
self._cause = AbortCause.RUN_TERMINAL
|
|
489
|
+
current = asyncio.current_task()
|
|
490
|
+
for task in self._tasks:
|
|
491
|
+
if task is not current:
|
|
492
|
+
task.cancel()
|
|
493
|
+
for pending in self._asks.values():
|
|
494
|
+
pending.future.cancel()
|
|
495
|
+
self._terminal_event.set()
|
|
496
|
+
|
|
497
|
+
def _cancel_activation(self, ref: AttemptRef) -> None:
|
|
498
|
+
"""Only ever issued by a terminal: the local task is cancelled here, the worker at teardown."""
|
|
499
|
+
shell = self._shells.get(ref.activation_id)
|
|
500
|
+
if shell is not None:
|
|
501
|
+
cause = self._cause or AbortCause.RUN_TERMINAL
|
|
502
|
+
task = asyncio.create_task(shell.abort(cause), name=f"chrys.workflow.abort.{ref.activation_id}")
|
|
503
|
+
self._aborts.add(task)
|
|
504
|
+
task.add_done_callback(self._aborts.discard)
|
|
505
|
+
|
|
506
|
+
# -- attempts --------------------------------------------------------------------
|
|
507
|
+
|
|
508
|
+
async def _activate(self, decision: Activate) -> None:
|
|
509
|
+
ref = decision.ref
|
|
510
|
+
try:
|
|
511
|
+
if decision.kind == KIND_PYTHON:
|
|
512
|
+
if decision.value is None:
|
|
513
|
+
raise RuntimeError("Activating a Python or agent node requires an input value.")
|
|
514
|
+
await self._run_python(ref, decision.value)
|
|
515
|
+
elif decision.kind == KIND_AGENT:
|
|
516
|
+
if decision.value is None:
|
|
517
|
+
raise RuntimeError("Activating a Python or agent node requires an input value.")
|
|
518
|
+
async with self._agent_slots:
|
|
519
|
+
await self._run_agent(ref, decision.value)
|
|
520
|
+
elif decision.kind == KIND_JOIN:
|
|
521
|
+
await self._run_join(ref, decision.value, decision.sources)
|
|
522
|
+
else:
|
|
523
|
+
await self._fail(
|
|
524
|
+
ref, FailureReport(ErrorClass.PYTHON_EXCEPTION, f"unknown node kind {decision.kind!r}")
|
|
525
|
+
)
|
|
526
|
+
except WorkerLostError as exc:
|
|
527
|
+
await self._worker_lost(str(exc))
|
|
528
|
+
except (ProtocolError, ValueShapeError) as exc:
|
|
529
|
+
await self._worker_lost(f"worker protocol violation at {ref.activation_id}: {exc}")
|
|
530
|
+
except WorkflowStorageFailed as exc:
|
|
531
|
+
self._storage_failed(str(exc))
|
|
532
|
+
|
|
533
|
+
async def _run_python(self, ref: AttemptRef, value: WorkflowValue) -> None:
|
|
534
|
+
if not await self._write_input(ref, {"value": value_to_wire(value)}):
|
|
535
|
+
return
|
|
536
|
+
node = self._graph.nodes[ref.node_id]
|
|
537
|
+
try:
|
|
538
|
+
result = await self._worker.run_python(ref, value, blocking=not node.fn_is_async, timeout=node.timeout)
|
|
539
|
+
except WorkerRpcError as exc:
|
|
540
|
+
await self._write_diagnostics(ref, "body", exc.stdout, exc.traceback)
|
|
541
|
+
if exc.code == ErrorCode.ATTEMPT_TERMINATED and self._terminal is not None:
|
|
542
|
+
return
|
|
543
|
+
error_class = _PYTHON_FAILURE_CLASSES.get(exc.code, ErrorClass.PYTHON_EXCEPTION)
|
|
544
|
+
message = exc.message if exc.code in _PYTHON_FAILURE_CLASSES else f"{exc.code}: {exc.message}"
|
|
545
|
+
await self._fail(ref, FailureReport(error_class, message))
|
|
546
|
+
except AttemptTimeout as exc:
|
|
547
|
+
await self._write_diagnostics(ref, "body", exc.stdout, exc.traceback)
|
|
548
|
+
message = f"no result within {node.timeout:g}s"
|
|
549
|
+
if exc.leaked_thread:
|
|
550
|
+
message += "; its thread is still running"
|
|
551
|
+
await self._fail(ref, FailureReport(ErrorClass.PYTHON_TIMEOUT, message))
|
|
552
|
+
else:
|
|
553
|
+
await self._write_diagnostics(ref, "body", result.stdout)
|
|
554
|
+
await self._complete(ref, result.value, last_emit_ordinal=result.last_emit_ordinal)
|
|
555
|
+
|
|
556
|
+
async def _run_agent(self, ref: AttemptRef, value: WorkflowValue) -> None:
|
|
557
|
+
record: dict[str, Any] = {"value": value_to_wire(value)}
|
|
558
|
+
if value.data is not None:
|
|
559
|
+
record[DATA_DROPPED_KEY] = True
|
|
560
|
+
if not await self._write_input(ref, record):
|
|
561
|
+
return
|
|
562
|
+
if value.data is not None and not self._data_dropped_noticed:
|
|
563
|
+
self._data_dropped_noticed = True
|
|
564
|
+
await self._record(
|
|
565
|
+
self._journal.run_notice(
|
|
566
|
+
ref.node_id,
|
|
567
|
+
ref.activation_id,
|
|
568
|
+
WORKFLOW_NOTICE_DATA_DROPPED,
|
|
569
|
+
f"Node {ref.node_id!r} received structured data; an agent node consumes only the text.",
|
|
570
|
+
)
|
|
571
|
+
)
|
|
572
|
+
shell = self._shell_for(ref)
|
|
573
|
+
if not shell.is_prepared:
|
|
574
|
+
try:
|
|
575
|
+
await shell.open(value.text, attempt=ref.attempt)
|
|
576
|
+
except WorkflowStorageFailed as exc:
|
|
577
|
+
self._storage_failed(str(exc))
|
|
578
|
+
return
|
|
579
|
+
except Exception as exc:
|
|
580
|
+
logger.warning(
|
|
581
|
+
"workflow run %s: agent node %s could not open", self._run_id, ref.node_id, exc_info=True
|
|
582
|
+
)
|
|
583
|
+
with contextlib.suppress(Exception):
|
|
584
|
+
await shell.close()
|
|
585
|
+
del self._shells[ref.activation_id] # the next attempt builds a fresh shell under the same identity
|
|
586
|
+
await self._fail(ref, FailureReport(ErrorClass.AGENT_NON_TRANSIENT, f"{type(exc).__name__}: {exc}"))
|
|
587
|
+
return
|
|
588
|
+
try:
|
|
589
|
+
result = await shell.run(
|
|
590
|
+
value.text,
|
|
591
|
+
timeout=self._graph.nodes[ref.node_id].timeout,
|
|
592
|
+
trajectory_context=self._trace.node_context(ref.activation_id, ref.attempt) if self._trace else None,
|
|
593
|
+
attempt=ref.attempt,
|
|
594
|
+
)
|
|
595
|
+
except WorkflowStorageFailed as exc:
|
|
596
|
+
self._storage_failed(str(exc))
|
|
597
|
+
return
|
|
598
|
+
if result.kind == "completed":
|
|
599
|
+
await self._complete(ref, WorkflowValue(text=result.text))
|
|
600
|
+
elif result.kind == "failed":
|
|
601
|
+
if result.failure is None:
|
|
602
|
+
raise RuntimeError("A failed agent attempt requires a failure report.")
|
|
603
|
+
await self._fail(ref, result.failure)
|
|
604
|
+
elif result.kind == "cancelled" and self._terminal is None:
|
|
605
|
+
await self._fail(ref, FailureReport(ErrorClass.AGENT_NON_TRANSIENT, "agent pass was cancelled"))
|
|
606
|
+
|
|
607
|
+
async def _run_join(self, ref: AttemptRef, value: WorkflowValue | None, sources: tuple[SourceValue, ...]) -> None:
|
|
608
|
+
if not await self._write_input(ref, {"sources": [source_to_wire(source) for source in sources]}):
|
|
609
|
+
return
|
|
610
|
+
if value is not None: # no user combine: the scheduler already folded the sources
|
|
611
|
+
await self._complete(ref, value)
|
|
612
|
+
return
|
|
613
|
+
timeout = self._graph.nodes[ref.node_id].timeout
|
|
614
|
+
try:
|
|
615
|
+
combined = await self._worker.combine(ref, sources, timeout=timeout)
|
|
616
|
+
except WorkerRpcError as exc:
|
|
617
|
+
await self._write_diagnostics(ref, "combine", exc.stdout, exc.traceback)
|
|
618
|
+
error_class = (
|
|
619
|
+
ErrorClass.VALUE_TOO_LARGE if exc.code == ErrorCode.VALUE_TOO_LARGE else ErrorClass.EVALUATION_ERROR
|
|
620
|
+
)
|
|
621
|
+
await self._fail(ref, FailureReport(error_class, f"combine failed: {exc.message}"))
|
|
622
|
+
except AttemptTimeout as exc:
|
|
623
|
+
await self._write_diagnostics(ref, "combine", exc.stdout, exc.traceback)
|
|
624
|
+
await self._fail(ref, FailureReport(ErrorClass.EVALUATION_ERROR, "combine did not finish in time."))
|
|
625
|
+
else:
|
|
626
|
+
await self._write_diagnostics(ref, "combine", combined.stdout)
|
|
627
|
+
await self._complete(ref, combined.value)
|
|
628
|
+
|
|
629
|
+
async def _evaluate_outgoing(self, decision: EvaluateOutgoing) -> None:
|
|
630
|
+
ref = decision.ref
|
|
631
|
+
try:
|
|
632
|
+
decided = await self._worker.eval_outgoing(ref, decision.value, decision.edge_ids)
|
|
633
|
+
except WorkerLostError as exc:
|
|
634
|
+
await self._worker_lost(str(exc))
|
|
635
|
+
return
|
|
636
|
+
except WorkerRpcError as exc:
|
|
637
|
+
await self._write_diagnostics(ref, "outgoing", exc.stdout, exc.traceback)
|
|
638
|
+
message = f"{exc.code}: {exc.message}"
|
|
639
|
+
except AttemptTimeout as exc:
|
|
640
|
+
await self._write_diagnostics(ref, "outgoing", exc.stdout, exc.traceback)
|
|
641
|
+
message = "edge condition did not finish in time."
|
|
642
|
+
except (ProtocolError, ValueShapeError) as exc:
|
|
643
|
+
await self._worker_lost(f"edge decisions are malformed: {exc}")
|
|
644
|
+
return
|
|
645
|
+
else:
|
|
646
|
+
await self._write_diagnostics(ref, "outgoing", decided.stdout)
|
|
647
|
+
await self._feed(lambda: self._scheduler.outgoing_evaluated(ref, decided.value))
|
|
648
|
+
return
|
|
649
|
+
await self._feed(lambda: self._scheduler.outgoing_failed(ref, message))
|
|
650
|
+
|
|
651
|
+
async def _evaluate_until(self, decision: EvaluateLoopUntil) -> None:
|
|
652
|
+
ref, iteration = decision.ref, decision.iteration
|
|
653
|
+
try:
|
|
654
|
+
verdict = await self._worker.eval_loop_until(ref, iteration, decision.value)
|
|
655
|
+
except WorkerLostError as exc:
|
|
656
|
+
await self._worker_lost(str(exc))
|
|
657
|
+
return
|
|
658
|
+
except WorkerRpcError as exc:
|
|
659
|
+
await self._write_diagnostics(ref, "until", exc.stdout, exc.traceback, iteration=iteration)
|
|
660
|
+
message = f"{exc.code}: {exc.message}"
|
|
661
|
+
except AttemptTimeout as exc:
|
|
662
|
+
await self._write_diagnostics(ref, "until", exc.stdout, exc.traceback, iteration=iteration)
|
|
663
|
+
message = "loop condition did not finish in time."
|
|
664
|
+
except (ProtocolError, ValueShapeError) as exc:
|
|
665
|
+
await self._worker_lost(f"loop verdict is malformed: {exc}")
|
|
666
|
+
return
|
|
667
|
+
else:
|
|
668
|
+
await self._write_diagnostics(ref, "until", verdict.stdout, iteration=iteration)
|
|
669
|
+
await self._feed(lambda: self._scheduler.loop_until_evaluated(ref, iteration, verdict.value))
|
|
670
|
+
return
|
|
671
|
+
await self._feed(lambda: self._scheduler.loop_until_failed(ref, iteration, message))
|
|
672
|
+
|
|
673
|
+
async def _backoff(self, decision: ScheduleRetry) -> None:
|
|
674
|
+
await asyncio.sleep(decision.backoff)
|
|
675
|
+
await self._feed(lambda: self._scheduler.backoff_elapsed(decision.ref))
|
|
676
|
+
|
|
677
|
+
async def _deadline(self, timeout: float) -> None:
|
|
678
|
+
await asyncio.sleep(timeout)
|
|
679
|
+
self.cancel(reason=REASON_DEADLINE_EXCEEDED)
|
|
680
|
+
|
|
681
|
+
async def _watch_worker(self) -> None:
|
|
682
|
+
"""A worker lost while no call is outstanding (a node awaiting retry, an agent-only stretch) still ends the run."""
|
|
683
|
+
error = await self._worker.wait_lost()
|
|
684
|
+
await self._worker_lost(str(error))
|
|
685
|
+
|
|
686
|
+
async def _worker_lost(self, message: str) -> None:
|
|
687
|
+
if not self._worker_error:
|
|
688
|
+
self._worker_error = message
|
|
689
|
+
await self._feed(self._scheduler.worker_lost)
|
|
690
|
+
|
|
691
|
+
def _loop_settles(self, decision: LoopIteration) -> bool:
|
|
692
|
+
"""Whether this verdict makes the iteration's exit value the loop activation's output."""
|
|
693
|
+
if decision.verdict is LoopVerdict.EXIT:
|
|
694
|
+
return True
|
|
695
|
+
loop = self._graph.nodes[decision.ref.node_id].loop
|
|
696
|
+
return (
|
|
697
|
+
decision.verdict is LoopVerdict.EXHAUSTED
|
|
698
|
+
and loop is not None
|
|
699
|
+
and loop.on_exhausted == ON_EXHAUSTED_CONTINUE
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
async def _settle_loop(self, ref: AttemptRef, value: WorkflowValue) -> None:
|
|
703
|
+
"""The loop activation's output, recorded like any node's: the value in full, then its final event."""
|
|
704
|
+
if await self._write_output(ref, value):
|
|
705
|
+
await self._record(self._journal.node_output(ref, WORKFLOW_OUTPUT_FINAL, 1, value.text))
|
|
706
|
+
|
|
707
|
+
async def _complete(self, ref: AttemptRef, value: WorkflowValue, *, last_emit_ordinal: int = 0) -> None:
|
|
708
|
+
if not await self._write_output(ref, value):
|
|
709
|
+
return
|
|
710
|
+
if (
|
|
711
|
+
await self._record(self._journal.node_output(ref, WORKFLOW_OUTPUT_FINAL, last_emit_ordinal + 1, value.text))
|
|
712
|
+
is None
|
|
713
|
+
):
|
|
714
|
+
return
|
|
715
|
+
await self._feed(lambda: self._scheduler.activation_completed(ref, value))
|
|
716
|
+
|
|
717
|
+
async def _fail(self, ref: AttemptRef, failure: FailureReport) -> None:
|
|
718
|
+
await self._feed(lambda: self._scheduler.activation_failed(ref, failure))
|
|
719
|
+
|
|
720
|
+
# -- records ---------------------------------------------------------------------
|
|
721
|
+
|
|
722
|
+
async def _record(self, write: Awaitable[int]) -> int | None:
|
|
723
|
+
"""One journal write; a storage failure becomes the run's fault instead of an exception."""
|
|
724
|
+
try:
|
|
725
|
+
return await write
|
|
726
|
+
except WorkflowStorageFailed:
|
|
727
|
+
self._storage_failed()
|
|
728
|
+
return None
|
|
729
|
+
|
|
730
|
+
async def _write_diagnostics(
|
|
731
|
+
self, ref: AttemptRef, phase: str, stdout: CapturedOutput, traceback: str = "", *, iteration: int | None = None
|
|
732
|
+
) -> None:
|
|
733
|
+
if iteration is None:
|
|
734
|
+
snapshot = self._scheduler.snapshot(ref.activation_id)
|
|
735
|
+
iteration = snapshot.iteration if snapshot is not None else 0
|
|
736
|
+
await self._journal.store.offload(
|
|
737
|
+
self._journal.store.write_node_diagnostics,
|
|
738
|
+
ref.activation_id,
|
|
739
|
+
ref.attempt,
|
|
740
|
+
phase=phase,
|
|
741
|
+
iteration=iteration,
|
|
742
|
+
stdout=stdout.text,
|
|
743
|
+
truncated=stdout.truncated,
|
|
744
|
+
traceback=traceback,
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
async def _write_input(self, ref: AttemptRef, record: dict[str, Any]) -> bool:
|
|
748
|
+
return await self._write_node_record(ref, NODE_RECORD_INPUT, record)
|
|
749
|
+
|
|
750
|
+
async def _write_output(self, ref: AttemptRef, value: WorkflowValue) -> bool:
|
|
751
|
+
# Retrying a loop's outgoing condition advances its attempt without rewriting this value.
|
|
752
|
+
self._output_attempts[ref.activation_id] = ref.attempt
|
|
753
|
+
return await self._write_node_record(ref, NODE_RECORD_OUTPUT, {"value": value_to_wire(value)})
|
|
754
|
+
|
|
755
|
+
async def _write_emit(self, ref: AttemptRef, ordinal: int, text: str) -> bool:
|
|
756
|
+
return await self._store(self._journal.store.append_node_emit, ref.activation_id, ref.attempt, ordinal, text)
|
|
757
|
+
|
|
758
|
+
async def _write_node_record(self, ref: AttemptRef, kind: str, record: dict[str, Any]) -> bool:
|
|
759
|
+
return await self._store(self._journal.store.write_node_value, ref.activation_id, ref.attempt, kind, record)
|
|
760
|
+
|
|
761
|
+
async def _store[**P](self, write: Callable[P, object], *args: P.args, **kwargs: P.kwargs) -> bool:
|
|
762
|
+
try:
|
|
763
|
+
await self._journal.store.offload(write, *args, **kwargs)
|
|
764
|
+
except WorkflowStorageFailed:
|
|
765
|
+
self._storage_failed()
|
|
766
|
+
return False
|
|
767
|
+
return True
|
|
768
|
+
|
|
769
|
+
def _storage_failed(self, error: str = "A node record could not be written.") -> None:
|
|
770
|
+
"""Queue the fault; the lock holder applies it after the current decisions, anyone else feeds it now.
|
|
771
|
+
|
|
772
|
+
The loss is remembered either way: a terminal settled in the same batch (or already settled) no
|
|
773
|
+
longer takes the fault as a scheduler input, and the run still finishes as ``storage_failed``.
|
|
774
|
+
"""
|
|
775
|
+
if self._record_error is None:
|
|
776
|
+
self._record_error = error
|
|
777
|
+
if self._terminal is not None or self._deferred_fault is not None:
|
|
778
|
+
return
|
|
779
|
+
if self._lock.locked():
|
|
780
|
+
self._deferred_fault = self._scheduler.storage_failed
|
|
781
|
+
else:
|
|
782
|
+
self._spawn(self._feed(self._scheduler.storage_failed))
|
|
783
|
+
|
|
784
|
+
# -- shells and tasks --------------------------------------------------------------
|
|
785
|
+
|
|
786
|
+
def _invocation_id(self, ref: AttemptRef) -> str:
|
|
787
|
+
"""The invocation identity of an agent activation: allocated once, kept across attempts."""
|
|
788
|
+
invocation_id = self._invocations.get(ref.activation_id)
|
|
789
|
+
if invocation_id is None:
|
|
790
|
+
invocation_id = new_analytics_id()
|
|
791
|
+
self._invocations[ref.activation_id] = invocation_id
|
|
792
|
+
return invocation_id
|
|
793
|
+
|
|
794
|
+
def set_approval_mode(self, mode: ApprovalMode) -> None:
|
|
795
|
+
"""Update live agent shells; shells opened later read the engine's launch policy."""
|
|
796
|
+
for shell in list(self._shells.values()):
|
|
797
|
+
shell.set_approval_mode(mode)
|
|
798
|
+
|
|
799
|
+
def _shell_for(self, ref: AttemptRef) -> WorkflowAgentShell:
|
|
800
|
+
shell = self._shells.get(ref.activation_id)
|
|
801
|
+
if shell is None:
|
|
802
|
+
binding = self._admitted.binding(ref.node_id)
|
|
803
|
+
invocation_id = self._invocation_id(ref)
|
|
804
|
+
shell = WorkflowAgentShell(
|
|
805
|
+
binding=binding,
|
|
806
|
+
node_id=ref.node_id,
|
|
807
|
+
invocation_id=invocation_id,
|
|
808
|
+
resources=self._resources,
|
|
809
|
+
archive=AgentNodeArchive(
|
|
810
|
+
self._journal.store,
|
|
811
|
+
activation_id=ref.activation_id,
|
|
812
|
+
invocation_id=invocation_id,
|
|
813
|
+
profile_name=binding.agent.display_name or binding.agent.name,
|
|
814
|
+
),
|
|
815
|
+
)
|
|
816
|
+
self._shells[ref.activation_id] = shell
|
|
817
|
+
return shell
|
|
818
|
+
|
|
819
|
+
async def _close_shell(self, activation_id: str) -> None:
|
|
820
|
+
await self._shells[activation_id].close()
|
|
821
|
+
del self._shells[activation_id]
|
|
822
|
+
|
|
823
|
+
def _spawn(self, coro: Awaitable[None]) -> asyncio.Task[None]:
|
|
824
|
+
task = asyncio.ensure_future(coro)
|
|
825
|
+
self._tasks.add(task)
|
|
826
|
+
task.add_done_callback(self._forget)
|
|
827
|
+
return task
|
|
828
|
+
|
|
829
|
+
def _forget(self, task: asyncio.Task[None]) -> None:
|
|
830
|
+
self._tasks.discard(task)
|
|
831
|
+
if task.cancelled():
|
|
832
|
+
if self._terminal is None:
|
|
833
|
+
self._unexpected_failure("run task was unexpectedly cancelled")
|
|
834
|
+
return
|
|
835
|
+
exc = task.exception()
|
|
836
|
+
if isinstance(exc, WorkflowStorageFailed):
|
|
837
|
+
self._storage_failed(str(exc))
|
|
838
|
+
elif exc is not None:
|
|
839
|
+
logger.error("workflow run %s: task failed", self._run_id, exc_info=exc)
|
|
840
|
+
self._unexpected_failure(f"{type(exc).__name__}: {exc}")
|
|
841
|
+
|
|
842
|
+
def _unexpected_failure(self, message: str) -> None:
|
|
843
|
+
if not self._internal_error:
|
|
844
|
+
self._internal_error = message
|
|
845
|
+
if self._terminal is None:
|
|
846
|
+
if self._cancel_task is not None and self._cancel_task.done():
|
|
847
|
+
# Cancellation's own publisher failed. Stop work without publishing more node decisions.
|
|
848
|
+
self._on_terminal(self._scheduler.finished or RunFinished(RunOutcome.CANCELLED, ()))
|
|
849
|
+
else:
|
|
850
|
+
self.cancel(reason=REASON_INTERNAL_ERROR)
|
|
851
|
+
elif self._terminal.outcome is RunOutcome.COMPLETED:
|
|
852
|
+
# A close task can fail after the last scheduler decision, before the terminal commit.
|
|
853
|
+
self._terminal = RunFinished(RunOutcome.CANCELLED, self._terminal.outputs)
|
|
854
|
+
self._reason = REASON_INTERNAL_ERROR
|
|
855
|
+
|
|
856
|
+
# -- terminal ------------------------------------------------------------------------
|
|
857
|
+
|
|
858
|
+
async def _teardown(self) -> None:
|
|
859
|
+
"""Drain tasks, close shells, and wait for the worker to exit; the terminal is recorded after."""
|
|
860
|
+
current = asyncio.current_task()
|
|
861
|
+
for watch in (self._deadline_task, self._worker_watch):
|
|
862
|
+
if watch is not None:
|
|
863
|
+
watch.cancel() # the terminal is settled: nothing waits out the timer, and the close below is no fault
|
|
864
|
+
live = [task for task in [*self._tasks, *self._aborts] if task is not current]
|
|
865
|
+
if live:
|
|
866
|
+
await asyncio.gather(*live, return_exceptions=True)
|
|
867
|
+
# Every shell, opened or interrupted while opening: close() tolerates both.
|
|
868
|
+
for activation_id, shell in list(self._shells.items()):
|
|
869
|
+
try:
|
|
870
|
+
await shell.close()
|
|
871
|
+
except WorkflowStorageFailed as exc:
|
|
872
|
+
self._storage_failed(str(exc))
|
|
873
|
+
except Exception as exc:
|
|
874
|
+
self._unexpected_failure(f"{type(exc).__name__}: {exc}")
|
|
875
|
+
logger.warning(
|
|
876
|
+
"workflow run %s: agent shell %s did not close cleanly",
|
|
877
|
+
self._run_id,
|
|
878
|
+
activation_id,
|
|
879
|
+
exc_info=True,
|
|
880
|
+
)
|
|
881
|
+
self._shells.clear()
|
|
882
|
+
await self._collect_run_output()
|
|
883
|
+
await self._worker.close()
|
|
884
|
+
|
|
885
|
+
async def _collect_run_output(self) -> None:
|
|
886
|
+
if self._output_collected:
|
|
887
|
+
return
|
|
888
|
+
self._output_collected = True
|
|
889
|
+
payload: dict[str, Any] = {"load": {"text": self._load_stdout.text, "truncated": self._load_stdout.truncated}}
|
|
890
|
+
if self._worker.lost is None:
|
|
891
|
+
try:
|
|
892
|
+
async with asyncio.timeout(3.0):
|
|
893
|
+
native = await self._worker.native_output()
|
|
894
|
+
payload["native"] = {"text": native.text, "dropped_bytes": native.dropped_bytes}
|
|
895
|
+
except Exception as exc:
|
|
896
|
+
payload["error"] = f"Native output could not be collected: {exc}"
|
|
897
|
+
logger.warning("workflow run %s: native output unavailable", self._run_id, exc_info=True)
|
|
898
|
+
else:
|
|
899
|
+
payload["error"] = "Native output unavailable because the worker was lost."
|
|
900
|
+
try:
|
|
901
|
+
await self._journal.store.offload(self._journal.store.write_run_output, payload)
|
|
902
|
+
except OSError, ValueError, TypeError:
|
|
903
|
+
logger.warning("workflow run %s: run output could not be written", self._run_id, exc_info=True)
|
|
904
|
+
|
|
905
|
+
async def _abandon(self, reason: str) -> WorkflowRunResult | None:
|
|
906
|
+
"""The run cannot continue on its own terms: converge as a cancel for *reason* and still tear down."""
|
|
907
|
+
if self._terminal is None:
|
|
908
|
+
if self._cause is None:
|
|
909
|
+
self._reason = reason
|
|
910
|
+
self._cause = _CANCEL_CAUSES.get(reason, AbortCause.OWNER_CLOSE)
|
|
911
|
+
with contextlib.suppress(Exception):
|
|
912
|
+
await self._feed(self._scheduler.cancel)
|
|
913
|
+
if self._terminal is None:
|
|
914
|
+
self._on_terminal(self._scheduler.finished or RunFinished(RunOutcome.CANCELLED, ()))
|
|
915
|
+
with contextlib.suppress(Exception):
|
|
916
|
+
await self._teardown()
|
|
917
|
+
if self._terminal is None:
|
|
918
|
+
return None
|
|
919
|
+
with contextlib.suppress(Exception):
|
|
920
|
+
return await self._finish()
|
|
921
|
+
return None
|
|
922
|
+
|
|
923
|
+
async def _finish(self) -> WorkflowRunResult:
|
|
924
|
+
if self._result is not None:
|
|
925
|
+
return self._result
|
|
926
|
+
terminal = self._terminal
|
|
927
|
+
if terminal is None:
|
|
928
|
+
raise RuntimeError("Finishing a workflow run requires a terminal decision.")
|
|
929
|
+
outputs = terminal.outputs
|
|
930
|
+
duration = time.monotonic() - self._started
|
|
931
|
+
summaries = [
|
|
932
|
+
WorkflowOutputSummary(
|
|
933
|
+
item.node_id,
|
|
934
|
+
item.activation_id,
|
|
935
|
+
summarize(item.value.text),
|
|
936
|
+
attempt=self._output_attempts[item.activation_id],
|
|
937
|
+
)
|
|
938
|
+
for item in outputs
|
|
939
|
+
]
|
|
940
|
+
error = terminal.error.message if terminal.error is not None else ""
|
|
941
|
+
node_id = terminal.node_id or ""
|
|
942
|
+
outcome = terminal.outcome
|
|
943
|
+
if outcome is RunOutcome.WORKER_LOST:
|
|
944
|
+
error = self._worker_error or str(self._worker.lost or "worker lost")
|
|
945
|
+
elif self._reason == REASON_INTERNAL_ERROR:
|
|
946
|
+
error = self._internal_error or error
|
|
947
|
+
if outcome is RunOutcome.COMPLETED:
|
|
948
|
+
outcome = RunOutcome.CANCELLED
|
|
949
|
+
if self._record_error is not None:
|
|
950
|
+
# A node record that failed to write after (or in the very batch of) the terminal: the run
|
|
951
|
+
# is not the completed one its outputs claim, so the record, the event and the result all say so.
|
|
952
|
+
outcome = RunOutcome.STORAGE_FAILED
|
|
953
|
+
error = f"{self._record_error}\n{error}" if error else self._record_error
|
|
954
|
+
try:
|
|
955
|
+
await self._checkpoint()
|
|
956
|
+
except Exception as exc:
|
|
957
|
+
logger.exception("workflow run %s: final session checkpoint failed", self._run_id)
|
|
958
|
+
outcome = RunOutcome.STORAGE_FAILED
|
|
959
|
+
checkpoint_error = f"Session checkpoint could not be saved: {type(exc).__name__}: {exc}"
|
|
960
|
+
error = f"{checkpoint_error}\n{error}" if error else checkpoint_error
|
|
961
|
+
await self._journal.finish(
|
|
962
|
+
outcome,
|
|
963
|
+
outputs=summaries,
|
|
964
|
+
duration=duration,
|
|
965
|
+
node_id=node_id,
|
|
966
|
+
error=error,
|
|
967
|
+
reason=self._reason,
|
|
968
|
+
)
|
|
969
|
+
if self._journal.storage_failed:
|
|
970
|
+
outcome = RunOutcome.STORAGE_FAILED
|
|
971
|
+
self._result = WorkflowRunResult(
|
|
972
|
+
run_id=self._run_id,
|
|
973
|
+
outcome=outcome,
|
|
974
|
+
outputs=outputs,
|
|
975
|
+
node_id=node_id,
|
|
976
|
+
error=error,
|
|
977
|
+
reason=self._reason,
|
|
978
|
+
duration=duration,
|
|
979
|
+
)
|
|
980
|
+
try:
|
|
981
|
+
if self._record_end is not None:
|
|
982
|
+
await self._record_end(self._result)
|
|
983
|
+
finally:
|
|
984
|
+
if self._trace is not None:
|
|
985
|
+
self._trace.finished(outcome.value)
|
|
986
|
+
return self._result
|