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,1127 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Session lifecycle handlers — create, restore, delete, profile/workspace switching."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import contextlib
|
|
9
|
+
import inspect
|
|
10
|
+
import os
|
|
11
|
+
from collections.abc import Awaitable, Callable
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from functools import partial
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from chrys.app.tui.i18n import render_str
|
|
18
|
+
from chrys.app.tui.screens.main.main_screen_presenter import MainScreenPresenter
|
|
19
|
+
from chrys.app.tui.screens.main.ports import (
|
|
20
|
+
AgentLoadUiPort,
|
|
21
|
+
BackendEventView,
|
|
22
|
+
NotificationSeverity,
|
|
23
|
+
ProfileDescriptionProvider,
|
|
24
|
+
RuntimeInfoProvider,
|
|
25
|
+
StatusMessage,
|
|
26
|
+
StatusTrail,
|
|
27
|
+
)
|
|
28
|
+
from chrys.app.tui.screens.main.runtime_info import RegistryRuntimeInfoProvider
|
|
29
|
+
from chrys.app.tui.screens.main.state import MainScreenServices, MainScreenState
|
|
30
|
+
from chrys.app.tui.screens.sessions import WorkflowSessionPick
|
|
31
|
+
from chrys.app.tui.support.gc_freeze import (
|
|
32
|
+
GcAbsorbReason,
|
|
33
|
+
GcAbsorbRequested,
|
|
34
|
+
GcReclaimReason,
|
|
35
|
+
GcReclaimRequested,
|
|
36
|
+
)
|
|
37
|
+
from chrys.app.tui.support.workspace_mru import schedule_workspace_mru_touches
|
|
38
|
+
from chrys.app.tui.widgets.chrome.input_bar import INPUT_CONTINUE, INPUT_RETRY
|
|
39
|
+
from chrys.app.tui.widgets.chrome.status_bar import (
|
|
40
|
+
STATUS_CUSTOM_TITLE_CLEARED,
|
|
41
|
+
STATUS_FORK_CREATED,
|
|
42
|
+
STATUS_FORK_NOTICE,
|
|
43
|
+
STATUS_OPENED_FORK,
|
|
44
|
+
STATUS_SESSION_RESTORED,
|
|
45
|
+
STATUS_SESSION_TITLE_UPDATED,
|
|
46
|
+
)
|
|
47
|
+
from chrys.app.tui.widgets.sidebar.context import ContextUsageState
|
|
48
|
+
from chrys.foundation.events.types import (
|
|
49
|
+
AgentRuntimeDetails,
|
|
50
|
+
Error,
|
|
51
|
+
ProfileSwitched,
|
|
52
|
+
SessionFork,
|
|
53
|
+
SessionForked,
|
|
54
|
+
SessionRestored,
|
|
55
|
+
SessionSaved,
|
|
56
|
+
SessionTitleUpdated,
|
|
57
|
+
WorkspaceUpdated,
|
|
58
|
+
)
|
|
59
|
+
from chrys.foundation.i18n import DisplayPath, MessageRef, msg
|
|
60
|
+
from chrys.foundation.i18n.formatting import format_message
|
|
61
|
+
from chrys.foundation.models.history_markers import HistoryMarkerKind
|
|
62
|
+
from chrys.foundation.models.todos import parse_todo_items
|
|
63
|
+
from chrys.foundation.platform import safe_getcwd
|
|
64
|
+
from chrys.foundation.util.session_ids import session_short_id
|
|
65
|
+
from chrys.service.session.persistence import has_real_messages
|
|
66
|
+
|
|
67
|
+
if TYPE_CHECKING:
|
|
68
|
+
from chrys.app.tui.i18n import LocaleController
|
|
69
|
+
from chrys.app.tui.screens.dialogs.fork_session import ForkSessionDialog
|
|
70
|
+
from chrys.app.tui.screens.main.ports import StartWorker
|
|
71
|
+
from chrys.app.tui.widgets.chat.file_snapshot import FileSnapshotPayload
|
|
72
|
+
from chrys.foundation.events.bus import EventBus
|
|
73
|
+
from chrys.service.context.providers.history import CompressedBlock
|
|
74
|
+
from chrys.service.state.store import StateStore
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_FINISH_SESSION_RESTORED = msg(
|
|
78
|
+
"tui.agent_load.finish.session_restored",
|
|
79
|
+
fallback="Session restored: {session_id}",
|
|
80
|
+
)
|
|
81
|
+
_FINISH_AGENT_READY = msg("tui.agent_load.finish.agent_ready", fallback="Agent ready: {agent}")
|
|
82
|
+
_FINISH_PROFILE_SWITCHED = msg(
|
|
83
|
+
"tui.agent_load.finish.profile_switched",
|
|
84
|
+
fallback="Profile switched: {from_label} -> {to_label}",
|
|
85
|
+
)
|
|
86
|
+
_RESUME_TITLE = msg("tui.session.title.resume", fallback="Resume")
|
|
87
|
+
_FORK_TITLE = msg("tui.session.title.fork", fallback="Fork")
|
|
88
|
+
CLEAR_SESSION_TITLE = msg("tui.main.confirm.clear_title", fallback="Clear Session")
|
|
89
|
+
_CLEAR_FAILED = msg(
|
|
90
|
+
"tui.session.clear.failed",
|
|
91
|
+
fallback="The current session was kept: {message}",
|
|
92
|
+
)
|
|
93
|
+
_SESSION_TITLE_TITLE = msg("tui.session.title.session_title", fallback="Session Title")
|
|
94
|
+
_NO_SAVED_SESSIONS = msg("tui.session.resume.none", fallback="No saved sessions found in this mode")
|
|
95
|
+
_FORK_TURN_RUNNING = msg(
|
|
96
|
+
"tui.session.fork.turn_running",
|
|
97
|
+
fallback="Cannot fork while a turn is running",
|
|
98
|
+
)
|
|
99
|
+
_FORK_EMPTY_SESSION = msg(
|
|
100
|
+
"tui.session.fork.empty_session",
|
|
101
|
+
fallback="Cannot fork an empty session",
|
|
102
|
+
)
|
|
103
|
+
_FORK_NO_ACTIVE_SESSION = msg(
|
|
104
|
+
"tui.session.fork.no_active_session",
|
|
105
|
+
fallback="No active session to fork",
|
|
106
|
+
)
|
|
107
|
+
_FORK_DIALOG_FAILED = msg(
|
|
108
|
+
"tui.session.fork.dialog_failed",
|
|
109
|
+
fallback="Could not open fork dialog",
|
|
110
|
+
)
|
|
111
|
+
_SESSION_TITLE_SAVE_FAILED = msg(
|
|
112
|
+
"tui.session.title.save_failed",
|
|
113
|
+
fallback="Failed to save session title",
|
|
114
|
+
)
|
|
115
|
+
_FORK_CREATED = msg("tui.session.fork.created", fallback="Created fork {fork_id}")
|
|
116
|
+
_PROFILE_SWITCH_INDICATOR = msg(
|
|
117
|
+
"tui.session.profile_switch_indicator",
|
|
118
|
+
fallback="Agent profile switched: {from_label} → {to_label}",
|
|
119
|
+
)
|
|
120
|
+
_WORKING_DIRECTORY_INDICATOR = msg(
|
|
121
|
+
"tui.session.working_directory_indicator",
|
|
122
|
+
fallback="Working directory → {path}",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class RestoreRequest(Enum):
|
|
127
|
+
"""What :meth:`SessionHandler.do_session_restore` did."""
|
|
128
|
+
|
|
129
|
+
REQUESTED = "requested"
|
|
130
|
+
"""A restore was published; ``SessionRestored`` or an ``Error`` follows."""
|
|
131
|
+
DECLINED = "declined"
|
|
132
|
+
"""The session's folder is gone and the user chose no other one."""
|
|
133
|
+
SKIPPED = "skipped"
|
|
134
|
+
"""Nothing was published (busy, or the loading UI could not open)."""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def _decline_missing_working_dir(_missing: str) -> str | None:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass(frozen=True, slots=True)
|
|
142
|
+
class SessionCallbacks:
|
|
143
|
+
"""Screen-owned effects required by session handlers."""
|
|
144
|
+
|
|
145
|
+
set_agent_loading: Callable[[bool], None]
|
|
146
|
+
set_has_messages: Callable[[bool], None]
|
|
147
|
+
set_creating_new_session: Callable[[bool], None]
|
|
148
|
+
set_restoring_session: Callable[[bool], None]
|
|
149
|
+
set_profile_display: Callable[[str], None]
|
|
150
|
+
set_active_model_profile_id: Callable[[str], None]
|
|
151
|
+
set_workspace_cwd: Callable[[str], None]
|
|
152
|
+
update_subtitle: Callable[[], None]
|
|
153
|
+
update_toc: Callable[[], None]
|
|
154
|
+
clear_suggestion_file_cache: Callable[[], None]
|
|
155
|
+
start_worker: StartWorker
|
|
156
|
+
post_gc_message: Callable[[GcAbsorbRequested | GcReclaimRequested], object]
|
|
157
|
+
debug: Callable[[str, str], None]
|
|
158
|
+
refresh_model_indicator: Callable[[], None]
|
|
159
|
+
choose_missing_working_dir: Callable[[str], Awaitable[str | None]] = _decline_missing_working_dir
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _samepath(a: str, b: str) -> bool:
|
|
163
|
+
"""Compare paths using OS semantics (handles case-insensitive FS, symlinks)."""
|
|
164
|
+
try:
|
|
165
|
+
return os.path.samefile(a, b)
|
|
166
|
+
except OSError, ValueError:
|
|
167
|
+
return a == b
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class SessionHandler:
|
|
171
|
+
"""Handles session lifecycle and profile/workspace switching.
|
|
172
|
+
|
|
173
|
+
Owns chain-tracking state for profile switches and chdir operations.
|
|
174
|
+
Constructed once by the main-screen owner.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def __init__(
|
|
178
|
+
self,
|
|
179
|
+
*,
|
|
180
|
+
state: MainScreenState,
|
|
181
|
+
services: MainScreenServices,
|
|
182
|
+
view: BackendEventView,
|
|
183
|
+
callbacks: SessionCallbacks,
|
|
184
|
+
agent_load: AgentLoadUiPort,
|
|
185
|
+
runtime_info: RuntimeInfoProvider | None = None,
|
|
186
|
+
profile_descriptions: ProfileDescriptionProvider | None = None,
|
|
187
|
+
locale_controller: LocaleController | None = None,
|
|
188
|
+
) -> None:
|
|
189
|
+
self._state = state
|
|
190
|
+
self._services = services
|
|
191
|
+
self._view = view
|
|
192
|
+
self._callbacks = callbacks
|
|
193
|
+
self._presenter = MainScreenPresenter(view, state)
|
|
194
|
+
runtime_info = runtime_info or RegistryRuntimeInfoProvider(services)
|
|
195
|
+
self._agent_load = agent_load
|
|
196
|
+
self._runtime_info = runtime_info
|
|
197
|
+
self._profile_descriptions = profile_descriptions or runtime_info
|
|
198
|
+
self._locale_controller = locale_controller
|
|
199
|
+
self._fork_dialog: ForkSessionDialog | None = None
|
|
200
|
+
self._fork_session_id = ""
|
|
201
|
+
self._fork_new_session_id = ""
|
|
202
|
+
# Serializes custom-title saves: FileLock acquisition is not FIFO,
|
|
203
|
+
# so two rapid dialog saves could otherwise land out of order.
|
|
204
|
+
self._custom_title_apply_lock = asyncio.Lock()
|
|
205
|
+
|
|
206
|
+
def _ui(self) -> MainScreenPresenter:
|
|
207
|
+
"""Return the screen presenter."""
|
|
208
|
+
return self._presenter
|
|
209
|
+
|
|
210
|
+
def _render_message(self, reference: MessageRef) -> str:
|
|
211
|
+
controller = self._locale_controller
|
|
212
|
+
return format_message(reference) if controller is None else render_str(controller.localizer, reference)
|
|
213
|
+
|
|
214
|
+
def _format_tool_info(
|
|
215
|
+
self,
|
|
216
|
+
tool_names: list[str],
|
|
217
|
+
skill_names: list[str],
|
|
218
|
+
*,
|
|
219
|
+
memory_files: list[str] | None = None,
|
|
220
|
+
runtime_details: AgentRuntimeDetails | None = None,
|
|
221
|
+
) -> StatusTrail:
|
|
222
|
+
return self._runtime_info.format_tool_info(
|
|
223
|
+
tool_names,
|
|
224
|
+
skill_names,
|
|
225
|
+
memory_files=memory_files,
|
|
226
|
+
runtime_details=runtime_details,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def _get_profile_description(self, profile_name: str) -> str:
|
|
230
|
+
return self._profile_descriptions.get_profile_description(profile_name)
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def bus(self) -> EventBus:
|
|
234
|
+
return self._services.bus
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def state_store(self) -> StateStore | None:
|
|
238
|
+
return self._services.state_store
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def agent_running(self) -> bool:
|
|
242
|
+
return self._state.run.agent_running
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def agent_loading(self) -> bool:
|
|
246
|
+
return self._state.run.agent_loading
|
|
247
|
+
|
|
248
|
+
def set_agent_loading(self, loading: bool) -> None:
|
|
249
|
+
self._state.run.agent_loading = loading
|
|
250
|
+
self._callbacks.set_agent_loading(loading)
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def has_messages(self) -> bool:
|
|
254
|
+
return self._state.run.has_messages
|
|
255
|
+
|
|
256
|
+
def set_has_messages(self, has_messages: bool) -> None:
|
|
257
|
+
self._state.run.has_messages = has_messages
|
|
258
|
+
self._callbacks.set_has_messages(has_messages)
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def creating_new_session(self) -> bool:
|
|
262
|
+
return self._state.session.creating_new_session
|
|
263
|
+
|
|
264
|
+
@creating_new_session.setter
|
|
265
|
+
def creating_new_session(self, value: bool) -> None:
|
|
266
|
+
self._state.session.creating_new_session = value
|
|
267
|
+
self._callbacks.set_creating_new_session(value)
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def restoring_session(self) -> bool:
|
|
271
|
+
return self._state.session.restoring_session
|
|
272
|
+
|
|
273
|
+
@restoring_session.setter
|
|
274
|
+
def restoring_session(self, value: bool) -> None:
|
|
275
|
+
self._state.session.restoring_session = value
|
|
276
|
+
self._callbacks.set_restoring_session(value)
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def profile(self) -> str:
|
|
280
|
+
return self._state.runtime.profile
|
|
281
|
+
|
|
282
|
+
@profile.setter
|
|
283
|
+
def profile(self, value: str) -> None:
|
|
284
|
+
self._state.runtime.profile = value
|
|
285
|
+
self._callbacks.set_profile_display(value)
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def runtime_metadata(self) -> AgentRuntimeDetails:
|
|
289
|
+
return self._state.runtime.details
|
|
290
|
+
|
|
291
|
+
@runtime_metadata.setter
|
|
292
|
+
def runtime_metadata(self, value: AgentRuntimeDetails) -> None:
|
|
293
|
+
self._state.runtime.details = value
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def main_usage_source_id(self) -> str:
|
|
297
|
+
return self._state.runtime.main_usage_source_id
|
|
298
|
+
|
|
299
|
+
@main_usage_source_id.setter
|
|
300
|
+
def main_usage_source_id(self, value: str) -> None:
|
|
301
|
+
self._state.runtime.main_usage_source_id = value
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def last_usage_tokens(self) -> int:
|
|
305
|
+
return self._state.usage.last_usage_tokens
|
|
306
|
+
|
|
307
|
+
@last_usage_tokens.setter
|
|
308
|
+
def last_usage_tokens(self, value: int) -> None:
|
|
309
|
+
self._state.usage.last_usage_tokens = value
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def last_total_session_tokens(self) -> int:
|
|
313
|
+
return self._state.usage.last_total_session_tokens
|
|
314
|
+
|
|
315
|
+
@last_total_session_tokens.setter
|
|
316
|
+
def last_total_session_tokens(self, value: int) -> None:
|
|
317
|
+
self._state.usage.last_total_session_tokens = value
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def context_usage_state(self) -> ContextUsageState | None:
|
|
321
|
+
return self._view.context_usage_state
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def chdir_current_cwd(self) -> str:
|
|
325
|
+
return self._state.workspace_marker.current_cwd
|
|
326
|
+
|
|
327
|
+
@chdir_current_cwd.setter
|
|
328
|
+
def chdir_current_cwd(self, value: str) -> None:
|
|
329
|
+
self._state.workspace.current_cwd = value
|
|
330
|
+
self._state.workspace_marker.current_cwd = value
|
|
331
|
+
self._callbacks.set_workspace_cwd(value)
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def chdir_original_cwd(self) -> str | None:
|
|
335
|
+
return self._state.workspace_marker.original_cwd
|
|
336
|
+
|
|
337
|
+
@chdir_original_cwd.setter
|
|
338
|
+
def chdir_original_cwd(self, value: str | None) -> None:
|
|
339
|
+
self._state.workspace_marker.original_cwd = value
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def profile_switch_from(self) -> str | None:
|
|
343
|
+
return self._state.profile_marker.from_profile
|
|
344
|
+
|
|
345
|
+
@profile_switch_from.setter
|
|
346
|
+
def profile_switch_from(self, value: str | None) -> None:
|
|
347
|
+
self._state.profile_marker.from_profile = value
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def profile_switch_to(self) -> str | None:
|
|
351
|
+
return self._state.profile_marker.to_profile
|
|
352
|
+
|
|
353
|
+
@profile_switch_to.setter
|
|
354
|
+
def profile_switch_to(self, value: str | None) -> None:
|
|
355
|
+
self._state.profile_marker.to_profile = value
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def profile_switch_seq(self) -> int:
|
|
359
|
+
return self._state.profile_marker.seq
|
|
360
|
+
|
|
361
|
+
@profile_switch_seq.setter
|
|
362
|
+
def profile_switch_seq(self, value: int) -> None:
|
|
363
|
+
self._state.profile_marker.seq = value
|
|
364
|
+
|
|
365
|
+
def notify(
|
|
366
|
+
self,
|
|
367
|
+
message: StatusMessage,
|
|
368
|
+
*,
|
|
369
|
+
title: StatusMessage,
|
|
370
|
+
severity: NotificationSeverity = "information",
|
|
371
|
+
timeout: float | None = 3,
|
|
372
|
+
) -> None:
|
|
373
|
+
self._view.notify(message, title=title, severity=severity, timeout=timeout)
|
|
374
|
+
|
|
375
|
+
def push_screen(self, screen: object, callback: object | None = None) -> object:
|
|
376
|
+
return self._view.push_screen(screen, callback)
|
|
377
|
+
|
|
378
|
+
def start_session_restore(self, session_id: str) -> object:
|
|
379
|
+
"""Run :meth:`do_session_restore` in a screen worker."""
|
|
380
|
+
return self._callbacks.start_worker(partial(self.do_session_restore, session_id))
|
|
381
|
+
|
|
382
|
+
def workspace_cwd(self) -> str:
|
|
383
|
+
return self.chdir_current_cwd or self._state.workspace.current_cwd or safe_getcwd()
|
|
384
|
+
|
|
385
|
+
def update_subtitle(self) -> None:
|
|
386
|
+
self._callbacks.update_subtitle()
|
|
387
|
+
|
|
388
|
+
def update_toc(self) -> None:
|
|
389
|
+
self._callbacks.update_toc()
|
|
390
|
+
|
|
391
|
+
def clear_suggestion_file_cache(self) -> None:
|
|
392
|
+
self._callbacks.clear_suggestion_file_cache()
|
|
393
|
+
|
|
394
|
+
def debug(self, key: str, message: str = "") -> None:
|
|
395
|
+
self._callbacks.debug(key, message)
|
|
396
|
+
|
|
397
|
+
# -------------------------------------------------------------- #
|
|
398
|
+
# Session lifecycle
|
|
399
|
+
# -------------------------------------------------------------- #
|
|
400
|
+
|
|
401
|
+
async def delete_current_and_new(self, session_id: str) -> None:
|
|
402
|
+
"""Delete the current session and start fresh as ONE fenced backend operation.
|
|
403
|
+
|
|
404
|
+
Input is blocked for the whole operation (typed text is kept), and the
|
|
405
|
+
fresh session only starts once the backend acknowledged the delete with
|
|
406
|
+
``SessionDeleted``. A failed delete keeps the current session: the
|
|
407
|
+
backend reports ``session_clear_failed`` (toast via
|
|
408
|
+
:meth:`on_session_clear_error`) and never starts a new session.
|
|
409
|
+
"""
|
|
410
|
+
s = self
|
|
411
|
+
# Guard and claim the UI synchronously — no await between them — so
|
|
412
|
+
# the /clear preflight and "input blocked" are one step: a submit that
|
|
413
|
+
# is still being admitted (``submit.active``; ``agent_running`` flips
|
|
414
|
+
# only afterwards) vetoes the clear, and nothing can be admitted once
|
|
415
|
+
# ``agent_loading`` is set.
|
|
416
|
+
if s.agent_loading or s.agent_running or self._state.submit.active or self._services.execution_busy():
|
|
417
|
+
return
|
|
418
|
+
s.set_agent_loading(True)
|
|
419
|
+
s.creating_new_session = True
|
|
420
|
+
from chrys.foundation.events.types import SessionClear, SessionDeleted
|
|
421
|
+
|
|
422
|
+
deleted = False
|
|
423
|
+
|
|
424
|
+
async def _on_deleted(event: SessionDeleted) -> None:
|
|
425
|
+
nonlocal deleted
|
|
426
|
+
if event.session_id == session_id:
|
|
427
|
+
deleted = True
|
|
428
|
+
|
|
429
|
+
await s.bus.subscribe(SessionDeleted, _on_deleted)
|
|
430
|
+
try:
|
|
431
|
+
await s.bus.publish(SessionClear(session_id=session_id))
|
|
432
|
+
finally:
|
|
433
|
+
await s.bus.unsubscribe(SessionDeleted, _on_deleted)
|
|
434
|
+
if not deleted:
|
|
435
|
+
# Nothing was deleted, so no fresh session was started: undo the
|
|
436
|
+
# optimistic new-session/loading state (idempotent with the
|
|
437
|
+
# ``session_clear_failed`` toast path).
|
|
438
|
+
s.creating_new_session = False
|
|
439
|
+
s.set_agent_loading(False)
|
|
440
|
+
|
|
441
|
+
async def create_new_session(self) -> None:
|
|
442
|
+
s = self
|
|
443
|
+
if s.agent_loading or s.agent_running or self._services.execution_busy():
|
|
444
|
+
return
|
|
445
|
+
from chrys.foundation.events.types import SessionNew
|
|
446
|
+
|
|
447
|
+
s.creating_new_session = True
|
|
448
|
+
await s.bus.publish(SessionNew())
|
|
449
|
+
|
|
450
|
+
async def resume_last_session(self) -> None:
|
|
451
|
+
"""Resume the most recent chat session.
|
|
452
|
+
|
|
453
|
+
The restore modal opens before the lookup so a slow first-time index
|
|
454
|
+
backfill still shows progress.
|
|
455
|
+
"""
|
|
456
|
+
s = self
|
|
457
|
+
if not s.state_store or s.agent_running or s.agent_loading or self._services.execution_busy():
|
|
458
|
+
return
|
|
459
|
+
s.restoring_session = True
|
|
460
|
+
try:
|
|
461
|
+
# Empty id: the modal opens with a blank subtitle and the resolved
|
|
462
|
+
# id fills it in via ``do_session_restore`` (same dialog).
|
|
463
|
+
await self._agent_load.begin_session_restore_load("")
|
|
464
|
+
selection = await s.state_store.load_latest_session_id(chat_only=True)
|
|
465
|
+
except Exception as exc:
|
|
466
|
+
s.restoring_session = False
|
|
467
|
+
with contextlib.suppress(Exception):
|
|
468
|
+
self._agent_load.cancel_agent_load()
|
|
469
|
+
s.debug("SessionRestore", f"failed to look up latest session: {exc}")
|
|
470
|
+
return
|
|
471
|
+
if selection is None:
|
|
472
|
+
s.restoring_session = False
|
|
473
|
+
self._agent_load.cancel_agent_load()
|
|
474
|
+
s.notify(_NO_SAVED_SESSIONS.bind(), title=_RESUME_TITLE.bind(), severity="warning", timeout=3)
|
|
475
|
+
return
|
|
476
|
+
# The lookup modal already marked the screen as loading.
|
|
477
|
+
await self.do_session_restore(selection, allow_while_loading=True)
|
|
478
|
+
|
|
479
|
+
async def fork_current_session(self) -> None:
|
|
480
|
+
"""Request a backend-managed fork of the current saved session."""
|
|
481
|
+
s = self
|
|
482
|
+
ui = self._ui()
|
|
483
|
+
|
|
484
|
+
if s.agent_loading:
|
|
485
|
+
return
|
|
486
|
+
if s.agent_running or self._services.execution_busy():
|
|
487
|
+
s.notify(_FORK_TURN_RUNNING.bind(), title=_FORK_TITLE.bind(), severity="warning", timeout=3)
|
|
488
|
+
return
|
|
489
|
+
if not s.has_messages:
|
|
490
|
+
s.notify(_FORK_EMPTY_SESSION.bind(), title=_FORK_TITLE.bind(), severity="warning", timeout=3)
|
|
491
|
+
return
|
|
492
|
+
session_id = ui.chat_session_id()
|
|
493
|
+
if not session_id:
|
|
494
|
+
s.notify(_FORK_NO_ACTIVE_SESSION.bind(), title=_FORK_TITLE.bind(), severity="warning", timeout=3)
|
|
495
|
+
return
|
|
496
|
+
try:
|
|
497
|
+
await self._open_session_fork_dialog(session_id)
|
|
498
|
+
except Exception as exc:
|
|
499
|
+
self._reset_session_fork_dialog()
|
|
500
|
+
s.set_agent_loading(False)
|
|
501
|
+
s.notify(_FORK_DIALOG_FAILED.bind(), title=_FORK_TITLE.bind(), severity="error", timeout=5)
|
|
502
|
+
s.debug("SessionFork", f"failed to open dialog: {exc}")
|
|
503
|
+
return
|
|
504
|
+
try:
|
|
505
|
+
await s.bus.publish(SessionFork(session_id=session_id))
|
|
506
|
+
except Exception as exc:
|
|
507
|
+
self.on_session_fork_error(
|
|
508
|
+
Error(session_id=session_id, code="session_fork_failed", message=str(exc)),
|
|
509
|
+
message=str(exc),
|
|
510
|
+
severity="error",
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
async def _open_session_fork_dialog(self, session_id: str) -> None:
|
|
514
|
+
"""Open the non-dismissible fork progress dialog and mark the UI busy."""
|
|
515
|
+
s = self
|
|
516
|
+
from chrys.app.tui.screens.dialogs.fork_session import ForkSessionDialog
|
|
517
|
+
from chrys.app.tui.terminal.launcher import can_open_new_chrys_window
|
|
518
|
+
|
|
519
|
+
if self._fork_dialog is not None:
|
|
520
|
+
return
|
|
521
|
+
dialog = ForkSessionDialog(
|
|
522
|
+
show_new_window=can_open_new_chrys_window(),
|
|
523
|
+
locale_controller=self._locale_controller,
|
|
524
|
+
)
|
|
525
|
+
self._fork_dialog = dialog
|
|
526
|
+
self._fork_session_id = session_id
|
|
527
|
+
self._fork_new_session_id = ""
|
|
528
|
+
s.set_agent_loading(True)
|
|
529
|
+
pushed = s.push_screen(dialog, callback=self._handle_session_fork_result)
|
|
530
|
+
if inspect.isawaitable(pushed):
|
|
531
|
+
await pushed
|
|
532
|
+
|
|
533
|
+
def _handle_session_fork_result(self, result: object) -> None:
|
|
534
|
+
"""Route the resolved fork dialog action."""
|
|
535
|
+
s = self
|
|
536
|
+
ui = self._ui()
|
|
537
|
+
|
|
538
|
+
new_session_id = self._fork_new_session_id
|
|
539
|
+
fork_short_id = session_short_id(new_session_id) if new_session_id else ""
|
|
540
|
+
self._reset_session_fork_dialog()
|
|
541
|
+
s.set_agent_loading(False)
|
|
542
|
+
|
|
543
|
+
if result == "switch" and new_session_id:
|
|
544
|
+
s.start_session_restore(new_session_id)
|
|
545
|
+
return
|
|
546
|
+
if result == "new_window" and new_session_id:
|
|
547
|
+
from chrys.app.tui.terminal.launcher import TerminalLaunchError, launch_new_chrys_window
|
|
548
|
+
|
|
549
|
+
try:
|
|
550
|
+
launch_new_chrys_window(new_session_id, cwd=s.workspace_cwd())
|
|
551
|
+
except TerminalLaunchError as exc:
|
|
552
|
+
message = str(exc) if exc.display_message is None else self._render_message(exc.display_message)
|
|
553
|
+
s.notify(message, title=_FORK_TITLE.bind(), severity="warning", timeout=5)
|
|
554
|
+
return
|
|
555
|
+
ui.flash_status(STATUS_OPENED_FORK.bind(fork_id=fork_short_id))
|
|
556
|
+
return
|
|
557
|
+
if result == "stay" and fork_short_id:
|
|
558
|
+
ui.flash_status(STATUS_FORK_CREATED.bind(fork_id=fork_short_id))
|
|
559
|
+
|
|
560
|
+
def _reset_session_fork_dialog(self) -> None:
|
|
561
|
+
self._fork_dialog = None
|
|
562
|
+
self._fork_session_id = ""
|
|
563
|
+
self._fork_new_session_id = ""
|
|
564
|
+
|
|
565
|
+
async def do_session_restore(
|
|
566
|
+
self,
|
|
567
|
+
session_id: str | WorkflowSessionPick,
|
|
568
|
+
*,
|
|
569
|
+
allow_while_loading: bool = False,
|
|
570
|
+
primary_cwd: str = "",
|
|
571
|
+
) -> RestoreRequest:
|
|
572
|
+
"""Restore *session_id*, in *primary_cwd* when given instead of its saved folder.
|
|
573
|
+
|
|
574
|
+
A session whose saved folder is gone is restored only in a folder the
|
|
575
|
+
user picks; declining leaves the current session as it was.
|
|
576
|
+
"""
|
|
577
|
+
s = self
|
|
578
|
+
if s.agent_running or self._services.execution_busy() or (s.agent_loading and not allow_while_loading):
|
|
579
|
+
return RestoreRequest.SKIPPED
|
|
580
|
+
if isinstance(session_id, WorkflowSessionPick):
|
|
581
|
+
await self._restore_workflow_session(session_id)
|
|
582
|
+
return RestoreRequest.REQUESTED
|
|
583
|
+
from chrys.foundation.events.types import SessionRestore
|
|
584
|
+
|
|
585
|
+
if not primary_cwd and (missing := await self._missing_saved_cwd(session_id)) is not None:
|
|
586
|
+
# /resume and startup open their loading UI first; the prompt replaces it.
|
|
587
|
+
if s.agent_loading:
|
|
588
|
+
with contextlib.suppress(Exception):
|
|
589
|
+
self._agent_load.cancel_agent_load()
|
|
590
|
+
s.restoring_session = False
|
|
591
|
+
chosen = await self._callbacks.choose_missing_working_dir(missing)
|
|
592
|
+
if chosen is None:
|
|
593
|
+
return RestoreRequest.DECLINED
|
|
594
|
+
return await self.do_session_restore(
|
|
595
|
+
session_id, allow_while_loading=allow_while_loading, primary_cwd=chosen
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
s.restoring_session = True
|
|
599
|
+
try:
|
|
600
|
+
await self._agent_load.begin_session_restore_load(session_id)
|
|
601
|
+
except Exception as exc:
|
|
602
|
+
s.restoring_session = False
|
|
603
|
+
with contextlib.suppress(Exception):
|
|
604
|
+
self._agent_load.cancel_agent_load()
|
|
605
|
+
s.debug("SessionRestore", f"failed to open loading UI: {exc}")
|
|
606
|
+
return RestoreRequest.SKIPPED
|
|
607
|
+
await s.bus.publish(
|
|
608
|
+
SessionRestore(
|
|
609
|
+
session_id=session_id,
|
|
610
|
+
primary_cwd=primary_cwd,
|
|
611
|
+
apply_saved_model=self._services.apply_saved_model_on_restore,
|
|
612
|
+
)
|
|
613
|
+
)
|
|
614
|
+
return RestoreRequest.REQUESTED
|
|
615
|
+
|
|
616
|
+
async def _missing_saved_cwd(self, session_id: str) -> str | None:
|
|
617
|
+
"""The saved folder of *session_id* when it no longer exists, read the way restore reads it.
|
|
618
|
+
|
|
619
|
+
Restoring another session prefers its crash-recovery copy, so this
|
|
620
|
+
does too. Read failures are left for the restore itself to report.
|
|
621
|
+
"""
|
|
622
|
+
store = self.state_store
|
|
623
|
+
if store is None:
|
|
624
|
+
return None
|
|
625
|
+
try:
|
|
626
|
+
meta = await store.load_session_meta(session_id, prefer_recovery=True)
|
|
627
|
+
except Exception:
|
|
628
|
+
return None
|
|
629
|
+
saved = meta.primary_cwd if meta is not None else ""
|
|
630
|
+
return saved if saved and not os.path.isdir(saved) else None
|
|
631
|
+
|
|
632
|
+
async def _restore_workflow_session(self, selection: WorkflowSessionPick) -> None:
|
|
633
|
+
self.restoring_session = True
|
|
634
|
+
try:
|
|
635
|
+
await self._view.restore_workflow_session(selection.session_id)
|
|
636
|
+
finally:
|
|
637
|
+
self.restoring_session = False
|
|
638
|
+
|
|
639
|
+
def load_file_edit_snapshots(
|
|
640
|
+
self,
|
|
641
|
+
session_id: str,
|
|
642
|
+
messages: list[dict],
|
|
643
|
+
state: dict[str, Any] | None,
|
|
644
|
+
) -> dict[str, list[FileSnapshotPayload]]:
|
|
645
|
+
"""Build ``{fw_call_id: [snapshot_payload, ...]}`` from MutationTracker data.
|
|
646
|
+
|
|
647
|
+
Returns a list per ``call_id`` so duplicate call_ids (observed when
|
|
648
|
+
some LLMs reuse ids across retries after approval rejection) keep
|
|
649
|
+
distinct snapshots. ``replay_history`` consumes these as available
|
|
650
|
+
file-tool snapshot cursors independent of tool-result pairing.
|
|
651
|
+
"""
|
|
652
|
+
s = self
|
|
653
|
+
if s.state_store is None:
|
|
654
|
+
return {}
|
|
655
|
+
try:
|
|
656
|
+
from chrys.app.tui.widgets.chat.file_snapshot import snapshot_payload_from_hashes
|
|
657
|
+
from chrys.service.mutations.store import SnapshotStore
|
|
658
|
+
from chrys.service.mutations.tracker import MutationTracker
|
|
659
|
+
|
|
660
|
+
mutations_data = state.get("chrys_mutations") if state is not None else None
|
|
661
|
+
if not mutations_data:
|
|
662
|
+
return {}
|
|
663
|
+
|
|
664
|
+
# Route through the store so the folder derivation matches
|
|
665
|
+
# wherever the session was actually saved (handles short/long
|
|
666
|
+
# ids and alternate store roots, e.g. in tests).
|
|
667
|
+
session_dir = s.state_store.session_dir(session_id)
|
|
668
|
+
store = SnapshotStore(session_dir)
|
|
669
|
+
tracker = MutationTracker.deserialize(mutations_data, store)
|
|
670
|
+
edit_snapshot_refs = tracker.get_file_edit_snapshot_refs()
|
|
671
|
+
if not edit_snapshot_refs:
|
|
672
|
+
return {}
|
|
673
|
+
|
|
674
|
+
_file_tools = {"edit_file", "write_file"}
|
|
675
|
+
fw_call_ids: list[str] = []
|
|
676
|
+
for message_item in messages:
|
|
677
|
+
if message_item.get("role") != "assistant":
|
|
678
|
+
continue
|
|
679
|
+
additional = message_item.get("additional_properties")
|
|
680
|
+
if isinstance(additional, dict) and HistoryMarkerKind.KEY in additional:
|
|
681
|
+
# Marker payloads are chrome that replay never renders;
|
|
682
|
+
# the tracker records snapshots only for executed calls,
|
|
683
|
+
# so a marker-carried call must not shift the zip.
|
|
684
|
+
continue
|
|
685
|
+
for c in message_item.get("contents", []):
|
|
686
|
+
if isinstance(c, dict) and c.get("type") == "function_call":
|
|
687
|
+
name = c.get("name", "")
|
|
688
|
+
cid = c.get("call_id", "")
|
|
689
|
+
if name in _file_tools and cid:
|
|
690
|
+
fw_call_ids.append(cid)
|
|
691
|
+
|
|
692
|
+
# Positional pairing: fw_call_ids[i] ↔ edit_snapshots[i]. Bucket
|
|
693
|
+
# into per-call_id lists so duplicate call_ids each keep their
|
|
694
|
+
# own snapshot. ``zip(..., strict=False)`` silently truncates if
|
|
695
|
+
# the lists disagree in length — preserves existing fail-soft
|
|
696
|
+
# semantics when history and tracker data are out of sync.
|
|
697
|
+
buckets: dict[str, list[FileSnapshotPayload]] = {}
|
|
698
|
+
for cid, snap_ref in zip(fw_call_ids, edit_snapshot_refs, strict=False):
|
|
699
|
+
buckets.setdefault(cid, []).append(
|
|
700
|
+
snapshot_payload_from_hashes(store.mutations_dir, snap_ref.before, snap_ref.after)
|
|
701
|
+
)
|
|
702
|
+
return buckets
|
|
703
|
+
except Exception:
|
|
704
|
+
return {}
|
|
705
|
+
|
|
706
|
+
# -------------------------------------------------------------- #
|
|
707
|
+
# EventBus callbacks
|
|
708
|
+
# -------------------------------------------------------------- #
|
|
709
|
+
|
|
710
|
+
async def on_session_restored(self, event: SessionRestored) -> None:
|
|
711
|
+
s = self
|
|
712
|
+
ui = self._ui()
|
|
713
|
+
|
|
714
|
+
self._state.workspace.roots = [event.primary_cwd, *event.working_dirs]
|
|
715
|
+
s.restoring_session = False
|
|
716
|
+
restored_has_messages = event.message_count > 0
|
|
717
|
+
s.chdir_original_cwd = None
|
|
718
|
+
cwd = event.primary_cwd or safe_getcwd()
|
|
719
|
+
s.chdir_current_cwd = cwd
|
|
720
|
+
ui.set_terminal_title_for_cwd(cwd)
|
|
721
|
+
if os.path.isdir(cwd):
|
|
722
|
+
await ui.change_shell_directory(cwd)
|
|
723
|
+
ui.set_input_retry_mode(False)
|
|
724
|
+
ui.set_input_paste_cwd(cwd)
|
|
725
|
+
ui.set_input_clipboard_dir(event.session_id)
|
|
726
|
+
display = event.display_name or event.agent_profile
|
|
727
|
+
s.main_usage_source_id = event.session_id
|
|
728
|
+
await ui.clear_chat()
|
|
729
|
+
ui.set_chat_workspace_cwd(cwd)
|
|
730
|
+
ui.update_welcome(profile=display, cwd=cwd)
|
|
731
|
+
ui.set_chat_session_id(event.session_id)
|
|
732
|
+
await self._restore_session_title_state(
|
|
733
|
+
event.session_id,
|
|
734
|
+
prefer_recovery=event.recovered_from_sidecar,
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
# Load compressed blocks (needed for both replay and Context Panel)
|
|
738
|
+
compressed_blocks_map: dict[str, CompressedBlock] = {}
|
|
739
|
+
loaded_state: dict[str, Any] | None = None
|
|
740
|
+
with contextlib.suppress(Exception):
|
|
741
|
+
ui.clear_compressed_blocks()
|
|
742
|
+
prefer_recovery = event.recovered_from_sidecar
|
|
743
|
+
if s.state_store:
|
|
744
|
+
try:
|
|
745
|
+
loaded = await s.state_store.load_session(event.session_id, prefer_recovery=prefer_recovery)
|
|
746
|
+
if loaded:
|
|
747
|
+
loaded_state = loaded
|
|
748
|
+
restored_has_messages = has_real_messages(loaded_state)
|
|
749
|
+
for block in loaded_state.get("compressed_msgs", []):
|
|
750
|
+
ctx_id = block.compressed_context_id
|
|
751
|
+
if ctx_id:
|
|
752
|
+
compressed_blocks_map[ctx_id] = block
|
|
753
|
+
ui.add_compressed_block(
|
|
754
|
+
ctx_id,
|
|
755
|
+
block.summary_text,
|
|
756
|
+
len(block.messages),
|
|
757
|
+
block.turn_range,
|
|
758
|
+
)
|
|
759
|
+
except Exception:
|
|
760
|
+
pass
|
|
761
|
+
s.set_has_messages(restored_has_messages)
|
|
762
|
+
|
|
763
|
+
# Reseed the Tasks panel from the restored state (covers session
|
|
764
|
+
# switch AND rollback-to-N) via the shared tolerant decoder — an
|
|
765
|
+
# ad-hoc parse could crash the sidebar on malformed/legacy state.
|
|
766
|
+
ui.set_todo_state(parse_todo_items(loaded_state.get("chrys_todos") if loaded_state else None))
|
|
767
|
+
|
|
768
|
+
# Replay conversation history from saved state
|
|
769
|
+
messages: list[dict[str, Any]] | None = None
|
|
770
|
+
if s.state_store:
|
|
771
|
+
messages = await s.state_store.load_session_raw(event.session_id, prefer_recovery=prefer_recovery)
|
|
772
|
+
if messages:
|
|
773
|
+
file_snapshots = self.load_file_edit_snapshots(event.session_id, messages, loaded_state)
|
|
774
|
+
|
|
775
|
+
await ui.replay_history(
|
|
776
|
+
messages,
|
|
777
|
+
initial_profile=event.initial_agent_profile or display,
|
|
778
|
+
file_snapshots=file_snapshots or None,
|
|
779
|
+
compressed_blocks=compressed_blocks_map or None,
|
|
780
|
+
)
|
|
781
|
+
s.update_toc()
|
|
782
|
+
|
|
783
|
+
# Check if the session ended with an error/interrupt — enable retry
|
|
784
|
+
if messages:
|
|
785
|
+
for m in reversed(messages):
|
|
786
|
+
ct = m.get("additional_properties", {}).get(HistoryMarkerKind.KEY)
|
|
787
|
+
if ct == HistoryMarkerKind.TURN:
|
|
788
|
+
continue
|
|
789
|
+
if ct == HistoryMarkerKind.INTERRUPTED:
|
|
790
|
+
source = m.get("additional_properties", {}).get("_interrupted_by", "user")
|
|
791
|
+
ui.set_input_retry(INPUT_RETRY.bind() if source == "error" else INPUT_CONTINUE.bind())
|
|
792
|
+
break
|
|
793
|
+
|
|
794
|
+
# Restored workspace roots are backend-confirmed — record them in the
|
|
795
|
+
# MRU as a detached background task (never inline in the handler).
|
|
796
|
+
schedule_workspace_mru_touches(
|
|
797
|
+
[cwd, *event.working_dirs],
|
|
798
|
+
max_entries=self._services.workspace_mru_max_entries,
|
|
799
|
+
session_id=event.session_id or "",
|
|
800
|
+
used_at=event.timestamp,
|
|
801
|
+
)
|
|
802
|
+
short_id = session_short_id(event.session_id)
|
|
803
|
+
info = STATUS_SESSION_RESTORED.bind(session_id=short_id)
|
|
804
|
+
ui.flash_status(info)
|
|
805
|
+
self._agent_load.finish_agent_load(_FINISH_SESSION_RESTORED.bind(session_id=short_id))
|
|
806
|
+
s.debug("SessionRestored", short_id)
|
|
807
|
+
self._callbacks.post_gc_message(GcReclaimRequested(GcReclaimReason.SESSION_RESTORED, prompt=True))
|
|
808
|
+
|
|
809
|
+
async def on_session_saved(self, event: SessionSaved) -> None:
|
|
810
|
+
self.debug("SessionSaved", session_short_id(event.session_id))
|
|
811
|
+
|
|
812
|
+
async def _restore_session_title_state(self, session_id: str, *, prefer_recovery: bool = False) -> None:
|
|
813
|
+
"""Reload title overlays from the session meta into the UI.
|
|
814
|
+
|
|
815
|
+
A crash-recovered session's history comes from the recovery
|
|
816
|
+
sidecar; its meta (title patches are mirrored into a live sidecar,
|
|
817
|
+
and a never-saved session has no primary at all) must come from
|
|
818
|
+
the same source.
|
|
819
|
+
"""
|
|
820
|
+
s = self
|
|
821
|
+
ui = self._ui()
|
|
822
|
+
ui.reset_session_title_state()
|
|
823
|
+
if not s.state_store:
|
|
824
|
+
return
|
|
825
|
+
try:
|
|
826
|
+
meta = await s.state_store.load_session_meta(session_id, prefer_recovery=prefer_recovery)
|
|
827
|
+
except Exception:
|
|
828
|
+
return
|
|
829
|
+
if meta is None:
|
|
830
|
+
return
|
|
831
|
+
ui.set_session_title_state(
|
|
832
|
+
custom=meta.custom_title,
|
|
833
|
+
generated=meta.generated_title,
|
|
834
|
+
fallback=meta.title,
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
async def apply_custom_session_title(self, custom_title: str, session_id: str) -> None:
|
|
838
|
+
"""Persist a user-edited custom title (empty clears it) and refresh the UI.
|
|
839
|
+
|
|
840
|
+
``session_id`` is the session the edit dialog was opened for; the
|
|
841
|
+
title is persisted there even if the UI has since moved to another
|
|
842
|
+
session, but the on-screen title only updates while it still shows
|
|
843
|
+
that session.
|
|
844
|
+
"""
|
|
845
|
+
s = self
|
|
846
|
+
ui = self._ui()
|
|
847
|
+
if not session_id or not s.state_store:
|
|
848
|
+
return
|
|
849
|
+
try:
|
|
850
|
+
async with self._custom_title_apply_lock:
|
|
851
|
+
meta = await s.state_store.update_session_titles(session_id, custom_title=custom_title)
|
|
852
|
+
except Exception:
|
|
853
|
+
s.debug("SessionTitle", "custom title save failed")
|
|
854
|
+
s.notify(
|
|
855
|
+
_SESSION_TITLE_SAVE_FAILED.bind(),
|
|
856
|
+
title=_SESSION_TITLE_TITLE.bind(),
|
|
857
|
+
severity="error",
|
|
858
|
+
timeout=3,
|
|
859
|
+
)
|
|
860
|
+
return
|
|
861
|
+
with contextlib.suppress(Exception):
|
|
862
|
+
await s.bus.publish(
|
|
863
|
+
SessionTitleUpdated(
|
|
864
|
+
session_id=session_id,
|
|
865
|
+
title=custom_title,
|
|
866
|
+
custom=True,
|
|
867
|
+
# A clear falls back to the generated/first-message
|
|
868
|
+
# title; ``meta`` is None only for unsaved sessions,
|
|
869
|
+
# which have no persisted fallback to fall back to.
|
|
870
|
+
display_title=meta.display_title if meta is not None else custom_title,
|
|
871
|
+
),
|
|
872
|
+
)
|
|
873
|
+
if ui.chat_session_id() != session_id:
|
|
874
|
+
return
|
|
875
|
+
if meta is not None:
|
|
876
|
+
ui.set_session_title_state(
|
|
877
|
+
custom=meta.custom_title,
|
|
878
|
+
generated=meta.generated_title,
|
|
879
|
+
fallback=meta.title,
|
|
880
|
+
)
|
|
881
|
+
else:
|
|
882
|
+
ui.set_session_title_state(custom=custom_title)
|
|
883
|
+
definition = STATUS_SESSION_TITLE_UPDATED if custom_title else STATUS_CUSTOM_TITLE_CLEARED
|
|
884
|
+
ui.flash_status(definition.bind())
|
|
885
|
+
|
|
886
|
+
async def on_session_title_updated(self, event: SessionTitleUpdated) -> None:
|
|
887
|
+
"""Reflect a persisted auto-generated title for the active session."""
|
|
888
|
+
ui = self._ui()
|
|
889
|
+
if event.custom or event.session_id != ui.chat_session_id():
|
|
890
|
+
return
|
|
891
|
+
if ui.session_custom_title():
|
|
892
|
+
return
|
|
893
|
+
ui.set_session_title_state(generated=event.title)
|
|
894
|
+
self.debug("SessionTitleUpdated", event.title)
|
|
895
|
+
|
|
896
|
+
async def on_session_forked(self, event: SessionForked) -> None:
|
|
897
|
+
s = self
|
|
898
|
+
ui = self._ui()
|
|
899
|
+
|
|
900
|
+
current_session_id = ui.chat_session_id()
|
|
901
|
+
if event.session_id != current_session_id:
|
|
902
|
+
return
|
|
903
|
+
|
|
904
|
+
fork_short_id = session_short_id(event.new_session_id)
|
|
905
|
+
if self._fork_dialog is None:
|
|
906
|
+
try:
|
|
907
|
+
await self._open_session_fork_dialog(event.session_id)
|
|
908
|
+
except Exception as exc:
|
|
909
|
+
s.set_agent_loading(False)
|
|
910
|
+
s.notify(
|
|
911
|
+
_FORK_CREATED.bind(fork_id=fork_short_id),
|
|
912
|
+
title=_FORK_TITLE.bind(),
|
|
913
|
+
severity="information",
|
|
914
|
+
timeout=3,
|
|
915
|
+
)
|
|
916
|
+
s.debug("SessionForked", f"{fork_short_id}; dialog failed: {exc}")
|
|
917
|
+
return
|
|
918
|
+
|
|
919
|
+
self._fork_new_session_id = event.new_session_id
|
|
920
|
+
fork_dialog = self._fork_dialog
|
|
921
|
+
if fork_dialog is not None:
|
|
922
|
+
fork_dialog.set_success(fork_short_id)
|
|
923
|
+
s.set_agent_loading(False)
|
|
924
|
+
s.notify(
|
|
925
|
+
_FORK_CREATED.bind(fork_id=fork_short_id),
|
|
926
|
+
title=_FORK_TITLE.bind(),
|
|
927
|
+
severity="information",
|
|
928
|
+
timeout=3,
|
|
929
|
+
)
|
|
930
|
+
s.debug("SessionForked", fork_short_id)
|
|
931
|
+
|
|
932
|
+
def on_session_clear_error(self, event: Error, *, message: str) -> None:
|
|
933
|
+
"""A /clear failed before anything was deleted: the current session stays."""
|
|
934
|
+
s = self
|
|
935
|
+
ui = self._ui()
|
|
936
|
+
|
|
937
|
+
s.creating_new_session = False
|
|
938
|
+
s.set_agent_loading(False)
|
|
939
|
+
ui.unlock_input_keep_if_locked()
|
|
940
|
+
s.notify(
|
|
941
|
+
_CLEAR_FAILED.bind(message=message),
|
|
942
|
+
title=CLEAR_SESSION_TITLE.bind(),
|
|
943
|
+
severity="error",
|
|
944
|
+
timeout=5,
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
def on_session_fork_error(self, event: Error, *, message: str, severity: NotificationSeverity) -> None:
|
|
948
|
+
"""Show a fork failure in the active fork dialog, if present."""
|
|
949
|
+
s = self
|
|
950
|
+
ui = self._ui()
|
|
951
|
+
|
|
952
|
+
if event.session_id and self._fork_session_id and event.session_id != self._fork_session_id:
|
|
953
|
+
return
|
|
954
|
+
|
|
955
|
+
if self._fork_dialog is not None:
|
|
956
|
+
self._fork_dialog.set_error(message)
|
|
957
|
+
else:
|
|
958
|
+
self._reset_session_fork_dialog()
|
|
959
|
+
s.set_agent_loading(False)
|
|
960
|
+
ui.flash_status(STATUS_FORK_NOTICE.bind(message=message), error=severity == "error")
|
|
961
|
+
ui.unlock_input_keep_if_locked()
|
|
962
|
+
s.notify(message, title=_FORK_TITLE.bind(), severity=severity, timeout=5)
|
|
963
|
+
|
|
964
|
+
async def on_profile_switched(self, event: ProfileSwitched) -> None:
|
|
965
|
+
"""Handle profile switch — update UI and show switch marker in chat."""
|
|
966
|
+
s = self
|
|
967
|
+
ui = self._ui()
|
|
968
|
+
|
|
969
|
+
to_label = event.to_display_name or event.to_profile
|
|
970
|
+
from_label = event.from_display_name or event.from_profile
|
|
971
|
+
s.runtime_metadata = event.runtime_details
|
|
972
|
+
self._state.runtime.details_confirmed = True
|
|
973
|
+
if event.runtime_details.model.selection_source == "active":
|
|
974
|
+
self._callbacks.set_active_model_profile_id(event.runtime_details.model.profile_id)
|
|
975
|
+
|
|
976
|
+
# Same profile (settings reload / workspace change) — update tool info
|
|
977
|
+
# and context panel but skip profile switch UI.
|
|
978
|
+
if event.from_profile == event.to_profile:
|
|
979
|
+
self._callbacks.refresh_model_indicator()
|
|
980
|
+
trail = self._format_tool_info(
|
|
981
|
+
event.tool_names,
|
|
982
|
+
event.skill_names,
|
|
983
|
+
memory_files=event.memory_files,
|
|
984
|
+
runtime_details=event.runtime_details,
|
|
985
|
+
)
|
|
986
|
+
ui.set_tool_info(trail)
|
|
987
|
+
ui.clear_status()
|
|
988
|
+
if event.max_context_tokens:
|
|
989
|
+
current_context_usage = s.context_usage_state
|
|
990
|
+
ui.set_context_usage_state(
|
|
991
|
+
ContextUsageState.with_window(
|
|
992
|
+
used_tokens=current_context_usage.used_tokens if current_context_usage else s.last_usage_tokens,
|
|
993
|
+
max_context_tokens=event.max_context_tokens,
|
|
994
|
+
total_session_tokens=(
|
|
995
|
+
current_context_usage.total_session_tokens
|
|
996
|
+
if current_context_usage
|
|
997
|
+
else s.last_total_session_tokens
|
|
998
|
+
),
|
|
999
|
+
total_session_input_tokens=(
|
|
1000
|
+
current_context_usage.total_session_input_tokens if current_context_usage else 0
|
|
1001
|
+
),
|
|
1002
|
+
total_session_output_tokens=(
|
|
1003
|
+
current_context_usage.total_session_output_tokens if current_context_usage else 0
|
|
1004
|
+
),
|
|
1005
|
+
total_session_cache_hit_tokens=(
|
|
1006
|
+
current_context_usage.total_session_cache_hit_tokens if current_context_usage else None
|
|
1007
|
+
),
|
|
1008
|
+
)
|
|
1009
|
+
)
|
|
1010
|
+
self._agent_load.finish_agent_load(_FINISH_AGENT_READY.bind(agent=to_label))
|
|
1011
|
+
self._callbacks.post_gc_message(GcAbsorbRequested(GcAbsorbReason.PROFILE_UI_UPDATED))
|
|
1012
|
+
return
|
|
1013
|
+
|
|
1014
|
+
s.profile = to_label
|
|
1015
|
+
self._callbacks.refresh_model_indicator()
|
|
1016
|
+
s.update_subtitle()
|
|
1017
|
+
ui.set_chat_profile(to_label)
|
|
1018
|
+
desc = self._get_profile_description(event.to_profile)
|
|
1019
|
+
ui.set_status_profile(to_label, description=desc)
|
|
1020
|
+
|
|
1021
|
+
if s.has_messages:
|
|
1022
|
+
# Each fresh switch (after messages) gets a unique key so committed
|
|
1023
|
+
# switch indicators stay permanently in the chat history.
|
|
1024
|
+
existing_from_label = s.profile_switch_from
|
|
1025
|
+
if existing_from_label is None:
|
|
1026
|
+
# Fresh switch — new unique key
|
|
1027
|
+
s.profile_switch_seq += 1
|
|
1028
|
+
key = f"profile-switch-{s.profile_switch_seq}"
|
|
1029
|
+
s.profile_switch_from = from_label
|
|
1030
|
+
s.profile_switch_to = to_label
|
|
1031
|
+
await ui.add_system(
|
|
1032
|
+
self._render_message(_PROFILE_SWITCH_INDICATOR.bind(from_label=from_label, to_label=to_label)),
|
|
1033
|
+
key=key,
|
|
1034
|
+
)
|
|
1035
|
+
elif s.profile_switch_to == from_label:
|
|
1036
|
+
# Consecutive switch (no messages since last switch)
|
|
1037
|
+
key = f"profile-switch-{s.profile_switch_seq}"
|
|
1038
|
+
s.profile_switch_to = to_label
|
|
1039
|
+
if existing_from_label == to_label:
|
|
1040
|
+
await ui.remove_system(key)
|
|
1041
|
+
s.profile_switch_from = None
|
|
1042
|
+
s.profile_switch_to = None
|
|
1043
|
+
else:
|
|
1044
|
+
await ui.update_system(
|
|
1045
|
+
key,
|
|
1046
|
+
self._render_message(
|
|
1047
|
+
_PROFILE_SWITCH_INDICATOR.bind(
|
|
1048
|
+
from_label=existing_from_label,
|
|
1049
|
+
to_label=to_label,
|
|
1050
|
+
)
|
|
1051
|
+
),
|
|
1052
|
+
)
|
|
1053
|
+
else:
|
|
1054
|
+
# Chain broken (e.g. A→B then C→D where C != B) — remove old
|
|
1055
|
+
key = f"profile-switch-{s.profile_switch_seq}"
|
|
1056
|
+
await ui.remove_system(key)
|
|
1057
|
+
s.profile_switch_from = None
|
|
1058
|
+
s.profile_switch_to = None
|
|
1059
|
+
else:
|
|
1060
|
+
ui.update_welcome(profile=to_label, cwd=s.workspace_cwd())
|
|
1061
|
+
|
|
1062
|
+
trail = self._format_tool_info(
|
|
1063
|
+
event.tool_names,
|
|
1064
|
+
event.skill_names,
|
|
1065
|
+
memory_files=event.memory_files,
|
|
1066
|
+
runtime_details=event.runtime_details,
|
|
1067
|
+
)
|
|
1068
|
+
ui.set_tool_info(trail)
|
|
1069
|
+
ui.clear_status()
|
|
1070
|
+
if event.sub_agent_tool_names:
|
|
1071
|
+
from chrys.app.tui.widgets.chat.renderers.sub_agent import SubAgentToolCall
|
|
1072
|
+
from chrys.app.tui.widgets.chat.tool_renderers import register_dynamic_renderer
|
|
1073
|
+
|
|
1074
|
+
for name in event.sub_agent_tool_names:
|
|
1075
|
+
register_dynamic_renderer(name, SubAgentToolCall)
|
|
1076
|
+
s.debug("ProfileSwitched", f"{from_label} -> {to_label}")
|
|
1077
|
+
self._agent_load.finish_agent_load(_FINISH_PROFILE_SWITCHED.bind(from_label=from_label, to_label=to_label))
|
|
1078
|
+
self._callbacks.post_gc_message(GcAbsorbRequested(GcAbsorbReason.PROFILE_UI_UPDATED))
|
|
1079
|
+
|
|
1080
|
+
async def on_workspace_updated(self, event: WorkspaceUpdated) -> None:
|
|
1081
|
+
"""Handle workspace update — refresh all cwd-dependent TUI state."""
|
|
1082
|
+
s = self
|
|
1083
|
+
ui = self._ui()
|
|
1084
|
+
|
|
1085
|
+
self._state.workspace.roots = [event.primary_cwd, *event.working_dirs]
|
|
1086
|
+
s.clear_suggestion_file_cache()
|
|
1087
|
+
ui.set_input_paste_cwd(event.primary_cwd)
|
|
1088
|
+
ui.set_chat_workspace_cwd(event.primary_cwd)
|
|
1089
|
+
if os.path.isdir(event.primary_cwd):
|
|
1090
|
+
await ui.change_shell_directory(event.primary_cwd)
|
|
1091
|
+
# Always track the latest cwd so callers (e.g. agent config screen)
|
|
1092
|
+
# can see the live workspace, even before the first message.
|
|
1093
|
+
previous_cwd = s.chdir_current_cwd
|
|
1094
|
+
s.chdir_current_cwd = event.primary_cwd
|
|
1095
|
+
ui.set_terminal_title_for_cwd(event.primary_cwd)
|
|
1096
|
+
|
|
1097
|
+
panel_key = "chdir"
|
|
1098
|
+
if s.has_messages:
|
|
1099
|
+
if s.chdir_original_cwd is None:
|
|
1100
|
+
await ui.remove_system(panel_key)
|
|
1101
|
+
s.chdir_original_cwd = previous_cwd
|
|
1102
|
+
await ui.add_system(
|
|
1103
|
+
self._render_message(_WORKING_DIRECTORY_INDICATOR.bind(path=DisplayPath(event.primary_cwd))),
|
|
1104
|
+
key=panel_key,
|
|
1105
|
+
)
|
|
1106
|
+
else:
|
|
1107
|
+
if _samepath(s.chdir_original_cwd, s.chdir_current_cwd):
|
|
1108
|
+
await ui.remove_system(panel_key)
|
|
1109
|
+
s.chdir_original_cwd = None
|
|
1110
|
+
else:
|
|
1111
|
+
await ui.update_system(
|
|
1112
|
+
panel_key,
|
|
1113
|
+
self._render_message(_WORKING_DIRECTORY_INDICATOR.bind(path=DisplayPath(event.primary_cwd))),
|
|
1114
|
+
)
|
|
1115
|
+
else:
|
|
1116
|
+
ui.update_welcome(profile=s.profile, cwd=event.primary_cwd)
|
|
1117
|
+
|
|
1118
|
+
# The workspace change is backend-confirmed at this point — record it
|
|
1119
|
+
# in the MRU as a detached background task (never inline).
|
|
1120
|
+
schedule_workspace_mru_touches(
|
|
1121
|
+
[event.primary_cwd, *event.working_dirs],
|
|
1122
|
+
max_entries=self._services.workspace_mru_max_entries,
|
|
1123
|
+
session_id=event.session_id or "",
|
|
1124
|
+
used_at=event.timestamp,
|
|
1125
|
+
)
|
|
1126
|
+
s.debug("WorkspaceUpdated", event.primary_cwd)
|
|
1127
|
+
self._callbacks.post_gc_message(GcAbsorbRequested(GcAbsorbReason.WORKSPACE_UI_UPDATED))
|