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,2014 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Backend event handlers — routes EventBus callbacks to TUI widgets."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from chrys.app.tui.i18n import render_str
|
|
16
|
+
from chrys.app.tui.notifications import NotificationEvent
|
|
17
|
+
from chrys.app.tui.screens.main.dialog_controllers import (
|
|
18
|
+
AgentLoadDialogController,
|
|
19
|
+
ApprovalDialogHandle,
|
|
20
|
+
ApprovalQueueController,
|
|
21
|
+
ApprovalResponseWorker,
|
|
22
|
+
ImageCompressionDialogController,
|
|
23
|
+
QuestionDialogHandle,
|
|
24
|
+
QuestionQueueController,
|
|
25
|
+
)
|
|
26
|
+
from chrys.app.tui.screens.main.dialog_gateway import UiGateway, UiGatewayCallbacks
|
|
27
|
+
from chrys.app.tui.screens.main.diff_controller import LiveDiffTracker
|
|
28
|
+
from chrys.app.tui.screens.main.main_screen_presenter import MainScreenPresenter
|
|
29
|
+
from chrys.app.tui.screens.main.ports import (
|
|
30
|
+
BackendEventView,
|
|
31
|
+
InputRestore,
|
|
32
|
+
NotificationSeverity,
|
|
33
|
+
StatusMessage,
|
|
34
|
+
StatusTrail,
|
|
35
|
+
)
|
|
36
|
+
from chrys.app.tui.screens.main.runtime_info import RegistryRuntimeInfoProvider
|
|
37
|
+
from chrys.app.tui.screens.main.state import MainScreenServices, MainScreenState
|
|
38
|
+
from chrys.app.tui.support.gc_freeze import (
|
|
39
|
+
GcAbsorbReason,
|
|
40
|
+
GcAbsorbRequested,
|
|
41
|
+
GcReclaimReason,
|
|
42
|
+
GcReclaimRequested,
|
|
43
|
+
)
|
|
44
|
+
from chrys.app.tui.support.workspace_mru import schedule_workspace_mru_touches
|
|
45
|
+
from chrys.app.tui.util.context_pressure import context_pressure_message
|
|
46
|
+
from chrys.app.tui.widgets import PromptDraft
|
|
47
|
+
from chrys.app.tui.widgets.chat.file_snapshot import FileSnapshotPayload, FileSnapshotRef, should_externalize_snapshot
|
|
48
|
+
from chrys.app.tui.widgets.chrome.app_header import APPROVAL_MODE_MESSAGES
|
|
49
|
+
from chrys.app.tui.widgets.chrome.input_bar import INPUT_RETRY
|
|
50
|
+
from chrys.app.tui.widgets.chrome.status_bar import (
|
|
51
|
+
STATUS_COMPACTING,
|
|
52
|
+
STATUS_ERROR,
|
|
53
|
+
STATUS_STREAMING,
|
|
54
|
+
STATUS_THINKING,
|
|
55
|
+
)
|
|
56
|
+
from chrys.app.tui.widgets.sidebar.context import ContextUsageState
|
|
57
|
+
from chrys.foundation.errors.display import DISPLAY_WITH_HINT
|
|
58
|
+
from chrys.foundation.events.types import (
|
|
59
|
+
AgentLoadFailed,
|
|
60
|
+
AgentLoadFinished,
|
|
61
|
+
AgentLoadProgress,
|
|
62
|
+
AgentLoadStarted,
|
|
63
|
+
AgentRuntimeDetails,
|
|
64
|
+
AgentRuntimeUpdated,
|
|
65
|
+
AgentThinking,
|
|
66
|
+
ApprovalAutoFulfillBlocked,
|
|
67
|
+
ApprovalCancelled,
|
|
68
|
+
ApprovalModeUpdated,
|
|
69
|
+
ApprovalRequest,
|
|
70
|
+
ApprovalReviewed,
|
|
71
|
+
AskUserTimedOut,
|
|
72
|
+
CompactionFinished,
|
|
73
|
+
CompactionStarted,
|
|
74
|
+
ContextCompressed,
|
|
75
|
+
Error,
|
|
76
|
+
ImageAttachmentCompressionFinished,
|
|
77
|
+
ImageAttachmentCompressionStarted,
|
|
78
|
+
InvocationAborted,
|
|
79
|
+
InvocationCascadeAborted,
|
|
80
|
+
InvocationCompactionCommitted,
|
|
81
|
+
InvocationCompactionFinished,
|
|
82
|
+
InvocationCompactionStarted,
|
|
83
|
+
InvocationContextPressure,
|
|
84
|
+
InvocationMessage,
|
|
85
|
+
InvocationPaused,
|
|
86
|
+
InvocationPresentationAttemptAccepted,
|
|
87
|
+
InvocationPresentationAttemptRejected,
|
|
88
|
+
InvocationProgress,
|
|
89
|
+
InvocationResumed,
|
|
90
|
+
InvocationRetryAttempt,
|
|
91
|
+
InvocationStarted,
|
|
92
|
+
InvocationToolCallArgsUpdated,
|
|
93
|
+
InvocationToolCallProgress,
|
|
94
|
+
InvocationToolCallResult,
|
|
95
|
+
InvocationToolCallStart,
|
|
96
|
+
InvocationToolCallStatusUpdated,
|
|
97
|
+
QuestionToUser,
|
|
98
|
+
SessionReady,
|
|
99
|
+
SettingsReloaded,
|
|
100
|
+
TodoListUpdated,
|
|
101
|
+
ToolCompacted,
|
|
102
|
+
UsageUpdate,
|
|
103
|
+
UserInjectResult,
|
|
104
|
+
Warning,
|
|
105
|
+
)
|
|
106
|
+
from chrys.foundation.hosted_tools import HostedToolStatus, normalize_hosted_tool_status
|
|
107
|
+
from chrys.foundation.i18n import DisplayBlock, MessageRef, msg
|
|
108
|
+
from chrys.foundation.i18n.formatting import format_message
|
|
109
|
+
from chrys.foundation.models.approval_reuse import ReuseChoice
|
|
110
|
+
from chrys.foundation.models.ask_user import AskUserAnswer
|
|
111
|
+
from chrys.foundation.tool_result_metadata import canonical_tool_result_status
|
|
112
|
+
from chrys.orchestration.engine.run.attachments import replace_image_mentions_with_paths
|
|
113
|
+
from chrys.service.approval.policy import ApprovalMode
|
|
114
|
+
from chrys.service.mutations.types import FileHashDiff
|
|
115
|
+
from chrys.service.profiles.models.schema import DEFAULT_MAX_CONTEXT_TOKENS
|
|
116
|
+
from chrys.service.state.store import StateStore
|
|
117
|
+
|
|
118
|
+
if TYPE_CHECKING:
|
|
119
|
+
from chrys.app.tui.i18n import LocaleController
|
|
120
|
+
from chrys.app.tui.screens.main.workspace_actions import MissingDirReason
|
|
121
|
+
from chrys.foundation.models.invocations import InvocationOrigin
|
|
122
|
+
|
|
123
|
+
_IMAGE_ERROR_CODES = {"image_attachment_error", "vision_unsupported"}
|
|
124
|
+
_IMAGE_WARNING_CODES = {
|
|
125
|
+
"image_attachment_error",
|
|
126
|
+
"image_attachment_retry_unsupported",
|
|
127
|
+
"image_attachment_while_running",
|
|
128
|
+
}
|
|
129
|
+
_SUBMIT_BLOCKING_WARNING_CODES = _IMAGE_WARNING_CODES | {"sub_agent_paused"}
|
|
130
|
+
_WORKING_DIR_MISSING_CODE = "working_dir_missing"
|
|
131
|
+
_SUBMIT_BLOCKING_ERROR_CODES = _IMAGE_ERROR_CODES | {
|
|
132
|
+
"hook_blocked",
|
|
133
|
+
"not_ready",
|
|
134
|
+
"prompt_admission_conflict",
|
|
135
|
+
"retry_missing_user_anchor",
|
|
136
|
+
_WORKING_DIR_MISSING_CODE,
|
|
137
|
+
}
|
|
138
|
+
_STALE_RUN_FILTERED_ERROR_CODES = {"executor_error", "retry_missing_user_anchor"}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
_SOFT_AGENT_LOAD_OPERATIONS = {"switch", "settings_reload", "workspace_change", "model_switch"}
|
|
142
|
+
|
|
143
|
+
_APPROVAL_TITLE = msg("tui.approval.title", fallback="Approval")
|
|
144
|
+
_APPROVAL_MODE_CHANGED = msg("tui.approval.mode_changed", fallback="Approval mode: {mode}")
|
|
145
|
+
_WARNING_TITLE = msg("tui.warning.title", fallback="Warning")
|
|
146
|
+
_UNKNOWN_ERROR = msg("tui.error.unknown", fallback="Unknown error")
|
|
147
|
+
_RETRY_NOT_STARTED = msg("tui.retry.not_started", fallback="Retry not started")
|
|
148
|
+
_AGENT_FAILED_TO_LOAD = msg("tui.agent_load.failed", fallback="Agent failed to load.")
|
|
149
|
+
_NON_TURN_NOTIFICATION_ERROR_CODES = {
|
|
150
|
+
"hook_blocked",
|
|
151
|
+
"image_attachment_error",
|
|
152
|
+
"no_state_store",
|
|
153
|
+
"not_ready",
|
|
154
|
+
"session_in_use",
|
|
155
|
+
"vision_unsupported",
|
|
156
|
+
}
|
|
157
|
+
_SESSION_FORK_ERROR_PREFIX = "session_fork_"
|
|
158
|
+
_SESSION_CLEAR_FAILED_CODE = "session_clear_failed"
|
|
159
|
+
|
|
160
|
+
logger = logging.getLogger(__name__)
|
|
161
|
+
|
|
162
|
+
_FINISH_SESSION_READY = msg("tui.agent_load.finish.session_ready", fallback="Session ready: {profile}")
|
|
163
|
+
_SESSION_IN_USE_TITLE = msg("tui.main.session_in_use.title", fallback="Session In Use")
|
|
164
|
+
_SESSION_IN_USE_MESSAGE = msg(
|
|
165
|
+
"tui.main.session_in_use.message",
|
|
166
|
+
fallback="Session Already Open\n\n{message}",
|
|
167
|
+
multiline=True,
|
|
168
|
+
)
|
|
169
|
+
_SESSION_IN_USE_OK = msg("tui.main.session_in_use.ok", fallback="OK")
|
|
170
|
+
_HOSTED_WEB_TOOLS_OK = msg("tui.main.hosted_web_tools.ok", fallback="OK")
|
|
171
|
+
_IMAGE_NOT_ATTACHED = msg("tui.vision_unsupported.title.not_attached", fallback="Image Not Attached")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _chat_sub_agent(origin: InvocationOrigin) -> bool:
|
|
175
|
+
"""A sub-agent with a card in the chat transcript: a workflow node's children have none."""
|
|
176
|
+
return origin.kind == "sub_agent" and origin.root.kind != "workflow_node"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass(frozen=True, slots=True)
|
|
180
|
+
class _WarningDedupeKey:
|
|
181
|
+
"""Session-scoped identity for one screen-lifetime warning toast."""
|
|
182
|
+
|
|
183
|
+
session_id: str | None
|
|
184
|
+
code: str
|
|
185
|
+
message: str
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass(frozen=True, slots=True)
|
|
189
|
+
class BackendEventCallbacks:
|
|
190
|
+
"""Screen-owned effects required by backend event handlers."""
|
|
191
|
+
|
|
192
|
+
set_agent_running: Callable[[bool], None]
|
|
193
|
+
set_agent_loading: Callable[[bool], None]
|
|
194
|
+
set_has_messages: Callable[[bool], None]
|
|
195
|
+
set_profile_display: Callable[[str], None]
|
|
196
|
+
set_active_model_profile_id: Callable[[str], None]
|
|
197
|
+
set_creating_new_session: Callable[[bool], None]
|
|
198
|
+
set_restoring_session: Callable[[bool], None]
|
|
199
|
+
set_workspace_cwd: Callable[[str], None]
|
|
200
|
+
refresh_git_branch: Callable[[], None]
|
|
201
|
+
update_subtitle: Callable[[], None]
|
|
202
|
+
update_toc: Callable[[], None]
|
|
203
|
+
on_session_fork_error: Callable[[Error, str, NotificationSeverity], None]
|
|
204
|
+
on_session_clear_error: Callable[[Error, str], None]
|
|
205
|
+
handle_approval_response: Callable[
|
|
206
|
+
[str, bool, str, dict[str, object] | None, ReuseChoice], ApprovalResponseWorker | None
|
|
207
|
+
]
|
|
208
|
+
handle_ask_user_response: Callable[[str, tuple[AskUserAnswer, ...]], object]
|
|
209
|
+
question_inline_preferred: Callable[[], bool]
|
|
210
|
+
approval_defer_while_judging: Callable[[], bool]
|
|
211
|
+
post_gc_message: Callable[[GcAbsorbRequested | GcReclaimRequested], object]
|
|
212
|
+
debug: Callable[[str, str], None]
|
|
213
|
+
refresh_model_indicator: Callable[[], None]
|
|
214
|
+
refresh_notification_settings: Callable[[], None]
|
|
215
|
+
refresh_trajectory_verify_commands: Callable[[], None]
|
|
216
|
+
settings_reloaded: Callable[[], None]
|
|
217
|
+
accept_approval_update: Callable[[ApprovalModeUpdated], bool] = lambda _event: True
|
|
218
|
+
route_session_error: Callable[[Error], bool] = lambda _event: False
|
|
219
|
+
prompt_missing_working_dir: Callable[[MissingDirReason], None] = lambda _reason: None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _usage_source_debug_suffix(source_id: str, *, main_source_id: str) -> str:
|
|
223
|
+
"""Return a short source marker for non-main usage debug entries."""
|
|
224
|
+
if not source_id or source_id == main_source_id:
|
|
225
|
+
return ""
|
|
226
|
+
short_source = source_id if len(source_id) <= 12 else f"{source_id[:6]}…{source_id[-4:]}"
|
|
227
|
+
return f" source={short_source}"
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _metadata_hashes(value: object) -> FileHashDiff:
|
|
231
|
+
"""Read before/after hashes from event metadata."""
|
|
232
|
+
if isinstance(value, FileHashDiff):
|
|
233
|
+
return value
|
|
234
|
+
if value is not None:
|
|
235
|
+
logger.debug("dropping unexpected file_mutation_hashes payload: %r", value)
|
|
236
|
+
return FileHashDiff(before=None, after=None)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _chat_file_snapshot_payload(
|
|
240
|
+
snapshot: object,
|
|
241
|
+
hashes: FileHashDiff,
|
|
242
|
+
state_store: StateStore | None,
|
|
243
|
+
session_id: str,
|
|
244
|
+
) -> FileSnapshotPayload | None:
|
|
245
|
+
"""Return the snapshot payload retained by chat widgets for a file tool."""
|
|
246
|
+
if not isinstance(snapshot, tuple) or len(snapshot) != 2:
|
|
247
|
+
return None
|
|
248
|
+
before, after = snapshot
|
|
249
|
+
if not isinstance(before, str) or not isinstance(after, str):
|
|
250
|
+
return None
|
|
251
|
+
text_snapshot = (before, after)
|
|
252
|
+
if not should_externalize_snapshot(text_snapshot):
|
|
253
|
+
return text_snapshot
|
|
254
|
+
if state_store is None or not session_id or (hashes.before is None and hashes.after is None):
|
|
255
|
+
return text_snapshot
|
|
256
|
+
try:
|
|
257
|
+
from chrys.service.mutations.store import SnapshotStore
|
|
258
|
+
|
|
259
|
+
mutations_dir = SnapshotStore(state_store.session_dir(session_id)).mutations_dir
|
|
260
|
+
except Exception:
|
|
261
|
+
return text_snapshot
|
|
262
|
+
return FileSnapshotRef(mutations_dir=mutations_dir, before_hash=hashes.before, after_hash=hashes.after)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class BackendEventHandler:
|
|
266
|
+
"""Handles all EventBus backend → TUI event routing.
|
|
267
|
+
|
|
268
|
+
Constructed once by the main-screen owner and receives a semantic host
|
|
269
|
+
for shared state, dialog controllers, and presenter-mediated UI.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(
|
|
273
|
+
self,
|
|
274
|
+
*,
|
|
275
|
+
state: MainScreenState,
|
|
276
|
+
services: MainScreenServices,
|
|
277
|
+
view: BackendEventView,
|
|
278
|
+
callbacks: BackendEventCallbacks,
|
|
279
|
+
runtime_info: RegistryRuntimeInfoProvider | None = None,
|
|
280
|
+
live_diff: LiveDiffTracker | None = None,
|
|
281
|
+
locale_controller: LocaleController | None = None,
|
|
282
|
+
) -> None:
|
|
283
|
+
self._state = state
|
|
284
|
+
self._services = services
|
|
285
|
+
self._view = view
|
|
286
|
+
self._callbacks = callbacks
|
|
287
|
+
self._locale_controller = locale_controller
|
|
288
|
+
self._dialog_gateway = UiGateway(
|
|
289
|
+
view,
|
|
290
|
+
UiGatewayCallbacks(
|
|
291
|
+
debug=callbacks.debug,
|
|
292
|
+
handle_approval_response=callbacks.handle_approval_response,
|
|
293
|
+
publish_auto_fulfill_blocked=self._publish_auto_fulfill_blocked,
|
|
294
|
+
handle_ask_user_response=callbacks.handle_ask_user_response,
|
|
295
|
+
question_inline_preferred=callbacks.question_inline_preferred,
|
|
296
|
+
approval_defer_while_judging=callbacks.approval_defer_while_judging,
|
|
297
|
+
set_agent_loading=self.set_agent_loading,
|
|
298
|
+
),
|
|
299
|
+
)
|
|
300
|
+
self._approval_controller = ApprovalQueueController(
|
|
301
|
+
self._dialog_gateway,
|
|
302
|
+
render_message=self._render_display,
|
|
303
|
+
)
|
|
304
|
+
self._question_controller = QuestionQueueController(self._dialog_gateway)
|
|
305
|
+
self._agent_load_controller = AgentLoadDialogController(self._dialog_gateway)
|
|
306
|
+
self._image_compression_controller = ImageCompressionDialogController(self._dialog_gateway)
|
|
307
|
+
self._presenter = MainScreenPresenter(view, state)
|
|
308
|
+
self._runtime_info = runtime_info or RegistryRuntimeInfoProvider(services)
|
|
309
|
+
self._live_diff = live_diff or LiveDiffTracker()
|
|
310
|
+
self._seen_warnings: set[_WarningDedupeKey] = set()
|
|
311
|
+
|
|
312
|
+
def _render_display(self, reference: MessageRef) -> str:
|
|
313
|
+
controller = self._locale_controller
|
|
314
|
+
return format_message(reference) if controller is None else render_str(controller.localizer, reference)
|
|
315
|
+
|
|
316
|
+
def _render_display_with_hint(self, message: MessageRef, hint: MessageRef | None) -> str:
|
|
317
|
+
"""Render an error's display message and its hint in the current locale, joined as that locale joins them."""
|
|
318
|
+
rendered = self._render_display(message)
|
|
319
|
+
if hint is None:
|
|
320
|
+
return rendered
|
|
321
|
+
return self._render_display(DISPLAY_WITH_HINT.bind(message=rendered, hint=self._render_display(hint)))
|
|
322
|
+
|
|
323
|
+
def _gateway(self) -> UiGateway:
|
|
324
|
+
"""Return the Textual dialog gateway."""
|
|
325
|
+
return self._dialog_gateway
|
|
326
|
+
|
|
327
|
+
def _ui(self) -> MainScreenPresenter:
|
|
328
|
+
"""Return the screen presenter."""
|
|
329
|
+
return self._presenter
|
|
330
|
+
|
|
331
|
+
def _approval(self) -> ApprovalQueueController:
|
|
332
|
+
return self._approval_controller
|
|
333
|
+
|
|
334
|
+
def _questions(self) -> QuestionQueueController:
|
|
335
|
+
return self._question_controller
|
|
336
|
+
|
|
337
|
+
def _agent_load(self) -> AgentLoadDialogController:
|
|
338
|
+
return self._agent_load_controller
|
|
339
|
+
|
|
340
|
+
def _image_compression(self) -> ImageCompressionDialogController:
|
|
341
|
+
return self._image_compression_controller
|
|
342
|
+
|
|
343
|
+
def _predates_active_run(self, event: InvocationMessage | Error) -> bool:
|
|
344
|
+
"""Return whether *event* was sourced before the current UI run."""
|
|
345
|
+
started_at = self._state.run.started_at
|
|
346
|
+
return self._state.run.agent_running and started_at is not None and event.timestamp < started_at
|
|
347
|
+
|
|
348
|
+
@property
|
|
349
|
+
def state_store(self) -> StateStore | None:
|
|
350
|
+
return self._services.state_store
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def agent_running(self) -> bool:
|
|
354
|
+
return self._state.run.agent_running
|
|
355
|
+
|
|
356
|
+
def set_agent_running(self, running: bool) -> None:
|
|
357
|
+
self._state.run.agent_running = running
|
|
358
|
+
self._callbacks.set_agent_running(running)
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def agent_loading(self) -> bool:
|
|
362
|
+
return self._state.run.agent_loading
|
|
363
|
+
|
|
364
|
+
def set_agent_loading(self, loading: bool) -> None:
|
|
365
|
+
self._state.run.agent_loading = loading
|
|
366
|
+
self._callbacks.set_agent_loading(loading)
|
|
367
|
+
|
|
368
|
+
@property
|
|
369
|
+
def has_messages(self) -> bool:
|
|
370
|
+
return self._state.run.has_messages
|
|
371
|
+
|
|
372
|
+
def set_has_messages(self, has_messages: bool) -> None:
|
|
373
|
+
self._state.run.has_messages = has_messages
|
|
374
|
+
self._callbacks.set_has_messages(has_messages)
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def profile(self) -> str:
|
|
378
|
+
return self._state.runtime.profile
|
|
379
|
+
|
|
380
|
+
@profile.setter
|
|
381
|
+
def profile(self, value: str) -> None:
|
|
382
|
+
self._state.runtime.profile = value
|
|
383
|
+
self._callbacks.set_profile_display(value)
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def runtime_metadata(self) -> AgentRuntimeDetails:
|
|
387
|
+
return self._state.runtime.details
|
|
388
|
+
|
|
389
|
+
@runtime_metadata.setter
|
|
390
|
+
def runtime_metadata(self, value: AgentRuntimeDetails) -> None:
|
|
391
|
+
self._state.runtime.details = value
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
def approval_mode(self) -> ApprovalMode:
|
|
395
|
+
return self._state.runtime.approval_mode
|
|
396
|
+
|
|
397
|
+
@approval_mode.setter
|
|
398
|
+
def approval_mode(self, value: ApprovalMode) -> None:
|
|
399
|
+
self._state.runtime.approval_mode = value
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def main_usage_source_id(self) -> str:
|
|
403
|
+
return self._state.runtime.main_usage_source_id
|
|
404
|
+
|
|
405
|
+
@main_usage_source_id.setter
|
|
406
|
+
def main_usage_source_id(self, value: str) -> None:
|
|
407
|
+
self._state.runtime.main_usage_source_id = value
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def creating_new_session(self) -> bool:
|
|
411
|
+
return self._state.session.creating_new_session
|
|
412
|
+
|
|
413
|
+
@creating_new_session.setter
|
|
414
|
+
def creating_new_session(self, value: bool) -> None:
|
|
415
|
+
self._state.session.creating_new_session = value
|
|
416
|
+
self._callbacks.set_creating_new_session(value)
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def restoring_session(self) -> bool:
|
|
420
|
+
return self._state.session.restoring_session
|
|
421
|
+
|
|
422
|
+
@restoring_session.setter
|
|
423
|
+
def restoring_session(self, value: bool) -> None:
|
|
424
|
+
self._state.session.restoring_session = value
|
|
425
|
+
self._callbacks.set_restoring_session(value)
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def last_usage_tokens(self) -> int:
|
|
429
|
+
return self._state.usage.last_usage_tokens
|
|
430
|
+
|
|
431
|
+
@last_usage_tokens.setter
|
|
432
|
+
def last_usage_tokens(self, value: int) -> None:
|
|
433
|
+
self._state.usage.last_usage_tokens = value
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def last_total_session_tokens(self) -> int:
|
|
437
|
+
return self._state.usage.last_total_session_tokens
|
|
438
|
+
|
|
439
|
+
@last_total_session_tokens.setter
|
|
440
|
+
def last_total_session_tokens(self, value: int) -> None:
|
|
441
|
+
self._state.usage.last_total_session_tokens = value
|
|
442
|
+
|
|
443
|
+
@property
|
|
444
|
+
def context_usage_state(self) -> ContextUsageState | None:
|
|
445
|
+
return self._view.context_usage_state
|
|
446
|
+
|
|
447
|
+
@property
|
|
448
|
+
def chdir_current_cwd(self) -> str:
|
|
449
|
+
return self._state.workspace_marker.current_cwd
|
|
450
|
+
|
|
451
|
+
@chdir_current_cwd.setter
|
|
452
|
+
def chdir_current_cwd(self, value: str) -> None:
|
|
453
|
+
self._state.workspace.current_cwd = value
|
|
454
|
+
self._state.workspace_marker.current_cwd = value
|
|
455
|
+
self._callbacks.set_workspace_cwd(value)
|
|
456
|
+
|
|
457
|
+
@property
|
|
458
|
+
def chdir_original_cwd(self) -> str | None:
|
|
459
|
+
return self._state.workspace_marker.original_cwd
|
|
460
|
+
|
|
461
|
+
@chdir_original_cwd.setter
|
|
462
|
+
def chdir_original_cwd(self, value: str | None) -> None:
|
|
463
|
+
self._state.workspace_marker.original_cwd = value
|
|
464
|
+
|
|
465
|
+
@property
|
|
466
|
+
def pending_user_submit_active(self) -> bool:
|
|
467
|
+
return self._state.submit.active
|
|
468
|
+
|
|
469
|
+
@property
|
|
470
|
+
def pending_user_submit_text(self) -> str:
|
|
471
|
+
return self._state.submit.text
|
|
472
|
+
|
|
473
|
+
@property
|
|
474
|
+
def pending_user_message_render_active(self) -> bool:
|
|
475
|
+
return self._state.render_gate.active
|
|
476
|
+
|
|
477
|
+
def defer_agent_message(self, event: InvocationMessage) -> None:
|
|
478
|
+
self._state.render_gate.defer(event)
|
|
479
|
+
|
|
480
|
+
def defer_error(self, event: Error) -> None:
|
|
481
|
+
self._state.render_gate.defer(event)
|
|
482
|
+
|
|
483
|
+
def block_pending_user_submit(self) -> None:
|
|
484
|
+
self._state.submit.block()
|
|
485
|
+
|
|
486
|
+
def notify(
|
|
487
|
+
self,
|
|
488
|
+
message: StatusMessage,
|
|
489
|
+
*,
|
|
490
|
+
title: StatusMessage,
|
|
491
|
+
severity: NotificationSeverity = "information",
|
|
492
|
+
timeout: float | None = 3,
|
|
493
|
+
) -> None:
|
|
494
|
+
self._view.notify(message, title=title, severity=severity, timeout=timeout)
|
|
495
|
+
|
|
496
|
+
def push_screen(self, screen: object, callback: object | None = None) -> object:
|
|
497
|
+
return self._view.push_screen(screen, callback)
|
|
498
|
+
|
|
499
|
+
def update_subtitle(self) -> None:
|
|
500
|
+
self._callbacks.update_subtitle()
|
|
501
|
+
|
|
502
|
+
def refresh_git_branch(self) -> None:
|
|
503
|
+
self._callbacks.refresh_git_branch()
|
|
504
|
+
|
|
505
|
+
def update_toc(self) -> None:
|
|
506
|
+
self._callbacks.update_toc()
|
|
507
|
+
|
|
508
|
+
def on_session_fork_error(self, event: Error, *, message: str, severity: NotificationSeverity) -> None:
|
|
509
|
+
self._callbacks.on_session_fork_error(event, message, severity)
|
|
510
|
+
|
|
511
|
+
def on_session_clear_error(self, event: Error, *, message: str) -> None:
|
|
512
|
+
self._callbacks.on_session_clear_error(event, message)
|
|
513
|
+
|
|
514
|
+
def debug(self, key: str, message: str = "") -> None:
|
|
515
|
+
self._callbacks.debug(key, message)
|
|
516
|
+
|
|
517
|
+
@property
|
|
518
|
+
def _approval_queue(self):
|
|
519
|
+
return self._approval().queue
|
|
520
|
+
|
|
521
|
+
@_approval_queue.setter
|
|
522
|
+
def _approval_queue(self, value) -> None:
|
|
523
|
+
self._approval().queue = value
|
|
524
|
+
|
|
525
|
+
@property
|
|
526
|
+
def _approval_request_lock(self):
|
|
527
|
+
return self._approval().request_lock
|
|
528
|
+
|
|
529
|
+
@_approval_request_lock.setter
|
|
530
|
+
def _approval_request_lock(self, value) -> None:
|
|
531
|
+
self._approval().request_lock = value
|
|
532
|
+
|
|
533
|
+
@property
|
|
534
|
+
def _approval_dialog_open(self) -> bool:
|
|
535
|
+
return self._approval().dialog_open
|
|
536
|
+
|
|
537
|
+
@_approval_dialog_open.setter
|
|
538
|
+
def _approval_dialog_open(self, value: bool) -> None:
|
|
539
|
+
self._approval().dialog_open = value
|
|
540
|
+
|
|
541
|
+
@property
|
|
542
|
+
def _approval_bodies(self) -> dict[str, object]:
|
|
543
|
+
return self._approval().bodies
|
|
544
|
+
|
|
545
|
+
@_approval_bodies.setter
|
|
546
|
+
def _approval_bodies(self, value: dict[str, object]) -> None:
|
|
547
|
+
self._approval().bodies = value
|
|
548
|
+
|
|
549
|
+
@property
|
|
550
|
+
def _open_approval_dialogs(self) -> dict[str, ApprovalDialogHandle]:
|
|
551
|
+
return self._approval().open_dialogs
|
|
552
|
+
|
|
553
|
+
@_open_approval_dialogs.setter
|
|
554
|
+
def _open_approval_dialogs(self, value: dict[str, ApprovalDialogHandle]) -> None:
|
|
555
|
+
self._approval().open_dialogs = value
|
|
556
|
+
|
|
557
|
+
@property
|
|
558
|
+
def _pending_verdicts(self) -> dict[str, ApprovalReviewed]:
|
|
559
|
+
return self._approval().pending_verdicts
|
|
560
|
+
|
|
561
|
+
@_pending_verdicts.setter
|
|
562
|
+
def _pending_verdicts(self, value: dict[str, ApprovalReviewed]) -> None:
|
|
563
|
+
self._approval().pending_verdicts = value
|
|
564
|
+
|
|
565
|
+
@property
|
|
566
|
+
def _dismissed_approval_requests(self) -> set[str]:
|
|
567
|
+
return self._approval().dismissed_requests
|
|
568
|
+
|
|
569
|
+
@_dismissed_approval_requests.setter
|
|
570
|
+
def _dismissed_approval_requests(self, value: set[str]) -> None:
|
|
571
|
+
self._approval().dismissed_requests = value
|
|
572
|
+
|
|
573
|
+
@property
|
|
574
|
+
def _reviewed_dismissed_approval_requests(self) -> set[str]:
|
|
575
|
+
return self._approval().reviewed_dismissed_requests
|
|
576
|
+
|
|
577
|
+
@_reviewed_dismissed_approval_requests.setter
|
|
578
|
+
def _reviewed_dismissed_approval_requests(self, value: set[str]) -> None:
|
|
579
|
+
self._approval().reviewed_dismissed_requests = value
|
|
580
|
+
|
|
581
|
+
@property
|
|
582
|
+
def _question_queue(self):
|
|
583
|
+
return self._questions().queue
|
|
584
|
+
|
|
585
|
+
@_question_queue.setter
|
|
586
|
+
def _question_queue(self, value) -> None:
|
|
587
|
+
self._questions().queue = value
|
|
588
|
+
|
|
589
|
+
@property
|
|
590
|
+
def _question_dialog_open(self) -> bool:
|
|
591
|
+
return self._questions().dialog_open
|
|
592
|
+
|
|
593
|
+
@_question_dialog_open.setter
|
|
594
|
+
def _question_dialog_open(self, value: bool) -> None:
|
|
595
|
+
self._questions().dialog_open = value
|
|
596
|
+
|
|
597
|
+
@property
|
|
598
|
+
def _open_question_dialogs(self) -> dict[str, QuestionDialogHandle]:
|
|
599
|
+
return self._questions().open_dialogs
|
|
600
|
+
|
|
601
|
+
@_open_question_dialogs.setter
|
|
602
|
+
def _open_question_dialogs(self, value: dict[str, QuestionDialogHandle]) -> None:
|
|
603
|
+
self._questions().open_dialogs = value
|
|
604
|
+
|
|
605
|
+
@property
|
|
606
|
+
def _inline_question_call_ids(self) -> dict[str, str]:
|
|
607
|
+
return self._questions().inline_call_ids
|
|
608
|
+
|
|
609
|
+
@_inline_question_call_ids.setter
|
|
610
|
+
def _inline_question_call_ids(self, value: dict[str, str]) -> None:
|
|
611
|
+
self._questions().inline_call_ids = value
|
|
612
|
+
|
|
613
|
+
@property
|
|
614
|
+
def _inline_question_request_ids(self) -> dict[str, str]:
|
|
615
|
+
return self._questions().inline_request_ids
|
|
616
|
+
|
|
617
|
+
@_inline_question_request_ids.setter
|
|
618
|
+
def _inline_question_request_ids(self, value: dict[str, str]) -> None:
|
|
619
|
+
self._questions().inline_request_ids = value
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def _question_drafts(self) -> dict[str, PromptDraft]:
|
|
623
|
+
return self._questions().drafts
|
|
624
|
+
|
|
625
|
+
@_question_drafts.setter
|
|
626
|
+
def _question_drafts(self, value: dict[str, PromptDraft]) -> None:
|
|
627
|
+
self._questions().drafts = value
|
|
628
|
+
|
|
629
|
+
@property
|
|
630
|
+
def _agent_load_dialog(self):
|
|
631
|
+
return self._agent_load().dialog
|
|
632
|
+
|
|
633
|
+
@_agent_load_dialog.setter
|
|
634
|
+
def _agent_load_dialog(self, value) -> None:
|
|
635
|
+
self._agent_load().dialog = value
|
|
636
|
+
|
|
637
|
+
@property
|
|
638
|
+
def _agent_load_status_snapshot(self) -> dict | None:
|
|
639
|
+
return self._agent_load().status_snapshot
|
|
640
|
+
|
|
641
|
+
@_agent_load_status_snapshot.setter
|
|
642
|
+
def _agent_load_status_snapshot(self, value: dict | None) -> None:
|
|
643
|
+
self._agent_load().status_snapshot = value
|
|
644
|
+
|
|
645
|
+
@property
|
|
646
|
+
def _image_compression_dialog(self):
|
|
647
|
+
return self._image_compression().dialog
|
|
648
|
+
|
|
649
|
+
@_image_compression_dialog.setter
|
|
650
|
+
def _image_compression_dialog(self, value) -> None:
|
|
651
|
+
self._image_compression().dialog = value
|
|
652
|
+
|
|
653
|
+
# -------------------------------------------------------------- #
|
|
654
|
+
# Helpers
|
|
655
|
+
# -------------------------------------------------------------- #
|
|
656
|
+
|
|
657
|
+
@staticmethod
|
|
658
|
+
def _extract_error_detail(raw: str) -> str:
|
|
659
|
+
"""Extract a readable error message from an API error string.
|
|
660
|
+
|
|
661
|
+
API errors look like: ``Error code: 400 - {'error': {'message': '...'}}``
|
|
662
|
+
This extracts the inner ``message`` field for the chat panel while
|
|
663
|
+
keeping the error code prefix.
|
|
664
|
+
"""
|
|
665
|
+
idx = raw.find("- {")
|
|
666
|
+
if idx < 0:
|
|
667
|
+
return raw
|
|
668
|
+
prefix = raw[:idx].strip()
|
|
669
|
+
payload = raw[idx + 2 :].strip()
|
|
670
|
+
try:
|
|
671
|
+
data = json.loads(payload.replace("'", '"'))
|
|
672
|
+
# Anthropic / OpenAI style: {'error': {'message': '...'}}
|
|
673
|
+
inner = data.get("error", data)
|
|
674
|
+
if isinstance(inner, dict):
|
|
675
|
+
detail = inner.get("message", "")
|
|
676
|
+
if detail:
|
|
677
|
+
return f"{prefix}\n{detail}"
|
|
678
|
+
except json.JSONDecodeError, TypeError, AttributeError:
|
|
679
|
+
pass
|
|
680
|
+
return raw
|
|
681
|
+
|
|
682
|
+
def _fallback_error_message(self, code: str) -> str:
|
|
683
|
+
"""Return a readable fallback when an error event has no message."""
|
|
684
|
+
label = code.replace("_", " ").strip()
|
|
685
|
+
return label.capitalize() if label else self._render_display(_UNKNOWN_ERROR.bind())
|
|
686
|
+
|
|
687
|
+
def _notify(self, event: NotificationEvent) -> None:
|
|
688
|
+
"""Send a background desktop notification through the active app service."""
|
|
689
|
+
self._view.notify_event(event)
|
|
690
|
+
|
|
691
|
+
async def _publish_auto_fulfill_blocked(self, event: ApprovalReviewed) -> None:
|
|
692
|
+
await self._services.bus.publish(
|
|
693
|
+
ApprovalAutoFulfillBlocked(request_id=event.request_id, session_id=event.session_id)
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
def format_tool_info(
|
|
697
|
+
self,
|
|
698
|
+
tool_names: list[str],
|
|
699
|
+
skill_names: list[str],
|
|
700
|
+
*,
|
|
701
|
+
memory_files: list[str] | None = None,
|
|
702
|
+
runtime_details: AgentRuntimeDetails | None = None,
|
|
703
|
+
) -> StatusTrail:
|
|
704
|
+
"""Build trail text for tool/skill/hook/file counts.
|
|
705
|
+
|
|
706
|
+
``memory_files`` are auto-loaded reference file display paths from
|
|
707
|
+
:class:`chrys.foundation.events.types.SessionReady` / ``ProfileSwitched``.
|
|
708
|
+
Counted into the trail when non-empty. Detailed metadata is shown in
|
|
709
|
+
the runtime details modal, so the tooltip is only a click hint.
|
|
710
|
+
"""
|
|
711
|
+
return self._runtime_info.format_tool_info(
|
|
712
|
+
tool_names,
|
|
713
|
+
skill_names,
|
|
714
|
+
memory_files=memory_files,
|
|
715
|
+
runtime_details=runtime_details,
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
def get_profile_description(self, profile_name: str) -> str:
|
|
719
|
+
"""Look up a profile's description from the registry."""
|
|
720
|
+
return self._runtime_info.get_profile_description(profile_name)
|
|
721
|
+
|
|
722
|
+
@staticmethod
|
|
723
|
+
def _load_title(operation: str) -> StatusMessage:
|
|
724
|
+
return AgentLoadDialogController.load_title(operation)
|
|
725
|
+
|
|
726
|
+
@staticmethod
|
|
727
|
+
def _format_load_count(current: int, total: int, failed: int = 0) -> str:
|
|
728
|
+
return AgentLoadDialogController.format_load_count(current, total, failed)
|
|
729
|
+
|
|
730
|
+
async def _show_agent_load_dialog(
|
|
731
|
+
self,
|
|
732
|
+
*,
|
|
733
|
+
title: str,
|
|
734
|
+
subtitle: str,
|
|
735
|
+
session_id: str | None,
|
|
736
|
+
initial_message: str,
|
|
737
|
+
initial_phase: str = "",
|
|
738
|
+
update_clipboard_dir: bool = True,
|
|
739
|
+
) -> None:
|
|
740
|
+
await self._agent_load().show_dialog(
|
|
741
|
+
title=title,
|
|
742
|
+
subtitle=subtitle,
|
|
743
|
+
session_id=session_id,
|
|
744
|
+
initial_message=initial_message,
|
|
745
|
+
initial_phase=initial_phase,
|
|
746
|
+
update_clipboard_dir=update_clipboard_dir,
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
async def begin_session_restore_load(self, session_id: str) -> None:
|
|
750
|
+
"""Open the restore modal before backend session ownership checks run.
|
|
751
|
+
|
|
752
|
+
An empty *session_id* opens the modal for a pending latest-session
|
|
753
|
+
lookup (blank subtitle); a later call with the resolved id reuses the
|
|
754
|
+
same dialog and fills the subtitle in.
|
|
755
|
+
"""
|
|
756
|
+
from chrys.foundation.util.session_ids import session_short_id
|
|
757
|
+
|
|
758
|
+
await self._agent_load().begin_session_restore_load(session_id, session_short_id(session_id))
|
|
759
|
+
|
|
760
|
+
def cancel_agent_load(self) -> None:
|
|
761
|
+
"""Dismiss any active agent-load UI without showing a load result."""
|
|
762
|
+
self._agent_load().cancel()
|
|
763
|
+
|
|
764
|
+
# -------------------------------------------------------------- #
|
|
765
|
+
# EventBus callbacks
|
|
766
|
+
# -------------------------------------------------------------- #
|
|
767
|
+
|
|
768
|
+
async def on_agent_load_started(self, event: AgentLoadStarted) -> None:
|
|
769
|
+
if event.operation not in _SOFT_AGENT_LOAD_OPERATIONS:
|
|
770
|
+
# Fail-closed for guard/picker reads, but don't repaint the tag
|
|
771
|
+
# yet: that flashes the select label between the old and new
|
|
772
|
+
# model names on every restore/new-session load. The terminal
|
|
773
|
+
# confirmation event repaints on success and AgentLoadFailed
|
|
774
|
+
# repaints the unconfirmed action state on failure.
|
|
775
|
+
self._state.runtime.details_confirmed = False
|
|
776
|
+
await self._agent_load().on_started(event)
|
|
777
|
+
|
|
778
|
+
async def on_agent_load_progress(self, event: AgentLoadProgress) -> None:
|
|
779
|
+
await self._agent_load().on_progress(event)
|
|
780
|
+
|
|
781
|
+
async def on_agent_load_finished(self, event: AgentLoadFinished) -> None:
|
|
782
|
+
self._agent_load().on_finished(event)
|
|
783
|
+
self._callbacks.post_gc_message(GcReclaimRequested(GcReclaimReason.AGENT_REBUILT, prompt=False))
|
|
784
|
+
|
|
785
|
+
def update_session_history_progress(self, current: int, total: int) -> None:
|
|
786
|
+
"""Forward mounted transcript counts to the active restore dialog."""
|
|
787
|
+
self._agent_load().update_session_history_progress(current, total)
|
|
788
|
+
|
|
789
|
+
def finish_agent_load(self, message: StatusMessage = "") -> None:
|
|
790
|
+
"""Dismiss any active agent-load UI and unlock the main input."""
|
|
791
|
+
self._agent_load().finish(message)
|
|
792
|
+
|
|
793
|
+
async def on_agent_load_failed(self, event: AgentLoadFailed) -> None:
|
|
794
|
+
s = self
|
|
795
|
+
|
|
796
|
+
label = event.display_name or event.agent_profile
|
|
797
|
+
if label:
|
|
798
|
+
s.profile = label
|
|
799
|
+
with contextlib.suppress(Exception):
|
|
800
|
+
s.update_subtitle()
|
|
801
|
+
|
|
802
|
+
if event.display_message is None:
|
|
803
|
+
self._agent_load().on_failed(event)
|
|
804
|
+
else:
|
|
805
|
+
self._agent_load().on_failed(
|
|
806
|
+
event,
|
|
807
|
+
display=self._render_display_with_hint(event.display_message, event.display_hint),
|
|
808
|
+
summary=self._render_display(event.display_message),
|
|
809
|
+
)
|
|
810
|
+
self._callbacks.refresh_model_indicator()
|
|
811
|
+
if event.operation != "startup":
|
|
812
|
+
self._callbacks.post_gc_message(GcReclaimRequested(GcReclaimReason.AGENT_REBUILD_FAILED, prompt=False))
|
|
813
|
+
|
|
814
|
+
async def on_image_attachment_compression_started(self, event: ImageAttachmentCompressionStarted) -> None:
|
|
815
|
+
"""Show a loading-only modal while oversized images are prepared."""
|
|
816
|
+
await self._image_compression().on_started(event)
|
|
817
|
+
|
|
818
|
+
async def on_image_attachment_compression_finished(self, event: ImageAttachmentCompressionFinished) -> None:
|
|
819
|
+
"""Dismiss the image-compression modal after preparation finishes."""
|
|
820
|
+
await self._image_compression().on_finished(event)
|
|
821
|
+
|
|
822
|
+
async def on_session_ready(self, event: SessionReady) -> None:
|
|
823
|
+
s = self
|
|
824
|
+
ui = self._ui()
|
|
825
|
+
|
|
826
|
+
self._state.workspace.roots = [event.primary_cwd, *event.working_dirs]
|
|
827
|
+
s.main_usage_source_id = event.session_id or ""
|
|
828
|
+
s.profile = event.display_name or event.agent_profile
|
|
829
|
+
s.runtime_metadata = event.runtime_details
|
|
830
|
+
self._state.runtime.details_confirmed = True
|
|
831
|
+
if event.runtime_details.model.selection_source == "active":
|
|
832
|
+
self._callbacks.set_active_model_profile_id(event.runtime_details.model.profile_id)
|
|
833
|
+
self._callbacks.refresh_model_indicator()
|
|
834
|
+
s.update_subtitle()
|
|
835
|
+
ui.set_chat_profile(s.profile)
|
|
836
|
+
ui.set_chat_tool_kinds(event.tool_kinds)
|
|
837
|
+
desc = self.get_profile_description(event.agent_profile)
|
|
838
|
+
ui.set_status_profile(s.profile, description=desc)
|
|
839
|
+
ui.set_input_clipboard_dir(event.session_id)
|
|
840
|
+
trail = self.format_tool_info(
|
|
841
|
+
event.tool_names,
|
|
842
|
+
event.skill_names,
|
|
843
|
+
memory_files=event.memory_files,
|
|
844
|
+
runtime_details=event.runtime_details,
|
|
845
|
+
)
|
|
846
|
+
ui.set_tool_info(trail)
|
|
847
|
+
# During a restore, _on_session_restored handles the chat panel
|
|
848
|
+
if s.creating_new_session:
|
|
849
|
+
from chrys.foundation.platform import safe_getcwd
|
|
850
|
+
|
|
851
|
+
s.creating_new_session = False
|
|
852
|
+
s.set_has_messages(False)
|
|
853
|
+
s.last_usage_tokens = 0
|
|
854
|
+
s.last_total_session_tokens = 0
|
|
855
|
+
# Post-success reset point: clearing on the SessionNew publish
|
|
856
|
+
# instead would desync the sidebar if backend startup fails.
|
|
857
|
+
ui.clear_todos()
|
|
858
|
+
ui.set_input_retry_mode(False)
|
|
859
|
+
s.chdir_original_cwd = None
|
|
860
|
+
cwd = event.primary_cwd or safe_getcwd()
|
|
861
|
+
s.chdir_current_cwd = cwd
|
|
862
|
+
ui.set_context_usage_state(
|
|
863
|
+
ContextUsageState.with_window(
|
|
864
|
+
used_tokens=0,
|
|
865
|
+
max_context_tokens=event.max_context_tokens or DEFAULT_MAX_CONTEXT_TOKENS,
|
|
866
|
+
)
|
|
867
|
+
)
|
|
868
|
+
ui.set_terminal_title_for_cwd(cwd)
|
|
869
|
+
ui.set_input_paste_cwd(cwd)
|
|
870
|
+
await ui.clear_chat()
|
|
871
|
+
s.update_toc()
|
|
872
|
+
with contextlib.suppress(Exception):
|
|
873
|
+
ui.reset_context_usage(event.max_context_tokens)
|
|
874
|
+
ui.set_chat_workspace_cwd(cwd)
|
|
875
|
+
ui.update_welcome(profile=s.profile, cwd=cwd)
|
|
876
|
+
if event.session_id:
|
|
877
|
+
ui.set_chat_session_id(event.session_id)
|
|
878
|
+
ui.reset_session_title_state()
|
|
879
|
+
ui.clear_status()
|
|
880
|
+
elif not s.restoring_session:
|
|
881
|
+
from chrys.foundation.platform import safe_getcwd
|
|
882
|
+
|
|
883
|
+
cwd = event.primary_cwd or safe_getcwd()
|
|
884
|
+
s.chdir_current_cwd = cwd
|
|
885
|
+
ui.set_chat_workspace_cwd(cwd)
|
|
886
|
+
ui.set_input_paste_cwd(cwd)
|
|
887
|
+
ui.update_welcome(profile=s.profile, cwd=cwd)
|
|
888
|
+
if event.session_id:
|
|
889
|
+
ui.set_chat_session_id(event.session_id)
|
|
890
|
+
ui.clear_status()
|
|
891
|
+
# Always propagate max_context_tokens so the Context panel shows the
|
|
892
|
+
# correct ceiling from the moment the session starts (before any LLM call).
|
|
893
|
+
if event.max_context_tokens:
|
|
894
|
+
current_context_usage = s.context_usage_state
|
|
895
|
+
if current_context_usage is None or current_context_usage.max_context_tokens != event.max_context_tokens:
|
|
896
|
+
ui.set_context_usage_state(
|
|
897
|
+
ContextUsageState.with_window(
|
|
898
|
+
used_tokens=current_context_usage.used_tokens if current_context_usage else s.last_usage_tokens,
|
|
899
|
+
max_context_tokens=event.max_context_tokens,
|
|
900
|
+
total_session_tokens=(
|
|
901
|
+
current_context_usage.total_session_tokens
|
|
902
|
+
if current_context_usage
|
|
903
|
+
else s.last_total_session_tokens
|
|
904
|
+
),
|
|
905
|
+
total_session_input_tokens=(
|
|
906
|
+
current_context_usage.total_session_input_tokens if current_context_usage else 0
|
|
907
|
+
),
|
|
908
|
+
total_session_output_tokens=(
|
|
909
|
+
current_context_usage.total_session_output_tokens if current_context_usage else 0
|
|
910
|
+
),
|
|
911
|
+
total_session_cache_hit_tokens=(
|
|
912
|
+
current_context_usage.total_session_cache_hit_tokens if current_context_usage else None
|
|
913
|
+
),
|
|
914
|
+
)
|
|
915
|
+
)
|
|
916
|
+
if event.sub_agent_tool_names:
|
|
917
|
+
from chrys.app.tui.widgets.chat.renderers.sub_agent import SubAgentToolCall
|
|
918
|
+
from chrys.app.tui.widgets.chat.tool_renderers import register_kind_renderer
|
|
919
|
+
from chrys.foundation.tool_kinds import KIND_SUB_AGENT
|
|
920
|
+
|
|
921
|
+
register_kind_renderer(KIND_SUB_AGENT, SubAgentToolCall)
|
|
922
|
+
from chrys.foundation.util.session_ids import session_short_id
|
|
923
|
+
|
|
924
|
+
# Backend-confirmed workspace roots: record them in the MRU as a
|
|
925
|
+
# detached background task (never inline — publish awaits handlers).
|
|
926
|
+
schedule_workspace_mru_touches(
|
|
927
|
+
[event.primary_cwd, *event.working_dirs],
|
|
928
|
+
max_entries=self._services.workspace_mru_max_entries,
|
|
929
|
+
session_id=event.session_id or "",
|
|
930
|
+
used_at=event.timestamp,
|
|
931
|
+
)
|
|
932
|
+
s.debug("SessionReady", f"{event.agent_profile} [{session_short_id(event.session_id or '')}]")
|
|
933
|
+
if not s.restoring_session:
|
|
934
|
+
self.finish_agent_load(_FINISH_SESSION_READY.bind(profile=s.profile))
|
|
935
|
+
self._callbacks.post_gc_message(GcReclaimRequested(GcReclaimReason.SESSION_READY, prompt=True))
|
|
936
|
+
|
|
937
|
+
async def on_settings_reloaded(self, _event: SettingsReloaded) -> None:
|
|
938
|
+
"""Route the LIVE tier: re-project what a reload only installed.
|
|
939
|
+
|
|
940
|
+
A reload replaces the settings in the shared handle, but a LIVE field
|
|
941
|
+
whose consumer holds a projection — the notification service's view,
|
|
942
|
+
the dashboard's verify-commands word list — stays on the old values
|
|
943
|
+
until someone projects again. RELOAD fields need nothing here (the
|
|
944
|
+
rebuild re-read them), and RESTART fields were held back by the
|
|
945
|
+
reload's own routing.
|
|
946
|
+
"""
|
|
947
|
+
self._callbacks.refresh_notification_settings()
|
|
948
|
+
self._callbacks.refresh_trajectory_verify_commands()
|
|
949
|
+
# The Settings panel projects written-but-not-yet-live values; a
|
|
950
|
+
# reload is when the live values catch up (or an unrelated reload
|
|
951
|
+
# arrives), so it re-reads and, for its own reload, takes the ack.
|
|
952
|
+
self._callbacks.settings_reloaded()
|
|
953
|
+
|
|
954
|
+
async def on_agent_runtime_updated(self, event: AgentRuntimeUpdated) -> None:
|
|
955
|
+
"""Refresh runtime details and the persistent status-bar counts."""
|
|
956
|
+
s = self
|
|
957
|
+
|
|
958
|
+
s.runtime_metadata = event.runtime_details
|
|
959
|
+
self._state.runtime.details_confirmed = True
|
|
960
|
+
self._callbacks.refresh_model_indicator()
|
|
961
|
+
trail = self.format_tool_info(
|
|
962
|
+
event.tool_names,
|
|
963
|
+
event.skill_names,
|
|
964
|
+
memory_files=event.memory_files,
|
|
965
|
+
runtime_details=event.runtime_details,
|
|
966
|
+
)
|
|
967
|
+
self._ui().set_tool_info(trail)
|
|
968
|
+
|
|
969
|
+
async def on_agent_thinking(self, _event: AgentThinking) -> None:
|
|
970
|
+
s = self
|
|
971
|
+
if not s.agent_running:
|
|
972
|
+
return
|
|
973
|
+
|
|
974
|
+
self._ui().show_status(STATUS_THINKING.bind())
|
|
975
|
+
s.debug("AgentThinking")
|
|
976
|
+
|
|
977
|
+
async def on_agent_message(self, event: InvocationMessage) -> None:
|
|
978
|
+
if event.origin.kind == "sub_agent":
|
|
979
|
+
await self.on_sub_agent_message(event)
|
|
980
|
+
return
|
|
981
|
+
if event.origin.kind != "turn":
|
|
982
|
+
return
|
|
983
|
+
s = self
|
|
984
|
+
if self._predates_active_run(event):
|
|
985
|
+
s.debug("InvocationMessage", "ignored stale prior-run event")
|
|
986
|
+
return
|
|
987
|
+
if s.pending_user_message_render_active:
|
|
988
|
+
s.defer_agent_message(event)
|
|
989
|
+
return
|
|
990
|
+
if not s.agent_running:
|
|
991
|
+
return
|
|
992
|
+
|
|
993
|
+
ui = self._ui()
|
|
994
|
+
|
|
995
|
+
if event.is_intermediate:
|
|
996
|
+
await ui.add_agent_message(
|
|
997
|
+
event.text,
|
|
998
|
+
is_final=True,
|
|
999
|
+
is_intermediate=True,
|
|
1000
|
+
presentation=event.presentation,
|
|
1001
|
+
created_at=event.timestamp,
|
|
1002
|
+
)
|
|
1003
|
+
s.debug("InvocationMessage", f"(intermediate, {len(event.text)} chars)")
|
|
1004
|
+
elif event.is_final:
|
|
1005
|
+
was_live = s.agent_running
|
|
1006
|
+
terminal_generation = self._state.run.generation
|
|
1007
|
+
terminal_request = GcAbsorbRequested(GcAbsorbReason.TURN_TERMINAL, terminal_boundary=True)
|
|
1008
|
+
terminal_owned = False
|
|
1009
|
+
try:
|
|
1010
|
+
ui.flash_turn_complete()
|
|
1011
|
+
await ui.add_agent_message(
|
|
1012
|
+
event.text,
|
|
1013
|
+
is_final=True,
|
|
1014
|
+
structured_output_completed=event.structured_output_completed,
|
|
1015
|
+
created_at=event.timestamp,
|
|
1016
|
+
)
|
|
1017
|
+
finally:
|
|
1018
|
+
if self._state.run.generation == terminal_generation and s.agent_running:
|
|
1019
|
+
ui.mark_terminal_title_completed()
|
|
1020
|
+
s.set_agent_running(False)
|
|
1021
|
+
terminal_owned = True
|
|
1022
|
+
if not terminal_owned:
|
|
1023
|
+
s.debug("InvocationMessage", "ignored stale terminal completion")
|
|
1024
|
+
return
|
|
1025
|
+
self._callbacks.post_gc_message(terminal_request)
|
|
1026
|
+
if was_live:
|
|
1027
|
+
self._notify(NotificationEvent.TURN_COMPLETE)
|
|
1028
|
+
s.debug("InvocationMessage", f"({len(event.text)} chars)")
|
|
1029
|
+
else:
|
|
1030
|
+
ui.show_status(STATUS_STREAMING.bind())
|
|
1031
|
+
await ui.add_agent_message(event.text, is_final=False, created_at=event.timestamp)
|
|
1032
|
+
|
|
1033
|
+
async def on_presentation_attempt_accepted(self, event: InvocationPresentationAttemptAccepted) -> None:
|
|
1034
|
+
"""Commit canonical provisional text and remove rejected siblings."""
|
|
1035
|
+
if event.origin.kind == "sub_agent":
|
|
1036
|
+
await self.on_sub_agent_presentation_accepted(event)
|
|
1037
|
+
return
|
|
1038
|
+
if event.origin.kind != "turn":
|
|
1039
|
+
return
|
|
1040
|
+
self._state.render_gate.accept_presentation_attempt(event.attempt_id, event.segment_ids)
|
|
1041
|
+
await self._ui().accept_presentation_attempt(event.attempt_id, event.segment_ids)
|
|
1042
|
+
|
|
1043
|
+
async def on_presentation_attempt_rejected(self, event: InvocationPresentationAttemptRejected) -> None:
|
|
1044
|
+
"""Retract provisional text from a rejected response attempt."""
|
|
1045
|
+
if event.origin.kind == "sub_agent":
|
|
1046
|
+
await self.on_sub_agent_presentation_rejected(event)
|
|
1047
|
+
return
|
|
1048
|
+
if event.origin.kind != "turn":
|
|
1049
|
+
return
|
|
1050
|
+
self._state.render_gate.reject_presentation_attempt(event.attempt_id)
|
|
1051
|
+
await self._ui().reject_presentation_attempt(event.attempt_id)
|
|
1052
|
+
|
|
1053
|
+
async def on_injection_outcome(self, event: UserInjectResult) -> None:
|
|
1054
|
+
s = self
|
|
1055
|
+
ui = self._ui()
|
|
1056
|
+
|
|
1057
|
+
# Only the tracked pending injection may touch the input bar. A result
|
|
1058
|
+
# for an id the user already cancelled (or replaced with a new submit)
|
|
1059
|
+
# must not clear or unlock text they are editing; id-less results come
|
|
1060
|
+
# from frontends without pending tracking and keep legacy behavior.
|
|
1061
|
+
pending = self._state.pending_injection
|
|
1062
|
+
owns_input = event.injection_id is None or pending.matches(event.injection_id)
|
|
1063
|
+
|
|
1064
|
+
if event.consumed:
|
|
1065
|
+
try:
|
|
1066
|
+
# Render the bubble even when cancelled-too-late: the model
|
|
1067
|
+
# did see this text, so the transcript must show it.
|
|
1068
|
+
await ui.add_user_injection(event.text, created_at=event.created_at)
|
|
1069
|
+
s.update_toc()
|
|
1070
|
+
s.debug("UserInjectResult", "consumed" if owns_input else "consumed (stale id)")
|
|
1071
|
+
finally:
|
|
1072
|
+
if owns_input:
|
|
1073
|
+
pending.clear()
|
|
1074
|
+
ui.unlock_input_after_consumed_injection()
|
|
1075
|
+
elif owns_input:
|
|
1076
|
+
pending.clear()
|
|
1077
|
+
ui.unlock_input_after_abandoned_injection()
|
|
1078
|
+
s.debug("UserInjectResult", "abandoned (text preserved)")
|
|
1079
|
+
else:
|
|
1080
|
+
s.debug("UserInjectResult", "abandoned (stale id, ignored)")
|
|
1081
|
+
|
|
1082
|
+
async def on_tool_start(self, event: InvocationToolCallStart) -> None:
|
|
1083
|
+
if event.origin.kind == "sub_agent":
|
|
1084
|
+
await self.on_sub_agent_tool_start(event)
|
|
1085
|
+
return
|
|
1086
|
+
if event.origin.kind != "turn":
|
|
1087
|
+
return
|
|
1088
|
+
s = self
|
|
1089
|
+
if not s.agent_running:
|
|
1090
|
+
return
|
|
1091
|
+
ui = self._ui()
|
|
1092
|
+
ui.start_tool_status(event.tool_name)
|
|
1093
|
+
args_json = json.dumps(event.args, ensure_ascii=False) if event.args else ""
|
|
1094
|
+
provider_status = normalize_hosted_tool_status(event.provider_status)
|
|
1095
|
+
canonical_status = (
|
|
1096
|
+
provider_status.value
|
|
1097
|
+
if event.provider_hosted and provider_status in {HostedToolStatus.PENDING, HostedToolStatus.RUNNING}
|
|
1098
|
+
else "running"
|
|
1099
|
+
)
|
|
1100
|
+
await ui.add_tool_start(
|
|
1101
|
+
event.call_id,
|
|
1102
|
+
event.tool_name,
|
|
1103
|
+
event.tool_kind,
|
|
1104
|
+
args_json,
|
|
1105
|
+
args=event.args,
|
|
1106
|
+
provider_hosted=event.provider_hosted,
|
|
1107
|
+
hosted_family=event.hosted_family,
|
|
1108
|
+
provider=event.provider,
|
|
1109
|
+
provider_item_type=event.provider_item_type,
|
|
1110
|
+
provider_status=event.provider_status,
|
|
1111
|
+
provider_call_id=event.provider_call_id,
|
|
1112
|
+
canonical_status=canonical_status,
|
|
1113
|
+
)
|
|
1114
|
+
self._live_diff.record_tool_start(event.call_id, event.tool_name, event.args)
|
|
1115
|
+
s.debug("InvocationToolCallStart", f"{event.tool_name}")
|
|
1116
|
+
|
|
1117
|
+
async def on_tool_progress(self, event: InvocationToolCallProgress) -> None:
|
|
1118
|
+
if event.origin.kind == "sub_agent":
|
|
1119
|
+
await self.on_sub_agent_tool_progress(event)
|
|
1120
|
+
return
|
|
1121
|
+
if event.origin.kind != "turn":
|
|
1122
|
+
return
|
|
1123
|
+
s = self
|
|
1124
|
+
if not s.agent_running:
|
|
1125
|
+
return
|
|
1126
|
+
self._ui().update_tool_progress(
|
|
1127
|
+
event.call_id,
|
|
1128
|
+
event.lines,
|
|
1129
|
+
image_contents=event.image_contents,
|
|
1130
|
+
snapshot_metadata=event.snapshot_metadata,
|
|
1131
|
+
provider_status=event.provider_status,
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
async def on_tool_args_updated(self, event: InvocationToolCallArgsUpdated) -> None:
|
|
1135
|
+
if event.origin.kind == "sub_agent":
|
|
1136
|
+
await self.on_sub_agent_tool_args_updated(event)
|
|
1137
|
+
return
|
|
1138
|
+
if event.origin.kind != "turn":
|
|
1139
|
+
return
|
|
1140
|
+
s = self
|
|
1141
|
+
if not s.agent_running:
|
|
1142
|
+
return
|
|
1143
|
+
self._ui().update_tool_args(event.call_id, event.args)
|
|
1144
|
+
s.debug("InvocationToolCallArgsUpdated", f"{event.tool_name}")
|
|
1145
|
+
|
|
1146
|
+
async def on_tool_status_updated(self, event: InvocationToolCallStatusUpdated) -> None:
|
|
1147
|
+
if event.origin.kind == "sub_agent":
|
|
1148
|
+
await self.on_sub_agent_tool_status_updated(event)
|
|
1149
|
+
return
|
|
1150
|
+
if event.origin.kind != "turn":
|
|
1151
|
+
return
|
|
1152
|
+
s = self
|
|
1153
|
+
if not s.agent_running:
|
|
1154
|
+
return
|
|
1155
|
+
self._ui().update_tool_status(
|
|
1156
|
+
event.call_id,
|
|
1157
|
+
event.status,
|
|
1158
|
+
provider_status=event.provider_status,
|
|
1159
|
+
metadata=event.metadata,
|
|
1160
|
+
)
|
|
1161
|
+
s.debug("ToolCallStatusUpdated", f"{event.tool_name}: {event.status}")
|
|
1162
|
+
|
|
1163
|
+
async def on_tool_result(self, event: InvocationToolCallResult) -> None:
|
|
1164
|
+
if event.origin.kind == "sub_agent":
|
|
1165
|
+
await self.on_sub_agent_tool_result(event)
|
|
1166
|
+
return
|
|
1167
|
+
if event.origin.kind != "turn":
|
|
1168
|
+
return
|
|
1169
|
+
s = self
|
|
1170
|
+
if not s.agent_running:
|
|
1171
|
+
return
|
|
1172
|
+
ui = self._ui()
|
|
1173
|
+
file_snapshot = event.metadata.get("file_snapshot")
|
|
1174
|
+
hashes = _metadata_hashes(event.metadata.get("file_mutation_hashes"))
|
|
1175
|
+
chat_file_snapshot = _chat_file_snapshot_payload(file_snapshot, hashes, s.state_store, ui.chat_session_id())
|
|
1176
|
+
approval = event.metadata.get("approval")
|
|
1177
|
+
canonical_status = canonical_tool_result_status(event.provider_status, event.metadata)
|
|
1178
|
+
await ui.add_tool_result(
|
|
1179
|
+
event.call_id,
|
|
1180
|
+
event.tool_name,
|
|
1181
|
+
event.result or "",
|
|
1182
|
+
event.duration_ms,
|
|
1183
|
+
image_contents=event.image_contents,
|
|
1184
|
+
file_snapshot=chat_file_snapshot,
|
|
1185
|
+
approval=approval,
|
|
1186
|
+
metadata=event.metadata,
|
|
1187
|
+
artifacts=event.artifacts,
|
|
1188
|
+
provider_status=event.provider_status,
|
|
1189
|
+
canonical_status=canonical_status,
|
|
1190
|
+
)
|
|
1191
|
+
self._questions().finish_inline_for_tool_result(event.call_id)
|
|
1192
|
+
self._live_diff.record_tool_result(event)
|
|
1193
|
+
s.refresh_git_branch()
|
|
1194
|
+
ui.show_status(STATUS_THINKING.bind())
|
|
1195
|
+
from chrys.app.tui.widgets.chat.tool_call import fmt_duration
|
|
1196
|
+
|
|
1197
|
+
s.debug("InvocationToolCallResult", f"{event.tool_name} ({fmt_duration(event.duration_ms)})")
|
|
1198
|
+
|
|
1199
|
+
async def on_sub_agent_invocation_start(self, event: InvocationStarted) -> None:
|
|
1200
|
+
"""Link the just-started invocation to its mounted widget.
|
|
1201
|
+
|
|
1202
|
+
Fires before the sub-agent's first LLM call so the widget lookup
|
|
1203
|
+
by ``invocation_id`` works for every subsequent event — including
|
|
1204
|
+
``InvocationRetryAttempt`` banners that would otherwise be dropped
|
|
1205
|
+
when the first LLM call hits transient errors.
|
|
1206
|
+
"""
|
|
1207
|
+
if not _chat_sub_agent(event.origin):
|
|
1208
|
+
return
|
|
1209
|
+
s = self
|
|
1210
|
+
if not s.agent_running:
|
|
1211
|
+
return
|
|
1212
|
+
|
|
1213
|
+
self._ui().link_sub_agent_invocation(
|
|
1214
|
+
event.parent_call_id,
|
|
1215
|
+
event.origin.invocation_id,
|
|
1216
|
+
event.agent_name,
|
|
1217
|
+
event.sub_agent_log_file,
|
|
1218
|
+
tool_name=event.tool_name,
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
async def on_sub_agent_message(self, event: InvocationMessage) -> None:
|
|
1222
|
+
"""Append invocation-scoped assistant prose to the nested transcript."""
|
|
1223
|
+
if not _chat_sub_agent(event.origin):
|
|
1224
|
+
return
|
|
1225
|
+
if not self.agent_running:
|
|
1226
|
+
return
|
|
1227
|
+
self._ui().add_sub_agent_message(
|
|
1228
|
+
event.agent_name,
|
|
1229
|
+
event.origin.invocation_id,
|
|
1230
|
+
event.text,
|
|
1231
|
+
presentation=event.presentation,
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
async def on_sub_agent_presentation_accepted(self, event: InvocationPresentationAttemptAccepted) -> None:
|
|
1235
|
+
"""Commit provisional nested assistant segments."""
|
|
1236
|
+
if not _chat_sub_agent(event.origin):
|
|
1237
|
+
return
|
|
1238
|
+
if not self.agent_running:
|
|
1239
|
+
return
|
|
1240
|
+
self._ui().accept_sub_agent_presentation(
|
|
1241
|
+
event.origin.invocation_id,
|
|
1242
|
+
event.attempt_id,
|
|
1243
|
+
event.segment_ids,
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
async def on_sub_agent_presentation_rejected(self, event: InvocationPresentationAttemptRejected) -> None:
|
|
1247
|
+
"""Retract provisional nested assistant segments."""
|
|
1248
|
+
if not _chat_sub_agent(event.origin):
|
|
1249
|
+
return
|
|
1250
|
+
if not self.agent_running:
|
|
1251
|
+
return
|
|
1252
|
+
self._ui().reject_sub_agent_presentation(event.origin.invocation_id, event.attempt_id)
|
|
1253
|
+
|
|
1254
|
+
async def on_sub_agent_tool_start(self, event: InvocationToolCallStart) -> None:
|
|
1255
|
+
if not _chat_sub_agent(event.origin):
|
|
1256
|
+
return
|
|
1257
|
+
s = self
|
|
1258
|
+
if not s.agent_running:
|
|
1259
|
+
return
|
|
1260
|
+
|
|
1261
|
+
await self._ui().add_sub_agent_tool_start(
|
|
1262
|
+
event.agent_name,
|
|
1263
|
+
event.origin.invocation_id,
|
|
1264
|
+
event.tool_name,
|
|
1265
|
+
event.args,
|
|
1266
|
+
event.call_id,
|
|
1267
|
+
tool_kind=event.tool_kind,
|
|
1268
|
+
provider_hosted=event.provider_hosted,
|
|
1269
|
+
hosted_family=event.hosted_family,
|
|
1270
|
+
provider=event.provider,
|
|
1271
|
+
provider_item_type=event.provider_item_type,
|
|
1272
|
+
provider_status=event.provider_status,
|
|
1273
|
+
provider_call_id=event.provider_call_id,
|
|
1274
|
+
)
|
|
1275
|
+
self._live_diff.record_tool_start(event.call_id, event.tool_name, event.args)
|
|
1276
|
+
|
|
1277
|
+
async def on_sub_agent_tool_result(self, event: InvocationToolCallResult) -> None:
|
|
1278
|
+
if not _chat_sub_agent(event.origin):
|
|
1279
|
+
return
|
|
1280
|
+
s = self
|
|
1281
|
+
if not s.agent_running:
|
|
1282
|
+
return
|
|
1283
|
+
|
|
1284
|
+
approval = event.metadata.get("approval")
|
|
1285
|
+
canonical_status = canonical_tool_result_status(event.provider_status, event.metadata)
|
|
1286
|
+
self._ui().complete_sub_agent_tool(
|
|
1287
|
+
event.agent_name,
|
|
1288
|
+
event.origin.invocation_id,
|
|
1289
|
+
event.call_id,
|
|
1290
|
+
event.result or "",
|
|
1291
|
+
event.duration_ms,
|
|
1292
|
+
image_contents=event.image_contents,
|
|
1293
|
+
artifacts=event.artifacts,
|
|
1294
|
+
approval=approval,
|
|
1295
|
+
metadata=event.metadata,
|
|
1296
|
+
provider_status=event.provider_status,
|
|
1297
|
+
canonical_status=canonical_status,
|
|
1298
|
+
)
|
|
1299
|
+
self._live_diff.record_tool_result(event)
|
|
1300
|
+
s.refresh_git_branch()
|
|
1301
|
+
|
|
1302
|
+
async def on_sub_agent_tool_args_updated(self, event: InvocationToolCallArgsUpdated) -> None:
|
|
1303
|
+
if not _chat_sub_agent(event.origin):
|
|
1304
|
+
return
|
|
1305
|
+
if not self.agent_running:
|
|
1306
|
+
return
|
|
1307
|
+
self._ui().update_sub_agent_tool_args(event.origin.invocation_id, event.call_id, event.args)
|
|
1308
|
+
self.debug("InvocationToolCallArgsUpdated", event.tool_name)
|
|
1309
|
+
|
|
1310
|
+
async def on_sub_agent_tool_status_updated(self, event: InvocationToolCallStatusUpdated) -> None:
|
|
1311
|
+
if not _chat_sub_agent(event.origin):
|
|
1312
|
+
return
|
|
1313
|
+
if not self.agent_running:
|
|
1314
|
+
return
|
|
1315
|
+
self._ui().update_sub_agent_tool_status(
|
|
1316
|
+
event.origin.invocation_id,
|
|
1317
|
+
event.call_id,
|
|
1318
|
+
event.status,
|
|
1319
|
+
metadata=event.metadata,
|
|
1320
|
+
provider_status=event.provider_status,
|
|
1321
|
+
)
|
|
1322
|
+
self.debug("InvocationToolCallStatusUpdated", f"{event.tool_name}: {event.status}")
|
|
1323
|
+
|
|
1324
|
+
async def on_sub_agent_tool_progress(self, event: InvocationToolCallProgress) -> None:
|
|
1325
|
+
if not _chat_sub_agent(event.origin):
|
|
1326
|
+
return
|
|
1327
|
+
if not self.agent_running:
|
|
1328
|
+
return
|
|
1329
|
+
self._ui().update_sub_agent_tool_progress(
|
|
1330
|
+
event.origin.invocation_id,
|
|
1331
|
+
event.call_id,
|
|
1332
|
+
event.lines,
|
|
1333
|
+
image_contents=event.image_contents,
|
|
1334
|
+
snapshot_metadata=event.snapshot_metadata,
|
|
1335
|
+
provider_status=event.provider_status,
|
|
1336
|
+
)
|
|
1337
|
+
self.debug("InvocationToolCallProgress", event.tool_name)
|
|
1338
|
+
|
|
1339
|
+
async def on_sub_agent_progress(self, event: InvocationProgress) -> None:
|
|
1340
|
+
if not _chat_sub_agent(event.origin):
|
|
1341
|
+
return
|
|
1342
|
+
s = self
|
|
1343
|
+
if not s.agent_running:
|
|
1344
|
+
return
|
|
1345
|
+
|
|
1346
|
+
self._ui().update_sub_agent_progress(
|
|
1347
|
+
event.origin.invocation_id,
|
|
1348
|
+
event.tool_call_count,
|
|
1349
|
+
event.total_tokens,
|
|
1350
|
+
event.total_usage_tokens,
|
|
1351
|
+
event.usage_unreported_attempts,
|
|
1352
|
+
)
|
|
1353
|
+
|
|
1354
|
+
async def on_sub_agent_compaction_started(self, event: InvocationCompactionStarted) -> None:
|
|
1355
|
+
"""Show a live compaction line inside the owning sub-agent card."""
|
|
1356
|
+
if not _chat_sub_agent(event.origin):
|
|
1357
|
+
return
|
|
1358
|
+
s = self
|
|
1359
|
+
if not s.agent_running:
|
|
1360
|
+
return
|
|
1361
|
+
|
|
1362
|
+
self._ui().add_sub_agent_compaction_start(event.agent_name, event.origin.invocation_id, event.compaction_id)
|
|
1363
|
+
s.debug("InvocationCompactionStarted", f"{event.agent_name} {event.phase}")
|
|
1364
|
+
|
|
1365
|
+
async def on_sub_agent_compaction_finished(self, event: InvocationCompactionFinished) -> None:
|
|
1366
|
+
"""Flip the sub-agent compaction line to its terminal state.
|
|
1367
|
+
|
|
1368
|
+
Deliberately not gated on ``agent_running``: a cancel-outcome signal
|
|
1369
|
+
can land while the run is tearing down, and finalizing a line on an
|
|
1370
|
+
already-aborted card is a safe no-op.
|
|
1371
|
+
"""
|
|
1372
|
+
if not _chat_sub_agent(event.origin):
|
|
1373
|
+
return
|
|
1374
|
+
s = self
|
|
1375
|
+
|
|
1376
|
+
self._ui().complete_sub_agent_compaction(
|
|
1377
|
+
event.origin.invocation_id,
|
|
1378
|
+
event.compaction_id,
|
|
1379
|
+
outcome=event.outcome,
|
|
1380
|
+
duration_ms=event.duration_ms,
|
|
1381
|
+
format_violation=event.format_violation,
|
|
1382
|
+
failure_reason=event.failure_reason,
|
|
1383
|
+
)
|
|
1384
|
+
from chrys.app.tui.widgets.chat.tool_call import fmt_duration
|
|
1385
|
+
|
|
1386
|
+
format_warning = f" — {event.format_violation}" if event.format_violation else ""
|
|
1387
|
+
s.debug(
|
|
1388
|
+
"InvocationCompactionFinished",
|
|
1389
|
+
f"{event.agent_name} {event.outcome} ({fmt_duration(event.duration_ms)}){format_warning}",
|
|
1390
|
+
)
|
|
1391
|
+
|
|
1392
|
+
async def on_sub_agent_compaction_committed(self, event: InvocationCompactionCommitted) -> None:
|
|
1393
|
+
"""Bump the sub-agent card's compaction counter for a committed round.
|
|
1394
|
+
|
|
1395
|
+
Like the finished handler, deliberately not gated on
|
|
1396
|
+
``agent_running`` — the committed signal trails finished(ok) and
|
|
1397
|
+
may land during run teardown; counting on a finalized card is
|
|
1398
|
+
harmless.
|
|
1399
|
+
"""
|
|
1400
|
+
if not _chat_sub_agent(event.origin):
|
|
1401
|
+
return
|
|
1402
|
+
s = self
|
|
1403
|
+
|
|
1404
|
+
self._ui().record_sub_agent_compaction_committed(event.origin.invocation_id, event.compaction_id)
|
|
1405
|
+
s.debug("InvocationCompactionCommitted", f"{event.agent_name} {event.compaction_id}")
|
|
1406
|
+
|
|
1407
|
+
async def on_sub_agent_retry_attempt(self, event: InvocationRetryAttempt) -> None:
|
|
1408
|
+
"""Render an auto-retry banner inside the owning sub-agent card."""
|
|
1409
|
+
if not _chat_sub_agent(event.origin):
|
|
1410
|
+
return
|
|
1411
|
+
message = (
|
|
1412
|
+
event.message
|
|
1413
|
+
if event.display_message is None
|
|
1414
|
+
else self._render_display_with_hint(event.display_message, event.display_hint)
|
|
1415
|
+
)
|
|
1416
|
+
self._ui().sub_agent_retry_attempt(
|
|
1417
|
+
event.origin.invocation_id,
|
|
1418
|
+
message,
|
|
1419
|
+
event.attempt,
|
|
1420
|
+
event.max_attempts,
|
|
1421
|
+
event.delay_seconds,
|
|
1422
|
+
)
|
|
1423
|
+
s = self
|
|
1424
|
+
s.debug(
|
|
1425
|
+
"Retry",
|
|
1426
|
+
f"{event.agent_name}: {event.message} ({event.attempt}/{event.max_attempts})",
|
|
1427
|
+
)
|
|
1428
|
+
|
|
1429
|
+
async def on_sub_agent_paused(self, event: InvocationPaused) -> None:
|
|
1430
|
+
"""Flip the sub-agent card into its paused state with Retry/Abort buttons.
|
|
1431
|
+
|
|
1432
|
+
The parent is normally still running (awaiting the sub-agent's
|
|
1433
|
+
tool call result), so in the happy path no gate is needed. But
|
|
1434
|
+
during a user interrupt the frontend flips the run state's
|
|
1435
|
+
``agent_running`` to False BEFORE the backend cascade finishes, so a late
|
|
1436
|
+
``InvocationPaused`` that was already in-flight when interrupt
|
|
1437
|
+
fired can arrive after the UI has already torn the run down.
|
|
1438
|
+
Skipping it here keeps cards from flickering into a stale paused
|
|
1439
|
+
state with live Retry/Abort buttons that point at a controller
|
|
1440
|
+
the engine has already dropped.
|
|
1441
|
+
"""
|
|
1442
|
+
if not _chat_sub_agent(event.origin):
|
|
1443
|
+
return
|
|
1444
|
+
s = self
|
|
1445
|
+
if not s.agent_running:
|
|
1446
|
+
return
|
|
1447
|
+
|
|
1448
|
+
self._ui().sub_agent_paused(
|
|
1449
|
+
event.origin.invocation_id,
|
|
1450
|
+
event.reason,
|
|
1451
|
+
event.last_error,
|
|
1452
|
+
event.retry_attempts,
|
|
1453
|
+
event.diagnostic_path,
|
|
1454
|
+
last_error_display=(
|
|
1455
|
+
None
|
|
1456
|
+
if event.last_error_display is None
|
|
1457
|
+
else self._render_display_with_hint(event.last_error_display, event.last_error_hint)
|
|
1458
|
+
),
|
|
1459
|
+
)
|
|
1460
|
+
|
|
1461
|
+
async def on_sub_agent_resumed_after_pause(self, event: InvocationResumed) -> None:
|
|
1462
|
+
"""Clear the paused state — the controller is running again."""
|
|
1463
|
+
if not _chat_sub_agent(event.origin):
|
|
1464
|
+
return
|
|
1465
|
+
self._ui().sub_agent_resumed_after_pause(event.origin.invocation_id)
|
|
1466
|
+
|
|
1467
|
+
async def on_sub_agent_cascade_aborted(self, event: InvocationCascadeAborted) -> None:
|
|
1468
|
+
"""Mark the sub-agent card as cancelled by a global interrupt."""
|
|
1469
|
+
if not _chat_sub_agent(event.origin):
|
|
1470
|
+
return
|
|
1471
|
+
self._ui().sub_agent_cascade_aborted(event.origin.invocation_id)
|
|
1472
|
+
|
|
1473
|
+
async def on_sub_agent_aborted(self, event: InvocationAborted) -> None:
|
|
1474
|
+
"""Mark the sub-agent card as aborted by the user after a pause.
|
|
1475
|
+
|
|
1476
|
+
Cleared independently of the parent :class:`InvocationToolCallResult` so the
|
|
1477
|
+
paused banner disappears the moment the controller publishes
|
|
1478
|
+
``InvocationAborted`` — without this, the card could stay visually
|
|
1479
|
+
paused if the parent tool result is delayed or lost.
|
|
1480
|
+
"""
|
|
1481
|
+
if not _chat_sub_agent(event.origin):
|
|
1482
|
+
return
|
|
1483
|
+
self._ui().sub_agent_aborted(event.origin.invocation_id, event.last_error)
|
|
1484
|
+
|
|
1485
|
+
# -- Live /diff accumulation helpers -----------------------------------
|
|
1486
|
+
|
|
1487
|
+
def _accumulate_live_mutation(
|
|
1488
|
+
self,
|
|
1489
|
+
path: str,
|
|
1490
|
+
before_text: str,
|
|
1491
|
+
after_text: str,
|
|
1492
|
+
op_str: str | None = None,
|
|
1493
|
+
*,
|
|
1494
|
+
bytes_changed: bool | None = None,
|
|
1495
|
+
before_hash: str | None = None,
|
|
1496
|
+
after_hash: str | None = None,
|
|
1497
|
+
source: str = "",
|
|
1498
|
+
) -> None:
|
|
1499
|
+
"""Record a single file mutation for live /diff."""
|
|
1500
|
+
self._live_diff.accumulate_live_mutation(
|
|
1501
|
+
path,
|
|
1502
|
+
before_text,
|
|
1503
|
+
after_text,
|
|
1504
|
+
op_str,
|
|
1505
|
+
bytes_changed=bytes_changed,
|
|
1506
|
+
before_hash=before_hash,
|
|
1507
|
+
after_hash=after_hash,
|
|
1508
|
+
source=source,
|
|
1509
|
+
)
|
|
1510
|
+
|
|
1511
|
+
def _accumulate_shell_snapshots(self, metadata: dict[str, object]) -> None:
|
|
1512
|
+
"""Accumulate shell-detected file mutations from event metadata."""
|
|
1513
|
+
self._live_diff.accumulate_shell_snapshots(metadata)
|
|
1514
|
+
|
|
1515
|
+
async def on_approval_request(self, event: ApprovalRequest) -> None:
|
|
1516
|
+
"""Queue an approval request and (maybe) show its dialog.
|
|
1517
|
+
|
|
1518
|
+
The backend decides the mode: BYPASS never publishes this event at
|
|
1519
|
+
all, MANUAL publishes with ``judging=False``, AUTO publishes with
|
|
1520
|
+
``judging=True`` and will later emit an ``ApprovalReviewed``. A
|
|
1521
|
+
judging request waits unseen for that verdict while
|
|
1522
|
+
``ui.approval.defer_while_judging`` is on; otherwise its dialog opens
|
|
1523
|
+
at once and the verdict updates it.
|
|
1524
|
+
"""
|
|
1525
|
+
await self._approval().on_request(event)
|
|
1526
|
+
|
|
1527
|
+
async def on_approval_cancelled(self, event: ApprovalCancelled) -> None:
|
|
1528
|
+
"""Dismiss an abandoned approval without sending an ApprovalResponse."""
|
|
1529
|
+
await self._approval().on_cancelled(event)
|
|
1530
|
+
|
|
1531
|
+
def _show_next_approval(self) -> None:
|
|
1532
|
+
"""Pop the next queued approval request and show its dialog.
|
|
1533
|
+
|
|
1534
|
+
Drains any cached judge verdict that arrived while the request was
|
|
1535
|
+
still queued (parallel tool execution in AUTO mode — see
|
|
1536
|
+
``_pending_verdicts``):
|
|
1537
|
+
|
|
1538
|
+
- Approved pre-mount → skip the dialog entirely and continue to the
|
|
1539
|
+
next queued request; the backend already ran the tool.
|
|
1540
|
+
- Flagged pre-mount → open the dialog already flagged, the concern
|
|
1541
|
+
shown and the reason focused, never a stuck "Evaluating" spinner.
|
|
1542
|
+
"""
|
|
1543
|
+
self._approval().show_next()
|
|
1544
|
+
|
|
1545
|
+
async def on_approval_reviewed(self, event: ApprovalReviewed) -> None:
|
|
1546
|
+
"""Deliver a judge verdict from the backend to the open dialog.
|
|
1547
|
+
|
|
1548
|
+
Four cases handled:
|
|
1549
|
+
|
|
1550
|
+
- Request deferred while judging → an approval drops it unseen; a
|
|
1551
|
+
flag queues it to open already flagged.
|
|
1552
|
+
- Matching dialog is live and undismissed → deliver verdict
|
|
1553
|
+
immediately (approved auto-dismisses; flagged shows the concern).
|
|
1554
|
+
- Dialog not yet pushed but request still queued → stash the verdict;
|
|
1555
|
+
it is applied when ``_show_next_approval`` eventually pops the
|
|
1556
|
+
request. Supports parallel tool execution in AUTO mode where
|
|
1557
|
+
judges for queued requests finish before their dialog is shown.
|
|
1558
|
+
- Late arrival for a request that was already user-dismissed or
|
|
1559
|
+
otherwise resolved → drop silently.
|
|
1560
|
+
"""
|
|
1561
|
+
await self._approval().on_reviewed(event)
|
|
1562
|
+
|
|
1563
|
+
async def on_approval_mode_updated(self, event: ApprovalModeUpdated) -> None:
|
|
1564
|
+
"""Sync the header badge + local cache from the backend's mode."""
|
|
1565
|
+
if not self._callbacks.accept_approval_update(event):
|
|
1566
|
+
return
|
|
1567
|
+
s = self
|
|
1568
|
+
try:
|
|
1569
|
+
mode = ApprovalMode(event.mode)
|
|
1570
|
+
except ValueError:
|
|
1571
|
+
return
|
|
1572
|
+
changed = s.approval_mode != mode
|
|
1573
|
+
s.approval_mode = mode
|
|
1574
|
+
self._ui().set_header_approval_mode(mode)
|
|
1575
|
+
label = mode.value.upper()
|
|
1576
|
+
# Only toast on a genuine change — skip the initial session-start sync.
|
|
1577
|
+
if changed:
|
|
1578
|
+
mode_label = self._render_display(APPROVAL_MODE_MESSAGES[mode].bind())
|
|
1579
|
+
s.notify(_APPROVAL_MODE_CHANGED.bind(mode=mode_label), title=_APPROVAL_TITLE.bind(), timeout=2)
|
|
1580
|
+
s.debug("ApprovalMode", label)
|
|
1581
|
+
|
|
1582
|
+
async def on_question_to_user(self, event: QuestionToUser) -> None:
|
|
1583
|
+
await self._questions().on_question(event)
|
|
1584
|
+
|
|
1585
|
+
def clear_pending_questions(self) -> None:
|
|
1586
|
+
"""Drop any ask_user UI state after the backend turn is cancelled."""
|
|
1587
|
+
self._questions().clear_pending()
|
|
1588
|
+
|
|
1589
|
+
def _show_next_question(self) -> None:
|
|
1590
|
+
"""Pop the next queued question and show its dialog."""
|
|
1591
|
+
self._questions().show_next()
|
|
1592
|
+
|
|
1593
|
+
def _show_question_inline(self, event: QuestionToUser, draft: PromptDraft | None = None) -> bool:
|
|
1594
|
+
"""Move an active ask_user prompt from modal UI into its tool renderer."""
|
|
1595
|
+
return self._gateway().show_question_inline(event, draft)
|
|
1596
|
+
|
|
1597
|
+
def _question_can_reopen_modal(self, event: QuestionToUser) -> bool:
|
|
1598
|
+
"""Return whether a failed inline handoff should reopen the modal."""
|
|
1599
|
+
return self._gateway().question_can_reopen_modal(event)
|
|
1600
|
+
|
|
1601
|
+
async def on_ask_user_timed_out(self, event: AskUserTimedOut) -> None:
|
|
1602
|
+
"""Dismiss or dequeue an ask_user dialog after the backend timeout fires."""
|
|
1603
|
+
await self._questions().on_timed_out(event)
|
|
1604
|
+
|
|
1605
|
+
async def on_usage_update(self, event: UsageUpdate) -> None:
|
|
1606
|
+
s = self
|
|
1607
|
+
if event.session_id and s.main_usage_source_id and event.session_id != s.main_usage_source_id:
|
|
1608
|
+
return
|
|
1609
|
+
|
|
1610
|
+
# Only treat an event as the main session window when both sides have
|
|
1611
|
+
# bound a non-empty source id and they match. Earlier we accepted any
|
|
1612
|
+
# empty event as the main window, but that silently mis-routed any
|
|
1613
|
+
# caller that forgot to set ``usage_source_id`` and also misclassified
|
|
1614
|
+
# parent usage that arrived before SessionReady set
|
|
1615
|
+
# ``main_usage_source_id``.
|
|
1616
|
+
is_session_window = bool(s.main_usage_source_id) and (event.usage_source_id == s.main_usage_source_id)
|
|
1617
|
+
if is_session_window:
|
|
1618
|
+
s.last_usage_tokens = event.total_tokens
|
|
1619
|
+
# Session totals advance with every UsageUpdate (parent or sub-agent) —
|
|
1620
|
+
# the cumulative figure is global, not per-source.
|
|
1621
|
+
s.last_total_session_tokens = event.total_session_tokens
|
|
1622
|
+
if is_session_window:
|
|
1623
|
+
# Main session: refresh used/max gauge, sparkline, AND totals row.
|
|
1624
|
+
self._ui().set_context_usage_state(
|
|
1625
|
+
ContextUsageState.with_window(
|
|
1626
|
+
used_tokens=event.total_tokens,
|
|
1627
|
+
max_context_tokens=event.max_context_tokens or DEFAULT_MAX_CONTEXT_TOKENS,
|
|
1628
|
+
total_session_tokens=event.total_session_tokens,
|
|
1629
|
+
total_session_input_tokens=event.total_session_input_tokens,
|
|
1630
|
+
total_session_output_tokens=event.total_session_output_tokens,
|
|
1631
|
+
total_session_cache_hit_tokens=event.total_session_cache_hit_tokens,
|
|
1632
|
+
)
|
|
1633
|
+
)
|
|
1634
|
+
else:
|
|
1635
|
+
# Sub-agent: keep the main session's used/max gauge & sparkline,
|
|
1636
|
+
# but let the cumulative totals row advance. Without this gate the
|
|
1637
|
+
# sidebar gauge would flicker between parent and sub-agent windows.
|
|
1638
|
+
self._ui().set_context_usage_state(
|
|
1639
|
+
ContextUsageState.session_totals_only(
|
|
1640
|
+
s.context_usage_state,
|
|
1641
|
+
fallback_used_tokens=s.last_usage_tokens,
|
|
1642
|
+
total_session_tokens=event.total_session_tokens,
|
|
1643
|
+
total_session_input_tokens=event.total_session_input_tokens,
|
|
1644
|
+
total_session_output_tokens=event.total_session_output_tokens,
|
|
1645
|
+
total_session_cache_hit_tokens=event.total_session_cache_hit_tokens,
|
|
1646
|
+
)
|
|
1647
|
+
)
|
|
1648
|
+
label = f"Usage[{event.agent_profile}]" if event.agent_profile else "UsageUpdate"
|
|
1649
|
+
source = _usage_source_debug_suffix(event.usage_source_id, main_source_id=s.main_usage_source_id)
|
|
1650
|
+
s.debug(label, f"{event.total_tokens:,} ({event.pct:.1f}%) local={event.local_tokens:,}{source}")
|
|
1651
|
+
|
|
1652
|
+
async def on_todo_list_updated(self, event: TodoListUpdated) -> None:
|
|
1653
|
+
s = self
|
|
1654
|
+
|
|
1655
|
+
self._ui().set_todo_state(tuple(event.items))
|
|
1656
|
+
done = sum(1 for item in event.items if item.status == "completed")
|
|
1657
|
+
s.debug("TodoListUpdated", f"{done}/{len(event.items)} done")
|
|
1658
|
+
|
|
1659
|
+
async def on_compaction_started(self, event: CompactionStarted) -> None:
|
|
1660
|
+
"""Mount a live "Compacting conversation..." card in the transcript."""
|
|
1661
|
+
s = self
|
|
1662
|
+
if not s.agent_running:
|
|
1663
|
+
return
|
|
1664
|
+
|
|
1665
|
+
await self._ui().add_compaction_start(event.compaction_id)
|
|
1666
|
+
self._ui().show_status(STATUS_COMPACTING.bind())
|
|
1667
|
+
s.debug("CompactionStarted", event.phase)
|
|
1668
|
+
|
|
1669
|
+
async def on_compaction_finished(self, event: CompactionFinished) -> None:
|
|
1670
|
+
"""Finalize the live compaction card (summary note, failure, or cancel).
|
|
1671
|
+
|
|
1672
|
+
Deliberately not gated on ``agent_running``: the cancel-outcome
|
|
1673
|
+
signal is published while the interrupted run is tearing down, and
|
|
1674
|
+
the card must still stop spinning. With no matching live card the
|
|
1675
|
+
update is a no-op. The status bar is a different story: a canceled
|
|
1676
|
+
outcome only ever means an interrupted run, and during teardown it
|
|
1677
|
+
can arrive before the frontend flips ``agent_running`` — re-showing
|
|
1678
|
+
the spinner then would overwrite the "Interrupted by user" flash
|
|
1679
|
+
and leave a stuck "Thinking" bar, so canceled never restores it.
|
|
1680
|
+
"""
|
|
1681
|
+
s = self
|
|
1682
|
+
|
|
1683
|
+
self._ui().complete_compaction(
|
|
1684
|
+
event.compaction_id,
|
|
1685
|
+
outcome=event.outcome,
|
|
1686
|
+
duration_ms=event.duration_ms,
|
|
1687
|
+
last_words=event.last_words,
|
|
1688
|
+
format_violation=event.format_violation,
|
|
1689
|
+
failure_reason=event.failure_reason,
|
|
1690
|
+
)
|
|
1691
|
+
if s.agent_running and event.outcome != "canceled":
|
|
1692
|
+
self._ui().show_status(STATUS_THINKING.bind())
|
|
1693
|
+
from chrys.app.tui.widgets.chat.tool_call import fmt_duration
|
|
1694
|
+
|
|
1695
|
+
format_warning = f" — {event.format_violation}" if event.format_violation else ""
|
|
1696
|
+
failure_note = f" — {event.failure_reason}" if event.failure_reason else ""
|
|
1697
|
+
s.debug(
|
|
1698
|
+
"CompactionFinished",
|
|
1699
|
+
f"{event.outcome} ({fmt_duration(event.duration_ms)}){failure_note}{format_warning}",
|
|
1700
|
+
)
|
|
1701
|
+
|
|
1702
|
+
async def on_tool_compacted(self, event: ToolCompacted) -> None:
|
|
1703
|
+
s = self
|
|
1704
|
+
freed = event.tokens_before - event.tokens_after
|
|
1705
|
+
event_key = f"ToolCompacted:{event.phase}" if event.phase else "ToolCompacted"
|
|
1706
|
+
|
|
1707
|
+
if event.phase in ("phase1", "phase2"):
|
|
1708
|
+
turns_str = ", ".join(str(t) for t in event.turn_numbers) if event.turn_numbers else "?"
|
|
1709
|
+
action = "summarised" if event.phase == "phase1" else "removed"
|
|
1710
|
+
s.debug(
|
|
1711
|
+
event_key,
|
|
1712
|
+
f"{event.tokens_before:,} \u2192 {event.tokens_after:,} (-{freed:,}) [turns {turns_str} {action}]",
|
|
1713
|
+
)
|
|
1714
|
+
else:
|
|
1715
|
+
action = "removed"
|
|
1716
|
+
s.debug(
|
|
1717
|
+
event_key,
|
|
1718
|
+
f"{event.tokens_before:,} \u2192 {event.tokens_after:,} (-{freed:,}) "
|
|
1719
|
+
f"[{event.compacted_groups} tool calls {action}]",
|
|
1720
|
+
)
|
|
1721
|
+
|
|
1722
|
+
async def on_context_compressed(self, event: ContextCompressed) -> None:
|
|
1723
|
+
s = self
|
|
1724
|
+
|
|
1725
|
+
ui = self._ui()
|
|
1726
|
+
if await ui.add_context_fold(
|
|
1727
|
+
event.compressed_context_id,
|
|
1728
|
+
event.summary,
|
|
1729
|
+
event.freed_messages,
|
|
1730
|
+
event.turn_range,
|
|
1731
|
+
):
|
|
1732
|
+
s.update_toc()
|
|
1733
|
+
with contextlib.suppress(Exception):
|
|
1734
|
+
ui.add_compressed_block(
|
|
1735
|
+
event.compressed_context_id,
|
|
1736
|
+
event.summary,
|
|
1737
|
+
event.freed_messages,
|
|
1738
|
+
event.turn_range,
|
|
1739
|
+
)
|
|
1740
|
+
source_label = "auto" if event.source == "auto" else "agent"
|
|
1741
|
+
event_key = f"ContextCompressed:{event.source}" if event.source == "auto" else "ContextCompressed"
|
|
1742
|
+
s.debug(event_key, f"-{event.freed_messages:,} messages ({source_label})")
|
|
1743
|
+
|
|
1744
|
+
async def on_context_pressure(self, event: InvocationContextPressure) -> None:
|
|
1745
|
+
"""Surface a Phase-4 breaker trip instead of silently losing compaction."""
|
|
1746
|
+
if event.origin.root.kind == "workflow_node":
|
|
1747
|
+
return
|
|
1748
|
+
message = context_pressure_message(event.reason, source=event.source)
|
|
1749
|
+
s = self
|
|
1750
|
+
# The breaker already deduplicates this event once per logical turn.
|
|
1751
|
+
# Do not route it through ``on_warning``: that handler's screen-lifetime
|
|
1752
|
+
# cache would suppress the same pressure reason in every later turn or
|
|
1753
|
+
# sibling sub-agent invocation.
|
|
1754
|
+
s.notify(message, title=_WARNING_TITLE.bind(), severity="warning", timeout=8)
|
|
1755
|
+
s.debug(
|
|
1756
|
+
"InvocationContextPressure",
|
|
1757
|
+
f"{event.source}:{event.reason} attempts={event.attempts} "
|
|
1758
|
+
f"side_calls={event.side_call_tokens:,}/{event.side_call_token_budget:,}",
|
|
1759
|
+
)
|
|
1760
|
+
|
|
1761
|
+
async def on_warning(self, event: Warning) -> None:
|
|
1762
|
+
s = self
|
|
1763
|
+
submit_rejected = event.code in _SUBMIT_BLOCKING_WARNING_CODES and s.pending_user_submit_active
|
|
1764
|
+
if submit_rejected:
|
|
1765
|
+
s.block_pending_user_submit()
|
|
1766
|
+
if event.code in _IMAGE_WARNING_CODES:
|
|
1767
|
+
display = (
|
|
1768
|
+
event.message if event.display_message is None else self._render_display(event.display_message)
|
|
1769
|
+
)
|
|
1770
|
+
self._show_image_rejection_dialog(event.code, display)
|
|
1771
|
+
s.debug("ImageAttachmentBlocked", event.message.splitlines()[0][:80])
|
|
1772
|
+
return
|
|
1773
|
+
self._restore_pending_submit_text()
|
|
1774
|
+
key = _WarningDedupeKey(
|
|
1775
|
+
# Capability ownership is a model configuration fact. Its message
|
|
1776
|
+
# already includes the model profile, so /new must not reopen the
|
|
1777
|
+
# same modal merely because it allocated another session id.
|
|
1778
|
+
session_id=None if event.code == "hosted_web_tools_preferred" else event.session_id,
|
|
1779
|
+
code=event.code,
|
|
1780
|
+
message=event.message,
|
|
1781
|
+
)
|
|
1782
|
+
# Each rejected user action needs feedback, even when the reason is
|
|
1783
|
+
# unchanged. Only background warnings share the screen-lifetime cache.
|
|
1784
|
+
if key in self._seen_warnings and not submit_rejected:
|
|
1785
|
+
return
|
|
1786
|
+
self._seen_warnings.add(key)
|
|
1787
|
+
# A capability handover is worth one modal per configuration, not a toast the
|
|
1788
|
+
# user can miss; the dedupe key already scopes it to the producing fingerprint.
|
|
1789
|
+
if event.code == "hosted_web_tools_preferred":
|
|
1790
|
+
from chrys.app.tui.screens.dialogs.confirm import ConfirmDialog
|
|
1791
|
+
|
|
1792
|
+
dialog = ConfirmDialog(
|
|
1793
|
+
title=_WARNING_TITLE.bind(),
|
|
1794
|
+
message=event.display_message or event.message,
|
|
1795
|
+
confirm_label=_HOSTED_WEB_TOOLS_OK.bind(),
|
|
1796
|
+
cancel_label=None,
|
|
1797
|
+
confirm_variant="warning",
|
|
1798
|
+
locale_controller=self._locale_controller,
|
|
1799
|
+
)
|
|
1800
|
+
dialog.add_class("-warning-border")
|
|
1801
|
+
s.push_screen(dialog)
|
|
1802
|
+
s.debug("Warning", f"[{event.code}] {event.message[:80]}")
|
|
1803
|
+
return
|
|
1804
|
+
s.notify(event.display_message or event.message, title=_WARNING_TITLE.bind(), severity="warning", timeout=8)
|
|
1805
|
+
s.debug("Warning", f"[{event.code}] {event.message[:80]}")
|
|
1806
|
+
|
|
1807
|
+
async def on_error(self, event: Error) -> None:
|
|
1808
|
+
if self._callbacks.route_session_error(event):
|
|
1809
|
+
return
|
|
1810
|
+
s = self
|
|
1811
|
+
if event.code in _STALE_RUN_FILTERED_ERROR_CODES and self._predates_active_run(event):
|
|
1812
|
+
s.debug("Error", "ignored stale prior-run error")
|
|
1813
|
+
return
|
|
1814
|
+
ui = self._ui()
|
|
1815
|
+
was_live = s.agent_running
|
|
1816
|
+
terminal_generation = self._state.run.generation
|
|
1817
|
+
s.restoring_session = False
|
|
1818
|
+
# Agent-load failures should arrive as ``AgentLoadFailed``. Keep
|
|
1819
|
+
# this fallback for failures that happen before the load lifecycle is
|
|
1820
|
+
# established or arrive from older backend paths.
|
|
1821
|
+
raw_msg = event.message.strip()
|
|
1822
|
+
if event.code.startswith(_SESSION_FORK_ERROR_PREFIX):
|
|
1823
|
+
full_msg = raw_msg or self._fallback_error_message(event.code)
|
|
1824
|
+
display_msg = full_msg if event.display_message is None else self._render_display(event.display_message)
|
|
1825
|
+
severity: NotificationSeverity = "error" if event.code == "session_fork_failed" else "warning"
|
|
1826
|
+
s.on_session_fork_error(event, message=display_msg, severity=severity)
|
|
1827
|
+
s.debug("Error", f"[{event.code}] {full_msg[:60]}")
|
|
1828
|
+
return
|
|
1829
|
+
if event.code == _SESSION_CLEAR_FAILED_CODE:
|
|
1830
|
+
# /clear failed before deleting anything: keep the session, toast,
|
|
1831
|
+
# and skip the generic chat-error/retry-mode surface.
|
|
1832
|
+
full_msg = raw_msg or self._fallback_error_message(event.code)
|
|
1833
|
+
display_msg = full_msg if event.display_message is None else self._render_display(event.display_message)
|
|
1834
|
+
s.on_session_clear_error(event, message=display_msg)
|
|
1835
|
+
s.debug("Error", f"[{event.code}] {full_msg[:60]}")
|
|
1836
|
+
return
|
|
1837
|
+
full_msg = raw_msg or (
|
|
1838
|
+
self._render_display(_AGENT_FAILED_TO_LOAD.bind())
|
|
1839
|
+
if s.agent_loading
|
|
1840
|
+
else self._fallback_error_message(event.code)
|
|
1841
|
+
)
|
|
1842
|
+
# UI surfaces show the localized display when the producer attached
|
|
1843
|
+
# one; ``full_msg`` keeps the raw protocol text for debug lines.
|
|
1844
|
+
display_full = (
|
|
1845
|
+
full_msg
|
|
1846
|
+
if event.display_message is None
|
|
1847
|
+
else self._render_display_with_hint(event.display_message, event.display_hint)
|
|
1848
|
+
)
|
|
1849
|
+
if event.code == "session_in_use":
|
|
1850
|
+
if s.agent_loading:
|
|
1851
|
+
self.cancel_agent_load()
|
|
1852
|
+
s.set_agent_running(False)
|
|
1853
|
+
from chrys.app.tui.screens.dialogs.confirm import ConfirmDialog
|
|
1854
|
+
|
|
1855
|
+
dialog = ConfirmDialog(
|
|
1856
|
+
title=_SESSION_IN_USE_TITLE.bind(),
|
|
1857
|
+
message=_SESSION_IN_USE_MESSAGE.bind(message=DisplayBlock(display_full)),
|
|
1858
|
+
confirm_label=_SESSION_IN_USE_OK.bind(),
|
|
1859
|
+
cancel_label=None,
|
|
1860
|
+
confirm_variant="warning",
|
|
1861
|
+
locale_controller=self._locale_controller,
|
|
1862
|
+
bold_message_prefix=True,
|
|
1863
|
+
)
|
|
1864
|
+
dialog.add_class("-warning-border")
|
|
1865
|
+
s.push_screen(dialog)
|
|
1866
|
+
s.debug("Error", f"[{event.code}] {full_msg[:60]}")
|
|
1867
|
+
return
|
|
1868
|
+
if s.pending_user_submit_active and event.code in _SUBMIT_BLOCKING_ERROR_CODES:
|
|
1869
|
+
s.set_agent_running(False)
|
|
1870
|
+
s.block_pending_user_submit()
|
|
1871
|
+
if event.code in _IMAGE_ERROR_CODES:
|
|
1872
|
+
self._show_image_rejection_dialog(event.code, display_full)
|
|
1873
|
+
s.debug("ImageAttachmentBlocked", full_msg.splitlines()[0][:80])
|
|
1874
|
+
return
|
|
1875
|
+
if event.code == _WORKING_DIR_MISSING_CODE:
|
|
1876
|
+
# The draft goes back first; the folder prompt never writes to it.
|
|
1877
|
+
restored = self._restore_pending_submit_text() is not None
|
|
1878
|
+
self._callbacks.prompt_missing_working_dir("submit" if restored else "turn_end")
|
|
1879
|
+
s.debug("Error", f"[{event.code}] {full_msg[:60]}")
|
|
1880
|
+
return
|
|
1881
|
+
await self._show_pending_submit_error(event.code, display_full, raw_message=full_msg)
|
|
1882
|
+
return
|
|
1883
|
+
if s.pending_user_message_render_active:
|
|
1884
|
+
s.defer_error(event)
|
|
1885
|
+
return
|
|
1886
|
+
# A classified failure shows what went wrong, then the service's own
|
|
1887
|
+
# words as evidence; other producers' display is their message in
|
|
1888
|
+
# the current locale, so it stands alone.
|
|
1889
|
+
with_raw = event.code == "executor_error" and event.display_message is not None and bool(raw_msg)
|
|
1890
|
+
if s.agent_loading:
|
|
1891
|
+
self._agent_load().fail(f"{display_full}\n{raw_msg}" if with_raw else display_full)
|
|
1892
|
+
terminal_request = GcAbsorbRequested(GcAbsorbReason.TURN_TERMINAL, terminal_boundary=True) if was_live else None
|
|
1893
|
+
# Build a short version for the status bar (strip JSON payload); its
|
|
1894
|
+
# one line has no room for the hint.
|
|
1895
|
+
short_msg = full_msg if event.display_message is None else self._render_display(event.display_message)
|
|
1896
|
+
if "- {" in short_msg:
|
|
1897
|
+
short_msg = short_msg[: short_msg.index("- {")].strip()
|
|
1898
|
+
# Build a readable version for the chat panel: extract 'message'
|
|
1899
|
+
# from the JSON payload if present, otherwise use full text.
|
|
1900
|
+
if with_raw:
|
|
1901
|
+
chat_msg = f"{display_full}\n{self._extract_error_detail(raw_msg)}"
|
|
1902
|
+
else:
|
|
1903
|
+
chat_msg = self._extract_error_detail(display_full)
|
|
1904
|
+
terminal_owned = not was_live
|
|
1905
|
+
try:
|
|
1906
|
+
ui.flash_status(STATUS_ERROR.bind(message=short_msg), error=True)
|
|
1907
|
+
await ui.add_error(chat_msg)
|
|
1908
|
+
finally:
|
|
1909
|
+
if not was_live or (self._state.run.generation == terminal_generation and s.agent_running):
|
|
1910
|
+
if was_live:
|
|
1911
|
+
ui.mark_terminal_title_failed()
|
|
1912
|
+
s.set_agent_running(False)
|
|
1913
|
+
ui.unlock_input_keep_if_locked()
|
|
1914
|
+
terminal_owned = True
|
|
1915
|
+
if not terminal_owned:
|
|
1916
|
+
s.debug("Error", "ignored stale terminal completion")
|
|
1917
|
+
return
|
|
1918
|
+
if terminal_request is not None:
|
|
1919
|
+
self._callbacks.post_gc_message(terminal_request)
|
|
1920
|
+
ui.set_input_retry(INPUT_RETRY.bind())
|
|
1921
|
+
if was_live and event.code not in _NON_TURN_NOTIFICATION_ERROR_CODES:
|
|
1922
|
+
self._notify(NotificationEvent.TURN_ERROR)
|
|
1923
|
+
s.debug("Error", f"[{event.code}] {full_msg[:60]}")
|
|
1924
|
+
|
|
1925
|
+
def _restore_pending_submit_text(self) -> InputRestore | None:
|
|
1926
|
+
"""Restore the prompt text that the input widget cleared on submit."""
|
|
1927
|
+
s = self
|
|
1928
|
+
return self._ui().restore_input_text(s.pending_user_submit_text)
|
|
1929
|
+
|
|
1930
|
+
def _show_image_rejection_dialog(self, code: str, message: str) -> None:
|
|
1931
|
+
"""Show the image rejection modal for a backend-validated prompt."""
|
|
1932
|
+
text = self.pending_user_submit_text
|
|
1933
|
+
converted_text = replace_image_mentions_with_paths(text, Path(self._state.workspace.current_cwd))
|
|
1934
|
+
show_path_action = converted_text != text
|
|
1935
|
+
restore = self._restore_pending_submit_text()
|
|
1936
|
+
|
|
1937
|
+
from chrys.app.tui.screens.dialogs.vision_unsupported import (
|
|
1938
|
+
USE_IMAGE_PATHS_RESULT,
|
|
1939
|
+
VISION_UNSUPPORTED_TITLE,
|
|
1940
|
+
VisionUnsupportedDialog,
|
|
1941
|
+
)
|
|
1942
|
+
|
|
1943
|
+
title = VISION_UNSUPPORTED_TITLE.bind() if code == "vision_unsupported" else _IMAGE_NOT_ATTACHED.bind()
|
|
1944
|
+
|
|
1945
|
+
dialog = VisionUnsupportedDialog(
|
|
1946
|
+
message,
|
|
1947
|
+
title=title,
|
|
1948
|
+
show_path_action=show_path_action,
|
|
1949
|
+
locale_controller=self._locale_controller,
|
|
1950
|
+
)
|
|
1951
|
+
|
|
1952
|
+
def on_dismiss(result: object) -> None:
|
|
1953
|
+
# Only the prompt this handler restored may be rewritten in place;
|
|
1954
|
+
# a draft the user typed instead is theirs, dialog or no dialog.
|
|
1955
|
+
if result == USE_IMAGE_PATHS_RESULT:
|
|
1956
|
+
self._ui().rewrite_restored_input(restore, converted_text)
|
|
1957
|
+
|
|
1958
|
+
self.push_screen(dialog, callback=on_dismiss)
|
|
1959
|
+
|
|
1960
|
+
async def _show_pending_submit_error(self, code: str, message: str, *, raw_message: str | None = None) -> None:
|
|
1961
|
+
"""Surface a synchronous submit rejection without switching into retry mode.
|
|
1962
|
+
|
|
1963
|
+
``message`` may be localized display text; the debug stream keeps
|
|
1964
|
+
``raw_message`` (the protocol English) when the caller provides it.
|
|
1965
|
+
"""
|
|
1966
|
+
s = self
|
|
1967
|
+
ui = self._ui()
|
|
1968
|
+
|
|
1969
|
+
self._restore_pending_submit_text()
|
|
1970
|
+
short_msg = message
|
|
1971
|
+
if "- {" in short_msg:
|
|
1972
|
+
short_msg = short_msg[: short_msg.index("- {")].strip()
|
|
1973
|
+
chat_msg = self._extract_error_detail(message)
|
|
1974
|
+
ui.flash_status(STATUS_ERROR.bind(message=short_msg), error=True)
|
|
1975
|
+
if self._state.submit.is_retry:
|
|
1976
|
+
# Admission did not start a new run. Keep the previous terminal
|
|
1977
|
+
# card and its action so the user can retry after resolving this.
|
|
1978
|
+
s.notify(chat_msg, title=_RETRY_NOT_STARTED.bind(), severity="error", timeout=8)
|
|
1979
|
+
else:
|
|
1980
|
+
await ui.add_error(chat_msg, action_label=None)
|
|
1981
|
+
s.debug("Error", f"[{code}] {(message if raw_message is None else raw_message)[:60]}")
|
|
1982
|
+
|
|
1983
|
+
async def on_retry_attempt(self, event: InvocationRetryAttempt) -> None:
|
|
1984
|
+
if event.origin.kind == "sub_agent":
|
|
1985
|
+
await self.on_sub_agent_retry_attempt(event)
|
|
1986
|
+
return
|
|
1987
|
+
if event.origin.kind != "turn":
|
|
1988
|
+
return
|
|
1989
|
+
s = self
|
|
1990
|
+
if not s.agent_running:
|
|
1991
|
+
return # Stale event after interrupt — ignore
|
|
1992
|
+
if event.scope == "compaction":
|
|
1993
|
+
# Phase-4 side-call retries recover on their own most of the
|
|
1994
|
+
# time — they surface as a quiet warning line inside the live
|
|
1995
|
+
# compaction card, never as the transcript-level error banner
|
|
1996
|
+
# (which would also wrongly finalize running tool widgets).
|
|
1997
|
+
reason = event.detail or event.message
|
|
1998
|
+
self._ui().show_compaction_retry(reason, event.attempt, event.max_attempts, event.delay_seconds)
|
|
1999
|
+
s.debug("Retry", f"{event.message} ({event.attempt}/{event.max_attempts})")
|
|
2000
|
+
return
|
|
2001
|
+
# Finalise pending widgets from the failed attempt BEFORE mounting
|
|
2002
|
+
# the retry notice. The backend's ``StreamRetryLoop`` rolls history
|
|
2003
|
+
# back before the next attempt, so any still-running tool cards
|
|
2004
|
+
# (especially sub-agents cancelled by the stream cleanup hooks) and
|
|
2005
|
+
# the open intermediate assistant message would otherwise act as
|
|
2006
|
+
# stale attach points for the re-run's tool calls, causing new
|
|
2007
|
+
# sub-agent cards to appear under the old assistant block.
|
|
2008
|
+
retry_msg = (
|
|
2009
|
+
event.message
|
|
2010
|
+
if event.display_message is None
|
|
2011
|
+
else self._render_display_with_hint(event.display_message, event.display_hint)
|
|
2012
|
+
)
|
|
2013
|
+
await self._ui().show_retry_attempt(retry_msg, event.attempt, event.max_attempts, event.delay_seconds)
|
|
2014
|
+
s.debug("Retry", f"{event.message} ({event.attempt}/{event.max_attempts})")
|