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,983 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""The trajectory dashboard widget: tabs, loading and live refresh, and the
|
|
4
|
+
presentation cache that feeds the text view the pages render into."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from dataclasses import replace
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from functools import partial
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from threading import Event
|
|
14
|
+
from time import monotonic
|
|
15
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
16
|
+
|
|
17
|
+
from rich.style import Style
|
|
18
|
+
from rich.text import Text
|
|
19
|
+
from textual import events, on
|
|
20
|
+
from textual.cache import LRUCache
|
|
21
|
+
from textual.containers import Container, VerticalGroup
|
|
22
|
+
from textual.css.query import NoMatches
|
|
23
|
+
from textual.geometry import Size
|
|
24
|
+
from textual.message import Message
|
|
25
|
+
from textual.widgets import Tab, Tabs
|
|
26
|
+
|
|
27
|
+
from chrys.app.tui.binding_display import localized_binding
|
|
28
|
+
from chrys.app.tui.clipboard import copy_text_to_clipboards
|
|
29
|
+
from chrys.app.tui.copy_messages import COPIED_TITLE
|
|
30
|
+
from chrys.app.tui.support.file_manager import can_open_in_file_manager, open_in_file_manager
|
|
31
|
+
from chrys.app.tui.support.gc_freeze import (
|
|
32
|
+
DetachedLruCache,
|
|
33
|
+
GcFreezeBlockReason,
|
|
34
|
+
detach_lru_cache,
|
|
35
|
+
renew_lru_cache,
|
|
36
|
+
)
|
|
37
|
+
from chrys.app.tui.widgets.chat.session_json import SessionJsonPanel
|
|
38
|
+
from chrys.app.tui.widgets.hatch import hatch_text_style, hatched_text_line
|
|
39
|
+
from chrys.app.tui.widgets.loading import ChrysLoadingIndicator
|
|
40
|
+
from chrys.app.tui.widgets.trajectory.insights import has_diagnostic_content, insights_lines
|
|
41
|
+
from chrys.app.tui.widgets.trajectory.overview import overview_lines
|
|
42
|
+
from chrys.app.tui.widgets.trajectory.presentation import (
|
|
43
|
+
NO_TURNS,
|
|
44
|
+
PRECISION_SYMBOLS,
|
|
45
|
+
TURN_LABEL,
|
|
46
|
+
UNAVAILABLE,
|
|
47
|
+
DashboardLook,
|
|
48
|
+
RenderContext,
|
|
49
|
+
ResponsiveTier,
|
|
50
|
+
precision_label,
|
|
51
|
+
precision_style,
|
|
52
|
+
render_message,
|
|
53
|
+
)
|
|
54
|
+
from chrys.app.tui.widgets.trajectory.session_info import SessionStorage, collect_session_storage
|
|
55
|
+
from chrys.app.tui.widgets.trajectory.text_view import TrajectoryTextView
|
|
56
|
+
from chrys.app.tui.widgets.trajectory.timeline import dependency_graph_lines, timeline_lines
|
|
57
|
+
from chrys.foundation.config.settings import DEFAULT_TRAJECTORY_VERIFY_COMMANDS
|
|
58
|
+
from chrys.foundation.i18n import Localizer, MessageDef, MessageRef, msg
|
|
59
|
+
from chrys.foundation.platform.files import surrogate_safe_text
|
|
60
|
+
from chrys.foundation.util.session_ids import session_short_id
|
|
61
|
+
from chrys.service.analytics import (
|
|
62
|
+
AnalysisAvailability,
|
|
63
|
+
TrajectoryAnalysis,
|
|
64
|
+
TrajectoryAnalyzer,
|
|
65
|
+
TrajectoryScanCancelled,
|
|
66
|
+
TurnAnalysis,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if TYPE_CHECKING:
|
|
70
|
+
from collections.abc import Callable
|
|
71
|
+
|
|
72
|
+
from textual.app import ComposeResult
|
|
73
|
+
from textual.timer import Timer
|
|
74
|
+
from textual.worker import Worker
|
|
75
|
+
|
|
76
|
+
from chrys.app.tui.i18n import LocaleController
|
|
77
|
+
|
|
78
|
+
# The session folder's sizes live outside the events log, so the live refresh
|
|
79
|
+
# recollects them on this coarser clock instead of walking the tree every tick.
|
|
80
|
+
_STORAGE_REFRESH_INTERVAL_S = 5.0
|
|
81
|
+
|
|
82
|
+
_DASHBOARD_TITLE = msg("tui.trajectory.title", fallback="Trajectory")
|
|
83
|
+
_OVERVIEW_TAB = msg("tui.trajectory.tab.overview", fallback="Overview")
|
|
84
|
+
_TIMELINE_TAB = msg("tui.trajectory.tab.timeline", fallback="Timeline")
|
|
85
|
+
_INSIGHTS_TAB = msg("tui.trajectory.tab.insights", fallback="Insights")
|
|
86
|
+
_SESSION_DATA_TAB = msg("tui.trajectory.tab.session_data", fallback="Session data")
|
|
87
|
+
_TOGGLE_GRAPH_BINDING = msg("tui.binding.toggle_graph", fallback="Dependency graph")
|
|
88
|
+
_READ_ERROR = msg("tui.trajectory.read_error", fallback="Trajectory read error: {error}")
|
|
89
|
+
_NO_ACTIVE_SESSION = msg("tui.main.session_json.no_active_session", fallback="No active session.")
|
|
90
|
+
_SESSION_INFO_PATH_COPIED = msg("tui.trajectory.session_info.path_copied", fallback="Path copied")
|
|
91
|
+
_SESSION_INFO_OPEN_FAILED = msg(
|
|
92
|
+
"tui.trajectory.session_info.open_failed",
|
|
93
|
+
fallback="Could not open the session folder: {error}",
|
|
94
|
+
)
|
|
95
|
+
_SESSION_INFO_OPEN_UNAVAILABLE = msg(
|
|
96
|
+
"tui.trajectory.session_info.open_unavailable",
|
|
97
|
+
fallback="Opening the session folder is not available in the current environment.",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
_TABS_HEIGHT = 2
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class DashboardTab(StrEnum):
|
|
104
|
+
"""The frozen four-tab dashboard information architecture."""
|
|
105
|
+
|
|
106
|
+
OVERVIEW = "overview"
|
|
107
|
+
TIMELINE = "timeline"
|
|
108
|
+
INSIGHTS = "insights"
|
|
109
|
+
SESSION_DATA = "session-data"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TrajectoryDashboard(Container):
|
|
113
|
+
"""Own dashboard foreground and active-tab state as one atomic pair."""
|
|
114
|
+
|
|
115
|
+
can_focus = True
|
|
116
|
+
|
|
117
|
+
BINDINGS: ClassVar[list] = [
|
|
118
|
+
localized_binding("space", "toggle_timeline_dependencies", _TOGGLE_GRAPH_BINDING, show=False),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
COMPONENT_CLASSES: ClassVar[set[str]] = {"trajectory-dashboard--muted-label", "hatch--pattern"}
|
|
122
|
+
|
|
123
|
+
DEFAULT_CSS = """
|
|
124
|
+
TrajectoryDashboard > .hatch--pattern {
|
|
125
|
+
color: $hatch-color;
|
|
126
|
+
}
|
|
127
|
+
TrajectoryDashboard > .trajectory-dashboard--muted-label {
|
|
128
|
+
color: $text-muted;
|
|
129
|
+
}
|
|
130
|
+
TrajectoryDashboard {
|
|
131
|
+
height: 1fr;
|
|
132
|
+
display: none;
|
|
133
|
+
layer: overlay;
|
|
134
|
+
layers: default loading;
|
|
135
|
+
background: $background;
|
|
136
|
+
padding: 0 1;
|
|
137
|
+
border: round $tui-border-accent $border-opacity;
|
|
138
|
+
border-title-align: left;
|
|
139
|
+
border-title-color: $tui-border-title-accent;
|
|
140
|
+
border-subtitle-align: right;
|
|
141
|
+
border-subtitle-color: $tui-border-title-accent;
|
|
142
|
+
}
|
|
143
|
+
TrajectoryDashboard > Tabs {
|
|
144
|
+
height: 2;
|
|
145
|
+
}
|
|
146
|
+
TrajectoryDashboard > #timeline-turn-tabs {
|
|
147
|
+
display: none;
|
|
148
|
+
}
|
|
149
|
+
/* The loading state floats on its own layer below the tab strip, so the
|
|
150
|
+
same indicator covers either the hidden text view or the Session Data
|
|
151
|
+
viewer while that viewer's background load is still running. */
|
|
152
|
+
TrajectoryDashboard > #trajectory-loading-state {
|
|
153
|
+
display: none;
|
|
154
|
+
layer: loading;
|
|
155
|
+
margin-top: 2;
|
|
156
|
+
width: 100%;
|
|
157
|
+
height: 1fr;
|
|
158
|
+
min-height: 1;
|
|
159
|
+
align: center middle;
|
|
160
|
+
background: $background;
|
|
161
|
+
}
|
|
162
|
+
TrajectoryDashboard > #trajectory-loading-state > ChrysLoadingIndicator {
|
|
163
|
+
width: 12;
|
|
164
|
+
height: 1;
|
|
165
|
+
color: $primary;
|
|
166
|
+
}
|
|
167
|
+
TrajectoryDashboard > SessionJsonPanel {
|
|
168
|
+
height: 1fr;
|
|
169
|
+
border: none;
|
|
170
|
+
}
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
class StateChanged(Message):
|
|
174
|
+
"""The foreground derivations need synchronization by MainScreen."""
|
|
175
|
+
|
|
176
|
+
def __init__(
|
|
177
|
+
self,
|
|
178
|
+
*,
|
|
179
|
+
locale_controller: LocaleController | None = None,
|
|
180
|
+
verify_commands: str = DEFAULT_TRAJECTORY_VERIFY_COMMANDS,
|
|
181
|
+
) -> None:
|
|
182
|
+
super().__init__()
|
|
183
|
+
self._locale_controller = locale_controller
|
|
184
|
+
self._verify_commands = verify_commands
|
|
185
|
+
self.foreground = False
|
|
186
|
+
self.active_tab = DashboardTab.OVERVIEW
|
|
187
|
+
self._timeline_dependencies = False
|
|
188
|
+
self._selected_turn_id: str | None = None
|
|
189
|
+
self._session_id = ""
|
|
190
|
+
self._path: Path | None = None
|
|
191
|
+
self._session_json_loaded = False
|
|
192
|
+
self._turn_tab_key: tuple[tuple[str, str], ...] = ()
|
|
193
|
+
self._analyzer: TrajectoryAnalyzer | None = None
|
|
194
|
+
self._analysis: TrajectoryAnalysis | None = None
|
|
195
|
+
self._session_storage: SessionStorage | None = None
|
|
196
|
+
self._can_open_session_folder = can_open_in_file_manager()
|
|
197
|
+
self._storage_collected_at = -_STORAGE_REFRESH_INTERVAL_S
|
|
198
|
+
self._load_generation = 0
|
|
199
|
+
self._load_pending = False
|
|
200
|
+
self._worker: Worker[Any] | None = None
|
|
201
|
+
self._scan_cancel_event: Event | None = None
|
|
202
|
+
self._live_timer: Timer | None = None
|
|
203
|
+
self._shell_restore_foreground = False
|
|
204
|
+
self._available_size = Size(0, 0)
|
|
205
|
+
self._presentation_key: tuple[int, int, int] | None = None
|
|
206
|
+
self._presentation_revision = 0
|
|
207
|
+
self._render_identity: tuple[DashboardTab, str | None] | None = None
|
|
208
|
+
self._presentation_cache: LRUCache[tuple[object, ...], tuple[Text, ...]] | DetachedLruCache = LRUCache(
|
|
209
|
+
maxsize=64
|
|
210
|
+
)
|
|
211
|
+
self._update_border_labels()
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def chat_foreground(self) -> bool:
|
|
215
|
+
return not self.foreground
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def session_json_visible(self) -> bool:
|
|
219
|
+
return self.foreground and self.active_tab is DashboardTab.SESSION_DATA
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def responsive_tier(self) -> ResponsiveTier:
|
|
223
|
+
return ResponsiveTier.for_width(self._available_width())
|
|
224
|
+
|
|
225
|
+
def compose(self) -> ComposeResult:
|
|
226
|
+
yield Tabs(
|
|
227
|
+
Tab(Text(self._render_message(_OVERVIEW_TAB.bind())), id=DashboardTab.OVERVIEW),
|
|
228
|
+
Tab(Text(self._render_message(_TIMELINE_TAB.bind())), id=DashboardTab.TIMELINE),
|
|
229
|
+
Tab(Text(self._render_message(_INSIGHTS_TAB.bind())), id=DashboardTab.INSIGHTS),
|
|
230
|
+
Tab(Text(self._render_message(_SESSION_DATA_TAB.bind())), id=DashboardTab.SESSION_DATA),
|
|
231
|
+
active=self.active_tab,
|
|
232
|
+
id="trajectory-tabs",
|
|
233
|
+
)
|
|
234
|
+
yield Tabs(id="timeline-turn-tabs")
|
|
235
|
+
with VerticalGroup(id="trajectory-loading-state"):
|
|
236
|
+
yield ChrysLoadingIndicator(id="trajectory-loading")
|
|
237
|
+
yield TrajectoryTextView(
|
|
238
|
+
on_resized=self._render_for_settled_view,
|
|
239
|
+
open_session_folder=self.open_session_folder,
|
|
240
|
+
copy_session_path=self.copy_session_path,
|
|
241
|
+
)
|
|
242
|
+
yield SessionJsonPanel(locale_controller=self._locale_controller)
|
|
243
|
+
|
|
244
|
+
def on_mount(self) -> None:
|
|
245
|
+
if self._locale_controller is not None:
|
|
246
|
+
self._locale_controller.register_surface(self)
|
|
247
|
+
self._live_timer = self.set_interval(0.5, self._refresh_live)
|
|
248
|
+
self._live_timer.pause()
|
|
249
|
+
self._sync_content_visibility()
|
|
250
|
+
|
|
251
|
+
def on_unmount(self) -> None:
|
|
252
|
+
if self._locale_controller is not None:
|
|
253
|
+
self._locale_controller.unregister_surface(self)
|
|
254
|
+
self._cancel_worker()
|
|
255
|
+
analyzer = self._analyzer
|
|
256
|
+
if analyzer is not None:
|
|
257
|
+
analyzer.release()
|
|
258
|
+
self._analyzer = None
|
|
259
|
+
self._analysis = None
|
|
260
|
+
|
|
261
|
+
def on_resize(self, event: events.Resize) -> None:
|
|
262
|
+
"""Invalidate presentation only; trajectory analysis never depends on size."""
|
|
263
|
+
content_size = self.size
|
|
264
|
+
if content_size == self._available_size:
|
|
265
|
+
return
|
|
266
|
+
self._available_size = content_size
|
|
267
|
+
if self.foreground and not self.session_json_visible:
|
|
268
|
+
self._render_active_view()
|
|
269
|
+
|
|
270
|
+
def _render_for_settled_view(self) -> None:
|
|
271
|
+
"""Re-render after the text view's own size settles asynchronously."""
|
|
272
|
+
if self.is_mounted and self.foreground and not self.session_json_visible:
|
|
273
|
+
self._render_active_view()
|
|
274
|
+
|
|
275
|
+
def notify_style_update(self) -> None:
|
|
276
|
+
"""Rebuild presentation lines that contain resolved theme colors."""
|
|
277
|
+
super().notify_style_update()
|
|
278
|
+
self._presentation_revision += 1
|
|
279
|
+
self._update_border_labels()
|
|
280
|
+
self._clear_presentation_cache()
|
|
281
|
+
self._render_active_view()
|
|
282
|
+
|
|
283
|
+
def refresh_localization(self) -> None:
|
|
284
|
+
self._presentation_revision += 1
|
|
285
|
+
self._update_border_labels()
|
|
286
|
+
for tab, label in (
|
|
287
|
+
(DashboardTab.OVERVIEW, _OVERVIEW_TAB),
|
|
288
|
+
(DashboardTab.TIMELINE, _TIMELINE_TAB),
|
|
289
|
+
(DashboardTab.INSIGHTS, _INSIGHTS_TAB),
|
|
290
|
+
(DashboardTab.SESSION_DATA, _SESSION_DATA_TAB),
|
|
291
|
+
):
|
|
292
|
+
self.query_one(f"#{tab}", Tab).label = Text(self._render_message(label.bind()))
|
|
293
|
+
self._clear_presentation_cache()
|
|
294
|
+
self._render_active_view()
|
|
295
|
+
|
|
296
|
+
def show_session(self, session_id: str, path: Path | None) -> None:
|
|
297
|
+
changed = session_id != self._session_id or path != self._path
|
|
298
|
+
self.foreground = True
|
|
299
|
+
self.display = True
|
|
300
|
+
self._session_id = session_id
|
|
301
|
+
self._path = path
|
|
302
|
+
self._update_border_labels()
|
|
303
|
+
if changed:
|
|
304
|
+
self._session_json_loaded = False
|
|
305
|
+
if changed or self._analysis is None:
|
|
306
|
+
self._release_analysis()
|
|
307
|
+
self._start_load()
|
|
308
|
+
self._sync_live_timer()
|
|
309
|
+
self._sync_content_visibility()
|
|
310
|
+
self.query_one("#trajectory-tabs", Tabs).focus()
|
|
311
|
+
|
|
312
|
+
def set_verify_commands(self, value: str) -> None:
|
|
313
|
+
"""Apply the live classification word list and rebuild the current projection."""
|
|
314
|
+
if value == self._verify_commands:
|
|
315
|
+
return
|
|
316
|
+
self._verify_commands = value
|
|
317
|
+
if self.foreground and self._path is not None:
|
|
318
|
+
self._release_analysis()
|
|
319
|
+
self._start_load()
|
|
320
|
+
|
|
321
|
+
def hide_dashboard(self) -> None:
|
|
322
|
+
self.foreground = False
|
|
323
|
+
self.display = False
|
|
324
|
+
self._sync_live_timer()
|
|
325
|
+
self._sync_content_visibility()
|
|
326
|
+
self._release_analysis()
|
|
327
|
+
|
|
328
|
+
def select_turn(self, turn_id: str) -> None:
|
|
329
|
+
turn = self._analysis.turn(turn_id) if self._analysis is not None else None
|
|
330
|
+
self._selected_turn_id = turn.turn_id if turn is not None else turn_id
|
|
331
|
+
self._timeline_dependencies = False
|
|
332
|
+
self.active_tab = DashboardTab.TIMELINE
|
|
333
|
+
self.query_one("#trajectory-tabs", Tabs).active = DashboardTab.TIMELINE
|
|
334
|
+
self._update_border_labels()
|
|
335
|
+
self._sync_live_timer()
|
|
336
|
+
self._sync_content_visibility()
|
|
337
|
+
self._render_active_view()
|
|
338
|
+
self.post_message(self.StateChanged())
|
|
339
|
+
|
|
340
|
+
def suspend_for_shell_mode(self) -> bool:
|
|
341
|
+
self._shell_restore_foreground = self.foreground
|
|
342
|
+
restore_json = self.session_json_visible
|
|
343
|
+
self.foreground = False
|
|
344
|
+
self.display = False
|
|
345
|
+
self._sync_live_timer()
|
|
346
|
+
self._release_analysis()
|
|
347
|
+
return restore_json
|
|
348
|
+
|
|
349
|
+
def finish_shell_mode(self) -> bool:
|
|
350
|
+
restore = self._shell_restore_foreground
|
|
351
|
+
self._shell_restore_foreground = False
|
|
352
|
+
if restore:
|
|
353
|
+
self.show_session(self._session_id, self._path)
|
|
354
|
+
return restore
|
|
355
|
+
|
|
356
|
+
def gc_freeze_block_reason(self) -> GcFreezeBlockReason | None:
|
|
357
|
+
if self.foreground and not self.session_json_visible:
|
|
358
|
+
return GcFreezeBlockReason.TRAJECTORY_DASHBOARD_VISIBLE
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
def prepare_for_gc_freeze(self) -> None:
|
|
362
|
+
if self.foreground and not self.session_json_visible:
|
|
363
|
+
return
|
|
364
|
+
self.query_one(TrajectoryTextView).detach_cache()
|
|
365
|
+
self._presentation_cache = detach_lru_cache(self._presentation_cache)
|
|
366
|
+
|
|
367
|
+
def after_gc_freeze(self) -> None:
|
|
368
|
+
self.query_one(TrajectoryTextView).renew_cache()
|
|
369
|
+
self._presentation_cache = renew_lru_cache(self._presentation_cache)
|
|
370
|
+
|
|
371
|
+
def abort_gc_freeze(self) -> None:
|
|
372
|
+
self.after_gc_freeze()
|
|
373
|
+
|
|
374
|
+
@on(Tabs.TabActivated, "#trajectory-tabs")
|
|
375
|
+
def _on_tab_activated(self, event: Tabs.TabActivated) -> None:
|
|
376
|
+
if event.tab.id is None:
|
|
377
|
+
return
|
|
378
|
+
self.active_tab = DashboardTab(event.tab.id)
|
|
379
|
+
self._update_border_labels()
|
|
380
|
+
self._sync_live_timer()
|
|
381
|
+
self._sync_content_visibility()
|
|
382
|
+
self._render_active_view()
|
|
383
|
+
if self.foreground:
|
|
384
|
+
if self.session_json_visible:
|
|
385
|
+
self.query_one(SessionJsonPanel).focus()
|
|
386
|
+
else:
|
|
387
|
+
self.query_one(TrajectoryTextView).focus()
|
|
388
|
+
self.post_message(self.StateChanged())
|
|
389
|
+
|
|
390
|
+
@on(Tabs.TabActivated, "#timeline-turn-tabs")
|
|
391
|
+
def _on_turn_tab_activated(self, event: Tabs.TabActivated) -> None:
|
|
392
|
+
event.stop()
|
|
393
|
+
if event.tabs is not self._query_turn_tabs() or event.tab.id is None:
|
|
394
|
+
return
|
|
395
|
+
index = int(event.tab.id.removeprefix("turn-"))
|
|
396
|
+
if index >= len(self._turn_tab_key):
|
|
397
|
+
return
|
|
398
|
+
turn_id = self._turn_tab_key[index][0]
|
|
399
|
+
if turn_id == self._selected_turn_id:
|
|
400
|
+
return
|
|
401
|
+
self._selected_turn_id = turn_id
|
|
402
|
+
self._render_active_view()
|
|
403
|
+
|
|
404
|
+
def action_toggle_timeline_dependencies(self) -> None:
|
|
405
|
+
"""Flip the timeline tab between the time axis and the dependency graph."""
|
|
406
|
+
if not self.foreground or self.active_tab is not DashboardTab.TIMELINE or self.session_json_visible:
|
|
407
|
+
return
|
|
408
|
+
self._timeline_dependencies = not self._timeline_dependencies
|
|
409
|
+
self._render_active_view()
|
|
410
|
+
|
|
411
|
+
@on(SessionJsonPanel.LoadStateChanged)
|
|
412
|
+
def _on_session_json_load_state_changed(self, event: SessionJsonPanel.LoadStateChanged) -> None:
|
|
413
|
+
event.stop()
|
|
414
|
+
self._sync_content_visibility()
|
|
415
|
+
|
|
416
|
+
def _sync_content_visibility(self) -> None:
|
|
417
|
+
if not self.is_mounted:
|
|
418
|
+
return
|
|
419
|
+
session_json = self.query_one(SessionJsonPanel)
|
|
420
|
+
text_view = self.query_one(TrajectoryTextView)
|
|
421
|
+
show_json = self.session_json_visible
|
|
422
|
+
if show_json:
|
|
423
|
+
session_json.display = True
|
|
424
|
+
if not self._session_json_loaded:
|
|
425
|
+
if self._session_id:
|
|
426
|
+
session_json.load_session(self._session_id)
|
|
427
|
+
else:
|
|
428
|
+
session_json.set_status(self._render_message(_NO_ACTIVE_SESSION.bind()))
|
|
429
|
+
self._session_json_loaded = True
|
|
430
|
+
else:
|
|
431
|
+
session_json.hide_session_json()
|
|
432
|
+
self._session_json_loaded = False
|
|
433
|
+
# One indicator for both slow paths: the analysis scan behind the
|
|
434
|
+
# hidden text view, and the Session Data viewer's background load
|
|
435
|
+
# (which must stay displayed so its worker can commit).
|
|
436
|
+
show_loading = self.foreground and (session_json.is_loading if show_json else self._load_pending)
|
|
437
|
+
text_view.set_class(self.active_tab is DashboardTab.OVERVIEW, "-overview")
|
|
438
|
+
text_view.set_class(self.active_tab is DashboardTab.TIMELINE, "-timeline")
|
|
439
|
+
text_view.display = self.foreground and not show_json and not show_loading
|
|
440
|
+
self.query_one("#trajectory-loading-state").display = show_loading
|
|
441
|
+
turn_tabs = self._query_turn_tabs()
|
|
442
|
+
if turn_tabs is not None:
|
|
443
|
+
turn_tabs.display = self._turn_tabs_visible()
|
|
444
|
+
|
|
445
|
+
def _turn_tabs_visible(self) -> bool:
|
|
446
|
+
return (
|
|
447
|
+
self.foreground
|
|
448
|
+
and not self.session_json_visible
|
|
449
|
+
and bool(self._turn_tab_key)
|
|
450
|
+
and self.active_tab is DashboardTab.TIMELINE
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
def _sync_turn_tabs(self, analysis: TrajectoryAnalysis | None) -> None:
|
|
454
|
+
turns = (
|
|
455
|
+
analysis.turns if analysis is not None and analysis.availability is AnalysisAvailability.AVAILABLE else ()
|
|
456
|
+
)
|
|
457
|
+
key = tuple(
|
|
458
|
+
(turn.turn_id, self._render_message(TURN_LABEL.bind(turn=turn.turn_number or "—"))) for turn in turns
|
|
459
|
+
)
|
|
460
|
+
if key != self._turn_tab_key:
|
|
461
|
+
self._turn_tab_key = key
|
|
462
|
+
# Rebuild by replacement: remove-then-mount runs atomically inside
|
|
463
|
+
# one callback, and building at execution time keeps the active tab
|
|
464
|
+
# aligned with whatever the selection is once the callback runs.
|
|
465
|
+
self.call_next(self._replace_turn_tabs)
|
|
466
|
+
return
|
|
467
|
+
tabs = self._query_turn_tabs()
|
|
468
|
+
if tabs is None:
|
|
469
|
+
return
|
|
470
|
+
active = self._active_turn_tab_id()
|
|
471
|
+
if active is not None and tabs.active != active and tabs.query(f"#{active}"):
|
|
472
|
+
tabs.active = active
|
|
473
|
+
tabs.display = self._turn_tabs_visible()
|
|
474
|
+
|
|
475
|
+
def _query_turn_tabs(self) -> Tabs | None:
|
|
476
|
+
# A pending replacement leaves a gap between remove and mount; callers
|
|
477
|
+
# skip the sync then because the mount applies the fresh state itself.
|
|
478
|
+
try:
|
|
479
|
+
return self.query_one("#timeline-turn-tabs", Tabs)
|
|
480
|
+
except NoMatches:
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
def _active_turn_tab_id(self) -> str | None:
|
|
484
|
+
return next(
|
|
485
|
+
(
|
|
486
|
+
f"turn-{index}"
|
|
487
|
+
for index, (turn_id, _) in enumerate(self._turn_tab_key)
|
|
488
|
+
if turn_id == self._selected_turn_id
|
|
489
|
+
),
|
|
490
|
+
None,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
async def _replace_turn_tabs(self) -> None:
|
|
494
|
+
old = self.query_one("#timeline-turn-tabs", Tabs)
|
|
495
|
+
anchor = self.query_one("#trajectory-tabs", Tabs)
|
|
496
|
+
replacement = Tabs(
|
|
497
|
+
*(Tab(Text(label), id=f"turn-{index}") for index, (_, label) in enumerate(self._turn_tab_key)),
|
|
498
|
+
active=self._active_turn_tab_id(),
|
|
499
|
+
id="timeline-turn-tabs",
|
|
500
|
+
)
|
|
501
|
+
await old.remove()
|
|
502
|
+
replacement.display = self._turn_tabs_visible()
|
|
503
|
+
await self.mount(replacement, after=anchor)
|
|
504
|
+
|
|
505
|
+
def _update_border_labels(self) -> None:
|
|
506
|
+
self.border_title = Text(self._render_message(_DASHBOARD_TITLE.bind()))
|
|
507
|
+
if not self._session_id:
|
|
508
|
+
self.border_subtitle = Text("")
|
|
509
|
+
return
|
|
510
|
+
if self.active_tab is DashboardTab.SESSION_DATA and self._path is not None:
|
|
511
|
+
self.border_subtitle = Text(surrogate_safe_text(str(self._path.parents[1] / "session.json")))
|
|
512
|
+
return
|
|
513
|
+
subtitle = self._precision_legend()
|
|
514
|
+
subtitle.append(" · ")
|
|
515
|
+
subtitle.append(session_short_id(self._session_id))
|
|
516
|
+
self.border_subtitle = subtitle
|
|
517
|
+
|
|
518
|
+
def _sync_live_timer(self) -> None:
|
|
519
|
+
timer = self._live_timer
|
|
520
|
+
if timer is None:
|
|
521
|
+
return
|
|
522
|
+
if self.foreground and self.active_tab in {DashboardTab.OVERVIEW, DashboardTab.TIMELINE}:
|
|
523
|
+
timer.resume()
|
|
524
|
+
else:
|
|
525
|
+
timer.pause()
|
|
526
|
+
|
|
527
|
+
def _start_load(self) -> None:
|
|
528
|
+
self._cancel_worker()
|
|
529
|
+
self._load_generation += 1
|
|
530
|
+
generation = self._load_generation
|
|
531
|
+
analyzer = TrajectoryAnalyzer(verify_commands=self._verify_commands)
|
|
532
|
+
path = self._path
|
|
533
|
+
cancel_event = Event()
|
|
534
|
+
self._analyzer = analyzer
|
|
535
|
+
self._scan_cancel_event = cancel_event
|
|
536
|
+
self.query_one(TrajectoryTextView).clear_lines()
|
|
537
|
+
# The text view stays hidden behind the loading indicator until the
|
|
538
|
+
# first analysis lands; a long scan must not read as "no data".
|
|
539
|
+
self._load_pending = True
|
|
540
|
+
self._worker = self.run_worker(
|
|
541
|
+
partial(self._load, generation, analyzer, path, cancel_event),
|
|
542
|
+
group="trajectory-analysis",
|
|
543
|
+
exclusive=True,
|
|
544
|
+
)
|
|
545
|
+
self._sync_turn_tabs(None)
|
|
546
|
+
self._sync_content_visibility()
|
|
547
|
+
|
|
548
|
+
async def _load(
|
|
549
|
+
self,
|
|
550
|
+
generation: int,
|
|
551
|
+
analyzer: TrajectoryAnalyzer,
|
|
552
|
+
path: Path | None,
|
|
553
|
+
cancel_event: Event,
|
|
554
|
+
) -> None:
|
|
555
|
+
storage: SessionStorage | None = None
|
|
556
|
+
if path is None:
|
|
557
|
+
analysis = None
|
|
558
|
+
else:
|
|
559
|
+
try:
|
|
560
|
+
analysis, storage = await asyncio.to_thread(
|
|
561
|
+
partial(_load_with_storage, analyzer, path, cancel_event=cancel_event)
|
|
562
|
+
)
|
|
563
|
+
except TrajectoryScanCancelled:
|
|
564
|
+
return
|
|
565
|
+
if generation != self._load_generation or analyzer is not self._analyzer or not self.foreground:
|
|
566
|
+
return
|
|
567
|
+
self._analysis = analysis
|
|
568
|
+
self._session_storage = storage
|
|
569
|
+
if storage is not None:
|
|
570
|
+
self._storage_collected_at = monotonic()
|
|
571
|
+
self._load_pending = False
|
|
572
|
+
self._presentation_revision += 1
|
|
573
|
+
self._sync_content_visibility()
|
|
574
|
+
self._render_active_view()
|
|
575
|
+
# The display flip above lands in the next layout pass, so the text
|
|
576
|
+
# view's region is still the placeholder's here; render again once the
|
|
577
|
+
# view settles or the first frame keeps the stale measurements.
|
|
578
|
+
self.call_after_refresh(self._render_for_settled_view)
|
|
579
|
+
|
|
580
|
+
async def _refresh_live(self) -> None:
|
|
581
|
+
analyzer = self._analyzer
|
|
582
|
+
worker = self._worker
|
|
583
|
+
if (
|
|
584
|
+
not self.foreground
|
|
585
|
+
or analyzer is None
|
|
586
|
+
or self._analysis is None
|
|
587
|
+
or (worker is not None and worker.is_running)
|
|
588
|
+
):
|
|
589
|
+
return
|
|
590
|
+
self._load_generation += 1
|
|
591
|
+
generation = self._load_generation
|
|
592
|
+
cancel_event = Event()
|
|
593
|
+
self._scan_cancel_event = cancel_event
|
|
594
|
+
self._worker = self.run_worker(
|
|
595
|
+
partial(self._refresh, generation, analyzer, cancel_event),
|
|
596
|
+
group="trajectory-analysis",
|
|
597
|
+
exclusive=True,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
async def _refresh(self, generation: int, analyzer: TrajectoryAnalyzer, cancel_event: Event) -> None:
|
|
601
|
+
# Storage lives outside the events log, so a quiet log must not pin it
|
|
602
|
+
# forever: recollect on a coarse clock even when the analysis is unchanged.
|
|
603
|
+
collect_storage = monotonic() - self._storage_collected_at >= _STORAGE_REFRESH_INTERVAL_S
|
|
604
|
+
try:
|
|
605
|
+
analysis, storage = await asyncio.to_thread(
|
|
606
|
+
partial(
|
|
607
|
+
_refresh_with_storage,
|
|
608
|
+
analyzer,
|
|
609
|
+
collect_storage=collect_storage,
|
|
610
|
+
cancel_event=cancel_event,
|
|
611
|
+
)
|
|
612
|
+
)
|
|
613
|
+
except TrajectoryScanCancelled:
|
|
614
|
+
return
|
|
615
|
+
if generation != self._load_generation or analyzer is not self._analyzer or not self.foreground:
|
|
616
|
+
return
|
|
617
|
+
changed = analysis is not self._analysis
|
|
618
|
+
if storage is not None:
|
|
619
|
+
self._storage_collected_at = monotonic()
|
|
620
|
+
if storage != self._session_storage:
|
|
621
|
+
self._session_storage = storage
|
|
622
|
+
changed = True
|
|
623
|
+
if not changed:
|
|
624
|
+
return
|
|
625
|
+
self._analysis = analysis
|
|
626
|
+
self._presentation_revision += 1
|
|
627
|
+
self._render_active_view()
|
|
628
|
+
|
|
629
|
+
def _cancel_worker(self) -> None:
|
|
630
|
+
self._load_generation += 1
|
|
631
|
+
self._load_pending = False
|
|
632
|
+
cancel_event = self._scan_cancel_event
|
|
633
|
+
if cancel_event is not None:
|
|
634
|
+
cancel_event.set()
|
|
635
|
+
self._scan_cancel_event = None
|
|
636
|
+
worker = self._worker
|
|
637
|
+
if worker is not None:
|
|
638
|
+
worker.cancel()
|
|
639
|
+
self._worker = None
|
|
640
|
+
|
|
641
|
+
def _release_analysis(self) -> None:
|
|
642
|
+
self._cancel_worker()
|
|
643
|
+
analyzer = self._analyzer
|
|
644
|
+
if analyzer is not None:
|
|
645
|
+
analyzer.release()
|
|
646
|
+
self._analyzer = None
|
|
647
|
+
self._analysis = None
|
|
648
|
+
self._session_storage = None
|
|
649
|
+
self._storage_collected_at = -_STORAGE_REFRESH_INTERVAL_S
|
|
650
|
+
self._presentation_key = None
|
|
651
|
+
self._render_identity = None
|
|
652
|
+
self._clear_presentation_cache()
|
|
653
|
+
if self.is_mounted:
|
|
654
|
+
self.query_one(TrajectoryTextView).release()
|
|
655
|
+
|
|
656
|
+
def _clear_presentation_cache(self) -> None:
|
|
657
|
+
if not isinstance(self._presentation_cache, DetachedLruCache):
|
|
658
|
+
self._presentation_cache.clear()
|
|
659
|
+
|
|
660
|
+
def _render_active_view(self) -> None:
|
|
661
|
+
if not self.is_mounted or self.session_json_visible or self._load_pending:
|
|
662
|
+
return
|
|
663
|
+
analysis = self._analysis
|
|
664
|
+
generation = analysis.generation if analysis is not None else -1
|
|
665
|
+
width = self._available_width()
|
|
666
|
+
height = self._available_height()
|
|
667
|
+
self._presentation_key = (generation, width, height)
|
|
668
|
+
turn = self._resolve_selected_turn(analysis) if self.active_tab is DashboardTab.TIMELINE else None
|
|
669
|
+
selected = self._selected_turn_id if self.active_tab is DashboardTab.TIMELINE else None
|
|
670
|
+
# The empty state and the hatch fill below short content are built for
|
|
671
|
+
# the text view's own region, which settles a beat after this widget
|
|
672
|
+
# resizes; the region (and the scrollbar state feeding the settle math)
|
|
673
|
+
# must key the cache so a build for a transitional layout is missed --
|
|
674
|
+
# not hit -- by the re-render that follows the settled view.
|
|
675
|
+
text_view = self.query_one(TrajectoryTextView)
|
|
676
|
+
region = text_view.scrollable_content_region
|
|
677
|
+
cache_key = (
|
|
678
|
+
self.active_tab,
|
|
679
|
+
self._timeline_dependencies,
|
|
680
|
+
selected,
|
|
681
|
+
generation,
|
|
682
|
+
width,
|
|
683
|
+
height,
|
|
684
|
+
region.width,
|
|
685
|
+
region.height,
|
|
686
|
+
text_view.show_vertical_scrollbar,
|
|
687
|
+
text_view.show_horizontal_scrollbar,
|
|
688
|
+
self._verify_commands,
|
|
689
|
+
self._presentation_revision,
|
|
690
|
+
)
|
|
691
|
+
cache = None if isinstance(self._presentation_cache, DetachedLruCache) else self._presentation_cache
|
|
692
|
+
if analysis is None or analysis.availability is not AnalysisAvailability.AVAILABLE or not analysis.turns:
|
|
693
|
+
cache = None
|
|
694
|
+
if not region.width or not region.height:
|
|
695
|
+
cache = None
|
|
696
|
+
cached = cache.get(cache_key) if cache is not None else None
|
|
697
|
+
if cached is not None:
|
|
698
|
+
self._commit_lines(list(cached))
|
|
699
|
+
else:
|
|
700
|
+
lines = self._settled_lines(partial(self._active_lines, analysis, turn))
|
|
701
|
+
if cache is not None:
|
|
702
|
+
cache[cache_key] = tuple(lines)
|
|
703
|
+
self._commit_lines(lines)
|
|
704
|
+
if self.active_tab is DashboardTab.TIMELINE:
|
|
705
|
+
self._sync_turn_tabs(analysis)
|
|
706
|
+
|
|
707
|
+
def _resolve_selected_turn(self, analysis: TrajectoryAnalysis | None) -> TurnAnalysis | None:
|
|
708
|
+
"""Settle which turn the Timeline shows before anything keys on it.
|
|
709
|
+
|
|
710
|
+
A selection the analysis still has stays; otherwise (a first visit, or
|
|
711
|
+
the turn is gone) the newest turn is picked. The pick is written back,
|
|
712
|
+
so turns a live session adds later don't move the reader off it.
|
|
713
|
+
"""
|
|
714
|
+
if analysis is None or analysis.availability is not AnalysisAvailability.AVAILABLE or not analysis.turns:
|
|
715
|
+
return None
|
|
716
|
+
turn = analysis.turn(self._selected_turn_id) if self._selected_turn_id is not None else None
|
|
717
|
+
turn = turn or analysis.turns[-1]
|
|
718
|
+
self._selected_turn_id = turn.turn_id
|
|
719
|
+
return turn
|
|
720
|
+
|
|
721
|
+
def _active_lines(
|
|
722
|
+
self,
|
|
723
|
+
analysis: TrajectoryAnalysis | None,
|
|
724
|
+
turn: TurnAnalysis | None,
|
|
725
|
+
context: RenderContext,
|
|
726
|
+
height: int,
|
|
727
|
+
) -> list[Text]:
|
|
728
|
+
"""The active tab's lines for *context*; *height* only sizes the empty state."""
|
|
729
|
+
if analysis is None or analysis.availability is AnalysisAvailability.UNAVAILABLE:
|
|
730
|
+
return self._empty_state_lines(UNAVAILABLE, width=context.width, height=height)
|
|
731
|
+
if analysis.availability is AnalysisAvailability.READ_ERROR:
|
|
732
|
+
return [Text(self._render_message(_READ_ERROR.bind(error=analysis.read_error or "")))]
|
|
733
|
+
if self.active_tab is DashboardTab.OVERVIEW:
|
|
734
|
+
if not analysis.turns:
|
|
735
|
+
return self._empty_state_lines(NO_TURNS, width=context.width, height=height)
|
|
736
|
+
return overview_lines(
|
|
737
|
+
self._look(),
|
|
738
|
+
context,
|
|
739
|
+
analysis,
|
|
740
|
+
folder=self._displayed_folder(),
|
|
741
|
+
storage=self._session_storage,
|
|
742
|
+
can_open_folder=self._can_open_session_folder,
|
|
743
|
+
)
|
|
744
|
+
if self.active_tab is DashboardTab.INSIGHTS:
|
|
745
|
+
# Diagnostics alone (a log of only corrupt lines) still make a page.
|
|
746
|
+
if not analysis.turns and not has_diagnostic_content(analysis.diagnostics):
|
|
747
|
+
return self._empty_state_lines(NO_TURNS, width=context.width, height=height)
|
|
748
|
+
return insights_lines(self._look(), context, analysis)
|
|
749
|
+
if self.active_tab is DashboardTab.TIMELINE:
|
|
750
|
+
# The selection is resolved before rendering: no turn means the
|
|
751
|
+
# session has none yet.
|
|
752
|
+
if turn is None:
|
|
753
|
+
return self._empty_state_lines(NO_TURNS, width=context.width, height=height)
|
|
754
|
+
if self._timeline_dependencies:
|
|
755
|
+
return dependency_graph_lines(self._look(), turn)
|
|
756
|
+
return timeline_lines(self._look(), context, turn)
|
|
757
|
+
return []
|
|
758
|
+
|
|
759
|
+
def _settled_lines(self, render: Callable[[RenderContext, int], list[Text]]) -> list[Text]:
|
|
760
|
+
"""Build for the region the asynchronous scrollbars settle on.
|
|
761
|
+
|
|
762
|
+
The scrollbars this commit provokes (or retires) resize the region one
|
|
763
|
+
cell after the fact; building for the settled region keeps every right
|
|
764
|
+
edge real instead of cropping it off. *render* takes the layout and the
|
|
765
|
+
height the empty state fills.
|
|
766
|
+
"""
|
|
767
|
+
context = self._render_context()
|
|
768
|
+
lines = render(context, self._content_height())
|
|
769
|
+
text_view = self.query_one(TrajectoryTextView)
|
|
770
|
+
region = text_view.scrollable_content_region
|
|
771
|
+
if not region.width or not region.height:
|
|
772
|
+
return lines
|
|
773
|
+
region_width = self._effective_region_width()
|
|
774
|
+
available = self._available_width()
|
|
775
|
+
base_width = region_width + (1 if text_view.show_vertical_scrollbar else 0)
|
|
776
|
+
if available:
|
|
777
|
+
base_width = min(base_width, available)
|
|
778
|
+
base_height = region.height + (1 if text_view.show_horizontal_scrollbar else 0)
|
|
779
|
+
overflows_y = len(lines) > base_height
|
|
780
|
+
settled_width = base_width - (1 if overflows_y else 0)
|
|
781
|
+
overflows_x = any(line.cell_len > settled_width for line in lines)
|
|
782
|
+
settled_height = base_height - (1 if overflows_x else 0)
|
|
783
|
+
if settled_width != region_width or settled_height != region.height:
|
|
784
|
+
lines = render(replace(context, width=max(1, settled_width)), max(1, settled_height))
|
|
785
|
+
return self._hatch_filled(lines, width=settled_width, height=settled_height)
|
|
786
|
+
|
|
787
|
+
def _hatch_filled(self, lines: list[Text], *, width: int, height: int) -> list[Text]:
|
|
788
|
+
"""Fill the viewport rows below short content with the hatch pattern."""
|
|
789
|
+
if len(lines) >= height:
|
|
790
|
+
return lines
|
|
791
|
+
hatch_style = self._hatch_style()
|
|
792
|
+
label_style = self._muted_label_style()
|
|
793
|
+
return [
|
|
794
|
+
*lines,
|
|
795
|
+
*(
|
|
796
|
+
hatched_text_line(width, hatch_style=hatch_style, label_style=label_style)
|
|
797
|
+
for _ in range(height - len(lines))
|
|
798
|
+
),
|
|
799
|
+
]
|
|
800
|
+
|
|
801
|
+
def _effective_region_width(self) -> int:
|
|
802
|
+
"""The text view's region width, bounded by the dashboard's own size.
|
|
803
|
+
|
|
804
|
+
The view's region lags a display flip by one layout pass, so right
|
|
805
|
+
after a load it can still report the width of a previous layout; the
|
|
806
|
+
dashboard's content size is current and caps it.
|
|
807
|
+
"""
|
|
808
|
+
width = self.query_one(TrajectoryTextView).scrollable_content_region.width
|
|
809
|
+
available = self._available_width()
|
|
810
|
+
return min(width, available) if width and available else width
|
|
811
|
+
|
|
812
|
+
def _content_width(self) -> int:
|
|
813
|
+
"""The width pages lay out in; the dashboard's own before the view has a region."""
|
|
814
|
+
width = self._effective_region_width()
|
|
815
|
+
return max(1, width) if width else max(1, self._available_width())
|
|
816
|
+
|
|
817
|
+
def _content_height(self) -> int:
|
|
818
|
+
"""The height the empty state fills; the dashboard's own before the view has a region."""
|
|
819
|
+
height = self.query_one(TrajectoryTextView).scrollable_content_region.height
|
|
820
|
+
return max(1, height) if height else self._available_height()
|
|
821
|
+
|
|
822
|
+
def _render_context(self) -> RenderContext:
|
|
823
|
+
"""The layout a page renders for before the scrollbars settle."""
|
|
824
|
+
return RenderContext(width=self._content_width(), tier=self.responsive_tier)
|
|
825
|
+
|
|
826
|
+
def _empty_state_lines(self, message: MessageDef, *, width: int, height: int) -> list[Text]:
|
|
827
|
+
hatch_style = self._hatch_style()
|
|
828
|
+
label_style = self._muted_label_style()
|
|
829
|
+
label = self._render_message(message.bind())
|
|
830
|
+
return [
|
|
831
|
+
hatched_text_line(
|
|
832
|
+
width, label if y == height // 2 else None, hatch_style=hatch_style, label_style=label_style
|
|
833
|
+
)
|
|
834
|
+
for y in range(height)
|
|
835
|
+
]
|
|
836
|
+
|
|
837
|
+
def _commit_lines(self, lines: list[Text]) -> None:
|
|
838
|
+
# The identity deliberately excludes the analysis generation: a live
|
|
839
|
+
# session bumps it on every appended event, and a refresh of the view
|
|
840
|
+
# the user is already reading must keep their scroll position. Session
|
|
841
|
+
# switches reset the identity through ``_release_analysis``.
|
|
842
|
+
identity = (
|
|
843
|
+
self.active_tab,
|
|
844
|
+
self._selected_turn_id if self.active_tab is DashboardTab.TIMELINE else None,
|
|
845
|
+
)
|
|
846
|
+
reset_scroll = identity != self._render_identity
|
|
847
|
+
self._render_identity = identity
|
|
848
|
+
self.query_one(TrajectoryTextView).set_lines(lines, reset_scroll=reset_scroll)
|
|
849
|
+
|
|
850
|
+
def _displayed_folder(self) -> Path | None:
|
|
851
|
+
"""The folder the session info section names: the session directory when
|
|
852
|
+
the events log sits in the store layout, otherwise the log's own parent."""
|
|
853
|
+
path = self._path
|
|
854
|
+
if path is None:
|
|
855
|
+
return None
|
|
856
|
+
session_dir = _session_directory(path)
|
|
857
|
+
return session_dir if session_dir is not None else path.parent
|
|
858
|
+
|
|
859
|
+
def open_session_folder(self) -> None:
|
|
860
|
+
"""Reveal the displayed session folder in the desktop file manager."""
|
|
861
|
+
folder = self._displayed_folder()
|
|
862
|
+
if folder is None:
|
|
863
|
+
return
|
|
864
|
+
if not can_open_in_file_manager():
|
|
865
|
+
self.notify(self._render_message(_SESSION_INFO_OPEN_UNAVAILABLE.bind()), severity="warning", markup=False)
|
|
866
|
+
return
|
|
867
|
+
try:
|
|
868
|
+
open_in_file_manager(folder)
|
|
869
|
+
except OSError as error:
|
|
870
|
+
# The OS error text is data, not markup: "[Errno 2] ..." would
|
|
871
|
+
# otherwise be parsed as a content tag.
|
|
872
|
+
self.notify(
|
|
873
|
+
self._render_message(_SESSION_INFO_OPEN_FAILED.bind(error=surrogate_safe_text(str(error)))),
|
|
874
|
+
severity="error",
|
|
875
|
+
markup=False,
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
def copy_session_path(self) -> None:
|
|
879
|
+
"""Copy the displayed session folder path to the available clipboards."""
|
|
880
|
+
folder = self._displayed_folder()
|
|
881
|
+
if folder is None:
|
|
882
|
+
return
|
|
883
|
+
copy_text_to_clipboards(self.app, str(folder))
|
|
884
|
+
self.notify(
|
|
885
|
+
self._render_message(_SESSION_INFO_PATH_COPIED.bind()),
|
|
886
|
+
title=self._render_message(COPIED_TITLE.bind()),
|
|
887
|
+
timeout=1,
|
|
888
|
+
markup=False,
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
def _precision_legend(self) -> Text:
|
|
892
|
+
# Only the glyphs carry semantic colour; the labels inherit the border
|
|
893
|
+
# subtitle's own colour so they read like the session id beside them.
|
|
894
|
+
look = self._look()
|
|
895
|
+
legend = Text()
|
|
896
|
+
for index, precision in enumerate(PRECISION_SYMBOLS):
|
|
897
|
+
if index:
|
|
898
|
+
legend.append(" ")
|
|
899
|
+
legend.append(PRECISION_SYMBOLS[precision], precision_style(look, precision))
|
|
900
|
+
legend.append(" ")
|
|
901
|
+
legend.append(precision_label(look, precision))
|
|
902
|
+
return legend
|
|
903
|
+
|
|
904
|
+
def _hatch_style(self) -> Style:
|
|
905
|
+
return hatch_text_style(self)
|
|
906
|
+
|
|
907
|
+
def _muted_label_style(self) -> Style:
|
|
908
|
+
# $text-muted is a composite value on every theme ("auto 60%",
|
|
909
|
+
# "ansi_white 40%"), which a plain color parse silently drops; only
|
|
910
|
+
# the stylesheet can resolve the alpha blend. Strip the stamped
|
|
911
|
+
# background so the label stays transparent over the hatch.
|
|
912
|
+
full = self.get_component_rich_style("trajectory-dashboard--muted-label")
|
|
913
|
+
return full.without_color + Style.from_color(full.color)
|
|
914
|
+
|
|
915
|
+
def _available_width(self) -> int:
|
|
916
|
+
return self._available_size.width or self.size.width
|
|
917
|
+
|
|
918
|
+
def _available_height(self) -> int:
|
|
919
|
+
height = self._available_size.height or self.size.height
|
|
920
|
+
return max(1, height - _TABS_HEIGHT)
|
|
921
|
+
|
|
922
|
+
def _look(self) -> DashboardLook:
|
|
923
|
+
return DashboardLook(
|
|
924
|
+
console=self.app.console,
|
|
925
|
+
theme_variables=self.app.theme_variables,
|
|
926
|
+
localizer=self._localizer(),
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
def _localizer(self) -> Localizer | None:
|
|
930
|
+
return None if self._locale_controller is None else self._locale_controller.localizer
|
|
931
|
+
|
|
932
|
+
def _render_message(self, reference: MessageRef) -> str:
|
|
933
|
+
# Takes only the localizer, never _look(): the constructor sets the
|
|
934
|
+
# border title before any App runs.
|
|
935
|
+
return render_message(self._localizer(), reference)
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def _session_directory(path: Path) -> Path | None:
|
|
939
|
+
"""The session folder owning *path*, only when it sits in the store layout.
|
|
940
|
+
|
|
941
|
+
Anything else (a loose events file under a scratch directory) has no
|
|
942
|
+
session folder to size or open; walking its parent would measure an
|
|
943
|
+
unrelated tree.
|
|
944
|
+
"""
|
|
945
|
+
if (
|
|
946
|
+
len(path.parents) < 3
|
|
947
|
+
or path.name != "events.jsonl"
|
|
948
|
+
or path.parent.name != "trajectory"
|
|
949
|
+
or path.parents[2].name != "sessions"
|
|
950
|
+
):
|
|
951
|
+
return None
|
|
952
|
+
return path.parents[1]
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
def _load_with_storage(
|
|
956
|
+
analyzer: TrajectoryAnalyzer,
|
|
957
|
+
path: Path,
|
|
958
|
+
*,
|
|
959
|
+
cancel_event: Event,
|
|
960
|
+
) -> tuple[TrajectoryAnalysis, SessionStorage | None]:
|
|
961
|
+
analysis = analyzer.load(path, cancel_event=cancel_event)
|
|
962
|
+
session_dir = _session_directory(path)
|
|
963
|
+
if session_dir is None:
|
|
964
|
+
return analysis, None
|
|
965
|
+
return analysis, collect_session_storage(session_dir, cancel_event=cancel_event)
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
def _refresh_with_storage(
|
|
969
|
+
analyzer: TrajectoryAnalyzer,
|
|
970
|
+
*,
|
|
971
|
+
collect_storage: bool,
|
|
972
|
+
cancel_event: Event,
|
|
973
|
+
) -> tuple[TrajectoryAnalysis, SessionStorage | None]:
|
|
974
|
+
analysis = analyzer.refresh(cancel_event=cancel_event)
|
|
975
|
+
if not collect_storage:
|
|
976
|
+
# Storage recollection stays on the caller's coarse clock even while
|
|
977
|
+
# the log is appending, or a busy session walks the directory tree on
|
|
978
|
+
# every poll tick; the panel keeps the previous figures meanwhile.
|
|
979
|
+
return analysis, None
|
|
980
|
+
session_dir = _session_directory(analysis.path)
|
|
981
|
+
if session_dir is None:
|
|
982
|
+
return analysis, None
|
|
983
|
+
return analysis, collect_session_storage(session_dir, cancel_event=cancel_event)
|