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,807 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Hook orchestrator — single entry point for every wire-up site.
|
|
4
|
+
|
|
5
|
+
The manager owns:
|
|
6
|
+
|
|
7
|
+
* The parsed :class:`HooksFile` (matched + filtered per dispatch).
|
|
8
|
+
* The :class:`HookRunner` (subprocess execution).
|
|
9
|
+
* The :class:`Outbox` (durable jobs).
|
|
10
|
+
* A set of in-flight non-detached tasks split into "this turn" /
|
|
11
|
+
"this session" / "background" so drains can target only awaited work.
|
|
12
|
+
|
|
13
|
+
Callers fire events through :meth:`fire`; the return value is a
|
|
14
|
+
:class:`HookDecision` that aggregates the event-supported outputs from
|
|
15
|
+
blocking hooks. Pure observer events can ignore the return value.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
import contextlib
|
|
22
|
+
import copy
|
|
23
|
+
import logging
|
|
24
|
+
from datetime import UTC, datetime
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from chrys.foundation.trajectory.context import TrajectoryContext
|
|
29
|
+
from chrys.service.hooks.events import HookEvent
|
|
30
|
+
from chrys.service.hooks.matcher import matches
|
|
31
|
+
from chrys.service.hooks.outbox import Outbox, OutboxJob
|
|
32
|
+
from chrys.service.hooks.runner import HookResult, HookRunner
|
|
33
|
+
from chrys.service.hooks.schema import HookConfig, HookDecision, HooksFile, MergedHooksFile
|
|
34
|
+
from chrys.service.trajectory.hooks import HookOperationTrace, HookOutcome, TrajectoryContextProvider
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
_GATED_EVENTS = frozenset({HookEvent.BEFORE_TOOL_CALL, HookEvent.USER_PROMPT_SUBMIT})
|
|
38
|
+
_ARGS_OVERRIDE_EVENTS = frozenset({HookEvent.BEFORE_TOOL_CALL})
|
|
39
|
+
_SYSTEM_REMINDER_EVENTS = frozenset({HookEvent.USER_PROMPT_SUBMIT})
|
|
40
|
+
_EXTRA_CONTEXT_EVENTS = frozenset({HookEvent.AFTER_TOOL_CALL, HookEvent.TOOL_ERROR})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Tracked in-flight task
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _Tracked:
|
|
49
|
+
"""Bookkeeping wrapper around an in-flight hook task.
|
|
50
|
+
|
|
51
|
+
``scope`` lets :meth:`HookManager.drain_turn` filter to just turn-
|
|
52
|
+
scoped tasks without touching background or session-scoped ones.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
__slots__ = ("hook", "job", "operation_id", "scope", "task")
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
*,
|
|
60
|
+
hook: HookConfig,
|
|
61
|
+
task: asyncio.Task[HookResult],
|
|
62
|
+
scope: str,
|
|
63
|
+
job: OutboxJob | None,
|
|
64
|
+
operation_id: str | None,
|
|
65
|
+
) -> None:
|
|
66
|
+
self.hook = hook
|
|
67
|
+
self.task = task
|
|
68
|
+
self.scope = scope # "turn" | "session" | "background"
|
|
69
|
+
self.job = job
|
|
70
|
+
self.operation_id = operation_id
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Manager
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class HookManager:
|
|
79
|
+
"""Async orchestrator for all hook activity.
|
|
80
|
+
|
|
81
|
+
Construct once per session when a hook config file exists. The
|
|
82
|
+
manager is dispatch-no-op when the loaded :class:`HooksFile` carries
|
|
83
|
+
no hooks, but construction still creates runtime directories for the
|
|
84
|
+
durable outbox.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
*,
|
|
90
|
+
file: HooksFile | MergedHooksFile,
|
|
91
|
+
hooks_dir: Path,
|
|
92
|
+
) -> None:
|
|
93
|
+
if isinstance(file, HooksFile):
|
|
94
|
+
file = MergedHooksFile.from_single(file)
|
|
95
|
+
self._file = file
|
|
96
|
+
self._hook_sources = _hook_sources(file)
|
|
97
|
+
self._hook_source_paths = _hook_source_paths(file)
|
|
98
|
+
self._hooks_by_id = _hooks_by_id(file.hooks)
|
|
99
|
+
self._hooks_by_recovery_key = _hooks_by_recovery_key(file.hooks, self._hook_sources, self._hook_source_paths)
|
|
100
|
+
self._hooks_dir = hooks_dir
|
|
101
|
+
self._runner = HookRunner(hooks_dir=hooks_dir)
|
|
102
|
+
self._outbox = Outbox(hooks_dir / "outbox")
|
|
103
|
+
# Cap concurrency across non-detached subprocesses.
|
|
104
|
+
self._sem = asyncio.Semaphore(max(1, file.settings.max_parallel_hooks))
|
|
105
|
+
# Open non-detached tasks by scope. Detached hooks are handed
|
|
106
|
+
# off to a worker process and are not tracked here.
|
|
107
|
+
self._inflight: list[_Tracked] = []
|
|
108
|
+
self._active_turn_drain_operation_ids: tuple[str, ...] = ()
|
|
109
|
+
# Listeners registered by middleware for "out-of-band" results
|
|
110
|
+
# (e.g. ``after_tool_call`` decisions that arrive after the
|
|
111
|
+
# tool already returned to the model). Indexed by event for
|
|
112
|
+
# cheap dispatch.
|
|
113
|
+
self._closed = False
|
|
114
|
+
self._session_drain_generation = 0
|
|
115
|
+
# Where hook runs are recorded when no model run binds an ambient
|
|
116
|
+
# trajectory scope (session / turn lifecycle hooks); the engine
|
|
117
|
+
# installs its recorder's resolver.
|
|
118
|
+
self.trajectory_context_provider: TrajectoryContextProvider | None = None
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def hooks_dir(self) -> Path:
|
|
122
|
+
return self._hooks_dir
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def file(self) -> MergedHooksFile:
|
|
126
|
+
return self._file
|
|
127
|
+
|
|
128
|
+
def sources(self) -> list[str]:
|
|
129
|
+
"""List of source paths included in the manager's config.
|
|
130
|
+
|
|
131
|
+
Empty when no source had a path (e.g. a synthetic empty
|
|
132
|
+
config). Populated by :class:`MergedHooksFile` with both
|
|
133
|
+
project and global paths in load order. Used for log lines
|
|
134
|
+
and the future ``/hooks`` TUI screen.
|
|
135
|
+
"""
|
|
136
|
+
return list(self._file.sources)
|
|
137
|
+
|
|
138
|
+
def has_hooks_for(self, event: HookEvent) -> bool:
|
|
139
|
+
"""Cheap fast-path check.
|
|
140
|
+
|
|
141
|
+
Wire-up sites guard with this so we don't even build payloads
|
|
142
|
+
when no hooks would match. Saves a dict allocation per tool
|
|
143
|
+
call when hooks are unused — and "no hooks at all" is the
|
|
144
|
+
common case.
|
|
145
|
+
"""
|
|
146
|
+
return any(h.enabled and h.event == event for h in self._file.hooks)
|
|
147
|
+
|
|
148
|
+
# ── dispatch ─────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
async def fire(
|
|
151
|
+
self,
|
|
152
|
+
event: HookEvent,
|
|
153
|
+
payload: dict[str, Any],
|
|
154
|
+
*,
|
|
155
|
+
scope: str = "turn",
|
|
156
|
+
target_operation_id: str | None = None,
|
|
157
|
+
trajectory_context: TrajectoryContext | None = None,
|
|
158
|
+
) -> HookDecision:
|
|
159
|
+
"""Fire every matching hook for *event*.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
event: The lifecycle point.
|
|
163
|
+
payload: Event-specific data; the manager adds ``event``,
|
|
164
|
+
``schema``, and ``timestamp`` envelope fields.
|
|
165
|
+
scope: ``"turn"`` (drained at end of turn), ``"session"``
|
|
166
|
+
(drained at shutdown), or ``"detached"`` (never
|
|
167
|
+
awaited). Caller picks the appropriate scope for the
|
|
168
|
+
event.
|
|
169
|
+
target_operation_id: The trajectory operation the hooks act
|
|
170
|
+
on (the tool operation for tool events), recorded on
|
|
171
|
+
every ``hook.operation.*`` this dispatch emits.
|
|
172
|
+
trajectory_context: Explicit recording owner for blocking hooks.
|
|
173
|
+
Non-blocking turn-scoped hooks deliberately retain the normal
|
|
174
|
+
provider context so response-fence membership remains resolvable.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Aggregated :class:`HookDecision`. For events with no
|
|
178
|
+
matching hooks, an empty (allow / no-op) decision.
|
|
179
|
+
"""
|
|
180
|
+
session_drain_generation = self._session_drain_generation
|
|
181
|
+
if self._closed:
|
|
182
|
+
return HookDecision()
|
|
183
|
+
candidates = [h for h in self._file.hooks if h.event == event and h.enabled]
|
|
184
|
+
if not candidates:
|
|
185
|
+
return HookDecision()
|
|
186
|
+
envelope = _build_envelope(event, payload)
|
|
187
|
+
decision = HookDecision()
|
|
188
|
+
|
|
189
|
+
# Two passes: blocking hooks run sequentially so the first
|
|
190
|
+
# ``block`` short-circuits; non-blocking hooks then spawn in
|
|
191
|
+
# parallel. Sequential blocking is intentional — users
|
|
192
|
+
# ordering hooks in the file should be able to reason about
|
|
193
|
+
# which fires first. Blocking ``modify`` decisions update the
|
|
194
|
+
# envelope immediately so later blocking hooks can validate the
|
|
195
|
+
# rewritten arguments.
|
|
196
|
+
blocking: list[HookConfig] = []
|
|
197
|
+
nonblocking: list[HookConfig] = []
|
|
198
|
+
for hook in candidates:
|
|
199
|
+
if hook.execution.mode == "blocking":
|
|
200
|
+
blocking.append(hook)
|
|
201
|
+
else:
|
|
202
|
+
nonblocking.append(hook)
|
|
203
|
+
|
|
204
|
+
for hook in blocking:
|
|
205
|
+
if self._closed or self._session_drain_generation != session_drain_generation:
|
|
206
|
+
return decision
|
|
207
|
+
if not matches(hook, envelope):
|
|
208
|
+
continue
|
|
209
|
+
trace = self._open_trace(target_operation_id, context=trajectory_context)
|
|
210
|
+
try:
|
|
211
|
+
# Inside the block that closes the span: the start marker
|
|
212
|
+
# awaits its write ack, and an interrupt landing there would
|
|
213
|
+
# otherwise leave the hook operation open forever.
|
|
214
|
+
if trace is not None:
|
|
215
|
+
await trace.started(
|
|
216
|
+
hook_id=hook.id,
|
|
217
|
+
hook_event=str(event),
|
|
218
|
+
execution_mode=hook.execution.mode,
|
|
219
|
+
detach=hook.execution.detach,
|
|
220
|
+
delivery=hook.execution.delivery,
|
|
221
|
+
scope=scope,
|
|
222
|
+
drain_scope=scope,
|
|
223
|
+
)
|
|
224
|
+
if self._closed or self._session_drain_generation != session_drain_generation:
|
|
225
|
+
if trace is not None:
|
|
226
|
+
trace.finished_soon(outcome=HookOutcome.ABANDONED)
|
|
227
|
+
return decision
|
|
228
|
+
result = await self._run_blocking(hook, envelope)
|
|
229
|
+
except asyncio.CancelledError:
|
|
230
|
+
if trace is not None:
|
|
231
|
+
trace.finished_soon(outcome=HookOutcome.CANCELLED)
|
|
232
|
+
raise
|
|
233
|
+
except Exception as exc:
|
|
234
|
+
logger.exception("Blocking hook %r failed before producing a result", hook.id)
|
|
235
|
+
result = HookResult(hook_id=hook.id, exit_code=None, stderr=str(exc))
|
|
236
|
+
blocked_before = decision.blocked
|
|
237
|
+
override_before = decision.args_override
|
|
238
|
+
_merge_blocking_result(decision, hook, result, event=event)
|
|
239
|
+
if trace is not None:
|
|
240
|
+
await trace.finished(
|
|
241
|
+
outcome=_hook_outcome(result, blocked=decision.blocked and not blocked_before),
|
|
242
|
+
arguments_modified=decision.args_override != override_before,
|
|
243
|
+
exit_code=result.exit_code,
|
|
244
|
+
timed_out=result.timed_out,
|
|
245
|
+
)
|
|
246
|
+
if decision.blocked:
|
|
247
|
+
# Cancel any planned non-blocking hooks for this event —
|
|
248
|
+
# nothing else will execute on this dispatch.
|
|
249
|
+
return decision
|
|
250
|
+
if decision.args_override is not None:
|
|
251
|
+
_apply_args_override_to_envelope(envelope, decision.args_override)
|
|
252
|
+
|
|
253
|
+
if self._closed or self._session_drain_generation != session_drain_generation:
|
|
254
|
+
return decision
|
|
255
|
+
for hook in nonblocking:
|
|
256
|
+
if not matches(hook, envelope):
|
|
257
|
+
continue
|
|
258
|
+
try:
|
|
259
|
+
await self._spawn_nonblocking(
|
|
260
|
+
hook,
|
|
261
|
+
envelope,
|
|
262
|
+
scope=scope,
|
|
263
|
+
session_drain_generation=session_drain_generation,
|
|
264
|
+
target_operation_id=target_operation_id,
|
|
265
|
+
)
|
|
266
|
+
except Exception as exc:
|
|
267
|
+
_log_nonblocking_spawn_failure(hook, exc)
|
|
268
|
+
|
|
269
|
+
return decision
|
|
270
|
+
|
|
271
|
+
def _open_trace(
|
|
272
|
+
self,
|
|
273
|
+
target_operation_id: str | None,
|
|
274
|
+
*,
|
|
275
|
+
context: TrajectoryContext | None = None,
|
|
276
|
+
) -> HookOperationTrace | None:
|
|
277
|
+
return HookOperationTrace.open(
|
|
278
|
+
target_operation_id=target_operation_id,
|
|
279
|
+
context=context,
|
|
280
|
+
provider=self.trajectory_context_provider,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# ── drains ───────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def active_turn_drain_operation_ids(self) -> tuple[str, ...]:
|
|
287
|
+
"""Hook operations captured by the latest turn drain fence."""
|
|
288
|
+
return self._active_turn_drain_operation_ids
|
|
289
|
+
|
|
290
|
+
async def drain_turn(self) -> tuple[str, ...]:
|
|
291
|
+
"""Await every ``async`` hook spawned in this turn.
|
|
292
|
+
|
|
293
|
+
``fire_and_forget`` tasks are observed for logging/outbox updates
|
|
294
|
+
but skipped by drains. ``detached`` tasks are already independent
|
|
295
|
+
of chrys. Errors are logged at WARN, not raised, so a flaky hook
|
|
296
|
+
cannot fail the turn that just succeeded.
|
|
297
|
+
"""
|
|
298
|
+
operation_ids = tuple(
|
|
299
|
+
tracked.operation_id
|
|
300
|
+
for tracked in self._inflight
|
|
301
|
+
if tracked.scope == "turn" and not tracked.task.done() and tracked.operation_id is not None
|
|
302
|
+
)
|
|
303
|
+
self._active_turn_drain_operation_ids = operation_ids
|
|
304
|
+
await self._drain(scopes={"turn"}, timeout=None)
|
|
305
|
+
return operation_ids
|
|
306
|
+
|
|
307
|
+
async def drain_session(self, *, close: bool = True) -> None:
|
|
308
|
+
"""Await every still-running ``async`` hook (turn + session scopes) up to
|
|
309
|
+
``shutdown_grace_seconds``.
|
|
310
|
+
|
|
311
|
+
On grace expiry surviving tasks are cancelled (their
|
|
312
|
+
subprocesses get killed by ``managed_subprocess``). Detached
|
|
313
|
+
subprocesses are never touched. Tasks outside the drained scopes keep
|
|
314
|
+
running and reporting results. The recorder settles unfinished traces
|
|
315
|
+
when its log closes; replacing this manager does not end observation.
|
|
316
|
+
|
|
317
|
+
``close=False`` waits the same way but leaves the manager open:
|
|
318
|
+
used when the live session ends before the engine shuts down
|
|
319
|
+
(deleting the active session), so ``session_end`` hooks finish
|
|
320
|
+
while the session files still exist and later events still fire.
|
|
321
|
+
"""
|
|
322
|
+
self._session_drain_generation += 1
|
|
323
|
+
await self._drain(
|
|
324
|
+
scopes={"turn", "session"},
|
|
325
|
+
timeout=self._file.settings.shutdown_grace_seconds,
|
|
326
|
+
)
|
|
327
|
+
if close:
|
|
328
|
+
self._closed = True
|
|
329
|
+
|
|
330
|
+
async def _drain(self, *, scopes: set[str], timeout: float | None) -> None:
|
|
331
|
+
tracked = [t for t in self._inflight if t.scope in scopes]
|
|
332
|
+
targets = [t for t in tracked if not t.task.done()]
|
|
333
|
+
if targets:
|
|
334
|
+
tasks = [t.task for t in targets]
|
|
335
|
+
try:
|
|
336
|
+
if timeout is None:
|
|
337
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
338
|
+
else:
|
|
339
|
+
await asyncio.wait_for(
|
|
340
|
+
asyncio.gather(*tasks, return_exceptions=True),
|
|
341
|
+
timeout=timeout,
|
|
342
|
+
)
|
|
343
|
+
except TimeoutError:
|
|
344
|
+
logger.warning(
|
|
345
|
+
"Hook drain timed out after %.1fs; cancelling %d task(s)",
|
|
346
|
+
timeout,
|
|
347
|
+
sum(1 for t in tasks if not t.done()),
|
|
348
|
+
)
|
|
349
|
+
for task in tasks:
|
|
350
|
+
if not task.done():
|
|
351
|
+
task.cancel()
|
|
352
|
+
# Best-effort await of cancellation so subprocess cleanup runs.
|
|
353
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
354
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
355
|
+
|
|
356
|
+
for tracked_task in tracked:
|
|
357
|
+
if not tracked_task.task.done():
|
|
358
|
+
continue
|
|
359
|
+
try:
|
|
360
|
+
tracked_task.task.result()
|
|
361
|
+
except asyncio.CancelledError:
|
|
362
|
+
pass
|
|
363
|
+
except Exception:
|
|
364
|
+
logger.exception("Hook task %r failed outside normal result handling", tracked_task.hook.id)
|
|
365
|
+
|
|
366
|
+
# Reap done tasks of these scopes, including hooks that completed
|
|
367
|
+
# before this drain call started.
|
|
368
|
+
self._inflight = [t for t in self._inflight if not (t.scope in scopes and t.task.done())]
|
|
369
|
+
|
|
370
|
+
# ── outbox recovery ──────────────────────────────────────────────
|
|
371
|
+
|
|
372
|
+
async def recover_outbox(self) -> int:
|
|
373
|
+
"""Scan ``outbox/pending/`` and retry stale jobs.
|
|
374
|
+
|
|
375
|
+
Returns the number of jobs queued for retry. Called once at
|
|
376
|
+
engine startup. Durable detached jobs are recovered through the
|
|
377
|
+
same detached worker path as fresh dispatches; semantics remain
|
|
378
|
+
at-least-once.
|
|
379
|
+
"""
|
|
380
|
+
stale = self._outbox.list_stale_pending(
|
|
381
|
+
retry_age_seconds=self._file.settings.outbox_retry_age_seconds,
|
|
382
|
+
)
|
|
383
|
+
if not stale:
|
|
384
|
+
return 0
|
|
385
|
+
|
|
386
|
+
max_retries = self._file.settings.outbox_max_retries
|
|
387
|
+
queued = 0
|
|
388
|
+
for job in stale:
|
|
389
|
+
if job.retries >= max_retries:
|
|
390
|
+
logger.warning(
|
|
391
|
+
"Outbox job %s (hook=%s) exceeded max_retries=%d; moving to failed/",
|
|
392
|
+
job.job_id,
|
|
393
|
+
job.hook_id,
|
|
394
|
+
max_retries,
|
|
395
|
+
)
|
|
396
|
+
with contextlib.suppress(OSError):
|
|
397
|
+
self._outbox.force_fail(job, reason=f"retries>={max_retries}")
|
|
398
|
+
continue
|
|
399
|
+
hook, missing_reason = self._resolve_recovery_hook(job)
|
|
400
|
+
if hook is None:
|
|
401
|
+
logger.warning(
|
|
402
|
+
"Outbox job %s references %s; moving to failed/",
|
|
403
|
+
job.job_id,
|
|
404
|
+
missing_reason,
|
|
405
|
+
)
|
|
406
|
+
with contextlib.suppress(OSError):
|
|
407
|
+
self._outbox.force_fail(job, reason=missing_reason)
|
|
408
|
+
continue
|
|
409
|
+
if not hook.enabled:
|
|
410
|
+
disabled_reason = f"disabled hook {job.hook_id!r}"
|
|
411
|
+
logger.warning(
|
|
412
|
+
"Outbox job %s references %s; moving to failed/",
|
|
413
|
+
job.job_id,
|
|
414
|
+
disabled_reason,
|
|
415
|
+
)
|
|
416
|
+
with contextlib.suppress(OSError):
|
|
417
|
+
self._outbox.force_fail(job, reason=disabled_reason)
|
|
418
|
+
continue
|
|
419
|
+
# Schedule a retry with the original payload. We reuse the
|
|
420
|
+
# same job_id so done/failed can be correlated back to the
|
|
421
|
+
# original incident if anyone goes looking.
|
|
422
|
+
await self._spawn_nonblocking(
|
|
423
|
+
hook,
|
|
424
|
+
job.payload,
|
|
425
|
+
scope="session",
|
|
426
|
+
session_drain_generation=self._session_drain_generation,
|
|
427
|
+
existing_job=job,
|
|
428
|
+
)
|
|
429
|
+
queued += 1
|
|
430
|
+
return queued
|
|
431
|
+
|
|
432
|
+
def _resolve_recovery_hook(self, job: OutboxJob) -> tuple[HookConfig | None, str]:
|
|
433
|
+
"""Return the hook a pending outbox job should retry, plus a failure label."""
|
|
434
|
+
if job.hook_source:
|
|
435
|
+
hook = self._hooks_by_recovery_key.get((job.hook_id, job.hook_source, job.hook_source_path))
|
|
436
|
+
if hook is None and not job.hook_source_path:
|
|
437
|
+
hook = self._resolve_legacy_recovery_hook_by_source(job)
|
|
438
|
+
if hook is None:
|
|
439
|
+
source_label = job.hook_source
|
|
440
|
+
if job.hook_source_path:
|
|
441
|
+
source_label = f"{job.hook_source!r} source at {job.hook_source_path!r}"
|
|
442
|
+
else:
|
|
443
|
+
source_label = f"{job.hook_source!r} source"
|
|
444
|
+
return None, f"missing hook {job.hook_id!r} from {source_label}"
|
|
445
|
+
return hook, ""
|
|
446
|
+
|
|
447
|
+
matches = self._hooks_by_id.get(job.hook_id, [])
|
|
448
|
+
if len(matches) > 1:
|
|
449
|
+
return None, f"ambiguous legacy hook id {job.hook_id!r} with no source marker"
|
|
450
|
+
if not matches:
|
|
451
|
+
return None, f"missing hook {job.hook_id!r}"
|
|
452
|
+
return matches[0], ""
|
|
453
|
+
|
|
454
|
+
def _resolve_legacy_recovery_hook_by_source(self, job: OutboxJob) -> HookConfig | None:
|
|
455
|
+
"""Resolve pre-source-path jobs that only recorded project/global."""
|
|
456
|
+
matches = [
|
|
457
|
+
hook
|
|
458
|
+
for hook in self._hooks_by_id.get(job.hook_id, [])
|
|
459
|
+
if self._hook_sources.get(id(hook), "") == job.hook_source
|
|
460
|
+
]
|
|
461
|
+
if len(matches) != 1:
|
|
462
|
+
return None
|
|
463
|
+
return matches[0]
|
|
464
|
+
|
|
465
|
+
# ── internals ────────────────────────────────────────────────────
|
|
466
|
+
|
|
467
|
+
async def _run_blocking(self, hook: HookConfig, payload: dict[str, Any]) -> HookResult:
|
|
468
|
+
async with self._sem:
|
|
469
|
+
return await self._runner.run_and_wait(hook, payload)
|
|
470
|
+
|
|
471
|
+
async def _spawn_nonblocking(
|
|
472
|
+
self,
|
|
473
|
+
hook: HookConfig,
|
|
474
|
+
payload: dict[str, Any],
|
|
475
|
+
*,
|
|
476
|
+
scope: str,
|
|
477
|
+
session_drain_generation: int,
|
|
478
|
+
existing_job: OutboxJob | None = None,
|
|
479
|
+
target_operation_id: str | None = None,
|
|
480
|
+
) -> None:
|
|
481
|
+
if self._closed or self._session_drain_generation != session_drain_generation:
|
|
482
|
+
return
|
|
483
|
+
job = existing_job
|
|
484
|
+
if hook.execution.delivery == "durable" and job is None:
|
|
485
|
+
job = self._outbox.write_pending(
|
|
486
|
+
hook_id=hook.id,
|
|
487
|
+
event=str(hook.event),
|
|
488
|
+
payload=payload,
|
|
489
|
+
hook_source=self._hook_sources.get(id(hook), ""),
|
|
490
|
+
hook_source_path=self._hook_source_paths.get(id(hook), ""),
|
|
491
|
+
)
|
|
492
|
+
drain_scope: str | None
|
|
493
|
+
if hook.execution.detach:
|
|
494
|
+
drain_scope = None
|
|
495
|
+
elif hook.execution.mode == "fire_and_forget":
|
|
496
|
+
drain_scope = "background"
|
|
497
|
+
else:
|
|
498
|
+
drain_scope = scope
|
|
499
|
+
# Async turn-scope hooks must stay in the active turn: drain_turn()
|
|
500
|
+
# publishes their ids in the response fence, whose analyzer lookup is
|
|
501
|
+
# intentionally confined to that turn's node membership.
|
|
502
|
+
trace = self._open_trace(target_operation_id)
|
|
503
|
+
if trace is not None:
|
|
504
|
+
try:
|
|
505
|
+
await trace.started(
|
|
506
|
+
hook_id=hook.id,
|
|
507
|
+
hook_event=str(hook.event),
|
|
508
|
+
execution_mode=hook.execution.mode,
|
|
509
|
+
detach=hook.execution.detach,
|
|
510
|
+
delivery=hook.execution.delivery,
|
|
511
|
+
scope=scope,
|
|
512
|
+
drain_scope=drain_scope,
|
|
513
|
+
)
|
|
514
|
+
except BaseException:
|
|
515
|
+
# Interrupted in the start marker's ack wait: no task exists
|
|
516
|
+
# yet that would close the span, so it is closed here — and
|
|
517
|
+
# the trace drops the terminal by itself if that marker never
|
|
518
|
+
# took a sequence.
|
|
519
|
+
trace.finished_soon(outcome=HookOutcome.CANCELLED)
|
|
520
|
+
raise
|
|
521
|
+
if self._closed or self._session_drain_generation != session_drain_generation:
|
|
522
|
+
if trace is not None:
|
|
523
|
+
trace.finished_soon(outcome=HookOutcome.ABANDONED)
|
|
524
|
+
return
|
|
525
|
+
|
|
526
|
+
# async / fire_and_forget — wrap in our internal task so we can
|
|
527
|
+
# observe completion and move outbox entries.
|
|
528
|
+
if hook.execution.mode == "async":
|
|
529
|
+
coro = self._run_async_tracked(hook, payload, job, trace)
|
|
530
|
+
task = asyncio.create_task(coro)
|
|
531
|
+
operation_id = trace.operation_id if trace is not None and trace.start_committed else None
|
|
532
|
+
tracked = _Tracked(hook=hook, task=task, scope=scope, job=job, operation_id=operation_id)
|
|
533
|
+
self._inflight.append(tracked)
|
|
534
|
+
if scope == "detached":
|
|
535
|
+
task.add_done_callback(lambda _task, tracked=tracked: self._reap_background(tracked))
|
|
536
|
+
return
|
|
537
|
+
|
|
538
|
+
# fire_and_forget
|
|
539
|
+
if hook.execution.detach:
|
|
540
|
+
try:
|
|
541
|
+
if job is not None:
|
|
542
|
+
self._outbox.mark_started(job)
|
|
543
|
+
await self._runner.spawn_detached(hook, payload, job=job)
|
|
544
|
+
except asyncio.CancelledError:
|
|
545
|
+
if trace is not None:
|
|
546
|
+
trace.finished_soon(outcome=HookOutcome.CANCELLED)
|
|
547
|
+
raise
|
|
548
|
+
except Exception as exc:
|
|
549
|
+
_log_nonblocking_spawn_failure(hook, exc)
|
|
550
|
+
if job is not None:
|
|
551
|
+
with contextlib.suppress(OSError):
|
|
552
|
+
self._outbox.mark_failed(job, exit_code=None, error=str(exc))
|
|
553
|
+
if trace is not None:
|
|
554
|
+
await trace.finished(outcome=HookOutcome.SPAWN_FAILED)
|
|
555
|
+
return
|
|
556
|
+
if trace is not None:
|
|
557
|
+
# Handed to the detached worker: the parent never observes
|
|
558
|
+
# its exit, so the recorded span ends at the hand-off.
|
|
559
|
+
await trace.finished(outcome=HookOutcome.DETACHED)
|
|
560
|
+
return
|
|
561
|
+
|
|
562
|
+
coro = self._run_async_tracked(hook, payload, job, trace)
|
|
563
|
+
task = asyncio.create_task(coro)
|
|
564
|
+
operation_id = trace.operation_id if trace is not None and trace.start_committed else None
|
|
565
|
+
tracked = _Tracked(hook=hook, task=task, scope="background", job=job, operation_id=operation_id)
|
|
566
|
+
self._inflight.append(tracked)
|
|
567
|
+
task.add_done_callback(lambda _task, tracked=tracked: self._reap_background(tracked))
|
|
568
|
+
|
|
569
|
+
def _reap_background(self, tracked: _Tracked) -> None:
|
|
570
|
+
with contextlib.suppress(ValueError):
|
|
571
|
+
self._inflight.remove(tracked)
|
|
572
|
+
try:
|
|
573
|
+
tracked.task.result()
|
|
574
|
+
except asyncio.CancelledError:
|
|
575
|
+
pass
|
|
576
|
+
except Exception:
|
|
577
|
+
logger.exception("Background hook task %r failed outside normal result handling", tracked.hook.id)
|
|
578
|
+
|
|
579
|
+
async def _run_async_tracked(
|
|
580
|
+
self,
|
|
581
|
+
hook: HookConfig,
|
|
582
|
+
payload: dict[str, Any],
|
|
583
|
+
job: OutboxJob | None,
|
|
584
|
+
trace: HookOperationTrace | None = None,
|
|
585
|
+
) -> HookResult:
|
|
586
|
+
"""Wrap ``runner.run_and_wait`` with outbox + semaphore + error logging."""
|
|
587
|
+
try:
|
|
588
|
+
# The slot is taken inside the ``try``: a hook cancelled while it
|
|
589
|
+
# queues behind ``max_parallel_hooks`` others never ran, but its
|
|
590
|
+
# span was opened before the task was created and only this
|
|
591
|
+
# handler ever closes it.
|
|
592
|
+
async with self._sem:
|
|
593
|
+
if job is not None:
|
|
594
|
+
self._outbox.mark_started(job)
|
|
595
|
+
result = await self._runner.run_and_wait(hook, payload)
|
|
596
|
+
except asyncio.CancelledError:
|
|
597
|
+
if job is not None and hook.execution.delivery == "durable":
|
|
598
|
+
logger.info(
|
|
599
|
+
"Durable hook %r was cancelled; leaving outbox job %s pending for retry",
|
|
600
|
+
hook.id,
|
|
601
|
+
job.job_id,
|
|
602
|
+
)
|
|
603
|
+
if trace is not None:
|
|
604
|
+
trace.finished_soon(outcome=HookOutcome.CANCELLED)
|
|
605
|
+
raise
|
|
606
|
+
except Exception as exc:
|
|
607
|
+
logger.exception("Hook %r raised", hook.id)
|
|
608
|
+
if job is not None:
|
|
609
|
+
with contextlib.suppress(OSError):
|
|
610
|
+
self._outbox.mark_failed(job, exit_code=None, error=str(exc))
|
|
611
|
+
if trace is not None:
|
|
612
|
+
await trace.finished(outcome=HookOutcome.LAUNCH_ERROR)
|
|
613
|
+
return HookResult(hook_id=hook.id, exit_code=None, stderr=str(exc))
|
|
614
|
+
# Before the span is closed: the hook has already run, and the outbox
|
|
615
|
+
# is what decides whether it runs again. An interrupt landing on the
|
|
616
|
+
# awaited marker below must not leave a finished durable job pending
|
|
617
|
+
# for the next runtime to replay.
|
|
618
|
+
if job is not None:
|
|
619
|
+
if _result_failed(result):
|
|
620
|
+
with contextlib.suppress(OSError):
|
|
621
|
+
self._outbox.mark_failed(
|
|
622
|
+
job,
|
|
623
|
+
exit_code=result.exit_code,
|
|
624
|
+
error=_result_failure_label(result),
|
|
625
|
+
stderr_tail=result.stderr,
|
|
626
|
+
)
|
|
627
|
+
else:
|
|
628
|
+
with contextlib.suppress(OSError):
|
|
629
|
+
self._outbox.mark_done(job, exit_code=result.exit_code or 0)
|
|
630
|
+
|
|
631
|
+
if trace is not None:
|
|
632
|
+
await trace.finished(
|
|
633
|
+
outcome=_hook_outcome(result, blocked=False),
|
|
634
|
+
exit_code=result.exit_code,
|
|
635
|
+
timed_out=result.timed_out,
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
if _result_failed(result):
|
|
639
|
+
_log_hook_failure(hook, result)
|
|
640
|
+
return result
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
# ---------------------------------------------------------------------------
|
|
644
|
+
# Module-level helpers
|
|
645
|
+
# ---------------------------------------------------------------------------
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _build_envelope(event: HookEvent, payload: dict[str, Any]) -> dict[str, Any]:
|
|
649
|
+
"""Combine *payload* with the standard envelope fields."""
|
|
650
|
+
envelope = {
|
|
651
|
+
**copy.deepcopy(payload),
|
|
652
|
+
"schema": 1,
|
|
653
|
+
"event": str(event),
|
|
654
|
+
"timestamp": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
655
|
+
}
|
|
656
|
+
return envelope
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _hook_sources(file: MergedHooksFile) -> dict[int, str]:
|
|
660
|
+
"""Map hook object identity to its merged configuration layer."""
|
|
661
|
+
sources: dict[int, str] = {}
|
|
662
|
+
if file.project is not None:
|
|
663
|
+
for hook in file.project.hooks:
|
|
664
|
+
sources[id(hook)] = "project"
|
|
665
|
+
if file.global_ is not None:
|
|
666
|
+
for hook in file.global_.hooks:
|
|
667
|
+
sources[id(hook)] = "global"
|
|
668
|
+
return sources
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def _hook_source_paths(file: MergedHooksFile) -> dict[int, str]:
|
|
672
|
+
"""Map hook object identity to the hooks config file that supplied it."""
|
|
673
|
+
sources: dict[int, str] = {}
|
|
674
|
+
if file.project is not None and file.project.source:
|
|
675
|
+
for hook in file.project.hooks:
|
|
676
|
+
sources[id(hook)] = file.project.source
|
|
677
|
+
if file.global_ is not None and file.global_.source:
|
|
678
|
+
for hook in file.global_.hooks:
|
|
679
|
+
sources[id(hook)] = file.global_.source
|
|
680
|
+
return sources
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def _hooks_by_recovery_key(
|
|
684
|
+
hooks: list[HookConfig],
|
|
685
|
+
hook_sources: dict[int, str],
|
|
686
|
+
hook_source_paths: dict[int, str],
|
|
687
|
+
) -> dict[tuple[str, str, str], HookConfig]:
|
|
688
|
+
"""Index hooks by id plus exact source layer/path for durable outbox retry."""
|
|
689
|
+
keyed: dict[tuple[str, str, str], HookConfig] = {}
|
|
690
|
+
for hook in hooks:
|
|
691
|
+
source = hook_sources.get(id(hook), "")
|
|
692
|
+
source_path = hook_source_paths.get(id(hook), "")
|
|
693
|
+
if not source and not source_path:
|
|
694
|
+
continue
|
|
695
|
+
keyed[(hook.id, source, source_path)] = hook
|
|
696
|
+
return keyed
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _hooks_by_id(hooks: list[HookConfig]) -> dict[str, list[HookConfig]]:
|
|
700
|
+
"""Group hooks by id while preserving merged order within each id."""
|
|
701
|
+
grouped: dict[str, list[HookConfig]] = {}
|
|
702
|
+
for hook in hooks:
|
|
703
|
+
grouped.setdefault(hook.id, []).append(hook)
|
|
704
|
+
return grouped
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _apply_args_override_to_envelope(envelope: dict[str, Any], args_override: dict[str, Any]) -> None:
|
|
708
|
+
"""Merge blocking hook argument overrides into the current tool payload."""
|
|
709
|
+
tool = envelope.get("tool")
|
|
710
|
+
if not isinstance(tool, dict):
|
|
711
|
+
return
|
|
712
|
+
args = tool.get("args")
|
|
713
|
+
if not isinstance(args, dict):
|
|
714
|
+
args = {}
|
|
715
|
+
tool["args"] = args
|
|
716
|
+
args.update(args_override)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _merge_blocking_result(
|
|
720
|
+
decision: HookDecision,
|
|
721
|
+
hook: HookConfig,
|
|
722
|
+
result: HookResult,
|
|
723
|
+
*,
|
|
724
|
+
event: HookEvent,
|
|
725
|
+
) -> None:
|
|
726
|
+
"""Fold one blocking hook's outcome into the running :class:`HookDecision`."""
|
|
727
|
+
on_error = hook.execution.on_error
|
|
728
|
+
failed = _result_failed(result)
|
|
729
|
+
|
|
730
|
+
if failed:
|
|
731
|
+
msg_kind = _result_failure_label(result)
|
|
732
|
+
if on_error == "block" and event in _GATED_EVENTS:
|
|
733
|
+
decision.blocked = True
|
|
734
|
+
decision.block_reason = f"hook '{hook.id}' failed ({msg_kind}): {result.stderr.strip()[:200]}"
|
|
735
|
+
return
|
|
736
|
+
if on_error == "warn":
|
|
737
|
+
logger.warning("Hook %r failed (%s) but on_error=warn; continuing", hook.id, msg_kind)
|
|
738
|
+
elif on_error == "block":
|
|
739
|
+
logger.warning(
|
|
740
|
+
"Hook %r failed (%s) but event %s is not gated; treating on_error=block as warn",
|
|
741
|
+
hook.id,
|
|
742
|
+
msg_kind,
|
|
743
|
+
event,
|
|
744
|
+
)
|
|
745
|
+
# A failed hook's decision file may be partial or stale. Honour
|
|
746
|
+
# only the configured error policy and ignore any action payload.
|
|
747
|
+
return
|
|
748
|
+
|
|
749
|
+
action = str(result.decision.get("action") or "allow").lower()
|
|
750
|
+
if action == "block":
|
|
751
|
+
if event in _GATED_EVENTS:
|
|
752
|
+
decision.blocked = True
|
|
753
|
+
reason = str(result.decision.get("reason") or "")
|
|
754
|
+
decision.block_reason = f"hook '{hook.id}' blocked: {reason}" if reason else f"hook '{hook.id}' blocked"
|
|
755
|
+
return
|
|
756
|
+
logger.warning("Hook %r returned action=block for non-gated event %s; ignoring block", hook.id, event)
|
|
757
|
+
if action == "modify" and event in _ARGS_OVERRIDE_EVENTS:
|
|
758
|
+
args_override = result.decision.get("args_override")
|
|
759
|
+
if isinstance(args_override, dict):
|
|
760
|
+
decision.args_override = {**(decision.args_override or {}), **args_override}
|
|
761
|
+
|
|
762
|
+
reminder = result.decision.get("system_reminder")
|
|
763
|
+
if event in _SYSTEM_REMINDER_EVENTS and isinstance(reminder, str) and reminder:
|
|
764
|
+
decision.system_reminders.append(reminder)
|
|
765
|
+
extra = result.decision.get("extra_context")
|
|
766
|
+
if event in _EXTRA_CONTEXT_EVENTS and isinstance(extra, str) and extra:
|
|
767
|
+
decision.extra_context.append(extra)
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def _log_hook_failure(hook: HookConfig, result: HookResult) -> None:
|
|
771
|
+
if hook.execution.on_error == "ignore":
|
|
772
|
+
logger.debug("Hook %r failed (on_error=ignore)", hook.id)
|
|
773
|
+
return
|
|
774
|
+
detail = _result_failure_label(result)
|
|
775
|
+
logger.warning("Hook %r failed (%s); stderr=%s", hook.id, detail, result.stderr.strip()[:500])
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def _log_nonblocking_spawn_failure(hook: HookConfig, exc: Exception) -> None:
|
|
779
|
+
if hook.execution.on_error == "ignore":
|
|
780
|
+
logger.debug("Non-blocking hook %r could not be spawned", hook.id, exc_info=True)
|
|
781
|
+
return
|
|
782
|
+
logger.warning("Non-blocking hook %r could not be spawned: %s", hook.id, exc, exc_info=True)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _result_failed(result: HookResult) -> bool:
|
|
786
|
+
return result.timed_out or result.exit_code is None or result.exit_code != 0
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def _hook_outcome(result: HookResult, *, blocked: bool) -> str:
|
|
790
|
+
"""Closed ``hook.operation.finished.outcome`` for one subprocess result."""
|
|
791
|
+
if result.timed_out:
|
|
792
|
+
return HookOutcome.TIMED_OUT
|
|
793
|
+
if result.exit_code is None:
|
|
794
|
+
return HookOutcome.LAUNCH_ERROR
|
|
795
|
+
if result.exit_code != 0:
|
|
796
|
+
return HookOutcome.FAILED
|
|
797
|
+
if blocked:
|
|
798
|
+
return HookOutcome.BLOCKED
|
|
799
|
+
return HookOutcome.SUCCESS
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def _result_failure_label(result: HookResult) -> str:
|
|
803
|
+
if result.timed_out:
|
|
804
|
+
return "timeout"
|
|
805
|
+
if result.exit_code is None:
|
|
806
|
+
return "launch_error"
|
|
807
|
+
return f"exit_code={result.exit_code}"
|