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,1031 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""MCP adapter — wraps MCP server tools as Chrys FunctionTools.
|
|
4
|
+
|
|
5
|
+
Uses Chrys-owned MCPStdioTool and MCPStreamableHTTPTool implementations to
|
|
6
|
+
connect to MCP servers and expose their tools as FunctionTool instances.
|
|
7
|
+
|
|
8
|
+
Failure model
|
|
9
|
+
-------------
|
|
10
|
+
``connect()`` raises :class:`MCPConnectionError` on failure; ``connect_all()``
|
|
11
|
+
catches ordinary per-server connection failures so one unavailable server
|
|
12
|
+
cannot block the agent build. Invalid or colliding tool names are configuration
|
|
13
|
+
errors and fail closed through :class:`MCPToolConfigurationError`; otherwise
|
|
14
|
+
the provider may reject the request or Chrys's last-wins tool map could execute
|
|
15
|
+
the wrong function. Ordinary failures are published to the ``EventBus`` as
|
|
16
|
+
``Warning`` events (``code=
|
|
17
|
+
"mcp.connect_failed"``) and retained on :attr:`MCPAdapter.failures`.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import contextlib
|
|
24
|
+
import dataclasses
|
|
25
|
+
import logging
|
|
26
|
+
import re
|
|
27
|
+
from collections import Counter, deque
|
|
28
|
+
from collections.abc import Awaitable, Callable, Collection, Mapping
|
|
29
|
+
from html import escape as xml_escape
|
|
30
|
+
from typing import TYPE_CHECKING, Any
|
|
31
|
+
|
|
32
|
+
from chrys.foundation.branding import APP_DISPLAY_NAME
|
|
33
|
+
from chrys.foundation.tool_call_context import get_tool_context
|
|
34
|
+
from chrys.kernel.tools import FunctionTool
|
|
35
|
+
from chrys.service.mcp import _connection
|
|
36
|
+
from chrys.service.mcp._connection import DEFAULT_CONNECT_TIMEOUT_SECONDS as DEFAULT_CONNECT_TIMEOUT_SECONDS
|
|
37
|
+
from chrys.service.mcp._progressive import ProgressiveMCPResumeProvider, _ProgressiveMCPExposure
|
|
38
|
+
from chrys.service.mcp._stdio_transport import MAX_BANNER_LINE_CHARS as MAX_BANNER_LINE_CHARS
|
|
39
|
+
from chrys.service.mcp._stdio_transport import MAX_BANNER_LINES_CAPTURED as MAX_BANNER_LINES_CAPTURED
|
|
40
|
+
from chrys.service.mcp._stdio_transport import MAX_STDIO_PATH_DISPLAY_CHARS as MAX_STDIO_PATH_DISPLAY_CHARS
|
|
41
|
+
from chrys.service.mcp._stdio_transport import MAX_STDIO_STDERR_BYTES_CAPTURED as MAX_STDIO_STDERR_BYTES_CAPTURED
|
|
42
|
+
from chrys.service.mcp._stdio_transport import MAX_STDIO_STDERR_PREVIEW_LINES as MAX_STDIO_STDERR_PREVIEW_LINES
|
|
43
|
+
from chrys.service.mcp._stdio_transport import _StdioFailureDiagnostics
|
|
44
|
+
from chrys.service.mcp._stdio_transport import tolerant_stdio_client as tolerant_stdio_client
|
|
45
|
+
from chrys.service.mcp.cache import MCPConnectionCache, MCPConnectionLease, clone_mcp_function_tool
|
|
46
|
+
from chrys.service.mcp.errors import (
|
|
47
|
+
MCPConnectionError,
|
|
48
|
+
MCPToolConfigurationError,
|
|
49
|
+
MCPToolNameAmbiguityError,
|
|
50
|
+
MCPToolNameCollisionError,
|
|
51
|
+
MCPToolNameValidationError,
|
|
52
|
+
)
|
|
53
|
+
from chrys.service.mcp.owned import _MCP_REMOTE_NAME_KEY, _mcp_config_names_for
|
|
54
|
+
from chrys.service.mcp.result_limits import (
|
|
55
|
+
DEFAULT_MCP_TOOL_RESULT_MAX_TOKENS as DEFAULT_MCP_TOOL_RESULT_MAX_TOKENS,
|
|
56
|
+
)
|
|
57
|
+
from chrys.service.mcp.result_limits import resolve_mcp_result_cap
|
|
58
|
+
from chrys.service.mcp.validation import validate_mcp_exposed_tool_name
|
|
59
|
+
|
|
60
|
+
if TYPE_CHECKING:
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
|
|
63
|
+
from chrys.foundation.events.bus import EventBus
|
|
64
|
+
from chrys.service.mcp.owned import MCPTool
|
|
65
|
+
from chrys.service.profiles.agents.schema import MCPServerConfig
|
|
66
|
+
|
|
67
|
+
logger = logging.getLogger(__name__)
|
|
68
|
+
|
|
69
|
+
# Hard timeouts that bound how long a misbehaving MCP server can hold up the
|
|
70
|
+
# UI / agent build. The MCP SDK's per-request timeout
|
|
71
|
+
# (``read_timeout_seconds``) defaults to ``None`` (wait forever), and a
|
|
72
|
+
# server that emits welcome banner text instead of a valid ``initialize``
|
|
73
|
+
# response will leave the client blocked indefinitely on
|
|
74
|
+
# ``response_stream_reader.receive()``. These floors are injected into
|
|
75
|
+
# the MCP tool's ``request_timeout`` when the user didn't set one, so the
|
|
76
|
+
# SDK's own timeout fires from *inside* MCPTool's lifecycle-owner
|
|
77
|
+
# task. We intentionally do **not** wrap ``__aenter__`` in
|
|
78
|
+
# ``asyncio.wait_for`` from the outside: the lifecycle owner is a
|
|
79
|
+
# separate ``asyncio.Task`` (see ``MCPTool._run_lifecycle_owner``) which
|
|
80
|
+
# ``wait_for`` cannot cancel safely (anyio cancel scopes are scoped per
|
|
81
|
+
# task and cross-task cancellation raises "Attempted to exit cancel scope
|
|
82
|
+
# in a different task"). ``MCPServerConfig.request_timeout`` (when set)
|
|
83
|
+
# always takes precedence — these are only the defaults applied when no
|
|
84
|
+
# per-server timeout is configured.
|
|
85
|
+
DEFAULT_TEST_TIMEOUT_SECONDS = 30
|
|
86
|
+
|
|
87
|
+
# Cap per-server InitializeResult.instructions rendered into the
|
|
88
|
+
# <mcp_instructions> reminder. The value is server-controlled and repeated
|
|
89
|
+
# on every LLM call, so it must stay bounded no matter what the server
|
|
90
|
+
# sends. The budget is charged against the *rendered* (escaped + indented)
|
|
91
|
+
# text — capping the raw input instead would let escape expansion multiply
|
|
92
|
+
# the cap (e.g. every '"' becomes '"'). 4k rendered characters fits
|
|
93
|
+
# real-world server instructions comfortably; the aggregate is bounded by
|
|
94
|
+
# the number of user-configured servers.
|
|
95
|
+
MCP_INSTRUCTIONS_CHAR_LIMIT = 4_000
|
|
96
|
+
|
|
97
|
+
# A trailing entity fragment left behind when a rendered line is cut at the
|
|
98
|
+
# budget boundary (e.g. '&quo'). Complete entities end in ';' and don't match.
|
|
99
|
+
_SEVERED_ENTITY_TAIL_RE = re.compile(r"&[#0-9A-Za-z]{0,9}$")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclasses.dataclass(frozen=True)
|
|
103
|
+
class MCPTestReport:
|
|
104
|
+
"""What a successful one-shot connection test learned about a server.
|
|
105
|
+
|
|
106
|
+
Every string field is server-controlled: renderers must sanitize each
|
|
107
|
+
one against markup/structure injection before display. ``capabilities``
|
|
108
|
+
holds dotted feature names the server advertised (``tools``,
|
|
109
|
+
``tools.listChanged``, …) flattened from ``ServerCapabilities``.
|
|
110
|
+
``initial_tool_names`` is the progressive-disclosure starting surface,
|
|
111
|
+
or ``None`` when progressive disclosure is off.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
server_name: str | None
|
|
115
|
+
server_title: str | None
|
|
116
|
+
server_version: str | None
|
|
117
|
+
protocol_version: str | None
|
|
118
|
+
capabilities: tuple[str, ...]
|
|
119
|
+
instructions: str | None
|
|
120
|
+
tools: tuple[tuple[str, str], ...]
|
|
121
|
+
prompts: tuple[tuple[str, str], ...]
|
|
122
|
+
initial_tool_names: tuple[str, ...] | None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Standard MCP server capability groups (the spec's named feature sets).
|
|
126
|
+
# The report renderer always shows these; the flattener records them ahead
|
|
127
|
+
# of extras so the entry cap can never make a declared one look
|
|
128
|
+
# unadvertised.
|
|
129
|
+
STANDARD_CAPABILITY_GROUPS = ("completions", "logging", "prompts", "resources", "tools")
|
|
130
|
+
|
|
131
|
+
# Bounds for flattening server-controlled capability trees: a hostile
|
|
132
|
+
# server could otherwise inflate the report with unbounded extra keys or
|
|
133
|
+
# deep nesting (``ServerCapabilities`` accepts extras).
|
|
134
|
+
_MAX_CAPABILITY_ENTRIES = 128
|
|
135
|
+
_MAX_CAPABILITY_DEPTH = 4
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _top_capability_order(key: object) -> tuple[bool, str]:
|
|
139
|
+
return (str(key) not in STANDARD_CAPABILITY_GROUPS, str(key))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _flatten_server_capabilities(capabilities: Any) -> tuple[str, ...]:
|
|
143
|
+
"""Flatten advertised ``ServerCapabilities`` into sorted dotted feature names.
|
|
144
|
+
|
|
145
|
+
Walks nested capability objects (e.g. ``tasks.requests.tools``):
|
|
146
|
+
``False`` leaves are omitted, any other advertised value marks presence.
|
|
147
|
+
Traversal is breadth-first with standard groups first at the top level,
|
|
148
|
+
so the entry cap only ever drops the deepest/extra branches — a declared
|
|
149
|
+
standard capability is always recorded.
|
|
150
|
+
"""
|
|
151
|
+
if capabilities is None:
|
|
152
|
+
return ()
|
|
153
|
+
try:
|
|
154
|
+
dumped = capabilities.model_dump(exclude_none=True)
|
|
155
|
+
except Exception:
|
|
156
|
+
return ()
|
|
157
|
+
if not isinstance(dumped, dict):
|
|
158
|
+
return ()
|
|
159
|
+
flattened: list[str] = []
|
|
160
|
+
queue: deque[tuple[str, dict[Any, Any], int]] = deque([("", dumped, 1)])
|
|
161
|
+
while queue:
|
|
162
|
+
prefix, mapping, depth = queue.popleft()
|
|
163
|
+
for key in sorted(mapping, key=_top_capability_order if depth == 1 else str):
|
|
164
|
+
if len(flattened) >= _MAX_CAPABILITY_ENTRIES:
|
|
165
|
+
return tuple(sorted(flattened))
|
|
166
|
+
value = mapping[key]
|
|
167
|
+
if value is False or value is None:
|
|
168
|
+
continue
|
|
169
|
+
path = f"{prefix}.{key}" if prefix else str(key)
|
|
170
|
+
flattened.append(path)
|
|
171
|
+
if isinstance(value, dict) and depth < _MAX_CAPABILITY_DEPTH:
|
|
172
|
+
queue.append((path, value, depth + 1))
|
|
173
|
+
return tuple(sorted(flattened))
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _mcp_remote_name(tool: FunctionTool) -> str:
|
|
177
|
+
remote_name = (tool.additional_properties or {}).get(_MCP_REMOTE_NAME_KEY)
|
|
178
|
+
return remote_name if isinstance(remote_name, str) else tool.name
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _sole_config_name(tool: FunctionTool, catalog: list[FunctionTool]) -> str | None:
|
|
182
|
+
"""Return the first ``allowed_tools``/``always_load`` name that selects *tool* and no other catalog tool."""
|
|
183
|
+
others = [other for other in catalog if other is not tool]
|
|
184
|
+
return next(
|
|
185
|
+
(
|
|
186
|
+
name
|
|
187
|
+
for name in _mcp_config_names_for(tool)
|
|
188
|
+
if all(name not in _mcp_config_names_for(other) for other in others)
|
|
189
|
+
),
|
|
190
|
+
None,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class MCPAdapter:
|
|
195
|
+
"""Manages MCP server connections and exposes their tools.
|
|
196
|
+
|
|
197
|
+
Supports stdio and HTTP (Streamable HTTP/SSE) transports as configured
|
|
198
|
+
in the agent profile YAML.
|
|
199
|
+
|
|
200
|
+
An ordinary per-server connection failure does not abort the agent build.
|
|
201
|
+
Deterministic tool-name collisions do abort it before any ambiguous tool
|
|
202
|
+
surface can reach the model. Ordinary failures are:
|
|
203
|
+
|
|
204
|
+
* published on the optional ``EventBus`` as ``Warning`` events
|
|
205
|
+
(``code="mcp.connect_failed"``) so the TUI can display them, and
|
|
206
|
+
* retained on :attr:`failures` for inspection by diagnostic UIs.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def __init__(
|
|
210
|
+
self,
|
|
211
|
+
bus: EventBus | None = None,
|
|
212
|
+
session_id: str | None = None,
|
|
213
|
+
*,
|
|
214
|
+
cache: MCPConnectionCache | None = None,
|
|
215
|
+
stdio_cwd: str | None = None,
|
|
216
|
+
session_dir: Path | None = None,
|
|
217
|
+
reserved_tool_names: Collection[str] = (),
|
|
218
|
+
) -> None:
|
|
219
|
+
self._cache = cache or MCPConnectionCache()
|
|
220
|
+
self._owns_cache = cache is None
|
|
221
|
+
self._leases: dict[str, MCPConnectionLease] = {}
|
|
222
|
+
# Compatibility/debug view of this adapter's acquired underlying
|
|
223
|
+
# MCPTool objects. Ownership still flows through ``_leases``.
|
|
224
|
+
self._servers: dict[str, MCPTool] = {}
|
|
225
|
+
self._functions_by_server: dict[str, list[FunctionTool]] = {}
|
|
226
|
+
# Per-server ``expose_instructions`` config, captured at registration.
|
|
227
|
+
# Adapter-level (not on the shared MCPTool): profiles sharing a cached
|
|
228
|
+
# connection may disagree on exposure.
|
|
229
|
+
self._instructions_exposure: dict[str, bool] = {}
|
|
230
|
+
self._progressive_exposures: dict[str, _ProgressiveMCPExposure] = {}
|
|
231
|
+
self._tool_namespace_by_server: dict[str, set[str]] = {}
|
|
232
|
+
self._connecting: dict[str, asyncio.Task[list[Any]]] = {}
|
|
233
|
+
self._failures: dict[str, MCPConnectionError] = {}
|
|
234
|
+
self._tool_names_by_server: dict[str, list[str]] = {}
|
|
235
|
+
self._bus = bus
|
|
236
|
+
self._session_id = session_id
|
|
237
|
+
self._stdio_cwd = stdio_cwd
|
|
238
|
+
self._session_dir = session_dir
|
|
239
|
+
self._reserved_tool_names = frozenset(reserved_tool_names)
|
|
240
|
+
# Serializes the registration critical section shared by ``connect``
|
|
241
|
+
# and ``disconnect``/``disconnect_all``. Without this, a ``disconnect_all``
|
|
242
|
+
# that snapshots ``_servers`` while a concurrent ``connect`` is mid-
|
|
243
|
+
# ``__aenter__`` would miss the about-to-be-registered server, leaving
|
|
244
|
+
# an orphaned subprocess. Tool ``__aexit__`` calls are intentionally
|
|
245
|
+
# performed outside the lock so parallel disconnect is preserved.
|
|
246
|
+
self._lock = asyncio.Lock()
|
|
247
|
+
|
|
248
|
+
def set_reserved_tool_names(self, reserved_tool_names: Collection[str]) -> None:
|
|
249
|
+
"""Replace names reserved from MCP namespace validation.
|
|
250
|
+
|
|
251
|
+
This is primarily used by configuration diagnostics whose surrounding
|
|
252
|
+
agent draft can change while the test panel remains mounted. Active
|
|
253
|
+
production adapters keep a stable namespace for their acquired leases.
|
|
254
|
+
"""
|
|
255
|
+
if self._leases or self._connecting:
|
|
256
|
+
raise RuntimeError("Cannot replace reserved MCP tool names while connections are active.")
|
|
257
|
+
self._reserved_tool_names = frozenset(reserved_tool_names)
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def _create_tool_with_timeout_floor(config: MCPServerConfig, default_timeout: int) -> Any:
|
|
261
|
+
"""Build the MCP tool, injecting the timeout floor when none is configured."""
|
|
262
|
+
return _connection._create_tool_with_timeout_floor(config, default_timeout)
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def _banner_lines_from(tool: Any) -> list[str]:
|
|
266
|
+
"""Extract banner diagnostics from a stdio tool, empty list otherwise."""
|
|
267
|
+
return _connection._banner_lines_from(tool)
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def _stdio_failure_diagnostics_from(tool: Any) -> _StdioFailureDiagnostics:
|
|
271
|
+
"""Snapshot bounded stdio subprocess diagnostics, empty otherwise."""
|
|
272
|
+
return _connection._stdio_failure_diagnostics_from(tool)
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def _is_timeout_cause(exc: BaseException) -> bool:
|
|
276
|
+
"""True if the exception chain contains an MCP/anyio read-timeout signal."""
|
|
277
|
+
return _connection._is_timeout_cause(exc)
|
|
278
|
+
|
|
279
|
+
def create_resume_provider(self) -> ProgressiveMCPResumeProvider | None:
|
|
280
|
+
"""Return the per-run refresh/resume provider when progressive servers exist."""
|
|
281
|
+
if not self._progressive_exposures:
|
|
282
|
+
return None
|
|
283
|
+
return ProgressiveMCPResumeProvider(self)
|
|
284
|
+
|
|
285
|
+
def get_server_instructions_map(self) -> dict[str, str]:
|
|
286
|
+
"""Return {server_name: instructions} for connected servers with instructions.
|
|
287
|
+
|
|
288
|
+
Servers whose config disabled ``expose_instructions`` are excluded.
|
|
289
|
+
"""
|
|
290
|
+
result: dict[str, str] = {}
|
|
291
|
+
for name, server in self._servers.items():
|
|
292
|
+
if not self._instructions_exposure.get(name, True):
|
|
293
|
+
continue
|
|
294
|
+
instructions = getattr(server, "_server_instructions", None)
|
|
295
|
+
if instructions:
|
|
296
|
+
result[name] = instructions
|
|
297
|
+
for name, lease in self._leases.items():
|
|
298
|
+
if name not in result and self._instructions_exposure.get(name, True):
|
|
299
|
+
instructions = getattr(lease.mcp_tool, "_server_instructions", None)
|
|
300
|
+
if instructions:
|
|
301
|
+
result[name] = instructions
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
def render_instructions_reminder(self) -> str | None:
|
|
305
|
+
"""Return an ``<mcp_instructions>`` block with all server instructions, or None.
|
|
306
|
+
|
|
307
|
+
Servers render in name order: ``connect_all`` registers concurrently,
|
|
308
|
+
so insertion order varies run to run and would leak that
|
|
309
|
+
nondeterminism into the prompt text. Per-server text is capped at
|
|
310
|
+
``MCP_INSTRUCTIONS_CHAR_LIMIT``: instructions are server-controlled
|
|
311
|
+
and injected into every LLM call, so an unbounded value from one
|
|
312
|
+
misbehaving server could push every request past the context limit.
|
|
313
|
+
"""
|
|
314
|
+
instructions_map = self.get_server_instructions_map()
|
|
315
|
+
if not instructions_map:
|
|
316
|
+
return None
|
|
317
|
+
lines = ["<mcp_instructions>"]
|
|
318
|
+
for name in sorted(instructions_map):
|
|
319
|
+
escaped_name = xml_escape(name, quote=True)
|
|
320
|
+
lines.append(f' <server name="{escaped_name}">')
|
|
321
|
+
budget = MCP_INSTRUCTIONS_CHAR_LIMIT
|
|
322
|
+
truncated = False
|
|
323
|
+
for line in instructions_map[name].strip().splitlines():
|
|
324
|
+
rendered = f" {xml_escape(line)}".rstrip()
|
|
325
|
+
# +1 charges the joining newline so a blank-line flood
|
|
326
|
+
# (zero-length rendered lines) cannot bypass the budget.
|
|
327
|
+
cost = len(rendered) + 1
|
|
328
|
+
if cost > budget:
|
|
329
|
+
if budget > 1:
|
|
330
|
+
lines.append(_SEVERED_ENTITY_TAIL_RE.sub("", rendered[: budget - 1]))
|
|
331
|
+
truncated = True
|
|
332
|
+
break
|
|
333
|
+
lines.append(rendered)
|
|
334
|
+
budget -= cost
|
|
335
|
+
if truncated:
|
|
336
|
+
lines.append(f" [instructions truncated: exceeded {MCP_INSTRUCTIONS_CHAR_LIMIT} characters]")
|
|
337
|
+
lines.append(" </server>")
|
|
338
|
+
lines.append("</mcp_instructions>")
|
|
339
|
+
return "\n".join(lines)
|
|
340
|
+
|
|
341
|
+
def _progressive_surface_tools(self) -> list[FunctionTool]:
|
|
342
|
+
"""Return current controls and always-loaded tools for a fresh run."""
|
|
343
|
+
return [tool for exposure in self._progressive_exposures.values() for tool in exposure.current_surface_tools]
|
|
344
|
+
|
|
345
|
+
def _validate_server_namespace(
|
|
346
|
+
self,
|
|
347
|
+
config: MCPServerConfig,
|
|
348
|
+
catalog: list[FunctionTool],
|
|
349
|
+
control_names: set[str],
|
|
350
|
+
) -> None:
|
|
351
|
+
"""Reject invalid or colliding names before tools reach the model."""
|
|
352
|
+
violations: list[str] = []
|
|
353
|
+
for tool in catalog:
|
|
354
|
+
error = validate_mcp_exposed_tool_name(tool.name)
|
|
355
|
+
if error is None:
|
|
356
|
+
continue
|
|
357
|
+
additional = tool.additional_properties or {}
|
|
358
|
+
remote_name = additional.get(_MCP_REMOTE_NAME_KEY)
|
|
359
|
+
origin = f" Remote catalog name: {remote_name!r}." if isinstance(remote_name, str) else ""
|
|
360
|
+
violations.append(f"{error}{origin}")
|
|
361
|
+
violations.extend(
|
|
362
|
+
f"{error} Generated progressive-disclosure control."
|
|
363
|
+
for control_name in sorted(control_names)
|
|
364
|
+
if (error := validate_mcp_exposed_tool_name(control_name)) is not None
|
|
365
|
+
)
|
|
366
|
+
if violations:
|
|
367
|
+
raise MCPToolNameValidationError(
|
|
368
|
+
config.name,
|
|
369
|
+
config.transport,
|
|
370
|
+
violations=violations,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
catalog_names = [tool.name for tool in catalog]
|
|
374
|
+
duplicate_catalog_names = {name for name, count in Counter(catalog_names).items() if count > 1}
|
|
375
|
+
if duplicate_catalog_names:
|
|
376
|
+
raise MCPToolNameCollisionError(
|
|
377
|
+
config.name,
|
|
378
|
+
config.transport,
|
|
379
|
+
conflicting_names=duplicate_catalog_names,
|
|
380
|
+
conflict_with=f"another permitted tool from MCP server {config.name!r}",
|
|
381
|
+
guidance=(
|
|
382
|
+
"Rename one of the remote entries, exclude the shared name from the Permitted Tool Set, "
|
|
383
|
+
"or disable 'Expose server prompts' when the duplicate is a prompt."
|
|
384
|
+
),
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# One configured name can be one tool's original name and another's
|
|
388
|
+
# prefixed name; both would pass the allowlist. Every tool a name
|
|
389
|
+
# selects survives that filter, so the permitted catalog shows it.
|
|
390
|
+
configured_names = dict.fromkeys([*(config.allowed_tools or ()), *config.always_load])
|
|
391
|
+
matches_by_name = {
|
|
392
|
+
name: [(_mcp_remote_name(tool), _sole_config_name(tool, catalog)) for tool in matches]
|
|
393
|
+
for name in configured_names
|
|
394
|
+
if len(matches := [tool for tool in catalog if name in _mcp_config_names_for(tool)]) > 1
|
|
395
|
+
}
|
|
396
|
+
if matches_by_name:
|
|
397
|
+
raise MCPToolNameAmbiguityError(config.name, config.transport, matches_by_name=matches_by_name)
|
|
398
|
+
|
|
399
|
+
catalog_name_set = set(catalog_names)
|
|
400
|
+
if control_collisions := catalog_name_set & control_names:
|
|
401
|
+
raise MCPToolNameCollisionError(
|
|
402
|
+
config.name,
|
|
403
|
+
config.transport,
|
|
404
|
+
conflicting_names=control_collisions,
|
|
405
|
+
conflict_with="a generated progressive-disclosure control",
|
|
406
|
+
guidance=(
|
|
407
|
+
"Exclude the remote tool, choose a non-conflicting Tool Name Prefix, "
|
|
408
|
+
"or disable progressive loading."
|
|
409
|
+
),
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
namespace = catalog_name_set | control_names
|
|
413
|
+
if chrys_collisions := namespace & self._reserved_tool_names:
|
|
414
|
+
raise MCPToolNameCollisionError(
|
|
415
|
+
config.name,
|
|
416
|
+
config.transport,
|
|
417
|
+
conflicting_names=chrys_collisions,
|
|
418
|
+
conflict_with=f"a reserved {APP_DISPLAY_NAME} tool",
|
|
419
|
+
guidance="Exclude the remote tool from the Permitted Tool Set or configure a Tool Name Prefix.",
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
for other_server, other_namespace in self._tool_namespace_by_server.items():
|
|
423
|
+
if other_server == config.name:
|
|
424
|
+
continue
|
|
425
|
+
if cross_server_collisions := namespace & other_namespace:
|
|
426
|
+
raise MCPToolNameCollisionError(
|
|
427
|
+
config.name,
|
|
428
|
+
config.transport,
|
|
429
|
+
conflicting_names=cross_server_collisions,
|
|
430
|
+
conflict_with=f"MCP server {other_server!r}",
|
|
431
|
+
guidance="Configure distinct Tool Name Prefix values or exclude the duplicate tool.",
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
if config.name in self._leases:
|
|
435
|
+
self._tool_namespace_by_server[config.name] = namespace
|
|
436
|
+
|
|
437
|
+
def _current_functions_for_server(
|
|
438
|
+
self,
|
|
439
|
+
name: str,
|
|
440
|
+
lease: MCPConnectionLease,
|
|
441
|
+
) -> list[FunctionTool]:
|
|
442
|
+
"""Return a fresh progressive surface or the ordinary cached functions."""
|
|
443
|
+
exposure = self._progressive_exposures.get(name)
|
|
444
|
+
if exposure is not None:
|
|
445
|
+
return exposure.initial_tools
|
|
446
|
+
return list(self._functions_by_server.get(name, lease.functions))
|
|
447
|
+
|
|
448
|
+
def _context_tools_without_collisions(
|
|
449
|
+
self,
|
|
450
|
+
visible_tools: list[Any],
|
|
451
|
+
candidates: list[FunctionTool],
|
|
452
|
+
) -> list[FunctionTool]:
|
|
453
|
+
"""Return missing candidates, rejecting foreign same-name tools loudly."""
|
|
454
|
+
visible_by_name: dict[str, list[Any]] = {}
|
|
455
|
+
for tool in visible_tools:
|
|
456
|
+
if isinstance(tool, FunctionTool):
|
|
457
|
+
visible_by_name.setdefault(tool.name, []).append(tool)
|
|
458
|
+
continue
|
|
459
|
+
if isinstance(tool, Mapping):
|
|
460
|
+
function = tool.get("function")
|
|
461
|
+
if isinstance(function, Mapping) and isinstance(function.get("name"), str):
|
|
462
|
+
visible_by_name.setdefault(function["name"], []).append(tool)
|
|
463
|
+
additions: list[FunctionTool] = []
|
|
464
|
+
for candidate in candidates:
|
|
465
|
+
existing = visible_by_name.get(candidate.name, [])
|
|
466
|
+
if not existing:
|
|
467
|
+
additions.append(candidate)
|
|
468
|
+
visible_by_name[candidate.name] = [candidate]
|
|
469
|
+
continue
|
|
470
|
+
|
|
471
|
+
provenance = get_tool_context(candidate)
|
|
472
|
+
server_name = provenance.get("server_name") if provenance is not None else None
|
|
473
|
+
exposure = self._progressive_exposures.get(server_name) if isinstance(server_name, str) else None
|
|
474
|
+
if (
|
|
475
|
+
len(existing) == 1
|
|
476
|
+
and exposure is not None
|
|
477
|
+
and isinstance(existing[0], FunctionTool)
|
|
478
|
+
and exposure._same_surface_identity(existing[0], candidate)
|
|
479
|
+
):
|
|
480
|
+
continue
|
|
481
|
+
config = exposure._config if exposure is not None else None
|
|
482
|
+
raise MCPToolNameCollisionError(
|
|
483
|
+
config.name if config is not None else server_name or "unknown",
|
|
484
|
+
config.transport if config is not None else "unknown",
|
|
485
|
+
conflicting_names={candidate.name},
|
|
486
|
+
conflict_with="another visible tool contributed to this run",
|
|
487
|
+
guidance="Configure a distinct Tool Name Prefix or remove the conflicting tool.",
|
|
488
|
+
)
|
|
489
|
+
return additions
|
|
490
|
+
|
|
491
|
+
def _resume_tools(self, calls: list[tuple[str, str | None]]) -> list[FunctionTool]:
|
|
492
|
+
"""Clone uniquely resolved hidden MCP tools referenced by pending calls."""
|
|
493
|
+
tools: list[FunctionTool] = []
|
|
494
|
+
added_names: set[str] = set()
|
|
495
|
+
for name, server_name in calls:
|
|
496
|
+
if name in added_names:
|
|
497
|
+
continue
|
|
498
|
+
if server_name is not None:
|
|
499
|
+
exposure = self._progressive_exposures.get(server_name)
|
|
500
|
+
source = exposure.resume_source(name) if exposure is not None else None
|
|
501
|
+
matches = [source] if source is not None else []
|
|
502
|
+
else:
|
|
503
|
+
matches = [
|
|
504
|
+
source
|
|
505
|
+
for exposure in self._progressive_exposures.values()
|
|
506
|
+
if (source := exposure.resume_source(name)) is not None
|
|
507
|
+
]
|
|
508
|
+
if len(matches) == 1:
|
|
509
|
+
match_context = get_tool_context(matches[0])
|
|
510
|
+
matched_server = (
|
|
511
|
+
server_name
|
|
512
|
+
if server_name is not None
|
|
513
|
+
else match_context.get("server_name")
|
|
514
|
+
if match_context is not None
|
|
515
|
+
else None
|
|
516
|
+
)
|
|
517
|
+
if (
|
|
518
|
+
isinstance(matched_server, str)
|
|
519
|
+
and (exposure := self._progressive_exposures.get(matched_server)) is not None
|
|
520
|
+
):
|
|
521
|
+
tools.append(exposure.clone_source(matches[0]))
|
|
522
|
+
else:
|
|
523
|
+
tools.append(clone_mcp_function_tool(matches[0]))
|
|
524
|
+
added_names.add(name)
|
|
525
|
+
elif len(matches) > 1:
|
|
526
|
+
logger.warning(
|
|
527
|
+
"Cannot resume ambiguous progressive MCP tool %r; configure distinct tool_name_prefix values.",
|
|
528
|
+
name,
|
|
529
|
+
)
|
|
530
|
+
return tools
|
|
531
|
+
|
|
532
|
+
async def connect(self, config: MCPServerConfig) -> list[Any]:
|
|
533
|
+
"""Connect to an MCP server and return its tools as FunctionTools.
|
|
534
|
+
|
|
535
|
+
Idempotent: calling with a previously-connected name returns the
|
|
536
|
+
cached tool list. Bounded by a hard timeout floor
|
|
537
|
+
(``DEFAULT_CONNECT_TIMEOUT_SECONDS``) injected into the SDK's
|
|
538
|
+
``request_timeout`` when the user didn't configure one — so a
|
|
539
|
+
hung server cannot block the agent build.
|
|
540
|
+
|
|
541
|
+
Raises:
|
|
542
|
+
MCPConnectionError: If the server fails to connect, initialize,
|
|
543
|
+
or exceeds the connect timeout. ``asyncio.CancelledError``
|
|
544
|
+
is propagated unchanged after the partially-opened transport
|
|
545
|
+
is closed.
|
|
546
|
+
"""
|
|
547
|
+
# Validate before cache lookup as well as connection creation: a cache
|
|
548
|
+
# hit must not let malformed wrapper-only progressive fields bypass the
|
|
549
|
+
# checks performed by ``_create_mcp_tool`` on a cold connection.
|
|
550
|
+
_connection._validate_config(config)
|
|
551
|
+
async with self._lock:
|
|
552
|
+
lease = self._leases.get(config.name)
|
|
553
|
+
if lease is not None:
|
|
554
|
+
return self._current_functions_for_server(config.name, lease)
|
|
555
|
+
task = self._connecting.get(config.name)
|
|
556
|
+
if task is None:
|
|
557
|
+
task = asyncio.create_task(self._acquire_new(config))
|
|
558
|
+
self._connecting[config.name] = task
|
|
559
|
+
|
|
560
|
+
def _discard_finished(done: asyncio.Task[list[Any]], name: str = config.name) -> None:
|
|
561
|
+
if self._connecting.get(name) is done:
|
|
562
|
+
self._connecting.pop(name, None)
|
|
563
|
+
|
|
564
|
+
task.add_done_callback(_discard_finished)
|
|
565
|
+
|
|
566
|
+
try:
|
|
567
|
+
return await task
|
|
568
|
+
finally:
|
|
569
|
+
if task.done():
|
|
570
|
+
async with self._lock:
|
|
571
|
+
if self._connecting.get(config.name) is task:
|
|
572
|
+
self._connecting.pop(config.name, None)
|
|
573
|
+
|
|
574
|
+
async def _connect_new(self, config: MCPServerConfig) -> list[Any]:
|
|
575
|
+
"""Open or lease a new MCP server connection and register it on success."""
|
|
576
|
+
current_task = asyncio.current_task()
|
|
577
|
+
inserted = False
|
|
578
|
+
if current_task is not None:
|
|
579
|
+
async with self._lock:
|
|
580
|
+
if config.name not in self._connecting:
|
|
581
|
+
self._connecting[config.name] = current_task
|
|
582
|
+
inserted = True
|
|
583
|
+
try:
|
|
584
|
+
return await self._acquire_new(config)
|
|
585
|
+
finally:
|
|
586
|
+
if inserted:
|
|
587
|
+
async with self._lock:
|
|
588
|
+
if self._connecting.get(config.name) is current_task:
|
|
589
|
+
self._connecting.pop(config.name, None)
|
|
590
|
+
|
|
591
|
+
async def _acquire_new(self, config: MCPServerConfig) -> list[Any]:
|
|
592
|
+
"""Acquire a cache lease and register it on this adapter."""
|
|
593
|
+
lease: MCPConnectionLease | None = None
|
|
594
|
+
duplicate_tools: list[Any] | None = None
|
|
595
|
+
release_duplicate = False
|
|
596
|
+
prune_private_cache = False
|
|
597
|
+
registered = False
|
|
598
|
+
exposure: _ProgressiveMCPExposure | None = None
|
|
599
|
+
delivered_tools: list[FunctionTool]
|
|
600
|
+
catalog: list[FunctionTool]
|
|
601
|
+
control_names: set[str] = set()
|
|
602
|
+
namespace: set[str]
|
|
603
|
+
|
|
604
|
+
try:
|
|
605
|
+
lease = await self._cache.acquire(config, stdio_cwd=self._stdio_cwd)
|
|
606
|
+
except MCPConnectionError as err:
|
|
607
|
+
self._failures[config.name] = err
|
|
608
|
+
raise
|
|
609
|
+
if lease is None:
|
|
610
|
+
raise RuntimeError("Acquiring an MCP connection did not return a lease.")
|
|
611
|
+
|
|
612
|
+
try:
|
|
613
|
+
catalog = [tool for tool in lease.mcp_tool.functions if isinstance(tool, FunctionTool)]
|
|
614
|
+
result_cap = resolve_mcp_result_cap(config.max_tool_result_tokens)
|
|
615
|
+
if config.use_progressive_disclosure:
|
|
616
|
+
exposure = _ProgressiveMCPExposure(
|
|
617
|
+
config,
|
|
618
|
+
lease.mcp_tool,
|
|
619
|
+
self._validate_server_namespace,
|
|
620
|
+
result_cap=result_cap,
|
|
621
|
+
spill_dir=self._session_dir,
|
|
622
|
+
)
|
|
623
|
+
control_names = exposure.control_names
|
|
624
|
+
delivered_tools = exposure.initial_tools
|
|
625
|
+
else:
|
|
626
|
+
self._validate_server_namespace(config, catalog, control_names)
|
|
627
|
+
delivered_tools = [
|
|
628
|
+
clone_mcp_function_tool(source, result_cap=result_cap, spill_dir=self._session_dir)
|
|
629
|
+
for source in catalog
|
|
630
|
+
]
|
|
631
|
+
namespace = {tool.name for tool in catalog} | control_names
|
|
632
|
+
except Exception:
|
|
633
|
+
await self._release_unregistered_lease(config.name, lease)
|
|
634
|
+
raise
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
async with self._lock:
|
|
638
|
+
existing = self._leases.get(config.name)
|
|
639
|
+
if existing is not None:
|
|
640
|
+
duplicate_tools = self._current_functions_for_server(config.name, existing)
|
|
641
|
+
release_duplicate = True
|
|
642
|
+
elif (existing_server := self._servers.get(config.name)) is not None:
|
|
643
|
+
# existing_server.functions belong to the owned MCP engine.
|
|
644
|
+
# Every outward path must deliver Chrys FunctionTool clones
|
|
645
|
+
# so each acquisition keeps its own tool state and result cap.
|
|
646
|
+
result_cap = resolve_mcp_result_cap(config.max_tool_result_tokens)
|
|
647
|
+
duplicate_tools = [
|
|
648
|
+
clone_mcp_function_tool(t, result_cap=result_cap, spill_dir=self._session_dir)
|
|
649
|
+
for t in existing_server.functions
|
|
650
|
+
]
|
|
651
|
+
release_duplicate = True
|
|
652
|
+
prune_private_cache = self._owns_cache
|
|
653
|
+
else:
|
|
654
|
+
current_task = asyncio.current_task()
|
|
655
|
+
if self._connecting.get(config.name) is not current_task:
|
|
656
|
+
raise asyncio.CancelledError
|
|
657
|
+
# Re-check under the registration lock so parallel server
|
|
658
|
+
# connects cannot both pass against an empty namespace.
|
|
659
|
+
self._validate_server_namespace(config, catalog, control_names)
|
|
660
|
+
self._leases[config.name] = lease
|
|
661
|
+
self._servers[config.name] = lease.mcp_tool
|
|
662
|
+
self._functions_by_server[config.name] = delivered_tools
|
|
663
|
+
self._instructions_exposure[config.name] = config.expose_instructions
|
|
664
|
+
self._tool_namespace_by_server[config.name] = namespace
|
|
665
|
+
if exposure is not None:
|
|
666
|
+
self._progressive_exposures[config.name] = exposure
|
|
667
|
+
registered = True
|
|
668
|
+
except BaseException:
|
|
669
|
+
if lease is not None and not registered:
|
|
670
|
+
await self._release_unregistered_lease(config.name, lease)
|
|
671
|
+
raise
|
|
672
|
+
|
|
673
|
+
if release_duplicate:
|
|
674
|
+
await self._release_unregistered_lease(config.name, lease)
|
|
675
|
+
if prune_private_cache:
|
|
676
|
+
await self._cache.prune_idle(max_idle_seconds=0)
|
|
677
|
+
if duplicate_tools is None:
|
|
678
|
+
raise RuntimeError("A duplicate MCP registration has no registered tools.")
|
|
679
|
+
return duplicate_tools
|
|
680
|
+
|
|
681
|
+
self._failures.pop(config.name, None)
|
|
682
|
+
if exposure is not None and (missing := exposure.unmatched_always_load_names):
|
|
683
|
+
missing_names = ", ".join(missing)
|
|
684
|
+
await self._publish_warning(
|
|
685
|
+
code="mcp.always_load_missing",
|
|
686
|
+
message=(
|
|
687
|
+
f"MCP server '{config.name}' did not advertise configured initially visible "
|
|
688
|
+
f"tool(s): {missing_names}. They will be picked up automatically if advertised later."
|
|
689
|
+
),
|
|
690
|
+
)
|
|
691
|
+
logger.debug(
|
|
692
|
+
"Connected to MCP server '%s' (%s): %d tools loaded",
|
|
693
|
+
config.name,
|
|
694
|
+
config.transport,
|
|
695
|
+
len(lease.functions),
|
|
696
|
+
)
|
|
697
|
+
return list(delivered_tools)
|
|
698
|
+
|
|
699
|
+
async def _release_unregistered_lease(self, name: str, lease: MCPConnectionLease) -> None:
|
|
700
|
+
try:
|
|
701
|
+
await self._finish_cleanup_on_cancel(self._cache.release(lease))
|
|
702
|
+
except Exception:
|
|
703
|
+
logger.exception("Error releasing duplicate MCP lease for '%s'", name)
|
|
704
|
+
raise
|
|
705
|
+
|
|
706
|
+
async def _finish_cleanup_on_cancel(self, awaitable: Awaitable[Any]) -> None:
|
|
707
|
+
"""Let cleanup finish before propagating caller cancellation."""
|
|
708
|
+
cleanup = asyncio.ensure_future(awaitable)
|
|
709
|
+
try:
|
|
710
|
+
await asyncio.shield(cleanup)
|
|
711
|
+
except asyncio.CancelledError:
|
|
712
|
+
with contextlib.suppress(Exception):
|
|
713
|
+
await cleanup
|
|
714
|
+
raise
|
|
715
|
+
|
|
716
|
+
async def test_connection(self, config: MCPServerConfig) -> MCPTestReport:
|
|
717
|
+
"""Attempt a one-shot connection test for an MCP server.
|
|
718
|
+
|
|
719
|
+
Establishes the server connection, snapshots what it advertised
|
|
720
|
+
(identity, capabilities, instructions, tool/prompt catalog) into an
|
|
721
|
+
:class:`MCPTestReport`, and immediately closes it. Bounded by
|
|
722
|
+
``DEFAULT_TEST_TIMEOUT_SECONDS`` injected into the SDK's
|
|
723
|
+
``request_timeout`` when none is configured — so the Test button
|
|
724
|
+
can never hang indefinitely.
|
|
725
|
+
|
|
726
|
+
Raises:
|
|
727
|
+
MCPConnectionError: If the server fails to connect, initialize,
|
|
728
|
+
or exceeds the test timeout. Carries any captured stdout
|
|
729
|
+
banner lines, bounded stderr tail, and child exit code so the
|
|
730
|
+
UI can show why a misbehaving server failed.
|
|
731
|
+
"""
|
|
732
|
+
mcp_tool: Any | None = None
|
|
733
|
+
try:
|
|
734
|
+
try:
|
|
735
|
+
mcp_tool = self._create_tool_with_timeout_floor(config, DEFAULT_TEST_TIMEOUT_SECONDS)
|
|
736
|
+
await mcp_tool.__aenter__()
|
|
737
|
+
catalog = [tool for tool in mcp_tool.functions if isinstance(tool, FunctionTool)]
|
|
738
|
+
initial_tool_names: tuple[str, ...] | None = None
|
|
739
|
+
if config.use_progressive_disclosure:
|
|
740
|
+
exposure = _ProgressiveMCPExposure(config, mcp_tool, self._validate_server_namespace)
|
|
741
|
+
# Materializing the surface validates both catalog names
|
|
742
|
+
# and generated controls through the production path.
|
|
743
|
+
initial_tool_names = tuple(tool.name for tool in exposure.initial_tools)
|
|
744
|
+
else:
|
|
745
|
+
self._validate_server_namespace(config, catalog, set())
|
|
746
|
+
return self._build_test_report(mcp_tool, catalog, initial_tool_names)
|
|
747
|
+
except MCPConnectionError:
|
|
748
|
+
raise
|
|
749
|
+
except Exception as exc:
|
|
750
|
+
diagnostics = self._stdio_failure_diagnostics_from(mcp_tool)
|
|
751
|
+
cause: BaseException = exc
|
|
752
|
+
if self._is_timeout_cause(exc):
|
|
753
|
+
timeout = getattr(mcp_tool, "request_timeout", DEFAULT_TEST_TIMEOUT_SECONDS)
|
|
754
|
+
cause = TimeoutError(
|
|
755
|
+
f"connection did not complete within {timeout}s (server may not be speaking MCP)"
|
|
756
|
+
)
|
|
757
|
+
raise MCPConnectionError(
|
|
758
|
+
config.name,
|
|
759
|
+
config.transport,
|
|
760
|
+
cause,
|
|
761
|
+
banner_lines=self._banner_lines_from(mcp_tool),
|
|
762
|
+
stderr_tail=diagnostics.stderr_tail,
|
|
763
|
+
stderr_dropped_bytes=diagnostics.stderr_dropped_bytes,
|
|
764
|
+
process_exit_code=diagnostics.process_exit_code,
|
|
765
|
+
resolved_executable=diagnostics.resolved_executable,
|
|
766
|
+
effective_cwd=diagnostics.effective_cwd,
|
|
767
|
+
) from exc
|
|
768
|
+
finally:
|
|
769
|
+
if mcp_tool is not None:
|
|
770
|
+
with contextlib.suppress(Exception):
|
|
771
|
+
await mcp_tool.__aexit__(None, None, None)
|
|
772
|
+
|
|
773
|
+
@staticmethod
|
|
774
|
+
def _build_test_report(
|
|
775
|
+
mcp_tool: Any,
|
|
776
|
+
catalog: list[FunctionTool],
|
|
777
|
+
initial_tool_names: tuple[str, ...] | None,
|
|
778
|
+
) -> MCPTestReport:
|
|
779
|
+
"""Snapshot server-advertised state before the test connection closes."""
|
|
780
|
+
server_info = getattr(mcp_tool, "_server_info", None)
|
|
781
|
+
prompt_remote_names = getattr(mcp_tool, "_loaded_prompt_remote_names", None) or set()
|
|
782
|
+
tools: list[tuple[str, str]] = []
|
|
783
|
+
prompts: list[tuple[str, str]] = []
|
|
784
|
+
for tool in catalog:
|
|
785
|
+
additional = tool.additional_properties or {}
|
|
786
|
+
remote_name = additional.get(_MCP_REMOTE_NAME_KEY)
|
|
787
|
+
entry = (tool.name, tool.description or "")
|
|
788
|
+
if isinstance(remote_name, str) and remote_name in prompt_remote_names:
|
|
789
|
+
prompts.append(entry)
|
|
790
|
+
else:
|
|
791
|
+
tools.append(entry)
|
|
792
|
+
return MCPTestReport(
|
|
793
|
+
server_name=getattr(server_info, "name", None),
|
|
794
|
+
server_title=getattr(server_info, "title", None),
|
|
795
|
+
server_version=getattr(server_info, "version", None),
|
|
796
|
+
protocol_version=getattr(mcp_tool, "_protocol_version", None),
|
|
797
|
+
capabilities=_flatten_server_capabilities(getattr(mcp_tool, "_server_capabilities", None)),
|
|
798
|
+
instructions=getattr(mcp_tool, "_server_instructions", None),
|
|
799
|
+
tools=tuple(tools),
|
|
800
|
+
prompts=tuple(prompts),
|
|
801
|
+
initial_tool_names=initial_tool_names,
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
async def connect_all(
|
|
805
|
+
self,
|
|
806
|
+
configs: list[MCPServerConfig],
|
|
807
|
+
*,
|
|
808
|
+
progress: Callable[[MCPServerConfig, str, int, int, int], Awaitable[None]] | None = None,
|
|
809
|
+
) -> list[Any]:
|
|
810
|
+
"""Connect to multiple enabled MCP servers and return all tools.
|
|
811
|
+
|
|
812
|
+
Per-server failures do not abort the sequence — they are recorded on
|
|
813
|
+
:attr:`failures` and published on the event bus as ``Warning`` events
|
|
814
|
+
so the TUI can display them to the user.
|
|
815
|
+
|
|
816
|
+
Connections run concurrently, so progress callbacks arrive in
|
|
817
|
+
completion order rather than profile declaration order. Callers
|
|
818
|
+
should treat ``current`` / ``failed`` / ``total`` as aggregate
|
|
819
|
+
counters and key any per-server UI by server name.
|
|
820
|
+
|
|
821
|
+
Any ordinary ``Exception`` is treated as a per-server failure and
|
|
822
|
+
dropped from the returned tool list. This includes the documented
|
|
823
|
+
``MCPConnectionError`` path and also defensive handling of
|
|
824
|
+
unexpected errors (e.g. ``ValueError`` from a malformed config
|
|
825
|
+
caught by ``_validate_config``, owned MCP engine errors,
|
|
826
|
+
``ExceptionGroup`` leaks from anyio task groups) — none of which
|
|
827
|
+
should be allowed to abort the agent build and leave the engine
|
|
828
|
+
half-initialised. ``MCPToolConfigurationError`` is the deliberate
|
|
829
|
+
exception: deterministic invalid/colliding tool names must abort the
|
|
830
|
+
build before a provider rejects the request or a last-wins tool map
|
|
831
|
+
selects the wrong function.
|
|
832
|
+
``BaseException`` subclasses (``CancelledError``,
|
|
833
|
+
``KeyboardInterrupt``, ``SystemExit``, and bare ``BaseExceptionGroup``)
|
|
834
|
+
propagate so structural cancellation / shutdown signals are never
|
|
835
|
+
swallowed.
|
|
836
|
+
"""
|
|
837
|
+
enabled_configs: list[MCPServerConfig] = []
|
|
838
|
+
for config in configs:
|
|
839
|
+
if not config.enabled:
|
|
840
|
+
logger.debug("Skipping disabled MCP server '%s'", config.name)
|
|
841
|
+
continue
|
|
842
|
+
enabled_configs.append(config)
|
|
843
|
+
self._tool_names_by_server = {}
|
|
844
|
+
|
|
845
|
+
total = len(enabled_configs)
|
|
846
|
+
connected = 0
|
|
847
|
+
failed = 0
|
|
848
|
+
|
|
849
|
+
async def _connect_one(config: MCPServerConfig) -> list[Any]:
|
|
850
|
+
nonlocal connected, failed
|
|
851
|
+
if progress is not None:
|
|
852
|
+
await progress(config, "starting", connected, total, failed)
|
|
853
|
+
try:
|
|
854
|
+
tools = await self.connect(config)
|
|
855
|
+
except asyncio.CancelledError:
|
|
856
|
+
raise
|
|
857
|
+
except MCPToolConfigurationError:
|
|
858
|
+
raise
|
|
859
|
+
except MCPConnectionError as exc:
|
|
860
|
+
await self._publish_failure(exc)
|
|
861
|
+
failed += 1
|
|
862
|
+
if progress is not None:
|
|
863
|
+
await progress(config, "failed", connected, total, failed)
|
|
864
|
+
return []
|
|
865
|
+
except Exception as exc:
|
|
866
|
+
err = MCPConnectionError(config.name, config.transport, exc)
|
|
867
|
+
self._failures[config.name] = err
|
|
868
|
+
await self._publish_failure(err)
|
|
869
|
+
failed += 1
|
|
870
|
+
if progress is not None:
|
|
871
|
+
await progress(config, "failed", connected, total, failed)
|
|
872
|
+
return []
|
|
873
|
+
connected += 1
|
|
874
|
+
exposure = self._progressive_exposures.get(config.name)
|
|
875
|
+
self._tool_names_by_server[config.name] = (
|
|
876
|
+
exposure.catalog_tool_names
|
|
877
|
+
if exposure is not None
|
|
878
|
+
else [getattr(tool, "name", str(tool)) for tool in tools]
|
|
879
|
+
)
|
|
880
|
+
if progress is not None:
|
|
881
|
+
await progress(config, "connected", connected, total, failed)
|
|
882
|
+
return tools
|
|
883
|
+
|
|
884
|
+
results = await asyncio.gather(*(_connect_one(config) for config in enabled_configs))
|
|
885
|
+
self._tool_names_by_server = {
|
|
886
|
+
config.name: self._tool_names_by_server[config.name]
|
|
887
|
+
for config in enabled_configs
|
|
888
|
+
if config.name in self._tool_names_by_server
|
|
889
|
+
}
|
|
890
|
+
return [tool for tools in results for tool in tools]
|
|
891
|
+
|
|
892
|
+
async def disconnect(self, name: str) -> None:
|
|
893
|
+
"""Disconnect from an MCP server.
|
|
894
|
+
|
|
895
|
+
The lock is held only for the pop so that a concurrent ``connect``
|
|
896
|
+
cannot stamp the server back into ``_servers`` after we snapshotted
|
|
897
|
+
its absence. ``__aexit__`` runs outside the lock.
|
|
898
|
+
"""
|
|
899
|
+
async with self._lock:
|
|
900
|
+
task = self._connecting.pop(name, None)
|
|
901
|
+
lease = self._leases.pop(name, None)
|
|
902
|
+
server = self._servers.pop(name, None)
|
|
903
|
+
self._functions_by_server.pop(name, None)
|
|
904
|
+
self._instructions_exposure.pop(name, None)
|
|
905
|
+
self._progressive_exposures.pop(name, None)
|
|
906
|
+
self._tool_namespace_by_server.pop(name, None)
|
|
907
|
+
if task is not None:
|
|
908
|
+
task.cancel()
|
|
909
|
+
with contextlib.suppress(asyncio.CancelledError, Exception):
|
|
910
|
+
await task
|
|
911
|
+
if lease is None:
|
|
912
|
+
if server is not None:
|
|
913
|
+
try:
|
|
914
|
+
await server.__aexit__(None, None, None)
|
|
915
|
+
except Exception:
|
|
916
|
+
logger.exception("Error disconnecting MCP server '%s'", name)
|
|
917
|
+
return
|
|
918
|
+
try:
|
|
919
|
+
await self._cache.release(lease)
|
|
920
|
+
if self._owns_cache:
|
|
921
|
+
await self._cache.prune_idle(max_idle_seconds=0)
|
|
922
|
+
except Exception:
|
|
923
|
+
logger.exception("Error disconnecting MCP server '%s'", name)
|
|
924
|
+
|
|
925
|
+
async def disconnect_all(self) -> None:
|
|
926
|
+
"""Disconnect from all connected MCP servers in parallel.
|
|
927
|
+
|
|
928
|
+
The snapshot+clear runs under ``_lock`` so connected servers are
|
|
929
|
+
disconnected and in-flight connects are cancelled before they can
|
|
930
|
+
register a server. The per-tool ``__aexit__`` calls run outside
|
|
931
|
+
the lock so they proceed concurrently and a slow shutdown does not
|
|
932
|
+
block new connects.
|
|
933
|
+
"""
|
|
934
|
+
async with self._lock:
|
|
935
|
+
items = list(self._leases.items())
|
|
936
|
+
leased_server_names = set(self._leases)
|
|
937
|
+
fallback_items = [
|
|
938
|
+
(name, server) for name, server in self._servers.items() if name not in leased_server_names
|
|
939
|
+
]
|
|
940
|
+
connecting = list(self._connecting.values())
|
|
941
|
+
self._leases.clear()
|
|
942
|
+
self._connecting.clear()
|
|
943
|
+
self._servers.clear()
|
|
944
|
+
self._functions_by_server.clear()
|
|
945
|
+
self._instructions_exposure.clear()
|
|
946
|
+
self._progressive_exposures.clear()
|
|
947
|
+
self._tool_namespace_by_server.clear()
|
|
948
|
+
for task in connecting:
|
|
949
|
+
task.cancel()
|
|
950
|
+
if connecting:
|
|
951
|
+
await self._finish_cleanup_on_cancel(asyncio.gather(*connecting, return_exceptions=True))
|
|
952
|
+
|
|
953
|
+
async def _release_one(name: str, lease: MCPConnectionLease) -> None:
|
|
954
|
+
try:
|
|
955
|
+
await self._cache.release(lease)
|
|
956
|
+
except Exception:
|
|
957
|
+
logger.exception("Error disconnecting MCP server '%s'", name)
|
|
958
|
+
|
|
959
|
+
if items:
|
|
960
|
+
await self._finish_cleanup_on_cancel(
|
|
961
|
+
asyncio.gather(*(_release_one(n, lease) for n, lease in items), return_exceptions=True)
|
|
962
|
+
)
|
|
963
|
+
if fallback_items:
|
|
964
|
+
|
|
965
|
+
async def _exit_one(name: str, tool: MCPTool) -> None:
|
|
966
|
+
try:
|
|
967
|
+
await tool.__aexit__(None, None, None)
|
|
968
|
+
except Exception:
|
|
969
|
+
logger.exception("Error disconnecting MCP server '%s'", name)
|
|
970
|
+
|
|
971
|
+
await self._finish_cleanup_on_cancel(
|
|
972
|
+
asyncio.gather(*(_exit_one(n, t) for n, t in fallback_items), return_exceptions=True)
|
|
973
|
+
)
|
|
974
|
+
if self._owns_cache:
|
|
975
|
+
try:
|
|
976
|
+
await self._finish_cleanup_on_cancel(self._cache.close_all())
|
|
977
|
+
finally:
|
|
978
|
+
self._cache = MCPConnectionCache()
|
|
979
|
+
|
|
980
|
+
async def _publish_warning(self, *, code: str, message: str) -> None:
|
|
981
|
+
"""Log and best-effort publish one user-visible non-fatal warning."""
|
|
982
|
+
logger.warning("%s", message)
|
|
983
|
+
if self._bus is None:
|
|
984
|
+
return
|
|
985
|
+
from chrys.foundation.events.types import Warning as WarningEvent
|
|
986
|
+
|
|
987
|
+
with contextlib.suppress(Exception):
|
|
988
|
+
await self._bus.publish(
|
|
989
|
+
WarningEvent(
|
|
990
|
+
code=code,
|
|
991
|
+
message=message,
|
|
992
|
+
session_id=self._session_id,
|
|
993
|
+
)
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
async def _publish_failure(self, err: MCPConnectionError) -> None:
|
|
997
|
+
"""Log and, if a bus is configured, publish a user-visible Warning."""
|
|
998
|
+
logger.error(
|
|
999
|
+
"MCP server '%s' (%s) failed to connect: %s",
|
|
1000
|
+
err.server_name,
|
|
1001
|
+
err.transport,
|
|
1002
|
+
err.cause,
|
|
1003
|
+
exc_info=err.cause,
|
|
1004
|
+
)
|
|
1005
|
+
if self._bus is None:
|
|
1006
|
+
return
|
|
1007
|
+
from chrys.foundation.events.types import Warning as WarningEvent
|
|
1008
|
+
|
|
1009
|
+
with contextlib.suppress(Exception):
|
|
1010
|
+
await self._bus.publish(
|
|
1011
|
+
WarningEvent(
|
|
1012
|
+
code="mcp.connect_failed",
|
|
1013
|
+
message=str(err),
|
|
1014
|
+
session_id=self._session_id,
|
|
1015
|
+
)
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
@property
|
|
1019
|
+
def server_names(self) -> list[str]:
|
|
1020
|
+
"""Names of currently connected MCP servers."""
|
|
1021
|
+
return list(dict.fromkeys([*self._leases.keys(), *self._servers.keys()]))
|
|
1022
|
+
|
|
1023
|
+
@property
|
|
1024
|
+
def tool_names_by_server(self) -> dict[str, list[str]]:
|
|
1025
|
+
"""Snapshot of server-name -> loaded tool names from the last connect_all call."""
|
|
1026
|
+
return {name: list(tools) for name, tools in self._tool_names_by_server.items()}
|
|
1027
|
+
|
|
1028
|
+
@property
|
|
1029
|
+
def failures(self) -> dict[str, MCPConnectionError]:
|
|
1030
|
+
"""Snapshot of server-name → last connect failure."""
|
|
1031
|
+
return dict(self._failures)
|