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,967 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Shell execution tools — instance-based tools that hold SessionEnvironment.
|
|
4
|
+
|
|
5
|
+
Uses Chrys kernel instance tool support: @tool on class methods binds to the
|
|
6
|
+
instance via the descriptor protocol, giving each tool access to self._runtime
|
|
7
|
+
for working directory, platform info, etc.
|
|
8
|
+
|
|
9
|
+
On Unix, commands run with stdout/stderr attached to a PTY and
|
|
10
|
+
``TERM=dumb`` so programs see terminal output streams but suppress most ANSI
|
|
11
|
+
formatting. Stdin is ``/dev/null`` and the PTY is not installed as a
|
|
12
|
+
controlling terminal, so interactive terminal reads fail instead of triggering
|
|
13
|
+
job-control stops. On Windows, commands run with stdout/stderr pipes (no PTY
|
|
14
|
+
available).
|
|
15
|
+
|
|
16
|
+
Each output stream is captured within a byte limit that keeps its head and
|
|
17
|
+
tail (``foundation/platform/output_capture.py``), and the result is truncated
|
|
18
|
+
to ``max_tokens`` with a 1:2 head:tail ratio. Every backend first assembles
|
|
19
|
+
the same canonical result shape; when a session directory is available, the
|
|
20
|
+
cleaned result is spilled there before the bounded view is returned.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import asyncio
|
|
26
|
+
import codecs
|
|
27
|
+
import logging
|
|
28
|
+
import os
|
|
29
|
+
import struct
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
from collections import deque
|
|
33
|
+
from contextlib import AsyncExitStack, suppress
|
|
34
|
+
from contextvars import ContextVar
|
|
35
|
+
from typing import TYPE_CHECKING, Annotated, Any, cast
|
|
36
|
+
|
|
37
|
+
from chrys.foundation.platform.output_capture import BoundedCapture
|
|
38
|
+
from chrys.foundation.platform.paths import resolve_workspace_path
|
|
39
|
+
from chrys.foundation.platform.process import (
|
|
40
|
+
MissingWorkingDirectoryError,
|
|
41
|
+
SubprocessStoppedError,
|
|
42
|
+
decode_subprocess_output,
|
|
43
|
+
kill_process_group,
|
|
44
|
+
kill_process_session,
|
|
45
|
+
managed_subprocess,
|
|
46
|
+
raise_if_missing_cwd,
|
|
47
|
+
wait_for_subprocess,
|
|
48
|
+
)
|
|
49
|
+
from chrys.foundation.platform.pty_output import PtyOutputProtocol
|
|
50
|
+
from chrys.foundation.platform.runtime_paths import reorder_path_demoting_runtime
|
|
51
|
+
from chrys.foundation.platform.win_console import preserve_console_mode
|
|
52
|
+
from chrys.foundation.text.tool_output import (
|
|
53
|
+
process_carriage_returns as _process_carriage_returns,
|
|
54
|
+
)
|
|
55
|
+
from chrys.foundation.text.tool_output import (
|
|
56
|
+
strip_ansi as _strip_ansi,
|
|
57
|
+
)
|
|
58
|
+
from chrys.foundation.tool_result_metadata import (
|
|
59
|
+
PARTIAL_OUTPUT_LABEL,
|
|
60
|
+
SHELL_EXIT_CODE_METADATA_KEY,
|
|
61
|
+
SHELL_TIMED_OUT_METADATA_KEY,
|
|
62
|
+
SHELL_TIMEOUT_SECONDS_METADATA_KEY,
|
|
63
|
+
TOOL_ERRORED_METADATA_KEY,
|
|
64
|
+
)
|
|
65
|
+
from chrys.service.mutations.trace import shell_trace_argv
|
|
66
|
+
from chrys.service.tools.approval_targets import (
|
|
67
|
+
CHANGED_AFTER_APPROVAL,
|
|
68
|
+
ApprovedTargets,
|
|
69
|
+
approved_cwd,
|
|
70
|
+
physical_dir,
|
|
71
|
+
)
|
|
72
|
+
from chrys.service.tools.kinds import KIND_SHELL, set_tool_kind, tool
|
|
73
|
+
from chrys.service.tools.result_metadata import tool_error
|
|
74
|
+
from chrys.service.tools.spill import bound_process_output
|
|
75
|
+
from chrys.service.tools.workspace_paths import missing_base_cwd_error, working_dir_missing_error
|
|
76
|
+
|
|
77
|
+
if sys.platform != "win32":
|
|
78
|
+
import fcntl
|
|
79
|
+
import pty
|
|
80
|
+
import select
|
|
81
|
+
import signal
|
|
82
|
+
import termios
|
|
83
|
+
|
|
84
|
+
if TYPE_CHECKING:
|
|
85
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
86
|
+
from pathlib import Path
|
|
87
|
+
|
|
88
|
+
from chrys.foundation.models.session_env import SessionEnvironment
|
|
89
|
+
from chrys.foundation.platform import ShellInfo
|
|
90
|
+
from chrys.foundation.platform.output_capture import CapturedOutput
|
|
91
|
+
from chrys.service.tools.builtins.shell_filter import ShellCommandFilter
|
|
92
|
+
|
|
93
|
+
logger = logging.getLogger(__name__)
|
|
94
|
+
|
|
95
|
+
_DEFAULT_MAX_TOKENS = 8000
|
|
96
|
+
|
|
97
|
+
SHELL_REASON_DESCRIPTION = (
|
|
98
|
+
"Brief user-facing explanation of why this command needs to run (1-2 sentences). "
|
|
99
|
+
"Write it in the same language as the user's latest prompt whenever possible. "
|
|
100
|
+
"This reason is shown to the user and recorded in audit logs, so state the command's intent plainly "
|
|
101
|
+
"without private reasoning or hidden context."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# Shell execution context
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
shell_progress_callback: ContextVar[Callable[[list[str]], Awaitable[None]] | None] = ContextVar(
|
|
109
|
+
"shell_progress_callback", default=None
|
|
110
|
+
)
|
|
111
|
+
"""Contextvar for streaming shell output lines to the TUI.
|
|
112
|
+
|
|
113
|
+
Set by ``ToolEventMiddleware`` before shell tool execution. The callback
|
|
114
|
+
receives batches of cleaned output lines (ANSI-stripped, \\r handled).
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
shell_result_metadata: ContextVar[dict[str, object] | None] = ContextVar("shell_result_metadata", default=None)
|
|
118
|
+
"""Mutable metadata buffer set by ``ToolEventMiddleware`` for shell calls."""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _record_shell_exit_code(returncode: int) -> None:
|
|
122
|
+
metadata = shell_result_metadata.get(None)
|
|
123
|
+
if metadata is not None:
|
|
124
|
+
metadata[SHELL_EXIT_CODE_METADATA_KEY] = returncode
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _record_shell_timeout(timeout: int) -> None:
|
|
128
|
+
metadata = shell_result_metadata.get(None)
|
|
129
|
+
if metadata is not None:
|
|
130
|
+
metadata[SHELL_TIMED_OUT_METADATA_KEY] = True
|
|
131
|
+
metadata[SHELL_TIMEOUT_SECONDS_METADATA_KEY] = timeout
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _record_shell_error() -> None:
|
|
135
|
+
metadata = shell_result_metadata.get(None)
|
|
136
|
+
if metadata is not None:
|
|
137
|
+
metadata[TOOL_ERRORED_METADATA_KEY] = True
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _working_dir_not_found(resolved: str, working_dir: str) -> str:
|
|
141
|
+
return tool_error(
|
|
142
|
+
"working_dir_not_found",
|
|
143
|
+
f"working_dir not found — {resolved}",
|
|
144
|
+
details={"working_dir": working_dir, "resolved_path": resolved},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
_PROGRESS_THROTTLE_INTERVAL = 0.2
|
|
149
|
+
"""Minimum seconds between progress emissions to avoid flooding the TUI."""
|
|
150
|
+
|
|
151
|
+
_PROGRESS_LINE_LIMIT = 64 * 1024
|
|
152
|
+
"""Most of an unfinished output line held for progress; its start is dropped past this."""
|
|
153
|
+
|
|
154
|
+
_PROGRESS_PENDING_LIMIT = 256 * 1024
|
|
155
|
+
"""Most characters of progress lines held between emissions; the oldest lines go first."""
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class _ProgressLines:
|
|
159
|
+
"""Output lines waiting for the next progress emission, within a size limit.
|
|
160
|
+
|
|
161
|
+
Dropping lines here only thins the live progress display; the command's
|
|
162
|
+
result comes from its output capture.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(self) -> None:
|
|
166
|
+
self._lines: deque[str] = deque()
|
|
167
|
+
self._size = 0
|
|
168
|
+
|
|
169
|
+
def __bool__(self) -> bool:
|
|
170
|
+
return bool(self._lines)
|
|
171
|
+
|
|
172
|
+
def add(self, line: str) -> None:
|
|
173
|
+
# Each line counts its newline too, so blank lines fill the limit.
|
|
174
|
+
self._lines.append(line)
|
|
175
|
+
self._size += len(line) + 1
|
|
176
|
+
while self._size > _PROGRESS_PENDING_LIMIT and len(self._lines) > 1:
|
|
177
|
+
self._size -= len(self._lines.popleft()) + 1
|
|
178
|
+
|
|
179
|
+
def take(self) -> list[str]:
|
|
180
|
+
lines = list(self._lines)
|
|
181
|
+
self._lines.clear()
|
|
182
|
+
self._size = 0
|
|
183
|
+
return lines
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
_PTY_DRAIN_GRACE = 2.0
|
|
187
|
+
"""Seconds to let the PTY reader catch up before the last slave fd is closed."""
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def _await_pty_master_drained(master_fd: int, *, grace: float = _PTY_DRAIN_GRACE) -> None:
|
|
191
|
+
"""Yield until the PTY reader has taken everything the tty still holds.
|
|
192
|
+
|
|
193
|
+
macOS drops whatever is still buffered in a tty the moment its last slave fd
|
|
194
|
+
closes, so releasing the parent's copy the instant the child exits discards
|
|
195
|
+
the output of any command whose reader callback has not been scheduled yet —
|
|
196
|
+
a busy event loop turns ``echo hello`` into a bare ``[exit_code: 0]``. The
|
|
197
|
+
child has already exited by the time this runs, so nothing new can arrive:
|
|
198
|
+
once the master stops reporting readable, the reader holds everything that
|
|
199
|
+
was written. ``grace`` bounds the wait so a reader paused by flow control
|
|
200
|
+
cannot stall the command.
|
|
201
|
+
"""
|
|
202
|
+
# Unlike select(), poll() accepts descriptors above FD_SETSIZE (1024).
|
|
203
|
+
poller = select.poll()
|
|
204
|
+
poller.register(master_fd, select.POLLIN)
|
|
205
|
+
deadline = time.monotonic() + grace
|
|
206
|
+
while True:
|
|
207
|
+
try:
|
|
208
|
+
events = poller.poll(0)
|
|
209
|
+
if not events or any(event & select.POLLNVAL for _, event in events):
|
|
210
|
+
return
|
|
211
|
+
except OSError:
|
|
212
|
+
return
|
|
213
|
+
if time.monotonic() >= deadline:
|
|
214
|
+
return
|
|
215
|
+
await asyncio.sleep(0)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _kill_pty_process(
|
|
219
|
+
proc: asyncio.subprocess.Process,
|
|
220
|
+
process_group_id: int | None = None,
|
|
221
|
+
process_session_id: int | None = None,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Kill a PTY subprocess without signaling Chrys's own process group."""
|
|
224
|
+
if process_session_id is not None and kill_process_session(process_session_id):
|
|
225
|
+
return
|
|
226
|
+
if process_group_id is not None and kill_process_group(process_group_id):
|
|
227
|
+
return
|
|
228
|
+
if proc.returncode is not None or proc.pid is None:
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
posix_os = cast(Any, os)
|
|
232
|
+
with suppress(ProcessLookupError, PermissionError, OSError):
|
|
233
|
+
child_pgid = posix_os.getpgid(proc.pid)
|
|
234
|
+
parent_pgid = posix_os.getpgrp()
|
|
235
|
+
if child_pgid == proc.pid and child_pgid != parent_pgid:
|
|
236
|
+
posix_os.killpg(child_pgid, signal.SIGKILL)
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
with suppress(ProcessLookupError, OSError):
|
|
240
|
+
os.kill(proc.pid, signal.SIGKILL)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _clean_output(text: str) -> str:
|
|
244
|
+
return _process_carriage_returns(_strip_ansi(text))
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _format_partial_output(output: CapturedOutput | None, stderr: CapturedOutput | None) -> str:
|
|
248
|
+
"""Assemble what a timed-out command printed, or ``""`` when it printed nothing."""
|
|
249
|
+
parts: list[str] = []
|
|
250
|
+
body = output.text(_clean_output) if output is not None else ""
|
|
251
|
+
if body:
|
|
252
|
+
parts.append(f"{PARTIAL_OUTPUT_LABEL}\n{body}")
|
|
253
|
+
stderr_clean = stderr.text(_clean_output) if stderr is not None else ""
|
|
254
|
+
if stderr_clean:
|
|
255
|
+
parts.append(f"[partial stderr]\n{stderr_clean}")
|
|
256
|
+
return "\n".join(parts)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
_PIPEFAIL_SHELLS = frozenset({"bash", "zsh", "git_bash"})
|
|
260
|
+
"""Shells launched with ``-o pipefail`` so pipelines propagate upstream failures.
|
|
261
|
+
|
|
262
|
+
Without pipefail a pipeline's status is its last stage's, so
|
|
263
|
+
``make 2>&1 | tail -20`` reports success even when the build failed.
|
|
264
|
+
Deliberately conservative: a basename cannot prove capability (a ``ksh``
|
|
265
|
+
may be ksh88 without pipefail, ``sh`` may be an old dash), and a wrong
|
|
266
|
+
guess makes the shell reject its argv — every command would fail. Shells
|
|
267
|
+
not listed here keep their native pipeline semantics unchanged.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _pipefail_args(shell: ShellInfo) -> tuple[str, ...]:
|
|
272
|
+
"""Return extra launch args enabling pipefail for shells known to support it."""
|
|
273
|
+
return ("-o", "pipefail") if shell.name in _PIPEFAIL_SHELLS else ()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _shell_argv_variants(shell: ShellInfo, command: str) -> list[tuple[str, ...]]:
|
|
277
|
+
"""Launch argvs to try in order: traced first (when armed), then bare.
|
|
278
|
+
|
|
279
|
+
The mutation pipeline hands an fsatrace wrapper prefix over via
|
|
280
|
+
contextvar; wrapping happens at
|
|
281
|
+
argv level — no shell-quoting hazards. Spawn failure of the traced
|
|
282
|
+
variant must degrade to the bare one: tracing may never change
|
|
283
|
+
user-visible shell behavior.
|
|
284
|
+
"""
|
|
285
|
+
base = (shell.path, *_pipefail_args(shell), *shell.args, command)
|
|
286
|
+
prefix = shell_trace_argv.get()
|
|
287
|
+
if prefix:
|
|
288
|
+
return [(*prefix, *base), base]
|
|
289
|
+
return [base]
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _build_subprocess_env() -> dict[str, str]:
|
|
293
|
+
"""Build a colour-suppressed environment for shell subprocesses.
|
|
294
|
+
|
|
295
|
+
Shared by both the PTY (Unix) and pipe (Windows) execution paths.
|
|
296
|
+
"""
|
|
297
|
+
env = os.environ.copy()
|
|
298
|
+
env["TERM"] = "dumb"
|
|
299
|
+
env["CHRYS"] = "1"
|
|
300
|
+
env["NO_COLOR"] = "1"
|
|
301
|
+
# Force UTF-8 output from Python subprocesses (and other tools
|
|
302
|
+
# that respect these variables) so CJK text is never garbled.
|
|
303
|
+
env["PYTHONUTF8"] = "1"
|
|
304
|
+
env["PYTHONIOENCODING"] = "utf-8"
|
|
305
|
+
# Prevent git (and other tools) from launching a pager, which
|
|
306
|
+
# would block forever waiting for interactive input.
|
|
307
|
+
env["GIT_PAGER"] = "cat"
|
|
308
|
+
env["PAGER"] = "cat"
|
|
309
|
+
# Suppress colour-forcing variables
|
|
310
|
+
env.pop("FORCE_COLOR", None)
|
|
311
|
+
env.pop("COLORTERM", None)
|
|
312
|
+
env.pop("CLICOLOR_FORCE", None)
|
|
313
|
+
# Shell commands intentionally mirror the user's environment more closely
|
|
314
|
+
# than managed hook/skill/MCP subprocesses, so keep PYTHONHOME/PYTHONPATH.
|
|
315
|
+
reorder_path_demoting_runtime(env)
|
|
316
|
+
return env
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
_POWERSHELL_UTF8_PREAMBLE = "\n".join(
|
|
320
|
+
(
|
|
321
|
+
"$__chrysUtf8 = [System.Text.UTF8Encoding]::new($false)",
|
|
322
|
+
"try {",
|
|
323
|
+
" [Console]::InputEncoding = $__chrysUtf8",
|
|
324
|
+
" [Console]::OutputEncoding = $__chrysUtf8",
|
|
325
|
+
"} catch { }",
|
|
326
|
+
"$OutputEncoding = $__chrysUtf8",
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _prepare_pipe_command(command: str, shell: ShellInfo) -> str:
|
|
332
|
+
"""Adjust commands for pipe-based shells before execution."""
|
|
333
|
+
if shell.name not in ("pwsh", "powershell"):
|
|
334
|
+
return command
|
|
335
|
+
return f"{_POWERSHELL_UTF8_PREAMBLE}\n{command}"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
SHELL_TOOL_NAMES = frozenset(
|
|
339
|
+
{"bash", "zsh", "fish", "sh", "dash", "ksh", "tcsh", "csh", "pwsh", "powershell", "cmd", "git_bash"}
|
|
340
|
+
)
|
|
341
|
+
"""All possible names for shell command tools.
|
|
342
|
+
|
|
343
|
+
Shell command tools are named after their shell (e.g. ``zsh``, ``pwsh``, ``powershell``)
|
|
344
|
+
by ``ShellTools.tools()``. This set covers all shells that
|
|
345
|
+
``platform.py`` can detect. Used by TUI renderers for tool-specific display.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class ShellTools:
|
|
350
|
+
"""Instance-based shell tools backed by SessionEnvironment.
|
|
351
|
+
|
|
352
|
+
Each @tool-decorated method is a FunctionTool that binds to the instance,
|
|
353
|
+
so it can access self._runtime for cwd and platform info.
|
|
354
|
+
|
|
355
|
+
Usage::
|
|
356
|
+
|
|
357
|
+
runtime = SessionEnvironment.capture()
|
|
358
|
+
shell = ShellTools(runtime)
|
|
359
|
+
tools = shell.tools() # → [shell.<detected shell name>]
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def __init__(
|
|
363
|
+
self,
|
|
364
|
+
runtime: SessionEnvironment,
|
|
365
|
+
command_filter: ShellCommandFilter | None = None,
|
|
366
|
+
shell: ShellInfo | None = None,
|
|
367
|
+
session_dir: Path | None = None,
|
|
368
|
+
) -> None:
|
|
369
|
+
self._runtime = runtime
|
|
370
|
+
self._filter = command_filter
|
|
371
|
+
self._shell = shell or runtime.platform.shell
|
|
372
|
+
self._session_dir = session_dir
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def shell(self) -> ShellInfo:
|
|
376
|
+
"""Return the shell configuration used by this tool instance."""
|
|
377
|
+
return self._shell
|
|
378
|
+
|
|
379
|
+
def approval_targets(self, arguments: dict[str, object]) -> ApprovedTargets:
|
|
380
|
+
"""The directory a command will run in; an unresolvable one is left to ``execute``."""
|
|
381
|
+
working_dir = arguments.get("working_dir")
|
|
382
|
+
if working_dir is not None and not isinstance(working_dir, str):
|
|
383
|
+
return ApprovedTargets()
|
|
384
|
+
try:
|
|
385
|
+
return ApprovedTargets(cwd=physical_dir(self._command_cwd(working_dir)))
|
|
386
|
+
except OSError, ValueError:
|
|
387
|
+
return ApprovedTargets()
|
|
388
|
+
|
|
389
|
+
def _command_cwd(self, working_dir: str | None) -> str:
|
|
390
|
+
return resolve_workspace_path(working_dir, base_cwd=self._runtime.cwd) if working_dir else self._runtime.cwd
|
|
391
|
+
|
|
392
|
+
async def _bound_result(
|
|
393
|
+
self, canonical: str, budget: int, captures: Sequence[CapturedOutput] = (), *, lead: str = ""
|
|
394
|
+
) -> str:
|
|
395
|
+
return await bound_process_output(self._session_dir, "shell", canonical, budget, captures, lead=lead)
|
|
396
|
+
|
|
397
|
+
def tools(self) -> list:
|
|
398
|
+
"""Return all tool instances bound to this object.
|
|
399
|
+
|
|
400
|
+
Dynamically sets descriptions to include detected shell info so the
|
|
401
|
+
LLM knows which shell/version it is writing commands for.
|
|
402
|
+
"""
|
|
403
|
+
shell = self._shell
|
|
404
|
+
shell_label = f"{shell.name} {shell.version}".strip() if shell.version else shell.name
|
|
405
|
+
if shell.name in ("pwsh", "powershell"):
|
|
406
|
+
output_guidance = (
|
|
407
|
+
"For large expected output, use PowerShell-native limiting/search commands "
|
|
408
|
+
"such as Select-Object -First/-Last, Select-String, or Get-Content -Tail. "
|
|
409
|
+
"To read a file's content, use the read_file tool instead of Get-Content (if present). "
|
|
410
|
+
)
|
|
411
|
+
elif shell.name in _PIPEFAIL_SHELLS:
|
|
412
|
+
output_guidance = (
|
|
413
|
+
"For large expected output, pipe through tail -n N or grep. "
|
|
414
|
+
"Pipelines fail when any stage fails (pipefail is enabled by default). "
|
|
415
|
+
"Avoid head and grep -q/-m in pipelines — they exit early and can kill "
|
|
416
|
+
"the upstream command with SIGPIPE, often causing the pipeline to exit "
|
|
417
|
+
"with status 141; if that happens, rerun without the early-exiting "
|
|
418
|
+
"stage to see the real status. "
|
|
419
|
+
)
|
|
420
|
+
else:
|
|
421
|
+
output_guidance = "For large expected output, pipe through head, tail, or grep. "
|
|
422
|
+
|
|
423
|
+
execute = self.execute
|
|
424
|
+
execute.name = shell.name
|
|
425
|
+
set_tool_kind(execute, KIND_SHELL)
|
|
426
|
+
description = (
|
|
427
|
+
f"Execute a command in {shell_label} ({shell.path}). "
|
|
428
|
+
"Commands run with a configurable timeout (default 30s). "
|
|
429
|
+
"Consider a larger timeout for builds, tests, downloads, and other long-running commands. "
|
|
430
|
+
"This call waits for the command's result. Calls in the same response are dispatched "
|
|
431
|
+
"together with no guaranteed execution order. The calling agent resumes only after the "
|
|
432
|
+
"entire batch returns. Calls that depend on this result or could conflict through shared "
|
|
433
|
+
"state must go in a later response. "
|
|
434
|
+
"No asynchronous task handle is provided. "
|
|
435
|
+
"Output is truncated to max_tokens (default 8000) preserving the first "
|
|
436
|
+
"and last portions with a truncation notice in the middle. Very large output "
|
|
437
|
+
"keeps only its beginning and end; the middle cannot be recovered. When possible, "
|
|
438
|
+
"the kept output is saved under the current session directory. "
|
|
439
|
+
f"{output_guidance}"
|
|
440
|
+
"Set reason to a brief user-facing intent in the same language as the user's latest prompt; "
|
|
441
|
+
"it is shown to the user and audit logs. "
|
|
442
|
+
"Use timeout, working_dir, and max_tokens to control runtime, cwd, and output size. "
|
|
443
|
+
"Do NOT use the shell to read/write/edit files or search code — "
|
|
444
|
+
"use the dedicated read_file, write_file, edit_file, grep, and glob tools instead. "
|
|
445
|
+
"Do NOT run interactive/TUI programs (vim, nano, less, htop, top) "
|
|
446
|
+
"or commands requiring user input (passwords, y/n prompts). "
|
|
447
|
+
"Use non-interactive flags when available "
|
|
448
|
+
"(e.g. --yes, --no-pager, --no-input, DEBIAN_FRONTEND=noninteractive)."
|
|
449
|
+
)
|
|
450
|
+
platform_info = self._runtime.platform
|
|
451
|
+
if shell.name == "powershell":
|
|
452
|
+
description += (
|
|
453
|
+
" IMPORTANT: This is Windows PowerShell 5.x, NOT bash/sh/cmd. "
|
|
454
|
+
"You MUST use PowerShell syntax — do NOT write bash/Unix commands. "
|
|
455
|
+
"Platform: Windows — paths use backslash (C:\\Users\\name\\project), "
|
|
456
|
+
"home dir is $env:USERPROFILE, "
|
|
457
|
+
"temp dir is $env:TEMP, program files at $env:ProgramFiles. "
|
|
458
|
+
"Command chaining: use ';' to separate commands — '&&' and '||' "
|
|
459
|
+
"are NOT supported and will cause syntax errors. "
|
|
460
|
+
"For conditional execution: "
|
|
461
|
+
"if ($LASTEXITCODE -eq 0) { next-command }. "
|
|
462
|
+
"Environment variables: $env:VAR (NOT $VAR or %VAR%). "
|
|
463
|
+
"Comparison operators: -eq, -ne, -gt, -lt, -ge, -le, -like, -match "
|
|
464
|
+
"(NOT ==, !=, >, <). "
|
|
465
|
+
"Logical operators: -and, -or, -not/! (NOT &&, ||). "
|
|
466
|
+
"Null output: | Out-Null or > $null (NOT > /dev/null). "
|
|
467
|
+
"Line continuation: backtick ` at end of line (NOT \\). "
|
|
468
|
+
'Here-strings: @" must end its line, "@ must start its own line '
|
|
469
|
+
"(NOT << 'EOF'). "
|
|
470
|
+
"Prefer full cmdlet names over aliases: Get-ChildItem, Select-String, "
|
|
471
|
+
"Remove-Item -Recurse -Force, Set-Location, Copy-Item, and Move-Item. "
|
|
472
|
+
"If you must use Get-Content in Windows PowerShell 5.x, specify "
|
|
473
|
+
"-Encoding UTF8; otherwise BOM-less UTF-8 can be decoded as the ANSI "
|
|
474
|
+
"code page and non-ASCII text can be corrupted. "
|
|
475
|
+
"Quoting: double quotes interpolate variables ($var expands), "
|
|
476
|
+
"single quotes are literal. "
|
|
477
|
+
"Exit codes: check $LASTEXITCODE for native commands, "
|
|
478
|
+
"$? for cmdlet success. "
|
|
479
|
+
"Examples: "
|
|
480
|
+
"Get-ChildItem C:\\project\\src -Filter *.py -Recurse. "
|
|
481
|
+
"Select-String -Path .\\*.log -Pattern 'error'. "
|
|
482
|
+
"$env:PATH -split ';'. "
|
|
483
|
+
"Test-Path C:\\Users\\name\\file.txt. "
|
|
484
|
+
"Join-Path $env:USERPROFILE 'Documents' 'project'."
|
|
485
|
+
)
|
|
486
|
+
elif shell.name == "pwsh":
|
|
487
|
+
if platform_info.is_windows:
|
|
488
|
+
platform_note = (
|
|
489
|
+
"Platform: Windows — paths use backslash "
|
|
490
|
+
"(C:\\Users\\name\\project) or forward slash. "
|
|
491
|
+
"Home dir: $HOME (e.g. C:\\Users\\name), "
|
|
492
|
+
"temp dir: $env:TEMP, program files: $env:ProgramFiles. "
|
|
493
|
+
"Path separator: ';'."
|
|
494
|
+
)
|
|
495
|
+
path_examples = (
|
|
496
|
+
"Get-ChildItem C:\\project\\src -Filter *.py -Recurse. "
|
|
497
|
+
"Join-Path $HOME 'Documents' 'project'. "
|
|
498
|
+
"Test-Path C:\\Users\\name\\file.txt. "
|
|
499
|
+
"$env:PATH -split ';'."
|
|
500
|
+
)
|
|
501
|
+
elif platform_info.is_macos:
|
|
502
|
+
platform_note = (
|
|
503
|
+
"Platform: macOS — paths use forward slash "
|
|
504
|
+
"(/Users/name/project). "
|
|
505
|
+
"Home dir: $HOME (e.g. /Users/name), "
|
|
506
|
+
"temp dir: $env:TMPDIR or /tmp. "
|
|
507
|
+
"Path separator: ':'."
|
|
508
|
+
)
|
|
509
|
+
path_examples = (
|
|
510
|
+
"Get-ChildItem ~/project/src -Filter *.py -Recurse. "
|
|
511
|
+
"Join-Path $HOME 'Documents' 'project'. "
|
|
512
|
+
"Test-Path /usr/local/bin/python3. "
|
|
513
|
+
"$env:PATH -split ':'."
|
|
514
|
+
)
|
|
515
|
+
else:
|
|
516
|
+
platform_note = (
|
|
517
|
+
"Platform: Linux — paths use forward slash "
|
|
518
|
+
"(/home/name/project). "
|
|
519
|
+
"Home dir: $HOME (e.g. /home/name), "
|
|
520
|
+
"temp dir: /tmp. "
|
|
521
|
+
"Path separator: ':'."
|
|
522
|
+
)
|
|
523
|
+
path_examples = (
|
|
524
|
+
"Get-ChildItem ~/project/src -Filter *.py -Recurse. "
|
|
525
|
+
"Join-Path $HOME 'Documents' 'project'. "
|
|
526
|
+
"Test-Path /usr/bin/python3. "
|
|
527
|
+
"$env:PATH -split ':'."
|
|
528
|
+
)
|
|
529
|
+
description += (
|
|
530
|
+
" IMPORTANT: This is PowerShell 7+ (cross-platform), NOT bash/sh. "
|
|
531
|
+
"You MUST use PowerShell syntax — do NOT write bash/Unix commands. "
|
|
532
|
+
f"{platform_note} "
|
|
533
|
+
"Command chaining: '&&' and '||' are supported as pipeline chain "
|
|
534
|
+
"operators (success/failure), ';' separates independent statements. "
|
|
535
|
+
"Environment variables: $env:VAR (NOT $VAR or export VAR=val). "
|
|
536
|
+
"Comparison operators: -eq, -ne, -gt, -lt, -ge, -le, -like, -match "
|
|
537
|
+
"(NOT ==, !=, >, <). "
|
|
538
|
+
"Logical operators: -and, -or, -not/! (do NOT use && or || as logical operators). "
|
|
539
|
+
"Null output: | Out-Null or > $null (NOT > /dev/null). "
|
|
540
|
+
"Line continuation: backtick ` at end of line (NOT \\). "
|
|
541
|
+
'Here-strings: @" must end its line, "@ must start its own line '
|
|
542
|
+
"(NOT << 'EOF'). "
|
|
543
|
+
"Ternary: $cond ? $trueVal : $falseVal. "
|
|
544
|
+
"Null-coalescing: $a ?? $default. "
|
|
545
|
+
"Prefer full cmdlet names over aliases: Get-ChildItem, Select-String, "
|
|
546
|
+
"Remove-Item -Recurse -Force, Set-Location, Copy-Item, and Move-Item. "
|
|
547
|
+
"Quoting: double quotes interpolate variables ($var expands), "
|
|
548
|
+
"single quotes are literal. "
|
|
549
|
+
"Exit codes: check $LASTEXITCODE for native commands, "
|
|
550
|
+
"$? for cmdlet success. "
|
|
551
|
+
f"Examples: {path_examples}"
|
|
552
|
+
)
|
|
553
|
+
execute.description = description
|
|
554
|
+
|
|
555
|
+
tools = [execute]
|
|
556
|
+
if self._runtime.working_dirs:
|
|
557
|
+
list_dirs = self.list_workspace_dirs
|
|
558
|
+
set_tool_kind(list_dirs, KIND_SHELL)
|
|
559
|
+
tools.append(list_dirs)
|
|
560
|
+
return tools
|
|
561
|
+
|
|
562
|
+
@tool(kind=KIND_SHELL)
|
|
563
|
+
async def execute(
|
|
564
|
+
self,
|
|
565
|
+
command: Annotated[str, "The command to execute in the platform's default shell."],
|
|
566
|
+
reason: Annotated[str, SHELL_REASON_DESCRIPTION],
|
|
567
|
+
timeout: Annotated[int, "Timeout in seconds. Default: 30."] = 30,
|
|
568
|
+
working_dir: Annotated[
|
|
569
|
+
str | None,
|
|
570
|
+
"Working directory for the command. Relative paths are resolved against the session workspace. "
|
|
571
|
+
"Default: session workspace directory.",
|
|
572
|
+
] = None,
|
|
573
|
+
max_tokens: Annotated[
|
|
574
|
+
int,
|
|
575
|
+
"Token budget for command output. Large outputs are truncated to fit.",
|
|
576
|
+
] = _DEFAULT_MAX_TOKENS,
|
|
577
|
+
) -> str:
|
|
578
|
+
"""Execute a shell command on the current platform."""
|
|
579
|
+
budget = max(100, max_tokens)
|
|
580
|
+
# Validate command against filter policy (if configured)
|
|
581
|
+
if self._filter is not None:
|
|
582
|
+
result = self._filter.validate(command)
|
|
583
|
+
if not result.allowed:
|
|
584
|
+
_record_shell_error()
|
|
585
|
+
return tool_error(
|
|
586
|
+
"command_blocked",
|
|
587
|
+
f"command blocked — {result.reason}",
|
|
588
|
+
details={"command": command, "reason": result.reason},
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
shell = self._shell
|
|
592
|
+
if working_dir:
|
|
593
|
+
missing_base = missing_base_cwd_error(working_dir, self._runtime.cwd)
|
|
594
|
+
if missing_base is not None:
|
|
595
|
+
_record_shell_error()
|
|
596
|
+
return missing_base
|
|
597
|
+
cwd = self._command_cwd(working_dir)
|
|
598
|
+
if not os.path.isdir(cwd):
|
|
599
|
+
_record_shell_error()
|
|
600
|
+
return _working_dir_not_found(cwd, working_dir)
|
|
601
|
+
else:
|
|
602
|
+
cwd = self._runtime.cwd
|
|
603
|
+
pinned_cwd = approved_cwd()
|
|
604
|
+
if pinned_cwd is not None:
|
|
605
|
+
if await asyncio.to_thread(physical_dir, cwd) != pinned_cwd:
|
|
606
|
+
_record_shell_error()
|
|
607
|
+
return tool_error(
|
|
608
|
+
"working_dir_changed",
|
|
609
|
+
CHANGED_AFTER_APPROVAL.format(target="Working directory"),
|
|
610
|
+
details={"working_dir": cwd},
|
|
611
|
+
)
|
|
612
|
+
# Start in the checked directory itself: a link in the path given
|
|
613
|
+
# could still be retargeted between this check and the spawn.
|
|
614
|
+
cwd = pinned_cwd
|
|
615
|
+
|
|
616
|
+
try:
|
|
617
|
+
if sys.platform == "win32":
|
|
618
|
+
# Windows: snapshot outer TUI's console mode so a child TUI
|
|
619
|
+
# (e.g. the agent running ``uv run icode``) cannot leave the
|
|
620
|
+
# outer chrys without mouse/keyboard input after it exits.
|
|
621
|
+
# See ``preserve_console_mode`` for the full rationale.
|
|
622
|
+
with preserve_console_mode():
|
|
623
|
+
stdout, returncode, stderr = await self._execute_pipe(command, shell, cwd, timeout)
|
|
624
|
+
body = stdout.text(_clean_output)
|
|
625
|
+
stderr_clean = stderr.text(_clean_output)
|
|
626
|
+
exit_suffix = f"[exit_code: {returncode}]"
|
|
627
|
+
parts: list[str] = []
|
|
628
|
+
if body:
|
|
629
|
+
parts.append(body)
|
|
630
|
+
if stderr_clean:
|
|
631
|
+
parts.append(f"[stderr]\n{stderr_clean}")
|
|
632
|
+
parts.append(exit_suffix)
|
|
633
|
+
_record_shell_exit_code(returncode)
|
|
634
|
+
return await self._bound_result("\n".join(parts), budget, (stdout, stderr))
|
|
635
|
+
merged, returncode = await self._execute_pty(command, shell, cwd, timeout)
|
|
636
|
+
body = merged.text(_clean_output)
|
|
637
|
+
suffix = f"[exit_code: {returncode}]"
|
|
638
|
+
parts = [body, suffix] if body else [suffix]
|
|
639
|
+
_record_shell_exit_code(returncode)
|
|
640
|
+
return await self._bound_result("\n".join(parts), budget, (merged,))
|
|
641
|
+
except _Timeout as exc:
|
|
642
|
+
_record_shell_timeout(timeout)
|
|
643
|
+
message = f"Error: command timed out after {timeout} seconds."
|
|
644
|
+
partial = _format_partial_output(exc.output, exc.stderr)
|
|
645
|
+
if not partial:
|
|
646
|
+
return message
|
|
647
|
+
return await self._bound_result(partial, budget, exc.captures, lead=f"{message}\n")
|
|
648
|
+
except SubprocessStoppedError:
|
|
649
|
+
_record_shell_error()
|
|
650
|
+
return tool_error("process_stopped", "command entered stopped state and was terminated.")
|
|
651
|
+
except MissingWorkingDirectoryError as exc:
|
|
652
|
+
_record_shell_error()
|
|
653
|
+
if working_dir:
|
|
654
|
+
return _working_dir_not_found(exc.path, working_dir)
|
|
655
|
+
return working_dir_missing_error(exc.path)
|
|
656
|
+
except FileNotFoundError:
|
|
657
|
+
_record_shell_error()
|
|
658
|
+
return tool_error("shell_not_found", f"shell not found at {shell.path}", details={"shell_path": shell.path})
|
|
659
|
+
except Exception as e:
|
|
660
|
+
_record_shell_error()
|
|
661
|
+
return tool_error(
|
|
662
|
+
"command_execution_failed", f"failed to execute command — {e}", details={"command": command}
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
# ------------------------------------------------------------------
|
|
666
|
+
# Execution backends
|
|
667
|
+
# ------------------------------------------------------------------
|
|
668
|
+
|
|
669
|
+
async def _execute_pty(
|
|
670
|
+
self, command: str, shell: ShellInfo, cwd: str, timeout: int | float
|
|
671
|
+
) -> tuple[CapturedOutput, int]:
|
|
672
|
+
"""Run *command* in a PTY (Unix only). Returns ``(output, returncode)``."""
|
|
673
|
+
master, slave = pty.openpty()
|
|
674
|
+
try:
|
|
675
|
+
# Set terminal size to 80x24
|
|
676
|
+
size = struct.pack("HHHH", 24, 80, 0, 0)
|
|
677
|
+
fcntl.ioctl(slave, termios.TIOCSWINSZ, size)
|
|
678
|
+
|
|
679
|
+
# Non-blocking master
|
|
680
|
+
posix_os = cast(Any, os)
|
|
681
|
+
flags = fcntl.fcntl(master, fcntl.F_GETFL)
|
|
682
|
+
fcntl.fcntl(master, fcntl.F_SETFL, flags | posix_os.O_NONBLOCK)
|
|
683
|
+
|
|
684
|
+
env = _build_subprocess_env()
|
|
685
|
+
|
|
686
|
+
def _setup_pty() -> None:
|
|
687
|
+
posix_os.setsid()
|
|
688
|
+
|
|
689
|
+
# fsatrace (when armed) execs between the PTY and the shell:
|
|
690
|
+
# stdio fds are inherited, ``setsid`` applies to fsatrace, and
|
|
691
|
+
# the shell inherits its process group — draining and
|
|
692
|
+
# group-kill behave exactly as untraced.
|
|
693
|
+
argv_variants = _shell_argv_variants(shell, command)
|
|
694
|
+
proc: asyncio.subprocess.Process | None = None
|
|
695
|
+
for index, argv in enumerate(argv_variants):
|
|
696
|
+
try:
|
|
697
|
+
proc = await asyncio.create_subprocess_exec(
|
|
698
|
+
*argv,
|
|
699
|
+
stdin=asyncio.subprocess.DEVNULL,
|
|
700
|
+
stdout=slave,
|
|
701
|
+
stderr=slave,
|
|
702
|
+
cwd=cwd,
|
|
703
|
+
env=env,
|
|
704
|
+
preexec_fn=_setup_pty,
|
|
705
|
+
)
|
|
706
|
+
break
|
|
707
|
+
except OSError as exc:
|
|
708
|
+
raise_if_missing_cwd(cwd, exc)
|
|
709
|
+
if index == len(argv_variants) - 1:
|
|
710
|
+
raise
|
|
711
|
+
logger.debug("Traced shell spawn failed; degrading to untraced", exc_info=True)
|
|
712
|
+
if proc is None:
|
|
713
|
+
raise RuntimeError("No shell process was created by the configured spawn variants.")
|
|
714
|
+
except BaseException:
|
|
715
|
+
# Clean up both fds if subprocess creation fails
|
|
716
|
+
os.close(slave)
|
|
717
|
+
os.close(master)
|
|
718
|
+
raise
|
|
719
|
+
|
|
720
|
+
# Keep the parent's ``slave`` fd open until the drain has finished. On
|
|
721
|
+
# macOS, if every slave fd closes (child exit + the parent closing its
|
|
722
|
+
# copy) before the async reader has drained the master, the PTY can
|
|
723
|
+
# reach EOF and the child's final output is silently dropped. Holding
|
|
724
|
+
# the slave open here keeps the master from reaching EOF until we close
|
|
725
|
+
# it ourselves — after the child has exited and its output is read.
|
|
726
|
+
transport: asyncio.BaseTransport | None = None
|
|
727
|
+
process_group_id = proc.pid
|
|
728
|
+
process_session_id = proc.pid
|
|
729
|
+
cleanup_process_group = True
|
|
730
|
+
slave_closed = False
|
|
731
|
+
capture = BoundedCapture()
|
|
732
|
+
|
|
733
|
+
def _release_slave() -> None:
|
|
734
|
+
nonlocal slave_closed
|
|
735
|
+
if not slave_closed:
|
|
736
|
+
slave_closed = True
|
|
737
|
+
with suppress(OSError):
|
|
738
|
+
os.close(slave)
|
|
739
|
+
|
|
740
|
+
try:
|
|
741
|
+
# Async reader on PTY master (same pattern as TUI shell)
|
|
742
|
+
reader = asyncio.StreamReader()
|
|
743
|
+
loop = asyncio.get_running_loop()
|
|
744
|
+
transport, _ = await loop.connect_read_pipe(
|
|
745
|
+
lambda: PtyOutputProtocol(reader),
|
|
746
|
+
os.fdopen(master, "rb", 0),
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
async def _drain() -> None:
|
|
750
|
+
progress_cb = shell_progress_callback.get(None)
|
|
751
|
+
if progress_cb is None:
|
|
752
|
+
# Original path — no progress streaming
|
|
753
|
+
while chunk := await reader.read(64 * 1024):
|
|
754
|
+
capture.feed(chunk)
|
|
755
|
+
return
|
|
756
|
+
|
|
757
|
+
# Streaming path — extract lines and emit progress
|
|
758
|
+
decoder = codecs.getincrementaldecoder("utf-8")(errors="replace")
|
|
759
|
+
line_buf = ""
|
|
760
|
+
pending = _ProgressLines()
|
|
761
|
+
last_emit = time.monotonic()
|
|
762
|
+
# The callback below may still be busy when the program ends. What
|
|
763
|
+
# arrived meanwhile is read all the same: PtyOutputProtocol ends the
|
|
764
|
+
# output after it, however the master reported the end.
|
|
765
|
+
while chunk := await reader.read(64 * 1024):
|
|
766
|
+
capture.feed(chunk)
|
|
767
|
+
*lines, line_buf = (line_buf + decoder.decode(chunk)).split("\n")
|
|
768
|
+
# Preserve completed blank lines. The tool card renders the
|
|
769
|
+
# streamed progress after completion, so dropping empty lines
|
|
770
|
+
# here changes visible command output compared to a real terminal.
|
|
771
|
+
for line in lines:
|
|
772
|
+
pending.add(_strip_ansi(line.rstrip("\r")))
|
|
773
|
+
if len(line_buf) > _PROGRESS_LINE_LIMIT:
|
|
774
|
+
line_buf = line_buf[-_PROGRESS_LINE_LIMIT:]
|
|
775
|
+
now = time.monotonic()
|
|
776
|
+
if pending and now - last_emit >= _PROGRESS_THROTTLE_INTERVAL:
|
|
777
|
+
await progress_cb(pending.take())
|
|
778
|
+
last_emit = now
|
|
779
|
+
# Flush remaining
|
|
780
|
+
line_buf += decoder.decode(b"", final=True)
|
|
781
|
+
if line_buf.strip():
|
|
782
|
+
remaining = _strip_ansi(line_buf.rstrip("\r\n"))
|
|
783
|
+
if remaining:
|
|
784
|
+
pending.add(remaining)
|
|
785
|
+
if pending:
|
|
786
|
+
await progress_cb(pending.take())
|
|
787
|
+
|
|
788
|
+
async def _wait_then_release() -> None:
|
|
789
|
+
# Once the child exits, release the parent's slave fd so the PTY
|
|
790
|
+
# master reaches EOF *after* the child's buffered output has been
|
|
791
|
+
# flushed, letting _drain() observe EOF and finish. Waiting for
|
|
792
|
+
# the reader to catch up first is what makes that flush real: see
|
|
793
|
+
# _await_pty_master_drained.
|
|
794
|
+
try:
|
|
795
|
+
await proc.wait()
|
|
796
|
+
await _await_pty_master_drained(master)
|
|
797
|
+
finally:
|
|
798
|
+
_release_slave()
|
|
799
|
+
|
|
800
|
+
await wait_for_subprocess(
|
|
801
|
+
asyncio.gather(_wait_then_release(), _drain()),
|
|
802
|
+
timeout=timeout,
|
|
803
|
+
process_group_id=process_group_id,
|
|
804
|
+
process_session_id=process_session_id,
|
|
805
|
+
)
|
|
806
|
+
returncode = proc.returncode if proc.returncode is not None else -1
|
|
807
|
+
cleanup_process_group = False
|
|
808
|
+
return capture.snapshot(), returncode
|
|
809
|
+
except TimeoutError:
|
|
810
|
+
raise _Timeout(output=capture.snapshot()) from None
|
|
811
|
+
finally:
|
|
812
|
+
# Release the parent's slave fd on every exit path (success,
|
|
813
|
+
# timeout, cancellation, stopped-state, or error) so it is never
|
|
814
|
+
# leaked and the master reader can observe EOF.
|
|
815
|
+
_release_slave()
|
|
816
|
+
# Kill the process if it's still running (timeout, cancellation,
|
|
817
|
+
# or any other early exit). Without this, CancelledError from
|
|
818
|
+
# user interrupt leaves the subprocess alive in the background.
|
|
819
|
+
if cleanup_process_group:
|
|
820
|
+
_kill_pty_process(proc, process_group_id, process_session_id)
|
|
821
|
+
if transport is not None:
|
|
822
|
+
transport.close()
|
|
823
|
+
|
|
824
|
+
async def _execute_pipe(
|
|
825
|
+
self, command: str, shell: ShellInfo, cwd: str, timeout: int | float
|
|
826
|
+
) -> tuple[CapturedOutput, int, CapturedOutput]:
|
|
827
|
+
"""Run *command* with pipes (Windows fallback). Returns ``(stdout, returncode, stderr)``."""
|
|
828
|
+
env = _build_subprocess_env()
|
|
829
|
+
command = _prepare_pipe_command(command, shell)
|
|
830
|
+
argv_variants = _shell_argv_variants(shell, command)
|
|
831
|
+
|
|
832
|
+
async with AsyncExitStack() as stack:
|
|
833
|
+
proc: asyncio.subprocess.Process | None = None
|
|
834
|
+
for index, argv in enumerate(argv_variants):
|
|
835
|
+
try:
|
|
836
|
+
proc = await stack.enter_async_context(
|
|
837
|
+
managed_subprocess(
|
|
838
|
+
*argv,
|
|
839
|
+
# stdin=DEVNULL so the child's STD_INPUT_HANDLE is not the
|
|
840
|
+
# outer chrys's console input handle on Windows. Without this,
|
|
841
|
+
# a grandchild TUI (e.g. ``uv run icode`` inside PowerShell)
|
|
842
|
+
# can call ``SetConsoleMode`` on the inherited handle and wipe
|
|
843
|
+
# the outer TUI's mouse/keyboard flags. Harmless on Unix.
|
|
844
|
+
stdin=asyncio.subprocess.DEVNULL,
|
|
845
|
+
stdout=asyncio.subprocess.PIPE,
|
|
846
|
+
stderr=asyncio.subprocess.PIPE,
|
|
847
|
+
cwd=cwd,
|
|
848
|
+
env=env,
|
|
849
|
+
)
|
|
850
|
+
)
|
|
851
|
+
break
|
|
852
|
+
except MissingWorkingDirectoryError:
|
|
853
|
+
raise
|
|
854
|
+
except OSError:
|
|
855
|
+
# Spawn failure surfaces at context entry, before the
|
|
856
|
+
# cleanup registers — safe to retry the bare argv.
|
|
857
|
+
if index == len(argv_variants) - 1:
|
|
858
|
+
raise
|
|
859
|
+
logger.debug("Traced shell spawn failed; degrading to untraced", exc_info=True)
|
|
860
|
+
if proc is None:
|
|
861
|
+
raise RuntimeError("No shell process was created by the configured spawn variants.")
|
|
862
|
+
capture = _PipeOutputCapture()
|
|
863
|
+
try:
|
|
864
|
+
progress_cb = shell_progress_callback.get(None)
|
|
865
|
+
process_group_id = getattr(proc, "pid", None) if sys.platform != "win32" else None
|
|
866
|
+
stdout, stderr = await wait_for_subprocess(
|
|
867
|
+
self._stream_pipe(proc, progress_cb, capture=capture),
|
|
868
|
+
timeout=timeout,
|
|
869
|
+
process_group_id=process_group_id,
|
|
870
|
+
)
|
|
871
|
+
except TimeoutError:
|
|
872
|
+
raise _Timeout(output=capture.stdout.snapshot(), stderr=capture.stderr.snapshot()) from None
|
|
873
|
+
|
|
874
|
+
returncode = proc.returncode if proc.returncode is not None else -1
|
|
875
|
+
return stdout, returncode, stderr
|
|
876
|
+
|
|
877
|
+
@staticmethod
|
|
878
|
+
async def _stream_pipe(
|
|
879
|
+
proc: asyncio.subprocess.Process,
|
|
880
|
+
progress_cb: Callable[[list[str]], Awaitable[None]] | None,
|
|
881
|
+
*,
|
|
882
|
+
capture: _PipeOutputCapture | None = None,
|
|
883
|
+
) -> tuple[CapturedOutput, CapturedOutput]:
|
|
884
|
+
"""Read stdout line-by-line with progress, read stderr fully."""
|
|
885
|
+
capture = capture or _PipeOutputCapture()
|
|
886
|
+
pending = _ProgressLines()
|
|
887
|
+
last_emit = time.monotonic()
|
|
888
|
+
|
|
889
|
+
async def _drain_stdout() -> None:
|
|
890
|
+
nonlocal last_emit
|
|
891
|
+
if proc.stdout is None:
|
|
892
|
+
raise RuntimeError("The shell process has no stdout pipe.")
|
|
893
|
+
line_buf = bytearray()
|
|
894
|
+
while chunk := await proc.stdout.read(64 * 1024):
|
|
895
|
+
capture.stdout.feed(chunk)
|
|
896
|
+
if progress_cb is None:
|
|
897
|
+
continue
|
|
898
|
+
line_buf.extend(chunk)
|
|
899
|
+
while b"\n" in line_buf:
|
|
900
|
+
newline_idx = line_buf.index(0x0A)
|
|
901
|
+
raw_line = bytes(line_buf[:newline_idx]).rstrip(b"\r")
|
|
902
|
+
del line_buf[: newline_idx + 1]
|
|
903
|
+
pending.add(_strip_ansi(decode_subprocess_output(raw_line)))
|
|
904
|
+
if len(line_buf) > _PROGRESS_LINE_LIMIT:
|
|
905
|
+
del line_buf[:-_PROGRESS_LINE_LIMIT]
|
|
906
|
+
now = time.monotonic()
|
|
907
|
+
if pending and now - last_emit >= _PROGRESS_THROTTLE_INTERVAL:
|
|
908
|
+
await progress_cb(pending.take())
|
|
909
|
+
last_emit = now
|
|
910
|
+
if progress_cb is not None:
|
|
911
|
+
if line_buf.strip():
|
|
912
|
+
remaining = _strip_ansi(decode_subprocess_output(bytes(line_buf).rstrip(b"\r\n")))
|
|
913
|
+
if remaining:
|
|
914
|
+
pending.add(remaining)
|
|
915
|
+
if pending:
|
|
916
|
+
await progress_cb(pending.take())
|
|
917
|
+
|
|
918
|
+
async def _drain_stderr() -> None:
|
|
919
|
+
if proc.stderr is None:
|
|
920
|
+
raise RuntimeError("The shell process has no stderr pipe.")
|
|
921
|
+
while chunk := await proc.stderr.read(64 * 1024):
|
|
922
|
+
capture.stderr.feed(chunk)
|
|
923
|
+
|
|
924
|
+
await asyncio.gather(_drain_stdout(), _drain_stderr(), proc.wait())
|
|
925
|
+
return capture.stdout.snapshot(), capture.stderr.snapshot()
|
|
926
|
+
|
|
927
|
+
# ------------------------------------------------------------------
|
|
928
|
+
# Other tools
|
|
929
|
+
# ------------------------------------------------------------------
|
|
930
|
+
|
|
931
|
+
@tool
|
|
932
|
+
def list_workspace_dirs(self) -> str:
|
|
933
|
+
"""List all working directories in the current workspace.
|
|
934
|
+
|
|
935
|
+
Returns directory paths with their labels and primary status.
|
|
936
|
+
Useful for understanding the project structure across multiple repositories.
|
|
937
|
+
"""
|
|
938
|
+
if not self._runtime.working_dirs:
|
|
939
|
+
return f"No workspace active. Current directory: {self._runtime.cwd}"
|
|
940
|
+
lines = [f"Workspace directories (cwd: {self._runtime.cwd}):"]
|
|
941
|
+
for wd in self._runtime.working_dirs:
|
|
942
|
+
marker = " (primary)" if wd.is_primary else ""
|
|
943
|
+
label = f" [{wd.label}]" if wd.label else ""
|
|
944
|
+
exists = " ok" if wd.exists else " MISSING"
|
|
945
|
+
lines.append(f" {wd.path}{label}{marker}{exists}")
|
|
946
|
+
return "\n".join(lines)
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
class _PipeOutputCapture:
|
|
950
|
+
"""Stream captures shared with the timeout handler."""
|
|
951
|
+
|
|
952
|
+
def __init__(self) -> None:
|
|
953
|
+
self.stdout = BoundedCapture()
|
|
954
|
+
self.stderr = BoundedCapture()
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
class _Timeout(Exception):
|
|
958
|
+
"""Internal sentinel — keeps timeout handling out of the backends."""
|
|
959
|
+
|
|
960
|
+
def __init__(self, output: CapturedOutput | None = None, stderr: CapturedOutput | None = None) -> None:
|
|
961
|
+
super().__init__()
|
|
962
|
+
self.output = output
|
|
963
|
+
self.stderr = stderr
|
|
964
|
+
|
|
965
|
+
@property
|
|
966
|
+
def captures(self) -> tuple[CapturedOutput, ...]:
|
|
967
|
+
return tuple(capture for capture in (self.output, self.stderr) if capture is not None)
|