iCode-TUI 0.29.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chrys/__init__.py +16 -0
- chrys/app/__init__.py +3 -0
- chrys/app/acp/__init__.py +3 -0
- chrys/app/acp/bridge.py +664 -0
- chrys/app/acp/content.py +132 -0
- chrys/app/acp/doc/frontend-api.md +425 -0
- chrys/app/acp/history.py +585 -0
- chrys/app/acp/server.py +1796 -0
- chrys/app/acp/session_manager.py +1815 -0
- chrys/app/acp/tool_status.py +45 -0
- chrys/app/cli/__init__.py +3 -0
- chrys/app/cli/acp.py +178 -0
- chrys/app/cli/app.py +165 -0
- chrys/app/cli/approvals.py +101 -0
- chrys/app/cli/headless.py +223 -0
- chrys/app/cli/launch_cwd.py +42 -0
- chrys/app/cli/profiles.py +298 -0
- chrys/app/cli/progress.py +857 -0
- chrys/app/cli/run.py +425 -0
- chrys/app/cli/serve.py +1593 -0
- chrys/app/cli/tool_summary.py +225 -0
- chrys/app/cli/trajectory.py +201 -0
- chrys/app/cli/workflow.py +601 -0
- chrys/app/features/__init__.py +3 -0
- chrys/app/features/buddy/__init__.py +3 -0
- chrys/app/features/buddy/actions.py +68 -0
- chrys/app/features/buddy/animation.py +76 -0
- chrys/app/features/buddy/commands.py +196 -0
- chrys/app/features/buddy/docs/design.md +187 -0
- chrys/app/features/buddy/hatchery.py +80 -0
- chrys/app/features/buddy/lifecycle.py +41 -0
- chrys/app/features/buddy/model.py +265 -0
- chrys/app/features/buddy/pixel_renderer.py +113 -0
- chrys/app/features/buddy/pixel_sprites.py +279 -0
- chrys/app/features/buddy/portrait.py +76 -0
- chrys/app/features/buddy/progression.py +58 -0
- chrys/app/features/buddy/replies.py +191 -0
- chrys/app/features/buddy/sprites/alien.toml +70 -0
- chrys/app/features/buddy/sprites/axolotl.toml +71 -0
- chrys/app/features/buddy/sprites/bat.toml +69 -0
- chrys/app/features/buddy/sprites/bee.toml +69 -0
- chrys/app/features/buddy/sprites/cactus.toml +73 -0
- chrys/app/features/buddy/sprites/capybara.toml +70 -0
- chrys/app/features/buddy/sprites/cat.toml +69 -0
- chrys/app/features/buddy/sprites/crab.toml +70 -0
- chrys/app/features/buddy/sprites/dragon.toml +71 -0
- chrys/app/features/buddy/sprites/duck.toml +69 -0
- chrys/app/features/buddy/sprites/fox.toml +69 -0
- chrys/app/features/buddy/sprites/frog.toml +70 -0
- chrys/app/features/buddy/sprites/ghost.toml +70 -0
- chrys/app/features/buddy/sprites/goose.toml +69 -0
- chrys/app/features/buddy/sprites/jelly.toml +70 -0
- chrys/app/features/buddy/sprites/llama.toml +70 -0
- chrys/app/features/buddy/sprites/mushroom.toml +69 -0
- chrys/app/features/buddy/sprites/octopus.toml +70 -0
- chrys/app/features/buddy/sprites/owl.toml +70 -0
- chrys/app/features/buddy/sprites/panda.toml +71 -0
- chrys/app/features/buddy/sprites/penguin.toml +69 -0
- chrys/app/features/buddy/sprites/rabbit.toml +70 -0
- chrys/app/features/buddy/sprites/robot.toml +72 -0
- chrys/app/features/buddy/sprites/shark.toml +70 -0
- chrys/app/features/buddy/sprites/snail.toml +69 -0
- chrys/app/features/buddy/sprites/snake.toml +69 -0
- chrys/app/features/buddy/sprites/tabby.toml +69 -0
- chrys/app/features/buddy/sprites/turtle.toml +69 -0
- chrys/app/features/buddy/store.py +143 -0
- chrys/app/features/session_title/__init__.py +7 -0
- chrys/app/features/session_title/updater.py +582 -0
- chrys/app/installer.py +651 -0
- chrys/app/parsing.py +17 -0
- chrys/app/tui/__init__.py +3 -0
- chrys/app/tui/app.py +1287 -0
- chrys/app/tui/behaviors/__init__.py +3 -0
- chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
- chrys/app/tui/behaviors/insert_clipboard.py +73 -0
- chrys/app/tui/behaviors/right_click_copy.py +146 -0
- chrys/app/tui/binding_display.py +139 -0
- chrys/app/tui/buddy_messages.py +17 -0
- chrys/app/tui/buddy_reply.py +86 -0
- chrys/app/tui/chrys.tcss +549 -0
- chrys/app/tui/clipboard.py +109 -0
- chrys/app/tui/copy_messages.py +10 -0
- chrys/app/tui/i18n.py +230 -0
- chrys/app/tui/language.py +58 -0
- chrys/app/tui/notifications/__init__.py +12 -0
- chrys/app/tui/notifications/drivers.py +180 -0
- chrys/app/tui/notifications/service.py +126 -0
- chrys/app/tui/notifications/settings.py +141 -0
- chrys/app/tui/screens/__init__.py +3 -0
- chrys/app/tui/screens/agents/__init__.py +8 -0
- chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
- chrys/app/tui/screens/agents/config.py +2142 -0
- chrys/app/tui/screens/agents/config.tcss +169 -0
- chrys/app/tui/screens/agents/panels/__init__.py +3 -0
- chrys/app/tui/screens/agents/panels/acp.py +1176 -0
- chrys/app/tui/screens/agents/panels/basic.py +497 -0
- chrys/app/tui/screens/agents/panels/compaction.py +231 -0
- chrys/app/tui/screens/agents/panels/config_card.py +218 -0
- chrys/app/tui/screens/agents/panels/instructions.py +91 -0
- chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
- chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
- chrys/app/tui/screens/agents/panels/memory.py +684 -0
- chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
- chrys/app/tui/screens/agents/panels/skills.py +806 -0
- chrys/app/tui/screens/agents/panels/subagents.py +652 -0
- chrys/app/tui/screens/agents/panels/tools.py +205 -0
- chrys/app/tui/screens/agents/picker.py +103 -0
- chrys/app/tui/screens/agents/skill_paths.py +38 -0
- chrys/app/tui/screens/agents/validation_messages.py +452 -0
- chrys/app/tui/screens/dialogs/__init__.py +35 -0
- chrys/app/tui/screens/dialogs/agent_load.py +834 -0
- chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
- chrys/app/tui/screens/dialogs/app_mode.py +55 -0
- chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
- chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
- chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
- chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
- chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
- chrys/app/tui/screens/dialogs/approval/body.py +110 -0
- chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
- chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
- chrys/app/tui/screens/dialogs/ask_user.py +148 -0
- chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
- chrys/app/tui/screens/dialogs/base.py +84 -0
- chrys/app/tui/screens/dialogs/confirm.py +184 -0
- chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
- chrys/app/tui/screens/dialogs/connection_test.py +183 -0
- chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
- chrys/app/tui/screens/dialogs/diagram.py +102 -0
- chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
- chrys/app/tui/screens/dialogs/editor.py +378 -0
- chrys/app/tui/screens/dialogs/editor.tcss +132 -0
- chrys/app/tui/screens/dialogs/file_picker.py +582 -0
- chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
- chrys/app/tui/screens/dialogs/fork_session.py +219 -0
- chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
- chrys/app/tui/screens/dialogs/image_compression.py +102 -0
- chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
- chrys/app/tui/screens/dialogs/man_page.py +172 -0
- chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
- chrys/app/tui/screens/dialogs/new_folder.py +277 -0
- chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
- chrys/app/tui/screens/dialogs/notice.tcss +14 -0
- chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
- chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
- chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
- chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
- chrys/app/tui/screens/dialogs/session_title.py +110 -0
- chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
- chrys/app/tui/screens/dialogs/tool_view.py +207 -0
- chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
- chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
- chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
- chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
- chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
- chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
- chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
- chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
- chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
- chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
- chrys/app/tui/screens/diff/__init__.py +22 -0
- chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
- chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
- chrys/app/tui/screens/diff/screen.py +1532 -0
- chrys/app/tui/screens/diff/screen.tcss +104 -0
- chrys/app/tui/screens/guides/__init__.py +7 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
- chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
- chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
- chrys/app/tui/screens/guides/index.py +215 -0
- chrys/app/tui/screens/guides/reader.py +173 -0
- chrys/app/tui/screens/guides/screen.py +442 -0
- chrys/app/tui/screens/guides/screen.tcss +52 -0
- chrys/app/tui/screens/logs/__init__.py +7 -0
- chrys/app/tui/screens/logs/screen.py +504 -0
- chrys/app/tui/screens/logs/screen.tcss +45 -0
- chrys/app/tui/screens/main/__init__.py +7 -0
- chrys/app/tui/screens/main/action_availability.py +30 -0
- chrys/app/tui/screens/main/buddy_command.py +143 -0
- chrys/app/tui/screens/main/commands.py +1073 -0
- chrys/app/tui/screens/main/config_actions.py +464 -0
- chrys/app/tui/screens/main/copy_actions.py +117 -0
- chrys/app/tui/screens/main/dialog_controllers.py +826 -0
- chrys/app/tui/screens/main/dialog_gateway.py +166 -0
- chrys/app/tui/screens/main/diff_controller.py +478 -0
- chrys/app/tui/screens/main/engine_read_model.py +184 -0
- chrys/app/tui/screens/main/event_handlers.py +2014 -0
- chrys/app/tui/screens/main/input_flow.py +241 -0
- chrys/app/tui/screens/main/live_diff.py +244 -0
- chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
- chrys/app/tui/screens/main/model_indicator.py +217 -0
- chrys/app/tui/screens/main/navigation.py +343 -0
- chrys/app/tui/screens/main/ports.py +571 -0
- chrys/app/tui/screens/main/recent_dirs.py +123 -0
- chrys/app/tui/screens/main/rollback_controller.py +586 -0
- chrys/app/tui/screens/main/runtime_info.py +71 -0
- chrys/app/tui/screens/main/screen.py +2265 -0
- chrys/app/tui/screens/main/screen.tcss +21 -0
- chrys/app/tui/screens/main/session_handlers.py +1127 -0
- chrys/app/tui/screens/main/session_title.py +239 -0
- chrys/app/tui/screens/main/settings_coordinator.py +365 -0
- chrys/app/tui/screens/main/settings_persistence.py +184 -0
- chrys/app/tui/screens/main/shell_mode.py +112 -0
- chrys/app/tui/screens/main/state.py +289 -0
- chrys/app/tui/screens/main/submit_state.py +11 -0
- chrys/app/tui/screens/main/subscriptions.py +177 -0
- chrys/app/tui/screens/main/suggestions.py +982 -0
- chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
- chrys/app/tui/screens/main/view_adapter.py +1567 -0
- chrys/app/tui/screens/main/workflow_browser.py +747 -0
- chrys/app/tui/screens/main/workflow_content.py +248 -0
- chrys/app/tui/screens/main/workflow_controller.py +240 -0
- chrys/app/tui/screens/main/workflow_feedback.py +137 -0
- chrys/app/tui/screens/main/workflow_flow.py +49 -0
- chrys/app/tui/screens/main/workflow_run_control.py +456 -0
- chrys/app/tui/screens/main/workflow_session_view.py +316 -0
- chrys/app/tui/screens/main/workspace_actions.py +272 -0
- chrys/app/tui/screens/main/workspace_branch.py +194 -0
- chrys/app/tui/screens/menus/__init__.py +8 -0
- chrys/app/tui/screens/menus/languages.py +114 -0
- chrys/app/tui/screens/models/__init__.py +7 -0
- chrys/app/tui/screens/models/picker.py +147 -0
- chrys/app/tui/screens/models/screen.py +2043 -0
- chrys/app/tui/screens/models/screen.tcss +255 -0
- chrys/app/tui/screens/sessions/__init__.py +8 -0
- chrys/app/tui/screens/sessions/presenter.py +586 -0
- chrys/app/tui/screens/sessions/screen.py +779 -0
- chrys/app/tui/screens/sessions/screen.tcss +127 -0
- chrys/app/tui/screens/settings/__init__.py +29 -0
- chrys/app/tui/screens/settings/danger.py +26 -0
- chrys/app/tui/screens/settings/dialog.py +281 -0
- chrys/app/tui/screens/settings/layout.py +519 -0
- chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
- chrys/app/tui/screens/settings/panes/__init__.py +3 -0
- chrys/app/tui/screens/settings/panes/notifications.py +248 -0
- chrys/app/tui/screens/settings/panes/sessions.py +148 -0
- chrys/app/tui/screens/settings/ports.py +55 -0
- chrys/app/tui/screens/settings/provenance.py +102 -0
- chrys/app/tui/screens/settings/rows.py +546 -0
- chrys/app/tui/screens/settings/settings.tcss +406 -0
- chrys/app/tui/screens/themes/__init__.py +8 -0
- chrys/app/tui/screens/themes/dialogs.py +330 -0
- chrys/app/tui/screens/themes/editor.py +632 -0
- chrys/app/tui/screens/themes/editor.tcss +268 -0
- chrys/app/tui/screens/themes/messages.py +95 -0
- chrys/app/tui/screens/themes/palette.py +694 -0
- chrys/app/tui/screens/themes/panel.py +113 -0
- chrys/app/tui/screens/themes/picker.py +203 -0
- chrys/app/tui/screens/themes/save.py +191 -0
- chrys/app/tui/support/__init__.py +3 -0
- chrys/app/tui/support/file_manager.py +64 -0
- chrys/app/tui/support/gc_freeze.py +1114 -0
- chrys/app/tui/support/path_shortcuts.py +50 -0
- chrys/app/tui/support/prompt_history.py +298 -0
- chrys/app/tui/support/workspace_mru.py +476 -0
- chrys/app/tui/terminal/__init__.py +6 -0
- chrys/app/tui/terminal/_pty_backend.py +114 -0
- chrys/app/tui/terminal/emulator/__init__.py +24 -0
- chrys/app/tui/terminal/emulator/buffer.py +456 -0
- chrys/app/tui/terminal/emulator/charsets.py +113 -0
- chrys/app/tui/terminal/emulator/core.py +963 -0
- chrys/app/tui/terminal/emulator/input.py +342 -0
- chrys/app/tui/terminal/emulator/parser.py +227 -0
- chrys/app/tui/terminal/emulator/pen.py +158 -0
- chrys/app/tui/terminal/launcher.py +455 -0
- chrys/app/tui/terminal/leaked_reports.py +44 -0
- chrys/app/tui/terminal/panel.py +323 -0
- chrys/app/tui/terminal/pty_process.py +405 -0
- chrys/app/tui/terminal/rendering.py +151 -0
- chrys/app/tui/terminal/shell.py +273 -0
- chrys/app/tui/terminal/shell_integration.py +199 -0
- chrys/app/tui/terminal/title.py +224 -0
- chrys/app/tui/terminal/widget.py +654 -0
- chrys/app/tui/theme.py +422 -0
- chrys/app/tui/theme_loader.py +201 -0
- chrys/app/tui/theme_stylesheet.py +49 -0
- chrys/app/tui/themes/__init__.py +3 -0
- chrys/app/tui/themes/document.py +120 -0
- chrys/app/tui/themes/preview.py +170 -0
- chrys/app/tui/themes/store.py +176 -0
- chrys/app/tui/toasts.py +69 -0
- chrys/app/tui/util/__init__.py +3 -0
- chrys/app/tui/util/context_pressure.py +146 -0
- chrys/app/tui/util/debug_log.py +292 -0
- chrys/app/tui/util/diff_entries.py +64 -0
- chrys/app/tui/util/formatting.py +53 -0
- chrys/app/tui/util/git_branch.py +326 -0
- chrys/app/tui/util/invocation_progress.py +57 -0
- chrys/app/tui/util/logo.py +32 -0
- chrys/app/tui/util/message_gate.py +35 -0
- chrys/app/tui/util/mutation_sources.py +14 -0
- chrys/app/tui/util/picker_text.py +27 -0
- chrys/app/tui/util/removal.py +47 -0
- chrys/app/tui/util/rich_style.py +36 -0
- chrys/app/tui/util/source_text.py +12 -0
- chrys/app/tui/util/static_update.py +88 -0
- chrys/app/tui/util/todo_format.py +51 -0
- chrys/app/tui/util/visibility.py +214 -0
- chrys/app/tui/widgets/__init__.py +60 -0
- chrys/app/tui/widgets/ask_user_controls.py +652 -0
- chrys/app/tui/widgets/ask_user_markdown.py +17 -0
- chrys/app/tui/widgets/ask_user_prompt.py +606 -0
- chrys/app/tui/widgets/buttons.py +52 -0
- chrys/app/tui/widgets/chat/__init__.py +3 -0
- chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
- chrys/app/tui/widgets/chat/chrome.py +296 -0
- chrys/app/tui/widgets/chat/compaction_card.py +353 -0
- chrys/app/tui/widgets/chat/context_fold.py +77 -0
- chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
- chrys/app/tui/widgets/chat/image_preview.py +214 -0
- chrys/app/tui/widgets/chat/live_renderer.py +383 -0
- chrys/app/tui/widgets/chat/messages.py +1039 -0
- chrys/app/tui/widgets/chat/panel.py +1769 -0
- chrys/app/tui/widgets/chat/ports.py +245 -0
- chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
- chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
- chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
- chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
- chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
- chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
- chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
- chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
- chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
- chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
- chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
- chrys/app/tui/widgets/chat/renderers/search.py +348 -0
- chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
- chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
- chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
- chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
- chrys/app/tui/widgets/chat/renderers/web.py +69 -0
- chrys/app/tui/widgets/chat/replay.py +1605 -0
- chrys/app/tui/widgets/chat/replay_mount.py +322 -0
- chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
- chrys/app/tui/widgets/chat/selection_controller.py +435 -0
- chrys/app/tui/widgets/chat/session_json.py +508 -0
- chrys/app/tui/widgets/chat/toc_model.py +156 -0
- chrys/app/tui/widgets/chat/tool_call.py +2478 -0
- chrys/app/tui/widgets/chat/tool_registry.py +753 -0
- chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
- chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
- chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
- chrys/app/tui/widgets/checkbox.py +24 -0
- chrys/app/tui/widgets/chrome/__init__.py +3 -0
- chrys/app/tui/widgets/chrome/app_header.py +283 -0
- chrys/app/tui/widgets/chrome/commands.py +95 -0
- chrys/app/tui/widgets/chrome/file_index.py +68 -0
- chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
- chrys/app/tui/widgets/chrome/footer.py +284 -0
- chrys/app/tui/widgets/chrome/image_paste.py +351 -0
- chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
- chrys/app/tui/widgets/chrome/status_bar.py +911 -0
- chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
- chrys/app/tui/widgets/click_affordance.py +24 -0
- chrys/app/tui/widgets/color_picker/__init__.py +254 -0
- chrys/app/tui/widgets/color_picker/controls.py +171 -0
- chrys/app/tui/widgets/color_picker/model.py +89 -0
- chrys/app/tui/widgets/dialog_buttons.py +58 -0
- chrys/app/tui/widgets/dialog_scroll.py +24 -0
- chrys/app/tui/widgets/diff_view/__init__.py +20 -0
- chrys/app/tui/widgets/diff_view/cells.py +59 -0
- chrys/app/tui/widgets/diff_view/code.py +280 -0
- chrys/app/tui/widgets/diff_view/compute.py +286 -0
- chrys/app/tui/widgets/diff_view/gutter.py +161 -0
- chrys/app/tui/widgets/diff_view/inline.py +77 -0
- chrys/app/tui/widgets/diff_view/palette.py +87 -0
- chrys/app/tui/widgets/diff_view/protocols.py +24 -0
- chrys/app/tui/widgets/diff_view/rows.py +138 -0
- chrys/app/tui/widgets/diff_view/unified.py +267 -0
- chrys/app/tui/widgets/diff_view/widget.py +209 -0
- chrys/app/tui/widgets/editor/__init__.py +79 -0
- chrys/app/tui/widgets/editor/coordinates.py +30 -0
- chrys/app/tui/widgets/editor/highlighter.py +139 -0
- chrys/app/tui/widgets/editor/keymaps.py +357 -0
- chrys/app/tui/widgets/editor/types.py +216 -0
- chrys/app/tui/widgets/editor/vim.py +1195 -0
- chrys/app/tui/widgets/editor/widget.py +401 -0
- chrys/app/tui/widgets/file_preview.py +58 -0
- chrys/app/tui/widgets/hatch.py +138 -0
- chrys/app/tui/widgets/input.py +66 -0
- chrys/app/tui/widgets/loading.py +125 -0
- chrys/app/tui/widgets/markdown/__init__.py +25 -0
- chrys/app/tui/widgets/markdown/_utils.py +21 -0
- chrys/app/tui/widgets/markdown/anchors.py +71 -0
- chrys/app/tui/widgets/markdown/blocks.py +115 -0
- chrys/app/tui/widgets/markdown/copyable.py +163 -0
- chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
- chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
- chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
- chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
- chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
- chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
- chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
- chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
- chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
- chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
- chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
- chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
- chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
- chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
- chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
- chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
- chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
- chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
- chrys/app/tui/widgets/markdown/links.py +47 -0
- chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
- chrys/app/tui/widgets/markdown/math/layout.py +739 -0
- chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
- chrys/app/tui/widgets/markdown/math/parser.py +325 -0
- chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
- chrys/app/tui/widgets/markdown/navigator.py +80 -0
- chrys/app/tui/widgets/markdown/parser.py +1057 -0
- chrys/app/tui/widgets/markdown/stream.py +73 -0
- chrys/app/tui/widgets/markdown/toc.py +98 -0
- chrys/app/tui/widgets/markdown/viewer.py +146 -0
- chrys/app/tui/widgets/markdown/widget.py +2046 -0
- chrys/app/tui/widgets/marquee.py +101 -0
- chrys/app/tui/widgets/option_menu.py +82 -0
- chrys/app/tui/widgets/page_navigator.py +118 -0
- chrys/app/tui/widgets/select.py +196 -0
- chrys/app/tui/widgets/selection.py +59 -0
- chrys/app/tui/widgets/sidebar/__init__.py +3 -0
- chrys/app/tui/widgets/sidebar/buddy.py +367 -0
- chrys/app/tui/widgets/sidebar/context.py +482 -0
- chrys/app/tui/widgets/sidebar/debug.py +195 -0
- chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
- chrys/app/tui/widgets/sidebar/panel.py +198 -0
- chrys/app/tui/widgets/sidebar/tasks.py +198 -0
- chrys/app/tui/widgets/sidebar/toc.py +166 -0
- chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
- chrys/app/tui/widgets/syntax_theme.py +85 -0
- chrys/app/tui/widgets/text_area.py +258 -0
- chrys/app/tui/widgets/trajectory/__init__.py +8 -0
- chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
- chrys/app/tui/widgets/trajectory/insights.py +1266 -0
- chrys/app/tui/widgets/trajectory/overview.py +1101 -0
- chrys/app/tui/widgets/trajectory/panel.py +983 -0
- chrys/app/tui/widgets/trajectory/presentation.py +311 -0
- chrys/app/tui/widgets/trajectory/session_info.py +124 -0
- chrys/app/tui/widgets/trajectory/text_view.py +124 -0
- chrys/app/tui/widgets/trajectory/timeline.py +380 -0
- chrys/app/tui/widgets/welcome.py +126 -0
- chrys/app/tui/widgets/workflow/__init__.py +5 -0
- chrys/app/tui/widgets/workflow/edges.py +89 -0
- chrys/app/tui/widgets/workflow/graph.py +869 -0
- chrys/app/tui/widgets/workflow/info.py +310 -0
- chrys/app/tui/widgets/workflow/node_view.py +27 -0
- chrys/app/tui/widgets/workflow/output.py +225 -0
- chrys/app/tui/widgets/workflow/panel.py +658 -0
- chrys/app/tui/widgets/workflow/projector.py +324 -0
- chrys/app/tui/widgets/workflow/records.py +69 -0
- chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
- chrys/app/tui/widgets/workflow/selection.py +151 -0
- chrys/app/tui/widgets/workflow/source.py +65 -0
- chrys/app/tui/widgets/workflow/text.py +257 -0
- chrys/app/tui/widgets/workflow/values.py +317 -0
- chrys/foundation/__init__.py +0 -0
- chrys/foundation/branding.py +15 -0
- chrys/foundation/config/__init__.py +7 -0
- chrys/foundation/config/coercion.py +374 -0
- chrys/foundation/config/context.py +34 -0
- chrys/foundation/config/env_file.py +141 -0
- chrys/foundation/config/env_layers.py +370 -0
- chrys/foundation/config/migrations.py +521 -0
- chrys/foundation/config/notification_events.py +22 -0
- chrys/foundation/config/process_settings.py +332 -0
- chrys/foundation/config/runtime_pointer.py +133 -0
- chrys/foundation/config/settings.py +1952 -0
- chrys/foundation/config/settings_store.py +996 -0
- chrys/foundation/config/spec.py +396 -0
- chrys/foundation/config/user_settings.py +140 -0
- chrys/foundation/config/warnings.py +262 -0
- chrys/foundation/config/web_values.py +135 -0
- chrys/foundation/config/yaml_store.py +487 -0
- chrys/foundation/errors/__init__.py +68 -0
- chrys/foundation/errors/_legacy.py +117 -0
- chrys/foundation/errors/_text.py +13 -0
- chrys/foundation/errors/_walk.py +85 -0
- chrys/foundation/errors/classify.py +278 -0
- chrys/foundation/errors/display.py +274 -0
- chrys/foundation/errors/formatting.py +283 -0
- chrys/foundation/errors/kinds.py +67 -0
- chrys/foundation/errors/network.py +497 -0
- chrys/foundation/errors/provider.py +445 -0
- chrys/foundation/errors/route.py +85 -0
- chrys/foundation/events/__init__.py +8 -0
- chrys/foundation/events/bus.py +143 -0
- chrys/foundation/events/types.py +1629 -0
- chrys/foundation/events/workflow.py +38 -0
- chrys/foundation/hosted_tools.py +142 -0
- chrys/foundation/i18n/__init__.py +32 -0
- chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
- chrys/foundation/i18n/catalogs.py +42 -0
- chrys/foundation/i18n/formatting.py +202 -0
- chrys/foundation/i18n/locale.py +92 -0
- chrys/foundation/i18n/localizer.py +187 -0
- chrys/foundation/i18n/messages.py +268 -0
- chrys/foundation/io/__init__.py +3 -0
- chrys/foundation/io/result_content.py +237 -0
- chrys/foundation/models/__init__.py +3 -0
- chrys/foundation/models/approval_reuse.py +23 -0
- chrys/foundation/models/ask_user.py +468 -0
- chrys/foundation/models/execution.py +18 -0
- chrys/foundation/models/history_markers.py +115 -0
- chrys/foundation/models/invocations.py +63 -0
- chrys/foundation/models/mutation_scope.py +28 -0
- chrys/foundation/models/session_env.py +87 -0
- chrys/foundation/models/session_surface.py +29 -0
- chrys/foundation/models/todos.py +74 -0
- chrys/foundation/models/turns.py +125 -0
- chrys/foundation/models/workflow_session.py +89 -0
- chrys/foundation/models/workspace.py +59 -0
- chrys/foundation/net/__init__.py +3 -0
- chrys/foundation/net/address_class.py +57 -0
- chrys/foundation/net/route_probe.py +94 -0
- chrys/foundation/net/url.py +107 -0
- chrys/foundation/observability/__init__.py +3 -0
- chrys/foundation/observability/exporters.py +174 -0
- chrys/foundation/observability/gate.py +31 -0
- chrys/foundation/observability/setup.py +497 -0
- chrys/foundation/observability/sink.py +228 -0
- chrys/foundation/patches/__init__.py +19 -0
- chrys/foundation/patches/mdurl_cache.py +59 -0
- chrys/foundation/patches/patcher.py +287 -0
- chrys/foundation/patches/staged_members.py +205 -0
- chrys/foundation/patches/textual_block_border.py +105 -0
- chrys/foundation/patches/textual_button_ansi.py +65 -0
- chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
- chrys/foundation/patches/textual_callback_cache.py +248 -0
- chrys/foundation/patches/textual_callback_dispatch.py +109 -0
- chrys/foundation/patches/textual_compositor_cjk.py +566 -0
- chrys/foundation/patches/textual_dispatch_cache.py +150 -0
- chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
- chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
- chrys/foundation/patches/textual_lru_acyclic.py +241 -0
- chrys/foundation/patches/textual_message_pump.py +136 -0
- chrys/foundation/patches/textual_node_diet.py +875 -0
- chrys/foundation/patches/textual_one_shot_timer.py +48 -0
- chrys/foundation/patches/textual_option_list.py +90 -0
- chrys/foundation/patches/textual_precompose.py +89 -0
- chrys/foundation/patches/textual_pruned_tabs.py +69 -0
- chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
- chrys/foundation/patches/textual_removed_node_caches.py +86 -0
- chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
- chrys/foundation/patches/textual_selection_extract.py +48 -0
- chrys/foundation/patches/textual_strip_cycles.py +119 -0
- chrys/foundation/patches/textual_tab_selection.py +1611 -0
- chrys/foundation/patches/textual_timer_skip.py +70 -0
- chrys/foundation/patches/textual_utf8_decoder.py +106 -0
- chrys/foundation/patches/textual_win_sleep.py +167 -0
- chrys/foundation/patches/watchdog_fsevents.py +70 -0
- chrys/foundation/patches/watchdog_windows.py +52 -0
- chrys/foundation/platform/__init__.py +556 -0
- chrys/foundation/platform/c_api.py +32 -0
- chrys/foundation/platform/child_reap.py +107 -0
- chrys/foundation/platform/command_line.py +95 -0
- chrys/foundation/platform/files.py +1470 -0
- chrys/foundation/platform/output_capture.py +113 -0
- chrys/foundation/platform/paths.py +193 -0
- chrys/foundation/platform/process.py +1468 -0
- chrys/foundation/platform/pty_output.py +19 -0
- chrys/foundation/platform/runtime_paths.py +281 -0
- chrys/foundation/platform/win_console.py +129 -0
- chrys/foundation/reasoning_origin.py +67 -0
- chrys/foundation/recovery.py +16 -0
- chrys/foundation/retry.py +278 -0
- chrys/foundation/text/__init__.py +3 -0
- chrys/foundation/text/encoding.py +603 -0
- chrys/foundation/text/images.py +304 -0
- chrys/foundation/text/lines.py +24 -0
- chrys/foundation/text/mentions.py +90 -0
- chrys/foundation/text/model_json.py +25 -0
- chrys/foundation/text/reading_order.py +20 -0
- chrys/foundation/text/tokenizer.py +25 -0
- chrys/foundation/text/tool_output.py +287 -0
- chrys/foundation/text/yaml_io.py +62 -0
- chrys/foundation/tool_call_context.py +202 -0
- chrys/foundation/tool_execution_stamp.py +184 -0
- chrys/foundation/tool_invocation_order.py +48 -0
- chrys/foundation/tool_kinds.py +102 -0
- chrys/foundation/tool_result_metadata.py +287 -0
- chrys/foundation/trajectory/__init__.py +10 -0
- chrys/foundation/trajectory/context.py +481 -0
- chrys/foundation/trajectory/envelope.py +571 -0
- chrys/foundation/trajectory/event_types.py +294 -0
- chrys/foundation/trajectory/finalizers.py +46 -0
- chrys/foundation/trajectory/fingerprint.py +62 -0
- chrys/foundation/trajectory/ids.py +155 -0
- chrys/foundation/trajectory/keys.py +99 -0
- chrys/foundation/trajectory/lease.py +173 -0
- chrys/foundation/trajectory/metadata.py +72 -0
- chrys/foundation/trajectory/reader.py +271 -0
- chrys/foundation/trajectory/recovery.py +225 -0
- chrys/foundation/trajectory/revisions.py +152 -0
- chrys/foundation/trajectory/segments.py +225 -0
- chrys/foundation/trajectory/tools.py +117 -0
- chrys/foundation/trajectory/usage.py +151 -0
- chrys/foundation/trajectory/writer.py +972 -0
- chrys/foundation/trajectory_timing.py +97 -0
- chrys/foundation/util/__init__.py +3 -0
- chrys/foundation/util/async_tasks.py +29 -0
- chrys/foundation/util/chrys_headers.py +23 -0
- chrys/foundation/util/env_templates.py +104 -0
- chrys/foundation/util/filenames.py +96 -0
- chrys/foundation/util/header_charset.py +138 -0
- chrys/foundation/util/httpx_helpers.py +12 -0
- chrys/foundation/util/lock.py +236 -0
- chrys/foundation/util/once_close.py +46 -0
- chrys/foundation/util/session_ids.py +29 -0
- chrys/foundation/util/shell_tokens.py +88 -0
- chrys/foundation/util/sub_agent_context.py +38 -0
- chrys/foundation/util/time.py +35 -0
- chrys/foundation/util/unicode_scalars.py +36 -0
- chrys/foundation/vendor/__init__.py +43 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
- chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
- chrys/kernel/__init__.py +256 -0
- chrys/kernel/_content.py +1866 -0
- chrys/kernel/_loop_recorder.py +631 -0
- chrys/kernel/_null_overlay.py +1026 -0
- chrys/kernel/_result_ceiling.py +216 -0
- chrys/kernel/_serialization.py +431 -0
- chrys/kernel/_tool_arg_errors.py +1629 -0
- chrys/kernel/_tool_execution.py +1172 -0
- chrys/kernel/_tool_expansion.py +26 -0
- chrys/kernel/_types.py +2598 -0
- chrys/kernel/agent.py +994 -0
- chrys/kernel/client.py +1084 -0
- chrys/kernel/compaction.py +1315 -0
- chrys/kernel/exceptions.py +211 -0
- chrys/kernel/exchanges.py +683 -0
- chrys/kernel/identity.py +95 -0
- chrys/kernel/images.py +36 -0
- chrys/kernel/instrumentation.py +1618 -0
- chrys/kernel/loop.py +2308 -0
- chrys/kernel/middleware.py +616 -0
- chrys/kernel/sessions.py +604 -0
- chrys/kernel/tools.py +1061 -0
- chrys/kernel/types.py +126 -0
- chrys/orchestration/__init__.py +3 -0
- chrys/orchestration/engine/__init__.py +3 -0
- chrys/orchestration/engine/assembly.py +236 -0
- chrys/orchestration/engine/build/__init__.py +3 -0
- chrys/orchestration/engine/build/builder.py +1061 -0
- chrys/orchestration/engine/build/construction.py +408 -0
- chrys/orchestration/engine/build/loaded.py +139 -0
- chrys/orchestration/engine/engine.py +789 -0
- chrys/orchestration/engine/execution.py +771 -0
- chrys/orchestration/engine/loader.py +865 -0
- chrys/orchestration/engine/rollback.py +918 -0
- chrys/orchestration/engine/run/__init__.py +3 -0
- chrys/orchestration/engine/run/active_injection.py +419 -0
- chrys/orchestration/engine/run/attachments.py +519 -0
- chrys/orchestration/engine/run/bindings.py +1095 -0
- chrys/orchestration/engine/run/coordinator.py +1062 -0
- chrys/orchestration/engine/run/finalizer.py +417 -0
- chrys/orchestration/engine/run/input_refs.py +60 -0
- chrys/orchestration/engine/run/prompt_content.py +457 -0
- chrys/orchestration/engine/run/resume.py +299 -0
- chrys/orchestration/engine/run/retry.py +948 -0
- chrys/orchestration/engine/run/runner.py +625 -0
- chrys/orchestration/engine/run/runtime_skills.py +146 -0
- chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
- chrys/orchestration/engine/run/turn_hooks.py +258 -0
- chrys/orchestration/engine/run/turn_state.py +149 -0
- chrys/orchestration/engine/run/working_dir.py +50 -0
- chrys/orchestration/engine/session_lifecycle.py +2013 -0
- chrys/orchestration/engine/state/__init__.py +3 -0
- chrys/orchestration/engine/state/active_session.py +183 -0
- chrys/orchestration/engine/state/controls.py +639 -0
- chrys/orchestration/engine/state/current_agent.py +26 -0
- chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
- chrys/orchestration/engine/state/machine.py +193 -0
- chrys/orchestration/engine/state/session_writer.py +434 -0
- chrys/orchestration/engine/trajectory.py +714 -0
- chrys/orchestration/engine/usage.py +118 -0
- chrys/orchestration/invoker/__init__.py +3 -0
- chrys/orchestration/invoker/acp.py +727 -0
- chrys/orchestration/invoker/acp_protocol.py +1115 -0
- chrys/orchestration/invoker/acp_spec.py +79 -0
- chrys/orchestration/invoker/attempts.py +994 -0
- chrys/orchestration/invoker/child_compaction.py +131 -0
- chrys/orchestration/invoker/child_history.py +99 -0
- chrys/orchestration/invoker/contracts.py +253 -0
- chrys/orchestration/invoker/evidence.py +94 -0
- chrys/orchestration/invoker/kernel.py +343 -0
- chrys/orchestration/invoker/origin.py +79 -0
- chrys/orchestration/invoker/resources.py +343 -0
- chrys/orchestration/invoker/runtime.py +299 -0
- chrys/orchestration/invoker/usage.py +80 -0
- chrys/orchestration/session_hooks.py +117 -0
- chrys/orchestration/session_host.py +1023 -0
- chrys/orchestration/session_resources.py +95 -0
- chrys/orchestration/session_usage.py +219 -0
- chrys/orchestration/startup.py +255 -0
- chrys/orchestration/sub_agents/__init__.py +3 -0
- chrys/orchestration/sub_agents/acp_policy.py +269 -0
- chrys/orchestration/sub_agents/kernel_policy.py +848 -0
- chrys/orchestration/sub_agents/registration.py +44 -0
- chrys/orchestration/sub_agents/shell.py +337 -0
- chrys/orchestration/sub_agents/tools.py +2228 -0
- chrys/orchestration/workflows/__init__.py +3 -0
- chrys/orchestration/workflows/agent_archive.py +156 -0
- chrys/orchestration/workflows/agent_node.py +894 -0
- chrys/orchestration/workflows/agent_node_build.py +687 -0
- chrys/orchestration/workflows/catalog.py +332 -0
- chrys/orchestration/workflows/coordinator.py +771 -0
- chrys/orchestration/workflows/hooks.py +176 -0
- chrys/orchestration/workflows/preview.py +322 -0
- chrys/orchestration/workflows/rollback.py +151 -0
- chrys/orchestration/workflows/runner.py +986 -0
- chrys/orchestration/workflows/session.py +390 -0
- chrys/orchestration/workflows/settings.py +97 -0
- chrys/orchestration/workflows/validation.py +674 -0
- chrys/orchestration/workflows/worker_client.py +1002 -0
- chrys/service/__init__.py +3 -0
- chrys/service/acp_client/__init__.py +65 -0
- chrys/service/acp_client/_transport.py +922 -0
- chrys/service/acp_client/client.py +1311 -0
- chrys/service/acp_client/errors.py +224 -0
- chrys/service/acp_client/protocol.py +220 -0
- chrys/service/acp_client/spawn.py +239 -0
- chrys/service/acp_client/spec.py +100 -0
- chrys/service/agent_middleware/__init__.py +60 -0
- chrys/service/agent_middleware/_metadata_keys.py +52 -0
- chrys/service/agent_middleware/control/__init__.py +3 -0
- chrys/service/agent_middleware/control/approval.py +769 -0
- chrys/service/agent_middleware/control/approval_hooks.py +62 -0
- chrys/service/agent_middleware/control/ask_user.py +207 -0
- chrys/service/agent_middleware/control/interrupt.py +50 -0
- chrys/service/agent_middleware/control/sleep.py +118 -0
- chrys/service/agent_middleware/control/todo.py +117 -0
- chrys/service/agent_middleware/events/__init__.py +3 -0
- chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
- chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
- chrys/service/agent_middleware/events/intermediate_text.py +128 -0
- chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
- chrys/service/agent_middleware/events/result_persistence.py +98 -0
- chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
- chrys/service/agent_middleware/events/tool_error_text.py +22 -0
- chrys/service/agent_middleware/events/tool_events.py +779 -0
- chrys/service/agent_middleware/injection.py +510 -0
- chrys/service/agent_middleware/reminders/__init__.py +91 -0
- chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
- chrys/service/agent_middleware/reminders/base.py +25 -0
- chrys/service/agent_middleware/reminders/context_usage.py +74 -0
- chrys/service/agent_middleware/reminders/file_change.py +63 -0
- chrys/service/agent_middleware/reminders/mcp.py +39 -0
- chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
- chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
- chrys/service/agent_middleware/reminders/skills.py +44 -0
- chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
- chrys/service/agent_middleware/reminders/todo.py +35 -0
- chrys/service/agent_middleware/reminders/turn_line.py +48 -0
- chrys/service/agent_middleware/response_validation.py +1380 -0
- chrys/service/agent_middleware/system_reminder.py +1107 -0
- chrys/service/agent_middleware/validators.py +396 -0
- chrys/service/analytics/__init__.py +124 -0
- chrys/service/analytics/_actions.py +896 -0
- chrys/service/analytics/_context_evidence.py +357 -0
- chrys/service/analytics/_critical_path.py +179 -0
- chrys/service/analytics/_facts.py +919 -0
- chrys/service/analytics/_insights.py +769 -0
- chrys/service/analytics/_metric_ops.py +83 -0
- chrys/service/analytics/_session_projection.py +460 -0
- chrys/service/analytics/_timeline.py +434 -0
- chrys/service/analytics/_turn_graph.py +1237 -0
- chrys/service/analytics/_turns.py +1377 -0
- chrys/service/analytics/_workflows.py +171 -0
- chrys/service/analytics/aggregation.py +1474 -0
- chrys/service/analytics/classification.py +128 -0
- chrys/service/analytics/export.py +803 -0
- chrys/service/analytics/findings.py +282 -0
- chrys/service/analytics/math.py +79 -0
- chrys/service/analytics/model.py +886 -0
- chrys/service/analytics/reader.py +321 -0
- chrys/service/approval/__init__.py +3 -0
- chrys/service/approval/arbitration.py +102 -0
- chrys/service/approval/command_identity.py +109 -0
- chrys/service/approval/correlation.py +57 -0
- chrys/service/approval/grant_store.py +108 -0
- chrys/service/approval/judge.py +589 -0
- chrys/service/approval/level.py +15 -0
- chrys/service/approval/policy.py +209 -0
- chrys/service/approval/reuse.py +223 -0
- chrys/service/approval/reuse_binding.py +133 -0
- chrys/service/approval/safety_classifier.py +536 -0
- chrys/service/approval/turn_context.py +49 -0
- chrys/service/context/__init__.py +3 -0
- chrys/service/context/compaction/__init__.py +38 -0
- chrys/service/context/compaction/breaker.py +156 -0
- chrys/service/context/compaction/budgets.py +93 -0
- chrys/service/context/compaction/calibration.py +77 -0
- chrys/service/context/compaction/compression.py +690 -0
- chrys/service/context/compaction/current_turn_drop.py +458 -0
- chrys/service/context/compaction/events.py +201 -0
- chrys/service/context/compaction/exclusions.py +261 -0
- chrys/service/context/compaction/groups.py +361 -0
- chrys/service/context/compaction/last_words.py +1996 -0
- chrys/service/context/compaction/last_words_state.py +752 -0
- chrys/service/context/compaction/scoped.py +472 -0
- chrys/service/context/compaction/spill.py +1947 -0
- chrys/service/context/compaction/strategy.py +1116 -0
- chrys/service/context/compaction/summaries.py +319 -0
- chrys/service/context/compaction/turns.py +375 -0
- chrys/service/context/manager.py +174 -0
- chrys/service/context/memory_loader.py +792 -0
- chrys/service/context/middleware/__init__.py +3 -0
- chrys/service/context/middleware/usage.py +332 -0
- chrys/service/context/providers/__init__.py +3 -0
- chrys/service/context/providers/context_management.py +432 -0
- chrys/service/context/providers/history.py +647 -0
- chrys/service/context/providers/memory.py +50 -0
- chrys/service/context/tool_content.py +22 -0
- chrys/service/hooks/__init__.py +37 -0
- chrys/service/hooks/detached_worker.py +118 -0
- chrys/service/hooks/docs/README.md +14 -0
- chrys/service/hooks/docs/authoring.md +684 -0
- chrys/service/hooks/docs/configuration.md +499 -0
- chrys/service/hooks/docs/design.md +359 -0
- chrys/service/hooks/docs/project-hooks.md +348 -0
- chrys/service/hooks/events.py +157 -0
- chrys/service/hooks/loader.py +665 -0
- chrys/service/hooks/manager.py +807 -0
- chrys/service/hooks/matcher.py +111 -0
- chrys/service/hooks/outbox.py +294 -0
- chrys/service/hooks/runner.py +572 -0
- chrys/service/hooks/schema.py +407 -0
- chrys/service/llm/__init__.py +7 -0
- chrys/service/llm/_structured_outputs.py +192 -0
- chrys/service/llm/anthropic_messages/__init__.py +9 -0
- chrys/service/llm/anthropic_messages/client.py +254 -0
- chrys/service/llm/anthropic_messages/decode.py +331 -0
- chrys/service/llm/anthropic_messages/history.py +279 -0
- chrys/service/llm/anthropic_messages/request.py +323 -0
- chrys/service/llm/anthropic_messages/server_tools.py +319 -0
- chrys/service/llm/anthropic_messages/stream.py +297 -0
- chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
- chrys/service/llm/chat_completions/__init__.py +9 -0
- chrys/service/llm/chat_completions/client.py +369 -0
- chrys/service/llm/chat_completions/decode.py +338 -0
- chrys/service/llm/chat_completions/history.py +657 -0
- chrys/service/llm/chat_completions/reasoning.py +244 -0
- chrys/service/llm/chat_completions/request.py +192 -0
- chrys/service/llm/chat_completions/stream.py +546 -0
- chrys/service/llm/chat_completions/validation.py +308 -0
- chrys/service/llm/clients.py +613 -0
- chrys/service/llm/images.py +65 -0
- chrys/service/llm/mock.py +413 -0
- chrys/service/llm/observer.py +446 -0
- chrys/service/llm/one_shot.py +106 -0
- chrys/service/llm/openai_exceptions.py +93 -0
- chrys/service/llm/openai_responses/__init__.py +10 -0
- chrys/service/llm/openai_responses/client.py +323 -0
- chrys/service/llm/openai_responses/decode.py +432 -0
- chrys/service/llm/openai_responses/history.py +519 -0
- chrys/service/llm/openai_responses/hosted.py +625 -0
- chrys/service/llm/openai_responses/replay.py +629 -0
- chrys/service/llm/openai_responses/request.py +342 -0
- chrys/service/llm/openai_responses/stream.py +847 -0
- chrys/service/llm/openai_timestamps.py +57 -0
- chrys/service/llm/providers.py +107 -0
- chrys/service/llm/proxy_route.py +54 -0
- chrys/service/llm/raw_http_log.py +309 -0
- chrys/service/llm/route_facts.py +63 -0
- chrys/service/llm/route_sessions.py +49 -0
- chrys/service/llm/sdk_anthropic.py +18 -0
- chrys/service/llm/sdk_headers.py +47 -0
- chrys/service/llm/sdk_openai.py +18 -0
- chrys/service/llm/sdk_retry.py +59 -0
- chrys/service/llm/wire_client.py +246 -0
- chrys/service/mcp/__init__.py +3 -0
- chrys/service/mcp/_connection.py +235 -0
- chrys/service/mcp/_http_transport.py +350 -0
- chrys/service/mcp/_progressive.py +459 -0
- chrys/service/mcp/_stdio_transport.py +614 -0
- chrys/service/mcp/_tool_mixins.py +96 -0
- chrys/service/mcp/adapter.py +1031 -0
- chrys/service/mcp/cache.py +476 -0
- chrys/service/mcp/content.py +32 -0
- chrys/service/mcp/errors.py +169 -0
- chrys/service/mcp/owned.py +1330 -0
- chrys/service/mcp/result_limits.py +168 -0
- chrys/service/mcp/thinking_warning.py +95 -0
- chrys/service/mcp/validation.py +74 -0
- chrys/service/mutations/__init__.py +84 -0
- chrys/service/mutations/coordination.py +894 -0
- chrys/service/mutations/detector.py +458 -0
- chrys/service/mutations/git_calibrator.py +397 -0
- chrys/service/mutations/git_state.py +647 -0
- chrys/service/mutations/pipeline.py +779 -0
- chrys/service/mutations/scanner.py +466 -0
- chrys/service/mutations/snapshot_files.py +233 -0
- chrys/service/mutations/store.py +625 -0
- chrys/service/mutations/tool_names.py +15 -0
- chrys/service/mutations/trace.py +246 -0
- chrys/service/mutations/tracker.py +1289 -0
- chrys/service/mutations/types.py +559 -0
- chrys/service/mutations/workspace_changes.py +1583 -0
- chrys/service/profiles/__init__.py +2 -0
- chrys/service/profiles/agents/__init__.py +3 -0
- chrys/service/profiles/agents/builtins/Code.yaml +199 -0
- chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
- chrys/service/profiles/agents/builtins/General.yaml +114 -0
- chrys/service/profiles/agents/builtins/QA.yaml +128 -0
- chrys/service/profiles/agents/loader.py +808 -0
- chrys/service/profiles/agents/registry.py +727 -0
- chrys/service/profiles/agents/schema.py +371 -0
- chrys/service/profiles/agents/serializer.py +196 -0
- chrys/service/profiles/coercion.py +22 -0
- chrys/service/profiles/models/__init__.py +3 -0
- chrys/service/profiles/models/env_bridge.py +157 -0
- chrys/service/profiles/models/loader.py +282 -0
- chrys/service/profiles/models/options.py +438 -0
- chrys/service/profiles/models/registry.py +104 -0
- chrys/service/profiles/models/resolver.py +286 -0
- chrys/service/profiles/models/schema.py +91 -0
- chrys/service/profiles/models/serializer.py +76 -0
- chrys/service/session/__init__.py +3 -0
- chrys/service/session/checkpoint.py +156 -0
- chrys/service/session/history.py +1616 -0
- chrys/service/session/message_metadata.py +151 -0
- chrys/service/session/persistence.py +435 -0
- chrys/service/session/runtime_metadata.py +210 -0
- chrys/service/session/sub_agent_logs.py +1069 -0
- chrys/service/session/sub_agent_transcript.py +654 -0
- chrys/service/skills/__init__.py +3 -0
- chrys/service/skills/adapter.py +408 -0
- chrys/service/skills/constants.py +8 -0
- chrys/service/skills/loader.py +575 -0
- chrys/service/skills/model.py +130 -0
- chrys/service/skills/provider.py +841 -0
- chrys/service/skills/runner.py +390 -0
- chrys/service/state/__init__.py +3 -0
- chrys/service/state/_fork.py +710 -0
- chrys/service/state/_session_files.py +175 -0
- chrys/service/state/_session_meta.py +552 -0
- chrys/service/state/locks.py +154 -0
- chrys/service/state/serializers.py +156 -0
- chrys/service/state/session_catalog.py +361 -0
- chrys/service/state/session_listing.py +84 -0
- chrys/service/state/session_migration.py +446 -0
- chrys/service/state/session_mru.py +315 -0
- chrys/service/state/store.py +2132 -0
- chrys/service/state/workflow.py +158 -0
- chrys/service/text_blocks.py +47 -0
- chrys/service/todos/__init__.py +3 -0
- chrys/service/todos/reminder.py +31 -0
- chrys/service/todos/tracker.py +58 -0
- chrys/service/tools/__init__.py +3 -0
- chrys/service/tools/approval_targets.py +92 -0
- chrys/service/tools/builtins/__init__.py +3 -0
- chrys/service/tools/builtins/ask_user.py +326 -0
- chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
- chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
- chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
- chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
- chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
- chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
- chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
- chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
- chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
- chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
- chrys/service/tools/builtins/doc_converter/registry.py +55 -0
- chrys/service/tools/builtins/filesystem.py +1071 -0
- chrys/service/tools/builtins/search.py +1265 -0
- chrys/service/tools/builtins/shell.py +967 -0
- chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
- chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
- chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
- chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
- chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
- chrys/service/tools/builtins/shell_filter.py +24 -0
- chrys/service/tools/builtins/sleep.py +91 -0
- chrys/service/tools/builtins/todo.py +64 -0
- chrys/service/tools/builtins/web/__init__.py +6 -0
- chrys/service/tools/builtins/web/build.py +268 -0
- chrys/service/tools/builtins/web/config.py +338 -0
- chrys/service/tools/builtins/web/credentials.py +37 -0
- chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
- chrys/service/tools/builtins/web/fetch/cache.py +68 -0
- chrys/service/tools/builtins/web/fetch/convert.py +56 -0
- chrys/service/tools/builtins/web/fetch/tool.py +223 -0
- chrys/service/tools/builtins/web/hosted.py +61 -0
- chrys/service/tools/builtins/web/http.py +330 -0
- chrys/service/tools/builtins/web/progress.py +18 -0
- chrys/service/tools/builtins/web/search/__init__.py +3 -0
- chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
- chrys/service/tools/builtins/web/search/html.py +152 -0
- chrys/service/tools/builtins/web/search/json_api.py +227 -0
- chrys/service/tools/builtins/web/search/providers.py +77 -0
- chrys/service/tools/builtins/web/search/tool.py +218 -0
- chrys/service/tools/builtins/web/search/types.py +28 -0
- chrys/service/tools/builtins/web/tls_identity.py +66 -0
- chrys/service/tools/kinds.py +115 -0
- chrys/service/tools/names.py +47 -0
- chrys/service/tools/registry.py +242 -0
- chrys/service/tools/result_metadata.py +103 -0
- chrys/service/tools/session_artifacts.py +196 -0
- chrys/service/tools/spill.py +218 -0
- chrys/service/tools/workspace_paths.py +81 -0
- chrys/service/trajectory/__init__.py +11 -0
- chrys/service/trajectory/approvals.py +201 -0
- chrys/service/trajectory/compaction.py +420 -0
- chrys/service/trajectory/fork.py +75 -0
- chrys/service/trajectory/hooks.py +217 -0
- chrys/service/trajectory/items.py +41 -0
- chrys/service/trajectory/preparation.py +374 -0
- chrys/service/trajectory/retries.py +95 -0
- chrys/service/trajectory/revisions.py +129 -0
- chrys/service/trajectory/segmented.py +163 -0
- chrys/service/trajectory/session.py +762 -0
- chrys/service/trajectory/state.py +83 -0
- chrys/service/trajectory/sub_agents.py +169 -0
- chrys/service/trajectory/tombstone.py +262 -0
- chrys/service/trajectory/tools.py +323 -0
- chrys/service/trajectory/validation.py +130 -0
- chrys/service/trajectory/waits.py +161 -0
- chrys/service/trajectory/workflow.py +148 -0
- chrys/service/vision.py +128 -0
- chrys/service/workflows/__init__.py +3 -0
- chrys/service/workflows/admission.py +151 -0
- chrys/service/workflows/artifacts.py +109 -0
- chrys/service/workflows/asks.py +130 -0
- chrys/service/workflows/builtins/__init__.py +12 -0
- chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
- chrys/service/workflows/builtins/demo-workflow.py +756 -0
- chrys/service/workflows/discovery.py +554 -0
- chrys/service/workflows/environment.py +284 -0
- chrys/service/workflows/graph.py +430 -0
- chrys/service/workflows/history.py +82 -0
- chrys/service/workflows/interpreter.py +107 -0
- chrys/service/workflows/journal.py +334 -0
- chrys/service/workflows/layout.py +56 -0
- chrys/service/workflows/ledger.py +149 -0
- chrys/service/workflows/model_selection.py +18 -0
- chrys/service/workflows/orphans.py +209 -0
- chrys/service/workflows/outcomes.py +46 -0
- chrys/service/workflows/protocol.py +210 -0
- chrys/service/workflows/records.py +142 -0
- chrys/service/workflows/scheduler.py +770 -0
- chrys/service/workflows/sdk/__init__.py +51 -0
- chrys/service/workflows/sdk/_ask.py +167 -0
- chrys/service/workflows/sdk/_builder.py +781 -0
- chrys/service/workflows/sdk/_values.py +92 -0
- chrys/service/workflows/sdk_artifact.py +74 -0
- chrys/service/workflows/store.py +494 -0
- chrys/service/workflows/transcript.py +102 -0
- chrys/service/workflows/values.py +73 -0
- chrys/service/workflows/worker_host.py +1820 -0
- chrys/workflows.py +38 -0
- icode_tui-0.29.1.dist-info/METADATA +104 -0
- icode_tui-0.29.1.dist-info/RECORD +1138 -0
- icode_tui-0.29.1.dist-info/WHEEL +4 -0
- icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
- icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
- icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""InputBar widget — multi-line input area with Send/Stop button.
|
|
4
|
+
|
|
5
|
+
Enter submits. Ctrl+J inserts a newline for multi-line input.
|
|
6
|
+
Cross-platform system clipboard support (macOS pbcopy/pbpaste,
|
|
7
|
+
Win32 ``SetClipboardData``/``GetClipboardData`` on Windows,
|
|
8
|
+
xclip/xsel on Linux).
|
|
9
|
+
When the agent is running, the button becomes Stop to interrupt.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import unicodedata
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from secrets import token_hex
|
|
19
|
+
from time import time
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
21
|
+
|
|
22
|
+
from rich.text import Text
|
|
23
|
+
from textual.containers import Horizontal
|
|
24
|
+
from textual.content import Content
|
|
25
|
+
from textual.css.scalar import Scalar
|
|
26
|
+
from textual.geometry import Spacing
|
|
27
|
+
from textual.message import Message
|
|
28
|
+
from textual.reactive import reactive
|
|
29
|
+
from textual.strip import Strip
|
|
30
|
+
from textual.timer import Timer
|
|
31
|
+
from textual.widget import Widget
|
|
32
|
+
from textual.widgets import Button, TextArea
|
|
33
|
+
|
|
34
|
+
from chrys.app.tui.i18n import render_content, render_str
|
|
35
|
+
from chrys.app.tui.support.prompt_history import DEFAULT_MAX_ENTRIES, PromptHistoryWriter, append_history, load_history
|
|
36
|
+
from chrys.app.tui.util.visibility import resync_compositor_regions
|
|
37
|
+
from chrys.app.tui.widgets import EnhancedTextArea
|
|
38
|
+
from chrys.app.tui.widgets.chrome.commands import is_slash_command_candidate
|
|
39
|
+
from chrys.app.tui.widgets.chrome.image_paste import (
|
|
40
|
+
convert_pasted_image_paths_to_mentions,
|
|
41
|
+
save_clipboard_image_to_file,
|
|
42
|
+
)
|
|
43
|
+
from chrys.app.tui.widgets.selection import NonSelectableTextMixin
|
|
44
|
+
from chrys.app.tui.widgets.text_area import MESSAGE_EDITOR_PASTE_MAX_TOKENS
|
|
45
|
+
from chrys.foundation.i18n import Localizer, MessageRef, msg
|
|
46
|
+
from chrys.foundation.i18n.formatting import format_message
|
|
47
|
+
from chrys.foundation.platform import safe_getcwd
|
|
48
|
+
|
|
49
|
+
# Status-bar chrome is synchronized by MainScreen through widget messages.
|
|
50
|
+
_SPINNER_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
51
|
+
# Unicode-name markers for CJK-family scripts where a preceding character cannot be an email local part.
|
|
52
|
+
_CJK_NAME_TOKENS = ("CJK", "HIRAGANA", "KATAKANA", "HANGUL", "BOPOMOFO", "IDEOGRAPH")
|
|
53
|
+
INPUT_SEND = msg("tui.input.send", fallback="Send")
|
|
54
|
+
INPUT_NEW = msg("tui.input.new", fallback="New")
|
|
55
|
+
INPUT_NEW_DESCRIPTION = msg("tui.input.new_description", fallback="Create a new session")
|
|
56
|
+
INPUT_QUEUED = msg("tui.input.queued", fallback="Queued")
|
|
57
|
+
INPUT_INTERRUPT = msg("tui.input.interrupt", fallback="Interrupt")
|
|
58
|
+
INPUT_CONTINUE = msg("tui.input.continue", fallback="Continue")
|
|
59
|
+
INPUT_RETRY = msg("tui.input.retry", fallback="Retry")
|
|
60
|
+
_INPUT_PROMPT_PLACEHOLDER = msg(
|
|
61
|
+
"tui.input.placeholder.prompt",
|
|
62
|
+
fallback="Type a message ({shortcut} for Editor)",
|
|
63
|
+
)
|
|
64
|
+
_INPUT_INJECT_PLACEHOLDER = msg("tui.input.placeholder.inject", fallback="Inject a message")
|
|
65
|
+
_INPUT_PLACEHOLDER_AGENTS = msg("tui.input.placeholder.agents", fallback="Agents")
|
|
66
|
+
_INPUT_PLACEHOLDER_MODELS = msg("tui.input.placeholder.models", fallback="Models")
|
|
67
|
+
_INPUT_PLACEHOLDER_SHELL = msg("tui.input.placeholder.shell", fallback="Shell")
|
|
68
|
+
_INPUT_PLACEHOLDER_COMMANDS = msg("tui.input.placeholder.commands", fallback="Commands")
|
|
69
|
+
_INPUT_PLACEHOLDER_FILES = msg("tui.input.placeholder.files", fallback="Files")
|
|
70
|
+
_INPUT_NEWLINE_HINT = msg("tui.input.hint.newline", fallback="{shortcut} for newline")
|
|
71
|
+
|
|
72
|
+
type InputLabel = MessageRef | str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _is_cjk_character(char: str) -> bool:
|
|
76
|
+
name = unicodedata.name(char, "")
|
|
77
|
+
return any(token in name for token in _CJK_NAME_TOKENS)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_file_trigger_boundary(text: str, at_pos: int) -> bool:
|
|
81
|
+
"""Return whether an ``@`` at *at_pos* should open file suggestions."""
|
|
82
|
+
if at_pos == 0:
|
|
83
|
+
return True
|
|
84
|
+
previous = text[at_pos - 1]
|
|
85
|
+
return previous.isspace() or _is_cjk_character(previous)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _placeholder_fragment(localizer: Localizer | None, reference: MessageRef) -> Content:
|
|
89
|
+
if localizer is None:
|
|
90
|
+
return Content.from_text(format_message(reference), markup=False)
|
|
91
|
+
return render_content(localizer, reference)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _build_prompt_placeholder(localizer: Localizer | None = None) -> Content:
|
|
95
|
+
"""Build the default placeholder with shortcut hints (reverse-video pills)."""
|
|
96
|
+
return Content.assemble(
|
|
97
|
+
_placeholder_fragment(localizer, _INPUT_PROMPT_PLACEHOLDER.bind(shortcut="Ctrl+O")),
|
|
98
|
+
" ",
|
|
99
|
+
(" # ", "r"),
|
|
100
|
+
" ",
|
|
101
|
+
_placeholder_fragment(localizer, _INPUT_PLACEHOLDER_AGENTS.bind()),
|
|
102
|
+
" ",
|
|
103
|
+
(" $ ", "r"),
|
|
104
|
+
" ",
|
|
105
|
+
_placeholder_fragment(localizer, _INPUT_PLACEHOLDER_MODELS.bind()),
|
|
106
|
+
" ",
|
|
107
|
+
(" / ", "r"),
|
|
108
|
+
" ",
|
|
109
|
+
_placeholder_fragment(localizer, _INPUT_PLACEHOLDER_COMMANDS.bind()),
|
|
110
|
+
" ",
|
|
111
|
+
(" @ ", "r"),
|
|
112
|
+
" ",
|
|
113
|
+
_placeholder_fragment(localizer, _INPUT_PLACEHOLDER_FILES.bind()),
|
|
114
|
+
" ",
|
|
115
|
+
(" ! ", "r"),
|
|
116
|
+
" ",
|
|
117
|
+
_placeholder_fragment(localizer, _INPUT_PLACEHOLDER_SHELL.bind()),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _build_inject_placeholder(localizer: Localizer | None = None) -> Content:
|
|
122
|
+
"""Build placeholder for when agent is running (inject + commands/files)."""
|
|
123
|
+
return Content.assemble(
|
|
124
|
+
_placeholder_fragment(localizer, _INPUT_INJECT_PLACEHOLDER.bind()),
|
|
125
|
+
" ",
|
|
126
|
+
(" / ", "r"),
|
|
127
|
+
" ",
|
|
128
|
+
_placeholder_fragment(localizer, _INPUT_PLACEHOLDER_COMMANDS.bind()),
|
|
129
|
+
" ",
|
|
130
|
+
(" @ ", "r"),
|
|
131
|
+
" ",
|
|
132
|
+
_placeholder_fragment(localizer, _INPUT_PLACEHOLDER_FILES.bind()),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if TYPE_CHECKING:
|
|
137
|
+
from textual.app import ComposeResult
|
|
138
|
+
|
|
139
|
+
from chrys.app.tui.i18n import LocaleController
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
_HISTORY_PREVIOUS_KEYS = frozenset({"up"})
|
|
143
|
+
_HISTORY_NEXT_KEYS = frozenset({"down"})
|
|
144
|
+
_BUTTON_GROUP_HORIZONTAL_PADDING = 2
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass(frozen=True, slots=True)
|
|
148
|
+
class InputDraftSnapshot:
|
|
149
|
+
"""Immutable chat draft captured before opening the message editor."""
|
|
150
|
+
|
|
151
|
+
text: str
|
|
152
|
+
cursor_location: tuple[int, int]
|
|
153
|
+
revision: int
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass
|
|
157
|
+
class _HistoryBrowser:
|
|
158
|
+
"""Mutable prompt-history browsing state for one history scope."""
|
|
159
|
+
|
|
160
|
+
entries: list[str] = field(default_factory=list)
|
|
161
|
+
index: int = -1
|
|
162
|
+
max_entries: int = DEFAULT_MAX_ENTRIES
|
|
163
|
+
storage_loaded: bool = False
|
|
164
|
+
|
|
165
|
+
def reset(self, entries: list[str] | None = None) -> None:
|
|
166
|
+
if entries is not None:
|
|
167
|
+
self.entries = list(entries)
|
|
168
|
+
self.index = -1
|
|
169
|
+
|
|
170
|
+
def append(self, text: str) -> None:
|
|
171
|
+
text = text.strip()
|
|
172
|
+
if text and (not self.entries or self.entries[-1] != text):
|
|
173
|
+
self.entries.append(text)
|
|
174
|
+
overflow = len(self.entries) - self.max_entries
|
|
175
|
+
if overflow > 0:
|
|
176
|
+
del self.entries[:overflow]
|
|
177
|
+
if self.index != -1:
|
|
178
|
+
self.index = max(0, self.index - overflow)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class _ChatTextArea(EnhancedTextArea):
|
|
182
|
+
"""EnhancedTextArea subclass: Enter = submit, Ctrl+J = newline, input history."""
|
|
183
|
+
|
|
184
|
+
_history: _HistoryBrowser
|
|
185
|
+
_history_browsing: bool
|
|
186
|
+
_history_draft: str | None
|
|
187
|
+
_instance_id: str
|
|
188
|
+
_right_placeholder: str
|
|
189
|
+
|
|
190
|
+
class Submitted(Message):
|
|
191
|
+
"""Posted when the user presses Enter."""
|
|
192
|
+
|
|
193
|
+
def __init__(self, text: str) -> None:
|
|
194
|
+
self.text = text
|
|
195
|
+
super().__init__()
|
|
196
|
+
|
|
197
|
+
class ShellModeRequested(Message):
|
|
198
|
+
"""Posted when user types ``!`` at position (0, 0) with empty text."""
|
|
199
|
+
|
|
200
|
+
class EditorRequested(Message):
|
|
201
|
+
"""Posted when the user requests the full-screen message editor."""
|
|
202
|
+
|
|
203
|
+
class ShellModeCancelled(Message):
|
|
204
|
+
"""Posted when user exits shell mode (Escape or backspace-to-empty)."""
|
|
205
|
+
|
|
206
|
+
class SlashTrigger(Message):
|
|
207
|
+
"""Posted when user types ``/`` at start of empty input."""
|
|
208
|
+
|
|
209
|
+
class FileTrigger(Message):
|
|
210
|
+
"""Posted when user types ``@``."""
|
|
211
|
+
|
|
212
|
+
class AgentTrigger(Message):
|
|
213
|
+
"""Posted when user types ``#`` at start of empty input."""
|
|
214
|
+
|
|
215
|
+
class ModelTrigger(Message):
|
|
216
|
+
"""Posted when user types ``$`` at start of empty input."""
|
|
217
|
+
|
|
218
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
219
|
+
kwargs.setdefault("max_paste_tokens", MESSAGE_EDITOR_PASTE_MAX_TOKENS)
|
|
220
|
+
super().__init__(*args, **kwargs)
|
|
221
|
+
self._history = _HistoryBrowser()
|
|
222
|
+
self._history_browsing = False
|
|
223
|
+
self._history_draft: str | None = None
|
|
224
|
+
self._instance_id = token_hex(8)
|
|
225
|
+
self._history_writer = PromptHistoryWriter(append_history)
|
|
226
|
+
self._suggestions_active: bool = False
|
|
227
|
+
self._right_placeholder = ""
|
|
228
|
+
# Session restore/workspace updates replace this before normal use; this covers early paste during startup.
|
|
229
|
+
self._paste_cwd: str = safe_getcwd()
|
|
230
|
+
self._clipboard_image_dir: Path | None = None
|
|
231
|
+
|
|
232
|
+
def set_right_placeholder(self, text: str) -> None:
|
|
233
|
+
"""Set literal placeholder text rendered against the input's right edge."""
|
|
234
|
+
if text == self._right_placeholder:
|
|
235
|
+
return
|
|
236
|
+
self._right_placeholder = text
|
|
237
|
+
self.refresh()
|
|
238
|
+
|
|
239
|
+
def render_line(self, y: int) -> Strip:
|
|
240
|
+
"""Render the optional right placeholder without adding a layout widget."""
|
|
241
|
+
line = super().render_line(y)
|
|
242
|
+
if self.text or y != 0 or not self.placeholder or not self._right_placeholder:
|
|
243
|
+
return line
|
|
244
|
+
|
|
245
|
+
hint = Content.from_text(self._right_placeholder, markup=False)
|
|
246
|
+
right_padding = 1
|
|
247
|
+
gap = self.content_size.width - line.cell_length - hint.cell_length - right_padding
|
|
248
|
+
if gap < 1:
|
|
249
|
+
return line
|
|
250
|
+
|
|
251
|
+
placeholder_style = self.get_visual_style("text-area--placeholder")
|
|
252
|
+
styled_hint = hint.stylize(placeholder_style)
|
|
253
|
+
hint_strip = Strip(
|
|
254
|
+
styled_hint.render_segments(self.visual_style),
|
|
255
|
+
styled_hint.cell_length,
|
|
256
|
+
)
|
|
257
|
+
return Strip.join(
|
|
258
|
+
(
|
|
259
|
+
line,
|
|
260
|
+
Strip.blank(gap, self.rich_style),
|
|
261
|
+
hint_strip,
|
|
262
|
+
Strip.blank(right_padding, self.rich_style),
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def _set_text(self, text: str) -> None:
|
|
267
|
+
"""Replace textarea content with *text* and move cursor to end."""
|
|
268
|
+
self.clear()
|
|
269
|
+
if text:
|
|
270
|
+
self.insert(text)
|
|
271
|
+
|
|
272
|
+
async def _refresh_history_entries(self) -> None:
|
|
273
|
+
browser = self._history
|
|
274
|
+
if browser.entries or browser.storage_loaded:
|
|
275
|
+
# Appends reach memory before their queued disk writes. Once this
|
|
276
|
+
# instance has entries, disk can only be an older prefix and must
|
|
277
|
+
# not replace the authoritative in-memory list.
|
|
278
|
+
return
|
|
279
|
+
refreshed_entries = await asyncio.to_thread(
|
|
280
|
+
load_history,
|
|
281
|
+
max_entries=browser.max_entries,
|
|
282
|
+
instance_id=self._instance_id,
|
|
283
|
+
)
|
|
284
|
+
browser.storage_loaded = True
|
|
285
|
+
if not browser.entries:
|
|
286
|
+
browser.entries = list(refreshed_entries)
|
|
287
|
+
|
|
288
|
+
def _capture_visible_draft(self) -> None:
|
|
289
|
+
if not self._history_browsing:
|
|
290
|
+
self._history_draft = self.text
|
|
291
|
+
|
|
292
|
+
async def _browse_history(self, direction: Literal[-1, 1]) -> bool:
|
|
293
|
+
"""Move within instance history, returning True when the key was handled."""
|
|
294
|
+
browser = self._history
|
|
295
|
+
|
|
296
|
+
# First entry into browsing mode saves the visible draft.
|
|
297
|
+
self._capture_visible_draft()
|
|
298
|
+
|
|
299
|
+
self._history_browsing = True
|
|
300
|
+
|
|
301
|
+
if browser.index == -1:
|
|
302
|
+
await self._refresh_history_entries()
|
|
303
|
+
if not browser.entries:
|
|
304
|
+
self._history_browsing = False
|
|
305
|
+
return False
|
|
306
|
+
browser.index = len(browser.entries) - 1
|
|
307
|
+
self._set_text(browser.entries[browser.index])
|
|
308
|
+
return True
|
|
309
|
+
|
|
310
|
+
if direction < 0:
|
|
311
|
+
if browser.index <= 0:
|
|
312
|
+
self._set_text(browser.entries[browser.index])
|
|
313
|
+
return True
|
|
314
|
+
browser.index -= 1
|
|
315
|
+
self._set_text(browser.entries[browser.index])
|
|
316
|
+
return True
|
|
317
|
+
|
|
318
|
+
if browser.index < len(browser.entries) - 1:
|
|
319
|
+
browser.index += 1
|
|
320
|
+
self._set_text(browser.entries[browser.index])
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
# Past the last entry — restore the saved draft and exit browsing.
|
|
324
|
+
browser.index = -1
|
|
325
|
+
self._history_browsing = False
|
|
326
|
+
self._set_text(self._history_draft or "")
|
|
327
|
+
return True
|
|
328
|
+
|
|
329
|
+
def _reset_history_browsing(self) -> None:
|
|
330
|
+
self._history_browsing = False
|
|
331
|
+
self._history_draft = None
|
|
332
|
+
self._history.reset()
|
|
333
|
+
|
|
334
|
+
def _prepare_paste_text(self, text: str) -> str:
|
|
335
|
+
"""Convert pasted/dropped supported image paths into ``@`` mentions."""
|
|
336
|
+
try:
|
|
337
|
+
return self._prepare_image_path_paste_text(text) or text
|
|
338
|
+
except OSError, RuntimeError, ValueError:
|
|
339
|
+
return text
|
|
340
|
+
|
|
341
|
+
def _prepare_image_path_paste_text(self, text: str) -> str | None:
|
|
342
|
+
"""Return converted image-path paste text, or ``None`` when not applicable."""
|
|
343
|
+
converted = convert_pasted_image_paths_to_mentions(text, cwd=Path(self._paste_cwd))
|
|
344
|
+
if converted == text:
|
|
345
|
+
return None
|
|
346
|
+
start, _end = self.selection
|
|
347
|
+
insert_index = self.document.get_index_from_location(start)
|
|
348
|
+
if insert_index > 0 and not self.text[insert_index - 1].isspace():
|
|
349
|
+
return f" {converted}"
|
|
350
|
+
return converted
|
|
351
|
+
|
|
352
|
+
def set_paste_cwd(self, cwd: str) -> None:
|
|
353
|
+
"""Set the cwd used to resolve relative pasted/dropped image paths."""
|
|
354
|
+
self._paste_cwd = cwd
|
|
355
|
+
|
|
356
|
+
def set_clipboard_image_dir(self, directory: str | Path | None) -> None:
|
|
357
|
+
"""Set the session-scoped directory used for clipboard image attachments."""
|
|
358
|
+
self._clipboard_image_dir = Path(directory) if directory is not None else None
|
|
359
|
+
|
|
360
|
+
def _prepare_clipboard_image_paste_text(self) -> str | None:
|
|
361
|
+
"""Return paste text for a bitmap image currently on the platform clipboard."""
|
|
362
|
+
# TODO(perf): Clipboard capture may run osascript/PIL/file I/O. Move
|
|
363
|
+
# this boundary to a worker when paste completion can remain ordered
|
|
364
|
+
# with subsequent input events.
|
|
365
|
+
path = save_clipboard_image_to_file(self._clipboard_image_dir)
|
|
366
|
+
if path is None:
|
|
367
|
+
return None
|
|
368
|
+
try:
|
|
369
|
+
return self._prepare_image_path_paste_text(str(path)) or str(path)
|
|
370
|
+
except OSError, RuntimeError, ValueError:
|
|
371
|
+
return str(path)
|
|
372
|
+
|
|
373
|
+
def _insert_paste_text(self, text: str) -> bool:
|
|
374
|
+
text = self._sanitize_paste_text(text)
|
|
375
|
+
if result := self._replace_via_keyboard(text, *self.selection):
|
|
376
|
+
self.move_cursor(result.end_location)
|
|
377
|
+
return True
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
async def _on_paste(self, event) -> None:
|
|
381
|
+
"""Handle text paste events and terminal image-clipboard no-ops."""
|
|
382
|
+
if not event.text:
|
|
383
|
+
event.prevent_default()
|
|
384
|
+
if not self.read_only:
|
|
385
|
+
self._insert_clipboard_image_paste()
|
|
386
|
+
event.stop()
|
|
387
|
+
return
|
|
388
|
+
await super()._on_paste(event)
|
|
389
|
+
|
|
390
|
+
def _insert_clipboard_image_paste(self) -> bool:
|
|
391
|
+
text = self._prepare_clipboard_image_paste_text()
|
|
392
|
+
if not text:
|
|
393
|
+
return False
|
|
394
|
+
return self._insert_paste_text(text)
|
|
395
|
+
|
|
396
|
+
def action_paste(self) -> None:
|
|
397
|
+
"""Paste text or a platform clipboard image into the chat input."""
|
|
398
|
+
if self.read_only:
|
|
399
|
+
return
|
|
400
|
+
if not self._insert_clipboard_image_paste():
|
|
401
|
+
super().action_paste()
|
|
402
|
+
|
|
403
|
+
async def _on_key(self, event) -> None:
|
|
404
|
+
# Prompt history navigation: up on first line, down on last line.
|
|
405
|
+
# Skip history when suggestion list is active — arrows navigate suggestions instead
|
|
406
|
+
if (
|
|
407
|
+
event.key in _HISTORY_PREVIOUS_KEYS
|
|
408
|
+
and self.cursor_location[0] == 0
|
|
409
|
+
and not self._suggestions_active
|
|
410
|
+
and await self._browse_history(-1)
|
|
411
|
+
):
|
|
412
|
+
event.stop()
|
|
413
|
+
event.prevent_default()
|
|
414
|
+
return
|
|
415
|
+
if event.key in _HISTORY_NEXT_KEYS and self._history.index != -1 and not self._suggestions_active:
|
|
416
|
+
last_line = self.document.line_count - 1
|
|
417
|
+
if self.cursor_location[0] == last_line:
|
|
418
|
+
if await self._browse_history(1):
|
|
419
|
+
event.stop()
|
|
420
|
+
event.prevent_default()
|
|
421
|
+
return
|
|
422
|
+
if event.key == "ctrl+o":
|
|
423
|
+
event.stop()
|
|
424
|
+
event.prevent_default()
|
|
425
|
+
self.post_message(self.EditorRequested())
|
|
426
|
+
return
|
|
427
|
+
# Shell mode trigger: "!" or fullwidth "!" (U+FF01) at start of empty input
|
|
428
|
+
if event.character in ("!", "\uff01") and not self.text and self.cursor_location == (0, 0):
|
|
429
|
+
event.stop()
|
|
430
|
+
event.prevent_default()
|
|
431
|
+
self.post_message(self.ShellModeRequested())
|
|
432
|
+
return
|
|
433
|
+
if event.key == "escape":
|
|
434
|
+
self.post_message(self.ShellModeCancelled())
|
|
435
|
+
if event.key in ("ctrl+j", "shift+enter"):
|
|
436
|
+
# Insert newline
|
|
437
|
+
event.stop()
|
|
438
|
+
event.prevent_default()
|
|
439
|
+
start, end = self.selection
|
|
440
|
+
if self._replace_via_keyboard("\n", start, end):
|
|
441
|
+
self.scroll_cursor_visible()
|
|
442
|
+
return
|
|
443
|
+
if event.key == "enter":
|
|
444
|
+
# Submit (suppressed when read_only / locked)
|
|
445
|
+
event.stop()
|
|
446
|
+
event.prevent_default()
|
|
447
|
+
if self.read_only:
|
|
448
|
+
return
|
|
449
|
+
text = self.text.strip()
|
|
450
|
+
if text or self._suggestions_active:
|
|
451
|
+
self._reset_history_browsing()
|
|
452
|
+
self.post_message(self.Submitted(text))
|
|
453
|
+
return
|
|
454
|
+
await super()._on_key(event)
|
|
455
|
+
# After character insertion, check for /, @, #, $ triggers.
|
|
456
|
+
# Accept fullwidth variants (U+FF0F, U+FF20, U+FF03, U+FF04) for CJK keyboards.
|
|
457
|
+
if event.character in ("/", "\uff0f") and self.text in ("/", "\uff0f") and self.cursor_location == (0, 1):
|
|
458
|
+
self.post_message(self.SlashTrigger())
|
|
459
|
+
elif event.character in ("@", "\uff20") and (self.text.endswith("@") or self.text.endswith("\uff20")):
|
|
460
|
+
# Trigger after whitespace/start/CJK: "@", "some text @", or "你好@".
|
|
461
|
+
txt = self.text
|
|
462
|
+
at_pos = len(txt) - 1
|
|
463
|
+
if _is_file_trigger_boundary(txt, at_pos):
|
|
464
|
+
self.post_message(self.FileTrigger())
|
|
465
|
+
elif event.character in ("#", "\uff03") and self.text in ("#", "\uff03") and self.cursor_location == (0, 1):
|
|
466
|
+
self.post_message(self.AgentTrigger())
|
|
467
|
+
elif event.character in ("$", "\uff04") and self.text in ("$", "\uff04") and self.cursor_location == (0, 1):
|
|
468
|
+
self.post_message(self.ModelTrigger())
|
|
469
|
+
|
|
470
|
+
def add_to_history(self, text: str, *, session_id: str | None = None) -> None:
|
|
471
|
+
"""Append *text* to input history (deduplicates consecutive)."""
|
|
472
|
+
self._history.append(text)
|
|
473
|
+
self._history_writer.enqueue(
|
|
474
|
+
text,
|
|
475
|
+
session_id=session_id,
|
|
476
|
+
instance_id=self._instance_id,
|
|
477
|
+
cwd=self._paste_cwd,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
async def on_unmount(self) -> None:
|
|
481
|
+
"""Best-effort drain persisted prompt history during TUI shutdown."""
|
|
482
|
+
await self._history_writer.close()
|
|
483
|
+
|
|
484
|
+
async def load_prompt_history(self, *, max_entries: int) -> list[str]:
|
|
485
|
+
"""Load global MRU entries after making this instance's queued writes visible."""
|
|
486
|
+
await self._history_writer.flush()
|
|
487
|
+
return await asyncio.to_thread(load_history, max_entries=max_entries)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class _InputBarButton(NonSelectableTextMixin, Button):
|
|
491
|
+
"""Input action button excluded from terminal drag selection and copy."""
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
class InputBar(Widget):
|
|
495
|
+
"""Multi-line input bar with Send/Stop button.
|
|
496
|
+
|
|
497
|
+
- Enter or clicking Send submits the message.
|
|
498
|
+
- Ctrl+J inserts a newline for multi-line input.
|
|
499
|
+
- When agent is running, button becomes Stop (fires ``InterruptRequested``).
|
|
500
|
+
"""
|
|
501
|
+
|
|
502
|
+
DEFAULT_CSS = """
|
|
503
|
+
InputBar {
|
|
504
|
+
height: auto;
|
|
505
|
+
max-height: 9;
|
|
506
|
+
padding: 0;
|
|
507
|
+
}
|
|
508
|
+
InputBar > Horizontal {
|
|
509
|
+
height: auto;
|
|
510
|
+
max-height: 9;
|
|
511
|
+
border: round $tui-border-accent $border-opacity;
|
|
512
|
+
}
|
|
513
|
+
InputBar > Horizontal > #editor-btn {
|
|
514
|
+
width: 3;
|
|
515
|
+
min-width: 3;
|
|
516
|
+
height: 1;
|
|
517
|
+
border: none;
|
|
518
|
+
padding: 0;
|
|
519
|
+
content-align: center middle;
|
|
520
|
+
background: transparent;
|
|
521
|
+
color: $accent;
|
|
522
|
+
text-style: bold;
|
|
523
|
+
}
|
|
524
|
+
InputBar > Horizontal > #editor-btn:hover,
|
|
525
|
+
InputBar > Horizontal > #editor-btn:focus,
|
|
526
|
+
InputBar > Horizontal > #editor-btn.-active {
|
|
527
|
+
background: $foreground;
|
|
528
|
+
color: $background;
|
|
529
|
+
}
|
|
530
|
+
InputBar > Horizontal > _ChatTextArea {
|
|
531
|
+
width: 1fr;
|
|
532
|
+
min-width: 4;
|
|
533
|
+
height: auto;
|
|
534
|
+
max-height: 7;
|
|
535
|
+
background: $foreground 4%;
|
|
536
|
+
padding: 0 0 0 1;
|
|
537
|
+
scrollbar-background: $foreground 12%;
|
|
538
|
+
scrollbar-background-hover: $foreground 16%;
|
|
539
|
+
scrollbar-background-active: $foreground 20%;
|
|
540
|
+
}
|
|
541
|
+
InputBar > Horizontal > #btn-group {
|
|
542
|
+
width: auto;
|
|
543
|
+
height: 1;
|
|
544
|
+
align: left middle;
|
|
545
|
+
padding: 0 1;
|
|
546
|
+
}
|
|
547
|
+
InputBar > Horizontal > #btn-group > Button {
|
|
548
|
+
height: 1;
|
|
549
|
+
border: none;
|
|
550
|
+
padding: 0 1;
|
|
551
|
+
background: $accent;
|
|
552
|
+
color: $button-color-foreground;
|
|
553
|
+
}
|
|
554
|
+
InputBar > Horizontal > #btn-group > #send-btn {
|
|
555
|
+
width: auto;
|
|
556
|
+
min-width: 0;
|
|
557
|
+
}
|
|
558
|
+
InputBar > Horizontal > #btn-group > Button:hover,
|
|
559
|
+
InputBar > Horizontal > #btn-group > Button:focus,
|
|
560
|
+
InputBar > Horizontal > #btn-group > Button.-active {
|
|
561
|
+
background: $foreground;
|
|
562
|
+
color: $background;
|
|
563
|
+
}
|
|
564
|
+
InputBar > Horizontal > #btn-group > #send-btn.-stop {
|
|
565
|
+
background: $error;
|
|
566
|
+
}
|
|
567
|
+
InputBar > Horizontal > #btn-group > #new-btn {
|
|
568
|
+
visibility: hidden;
|
|
569
|
+
width: 0;
|
|
570
|
+
min-width: 0;
|
|
571
|
+
margin: 0;
|
|
572
|
+
}
|
|
573
|
+
InputBar.-locked > Horizontal > #editor-btn {
|
|
574
|
+
color: $text-muted;
|
|
575
|
+
}
|
|
576
|
+
InputBar.-locked > Horizontal > _ChatTextArea {
|
|
577
|
+
color: $text-muted;
|
|
578
|
+
}
|
|
579
|
+
"""
|
|
580
|
+
|
|
581
|
+
agent_running: reactive[bool] = reactive(False)
|
|
582
|
+
agent_loading: reactive[bool] = reactive(False)
|
|
583
|
+
locked: reactive[bool] = reactive(False)
|
|
584
|
+
has_messages: reactive[bool] = reactive(False)
|
|
585
|
+
shell_mode: reactive[bool] = reactive(False)
|
|
586
|
+
# ``_retry_label`` is a semantic plain attribute, so label-only transitions
|
|
587
|
+
# (e.g. Retry -> Continue while retry_mode stays True) reach the interaction
|
|
588
|
+
# refresh via the always-firing watcher; the refresh itself dedupes.
|
|
589
|
+
retry_mode: reactive[bool] = reactive(False, always_update=True)
|
|
590
|
+
retry_pending: reactive[bool] = reactive(False)
|
|
591
|
+
_retry_label: InputLabel
|
|
592
|
+
|
|
593
|
+
class UserSubmitted(Message):
|
|
594
|
+
"""Posted when the user submits text."""
|
|
595
|
+
|
|
596
|
+
def __init__(self, text: str) -> None:
|
|
597
|
+
self.text = text
|
|
598
|
+
super().__init__()
|
|
599
|
+
|
|
600
|
+
class InterruptRequested(Message):
|
|
601
|
+
"""Posted when the user clicks Stop."""
|
|
602
|
+
|
|
603
|
+
class RetryRequested(Message):
|
|
604
|
+
"""Posted when the user clicks Retry / Continue.
|
|
605
|
+
|
|
606
|
+
Carries the current input-bar text (may be empty). When
|
|
607
|
+
non-empty, the engine uses it as the mid-turn continuation
|
|
608
|
+
prompt instead of the default ``"continue"`` placeholder.
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
def __init__(self, text: str = "") -> None:
|
|
612
|
+
self.text = text
|
|
613
|
+
super().__init__()
|
|
614
|
+
|
|
615
|
+
class ShellModeRequested(Message):
|
|
616
|
+
"""Posted when the user requests shell mode from the input."""
|
|
617
|
+
|
|
618
|
+
class EditorRequested(Message):
|
|
619
|
+
"""Posted when the user requests the full-screen message editor."""
|
|
620
|
+
|
|
621
|
+
class NewSessionRequested(Message):
|
|
622
|
+
"""Posted when the user clicks New."""
|
|
623
|
+
|
|
624
|
+
class SlashTriggered(Message):
|
|
625
|
+
"""Posted when ``/`` is typed at start of empty input."""
|
|
626
|
+
|
|
627
|
+
class FileTriggered(Message):
|
|
628
|
+
"""Posted when ``@`` is typed at a file-mention boundary."""
|
|
629
|
+
|
|
630
|
+
class AgentTriggered(Message):
|
|
631
|
+
"""Posted when ``#`` is typed at start of empty input."""
|
|
632
|
+
|
|
633
|
+
class ModelTriggered(Message):
|
|
634
|
+
"""Posted when ``$`` is typed at start of empty input."""
|
|
635
|
+
|
|
636
|
+
class SuggestionNavigate(Message):
|
|
637
|
+
"""Arrow key pressed while suggestions are active."""
|
|
638
|
+
|
|
639
|
+
def __init__(self, direction: str) -> None:
|
|
640
|
+
self.direction = direction
|
|
641
|
+
super().__init__()
|
|
642
|
+
|
|
643
|
+
class SuggestionSelect(Message):
|
|
644
|
+
"""Tab or Enter pressed while suggestions are active."""
|
|
645
|
+
|
|
646
|
+
def __init__(self, execute: bool = False) -> None:
|
|
647
|
+
self.execute = execute
|
|
648
|
+
super().__init__()
|
|
649
|
+
|
|
650
|
+
class SuggestionDismiss(Message):
|
|
651
|
+
"""Escape pressed while suggestions are active."""
|
|
652
|
+
|
|
653
|
+
class TextChanged(Message):
|
|
654
|
+
"""Text changed while suggestions are active."""
|
|
655
|
+
|
|
656
|
+
def __init__(self, text: str) -> None:
|
|
657
|
+
self.text = text
|
|
658
|
+
super().__init__()
|
|
659
|
+
|
|
660
|
+
class LockedChanged(Message):
|
|
661
|
+
"""Posted when queued-injection locking changes."""
|
|
662
|
+
|
|
663
|
+
def __init__(self, locked: bool) -> None:
|
|
664
|
+
self.locked = locked
|
|
665
|
+
super().__init__()
|
|
666
|
+
|
|
667
|
+
def __init__(
|
|
668
|
+
self,
|
|
669
|
+
placeholder: str | Content | None = None,
|
|
670
|
+
*,
|
|
671
|
+
locale_controller: LocaleController | None = None,
|
|
672
|
+
**kwargs: Any,
|
|
673
|
+
) -> None:
|
|
674
|
+
self._draft_revision = 0
|
|
675
|
+
self._locale_controller = locale_controller
|
|
676
|
+
self._uses_default_placeholder = placeholder is None
|
|
677
|
+
self._placeholder = placeholder if placeholder is not None else _build_prompt_placeholder(self._localizer())
|
|
678
|
+
self._suggestions_active: bool = False
|
|
679
|
+
# Session restore/workspace updates replace this before normal use; this covers early paste during startup.
|
|
680
|
+
self._paste_cwd: str = safe_getcwd()
|
|
681
|
+
self._clipboard_image_dir: Path | None = None
|
|
682
|
+
self._spin_timer: Timer | None = None
|
|
683
|
+
self._lock_label: InputLabel = ""
|
|
684
|
+
self._lock_stop_style: bool = False
|
|
685
|
+
self._lock_spin_prompt: bool = False
|
|
686
|
+
self._interaction_signature: tuple[object, ...] | None = None
|
|
687
|
+
self._button_geometry_dirty = False
|
|
688
|
+
self._retry_label = INPUT_CONTINUE.bind()
|
|
689
|
+
self._inject_placeholder = _build_inject_placeholder(self._localizer())
|
|
690
|
+
super().__init__(**kwargs)
|
|
691
|
+
|
|
692
|
+
def set_suggestions_active(self, active: bool, mode: str = "") -> None:
|
|
693
|
+
"""Set suggestion-active state on both InputBar and _ChatTextArea."""
|
|
694
|
+
self._suggestions_active = active
|
|
695
|
+
self._suggestion_mode = mode
|
|
696
|
+
self.query_one("#chat-input", _ChatTextArea)._suggestions_active = active
|
|
697
|
+
|
|
698
|
+
@property
|
|
699
|
+
def suggestions_active(self) -> bool:
|
|
700
|
+
"""Whether the completion suggestion list is currently using input keys."""
|
|
701
|
+
return self._suggestions_active
|
|
702
|
+
|
|
703
|
+
def set_paste_cwd(self, cwd: str) -> None:
|
|
704
|
+
"""Set the cwd used to resolve relative pasted/dropped image paths."""
|
|
705
|
+
self._paste_cwd = cwd
|
|
706
|
+
if self.is_mounted:
|
|
707
|
+
self.query_one("#chat-input", _ChatTextArea).set_paste_cwd(cwd)
|
|
708
|
+
|
|
709
|
+
def set_clipboard_image_dir(self, directory: str | Path | None) -> None:
|
|
710
|
+
"""Set the session-scoped directory used for clipboard image attachments."""
|
|
711
|
+
self._clipboard_image_dir = Path(directory) if directory is not None else None
|
|
712
|
+
if self.is_mounted:
|
|
713
|
+
self.query_one("#chat-input", _ChatTextArea).set_clipboard_image_dir(self._clipboard_image_dir)
|
|
714
|
+
|
|
715
|
+
def compose(self) -> ComposeResult:
|
|
716
|
+
with Horizontal():
|
|
717
|
+
yield _InputBarButton(Text(">"), id="editor-btn")
|
|
718
|
+
yield _ChatTextArea(
|
|
719
|
+
id="chat-input",
|
|
720
|
+
compact=True,
|
|
721
|
+
soft_wrap=True,
|
|
722
|
+
show_line_numbers=False,
|
|
723
|
+
)
|
|
724
|
+
with Horizontal(id="btn-group"):
|
|
725
|
+
yield _InputBarButton(Text(self._render_label(INPUT_SEND.bind())), id="send-btn")
|
|
726
|
+
yield _InputBarButton(
|
|
727
|
+
Text(self._render_label(INPUT_NEW.bind())),
|
|
728
|
+
id="new-btn",
|
|
729
|
+
variant="primary",
|
|
730
|
+
flat=True,
|
|
731
|
+
tooltip=Text(self._render_label(INPUT_NEW_DESCRIPTION.bind())),
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
def on_mount(self) -> None:
|
|
735
|
+
if self._locale_controller is not None:
|
|
736
|
+
self._locale_controller.register_surface(self)
|
|
737
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
738
|
+
ta.set_paste_cwd(self._paste_cwd)
|
|
739
|
+
ta.set_clipboard_image_dir(self._clipboard_image_dir)
|
|
740
|
+
ta.placeholder = self._placeholder
|
|
741
|
+
ta.set_right_placeholder(self._render_newline_hint())
|
|
742
|
+
self.focus_input()
|
|
743
|
+
# Pin the group to the current visible button widths. Subsequent state
|
|
744
|
+
# changes remap only this fixed InputBar row, never MainScreen layout.
|
|
745
|
+
self._set_btn_label(self._render_label(INPUT_SEND.bind()), defer_geometry=True)
|
|
746
|
+
self._refresh_interaction_state()
|
|
747
|
+
|
|
748
|
+
def on_unmount(self) -> None:
|
|
749
|
+
if self._locale_controller is not None:
|
|
750
|
+
self._locale_controller.unregister_surface(self)
|
|
751
|
+
|
|
752
|
+
def refresh_localization(self) -> None:
|
|
753
|
+
"""Retranslate placeholders and semantic button state in place."""
|
|
754
|
+
if self._uses_default_placeholder:
|
|
755
|
+
self._placeholder = _build_prompt_placeholder(self._localizer())
|
|
756
|
+
self._inject_placeholder = _build_inject_placeholder(self._localizer())
|
|
757
|
+
self._interaction_signature = None
|
|
758
|
+
if not self.is_mounted:
|
|
759
|
+
return
|
|
760
|
+
text_area = self.query_one("#chat-input", _ChatTextArea)
|
|
761
|
+
text_area.placeholder = self._inject_placeholder if self.agent_running else self._placeholder
|
|
762
|
+
text_area.set_right_placeholder(self._render_newline_hint())
|
|
763
|
+
self._set_btn_label(
|
|
764
|
+
self._render_label(INPUT_NEW.bind()),
|
|
765
|
+
button_id="new-btn",
|
|
766
|
+
defer_geometry=True,
|
|
767
|
+
)
|
|
768
|
+
self.query_one("#new-btn", Button).tooltip = Text(self._render_label(INPUT_NEW_DESCRIPTION.bind()))
|
|
769
|
+
self._refresh_interaction_state()
|
|
770
|
+
|
|
771
|
+
def _localizer(self) -> Localizer | None:
|
|
772
|
+
controller = self._locale_controller
|
|
773
|
+
if controller is None:
|
|
774
|
+
return None
|
|
775
|
+
return controller.localizer
|
|
776
|
+
|
|
777
|
+
def _render_label(self, label: InputLabel) -> str:
|
|
778
|
+
if isinstance(label, str):
|
|
779
|
+
return label
|
|
780
|
+
localizer = self._localizer()
|
|
781
|
+
if localizer is None:
|
|
782
|
+
return format_message(label)
|
|
783
|
+
return render_str(localizer, label)
|
|
784
|
+
|
|
785
|
+
def _render_newline_hint(self) -> str:
|
|
786
|
+
return self._render_label(_INPUT_NEWLINE_HINT.bind(shortcut="Ctrl+J"))
|
|
787
|
+
|
|
788
|
+
def _update_new_btn_visibility(self) -> None:
|
|
789
|
+
"""Show New when Chat has history and execution is idle."""
|
|
790
|
+
btn = self.query_one("#new-btn", Button)
|
|
791
|
+
# Admission can outlive an interrupted run's unwind. Reflect the
|
|
792
|
+
# navigation guard instead of leaving a seemingly actionable button.
|
|
793
|
+
btn.disabled = self.retry_pending
|
|
794
|
+
visible = self.has_messages and not self.retry_mode and not self.agent_running and not self.agent_loading
|
|
795
|
+
width = Scalar.from_number(Text(str(btn.label)).cell_len + 4 if visible else 0)
|
|
796
|
+
changed = btn.styles.set_rule("width", width)
|
|
797
|
+
changed = btn.styles.set_rule("min_width", width) or changed
|
|
798
|
+
changed = btn.styles.set_rule("margin", Spacing(0, 0, 0, 1 if visible else 0)) or changed
|
|
799
|
+
changed = btn.styles.set_rule("padding", Spacing(0, 1, 0, 1) if visible else Spacing.all(0)) or changed
|
|
800
|
+
changed = btn.styles.set_rule("visibility", "visible" if visible else "hidden") or changed
|
|
801
|
+
if not visible and btn.has_focus:
|
|
802
|
+
btn.blur()
|
|
803
|
+
if changed:
|
|
804
|
+
self._mark_button_geometry_dirty()
|
|
805
|
+
self._flush_button_geometry()
|
|
806
|
+
|
|
807
|
+
def watch_agent_running(self, running: bool) -> None:
|
|
808
|
+
if running:
|
|
809
|
+
self.retry_mode = False
|
|
810
|
+
self._refresh_interaction_state()
|
|
811
|
+
|
|
812
|
+
def watch_agent_loading(self, loading: bool) -> None:
|
|
813
|
+
self._refresh_interaction_state()
|
|
814
|
+
|
|
815
|
+
def watch_locked(self, locked: bool) -> None:
|
|
816
|
+
self.post_message(self.LockedChanged(locked))
|
|
817
|
+
self._refresh_interaction_state()
|
|
818
|
+
|
|
819
|
+
def watch_retry_mode(self, retry: bool) -> None:
|
|
820
|
+
self._refresh_interaction_state()
|
|
821
|
+
|
|
822
|
+
def watch_retry_pending(self, pending: bool) -> None:
|
|
823
|
+
self._refresh_interaction_state()
|
|
824
|
+
|
|
825
|
+
def _set_btn_label(
|
|
826
|
+
self,
|
|
827
|
+
label: str,
|
|
828
|
+
*,
|
|
829
|
+
button_id: str = "send-btn",
|
|
830
|
+
defer_geometry: bool = False,
|
|
831
|
+
) -> None:
|
|
832
|
+
"""Set a cell-aware natural label width without escalating screen layout."""
|
|
833
|
+
btn = self.query_one(f"#{button_id}", Button)
|
|
834
|
+
if str(btn.label) != label:
|
|
835
|
+
btn.label = Text(label)
|
|
836
|
+
changed = False
|
|
837
|
+
if button_id == "send-btn":
|
|
838
|
+
width = Scalar.from_number(Text(label).cell_len + 4)
|
|
839
|
+
changed = btn.styles.set_rule("width", width)
|
|
840
|
+
changed = btn.styles.set_rule("min_width", width) or changed
|
|
841
|
+
else:
|
|
842
|
+
changed = True
|
|
843
|
+
if changed:
|
|
844
|
+
self._mark_button_geometry_dirty()
|
|
845
|
+
if not defer_geometry:
|
|
846
|
+
self._flush_button_geometry()
|
|
847
|
+
|
|
848
|
+
def _mark_button_geometry_dirty(self) -> None:
|
|
849
|
+
"""Invalidate only the InputBar row affected by button geometry."""
|
|
850
|
+
self._button_geometry_dirty = True
|
|
851
|
+
if self.is_mounted:
|
|
852
|
+
group = self.query_one("#btn-group", Horizontal)
|
|
853
|
+
self._pin_button_group_width(group)
|
|
854
|
+
group._clear_arrangement_cache()
|
|
855
|
+
row = group.parent
|
|
856
|
+
if isinstance(row, Widget):
|
|
857
|
+
row._clear_arrangement_cache()
|
|
858
|
+
|
|
859
|
+
def _flush_button_geometry(self) -> None:
|
|
860
|
+
"""Apply cache-local geometry, deferring while an overlay is active."""
|
|
861
|
+
if not self._button_geometry_dirty or not self.is_mounted or self.app.screen is not self.screen:
|
|
862
|
+
return
|
|
863
|
+
group = self.query_one("#btn-group", Horizontal)
|
|
864
|
+
group._clear_arrangement_cache()
|
|
865
|
+
row = group.parent
|
|
866
|
+
if not isinstance(row, Widget):
|
|
867
|
+
return
|
|
868
|
+
row._clear_arrangement_cache()
|
|
869
|
+
resync_compositor_regions(row)
|
|
870
|
+
self._button_geometry_dirty = False
|
|
871
|
+
|
|
872
|
+
def _pin_button_group_width(self, group: Horizontal) -> None:
|
|
873
|
+
"""Content-size the group from its currently visible buttons."""
|
|
874
|
+
send = self.query_one("#send-btn", Button)
|
|
875
|
+
new = self.query_one("#new-btn", Button)
|
|
876
|
+
send_width = Text(str(send.label)).cell_len + 4
|
|
877
|
+
new_visible = new.styles.visibility == "visible"
|
|
878
|
+
new_width = Text(str(new.label)).cell_len + 4 if new_visible else 0
|
|
879
|
+
inter_button_margin = 1 if new_visible else 0
|
|
880
|
+
width = send_width + new_width + inter_button_margin + _BUTTON_GROUP_HORIZONTAL_PADDING
|
|
881
|
+
group.styles.set_rule("width", Scalar.from_number(width))
|
|
882
|
+
|
|
883
|
+
def sync_deferred_button_geometry(self) -> None:
|
|
884
|
+
"""Apply geometry deferred while an overlay covered the owning screen."""
|
|
885
|
+
self._flush_button_geometry()
|
|
886
|
+
|
|
887
|
+
def _set_prompt_spinner(self, active: bool) -> None:
|
|
888
|
+
if active:
|
|
889
|
+
if self._spin_timer is None:
|
|
890
|
+
self._spin_timer = self.set_interval(0.08, self._spin_prompt)
|
|
891
|
+
return
|
|
892
|
+
if self._spin_timer is not None:
|
|
893
|
+
self._spin_timer.stop()
|
|
894
|
+
self._spin_timer = None
|
|
895
|
+
self.query_one("#editor-btn", Button).label = Text(">")
|
|
896
|
+
|
|
897
|
+
def _request_editor(self) -> None:
|
|
898
|
+
"""Request the prompt editor through the shared keyboard/button path."""
|
|
899
|
+
if self.locked or self.shell_mode:
|
|
900
|
+
return
|
|
901
|
+
# A mouse press focuses the prompt button before this handler runs.
|
|
902
|
+
# Restore the chat input first so closing the editor returns focus and
|
|
903
|
+
# its cursor to the draft instead of leaving focus on the button.
|
|
904
|
+
self.focus_input()
|
|
905
|
+
if self._suggestions_active:
|
|
906
|
+
self.post_message(self.SuggestionDismiss())
|
|
907
|
+
self.post_message(self.EditorRequested())
|
|
908
|
+
|
|
909
|
+
def _refresh_interaction_state(self) -> None:
|
|
910
|
+
"""Derive the complete input/button state from the current mode flags."""
|
|
911
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
912
|
+
btn = self.query_one("#send-btn", Button)
|
|
913
|
+
self.query_one("#editor-btn", Button).disabled = self.locked or self.shell_mode
|
|
914
|
+
|
|
915
|
+
signature = (
|
|
916
|
+
self.locked,
|
|
917
|
+
self._lock_label,
|
|
918
|
+
self._lock_stop_style,
|
|
919
|
+
self._lock_spin_prompt,
|
|
920
|
+
self.agent_running,
|
|
921
|
+
self.agent_loading,
|
|
922
|
+
self.retry_mode,
|
|
923
|
+
self.retry_pending,
|
|
924
|
+
self._retry_label,
|
|
925
|
+
bool(ta.text.strip()),
|
|
926
|
+
self.has_messages,
|
|
927
|
+
self.shell_mode,
|
|
928
|
+
)
|
|
929
|
+
if signature == self._interaction_signature:
|
|
930
|
+
return
|
|
931
|
+
self._interaction_signature = signature
|
|
932
|
+
|
|
933
|
+
# A hard lock (queued injection) takes precedence over startup
|
|
934
|
+
# loading if both flags are ever set.
|
|
935
|
+
if self.locked:
|
|
936
|
+
self.add_class("-locked")
|
|
937
|
+
# ``read_only`` blocks edits; ``disabled`` also suppresses cursor
|
|
938
|
+
# movement and history navigation while the queued injection is pending.
|
|
939
|
+
ta.read_only = True
|
|
940
|
+
ta.disabled = True
|
|
941
|
+
self._set_btn_label(self._render_label(self._lock_label or INPUT_QUEUED.bind()), defer_geometry=True)
|
|
942
|
+
btn.set_class(self._lock_stop_style, "-stop")
|
|
943
|
+
btn.disabled = True
|
|
944
|
+
self._set_prompt_spinner(self._lock_spin_prompt)
|
|
945
|
+
self._update_new_btn_visibility()
|
|
946
|
+
return
|
|
947
|
+
|
|
948
|
+
self.remove_class("-locked")
|
|
949
|
+
ta.disabled = False
|
|
950
|
+
ta.read_only = False
|
|
951
|
+
ta.show_cursor = True
|
|
952
|
+
self._set_prompt_spinner(False)
|
|
953
|
+
|
|
954
|
+
if self.agent_running:
|
|
955
|
+
self._set_btn_label(self._render_label(INPUT_INTERRUPT.bind()), defer_geometry=True)
|
|
956
|
+
btn.add_class("-stop")
|
|
957
|
+
btn.disabled = False
|
|
958
|
+
ta.placeholder = self._inject_placeholder
|
|
959
|
+
else:
|
|
960
|
+
btn.remove_class("-stop")
|
|
961
|
+
ta.placeholder = self._placeholder
|
|
962
|
+
retry = self.retry_mode
|
|
963
|
+
label = self._retry_label if retry else INPUT_SEND.bind()
|
|
964
|
+
self._set_btn_label(self._render_label(label), defer_geometry=True)
|
|
965
|
+
btn.disabled = self.retry_pending or self.agent_loading or (not retry and not bool(ta.text.strip()))
|
|
966
|
+
|
|
967
|
+
self._update_new_btn_visibility()
|
|
968
|
+
|
|
969
|
+
def watch_has_messages(self, _has_messages: bool) -> None:
|
|
970
|
+
self._refresh_interaction_state()
|
|
971
|
+
|
|
972
|
+
def watch_shell_mode(self, shell_mode: bool) -> None:
|
|
973
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
974
|
+
if not shell_mode:
|
|
975
|
+
# Restore normal state when exiting shell mode
|
|
976
|
+
ta.clear()
|
|
977
|
+
self._refresh_interaction_state()
|
|
978
|
+
|
|
979
|
+
def on__chat_text_area_shell_mode_requested(self, event: _ChatTextArea.ShellModeRequested) -> None:
|
|
980
|
+
event.stop()
|
|
981
|
+
if self.agent_running or self.locked:
|
|
982
|
+
return
|
|
983
|
+
self.post_message(self.ShellModeRequested())
|
|
984
|
+
|
|
985
|
+
def on__chat_text_area_editor_requested(self, event: _ChatTextArea.EditorRequested) -> None:
|
|
986
|
+
"""Forward editor requests when the chat draft remains writable."""
|
|
987
|
+
event.stop()
|
|
988
|
+
self._request_editor()
|
|
989
|
+
|
|
990
|
+
def on__chat_text_area_shell_mode_cancelled(self, event: _ChatTextArea.ShellModeCancelled) -> None:
|
|
991
|
+
event.stop()
|
|
992
|
+
if self._suggestions_active:
|
|
993
|
+
self.post_message(self.SuggestionDismiss())
|
|
994
|
+
|
|
995
|
+
def on__chat_text_area_slash_trigger(self, event: _ChatTextArea.SlashTrigger) -> None:
|
|
996
|
+
event.stop()
|
|
997
|
+
if self.locked or self.shell_mode:
|
|
998
|
+
return
|
|
999
|
+
self.post_message(self.SlashTriggered())
|
|
1000
|
+
|
|
1001
|
+
def on__chat_text_area_file_trigger(self, event: _ChatTextArea.FileTrigger) -> None:
|
|
1002
|
+
event.stop()
|
|
1003
|
+
if self.locked or self.shell_mode:
|
|
1004
|
+
return
|
|
1005
|
+
self.post_message(self.FileTriggered())
|
|
1006
|
+
|
|
1007
|
+
def on__chat_text_area_agent_trigger(self, event: _ChatTextArea.AgentTrigger) -> None:
|
|
1008
|
+
event.stop()
|
|
1009
|
+
if self.agent_running or self.agent_loading or self.locked or self.shell_mode:
|
|
1010
|
+
return
|
|
1011
|
+
self.post_message(self.AgentTriggered())
|
|
1012
|
+
|
|
1013
|
+
def on__chat_text_area_model_trigger(self, event: _ChatTextArea.ModelTrigger) -> None:
|
|
1014
|
+
event.stop()
|
|
1015
|
+
# Same four guards the status-bar selectors use: a profile switch that
|
|
1016
|
+
# lands mid-load is dropped downstream, after the draft is already gone.
|
|
1017
|
+
if self.agent_running or self.agent_loading or self.locked or self.shell_mode:
|
|
1018
|
+
return
|
|
1019
|
+
self.post_message(self.ModelTriggered())
|
|
1020
|
+
|
|
1021
|
+
def on__chat_text_area_submitted(self, event: _ChatTextArea.Submitted) -> None:
|
|
1022
|
+
"""Forward Enter-submit, selecting the active suggestion when applicable."""
|
|
1023
|
+
event.stop()
|
|
1024
|
+
if not self.display:
|
|
1025
|
+
return
|
|
1026
|
+
if self._suggestions_active and self._suggestion_mode == "history":
|
|
1027
|
+
self.post_message(self.SuggestionSelect(execute=True))
|
|
1028
|
+
return
|
|
1029
|
+
if self.retry_pending:
|
|
1030
|
+
return
|
|
1031
|
+
if self.agent_loading:
|
|
1032
|
+
self._refresh_interaction_state()
|
|
1033
|
+
return
|
|
1034
|
+
if self._suggestions_active:
|
|
1035
|
+
if self._suggestion_mode in ("files", "agents", "models", "commands", "subcommands"):
|
|
1036
|
+
self.post_message(self.SuggestionSelect(execute=True))
|
|
1037
|
+
return
|
|
1038
|
+
self.post_message(self.SuggestionDismiss())
|
|
1039
|
+
# In retry_mode a plain Enter-submit (no slash command) is treated
|
|
1040
|
+
# the same as clicking Continue/Retry: the typed text rides along
|
|
1041
|
+
# as a mid-turn note rather than starting a brand-new turn.
|
|
1042
|
+
# Slash commands always route through ``UserSubmitted`` so the
|
|
1043
|
+
# screen's dispatcher handles them.
|
|
1044
|
+
is_slash_command = is_slash_command_candidate(event.text)
|
|
1045
|
+
if self.retry_mode and not self.agent_running and not is_slash_command:
|
|
1046
|
+
text = event.text.strip()
|
|
1047
|
+
self.post_message(self.RetryRequested(text=text))
|
|
1048
|
+
self.query_one("#chat-input", _ChatTextArea).clear()
|
|
1049
|
+
self._refresh_interaction_state()
|
|
1050
|
+
return
|
|
1051
|
+
self.post_message(self.UserSubmitted(event.text))
|
|
1052
|
+
if not self.agent_running or is_slash_command:
|
|
1053
|
+
self.query_one("#chat-input", _ChatTextArea).clear()
|
|
1054
|
+
self._refresh_interaction_state()
|
|
1055
|
+
|
|
1056
|
+
def on_text_area_changed(self, event: TextArea.Changed) -> None:
|
|
1057
|
+
"""Forward text changes when suggestions are active."""
|
|
1058
|
+
self._draft_revision += 1
|
|
1059
|
+
if self._suggestions_active:
|
|
1060
|
+
self.post_message(self.TextChanged(event.text_area.text))
|
|
1061
|
+
self._refresh_interaction_state()
|
|
1062
|
+
|
|
1063
|
+
def on_key(self, event) -> None:
|
|
1064
|
+
"""Intercept arrow keys and Tab when suggestions are active."""
|
|
1065
|
+
if not self._suggestions_active:
|
|
1066
|
+
return
|
|
1067
|
+
if event.key == "up":
|
|
1068
|
+
event.stop()
|
|
1069
|
+
event.prevent_default()
|
|
1070
|
+
self.post_message(self.SuggestionNavigate(direction="up"))
|
|
1071
|
+
elif event.key == "down":
|
|
1072
|
+
event.stop()
|
|
1073
|
+
event.prevent_default()
|
|
1074
|
+
self.post_message(self.SuggestionNavigate(direction="down"))
|
|
1075
|
+
elif event.key == "tab":
|
|
1076
|
+
event.stop()
|
|
1077
|
+
event.prevent_default()
|
|
1078
|
+
self.post_message(self.SuggestionSelect())
|
|
1079
|
+
|
|
1080
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
1081
|
+
event.stop()
|
|
1082
|
+
if not self.display:
|
|
1083
|
+
return
|
|
1084
|
+
if event.button.id == "editor-btn":
|
|
1085
|
+
self._request_editor()
|
|
1086
|
+
return
|
|
1087
|
+
if self.locked or self.retry_pending:
|
|
1088
|
+
return # keep the admission owner stable; draft editing stays available
|
|
1089
|
+
if self.agent_loading:
|
|
1090
|
+
return
|
|
1091
|
+
if event.button.id == "new-btn":
|
|
1092
|
+
self.post_message(self.NewSessionRequested())
|
|
1093
|
+
return
|
|
1094
|
+
if self.agent_running:
|
|
1095
|
+
self.post_message(self.InterruptRequested())
|
|
1096
|
+
elif self.retry_mode:
|
|
1097
|
+
# In retry mode the button always resumes the current turn.
|
|
1098
|
+
# Any text the user typed rides along as a mid-turn note;
|
|
1099
|
+
# the engine will use it as the continuation prompt in place
|
|
1100
|
+
# of the default ``"continue"`` placeholder.
|
|
1101
|
+
self.post_message(self.RetryRequested(text=self.consume_retry_text()))
|
|
1102
|
+
else:
|
|
1103
|
+
self._do_submit()
|
|
1104
|
+
|
|
1105
|
+
def _do_submit(self) -> None:
|
|
1106
|
+
"""Submit text from the text area."""
|
|
1107
|
+
if not self.display:
|
|
1108
|
+
return
|
|
1109
|
+
if self.agent_loading or self.retry_pending:
|
|
1110
|
+
self._refresh_interaction_state()
|
|
1111
|
+
return
|
|
1112
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
1113
|
+
text = ta.text.strip()
|
|
1114
|
+
if text:
|
|
1115
|
+
ta._reset_history_browsing()
|
|
1116
|
+
self.post_message(self.UserSubmitted(text))
|
|
1117
|
+
ta.clear()
|
|
1118
|
+
self._refresh_interaction_state()
|
|
1119
|
+
|
|
1120
|
+
def _lock(self, label: InputLabel, *, stop_style: bool, spin_prompt: bool = True) -> None:
|
|
1121
|
+
"""Lock the input bar — text stays visible, input becomes read-only."""
|
|
1122
|
+
self._lock_label = label
|
|
1123
|
+
self._lock_stop_style = stop_style
|
|
1124
|
+
self._lock_spin_prompt = spin_prompt
|
|
1125
|
+
self.locked = True
|
|
1126
|
+
self._refresh_interaction_state()
|
|
1127
|
+
|
|
1128
|
+
def lock_with_text(self) -> None:
|
|
1129
|
+
"""Lock while a typed injection is queued for a running agent."""
|
|
1130
|
+
self._lock(INPUT_QUEUED.bind(), stop_style=True)
|
|
1131
|
+
|
|
1132
|
+
def _spin_prompt(self) -> None:
|
|
1133
|
+
"""Update the prompt indicator with a spinner frame."""
|
|
1134
|
+
frame = _SPINNER_FRAMES[int(time() * 8) % len(_SPINNER_FRAMES)]
|
|
1135
|
+
self.query_one("#editor-btn", Button).label = Text(frame)
|
|
1136
|
+
|
|
1137
|
+
def _unlock(self) -> None:
|
|
1138
|
+
"""Common unlock logic."""
|
|
1139
|
+
self.locked = False
|
|
1140
|
+
self._lock_label = ""
|
|
1141
|
+
self._lock_stop_style = False
|
|
1142
|
+
self._lock_spin_prompt = False
|
|
1143
|
+
self._refresh_interaction_state()
|
|
1144
|
+
|
|
1145
|
+
def unlock_and_clear(self) -> None:
|
|
1146
|
+
"""Unlock and clear the text — injection was consumed."""
|
|
1147
|
+
self._unlock()
|
|
1148
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
1149
|
+
ta.clear()
|
|
1150
|
+
self.focus_input()
|
|
1151
|
+
self._refresh_interaction_state()
|
|
1152
|
+
|
|
1153
|
+
def unlock_and_keep(self) -> None:
|
|
1154
|
+
"""Unlock but keep the text — injection was abandoned."""
|
|
1155
|
+
self._unlock()
|
|
1156
|
+
self.focus_input()
|
|
1157
|
+
self._refresh_interaction_state()
|
|
1158
|
+
|
|
1159
|
+
def focus_input(self) -> None:
|
|
1160
|
+
"""Focus the visible composer without activating a hidden Chat draft."""
|
|
1161
|
+
if self.display:
|
|
1162
|
+
self.query_one("#chat-input", _ChatTextArea).focus()
|
|
1163
|
+
|
|
1164
|
+
def snapshot_draft(self) -> InputDraftSnapshot:
|
|
1165
|
+
"""Capture the current chat draft and cursor without mutating them."""
|
|
1166
|
+
text_area = self.query_one("#chat-input", _ChatTextArea)
|
|
1167
|
+
return InputDraftSnapshot(text_area.text, text_area.cursor_location, self._draft_revision)
|
|
1168
|
+
|
|
1169
|
+
@property
|
|
1170
|
+
def draft_revision(self) -> int:
|
|
1171
|
+
"""Return the monotonic generation of the underlying chat draft."""
|
|
1172
|
+
return self._draft_revision
|
|
1173
|
+
|
|
1174
|
+
def restore_draft(self, text: str) -> bool:
|
|
1175
|
+
"""Hand *text* back to an empty composer; return whether it was written.
|
|
1176
|
+
|
|
1177
|
+
Every caller reaches this from a path that already consumed the draft —
|
|
1178
|
+
a submit, a retry, a rollback, a rejection — and the hand-back travels
|
|
1179
|
+
as a message, so the user may have started typing before it lands.
|
|
1180
|
+
``replace_draft`` would destroy that draft and move the cursor, which
|
|
1181
|
+
also breaks an in-progress IME composition, so a draft that is already
|
|
1182
|
+
there wins and the caller is told nothing was restored. Whitespace is
|
|
1183
|
+
such a draft: an indent, a newline or a single space is something the
|
|
1184
|
+
user typed, and no consume path can leave one behind — a whitespace-only
|
|
1185
|
+
composer never submits.
|
|
1186
|
+
"""
|
|
1187
|
+
if not text:
|
|
1188
|
+
return False
|
|
1189
|
+
if self.query_one("#chat-input", _ChatTextArea).text:
|
|
1190
|
+
return False
|
|
1191
|
+
self.replace_draft(text)
|
|
1192
|
+
return True
|
|
1193
|
+
|
|
1194
|
+
def replace_draft(self, text: str) -> None:
|
|
1195
|
+
"""Replace the draft, move its cursor to the end, and refresh controls."""
|
|
1196
|
+
self._draft_revision += 1
|
|
1197
|
+
text_area = self.query_one("#chat-input", _ChatTextArea)
|
|
1198
|
+
text_area._reset_history_browsing()
|
|
1199
|
+
text_area.clear()
|
|
1200
|
+
if text:
|
|
1201
|
+
text_area.insert(text)
|
|
1202
|
+
self._refresh_interaction_state()
|
|
1203
|
+
|
|
1204
|
+
def can_commit_editor_draft(self) -> bool:
|
|
1205
|
+
"""Return whether an open editor may currently replace this draft."""
|
|
1206
|
+
return not self.locked and not self.shell_mode
|
|
1207
|
+
|
|
1208
|
+
def insert_image_paste_payload(self, text: str) -> bool:
|
|
1209
|
+
"""Insert a pasted/dropped image path payload into the chat input.
|
|
1210
|
+
|
|
1211
|
+
Returns ``True`` only when *text* was recognized as supported image
|
|
1212
|
+
path input and inserted. Normal prose/code pastes are left for the
|
|
1213
|
+
original event target to handle.
|
|
1214
|
+
"""
|
|
1215
|
+
# MainScreen also guards shell mode; keep this local guard for direct InputBar callers.
|
|
1216
|
+
if self.shell_mode:
|
|
1217
|
+
return False
|
|
1218
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
1219
|
+
if ta.read_only:
|
|
1220
|
+
return False
|
|
1221
|
+
try:
|
|
1222
|
+
prepared = ta._prepare_image_path_paste_text(text)
|
|
1223
|
+
except OSError, RuntimeError, ValueError:
|
|
1224
|
+
return False
|
|
1225
|
+
if not prepared:
|
|
1226
|
+
return False
|
|
1227
|
+
# This fallback handles path-only image payloads; normal large text pastes stay on the original target.
|
|
1228
|
+
result = ta._replace_via_keyboard(prepared, *ta.selection)
|
|
1229
|
+
if not result:
|
|
1230
|
+
return False
|
|
1231
|
+
ta.move_cursor(result.end_location)
|
|
1232
|
+
self.focus_input()
|
|
1233
|
+
self._refresh_interaction_state()
|
|
1234
|
+
return True
|
|
1235
|
+
|
|
1236
|
+
def insert_clipboard_image_paste(self) -> bool:
|
|
1237
|
+
"""Insert the current platform clipboard bitmap into the chat input."""
|
|
1238
|
+
# MainScreen also guards shell mode; keep this local guard for direct InputBar callers.
|
|
1239
|
+
if self.shell_mode:
|
|
1240
|
+
return False
|
|
1241
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
1242
|
+
if ta.read_only:
|
|
1243
|
+
return False
|
|
1244
|
+
if not ta._insert_clipboard_image_paste():
|
|
1245
|
+
return False
|
|
1246
|
+
self.focus_input()
|
|
1247
|
+
self._refresh_interaction_state()
|
|
1248
|
+
return True
|
|
1249
|
+
|
|
1250
|
+
def replace_trigger_text(self, trigger: str, replacement: str) -> None:
|
|
1251
|
+
"""Replace the trigger token (e.g. ``/query`` or ``@query``) with *replacement*.
|
|
1252
|
+
|
|
1253
|
+
Finds the last occurrence of *trigger* character in the text and replaces
|
|
1254
|
+
everything from that position to the cursor with *replacement*.
|
|
1255
|
+
Also checks for fullwidth CJK equivalents (e.g. U+FF20 for ``@``).
|
|
1256
|
+
"""
|
|
1257
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
1258
|
+
text = ta.text
|
|
1259
|
+
idx = text.rfind(trigger)
|
|
1260
|
+
# Check fullwidth variant for CJK keyboard support (ASCII+0xFEE0)
|
|
1261
|
+
if idx < 0 and len(trigger) == 1 and 0x21 <= ord(trigger) <= 0x7E:
|
|
1262
|
+
idx = text.rfind(chr(ord(trigger) + 0xFEE0))
|
|
1263
|
+
if idx < 0:
|
|
1264
|
+
return
|
|
1265
|
+
before = text[:idx]
|
|
1266
|
+
ta.clear()
|
|
1267
|
+
new_text = before + replacement
|
|
1268
|
+
if new_text:
|
|
1269
|
+
ta.insert(new_text)
|
|
1270
|
+
|
|
1271
|
+
@property
|
|
1272
|
+
def value(self) -> str:
|
|
1273
|
+
return self.query_one("#chat-input", _ChatTextArea).text
|
|
1274
|
+
|
|
1275
|
+
@value.setter
|
|
1276
|
+
def value(self, text: str) -> None:
|
|
1277
|
+
self.replace_draft(text)
|
|
1278
|
+
|
|
1279
|
+
def consume_retry_text(self) -> str:
|
|
1280
|
+
"""Return and clear the typed retry/continue note, if any."""
|
|
1281
|
+
ta = self.query_one("#chat-input", _ChatTextArea)
|
|
1282
|
+
text = ta.text.strip()
|
|
1283
|
+
if text:
|
|
1284
|
+
ta._reset_history_browsing()
|
|
1285
|
+
ta.clear()
|
|
1286
|
+
self._refresh_interaction_state()
|
|
1287
|
+
return text
|
|
1288
|
+
|
|
1289
|
+
def add_to_history(self, text: str, *, session_id: str | None = None) -> None:
|
|
1290
|
+
"""Add *text* to input history."""
|
|
1291
|
+
self.query_one("#chat-input", _ChatTextArea).add_to_history(text, session_id=session_id)
|
|
1292
|
+
|
|
1293
|
+
async def load_prompt_history(self, *, max_entries: int) -> list[str]:
|
|
1294
|
+
"""Load global prompt history for the suggestion popup."""
|
|
1295
|
+
return await self.query_one("#chat-input", _ChatTextArea).load_prompt_history(max_entries=max_entries)
|
|
1296
|
+
|
|
1297
|
+
async def action_submit(self) -> None:
|
|
1298
|
+
"""Programmatic submit (for testing)."""
|
|
1299
|
+
self._do_submit()
|