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,922 @@
|
|
|
1
|
+
# Copyright (c) the agent-client-protocol authors (Chojan Shang, Frost Ming and contributors)
|
|
2
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
3
|
+
# Contains code adapted from the Agent Client Protocol Python SDK (Apache License 2.0; see NOTICE).
|
|
4
|
+
|
|
5
|
+
"""ACP transport framing, backpressure, observation, and tracking.
|
|
6
|
+
|
|
7
|
+
``_TrackingStateStore`` and ``_BackpressureDispatcher`` are derived from
|
|
8
|
+
``InMemoryMessageStateStore`` (``acp/task/state.py``) and
|
|
9
|
+
``DefaultMessageDispatcher`` (``acp/task/dispatcher.py``) in the Agent Client
|
|
10
|
+
Protocol Python SDK (https://github.com/agentclientprotocol/python-sdk),
|
|
11
|
+
licensed under the Apache License, Version 2.0, and have been modified.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import contextlib
|
|
18
|
+
import contextvars
|
|
19
|
+
import logging
|
|
20
|
+
from collections import deque
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from acp.connection import StreamDirection, StreamEvent
|
|
26
|
+
from acp.task import (
|
|
27
|
+
MessageQueue,
|
|
28
|
+
MessageStateStore,
|
|
29
|
+
NotificationRunner,
|
|
30
|
+
RequestRunner,
|
|
31
|
+
RpcTaskKind,
|
|
32
|
+
TaskSupervisor,
|
|
33
|
+
)
|
|
34
|
+
from acp.task.state import IncomingMessage
|
|
35
|
+
|
|
36
|
+
from . import protocol
|
|
37
|
+
from .errors import AcpConfigError, AcpTransportError, classify_protocol_frame_error
|
|
38
|
+
from .protocol import (
|
|
39
|
+
_validate_payload_caps,
|
|
40
|
+
_validate_update_payload_caps,
|
|
41
|
+
encode_protocol_json,
|
|
42
|
+
parse_protocol_json,
|
|
43
|
+
validate_json_rpc_envelope,
|
|
44
|
+
)
|
|
45
|
+
from .spec import AcpPromptUsage
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
_MAX_ATTEMPT_FRAMES = 100_000
|
|
50
|
+
_MAX_ATTEMPT_BYTES = 256 * 1024 * 1024
|
|
51
|
+
_MAX_REQUEST_FRAMES = 8_192
|
|
52
|
+
_MAX_UPDATE_ITEMS = 4_096
|
|
53
|
+
_MAX_UPDATE_BYTES = 64 * 1024 * 1024
|
|
54
|
+
_MAX_HUMAN_WAITS = 8
|
|
55
|
+
_MAX_USAGE_VALUE = (1 << 63) - 1
|
|
56
|
+
_REQUEST_CONCURRENCY = 32
|
|
57
|
+
_CLOSE_STAGE_TIMEOUT_SECONDS = 2.0
|
|
58
|
+
_REQUEST_DRAIN_TIMEOUT_SECONDS = 1.0
|
|
59
|
+
|
|
60
|
+
_PROMPT_METHOD = "session/prompt"
|
|
61
|
+
_UPDATE_METHOD = "session/update"
|
|
62
|
+
_HUMAN_METHODS = frozenset({protocol._PERMISSION_METHOD, protocol._ASK_USER_METHOD})
|
|
63
|
+
_CURRENT_INBOUND_ID: contextvars.ContextVar[str | int | None] = contextvars.ContextVar(
|
|
64
|
+
"chrys_acp_inbound_request_id",
|
|
65
|
+
default=None,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(slots=True)
|
|
70
|
+
class _OutgoingRecord:
|
|
71
|
+
request_id: int
|
|
72
|
+
method: str
|
|
73
|
+
future: asyncio.Future[Any]
|
|
74
|
+
write_committed: bool = False
|
|
75
|
+
response_observed: bool = False
|
|
76
|
+
caps_violated: bool = False
|
|
77
|
+
raw_response: dict[str, Any] | None = None
|
|
78
|
+
usage: AcpPromptUsage | None = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class _TrackingStateStore(MessageStateStore):
|
|
82
|
+
"""SDK state store with outgoing metadata and no inbound retention."""
|
|
83
|
+
|
|
84
|
+
def __init__(self) -> None:
|
|
85
|
+
self._outgoing: dict[int, _OutgoingRecord] = {}
|
|
86
|
+
self._completed: dict[int, _OutgoingRecord] = {}
|
|
87
|
+
self._prompt_id: int | None = None
|
|
88
|
+
self._prompt_registered = asyncio.Event()
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def prompt_id(self) -> int | None:
|
|
92
|
+
return self._prompt_id
|
|
93
|
+
|
|
94
|
+
async def wait_for_prompt_id(self) -> int:
|
|
95
|
+
await self._prompt_registered.wait()
|
|
96
|
+
if self._prompt_id is None:
|
|
97
|
+
raise RuntimeError("Prompt registration event had no request id.")
|
|
98
|
+
return self._prompt_id
|
|
99
|
+
|
|
100
|
+
def reset_prompt(self) -> None:
|
|
101
|
+
self._prompt_id = None
|
|
102
|
+
self._prompt_registered.clear()
|
|
103
|
+
|
|
104
|
+
def register_outgoing(self, request_id: int, method: str) -> asyncio.Future[Any]:
|
|
105
|
+
future: asyncio.Future[Any] = asyncio.get_running_loop().create_future()
|
|
106
|
+
future.add_done_callback(_consume_future_exception)
|
|
107
|
+
record = _OutgoingRecord(request_id=request_id, method=method, future=future)
|
|
108
|
+
self._outgoing[request_id] = record
|
|
109
|
+
if method == _PROMPT_METHOD:
|
|
110
|
+
self._prompt_id = request_id
|
|
111
|
+
self._prompt_registered.set()
|
|
112
|
+
return future
|
|
113
|
+
|
|
114
|
+
def mark_write_committed(self, request_id: int) -> None:
|
|
115
|
+
record = self._outgoing.get(request_id)
|
|
116
|
+
if record is not None:
|
|
117
|
+
record.write_committed = True
|
|
118
|
+
|
|
119
|
+
def observe_response(self, request_id: str | int, message: dict[str, Any]) -> _OutgoingRecord | None:
|
|
120
|
+
if type(request_id) is not int:
|
|
121
|
+
return None
|
|
122
|
+
record = self._outgoing.get(request_id)
|
|
123
|
+
if record is None:
|
|
124
|
+
return None
|
|
125
|
+
if not record.write_committed:
|
|
126
|
+
raise ValueError("ACP response arrived before the request write was committed.")
|
|
127
|
+
if record.response_observed or record.caps_violated:
|
|
128
|
+
# First-response-wins: the pump observes every frame, and two
|
|
129
|
+
# coalesced responses for one id both arrive before SDK routing
|
|
130
|
+
# pops the record. The SDK resolves only the first, so a later
|
|
131
|
+
# duplicate must not overwrite the retained response or usage,
|
|
132
|
+
# re-run the caps, or resurrect a caps-violated record.
|
|
133
|
+
return None
|
|
134
|
+
# Retained record fields ride the same §7.4 caps as permission and
|
|
135
|
+
# ask-user payloads: without this, a handful of oversized responses
|
|
136
|
+
# could park most of the attempt byte budget in ``_completed`` forever.
|
|
137
|
+
# The flag is load-bearing beyond the raise: the SDK receive loop still
|
|
138
|
+
# routes this same frame into resolve/reject afterwards (the observer
|
|
139
|
+
# cannot veto it), and those paths must not store the oversized payload
|
|
140
|
+
# in the completed record's future.
|
|
141
|
+
try:
|
|
142
|
+
_validate_payload_caps(message)
|
|
143
|
+
except ValueError as exc:
|
|
144
|
+
record.caps_violated = True
|
|
145
|
+
# §6.2: validly reported usage must still account even when the
|
|
146
|
+
# response itself is rejected — extract it before raising so every
|
|
147
|
+
# resulting AcpTransportError carries it.
|
|
148
|
+
if record.method == _PROMPT_METHOD:
|
|
149
|
+
record.usage = _extract_raw_usage(message)
|
|
150
|
+
raise _AcpProtocolLimitError(
|
|
151
|
+
"ACP response frame exceeds the retained-payload caps.", cause=exc, usage=record.usage
|
|
152
|
+
) from exc
|
|
153
|
+
record.response_observed = True
|
|
154
|
+
record.raw_response = message
|
|
155
|
+
if record.method == _PROMPT_METHOD:
|
|
156
|
+
record.usage = _extract_raw_usage(message)
|
|
157
|
+
return record
|
|
158
|
+
|
|
159
|
+
def record_for(self, request_id: int) -> _OutgoingRecord | None:
|
|
160
|
+
return self._outgoing.get(request_id) or self._completed.get(request_id)
|
|
161
|
+
|
|
162
|
+
def outstanding_record(self, request_id: str | int) -> _OutgoingRecord | None:
|
|
163
|
+
"""The in-flight record for a response id, or None once routed.
|
|
164
|
+
|
|
165
|
+
The observer keys seal/barrier off this instead of ``record_for`` so
|
|
166
|
+
a duplicate response frame arriving after SDK routing stays inert.
|
|
167
|
+
"""
|
|
168
|
+
if type(request_id) is not int:
|
|
169
|
+
return None
|
|
170
|
+
return self._outgoing.get(request_id)
|
|
171
|
+
|
|
172
|
+
def latest_raw_result(self, method: str) -> dict[str, Any] | None:
|
|
173
|
+
records = [*self._outgoing.values(), *self._completed.values()]
|
|
174
|
+
for record in reversed(records):
|
|
175
|
+
if record.method != method or record.raw_response is None:
|
|
176
|
+
continue
|
|
177
|
+
result = record.raw_response.get("result")
|
|
178
|
+
return result if type(result) is dict else None
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def resolve_outgoing(self, request_id: int, result: Any) -> None:
|
|
182
|
+
record = self._outgoing.pop(request_id, None)
|
|
183
|
+
if record is not None:
|
|
184
|
+
self._completed[request_id] = record
|
|
185
|
+
if not record.future.done():
|
|
186
|
+
if record.caps_violated:
|
|
187
|
+
record.future.set_exception(
|
|
188
|
+
AcpTransportError("ACP response frame exceeds the retained-payload caps.", usage=record.usage)
|
|
189
|
+
)
|
|
190
|
+
else:
|
|
191
|
+
record.future.set_result(result)
|
|
192
|
+
|
|
193
|
+
def reject_outgoing(self, request_id: int, error: Any) -> None:
|
|
194
|
+
record = self._outgoing.pop(request_id, None)
|
|
195
|
+
if record is not None:
|
|
196
|
+
self._completed[request_id] = record
|
|
197
|
+
if not record.future.done():
|
|
198
|
+
if record.caps_violated:
|
|
199
|
+
record.future.set_exception(
|
|
200
|
+
AcpTransportError("ACP response frame exceeds the retained-payload caps.", usage=record.usage)
|
|
201
|
+
)
|
|
202
|
+
else:
|
|
203
|
+
record.future.set_exception(error)
|
|
204
|
+
|
|
205
|
+
def reject_all_outgoing(self, error: Any) -> None:
|
|
206
|
+
for request_id, record in tuple(self._outgoing.items()):
|
|
207
|
+
self._completed[request_id] = record
|
|
208
|
+
if not record.future.done():
|
|
209
|
+
record.future.set_exception(error)
|
|
210
|
+
self._outgoing.clear()
|
|
211
|
+
|
|
212
|
+
def begin_incoming(self, method: str, params: Any) -> IncomingMessage:
|
|
213
|
+
return IncomingMessage(method=method, params=None)
|
|
214
|
+
|
|
215
|
+
def complete_incoming(self, record: IncomingMessage, result: Any) -> None:
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
def fail_incoming(self, record: IncomingMessage, error: Any) -> None:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _consume_future_exception(future: asyncio.Future[Any]) -> None:
|
|
223
|
+
"""Mark background rejection exceptions retrieved without changing await semantics."""
|
|
224
|
+
if not future.cancelled():
|
|
225
|
+
future.exception()
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@dataclass(slots=True)
|
|
229
|
+
class _PendingSend:
|
|
230
|
+
payload: dict[str, Any]
|
|
231
|
+
encoded: bytes
|
|
232
|
+
future: asyncio.Future[None]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class _RetainedSender:
|
|
236
|
+
"""SDK sender replacement with strict encoding and total pending-send failure."""
|
|
237
|
+
|
|
238
|
+
def __init__(
|
|
239
|
+
self,
|
|
240
|
+
writer: asyncio.StreamWriter,
|
|
241
|
+
supervisor: TaskSupervisor,
|
|
242
|
+
*,
|
|
243
|
+
store: _TrackingStateStore,
|
|
244
|
+
before_response_send: Callable[[str | int], None],
|
|
245
|
+
on_failure: Callable[[BaseException], None],
|
|
246
|
+
) -> None:
|
|
247
|
+
self._writer = writer
|
|
248
|
+
self._store = store
|
|
249
|
+
self._before_response_send = before_response_send
|
|
250
|
+
self._on_failure = on_failure
|
|
251
|
+
self._queue: asyncio.Queue[_PendingSend | None] = asyncio.Queue()
|
|
252
|
+
self._closing = False
|
|
253
|
+
self._active: _PendingSend | None = None
|
|
254
|
+
self._task = supervisor.create(self._run(), name="chrys.acp.sender", on_error=self._task_failed)
|
|
255
|
+
|
|
256
|
+
async def send(self, payload: dict[str, Any]) -> None:
|
|
257
|
+
is_response = "method" not in payload and ("result" in payload or "error" in payload)
|
|
258
|
+
if self._task.done():
|
|
259
|
+
raise ConnectionError("ACP sender has stopped.")
|
|
260
|
+
if self._closing and not is_response:
|
|
261
|
+
raise ConnectionError("ACP sender is closing.")
|
|
262
|
+
try:
|
|
263
|
+
encoded = encode_protocol_json(payload) + b"\n"
|
|
264
|
+
except ValueError as exc:
|
|
265
|
+
failure = AcpConfigError("A local ACP payload is not valid JSON.", cause=exc)
|
|
266
|
+
request_id = payload.get("id")
|
|
267
|
+
if "method" in payload and type(request_id) is int:
|
|
268
|
+
self._store.reject_outgoing(request_id, failure)
|
|
269
|
+
self._on_failure(failure)
|
|
270
|
+
raise failure from exc
|
|
271
|
+
future: asyncio.Future[None] = asyncio.get_running_loop().create_future()
|
|
272
|
+
await self._queue.put(_PendingSend(payload=payload, encoded=encoded, future=future))
|
|
273
|
+
await future
|
|
274
|
+
|
|
275
|
+
def begin_close(self) -> None:
|
|
276
|
+
self._closing = True
|
|
277
|
+
|
|
278
|
+
async def close(self) -> None:
|
|
279
|
+
if not self._closing:
|
|
280
|
+
self._closing = True
|
|
281
|
+
if self._task.done():
|
|
282
|
+
self._reject_pending(ConnectionError("ACP sender stopped."))
|
|
283
|
+
with contextlib.suppress(asyncio.CancelledError, Exception):
|
|
284
|
+
await self._task
|
|
285
|
+
return
|
|
286
|
+
await self._queue.put(None)
|
|
287
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
288
|
+
await self._task
|
|
289
|
+
|
|
290
|
+
async def emergency_stop(self) -> None:
|
|
291
|
+
self._closing = True
|
|
292
|
+
self._task.cancel()
|
|
293
|
+
# This runs only after the normal close already wedged, so the sender
|
|
294
|
+
# cannot be trusted to honor that single cancellation; a direct await
|
|
295
|
+
# would hand the close ladder to a task that may never finish, and
|
|
296
|
+
# queued sends must be rejected even when it never does.
|
|
297
|
+
await _reap_task(self._task)
|
|
298
|
+
self._reject_pending(ConnectionError("ACP sender stopped."))
|
|
299
|
+
# The item _run already popped is unreachable from the queue; if the
|
|
300
|
+
# task outlived both reap cancellations, its caller must still be
|
|
301
|
+
# released here (both sides guard with future.done()).
|
|
302
|
+
active = self._active
|
|
303
|
+
if active is not None and not active.future.done():
|
|
304
|
+
active.future.set_exception(ConnectionError("ACP sender stopped."))
|
|
305
|
+
|
|
306
|
+
async def _run(self) -> None:
|
|
307
|
+
failure: BaseException | None = None
|
|
308
|
+
try:
|
|
309
|
+
while True:
|
|
310
|
+
item = await self._queue.get()
|
|
311
|
+
if item is None:
|
|
312
|
+
return
|
|
313
|
+
if item.future.cancelled() and "method" in item.payload:
|
|
314
|
+
# Normal close appends its sentinel behind queued items,
|
|
315
|
+
# so a request whose caller already gave up (caller
|
|
316
|
+
# cancellation or a terminal wake) would still flush to
|
|
317
|
+
# the agent and execute remotely. Responses stay owed to
|
|
318
|
+
# the peer regardless of local cancellation, so only
|
|
319
|
+
# method-bearing frames are dropped.
|
|
320
|
+
continue
|
|
321
|
+
self._active = item
|
|
322
|
+
try:
|
|
323
|
+
payload = item.payload
|
|
324
|
+
if "method" in payload and "id" in payload and type(payload["id"]) is int:
|
|
325
|
+
self._store.mark_write_committed(payload["id"])
|
|
326
|
+
elif "method" not in payload and "id" in payload:
|
|
327
|
+
self._before_response_send(payload["id"])
|
|
328
|
+
self._writer.write(item.encoded)
|
|
329
|
+
await self._writer.drain()
|
|
330
|
+
except BaseException as exc:
|
|
331
|
+
failure = exc
|
|
332
|
+
if not item.future.done():
|
|
333
|
+
item.future.set_exception(exc)
|
|
334
|
+
raise
|
|
335
|
+
else:
|
|
336
|
+
if not item.future.done():
|
|
337
|
+
item.future.set_result(None)
|
|
338
|
+
self._active = None
|
|
339
|
+
except asyncio.CancelledError:
|
|
340
|
+
raise
|
|
341
|
+
finally:
|
|
342
|
+
self._reject_pending(failure or ConnectionError("ACP sender stopped."))
|
|
343
|
+
|
|
344
|
+
def _reject_pending(self, error: BaseException) -> None:
|
|
345
|
+
while True:
|
|
346
|
+
try:
|
|
347
|
+
item = self._queue.get_nowait()
|
|
348
|
+
except asyncio.QueueEmpty:
|
|
349
|
+
return
|
|
350
|
+
if item is not None and not item.future.done():
|
|
351
|
+
item.future.set_exception(error)
|
|
352
|
+
|
|
353
|
+
def _task_failed(self, _task: asyncio.Task[Any], error: BaseException) -> None:
|
|
354
|
+
self._store.reject_all_outgoing(ConnectionError("ACP sender failed."))
|
|
355
|
+
self._on_failure(error)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class _BackpressureDispatcher:
|
|
359
|
+
"""Request-semaphore dispatcher that sinks every notification without tasks."""
|
|
360
|
+
|
|
361
|
+
def __init__(
|
|
362
|
+
self,
|
|
363
|
+
*,
|
|
364
|
+
queue: MessageQueue,
|
|
365
|
+
supervisor: TaskSupervisor,
|
|
366
|
+
store: MessageStateStore,
|
|
367
|
+
request_runner: RequestRunner,
|
|
368
|
+
notification_runner: NotificationRunner,
|
|
369
|
+
on_failure: Callable[[BaseException], None],
|
|
370
|
+
) -> None:
|
|
371
|
+
self._queue = queue
|
|
372
|
+
self._supervisor = supervisor
|
|
373
|
+
self._store = store
|
|
374
|
+
self._request_runner = request_runner
|
|
375
|
+
self._on_failure = on_failure
|
|
376
|
+
self._semaphore = asyncio.Semaphore(_REQUEST_CONCURRENCY)
|
|
377
|
+
self._task: asyncio.Task[None] | None = None
|
|
378
|
+
self._runners: set[asyncio.Task[Any]] = set()
|
|
379
|
+
self._admitting = True
|
|
380
|
+
|
|
381
|
+
def start(self) -> None:
|
|
382
|
+
if self._task is not None:
|
|
383
|
+
raise RuntimeError("ACP dispatcher already started.")
|
|
384
|
+
self._task = self._supervisor.create(
|
|
385
|
+
self._run(),
|
|
386
|
+
name="chrys.acp.dispatcher",
|
|
387
|
+
on_error=self._dispatcher_failed,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
async def _run(self) -> None:
|
|
391
|
+
async for task in self._queue:
|
|
392
|
+
try:
|
|
393
|
+
if task.kind is not RpcTaskKind.REQUEST or not self._admitting:
|
|
394
|
+
continue
|
|
395
|
+
await self._semaphore.acquire()
|
|
396
|
+
if not self._admitting:
|
|
397
|
+
self._semaphore.release()
|
|
398
|
+
continue
|
|
399
|
+
self._dispatch_request(task.message)
|
|
400
|
+
finally:
|
|
401
|
+
self._queue.task_done()
|
|
402
|
+
|
|
403
|
+
def _dispatch_request(self, message: dict[str, Any]) -> None:
|
|
404
|
+
record = self._store.begin_incoming(message["method"], message.get("params"))
|
|
405
|
+
|
|
406
|
+
async def runner() -> None:
|
|
407
|
+
token = _CURRENT_INBOUND_ID.set(message["id"])
|
|
408
|
+
try:
|
|
409
|
+
result = await self._request_runner(message)
|
|
410
|
+
except Exception as exc:
|
|
411
|
+
self._store.fail_incoming(record, exc)
|
|
412
|
+
raise
|
|
413
|
+
else:
|
|
414
|
+
self._store.complete_incoming(record, result)
|
|
415
|
+
finally:
|
|
416
|
+
_CURRENT_INBOUND_ID.reset(token)
|
|
417
|
+
self._semaphore.release()
|
|
418
|
+
|
|
419
|
+
task = self._supervisor.create(runner(), name="chrys.acp.request")
|
|
420
|
+
self._runners.add(task)
|
|
421
|
+
task.add_done_callback(self._runners.discard)
|
|
422
|
+
|
|
423
|
+
def close_admission(self) -> None:
|
|
424
|
+
self._admitting = False
|
|
425
|
+
if self._task is not None:
|
|
426
|
+
self._task.cancel()
|
|
427
|
+
|
|
428
|
+
async def drain_runners(self) -> None:
|
|
429
|
+
if not self._runners:
|
|
430
|
+
return
|
|
431
|
+
done, pending = await asyncio.wait(tuple(self._runners), timeout=_REQUEST_DRAIN_TIMEOUT_SECONDS)
|
|
432
|
+
for task in pending:
|
|
433
|
+
task.cancel()
|
|
434
|
+
cancelled_done: set[asyncio.Task[Any]] = set()
|
|
435
|
+
if pending:
|
|
436
|
+
cancelled_done, _still_pending = await asyncio.wait(
|
|
437
|
+
pending,
|
|
438
|
+
timeout=_REQUEST_DRAIN_TIMEOUT_SECONDS,
|
|
439
|
+
)
|
|
440
|
+
for task in (*done, *cancelled_done):
|
|
441
|
+
with contextlib.suppress(asyncio.CancelledError, Exception):
|
|
442
|
+
await task
|
|
443
|
+
|
|
444
|
+
async def stop(self) -> None:
|
|
445
|
+
self.close_admission()
|
|
446
|
+
if self._task is not None:
|
|
447
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
448
|
+
await self._task
|
|
449
|
+
self._task = None
|
|
450
|
+
|
|
451
|
+
async def cancel_runners(self) -> None:
|
|
452
|
+
"""Cancel in-flight request callbacks without closing admission.
|
|
453
|
+
|
|
454
|
+
Admission stays open so requests arriving after the prompt seal are
|
|
455
|
+
still dispatched and politely auto-denied by the callbacks; the close
|
|
456
|
+
ladder remains the sole owner of dispatcher teardown. Supervisor error
|
|
457
|
+
hooks skip cancelled tasks, so this never feeds the failure path.
|
|
458
|
+
"""
|
|
459
|
+
for task in tuple(self._runners):
|
|
460
|
+
task.cancel()
|
|
461
|
+
await self.drain_runners()
|
|
462
|
+
|
|
463
|
+
async def emergency_stop(self) -> None:
|
|
464
|
+
self.close_admission()
|
|
465
|
+
await self.cancel_runners()
|
|
466
|
+
|
|
467
|
+
def _dispatcher_failed(self, _task: asyncio.Task[Any], error: BaseException) -> None:
|
|
468
|
+
self._on_failure(error)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@dataclass(frozen=True, slots=True)
|
|
472
|
+
class _PromptBarrier:
|
|
473
|
+
request_id: int
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
class _AcpDisconnectError(AcpTransportError):
|
|
477
|
+
"""EOF after the exact prompt response; drain the barrier before closing."""
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class _AcpProtocolLimitError(AcpTransportError):
|
|
481
|
+
"""Deterministic cap or budget violation; never retryable pre-stateful."""
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class _ObserverBuffer:
|
|
485
|
+
"""Bounded update lane plus an unmetered control-sentinel lane."""
|
|
486
|
+
|
|
487
|
+
def __init__(self) -> None:
|
|
488
|
+
self._items: deque[tuple[dict[str, Any] | _PromptBarrier, int]] = deque()
|
|
489
|
+
self._data_items = 0
|
|
490
|
+
self._data_bytes = 0
|
|
491
|
+
self._ready = asyncio.Event()
|
|
492
|
+
self._closed = False
|
|
493
|
+
|
|
494
|
+
def put_update(self, params: dict[str, Any]) -> None:
|
|
495
|
+
# Updates are retained (buffered) and then published, so they ride the
|
|
496
|
+
# same §7.4 caps as every other retained frame. Validated tool-image
|
|
497
|
+
# encodings are the sole exception because base64 expands the shared
|
|
498
|
+
# 3 MiB image limit beyond the generic string and payload ceilings.
|
|
499
|
+
try:
|
|
500
|
+
_validate_update_payload_caps(params)
|
|
501
|
+
except ValueError as exc:
|
|
502
|
+
raise AcpTransportError("An ACP update exceeds the retained-payload caps.", cause=exc) from exc
|
|
503
|
+
size = len(encode_protocol_json(params))
|
|
504
|
+
if self._data_items >= _MAX_UPDATE_ITEMS or self._data_bytes + size > _MAX_UPDATE_BYTES:
|
|
505
|
+
raise AcpTransportError("The ACP update buffer exceeded its safety budget.")
|
|
506
|
+
self._items.append((params, size))
|
|
507
|
+
self._data_items += 1
|
|
508
|
+
self._data_bytes += size
|
|
509
|
+
self._ready.set()
|
|
510
|
+
|
|
511
|
+
def put_barrier(self, request_id: int) -> None:
|
|
512
|
+
self._items.append((_PromptBarrier(request_id), 0))
|
|
513
|
+
self._ready.set()
|
|
514
|
+
|
|
515
|
+
async def get(self) -> dict[str, Any] | _PromptBarrier:
|
|
516
|
+
while not self._items:
|
|
517
|
+
if self._closed:
|
|
518
|
+
raise EOFError
|
|
519
|
+
self._ready.clear()
|
|
520
|
+
await self._ready.wait()
|
|
521
|
+
item, size = self._items.popleft()
|
|
522
|
+
if size:
|
|
523
|
+
self._data_items -= 1
|
|
524
|
+
self._data_bytes -= size
|
|
525
|
+
return item
|
|
526
|
+
|
|
527
|
+
def close(self) -> None:
|
|
528
|
+
self._closed = True
|
|
529
|
+
self._ready.set()
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
@dataclass(slots=True)
|
|
533
|
+
class _InboundRequest:
|
|
534
|
+
method: str
|
|
535
|
+
human_wait: bool
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
class _FrameObserver:
|
|
539
|
+
"""Synchronous SDK observer retaining exact wire order without raising."""
|
|
540
|
+
|
|
541
|
+
def __init__(
|
|
542
|
+
self,
|
|
543
|
+
*,
|
|
544
|
+
store: _TrackingStateStore,
|
|
545
|
+
updates: _ObserverBuffer,
|
|
546
|
+
active_session: Callable[[], str | None],
|
|
547
|
+
fail: Callable[[BaseException], None],
|
|
548
|
+
activity: Callable[[], None],
|
|
549
|
+
) -> None:
|
|
550
|
+
self._store = store
|
|
551
|
+
self._updates = updates
|
|
552
|
+
self._active_session = active_session
|
|
553
|
+
self._fail = fail
|
|
554
|
+
self._activity = activity
|
|
555
|
+
self._inbound: dict[str | int, _InboundRequest] = {}
|
|
556
|
+
self._human_waits = 0
|
|
557
|
+
self._sealed = False
|
|
558
|
+
|
|
559
|
+
@property
|
|
560
|
+
def human_wait_count(self) -> int:
|
|
561
|
+
return self._human_waits
|
|
562
|
+
|
|
563
|
+
@property
|
|
564
|
+
def prompt_sealed(self) -> bool:
|
|
565
|
+
"""Whether the exact prompt response has been observed on the wire."""
|
|
566
|
+
return self._sealed
|
|
567
|
+
|
|
568
|
+
def human_wait_admitted(self, request_id: str | int | None) -> bool:
|
|
569
|
+
"""Return whether this request holds one of the capped human-wait slots."""
|
|
570
|
+
if request_id is None:
|
|
571
|
+
return False
|
|
572
|
+
request = self._inbound.get(request_id)
|
|
573
|
+
return request is not None and request.human_wait
|
|
574
|
+
|
|
575
|
+
def __call__(self, event: StreamEvent) -> None:
|
|
576
|
+
try:
|
|
577
|
+
if event.direction is StreamDirection.INCOMING:
|
|
578
|
+
self._observe_incoming(event.message)
|
|
579
|
+
else:
|
|
580
|
+
self._observe_outgoing(event.message)
|
|
581
|
+
except BaseException as exc:
|
|
582
|
+
failure = (
|
|
583
|
+
exc
|
|
584
|
+
if isinstance(exc, AcpTransportError)
|
|
585
|
+
else AcpTransportError(
|
|
586
|
+
"The ACP frame observer failed.",
|
|
587
|
+
cause=exc,
|
|
588
|
+
)
|
|
589
|
+
)
|
|
590
|
+
self._fail(failure)
|
|
591
|
+
|
|
592
|
+
def _observe_incoming(self, message: dict[str, Any]) -> None:
|
|
593
|
+
if "method" in message and "id" in message:
|
|
594
|
+
request_id = message["id"]
|
|
595
|
+
if request_id in self._inbound:
|
|
596
|
+
raise ValueError("Duplicate outstanding inbound JSON-RPC id.")
|
|
597
|
+
method = message["method"]
|
|
598
|
+
active_session = self._is_active_session_frame(message)
|
|
599
|
+
# Registration is capped here, at arrival: an uncapped flag from a
|
|
600
|
+
# request the dispatcher never admits would suspend idle expiry
|
|
601
|
+
# forever. Unflagged requests are auto-rejected by the callbacks.
|
|
602
|
+
# A sealed attempt admits no new waits: the turn is already over.
|
|
603
|
+
human_wait = (
|
|
604
|
+
method in _HUMAN_METHODS
|
|
605
|
+
and active_session
|
|
606
|
+
and self._human_waits < _MAX_HUMAN_WAITS
|
|
607
|
+
and not self._sealed
|
|
608
|
+
)
|
|
609
|
+
self._inbound[request_id] = _InboundRequest(method=method, human_wait=human_wait)
|
|
610
|
+
if human_wait:
|
|
611
|
+
self._human_waits += 1
|
|
612
|
+
if active_session:
|
|
613
|
+
self._activity()
|
|
614
|
+
return
|
|
615
|
+
|
|
616
|
+
if "method" in message:
|
|
617
|
+
if message["method"] == _UPDATE_METHOD:
|
|
618
|
+
if self._sealed:
|
|
619
|
+
# Wire order is queue order, so anything observed after
|
|
620
|
+
# the exact prompt response would land behind the barrier
|
|
621
|
+
# and leak into the sink after prompt() returned.
|
|
622
|
+
logger.debug("Dropping an ACP session/update observed after the prompt response")
|
|
623
|
+
return
|
|
624
|
+
if not self._is_active_session_frame(message):
|
|
625
|
+
# Foreign traffic is inert by contract: it must not
|
|
626
|
+
# consume the active attempt's update budget, and a
|
|
627
|
+
# malformed or oversized foreign frame must not be able
|
|
628
|
+
# to fail the attempt from here or from the consumer.
|
|
629
|
+
logger.debug("Dropping an ACP session/update for a foreign session")
|
|
630
|
+
return
|
|
631
|
+
self._activity()
|
|
632
|
+
self._updates.put_update(message.get("params", {}))
|
|
633
|
+
elif self._is_active_session_frame(message):
|
|
634
|
+
self._activity()
|
|
635
|
+
return
|
|
636
|
+
|
|
637
|
+
request_id = message["id"]
|
|
638
|
+
# The pump already ran ``observe_response`` in frame order (a response
|
|
639
|
+
# coalesced with EOF must be tracked before the EOF classification);
|
|
640
|
+
# this branch only orders seal/barrier against the queued updates.
|
|
641
|
+
record = self._store.outstanding_record(request_id)
|
|
642
|
+
if record is None or not record.response_observed:
|
|
643
|
+
return
|
|
644
|
+
self._activity()
|
|
645
|
+
if record.method == _PROMPT_METHOD:
|
|
646
|
+
if record.request_id == self._store.prompt_id:
|
|
647
|
+
self._seal_prompt()
|
|
648
|
+
self._updates.put_barrier(record.request_id)
|
|
649
|
+
|
|
650
|
+
def _seal_prompt(self) -> None:
|
|
651
|
+
"""Mark the attempt terminal at the exact prompt response.
|
|
652
|
+
|
|
653
|
+
Sealing happens synchronously in the pump's frame order, so every
|
|
654
|
+
later frame — updates, new human requests — sees it, and the idle
|
|
655
|
+
watchdog can never blame the agent for local post-response drains.
|
|
656
|
+
Outstanding human-wait slots are released here: their callbacks are
|
|
657
|
+
auto-denied (not yet started) or cancelled (in flight) by prompt()'s
|
|
658
|
+
completion path and must not suspend idle expiry meanwhile.
|
|
659
|
+
"""
|
|
660
|
+
self._sealed = True
|
|
661
|
+
for request in self._inbound.values():
|
|
662
|
+
request.human_wait = False
|
|
663
|
+
self._human_waits = 0
|
|
664
|
+
|
|
665
|
+
def _observe_outgoing(self, message: dict[str, Any]) -> None:
|
|
666
|
+
# Inbound ids are retired at the response-write seam
|
|
667
|
+
# (before_response_send). This observer runs only after the SDK
|
|
668
|
+
# finishes its send, and the peer may legally reuse an id the moment
|
|
669
|
+
# it reads the response — popping here could retire the reused id's
|
|
670
|
+
# fresh registration.
|
|
671
|
+
return
|
|
672
|
+
|
|
673
|
+
def before_response_send(self, request_id: str | int) -> None:
|
|
674
|
+
# The write seam is the id's end of life: writer.write follows this
|
|
675
|
+
# call synchronously, and every response flows through the retained
|
|
676
|
+
# sender, so retirement here is total and the peer may reuse the id
|
|
677
|
+
# as soon as it reads the response.
|
|
678
|
+
request = self._inbound.pop(request_id, None)
|
|
679
|
+
if request is not None and request.human_wait:
|
|
680
|
+
request.human_wait = False
|
|
681
|
+
self._human_waits -= 1
|
|
682
|
+
self._activity()
|
|
683
|
+
|
|
684
|
+
def clear_human_wait(self, request_id: str | int | None) -> None:
|
|
685
|
+
if request_id is None:
|
|
686
|
+
return
|
|
687
|
+
request = self._inbound.get(request_id)
|
|
688
|
+
if request is not None and request.human_wait:
|
|
689
|
+
request.human_wait = False
|
|
690
|
+
self._human_waits -= 1
|
|
691
|
+
self._activity()
|
|
692
|
+
|
|
693
|
+
def clear(self) -> None:
|
|
694
|
+
self._inbound.clear()
|
|
695
|
+
self._human_waits = 0
|
|
696
|
+
self._activity()
|
|
697
|
+
|
|
698
|
+
def _is_active_session_frame(self, message: dict[str, Any]) -> bool:
|
|
699
|
+
return _is_active_session_payload(message, self._active_session())
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def _is_active_session_payload(message: dict[str, Any], active: str | None) -> bool:
|
|
703
|
+
if active is None:
|
|
704
|
+
return False
|
|
705
|
+
params = message.get("params")
|
|
706
|
+
return type(params) is dict and type(params.get("sessionId")) is str and params["sessionId"] == active
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
class _PauseTransport:
|
|
710
|
+
"""StreamReader pause/resume hook that backpressures the raw framing pump."""
|
|
711
|
+
|
|
712
|
+
def __init__(self) -> None:
|
|
713
|
+
self._reading = asyncio.Event()
|
|
714
|
+
self._reading.set()
|
|
715
|
+
|
|
716
|
+
async def wait_until_reading(self) -> None:
|
|
717
|
+
await self._reading.wait()
|
|
718
|
+
|
|
719
|
+
def pause_reading(self) -> None:
|
|
720
|
+
self._reading.clear()
|
|
721
|
+
|
|
722
|
+
def resume_reading(self) -> None:
|
|
723
|
+
self._reading.set()
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
class _FramingPump:
|
|
727
|
+
"""Validate and meter raw stdout before forwarding frames to the SDK."""
|
|
728
|
+
|
|
729
|
+
def __init__(
|
|
730
|
+
self,
|
|
731
|
+
raw_reader: asyncio.StreamReader,
|
|
732
|
+
sdk_reader: asyncio.StreamReader,
|
|
733
|
+
pause_transport: _PauseTransport,
|
|
734
|
+
*,
|
|
735
|
+
stateful: Callable[[], bool],
|
|
736
|
+
closing: Callable[[], bool],
|
|
737
|
+
preflight: Callable[[dict[str, Any]], dict[str, Any]],
|
|
738
|
+
on_response: Callable[[dict[str, Any]], None],
|
|
739
|
+
active_session: Callable[[], str | None],
|
|
740
|
+
fail: Callable[[BaseException], None],
|
|
741
|
+
) -> None:
|
|
742
|
+
self._raw_reader = raw_reader
|
|
743
|
+
self._sdk_reader = sdk_reader
|
|
744
|
+
self._pause_transport = pause_transport
|
|
745
|
+
self._stateful = stateful
|
|
746
|
+
self._closing = closing
|
|
747
|
+
self._preflight = preflight
|
|
748
|
+
self._on_response = on_response
|
|
749
|
+
self._active_session = active_session
|
|
750
|
+
self._fail = fail
|
|
751
|
+
self._frames = 0
|
|
752
|
+
self._bytes = 0
|
|
753
|
+
self._request_frames = 0
|
|
754
|
+
self._task: asyncio.Task[None] | None = None
|
|
755
|
+
|
|
756
|
+
@property
|
|
757
|
+
def frames(self) -> int:
|
|
758
|
+
return self._frames
|
|
759
|
+
|
|
760
|
+
@property
|
|
761
|
+
def bytes(self) -> int:
|
|
762
|
+
return self._bytes
|
|
763
|
+
|
|
764
|
+
def start(self) -> None:
|
|
765
|
+
self._task = asyncio.create_task(self._run(), name="chrys.acp.framing")
|
|
766
|
+
|
|
767
|
+
async def stop(self) -> None:
|
|
768
|
+
task = self._task
|
|
769
|
+
if task is None:
|
|
770
|
+
return
|
|
771
|
+
if task is asyncio.current_task():
|
|
772
|
+
return
|
|
773
|
+
if not task.done():
|
|
774
|
+
task.cancel()
|
|
775
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
776
|
+
await task
|
|
777
|
+
self._task = None
|
|
778
|
+
# The SDK receive loop blocks on this reader; without EOF it would
|
|
779
|
+
# outlive a close whose SDK-side shutdown never runs (wedged sender).
|
|
780
|
+
self._sdk_reader.feed_eof()
|
|
781
|
+
|
|
782
|
+
async def _run(self) -> None:
|
|
783
|
+
try:
|
|
784
|
+
while True:
|
|
785
|
+
await self._pause_transport.wait_until_reading()
|
|
786
|
+
try:
|
|
787
|
+
line = await self._raw_reader.readline()
|
|
788
|
+
except ValueError as exc:
|
|
789
|
+
raise _AcpProtocolLimitError("The ACP agent emitted an oversized stdout frame.", cause=exc) from exc
|
|
790
|
+
if not line:
|
|
791
|
+
if not self._closing():
|
|
792
|
+
failure = (
|
|
793
|
+
_AcpDisconnectError("The ACP process closed its protocol stream.")
|
|
794
|
+
if self._stateful()
|
|
795
|
+
else classify_protocol_frame_error(
|
|
796
|
+
EOFError("ACP stdout closed."),
|
|
797
|
+
stateful=False,
|
|
798
|
+
complete=False,
|
|
799
|
+
)
|
|
800
|
+
)
|
|
801
|
+
self._fail(failure)
|
|
802
|
+
self._sdk_reader.feed_eof()
|
|
803
|
+
return
|
|
804
|
+
self._frames += 1
|
|
805
|
+
self._bytes += len(line)
|
|
806
|
+
if self._frames > _MAX_ATTEMPT_FRAMES or self._bytes > _MAX_ATTEMPT_BYTES:
|
|
807
|
+
raise _AcpProtocolLimitError("The ACP inbound frame budget was exhausted.")
|
|
808
|
+
|
|
809
|
+
complete = line.endswith(b"\n")
|
|
810
|
+
raw = line[:-1]
|
|
811
|
+
if raw.endswith(b"\r"):
|
|
812
|
+
raw = raw[:-1]
|
|
813
|
+
if not complete:
|
|
814
|
+
raise classify_protocol_frame_error(
|
|
815
|
+
EOFError("ACP frame ended without a newline."),
|
|
816
|
+
stateful=self._stateful(),
|
|
817
|
+
complete=False,
|
|
818
|
+
)
|
|
819
|
+
try:
|
|
820
|
+
message = parse_protocol_json(raw)
|
|
821
|
+
kind = validate_json_rpc_envelope(message)
|
|
822
|
+
except BaseException as exc:
|
|
823
|
+
raise classify_protocol_frame_error(
|
|
824
|
+
exc,
|
|
825
|
+
stateful=self._stateful(),
|
|
826
|
+
complete=True,
|
|
827
|
+
) from exc
|
|
828
|
+
if kind == "request":
|
|
829
|
+
self._request_frames += 1
|
|
830
|
+
if self._request_frames > _MAX_REQUEST_FRAMES:
|
|
831
|
+
raise _AcpProtocolLimitError("The ACP inbound request-frame budget was exhausted.")
|
|
832
|
+
message = self._preflight(message)
|
|
833
|
+
raw = encode_protocol_json(message)
|
|
834
|
+
elif kind == "notification":
|
|
835
|
+
if message.get("method") == _UPDATE_METHOD and not _is_active_session_payload(
|
|
836
|
+
message, self._active_session()
|
|
837
|
+
):
|
|
838
|
+
# Foreign traffic is inert by contract even when
|
|
839
|
+
# oversized: it must be dropped before the caps can
|
|
840
|
+
# turn it transport-fatal. A flood still burns the
|
|
841
|
+
# attempt frame budget above, so it stays bounded.
|
|
842
|
+
logger.debug("Dropping an ACP session/update for a foreign session at the pump")
|
|
843
|
+
continue
|
|
844
|
+
# The SDK re-parses every forwarded notification and the
|
|
845
|
+
# dispatcher caps only session/update, so ext/unknown
|
|
846
|
+
# methods must ride the retained-payload caps here.
|
|
847
|
+
# Notifications owe no response, so a violation is
|
|
848
|
+
# transport-fatal (matching put_update) rather than
|
|
849
|
+
# substituted like request params.
|
|
850
|
+
try:
|
|
851
|
+
params = message.get("params")
|
|
852
|
+
if message.get("method") == _UPDATE_METHOD:
|
|
853
|
+
_validate_update_payload_caps(params)
|
|
854
|
+
else:
|
|
855
|
+
_validate_payload_caps(params)
|
|
856
|
+
except ValueError as exc:
|
|
857
|
+
raise _AcpProtocolLimitError(
|
|
858
|
+
"An ACP notification exceeds the retained-payload caps.",
|
|
859
|
+
cause=exc,
|
|
860
|
+
) from exc
|
|
861
|
+
elif kind == "response":
|
|
862
|
+
# The pump is the single serial point ahead of SDK routing;
|
|
863
|
+
# announcing here is what keeps preflight/observer session
|
|
864
|
+
# checks race-free for frames written back-to-back with the
|
|
865
|
+
# session/new response.
|
|
866
|
+
self._on_response(message)
|
|
867
|
+
self._sdk_reader.feed_data(raw + b"\n")
|
|
868
|
+
except asyncio.CancelledError:
|
|
869
|
+
raise
|
|
870
|
+
except BaseException as exc:
|
|
871
|
+
self._fail(exc)
|
|
872
|
+
self._sdk_reader.feed_eof()
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def _extract_raw_usage(message: dict[str, Any]) -> AcpPromptUsage | None:
|
|
876
|
+
result = message.get("result")
|
|
877
|
+
usage = result.get("usage") if type(result) is dict else None
|
|
878
|
+
if usage is None:
|
|
879
|
+
error = message.get("error")
|
|
880
|
+
data = error.get("data") if type(error) is dict else None
|
|
881
|
+
usage = data.get("usage") if type(data) is dict else None
|
|
882
|
+
if type(usage) is not dict:
|
|
883
|
+
return None
|
|
884
|
+
required = ("inputTokens", "outputTokens", "totalTokens")
|
|
885
|
+
optional = ("cachedReadTokens", "cachedWriteTokens", "thoughtTokens")
|
|
886
|
+
if any(not _valid_usage_int(usage.get(field)) for field in required):
|
|
887
|
+
return None
|
|
888
|
+
if any(field in usage and usage[field] is not None and not _valid_usage_int(usage[field]) for field in optional):
|
|
889
|
+
return None
|
|
890
|
+
return AcpPromptUsage(
|
|
891
|
+
input_tokens=usage["inputTokens"],
|
|
892
|
+
output_tokens=usage["outputTokens"],
|
|
893
|
+
total_tokens=usage["totalTokens"],
|
|
894
|
+
cached_read_tokens=usage.get("cachedReadTokens"),
|
|
895
|
+
cached_write_tokens=usage.get("cachedWriteTokens"),
|
|
896
|
+
thought_tokens=usage.get("thoughtTokens"),
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def _valid_usage_int(value: Any) -> bool:
|
|
901
|
+
return type(value) is int and 0 <= value <= _MAX_USAGE_VALUE
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
async def _reap_task(
|
|
905
|
+
task: asyncio.Task[Any],
|
|
906
|
+
*,
|
|
907
|
+
timeout_seconds: float = _CLOSE_STAGE_TIMEOUT_SECONDS,
|
|
908
|
+
) -> None:
|
|
909
|
+
"""Bounded task reap that cannot be held hostage by swallowed cancellation.
|
|
910
|
+
|
|
911
|
+
``await task`` would forward an outer cancellation into the awaited task
|
|
912
|
+
(``Task.cancel`` cancels ``_fut_waiter``); a task that swallows that single
|
|
913
|
+
CancelledError would strand this reaper after ``asyncio.timeout`` spent its
|
|
914
|
+
one shot. ``asyncio.wait`` never cancels the watched task and returns at
|
|
915
|
+
the deadline unconditionally, so the close ladder below stays reachable.
|
|
916
|
+
"""
|
|
917
|
+
task.add_done_callback(_consume_future_exception)
|
|
918
|
+
pending: set[asyncio.Task[Any]] = {task}
|
|
919
|
+
with contextlib.suppress(asyncio.CancelledError, Exception):
|
|
920
|
+
_, pending = await asyncio.wait(pending, timeout=timeout_seconds)
|
|
921
|
+
for stuck in pending:
|
|
922
|
+
stuck.cancel()
|