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
chrys/app/tui/app.py
ADDED
|
@@ -0,0 +1,1287 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Chrys TUI — Textual-based terminal UI entry point.
|
|
4
|
+
|
|
5
|
+
This is the Textual app entry point used by ``chrys``.
|
|
6
|
+
It launches the Textual app which communicates with the backend Engine
|
|
7
|
+
exclusively through the EventBus.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import asyncio
|
|
14
|
+
import contextlib
|
|
15
|
+
import inspect
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
20
|
+
|
|
21
|
+
from textual import __version__ as TEXTUAL_VERSION
|
|
22
|
+
from textual import events as textual_events
|
|
23
|
+
from textual.app import App, ScreenStackError
|
|
24
|
+
from textual.await_remove import AwaitRemove
|
|
25
|
+
from textual.css.stylesheet import Stylesheet, StylesheetError
|
|
26
|
+
from textual.css.tokenizer import TokenError
|
|
27
|
+
from textual.dom import DOMNode, NoScreen
|
|
28
|
+
from textual.errors import NoWidget
|
|
29
|
+
from textual.screen import Screen
|
|
30
|
+
from textual.theme import Theme
|
|
31
|
+
from textual.widget import Widget
|
|
32
|
+
|
|
33
|
+
from chrys import __version__
|
|
34
|
+
from chrys.app.cli.launch_cwd import launch_cwd_missing_message
|
|
35
|
+
from chrys.app.features.buddy.lifecycle import on_successful_turn as on_buddy_successful_turn
|
|
36
|
+
from chrys.app.features.session_title import SessionTitleUpdater
|
|
37
|
+
from chrys.app.parsing import SanitizingArgumentParser
|
|
38
|
+
from chrys.app.tui.behaviors.insert_clipboard import INSERT_CLIPBOARD_BINDINGS, copy_with_insert, paste_with_insert
|
|
39
|
+
from chrys.app.tui.binding_display import QUIT_BINDING, localized_binding
|
|
40
|
+
from chrys.app.tui.i18n import LocaleController, render_str
|
|
41
|
+
from chrys.app.tui.notifications import NotificationService, NotificationSettings
|
|
42
|
+
from chrys.app.tui.screens.main import MainScreen
|
|
43
|
+
from chrys.app.tui.screens.settings import SettingsDialog
|
|
44
|
+
from chrys.app.tui.screens.settings.dialog import THEME_PREVIEW_HINT
|
|
45
|
+
from chrys.app.tui.support.gc_freeze import (
|
|
46
|
+
GC_FREEZE_ENABLED,
|
|
47
|
+
GC_FREEZE_WATCHDOG_PERIOD_SECONDS,
|
|
48
|
+
GcAbsorbReason,
|
|
49
|
+
GcAbsorbRequested,
|
|
50
|
+
GcFreezeBlockReason,
|
|
51
|
+
GcFreezeCoordinator,
|
|
52
|
+
GcReclaimReason,
|
|
53
|
+
GcReclaimRequested,
|
|
54
|
+
)
|
|
55
|
+
from chrys.app.tui.terminal.title import (
|
|
56
|
+
running_under_textual_web_driver,
|
|
57
|
+
set_app_terminal_title_for_current_cwd,
|
|
58
|
+
set_terminal_title_for_current_cwd,
|
|
59
|
+
)
|
|
60
|
+
from chrys.app.tui.theme import (
|
|
61
|
+
CHRYS_ANSI_THEME,
|
|
62
|
+
CHRYS_LEGACY_THEME,
|
|
63
|
+
CHRYS_THEME,
|
|
64
|
+
TuiVariableDefaultsMixin,
|
|
65
|
+
with_tui_css_variables,
|
|
66
|
+
)
|
|
67
|
+
from chrys.app.tui.theme_loader import load_user_themes, user_theme_warning
|
|
68
|
+
from chrys.app.tui.theme_stylesheet import ThemeStylesheet
|
|
69
|
+
from chrys.app.tui.themes.preview import ThemePreview
|
|
70
|
+
from chrys.app.tui.toasts import TEXTUAL_TOAST_RACK_FORK_VERSION, refresh_newest_first
|
|
71
|
+
from chrys.app.tui.util.debug_log import configure_debug_logging, stop_debug_logging
|
|
72
|
+
from chrys.app.tui.widgets.chrome.status_bar import STATUS_AGENT_STARTUP_FAILED
|
|
73
|
+
from chrys.foundation import platform as chrys_platform
|
|
74
|
+
from chrys.foundation.branding import APP_DISPLAY_NAME, format_app_version_title
|
|
75
|
+
from chrys.foundation.config.runtime_pointer import set_model_pointer
|
|
76
|
+
from chrys.foundation.config.settings import DEFAULT_AGENT_PROFILE, DEFAULT_THEME, Settings, persist_theme
|
|
77
|
+
from chrys.foundation.config.settings_store import LoadedSettings, SettingsHandle
|
|
78
|
+
from chrys.foundation.config.spec import SettingOrigin, Source
|
|
79
|
+
from chrys.foundation.events.bus import EventBus
|
|
80
|
+
from chrys.foundation.events.types import Warning
|
|
81
|
+
from chrys.foundation.i18n import Localizer, MessageRef, msg
|
|
82
|
+
from chrys.foundation.models.session_surface import SessionSurface
|
|
83
|
+
from chrys.foundation.util.session_ids import session_short_id
|
|
84
|
+
from chrys.orchestration.engine.assembly import assemble_agent_engine
|
|
85
|
+
from chrys.orchestration.engine.engine import AgentEngine
|
|
86
|
+
from chrys.orchestration.startup import catalog_load_warning
|
|
87
|
+
from chrys.service.profiles.agents.registry import AgentProfileRegistry, resolve_profile_selector
|
|
88
|
+
from chrys.service.profiles.models.registry import ModelProfileRegistry
|
|
89
|
+
from chrys.service.profiles.models.resolver import (
|
|
90
|
+
format_available_profile_labels,
|
|
91
|
+
loaded_with_active_model_profile,
|
|
92
|
+
)
|
|
93
|
+
from chrys.service.profiles.models.resolver import (
|
|
94
|
+
resolve_profile_selector as resolve_model_profile_selector,
|
|
95
|
+
)
|
|
96
|
+
from chrys.service.state.store import JsonFileStateStore
|
|
97
|
+
|
|
98
|
+
logger = logging.getLogger(__name__)
|
|
99
|
+
|
|
100
|
+
_SESSION_NOTIFICATION_TITLE = msg("tui.startup.title.session", fallback="Session")
|
|
101
|
+
_AGENT_STARTUP_FAILED_TITLE = msg(
|
|
102
|
+
"tui.startup.title.agent_failed",
|
|
103
|
+
fallback="Agent startup failed",
|
|
104
|
+
)
|
|
105
|
+
_SESSION_READ_FAILED = msg(
|
|
106
|
+
"tui.startup.session.read_failed",
|
|
107
|
+
fallback="Could not read session {session_id}: {error}",
|
|
108
|
+
)
|
|
109
|
+
_SESSION_NOT_FOUND = msg(
|
|
110
|
+
"tui.startup.session.not_found",
|
|
111
|
+
fallback="Session {session_id} was not found.",
|
|
112
|
+
)
|
|
113
|
+
_SESSION_RESTORE_FALLBACK = msg(
|
|
114
|
+
"tui.startup.session.restore_fallback",
|
|
115
|
+
fallback="Could not restore session {session_id}; started a new session instead.",
|
|
116
|
+
)
|
|
117
|
+
_SESSION_RESTORE_FAILED = msg(
|
|
118
|
+
"tui.startup.session.restore_failed",
|
|
119
|
+
fallback="Could not restore session {session_id}: {error}",
|
|
120
|
+
)
|
|
121
|
+
_QUIT_HELP = msg("tui.app.quit_help", fallback="Press {key} to quit the app")
|
|
122
|
+
_QUIT_HELP_TITLE = msg("tui.app.quit_help_title", fallback="Do you want to quit?")
|
|
123
|
+
|
|
124
|
+
_GC_FREEZE_DISABLED_WARNING_CODE = "gc_freeze_disabled"
|
|
125
|
+
_TEXTUAL_LOAD_SCREEN_CSS_FORK_VERSION = "8.2.7"
|
|
126
|
+
_GC_ACTIVITY_EVENTS = (
|
|
127
|
+
textual_events.Key,
|
|
128
|
+
textual_events.Paste,
|
|
129
|
+
textual_events.MouseDown,
|
|
130
|
+
textual_events.MouseUp,
|
|
131
|
+
textual_events.MouseScrollDown,
|
|
132
|
+
textual_events.MouseScrollUp,
|
|
133
|
+
textual_events.MouseScrollLeft,
|
|
134
|
+
textual_events.MouseScrollRight,
|
|
135
|
+
textual_events.Resize,
|
|
136
|
+
textual_events.AppFocus,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
_TERMINAL_MODE_RESET = (
|
|
140
|
+
"\x1b[?1000l" # Disable VT200 mouse
|
|
141
|
+
"\x1b[?1002l" # Disable button-event mouse
|
|
142
|
+
"\x1b[?1003l" # Disable any-event mouse
|
|
143
|
+
"\x1b[?1005l" # Disable UTF-8 extended mouse
|
|
144
|
+
"\x1b[?1006l" # Disable SGR mouse
|
|
145
|
+
"\x1b[?1015l" # Disable urxvt mouse
|
|
146
|
+
"\x1b[?1004l" # Disable focus-in/focus-out reports
|
|
147
|
+
"\x1b[?1007l" # Disable alternate-scroll mouse mode
|
|
148
|
+
"\x1b[?25h" # Show cursor
|
|
149
|
+
"\x1b[?2004l" # Disable bracketed paste
|
|
150
|
+
"\x1b[<u" # Disable Kitty keyboard protocol
|
|
151
|
+
"\x1b[=0;1u" # Hard-reset Kitty keyboard flags
|
|
152
|
+
)
|
|
153
|
+
if TYPE_CHECKING:
|
|
154
|
+
from textual.timer import Timer
|
|
155
|
+
|
|
156
|
+
from chrys.app.tui.widgets.markdown.diagram.messages import DiagramOpenRequested
|
|
157
|
+
from chrys.service.profiles.agents.schema import AgentProfile
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _resolve_startup_profile(registry: AgentProfileRegistry, preferred_name: str) -> AgentProfile | None:
|
|
161
|
+
"""Resolve the TUI launch profile, falling back through Code to the first visible profile."""
|
|
162
|
+
requested = preferred_name.strip() or DEFAULT_AGENT_PROFILE
|
|
163
|
+
# Match the TUI picker: hidden and sub-agent-only profiles are not valid main launch profiles.
|
|
164
|
+
profiles = registry.list_profiles()
|
|
165
|
+
by_name = {profile.name: profile for profile in profiles}
|
|
166
|
+
|
|
167
|
+
profile = resolve_profile_selector(profiles, requested)
|
|
168
|
+
if profile is not None:
|
|
169
|
+
return profile
|
|
170
|
+
|
|
171
|
+
if requested != DEFAULT_AGENT_PROFILE:
|
|
172
|
+
fallback = by_name.get(DEFAULT_AGENT_PROFILE)
|
|
173
|
+
if fallback is not None:
|
|
174
|
+
logger.warning(
|
|
175
|
+
"Default agent profile %r is not usable as a launch profile, falling back to %r.",
|
|
176
|
+
requested,
|
|
177
|
+
DEFAULT_AGENT_PROFILE,
|
|
178
|
+
)
|
|
179
|
+
return fallback
|
|
180
|
+
|
|
181
|
+
if profiles:
|
|
182
|
+
logger.warning(
|
|
183
|
+
"Default agent profile %r is not usable as a launch profile, falling back to %r.",
|
|
184
|
+
requested,
|
|
185
|
+
profiles[0].name,
|
|
186
|
+
)
|
|
187
|
+
return profiles[0]
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _apply_startup_active_model_selection(
|
|
192
|
+
parser: argparse.ArgumentParser,
|
|
193
|
+
loaded: LoadedSettings,
|
|
194
|
+
model_registry: ModelProfileRegistry,
|
|
195
|
+
model_profile: str,
|
|
196
|
+
) -> LoadedSettings:
|
|
197
|
+
model = model_profile.strip()
|
|
198
|
+
if not model:
|
|
199
|
+
return loaded
|
|
200
|
+
profile = resolve_model_profile_selector(model_registry, model)
|
|
201
|
+
if profile is None:
|
|
202
|
+
error_message = f"Model profile not found: {model}"
|
|
203
|
+
available = format_available_profile_labels(model_registry)
|
|
204
|
+
if available:
|
|
205
|
+
error_message = f"{error_message}. Available model profiles: {available}"
|
|
206
|
+
parser.error(error_message)
|
|
207
|
+
# Keep startup --model durable across SettingsReload without writing the
|
|
208
|
+
# user's .env. ModelConfigScreen may still replace this process-local value
|
|
209
|
+
# later when the user intentionally applies a different active profile.
|
|
210
|
+
set_model_pointer(profile.id, origin=SettingOrigin(layer=Source.CLI))
|
|
211
|
+
return loaded_with_active_model_profile(loaded, profile, Source.CLI)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _restore_terminal_after_run(app: ChrysApp) -> None:
|
|
215
|
+
"""Restore terminal state after a native TUI run."""
|
|
216
|
+
if running_under_textual_web_driver():
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
import sys
|
|
220
|
+
|
|
221
|
+
reset = _TERMINAL_MODE_RESET
|
|
222
|
+
# Include alt-screen exit only when Textual exited normally. On error exit,
|
|
223
|
+
# Textual already sent \x1b[?1049l in stop_application_mode() before
|
|
224
|
+
# printing the traceback to stderr.
|
|
225
|
+
if app._return_code == 0:
|
|
226
|
+
reset += "\x1b[?1049l" # Exit alt screen
|
|
227
|
+
sys.stderr.write(reset)
|
|
228
|
+
sys.stderr.flush()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _reset_terminal_title_after_run() -> None:
|
|
232
|
+
"""Leave the host shell with a cwd title instead of the last prompt preview."""
|
|
233
|
+
set_terminal_title_for_current_cwd()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class ChrysApp(TuiVariableDefaultsMixin, App):
|
|
237
|
+
"""Textual application for Chrys."""
|
|
238
|
+
|
|
239
|
+
TITLE = format_app_version_title(__version__)
|
|
240
|
+
CSS_PATH: ClassVar[list[str]] = ["chrys.tcss", "screens/themes/editor.tcss"]
|
|
241
|
+
COMMAND_PALETTE_BINDING = "ctrl+q"
|
|
242
|
+
BINDINGS: ClassVar[list] = [
|
|
243
|
+
# Application fallbacks cover selectable screens and stock Textual
|
|
244
|
+
# controls. Chrys editors bind these keys themselves so specialized
|
|
245
|
+
# paste behavior (image paste and Vim mode gating) runs first, and
|
|
246
|
+
# modal roots repeat the bindings because Textual stops the
|
|
247
|
+
# non-priority binding chain at a modal screen.
|
|
248
|
+
*INSERT_CLIPBOARD_BINDINGS,
|
|
249
|
+
# Shadow stock App's ctrl+q so screens without their own quit binding
|
|
250
|
+
# (e.g. DiffScreen) still resolve a localized command-palette slot.
|
|
251
|
+
localized_binding("ctrl+q", "quit", QUIT_BINDING, show=False, priority=True),
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
event_bus: EventBus,
|
|
257
|
+
engine: AgentEngine,
|
|
258
|
+
settings: Settings | None = None,
|
|
259
|
+
profile_name: str = "",
|
|
260
|
+
state_store: JsonFileStateStore | None = None,
|
|
261
|
+
agent_registry: AgentProfileRegistry | None = None,
|
|
262
|
+
model_registry: ModelProfileRegistry | None = None,
|
|
263
|
+
startup_warnings: list[Warning] | None = None,
|
|
264
|
+
settings_warnings: list[Warning] | None = None,
|
|
265
|
+
startup_session_id: str = "",
|
|
266
|
+
apply_saved_model_on_restore: bool = True,
|
|
267
|
+
session_title_updater: SessionTitleUpdater | None = None,
|
|
268
|
+
*,
|
|
269
|
+
localizer: Localizer | None = None,
|
|
270
|
+
settings_handle: SettingsHandle | None = None,
|
|
271
|
+
gc_freeze_enabled: bool | None = None,
|
|
272
|
+
gc_freeze_measure_counts: bool = False,
|
|
273
|
+
) -> None:
|
|
274
|
+
self._bus = event_bus
|
|
275
|
+
self._engine = engine
|
|
276
|
+
if settings_handle is not None and settings is not None and settings is not settings_handle.settings:
|
|
277
|
+
error_message = "Pass either settings or settings_handle, not two different ones."
|
|
278
|
+
raise ValueError(error_message)
|
|
279
|
+
# ``main()`` passes the engine's handle, so the two read one value
|
|
280
|
+
# rather than two that started equal. An app built without one owns
|
|
281
|
+
# its settings alone.
|
|
282
|
+
self._settings_handle = settings_handle or SettingsHandle(
|
|
283
|
+
LoadedSettings(settings=settings or Settings.from_env(), provenance={})
|
|
284
|
+
)
|
|
285
|
+
self._locale_controller = LocaleController(
|
|
286
|
+
localizer=localizer,
|
|
287
|
+
settings_handle=self._settings_handle,
|
|
288
|
+
)
|
|
289
|
+
self._profile_name = (profile_name or self._settings.default_agent).strip() or DEFAULT_AGENT_PROFILE
|
|
290
|
+
self._state_store = state_store or JsonFileStateStore()
|
|
291
|
+
self._agent_registry = agent_registry or AgentProfileRegistry()
|
|
292
|
+
self._model_registry = model_registry or ModelProfileRegistry()
|
|
293
|
+
# Warnings collected during pre-mount setup (e.g. invalid env
|
|
294
|
+
# values scrubbed before the bus had subscribers); flushed after
|
|
295
|
+
# MainScreen subscribes to ``Warning`` so the user sees them as
|
|
296
|
+
# toast notifications.
|
|
297
|
+
self._startup_session_id = startup_session_id.strip()
|
|
298
|
+
self._startup_warnings: list[Warning] = list(startup_warnings or [])
|
|
299
|
+
# Settings-derived warnings describe the bootstrap load rooted at the
|
|
300
|
+
# process cwd. A startup restore replaces those settings with ones
|
|
301
|
+
# loaded from the restored session's own root — and the restore
|
|
302
|
+
# republishes that load's warnings itself — so flushing these up
|
|
303
|
+
# front would report a repository the session doesn't live in (or
|
|
304
|
+
# duplicate every warning when the roots coincide). Deferred until
|
|
305
|
+
# the restore's outcome is known; dropped on success, flushed when
|
|
306
|
+
# the app falls back to the bootstrap settings.
|
|
307
|
+
self._deferred_settings_warnings: list[Warning] = []
|
|
308
|
+
if self._startup_session_id:
|
|
309
|
+
self._deferred_settings_warnings.extend(settings_warnings or [])
|
|
310
|
+
else:
|
|
311
|
+
self._startup_warnings.extend(settings_warnings or [])
|
|
312
|
+
if self.localizer.first_load_warning is not None:
|
|
313
|
+
self._startup_warnings.append(catalog_load_warning(self.localizer.first_load_warning))
|
|
314
|
+
self._apply_saved_model_on_restore = apply_saved_model_on_restore
|
|
315
|
+
self._session_title_updater = session_title_updater
|
|
316
|
+
# Projected from the loaded settings: bootstrap folds any legacy
|
|
317
|
+
# ``notifications.yaml`` into the settings document before those
|
|
318
|
+
# settings are loaded, so this view never reads the retired file.
|
|
319
|
+
self._notification_service = NotificationService(
|
|
320
|
+
self,
|
|
321
|
+
NotificationSettings.from_settings(self._settings),
|
|
322
|
+
locale_controller=self._locale_controller,
|
|
323
|
+
)
|
|
324
|
+
# Crash-log state: first crash within a run truncates error.log,
|
|
325
|
+
# subsequent crashes append (so cascading teardown errors survive).
|
|
326
|
+
self._crash_log_initialized = False
|
|
327
|
+
self._startup_task: asyncio.Task[None] | None = None
|
|
328
|
+
self._main_screen: MainScreen | None = None
|
|
329
|
+
self._gc_freeze_watchdog: Timer | None = None
|
|
330
|
+
self._gc_pointer_buttons_down: set[int] = set()
|
|
331
|
+
self._gc_effective_theme = ""
|
|
332
|
+
# Set before ``super().__init__()``: Textual initialization can invoke
|
|
333
|
+
# the theme watcher, which must not treat startup as a user's choice.
|
|
334
|
+
self._theme_persist_armed = False
|
|
335
|
+
self.theme_preview: ThemePreview | None = None
|
|
336
|
+
self._theme_preview_source = ""
|
|
337
|
+
self._theme_selection_depth = 0
|
|
338
|
+
freeze_enabled = GC_FREEZE_ENABLED if gc_freeze_enabled is None else gc_freeze_enabled
|
|
339
|
+
# Textual initialization may invoke reactive watchers, so the
|
|
340
|
+
# coordinator exists first. It stays inert until ``start()`` after the
|
|
341
|
+
# main screen has mounted.
|
|
342
|
+
self._gc_freeze = GcFreezeCoordinator(
|
|
343
|
+
self,
|
|
344
|
+
enabled=freeze_enabled,
|
|
345
|
+
measure_freeze_counts=gc_freeze_measure_counts,
|
|
346
|
+
)
|
|
347
|
+
super().__init__()
|
|
348
|
+
self.register_theme(CHRYS_THEME)
|
|
349
|
+
self.register_theme(CHRYS_LEGACY_THEME)
|
|
350
|
+
self.register_theme(CHRYS_ANSI_THEME)
|
|
351
|
+
# User themes come from <config_dir>/themes/*.yaml; per-file failures
|
|
352
|
+
# surface as startup warnings and never block the app.
|
|
353
|
+
user_themes, user_theme_warnings = load_user_themes()
|
|
354
|
+
for user_theme in user_themes:
|
|
355
|
+
self.register_theme(user_theme)
|
|
356
|
+
self._user_theme_names = {theme.name for theme in user_themes}
|
|
357
|
+
stylesheet = ThemeStylesheet(variables=self.get_css_variables(), recover=self._recover_user_theme)
|
|
358
|
+
stylesheet.source = self.stylesheet.source.copy()
|
|
359
|
+
self.stylesheet = stylesheet
|
|
360
|
+
self._startup_warnings.extend(user_theme_warnings)
|
|
361
|
+
# Resolve the persisted theme; fall back to the default if empty,
|
|
362
|
+
# unknown, or unregistered.
|
|
363
|
+
theme_name = self._settings.theme
|
|
364
|
+
if not theme_name or theme_name not in self.available_themes:
|
|
365
|
+
theme_name = DEFAULT_THEME
|
|
366
|
+
self.theme = theme_name
|
|
367
|
+
# Only now may a theme change be written back. The fallback above is a
|
|
368
|
+
# decision about what to *draw* when the saved name resolves to nothing
|
|
369
|
+
# — a theme renamed between releases, a typo, a value some other writer
|
|
370
|
+
# damaged. Persisting it would delete the name the user chose, which is
|
|
371
|
+
# the one thing the loader's ``FALL_THROUGH`` policy exists to preserve:
|
|
372
|
+
# an unusable value costs them this session's colours, not the setting.
|
|
373
|
+
self._theme_persist_armed = True
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def _settings(self) -> Settings:
|
|
377
|
+
"""Read-only: settings change through the handle, not the holder."""
|
|
378
|
+
return self._settings_handle.settings
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def _loaded_settings(self) -> LoadedSettings:
|
|
382
|
+
return self._settings_handle.loaded
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def settings_handle(self) -> SettingsHandle:
|
|
386
|
+
"""The shared settings cell, for screens that project or overlay it."""
|
|
387
|
+
return self._settings_handle
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def engine(self) -> AgentEngine:
|
|
391
|
+
"""Public accessor for the backend engine.
|
|
392
|
+
|
|
393
|
+
Screens and widgets should read the engine through this property
|
|
394
|
+
rather than poking at ``self.app._engine`` — keeps the
|
|
395
|
+
private-attr boundary explicit and lets the TUI layer mock a
|
|
396
|
+
stand-in engine in tests by subclassing ``ChrysApp``.
|
|
397
|
+
"""
|
|
398
|
+
return self._engine
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def localizer(self) -> Localizer:
|
|
402
|
+
"""Application-owned display localizer."""
|
|
403
|
+
return self._locale_controller.localizer
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def locale_controller(self) -> LocaleController:
|
|
407
|
+
"""Application-owned locale switch and live-surface controller."""
|
|
408
|
+
return self._locale_controller
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def notification_service(self) -> NotificationService:
|
|
412
|
+
"""Runtime desktop notification service."""
|
|
413
|
+
return self._notification_service
|
|
414
|
+
|
|
415
|
+
def override_notification_settings(self, settings: NotificationSettings) -> None:
|
|
416
|
+
"""Install a live notification choice in the shared handle.
|
|
417
|
+
|
|
418
|
+
The same pairing every other live surface uses — theme, locale and the
|
|
419
|
+
approval mode each persist *and* overlay — and for the same reason. The
|
|
420
|
+
write to disk is debounced, so between the edit and the write the handle
|
|
421
|
+
is the only place the choice exists; a reload landing in that window
|
|
422
|
+
installs the pre-edit document, and :meth:`refresh_notification_settings`
|
|
423
|
+
then projects it straight back over the delivery service. The overlay
|
|
424
|
+
survives that reload by construction, which is what makes the edit
|
|
425
|
+
survive it too.
|
|
426
|
+
"""
|
|
427
|
+
self._settings_handle.override(**settings.to_settings_fields())
|
|
428
|
+
|
|
429
|
+
def refresh_notification_settings(self) -> None:
|
|
430
|
+
"""Re-project the notification settings into the delivery service.
|
|
431
|
+
|
|
432
|
+
The service holds a projection taken from ``Settings``, not the
|
|
433
|
+
settings themselves — so when a reload installs new values into the
|
|
434
|
+
shared handle, the LIVE ``notifications.*`` fields only take effect
|
|
435
|
+
once someone projects them again. This is that someone.
|
|
436
|
+
"""
|
|
437
|
+
self._notification_service.update_settings(NotificationSettings.from_settings(self._settings))
|
|
438
|
+
|
|
439
|
+
def freeze_block_reason(self) -> GcFreezeBlockReason | None:
|
|
440
|
+
"""Return the App-owned top-screen gate for GC freeze dispatch."""
|
|
441
|
+
if self._gc_pointer_buttons_down:
|
|
442
|
+
return GcFreezeBlockReason.POINTER_BUTTON_HELD
|
|
443
|
+
# Tests use deliberately tiny engine stand-ins. Production always
|
|
444
|
+
# owns AgentEngine, whose run task remains active through session save
|
|
445
|
+
# and after-turn hooks even after the UI has rendered its terminal.
|
|
446
|
+
if isinstance(self._engine, AgentEngine) and self._engine.is_turn_lifecycle_active:
|
|
447
|
+
return GcFreezeBlockReason.BACKEND_TURN_LIFECYCLE
|
|
448
|
+
if self._main_screen is None or self.screen is not self._main_screen:
|
|
449
|
+
return GcFreezeBlockReason.TOP_SCREEN
|
|
450
|
+
return self._main_screen.gc_freeze_block_reason()
|
|
451
|
+
|
|
452
|
+
def prepare_for_gc_freeze(self) -> None:
|
|
453
|
+
"""Prepare mounted freeze participants.
|
|
454
|
+
|
|
455
|
+
The MainScreen aggregate owns the concrete participant list.
|
|
456
|
+
"""
|
|
457
|
+
if self._main_screen is not None:
|
|
458
|
+
self._main_screen.prepare_for_gc_freeze()
|
|
459
|
+
|
|
460
|
+
def after_gc_freeze(self) -> None:
|
|
461
|
+
"""Renew mounted mutable cyclic participants after a freeze.
|
|
462
|
+
|
|
463
|
+
The MainScreen aggregate owns the concrete participant list.
|
|
464
|
+
"""
|
|
465
|
+
if self._main_screen is not None:
|
|
466
|
+
self._main_screen.after_gc_freeze()
|
|
467
|
+
|
|
468
|
+
def abort_gc_freeze(self) -> None:
|
|
469
|
+
"""Best-effort normalize mounted caches after an incomplete hook pass."""
|
|
470
|
+
if self._main_screen is not None:
|
|
471
|
+
self._main_screen.abort_gc_freeze()
|
|
472
|
+
|
|
473
|
+
def on_gc_absorb_requested(self, event: GcAbsorbRequested) -> None:
|
|
474
|
+
"""Forward a UI-local absorb request with its source timestamp."""
|
|
475
|
+
self._gc_freeze.request_absorb(
|
|
476
|
+
reason=event.reason,
|
|
477
|
+
terminal_boundary=event.terminal_boundary,
|
|
478
|
+
requested_at=event.time,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def on_gc_reclaim_requested(self, event: GcReclaimRequested) -> None:
|
|
482
|
+
"""Forward a UI-local reclaim request with its source timestamp."""
|
|
483
|
+
self._gc_freeze.request_reclaim(
|
|
484
|
+
reason=event.reason,
|
|
485
|
+
prompt=event.prompt,
|
|
486
|
+
requested_at=event.time,
|
|
487
|
+
removed=event.removed,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
def on_diagram_open_requested(self, event: DiagramOpenRequested) -> None:
|
|
491
|
+
"""Present a Markdown diagram without introducing a widget-to-screen import."""
|
|
492
|
+
from chrys.app.tui.screens.dialogs.diagram import DiagramDialog
|
|
493
|
+
|
|
494
|
+
event.stop()
|
|
495
|
+
self.push_screen(DiagramDialog(event.diagram))
|
|
496
|
+
|
|
497
|
+
def _watch_app_focus(self, focus: bool) -> None:
|
|
498
|
+
"""Preserve native widget focus without restyling or relayout.
|
|
499
|
+
|
|
500
|
+
Textual clears and restores the focused widget across App blur/focus.
|
|
501
|
+
Each transition refreshes bindings, which makes Footer request several
|
|
502
|
+
full-screen layouts. Chrys's native UI has no App-focus-dependent CSS,
|
|
503
|
+
and it receives no input while the terminal is unfocused, so retaining
|
|
504
|
+
the internal focused widget is both sufficient and transcript-size
|
|
505
|
+
independent. The web host keeps Textual's exact behavior.
|
|
506
|
+
"""
|
|
507
|
+
if running_under_textual_web_driver():
|
|
508
|
+
super()._watch_app_focus(focus)
|
|
509
|
+
return
|
|
510
|
+
|
|
511
|
+
self._last_focused_on_app_blur = None
|
|
512
|
+
|
|
513
|
+
def _on_app_focus(self, event: textual_events.AppFocus) -> None: # ty: ignore[invalid-method-override] # Textual dispatch accepts sync handlers.
|
|
514
|
+
"""Record focus and recover bindings for the visible screen only."""
|
|
515
|
+
self._notification_service.set_focused(True)
|
|
516
|
+
if running_under_textual_web_driver():
|
|
517
|
+
return
|
|
518
|
+
event.prevent_default()
|
|
519
|
+
self.app_focus = True
|
|
520
|
+
screen_stack = self.screen_stack
|
|
521
|
+
if not screen_stack:
|
|
522
|
+
return
|
|
523
|
+
screen = screen_stack[-1]
|
|
524
|
+
if self._main_screen is not None and screen is self._main_screen:
|
|
525
|
+
self._main_screen.sync_footer_bindings()
|
|
526
|
+
else:
|
|
527
|
+
# Stock Footer deliberately ignores binding changes while the App
|
|
528
|
+
# is unfocused. Deferred screens may become interactive during
|
|
529
|
+
# that interval, so mirror Textual's refocus recovery for the
|
|
530
|
+
# active overlay without restyling the suspended transcript.
|
|
531
|
+
screen.refresh_bindings()
|
|
532
|
+
|
|
533
|
+
def _on_app_blur(self, event: textual_events.AppBlur) -> None: # ty: ignore[invalid-method-override] # Textual dispatch accepts sync handlers.
|
|
534
|
+
"""Record blur while retaining the native focused widget."""
|
|
535
|
+
self._notification_service.set_focused(False)
|
|
536
|
+
if running_under_textual_web_driver():
|
|
537
|
+
return
|
|
538
|
+
event.prevent_default()
|
|
539
|
+
self.app_focus = False
|
|
540
|
+
|
|
541
|
+
def _load_screen_css(self, screen: Screen) -> None:
|
|
542
|
+
"""Load native secondary-screen CSS without restyling the underlay.
|
|
543
|
+
|
|
544
|
+
Textual updates the entire App when it discovers a Screen stylesheet.
|
|
545
|
+
Chrys secondary-screen styles are screen-local, so existing MainScreen
|
|
546
|
+
nodes cannot start matching them. Parse globally so subsequently mounted
|
|
547
|
+
children see the new rules, then update only the already-registered
|
|
548
|
+
screen root. Keep Textual's behavior for web and the startup MainScreen.
|
|
549
|
+
|
|
550
|
+
This is a version-gated fork of Textual's private
|
|
551
|
+
``App._load_screen_css`` implementation. The intentional semantic deltas
|
|
552
|
+
are updating ``screen`` rather than the entire App, and parsing the live
|
|
553
|
+
stylesheet in place rather than reparsing into a fresh one: the fresh
|
|
554
|
+
stylesheet's empty rule cache re-tokenizes every source, while the live
|
|
555
|
+
cache only holds rules for the current variables (``set_variables``
|
|
556
|
+
clears it), so only the new sources are tokenized.
|
|
557
|
+
"""
|
|
558
|
+
if (
|
|
559
|
+
TEXTUAL_VERSION != _TEXTUAL_LOAD_SCREEN_CSS_FORK_VERSION
|
|
560
|
+
or running_under_textual_web_driver()
|
|
561
|
+
or isinstance(screen, MainScreen)
|
|
562
|
+
):
|
|
563
|
+
super()._load_screen_css(screen)
|
|
564
|
+
return
|
|
565
|
+
|
|
566
|
+
if self.css_monitor is not None:
|
|
567
|
+
self.css_monitor.add_paths(screen.css_path)
|
|
568
|
+
|
|
569
|
+
update = False
|
|
570
|
+
for path in screen.css_path:
|
|
571
|
+
if not self.stylesheet.has_source(str(path), ""):
|
|
572
|
+
self.stylesheet.read(path)
|
|
573
|
+
update = True
|
|
574
|
+
if screen.CSS:
|
|
575
|
+
try:
|
|
576
|
+
screen_path = inspect.getfile(screen.__class__)
|
|
577
|
+
except TypeError, OSError:
|
|
578
|
+
screen_path = ""
|
|
579
|
+
screen_class_var = f"{screen.__class__.__name__}.CSS"
|
|
580
|
+
if not self.stylesheet.has_source(screen_path, screen_class_var):
|
|
581
|
+
self.stylesheet.add_source(
|
|
582
|
+
screen.CSS,
|
|
583
|
+
read_from=(screen_path, screen_class_var),
|
|
584
|
+
is_default_css=False,
|
|
585
|
+
scope=screen._css_type_name if screen.SCOPED_CSS else "",
|
|
586
|
+
)
|
|
587
|
+
update = True
|
|
588
|
+
if update:
|
|
589
|
+
self.stylesheet.parse()
|
|
590
|
+
self.stylesheet.update(screen)
|
|
591
|
+
|
|
592
|
+
def _refresh_notifications(self) -> None:
|
|
593
|
+
"""Show toasts under the header with the newest on top.
|
|
594
|
+
|
|
595
|
+
Textual appends each new toast below the older ones and scrolls the
|
|
596
|
+
rack to its end, the right stacking for its bottom-docked rack. Chrys
|
|
597
|
+
docks the rack under the header, so new toasts mount ahead of the
|
|
598
|
+
older ones and the rack keeps its start in view: the stack grows
|
|
599
|
+
downward from the newest toast.
|
|
600
|
+
|
|
601
|
+
This is a version-gated fork of Textual's private
|
|
602
|
+
``App._refresh_notifications`` and ``ToastRack.show``. The only
|
|
603
|
+
intentional semantic delta is where new toasts mount and which end of
|
|
604
|
+
the rack stays in view.
|
|
605
|
+
"""
|
|
606
|
+
if TEXTUAL_VERSION != TEXTUAL_TOAST_RACK_FORK_VERSION:
|
|
607
|
+
super()._refresh_notifications()
|
|
608
|
+
return
|
|
609
|
+
refresh_newest_first(self)
|
|
610
|
+
|
|
611
|
+
def _prune(self, *nodes: Widget, parent: DOMNode | None = None) -> AwaitRemove:
|
|
612
|
+
"""Prune whole-screen overlays without relayout of the screen beneath.
|
|
613
|
+
|
|
614
|
+
Textual's generic DOM prune refreshes the parent with ``layout=True``.
|
|
615
|
+
Screens fill the viewport independently, so removing a Screen from the
|
|
616
|
+
App cannot change MainScreen geometry. Descendant pruning retains the
|
|
617
|
+
stock path, including its required parent layout.
|
|
618
|
+
"""
|
|
619
|
+
whole_screens = parent is self and nodes and all(isinstance(node, Screen) for node in nodes)
|
|
620
|
+
if not whole_screens:
|
|
621
|
+
return super()._prune(*nodes, parent=parent)
|
|
622
|
+
|
|
623
|
+
# Suppress only App.refresh(layout=True). Textual's normal post-prune
|
|
624
|
+
# path also transfers hover state to the newly revealed screen, which
|
|
625
|
+
# must still happen without moving the mouse.
|
|
626
|
+
await_remove = super()._prune(*nodes, parent=None)
|
|
627
|
+
try:
|
|
628
|
+
revealed_screen = self.screen
|
|
629
|
+
except ScreenStackError, NoScreen:
|
|
630
|
+
return await_remove
|
|
631
|
+
|
|
632
|
+
if self.mouse_over is None:
|
|
633
|
+
return await_remove
|
|
634
|
+
|
|
635
|
+
async def update_mouse_over_after_remove() -> None:
|
|
636
|
+
await await_remove
|
|
637
|
+
if revealed_screen._running:
|
|
638
|
+
try:
|
|
639
|
+
mouse_over, hover_over = revealed_screen.get_hover_widgets_at(*self.mouse_position).widgets
|
|
640
|
+
except NoWidget:
|
|
641
|
+
self._set_mouse_over(None, None)
|
|
642
|
+
else:
|
|
643
|
+
self._set_mouse_over(mouse_over, hover_over)
|
|
644
|
+
|
|
645
|
+
self.call_next(update_mouse_over_after_remove)
|
|
646
|
+
return await_remove
|
|
647
|
+
|
|
648
|
+
async def on_event(self, event: textual_events.Event) -> None:
|
|
649
|
+
"""Observe GC-relevant activity before Textual dispatches it."""
|
|
650
|
+
if not event.is_forwarded:
|
|
651
|
+
if isinstance(event, textual_events.MouseDown) and event.button:
|
|
652
|
+
self._gc_pointer_buttons_down.add(event.button)
|
|
653
|
+
elif isinstance(event, textual_events.MouseUp) and event.button:
|
|
654
|
+
self._gc_pointer_buttons_down.discard(event.button)
|
|
655
|
+
elif isinstance(event, textual_events.MouseMove):
|
|
656
|
+
if event.button:
|
|
657
|
+
self._gc_pointer_buttons_down.add(event.button)
|
|
658
|
+
self._gc_freeze.note_input(occurred_at=event.time)
|
|
659
|
+
elif self._gc_pointer_buttons_down:
|
|
660
|
+
self._gc_pointer_buttons_down.clear()
|
|
661
|
+
elif isinstance(event, (textual_events.AppBlur, textual_events.AppFocus)):
|
|
662
|
+
self._gc_pointer_buttons_down.clear()
|
|
663
|
+
|
|
664
|
+
if isinstance(event, _GC_ACTIVITY_EVENTS):
|
|
665
|
+
self._gc_freeze.note_input(occurred_at=event.time)
|
|
666
|
+
if isinstance(event, textual_events.Resize):
|
|
667
|
+
self._gc_freeze.request_absorb(
|
|
668
|
+
reason=GcAbsorbReason.VIEWPORT_UPDATED,
|
|
669
|
+
terminal_boundary=False,
|
|
670
|
+
requested_at=event.time,
|
|
671
|
+
)
|
|
672
|
+
await super().on_event(event)
|
|
673
|
+
|
|
674
|
+
@property
|
|
675
|
+
def current_theme(self) -> Theme:
|
|
676
|
+
if self.theme_preview is not None and self.theme_preview.current is not None:
|
|
677
|
+
return self.theme_preview.current
|
|
678
|
+
return super().current_theme
|
|
679
|
+
|
|
680
|
+
def get_css_variables(self) -> dict[str, str]:
|
|
681
|
+
if self.theme_preview is not None and self.theme_preview.variables is not None:
|
|
682
|
+
self.theme_variables = self.theme_preview.variables.copy()
|
|
683
|
+
return self.theme_variables
|
|
684
|
+
return super().get_css_variables()
|
|
685
|
+
|
|
686
|
+
def begin_theme_preview(self, theme: Theme) -> bool:
|
|
687
|
+
if self.theme_preview is not None:
|
|
688
|
+
return False
|
|
689
|
+
self._theme_preview_source = self.theme
|
|
690
|
+
self.theme_preview = ThemePreview(self)
|
|
691
|
+
return self.theme_preview.show(theme)
|
|
692
|
+
|
|
693
|
+
def end_theme_preview(self) -> None:
|
|
694
|
+
preview = self.theme_preview
|
|
695
|
+
if preview is None:
|
|
696
|
+
return
|
|
697
|
+
source = self.get_theme(self.theme)
|
|
698
|
+
restored = source is not None and preview.show(source)
|
|
699
|
+
self.theme_preview = None
|
|
700
|
+
if not restored:
|
|
701
|
+
# Restore classes, Rich consumers and CSS even if preview admission
|
|
702
|
+
# failed. Closing an editor must never persist a fallback selection.
|
|
703
|
+
self.suspend_theme_choice()
|
|
704
|
+
try:
|
|
705
|
+
if source is None:
|
|
706
|
+
self.set_reactive(App.theme, DEFAULT_THEME)
|
|
707
|
+
self._watch_theme(self.theme)
|
|
708
|
+
finally:
|
|
709
|
+
self.resume_theme_choice()
|
|
710
|
+
|
|
711
|
+
def suspend_theme_choice(self) -> None:
|
|
712
|
+
self._theme_selection_depth += 1
|
|
713
|
+
|
|
714
|
+
def resume_theme_choice(self) -> None:
|
|
715
|
+
self._theme_selection_depth = max(0, self._theme_selection_depth - 1)
|
|
716
|
+
|
|
717
|
+
def register_user_theme(self, theme: Theme) -> None:
|
|
718
|
+
from chrys.app.tui.theme_loader import theme_is_read_only
|
|
719
|
+
|
|
720
|
+
if theme_is_read_only(theme.name):
|
|
721
|
+
raise ValueError("Built-in themes are read-only")
|
|
722
|
+
self.register_theme(theme)
|
|
723
|
+
self._user_theme_names.add(theme.name)
|
|
724
|
+
|
|
725
|
+
def apply_saved_theme(self, theme: Theme) -> None:
|
|
726
|
+
"""Apply an editor's successful save and make it the new preview exit target."""
|
|
727
|
+
self.register_user_theme(theme)
|
|
728
|
+
preview = self.theme_preview
|
|
729
|
+
# Only a completed save commits a selection while editing. Reuse the
|
|
730
|
+
# normal watcher/persistence path, then retain the editor's live preview.
|
|
731
|
+
self.theme_preview = None
|
|
732
|
+
try:
|
|
733
|
+
self.apply_theme_setting(theme.name)
|
|
734
|
+
self._theme_preview_source = self.theme
|
|
735
|
+
finally:
|
|
736
|
+
self.theme_preview = preview
|
|
737
|
+
|
|
738
|
+
def unregister_user_theme(self, name: str) -> None:
|
|
739
|
+
"""Forget a deleted file and repair its applied selection, including previews."""
|
|
740
|
+
from chrys.app.tui.theme_loader import theme_is_read_only
|
|
741
|
+
|
|
742
|
+
if theme_is_read_only(name):
|
|
743
|
+
raise ValueError("Built-in themes are read-only")
|
|
744
|
+
if name in {self.theme, self._settings.theme}:
|
|
745
|
+
preview = self.theme_preview
|
|
746
|
+
self.theme_preview = None
|
|
747
|
+
previous = self.theme
|
|
748
|
+
self.apply_theme_setting(DEFAULT_THEME)
|
|
749
|
+
if preview is not None:
|
|
750
|
+
# Applying a different name schedules the required style pass.
|
|
751
|
+
# An already active fallback also needs to shed its draft CSS.
|
|
752
|
+
if previous == self.theme:
|
|
753
|
+
self._watch_theme(self.theme)
|
|
754
|
+
self._theme_preview_source = self.theme
|
|
755
|
+
self.theme_preview = ThemePreview(self)
|
|
756
|
+
self.unregister_theme(name)
|
|
757
|
+
self._user_theme_names.discard(name)
|
|
758
|
+
|
|
759
|
+
def _watch_theme(self, theme_name: str) -> None:
|
|
760
|
+
"""Apply the selected theme and persist confirmed choices."""
|
|
761
|
+
if self.theme_preview is not None:
|
|
762
|
+
self.set_reactive(App.theme, self._theme_preview_source)
|
|
763
|
+
return
|
|
764
|
+
previous_theme = self._gc_effective_theme
|
|
765
|
+
if self.ansi_color is not None:
|
|
766
|
+
self.ansi_color = None
|
|
767
|
+
super()._watch_theme(theme_name)
|
|
768
|
+
# Persist the choice so it survives restart. Skip no-op writes
|
|
769
|
+
# (startup applies the already-persisted value), ignore transient names
|
|
770
|
+
# not registered as real themes, and stay out of startup entirely —
|
|
771
|
+
# see ``_theme_persist_armed`` in ``__init__``.
|
|
772
|
+
if (
|
|
773
|
+
self._theme_persist_armed
|
|
774
|
+
and theme_name
|
|
775
|
+
and theme_name in self.available_themes
|
|
776
|
+
and theme_name != self._settings.theme
|
|
777
|
+
):
|
|
778
|
+
self._record_theme_choice(theme_name)
|
|
779
|
+
self._gc_effective_theme = theme_name
|
|
780
|
+
if previous_theme and theme_name != previous_theme:
|
|
781
|
+
self._gc_freeze.request_reclaim(
|
|
782
|
+
reason=GcReclaimReason.THEME_UPDATED,
|
|
783
|
+
prompt=False,
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
def _recover_user_theme(self, stylesheet: Stylesheet, error: Exception) -> bool:
|
|
787
|
+
"""Fall back only if these same styles work with the default theme."""
|
|
788
|
+
if self.theme_preview is not None:
|
|
789
|
+
return self.theme_preview.recover(stylesheet, error)
|
|
790
|
+
name = self.theme
|
|
791
|
+
if name not in self._user_theme_names:
|
|
792
|
+
return False
|
|
793
|
+
fallback = with_tui_css_variables(
|
|
794
|
+
CHRYS_THEME,
|
|
795
|
+
{**self.get_theme_variable_defaults(), **CHRYS_THEME.to_color_system().generate(), **CHRYS_THEME.variables},
|
|
796
|
+
)
|
|
797
|
+
probe = Stylesheet(variables=fallback)
|
|
798
|
+
probe.source = stylesheet.source.copy()
|
|
799
|
+
try:
|
|
800
|
+
probe.parse()
|
|
801
|
+
except StylesheetError, TokenError:
|
|
802
|
+
# An application CSS error must still surface as an application error.
|
|
803
|
+
return False
|
|
804
|
+
stylesheet.set_variables(fallback)
|
|
805
|
+
# As with an unknown saved theme, recovery must not overwrite the choice.
|
|
806
|
+
armed = self._theme_persist_armed
|
|
807
|
+
self._theme_persist_armed = False
|
|
808
|
+
try:
|
|
809
|
+
self.theme = DEFAULT_THEME
|
|
810
|
+
finally:
|
|
811
|
+
self._theme_persist_armed = armed
|
|
812
|
+
logger.warning("User theme %r failed CSS parsing; using %s", name, DEFAULT_THEME, exc_info=error)
|
|
813
|
+
warning = user_theme_warning(name, f"Theme {name!r} has invalid CSS; using {DEFAULT_THEME}")
|
|
814
|
+
if self._main_screen is None:
|
|
815
|
+
self._startup_warnings.append(warning)
|
|
816
|
+
else:
|
|
817
|
+
self.call_next(self._bus.publish, warning)
|
|
818
|
+
return True
|
|
819
|
+
|
|
820
|
+
def _record_theme_choice(self, theme_name: str) -> None:
|
|
821
|
+
"""Persist a theme choice and overlay it on the shared handle, as a pair."""
|
|
822
|
+
if self.theme_preview is not None or self._theme_selection_depth:
|
|
823
|
+
return
|
|
824
|
+
persist_theme(theme_name)
|
|
825
|
+
self._settings_handle.override(theme=theme_name)
|
|
826
|
+
|
|
827
|
+
def apply_theme_setting(self, theme_name: str) -> bool:
|
|
828
|
+
"""Apply and persist an explicit theme choice, including an unchanged fallback.
|
|
829
|
+
|
|
830
|
+
Normally the reactive assignment runs ``_watch_theme`` and that
|
|
831
|
+
persists. The exception is a theme already showing but not saved —
|
|
832
|
+
a saved theme that no longer exists fell back to the default at
|
|
833
|
+
startup, and the user now picks that default: the reactive does not
|
|
834
|
+
fire on an equal value, so the persist/overlay pair runs directly.
|
|
835
|
+
"""
|
|
836
|
+
if self.theme_preview is not None:
|
|
837
|
+
self.notify(render_str(self.localizer, THEME_PREVIEW_HINT.bind()), severity="warning", markup=False)
|
|
838
|
+
return False
|
|
839
|
+
if theme_name not in self.available_themes:
|
|
840
|
+
return False
|
|
841
|
+
if theme_name == self.theme:
|
|
842
|
+
if theme_name != self._settings.theme:
|
|
843
|
+
self._record_theme_choice(theme_name)
|
|
844
|
+
return True
|
|
845
|
+
self.theme = theme_name
|
|
846
|
+
return True
|
|
847
|
+
|
|
848
|
+
def action_command_palette(self) -> None:
|
|
849
|
+
"""Repurpose command palette shortcut as quit."""
|
|
850
|
+
self.exit()
|
|
851
|
+
|
|
852
|
+
async def action_quit(self) -> None:
|
|
853
|
+
"""Ctrl+Q with a modal on top: let the Settings dialog commit what is being typed first.
|
|
854
|
+
|
|
855
|
+
The dialog may sit under another modal (a confirmation, a file picker),
|
|
856
|
+
so the whole stack is checked rather than the top screen alone.
|
|
857
|
+
"""
|
|
858
|
+
for screen in self.screen_stack:
|
|
859
|
+
if isinstance(screen, SettingsDialog):
|
|
860
|
+
screen.commit_pending()
|
|
861
|
+
self.exit()
|
|
862
|
+
|
|
863
|
+
def action_copy_with_insert(self) -> None:
|
|
864
|
+
"""Copy the focused editor selection, then fall back to the screen selection."""
|
|
865
|
+
copy_with_insert(self)
|
|
866
|
+
|
|
867
|
+
def action_paste_with_insert(self) -> None:
|
|
868
|
+
"""Route clipboard text through the same Paste event used by the terminal driver."""
|
|
869
|
+
paste_with_insert(self)
|
|
870
|
+
|
|
871
|
+
def action_help_quit(self) -> None:
|
|
872
|
+
"""Localize stock App's inherited ctrl+c hint (English notify)."""
|
|
873
|
+
for key, active_binding in self.active_bindings.items():
|
|
874
|
+
if active_binding.binding.action in ("quit", "app.quit"):
|
|
875
|
+
self.notify(
|
|
876
|
+
render_str(self.localizer, _QUIT_HELP.bind(key=key)),
|
|
877
|
+
title=render_str(self.localizer, _QUIT_HELP_TITLE.bind()),
|
|
878
|
+
markup=False,
|
|
879
|
+
)
|
|
880
|
+
return
|
|
881
|
+
|
|
882
|
+
def _record_editor_keymap_override(self, value: str) -> None:
|
|
883
|
+
"""Record the editor's keymap pick like the theme and locale pickers.
|
|
884
|
+
|
|
885
|
+
A RUNTIME override on the shared handle keeps settings describing the
|
|
886
|
+
screen — the persisting write next to it swallows failures, and a
|
|
887
|
+
reload must not un-switch the visible keymap.
|
|
888
|
+
"""
|
|
889
|
+
self._settings_handle.override(editor_keymap=value)
|
|
890
|
+
|
|
891
|
+
def _build_main_screen(self) -> MainScreen:
|
|
892
|
+
"""Build the main screen mounted at startup.
|
|
893
|
+
|
|
894
|
+
Subclasses can override this factory to provide a MainScreen variant
|
|
895
|
+
while reusing the standard app bootstrap.
|
|
896
|
+
"""
|
|
897
|
+
return MainScreen(
|
|
898
|
+
self._bus,
|
|
899
|
+
state_store=self._state_store,
|
|
900
|
+
agent_registry=self._agent_registry,
|
|
901
|
+
model_registry=self._model_registry,
|
|
902
|
+
active_model_profile_id=self._settings.model_profile,
|
|
903
|
+
apply_saved_model_on_restore=self._apply_saved_model_on_restore,
|
|
904
|
+
workspace_mru_max_entries=self._settings.workspace_mru_max_entries,
|
|
905
|
+
editor_keymap=self._settings.editor_keymap,
|
|
906
|
+
trajectory_verify_commands=self._settings.trajectory_verify_commands,
|
|
907
|
+
engine_provider=lambda: self._engine,
|
|
908
|
+
settings_handle=self._settings_handle,
|
|
909
|
+
localization=self.localizer,
|
|
910
|
+
locale_controller=self._locale_controller,
|
|
911
|
+
on_editor_keymap_changed=self._record_editor_keymap_override,
|
|
912
|
+
tool_groups_expanded=lambda: self._settings_handle.settings.tool_groups_expanded,
|
|
913
|
+
approval_mode=self._engine.approval_mode,
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
async def on_mount(self) -> None:
|
|
917
|
+
"""Push the main screen and start the engine."""
|
|
918
|
+
self._set_terminal_title_for_cwd()
|
|
919
|
+
screen = self._build_main_screen()
|
|
920
|
+
await self.push_screen(screen)
|
|
921
|
+
self._main_screen = screen
|
|
922
|
+
self._gc_freeze.start()
|
|
923
|
+
if self._gc_freeze.startup_warning is not None:
|
|
924
|
+
self._startup_warnings.append(
|
|
925
|
+
Warning(code=_GC_FREEZE_DISABLED_WARNING_CODE, message=self._gc_freeze.startup_warning)
|
|
926
|
+
)
|
|
927
|
+
if self._gc_freeze.started:
|
|
928
|
+
self._gc_freeze_watchdog = self.set_interval(GC_FREEZE_WATCHDOG_PERIOD_SECONDS, self._gc_freeze.on_tick)
|
|
929
|
+
|
|
930
|
+
# MainScreen has now subscribed to ``Warning``; flush any
|
|
931
|
+
# warnings collected before the bus had listeners.
|
|
932
|
+
for w in self._startup_warnings:
|
|
933
|
+
await self._bus.publish(w)
|
|
934
|
+
self._startup_warnings.clear()
|
|
935
|
+
|
|
936
|
+
registry = self._agent_registry
|
|
937
|
+
if not registry.list_profiles():
|
|
938
|
+
registry.load_all()
|
|
939
|
+
|
|
940
|
+
profile = _resolve_startup_profile(registry, self._profile_name)
|
|
941
|
+
|
|
942
|
+
if profile is not None:
|
|
943
|
+
screen.set_startup_agent_loading(True)
|
|
944
|
+
self._startup_task = asyncio.create_task(self._start_engine(profile, screen))
|
|
945
|
+
else:
|
|
946
|
+
# No engine start means no restore either: the bootstrap settings
|
|
947
|
+
# stay in force, so their warnings are due after all.
|
|
948
|
+
await self._flush_deferred_settings_warnings()
|
|
949
|
+
|
|
950
|
+
async def _flush_deferred_settings_warnings(self) -> None:
|
|
951
|
+
for w in self._deferred_settings_warnings:
|
|
952
|
+
await self._bus.publish(w)
|
|
953
|
+
self._deferred_settings_warnings.clear()
|
|
954
|
+
|
|
955
|
+
async def _start_engine(self, profile: AgentProfile, screen: MainScreen) -> None:
|
|
956
|
+
"""Start the backend without holding Textual's mount path."""
|
|
957
|
+
try:
|
|
958
|
+
if self._startup_session_id:
|
|
959
|
+
# Session restore builds the saved profile/runtime itself. Preparing
|
|
960
|
+
# subscriptions first avoids building the default agent only to tear
|
|
961
|
+
# it down immediately in the restore path.
|
|
962
|
+
await self._engine.prepare(profile)
|
|
963
|
+
if await self._restore_startup_session(screen):
|
|
964
|
+
# The restore republished the warnings of its own settings
|
|
965
|
+
# load; the bootstrap ones no longer describe anything live.
|
|
966
|
+
self._deferred_settings_warnings.clear()
|
|
967
|
+
return
|
|
968
|
+
await self._engine.reset_after_failed_startup_restore()
|
|
969
|
+
await self._flush_deferred_settings_warnings()
|
|
970
|
+
await self._engine.start(profile)
|
|
971
|
+
except asyncio.CancelledError:
|
|
972
|
+
raise
|
|
973
|
+
except Exception as exc:
|
|
974
|
+
if screen.is_startup_agent_loading():
|
|
975
|
+
screen.set_startup_agent_loading(False)
|
|
976
|
+
self._show_startup_failure(screen, exc)
|
|
977
|
+
logger.exception("Engine startup failed")
|
|
978
|
+
|
|
979
|
+
async def _restore_startup_session(self, screen: MainScreen) -> bool:
|
|
980
|
+
"""Restore the requested startup session, returning whether it succeeded."""
|
|
981
|
+
session_id = self._startup_session_id
|
|
982
|
+
if not session_id:
|
|
983
|
+
return False
|
|
984
|
+
self._startup_session_id = ""
|
|
985
|
+
screen.set_startup_agent_loading(True)
|
|
986
|
+
try:
|
|
987
|
+
meta = await self._state_store.load_session_meta(session_id)
|
|
988
|
+
except Exception as exc:
|
|
989
|
+
screen.set_startup_agent_loading(False)
|
|
990
|
+
logger.warning("Failed to read startup session %s", session_id, exc_info=True)
|
|
991
|
+
self._show_startup_session_warning(
|
|
992
|
+
screen,
|
|
993
|
+
_SESSION_READ_FAILED.bind(session_id=session_short_id(session_id), error=str(exc)),
|
|
994
|
+
)
|
|
995
|
+
return False
|
|
996
|
+
if meta is None:
|
|
997
|
+
screen.set_startup_agent_loading(False)
|
|
998
|
+
self._show_startup_session_warning(
|
|
999
|
+
screen,
|
|
1000
|
+
_SESSION_NOT_FOUND.bind(session_id=session_short_id(session_id)),
|
|
1001
|
+
)
|
|
1002
|
+
return False
|
|
1003
|
+
session_id = meta.session_id or session_id
|
|
1004
|
+
await self._dismiss_startup_load_dialog_before_restore(screen)
|
|
1005
|
+
try:
|
|
1006
|
+
outcome = await screen.restore_startup_session(session_id)
|
|
1007
|
+
if outcome == "restored":
|
|
1008
|
+
return True
|
|
1009
|
+
screen.cancel_startup_session_restore()
|
|
1010
|
+
if outcome == "declined":
|
|
1011
|
+
# The user chose no folder for a session whose folder is gone: start fresh, quietly.
|
|
1012
|
+
return False
|
|
1013
|
+
logger.warning("Startup session restore produced no SessionRestored event for %s", session_id)
|
|
1014
|
+
self._show_startup_session_warning(
|
|
1015
|
+
screen,
|
|
1016
|
+
_SESSION_RESTORE_FALLBACK.bind(session_id=session_short_id(session_id)),
|
|
1017
|
+
)
|
|
1018
|
+
return False
|
|
1019
|
+
except Exception as exc:
|
|
1020
|
+
screen.cancel_startup_session_restore()
|
|
1021
|
+
screen.set_startup_agent_loading(False)
|
|
1022
|
+
logger.warning("Failed to restore startup session %s", session_id, exc_info=True)
|
|
1023
|
+
self._show_startup_session_warning(
|
|
1024
|
+
screen,
|
|
1025
|
+
_SESSION_RESTORE_FAILED.bind(session_id=session_short_id(session_id), error=str(exc)),
|
|
1026
|
+
)
|
|
1027
|
+
return False
|
|
1028
|
+
|
|
1029
|
+
async def _dismiss_startup_load_dialog_before_restore(self, screen: MainScreen) -> None:
|
|
1030
|
+
"""Clear the startup loading modal before opening the restore modal."""
|
|
1031
|
+
await screen.dismiss_startup_load_dialog_before_restore()
|
|
1032
|
+
|
|
1033
|
+
def _show_startup_session_warning(self, screen: MainScreen, message: MessageRef | str) -> None:
|
|
1034
|
+
with contextlib.suppress(Exception):
|
|
1035
|
+
from chrys.app.tui.widgets.chrome.status_bar import StatusBar
|
|
1036
|
+
|
|
1037
|
+
screen.query_one(StatusBar).flash(message, error=True)
|
|
1038
|
+
with contextlib.suppress(Exception):
|
|
1039
|
+
screen.notify(
|
|
1040
|
+
message if isinstance(message, str) else render_str(self.localizer, message),
|
|
1041
|
+
title=render_str(self.localizer, _SESSION_NOTIFICATION_TITLE.bind()),
|
|
1042
|
+
severity="warning",
|
|
1043
|
+
timeout=6,
|
|
1044
|
+
markup=False,
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
def _set_terminal_title_for_cwd(self) -> None:
|
|
1048
|
+
"""Set the terminal title for the active workspace directory."""
|
|
1049
|
+
set_app_terminal_title_for_current_cwd(self)
|
|
1050
|
+
|
|
1051
|
+
def _show_startup_failure(self, screen: MainScreen, exc: Exception) -> None:
|
|
1052
|
+
"""Surface startup failures that occur before AgentLoadStarted exists."""
|
|
1053
|
+
message = str(exc) or type(exc).__name__
|
|
1054
|
+
with contextlib.suppress(Exception):
|
|
1055
|
+
from chrys.app.tui.widgets.chrome.status_bar import StatusBar
|
|
1056
|
+
|
|
1057
|
+
screen.query_one(StatusBar).flash(STATUS_AGENT_STARTUP_FAILED.bind(message=message), error=True)
|
|
1058
|
+
with contextlib.suppress(Exception):
|
|
1059
|
+
screen.notify(
|
|
1060
|
+
message,
|
|
1061
|
+
title=render_str(self.localizer, _AGENT_STARTUP_FAILED_TITLE.bind()),
|
|
1062
|
+
severity="error",
|
|
1063
|
+
timeout=10,
|
|
1064
|
+
markup=False,
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
async def on_unmount(self) -> None:
|
|
1068
|
+
"""Shutdown engine on exit."""
|
|
1069
|
+
self._gc_freeze.close()
|
|
1070
|
+
if self._gc_freeze_watchdog is not None:
|
|
1071
|
+
self._gc_freeze_watchdog.stop()
|
|
1072
|
+
self._gc_freeze_watchdog = None
|
|
1073
|
+
if self._startup_task is not None and not self._startup_task.done():
|
|
1074
|
+
self._startup_task.cancel()
|
|
1075
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
1076
|
+
await self._startup_task
|
|
1077
|
+
self._startup_task = None
|
|
1078
|
+
await self._engine.shutdown()
|
|
1079
|
+
# After the engine: its shutdown drains a still-finalizing run
|
|
1080
|
+
# whose success callback can schedule one last title task; the
|
|
1081
|
+
# updater must be the last thing torn down so that task is
|
|
1082
|
+
# cancelled and awaited (its closed flag backstops other hosts).
|
|
1083
|
+
if self._session_title_updater is not None:
|
|
1084
|
+
await self._session_title_updater.shutdown()
|
|
1085
|
+
|
|
1086
|
+
def _handle_exception(self, error: Exception) -> None:
|
|
1087
|
+
"""Mirror Textual's crash handling, but also dump the traceback to a file.
|
|
1088
|
+
|
|
1089
|
+
Textual's default path renders the traceback to ``self.error_console``
|
|
1090
|
+
(stderr) only after the terminal has been restored at shutdown. That
|
|
1091
|
+
is great for visibility but the trace is lost the moment the user
|
|
1092
|
+
clears the terminal. Writing it to ``~/.chrys/logs/error.log`` gives
|
|
1093
|
+
the user a persistent copy without changing Textual's console output.
|
|
1094
|
+
"""
|
|
1095
|
+
# File logging must NEVER break the crash-handling path.
|
|
1096
|
+
with contextlib.suppress(Exception):
|
|
1097
|
+
self._write_crash_log(error)
|
|
1098
|
+
super()._handle_exception(error)
|
|
1099
|
+
|
|
1100
|
+
def _write_crash_log(self, error: Exception) -> None:
|
|
1101
|
+
"""Write a plain-text crash report to ``~/.chrys/logs/error.log``.
|
|
1102
|
+
|
|
1103
|
+
The first crash within a single app run truncates the file (so stale
|
|
1104
|
+
data from a previous run never confuses the user); subsequent crashes
|
|
1105
|
+
in the same run append, preserving cascading teardown errors that
|
|
1106
|
+
Textual would otherwise collect into ``_exit_renderables``.
|
|
1107
|
+
"""
|
|
1108
|
+
import traceback
|
|
1109
|
+
from datetime import datetime
|
|
1110
|
+
|
|
1111
|
+
pinfo = chrys_platform.get_platform()
|
|
1112
|
+
log_path = pinfo.config_dir / "logs" / "error.log"
|
|
1113
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1114
|
+
|
|
1115
|
+
mode = "w" if not self._crash_log_initialized else "a"
|
|
1116
|
+
self._crash_log_initialized = True
|
|
1117
|
+
|
|
1118
|
+
version = __version__
|
|
1119
|
+
|
|
1120
|
+
# Indent every line of the (possibly multi-line) error message by 4
|
|
1121
|
+
# spaces so continuation lines stay visually inside the Error block.
|
|
1122
|
+
msg_lines = str(error).splitlines() or [""]
|
|
1123
|
+
first_msg, *rest_msg = msg_lines
|
|
1124
|
+
message_block = "\n".join([f" Message: {first_msg}", *(f" {line}" for line in rest_msg)])
|
|
1125
|
+
|
|
1126
|
+
header = (
|
|
1127
|
+
"================================================================\n"
|
|
1128
|
+
f"{APP_DISPLAY_NAME} Crash Report\n"
|
|
1129
|
+
f"Time: {datetime.now().astimezone().isoformat(timespec='seconds')}\n"
|
|
1130
|
+
f"Version: {version}\n"
|
|
1131
|
+
f"OS: {pinfo.os_display}\n"
|
|
1132
|
+
f"Release: {pinfo.os_version}\n"
|
|
1133
|
+
f"Arch: {pinfo.arch}\n"
|
|
1134
|
+
f"Shell: {pinfo.shell.path}\n"
|
|
1135
|
+
f"Cwd: {chrys_platform.safe_getcwd()}\n"
|
|
1136
|
+
"Error:\n"
|
|
1137
|
+
f" {type(error).__name__}\n"
|
|
1138
|
+
f"{message_block}\n"
|
|
1139
|
+
"================================================================\n"
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1142
|
+
# Try a rich Traceback for parity with what Textual prints to stderr
|
|
1143
|
+
# (frames + locals). Fall back to stdlib if rich rendering itself
|
|
1144
|
+
# raises — we must always produce *some* output here.
|
|
1145
|
+
body: str
|
|
1146
|
+
try:
|
|
1147
|
+
import io
|
|
1148
|
+
|
|
1149
|
+
import rich
|
|
1150
|
+
from rich.console import Console
|
|
1151
|
+
from rich.traceback import Traceback
|
|
1152
|
+
|
|
1153
|
+
buf = io.StringIO()
|
|
1154
|
+
console = Console(file=buf, no_color=True, force_terminal=False, width=120)
|
|
1155
|
+
tb = Traceback.from_exception(
|
|
1156
|
+
type(error),
|
|
1157
|
+
error,
|
|
1158
|
+
error.__traceback__,
|
|
1159
|
+
show_locals=True,
|
|
1160
|
+
locals_max_length=5,
|
|
1161
|
+
suppress=[rich],
|
|
1162
|
+
)
|
|
1163
|
+
console.print(tb)
|
|
1164
|
+
body = buf.getvalue()
|
|
1165
|
+
except Exception:
|
|
1166
|
+
body = "".join(traceback.format_exception(type(error), error, error.__traceback__))
|
|
1167
|
+
|
|
1168
|
+
with open(log_path, mode, encoding="utf-8") as f:
|
|
1169
|
+
f.write(header)
|
|
1170
|
+
f.write(body)
|
|
1171
|
+
if not body.endswith("\n"):
|
|
1172
|
+
f.write("\n")
|
|
1173
|
+
f.write("\n")
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
def main(app_cls: type[ChrysApp] = ChrysApp) -> None:
|
|
1177
|
+
"""Entry point for the ``chrys`` console script.
|
|
1178
|
+
|
|
1179
|
+
``app_cls`` lets development tools swap in a ``ChrysApp`` subclass
|
|
1180
|
+
while reusing the rest of the bootstrap wiring. Defaults to
|
|
1181
|
+
``ChrysApp`` for the production entry point.
|
|
1182
|
+
"""
|
|
1183
|
+
import logging
|
|
1184
|
+
|
|
1185
|
+
from chrys.foundation.config.warnings import settings_warning_events
|
|
1186
|
+
from chrys.orchestration.startup import bootstrap_runtime, configure_utf8_stdio
|
|
1187
|
+
|
|
1188
|
+
configure_utf8_stdio()
|
|
1189
|
+
|
|
1190
|
+
# Early argparse prose remains English; dynamic error details are sanitized.
|
|
1191
|
+
parser = SanitizingArgumentParser(
|
|
1192
|
+
description=f"{APP_DISPLAY_NAME} - A general-purpose extensible agent platform for rapidly building and evolving custom agents."
|
|
1193
|
+
)
|
|
1194
|
+
parser.add_argument("-s", "--session", default="", help="Restore the given session id after startup.")
|
|
1195
|
+
parser.add_argument(
|
|
1196
|
+
"-a", "--agent", default="", help="Start with the given agent profile id, name, or display name."
|
|
1197
|
+
)
|
|
1198
|
+
parser.add_argument(
|
|
1199
|
+
"-m",
|
|
1200
|
+
"--model",
|
|
1201
|
+
default="",
|
|
1202
|
+
help="Start with the given active model profile id or name.",
|
|
1203
|
+
)
|
|
1204
|
+
parser.add_argument("-C", "--workdir", default="", metavar="DIR", help="Start in the given working directory.")
|
|
1205
|
+
args = parser.parse_args()
|
|
1206
|
+
if (missing_cwd := launch_cwd_missing_message(workdir_flag=True, workdir=args.workdir)) is not None:
|
|
1207
|
+
parser.exit(1, f"Error: {missing_cwd}\n")
|
|
1208
|
+
if args.workdir:
|
|
1209
|
+
workdir = Path(args.workdir).expanduser()
|
|
1210
|
+
if not workdir.is_dir():
|
|
1211
|
+
parser.error(f"workdir does not exist or is not a directory: {args.workdir}")
|
|
1212
|
+
os.chdir(workdir)
|
|
1213
|
+
|
|
1214
|
+
log_dir = chrys_platform.get_platform().config_dir / "logs"
|
|
1215
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
1216
|
+
debug_log = configure_debug_logging(log_dir / "debug.log")
|
|
1217
|
+
try:
|
|
1218
|
+
# Silence noisy third-party loggers
|
|
1219
|
+
logging.getLogger("markdown_it").setLevel(logging.WARNING)
|
|
1220
|
+
|
|
1221
|
+
# Install the in-memory log handler used by the F6 Log Viewer modal
|
|
1222
|
+
# *before* any engine / agent / profile code runs, so logs emitted
|
|
1223
|
+
# during startup (profile load, engine build, OTel setup, first agent
|
|
1224
|
+
# construction) are captured and visible the moment the viewer is
|
|
1225
|
+
# opened.
|
|
1226
|
+
from chrys.app.tui.screens.logs import install_log_handler
|
|
1227
|
+
|
|
1228
|
+
install_log_handler()
|
|
1229
|
+
|
|
1230
|
+
bootstrap = bootstrap_runtime(dotenv_override=True, project_root=Path(os.getcwd()))
|
|
1231
|
+
|
|
1232
|
+
bus = EventBus()
|
|
1233
|
+
registry = AgentProfileRegistry()
|
|
1234
|
+
registry.load_all()
|
|
1235
|
+
model_registry = ModelProfileRegistry()
|
|
1236
|
+
model_registry.load_all()
|
|
1237
|
+
loaded = _apply_startup_active_model_selection(parser, bootstrap.loaded, model_registry, args.model)
|
|
1238
|
+
state_store = JsonFileStateStore()
|
|
1239
|
+
session_title_updater = SessionTitleUpdater(bus, state_store)
|
|
1240
|
+
|
|
1241
|
+
def _on_successful_turn() -> None:
|
|
1242
|
+
on_buddy_successful_turn()
|
|
1243
|
+
session_title_updater.on_turn_finished()
|
|
1244
|
+
|
|
1245
|
+
engine = assemble_agent_engine(
|
|
1246
|
+
bus,
|
|
1247
|
+
loaded_settings=loaded,
|
|
1248
|
+
agent_registry=registry,
|
|
1249
|
+
state_store=state_store,
|
|
1250
|
+
model_registry=model_registry,
|
|
1251
|
+
on_successful_turn=_on_successful_turn,
|
|
1252
|
+
on_turn_started=session_title_updater.on_turn_started,
|
|
1253
|
+
surface=SessionSurface.TUI,
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
app = app_cls(
|
|
1257
|
+
bus,
|
|
1258
|
+
engine,
|
|
1259
|
+
settings_handle=engine.settings_handle,
|
|
1260
|
+
profile_name=args.agent,
|
|
1261
|
+
state_store=state_store,
|
|
1262
|
+
agent_registry=registry,
|
|
1263
|
+
model_registry=model_registry,
|
|
1264
|
+
startup_warnings=[*bootstrap.warnings],
|
|
1265
|
+
settings_warnings=settings_warning_events(loaded),
|
|
1266
|
+
startup_session_id=args.session,
|
|
1267
|
+
apply_saved_model_on_restore=not bool(args.model.strip()),
|
|
1268
|
+
session_title_updater=session_title_updater,
|
|
1269
|
+
)
|
|
1270
|
+
try:
|
|
1271
|
+
# On Windows, the ProactorEventLoop shutdown can raise KeyboardInterrupt
|
|
1272
|
+
# from GetQueuedCompletionStatus when a pending signal races with asyncio
|
|
1273
|
+
# runner cleanup. The app has already exited at this point — safe to suppress.
|
|
1274
|
+
with contextlib.suppress(KeyboardInterrupt):
|
|
1275
|
+
app.run()
|
|
1276
|
+
finally:
|
|
1277
|
+
# Safety net: ensure terminal is fully restored after native Textual exits.
|
|
1278
|
+
# Textual should handle this, but shell PTY activity during shutdown can
|
|
1279
|
+
# race with cleanup, leaving mouse tracking, focus reports, or alt screen on.
|
|
1280
|
+
_restore_terminal_after_run(app)
|
|
1281
|
+
_reset_terminal_title_after_run()
|
|
1282
|
+
finally:
|
|
1283
|
+
stop_debug_logging(debug_log)
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
if __name__ == "__main__":
|
|
1287
|
+
main()
|