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,925 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Renderers for ``edit_file`` and ``write_file`` tools.
|
|
4
|
+
|
|
5
|
+
Before/after file content is provided by the mutation tracking system
|
|
6
|
+
via ``file_snapshot``. Large snapshots may be disk-backed refs and
|
|
7
|
+
are resolved only when the inline diff/copy/detail view needs them.
|
|
8
|
+
|
|
9
|
+
edit_file: split DiffView (side-by-side before/after).
|
|
10
|
+
write_file: unified DiffView (new file content).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
from contextlib import suppress
|
|
20
|
+
from difflib import unified_diff
|
|
21
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
22
|
+
|
|
23
|
+
from rich.text import Text
|
|
24
|
+
from textual.timer import Timer
|
|
25
|
+
from textual.widget import Widget
|
|
26
|
+
from textual.widgets import Static
|
|
27
|
+
|
|
28
|
+
import chrys.app.tui.widgets.file_preview as _file_preview
|
|
29
|
+
from chrys.app.tui.i18n import render_str, render_text, widget_localizer
|
|
30
|
+
from chrys.app.tui.support.gc_freeze import GcAbsorbReason, GcAbsorbRequested
|
|
31
|
+
from chrys.app.tui.util.static_update import update_static_in_place
|
|
32
|
+
from chrys.app.tui.util.visibility import is_widget_shown_on_active_screen
|
|
33
|
+
from chrys.app.tui.widgets.chat.file_snapshot import FileSnapshotPayload, FileSnapshotRef
|
|
34
|
+
from chrys.app.tui.widgets.chat.tool_call import (
|
|
35
|
+
TOOL_CARD_REJECTED,
|
|
36
|
+
BaseToolCard,
|
|
37
|
+
ToolCardHeader,
|
|
38
|
+
fmt_duration,
|
|
39
|
+
tool_result_render_status,
|
|
40
|
+
)
|
|
41
|
+
from chrys.app.tui.widgets.chat.tool_view_builders import ToolViewDiff, build_output_views
|
|
42
|
+
from chrys.foundation.i18n import msg
|
|
43
|
+
from chrys.foundation.tool_kinds import KIND_FILESYSTEM_WRITE
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from textual.app import ComposeResult
|
|
47
|
+
|
|
48
|
+
_FILE_EDIT_WRITING = msg("tui.tool_card.file_edit.writing", fallback="writing")
|
|
49
|
+
_FILE_EDIT_EDITING = msg("tui.tool_card.file_edit.editing", fallback="editing")
|
|
50
|
+
_FILE_EDIT_ERROR = msg("tui.tool_card.file_edit.status.error", fallback="error")
|
|
51
|
+
_FILE_EDIT_CHANGES = msg("tui.tool_card.file_edit.status.changes", fallback="changes")
|
|
52
|
+
_FILE_EDIT_CHANGE_COUNTS = msg(
|
|
53
|
+
"tui.tool_card.file_edit.status.change_counts",
|
|
54
|
+
fallback="+{additions}, -{removals}",
|
|
55
|
+
)
|
|
56
|
+
_FILE_EDIT_LINES_WRITTEN = msg(
|
|
57
|
+
"tui.tool_card.file_edit.status.lines_written",
|
|
58
|
+
fallback="{lines} lines written",
|
|
59
|
+
)
|
|
60
|
+
_FILE_EDIT_OVERWRITE = msg("tui.tool_card.file_edit.overwrite", fallback="(overwrite)")
|
|
61
|
+
_FILE_EDIT_DIFF = msg("tui.tool_card.file_edit.section.diff", fallback="Diff")
|
|
62
|
+
_FILE_EDIT_DIFF_PREVIEW = msg(
|
|
63
|
+
"tui.tool_card.file_edit.section.diff_preview",
|
|
64
|
+
fallback="Diff Preview",
|
|
65
|
+
)
|
|
66
|
+
_FILE_EDIT_FULL_DIFF_OMITTED = msg(
|
|
67
|
+
"tui.tool_card.file_edit.full_diff_omitted",
|
|
68
|
+
fallback=(
|
|
69
|
+
"@@ Full diff omitted: snapshot is {total_chars} chars (limit {limit_chars}); "
|
|
70
|
+
"showing bounded diff around first change @@"
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
_FILE_EDIT_BOUNDED_PREVIEW_UNCHANGED = msg(
|
|
74
|
+
"tui.tool_card.file_edit.bounded_preview_unchanged",
|
|
75
|
+
fallback="@@ Bounded preview contains no changed lines @@",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
_WRITE_FILE_LINE_COUNT_SCAN_CHARS = 64 * 1024
|
|
80
|
+
_FILE_COPY_FULL_DIFF_MAX_CHARS = 64 * 1024
|
|
81
|
+
_FILE_COPY_PREVIEW_LINES = 12
|
|
82
|
+
_FILE_COPY_CONTEXT_LINES = 3
|
|
83
|
+
_FILE_COPY_DIFF_SCAN_CHUNK_CHARS = 4096
|
|
84
|
+
_EDIT_FILE_INLINE_MAX_DISPLAY_LINES = 24
|
|
85
|
+
_INLINE_DIFF_MOUNT_DELAY_SECONDS = 0.2
|
|
86
|
+
_WRITE_FILE_RESULT_LINES_RE = re.compile(r"\((\d+) lines?\)")
|
|
87
|
+
|
|
88
|
+
logger = logging.getLogger(__name__)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _write_file_line_count(result: str, after: str) -> str:
|
|
92
|
+
"""Return the written line count without materializing all lines."""
|
|
93
|
+
if match := _WRITE_FILE_RESULT_LINES_RE.search(result):
|
|
94
|
+
return match.group(1)
|
|
95
|
+
if not after:
|
|
96
|
+
return "0"
|
|
97
|
+
scan_end = min(len(after), _WRITE_FILE_LINE_COUNT_SCAN_CHARS)
|
|
98
|
+
newlines = after.count("\n", 0, scan_end)
|
|
99
|
+
if scan_end == len(after):
|
|
100
|
+
count = newlines + (0 if after.endswith("\n") else 1)
|
|
101
|
+
return str(count)
|
|
102
|
+
count = newlines if after[scan_end - 1] == "\n" else newlines + 1
|
|
103
|
+
return f"{count}+"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _line_ending_sensitive_change(before: str, after: str, *, changed_index: int | None = None) -> bool:
|
|
107
|
+
"""Return true when content lines match but terminators differ."""
|
|
108
|
+
if changed_index is not None:
|
|
109
|
+
before_char = before[changed_index] if changed_index < len(before) else ""
|
|
110
|
+
after_char = after[changed_index] if changed_index < len(after) else ""
|
|
111
|
+
return before_char in {"\r", "\n", ""} and after_char in {"\r", "\n", ""}
|
|
112
|
+
if not before or not after:
|
|
113
|
+
return False
|
|
114
|
+
return before != after and before.splitlines() == after.splitlines()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _split_line_ending(line: str) -> tuple[str, str]:
|
|
118
|
+
"""Return ``(body, label)`` for a possibly terminated line chunk."""
|
|
119
|
+
if line.endswith("\r\n"):
|
|
120
|
+
return line[:-2], "CRLF"
|
|
121
|
+
if line.endswith("\n"):
|
|
122
|
+
return line[:-1], "LF"
|
|
123
|
+
if line.endswith("\r"):
|
|
124
|
+
return line[:-1], "CR"
|
|
125
|
+
return line, "none"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _format_eol_marker(label: str) -> str:
|
|
129
|
+
"""Return a visible line-ending marker for copied diffs."""
|
|
130
|
+
return " [no newline]" if label == "none" else f" [EOL: {label}]"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _copy_diff_source_lines(text: str, *, show_eol_markers: bool) -> list[str]:
|
|
134
|
+
"""Return source lines for copy-only diffs, optionally exposing EOLs."""
|
|
135
|
+
if not show_eol_markers:
|
|
136
|
+
return text.splitlines()
|
|
137
|
+
lines: list[str] = []
|
|
138
|
+
for line in text.splitlines(keepends=True):
|
|
139
|
+
body, eol = _split_line_ending(line)
|
|
140
|
+
lines.append(_file_preview.sanitize_preview_line(body) + _format_eol_marker(eol))
|
|
141
|
+
return lines
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _first_mismatch_index(before: str, after: str) -> int | None:
|
|
145
|
+
"""Return the first differing character index without splitting snapshots."""
|
|
146
|
+
common_len = min(len(before), len(after))
|
|
147
|
+
pos = 0
|
|
148
|
+
while pos < common_len:
|
|
149
|
+
end = min(pos + _FILE_COPY_DIFF_SCAN_CHUNK_CHARS, common_len)
|
|
150
|
+
if before[pos:end] == after[pos:end]:
|
|
151
|
+
pos = end
|
|
152
|
+
continue
|
|
153
|
+
for idx in range(pos, end):
|
|
154
|
+
if before[idx] != after[idx]:
|
|
155
|
+
return idx
|
|
156
|
+
if len(before) != len(after):
|
|
157
|
+
return common_len
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _line_start_with_context(text: str, index: int, context_lines: int) -> int:
|
|
162
|
+
"""Return the line start before *index*, including bounded context."""
|
|
163
|
+
if not text:
|
|
164
|
+
return 0
|
|
165
|
+
index = min(max(index, 0), len(text))
|
|
166
|
+
start = text.rfind("\n", 0, index) + 1
|
|
167
|
+
for _ in range(context_lines):
|
|
168
|
+
if start == 0:
|
|
169
|
+
break
|
|
170
|
+
prev_newline = text.rfind("\n", 0, start - 1)
|
|
171
|
+
start = 0 if prev_newline < 0 else prev_newline + 1
|
|
172
|
+
return start
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _sanitize_bounded_diff_line(line: str, changed_offset: int | None, max_chars: int) -> str:
|
|
176
|
+
"""Return one terminal-safe bounded line for diff previews."""
|
|
177
|
+
if len(line) <= max_chars:
|
|
178
|
+
return _file_preview.sanitize_preview_line(line)
|
|
179
|
+
|
|
180
|
+
if changed_offset is None:
|
|
181
|
+
return _file_preview.sanitize_preview_line(line[:max_chars]) + "..."
|
|
182
|
+
|
|
183
|
+
changed_offset = min(max(changed_offset, 0), len(line))
|
|
184
|
+
budget = max(1, max_chars - 6)
|
|
185
|
+
start = max(0, changed_offset - (budget // 2))
|
|
186
|
+
end = min(len(line), start + budget)
|
|
187
|
+
start = max(0, end - budget)
|
|
188
|
+
prefix = "..." if start > 0 else ""
|
|
189
|
+
suffix = "..." if end < len(line) else ""
|
|
190
|
+
return prefix + _file_preview.sanitize_preview_line(line[start:end]) + suffix
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _bounded_changed_window(
|
|
194
|
+
text: str,
|
|
195
|
+
changed_index: int,
|
|
196
|
+
*,
|
|
197
|
+
max_lines: int,
|
|
198
|
+
max_line_chars: int,
|
|
199
|
+
show_eol_markers: bool = False,
|
|
200
|
+
) -> list[str]:
|
|
201
|
+
"""Return bounded lines around the actual changed region."""
|
|
202
|
+
if not text or max_lines <= 0 or max_line_chars <= 0:
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
changed_index = min(max(changed_index, 0), len(text))
|
|
206
|
+
pos = _line_start_with_context(text, changed_index, _FILE_COPY_CONTEXT_LINES)
|
|
207
|
+
lines: list[str] = []
|
|
208
|
+
while pos <= len(text) and len(lines) < max_lines:
|
|
209
|
+
newline = text.find("\n", pos)
|
|
210
|
+
if newline < 0:
|
|
211
|
+
line_end = len(text)
|
|
212
|
+
next_pos = len(text) + 1
|
|
213
|
+
else:
|
|
214
|
+
line_end = newline
|
|
215
|
+
next_pos = newline + 1
|
|
216
|
+
|
|
217
|
+
changed_offset: int | None = None
|
|
218
|
+
if pos <= changed_index <= line_end:
|
|
219
|
+
changed_offset = changed_index - pos
|
|
220
|
+
body, eol = _split_line_ending(text[pos:next_pos])
|
|
221
|
+
line = _sanitize_bounded_diff_line(body, changed_offset, max_line_chars)
|
|
222
|
+
if show_eol_markers:
|
|
223
|
+
line += _format_eol_marker(eol)
|
|
224
|
+
lines.append(line)
|
|
225
|
+
|
|
226
|
+
if newline < 0:
|
|
227
|
+
break
|
|
228
|
+
pos = next_pos
|
|
229
|
+
|
|
230
|
+
return lines
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class _FileToolCall(BaseToolCard):
|
|
234
|
+
"""Base renderer for file mutation tools (edit_file, write_file).
|
|
235
|
+
|
|
236
|
+
Before/after content is supplied via ``file_snapshot`` in
|
|
237
|
+
``set_complete()`` — sourced from the mutation tracking system
|
|
238
|
+
during live execution and from serialized blobs during replay.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
DEFAULT_CSS = """
|
|
242
|
+
_FileToolCall {
|
|
243
|
+
height: auto;
|
|
244
|
+
padding: 0 0 0 2;
|
|
245
|
+
margin: 0 0 1 0;
|
|
246
|
+
}
|
|
247
|
+
_FileToolCall #ft-label {
|
|
248
|
+
height: auto;
|
|
249
|
+
}
|
|
250
|
+
_FileToolCall > #ft-panel {
|
|
251
|
+
height: auto;
|
|
252
|
+
margin: 0 0 0 2;
|
|
253
|
+
border: round $tui-border-primary 50%;
|
|
254
|
+
border-title-color: $text;
|
|
255
|
+
border-title-style: bold;
|
|
256
|
+
border-subtitle-align: right;
|
|
257
|
+
border-subtitle-color: $text-muted;
|
|
258
|
+
padding: 0 1;
|
|
259
|
+
}
|
|
260
|
+
_FileToolCall.-success > #ft-panel {
|
|
261
|
+
border: round $tui-border-success 30%;
|
|
262
|
+
border-title-color: $success;
|
|
263
|
+
border-subtitle-color: $success;
|
|
264
|
+
|
|
265
|
+
border-title-style: not bold;
|
|
266
|
+
}
|
|
267
|
+
_FileToolCall.-error > #ft-panel {
|
|
268
|
+
border: round $tui-border-error 50%;
|
|
269
|
+
border-title-color: $text-error;
|
|
270
|
+
border-subtitle-color: $text-error;
|
|
271
|
+
|
|
272
|
+
border-title-style: not bold;
|
|
273
|
+
}
|
|
274
|
+
_FileToolCall.-rejected > #ft-panel {
|
|
275
|
+
border: round $tui-border-warning 50%;
|
|
276
|
+
border-title-color: $warning;
|
|
277
|
+
border-subtitle-color: $warning;
|
|
278
|
+
|
|
279
|
+
border-title-style: not bold;
|
|
280
|
+
}
|
|
281
|
+
_FileToolCall #ft-spinner {
|
|
282
|
+
height: auto;
|
|
283
|
+
}
|
|
284
|
+
_FileToolCall #ft-content {
|
|
285
|
+
height: auto;
|
|
286
|
+
display: none;
|
|
287
|
+
}
|
|
288
|
+
_FileToolCall.-done #ft-spinner {
|
|
289
|
+
display: none;
|
|
290
|
+
}
|
|
291
|
+
_FileToolCall.-done #ft-content {
|
|
292
|
+
display: block;
|
|
293
|
+
}
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
_SPINNERS: ClassVar[str] = "◐◓◑◒"
|
|
297
|
+
|
|
298
|
+
def __init__(
|
|
299
|
+
self,
|
|
300
|
+
call_id: str,
|
|
301
|
+
tool_name: str,
|
|
302
|
+
args_summary: str = "",
|
|
303
|
+
args: dict[str, Any] | None = None,
|
|
304
|
+
) -> None:
|
|
305
|
+
super().__init__(call_id, tool_name, args_summary, args=args)
|
|
306
|
+
self._spin_idx = 0
|
|
307
|
+
self._file_path = self.args.get("path", "")
|
|
308
|
+
self._before_content = ""
|
|
309
|
+
self._after_content: str | None = None
|
|
310
|
+
self._snapshot_ref: FileSnapshotRef | None = None
|
|
311
|
+
self._diff_pending = False
|
|
312
|
+
self._diff_mounting = False
|
|
313
|
+
self._diff_timer: Timer | None = None
|
|
314
|
+
self._content_release_epoch = 0
|
|
315
|
+
self._timer: Timer | None = None
|
|
316
|
+
|
|
317
|
+
def _label_suffix(self) -> str:
|
|
318
|
+
"""Extra text for the label. Override in subclasses."""
|
|
319
|
+
return ""
|
|
320
|
+
|
|
321
|
+
def _label_text(self, duration_ms: int = 0) -> Text:
|
|
322
|
+
t = Text()
|
|
323
|
+
t.append("• ", style="bold")
|
|
324
|
+
t.append(self.tool_name, style="bold")
|
|
325
|
+
if duration_ms:
|
|
326
|
+
t.append(f" ({fmt_duration(duration_ms)})", style="dim")
|
|
327
|
+
if self._file_path:
|
|
328
|
+
t.append(f" {self._file_path}", style="dim")
|
|
329
|
+
suffix = self._label_suffix()
|
|
330
|
+
if suffix:
|
|
331
|
+
t.append(f" {suffix}", style="dim italic")
|
|
332
|
+
return t
|
|
333
|
+
|
|
334
|
+
def compose(self) -> ComposeResult:
|
|
335
|
+
yield ToolCardHeader(self._label_text(), id="ft-label")
|
|
336
|
+
panel = Widget(id="ft-panel")
|
|
337
|
+
panel.border_title = Text(os.path.basename(self._file_path)) if self._file_path else Text(self.tool_name)
|
|
338
|
+
yield panel
|
|
339
|
+
|
|
340
|
+
def on_mount(self) -> None:
|
|
341
|
+
panel = self.query_one("#ft-panel")
|
|
342
|
+
panel.mount(Static(self._render_spinner(), id="ft-spinner"))
|
|
343
|
+
panel.mount(Widget(id="ft-content"))
|
|
344
|
+
self._timer = self.set_interval(0.12, self._spin)
|
|
345
|
+
|
|
346
|
+
def on_unmount(self) -> None:
|
|
347
|
+
if self._timer is not None:
|
|
348
|
+
self._timer.stop()
|
|
349
|
+
if self._diff_timer is not None:
|
|
350
|
+
self._diff_timer.stop()
|
|
351
|
+
|
|
352
|
+
def _spin(self) -> None:
|
|
353
|
+
if self.status == "running":
|
|
354
|
+
self._spin_idx = (self._spin_idx + 1) % len(self._SPINNERS)
|
|
355
|
+
# Only a shown card repaints (see ``ToolCall._spin``); the first tick after it shows paints it.
|
|
356
|
+
if not is_widget_shown_on_active_screen(self):
|
|
357
|
+
return
|
|
358
|
+
with suppress(Exception):
|
|
359
|
+
update_static_in_place(self.query_one("#ft-spinner", Static), self._render_spinner())
|
|
360
|
+
|
|
361
|
+
def _render_spinner(self) -> Text:
|
|
362
|
+
t = Text()
|
|
363
|
+
t.append(f"{self._SPINNERS[self._spin_idx]} ", style="yellow")
|
|
364
|
+
t.append(render_str(widget_localizer(self), _FILE_EDIT_WRITING.bind()), style="yellow")
|
|
365
|
+
return t
|
|
366
|
+
|
|
367
|
+
def _get_after_content(self) -> str:
|
|
368
|
+
"""Return the after-mutation file content from snapshot."""
|
|
369
|
+
self._ensure_snapshot_loaded()
|
|
370
|
+
return self._after_content if self._after_content is not None else ""
|
|
371
|
+
|
|
372
|
+
def _has_snapshot_data(self) -> bool:
|
|
373
|
+
"""Return true when a text snapshot is available inline or by disk ref."""
|
|
374
|
+
return self._after_content is not None or self._snapshot_ref is not None
|
|
375
|
+
|
|
376
|
+
def _ensure_snapshot_loaded(self) -> None:
|
|
377
|
+
"""Load a disk-backed snapshot ref on demand."""
|
|
378
|
+
if self._snapshot_ref is None or self._after_content is not None:
|
|
379
|
+
return
|
|
380
|
+
self._before_content, self._after_content = self._snapshot_ref.resolve()
|
|
381
|
+
|
|
382
|
+
async def _ensure_snapshot_loaded_async(self) -> None:
|
|
383
|
+
"""Load a disk-backed snapshot ref without blocking the UI loop."""
|
|
384
|
+
if self._snapshot_ref is None or self._after_content is not None:
|
|
385
|
+
return
|
|
386
|
+
before, after = await asyncio.to_thread(self._snapshot_ref.resolve)
|
|
387
|
+
if self._snapshot_ref is not None and self._after_content is None:
|
|
388
|
+
self._before_content = before
|
|
389
|
+
self._after_content = after
|
|
390
|
+
|
|
391
|
+
async def _copy_after_snapshot_load(self) -> None:
|
|
392
|
+
"""Load a disk-backed snapshot before copying tool details."""
|
|
393
|
+
await self._ensure_snapshot_loaded_async()
|
|
394
|
+
self.copy_tool_execution()
|
|
395
|
+
|
|
396
|
+
async def _open_view_after_snapshot_load(self) -> None:
|
|
397
|
+
"""Load a disk-backed snapshot before opening the full detail view."""
|
|
398
|
+
await self._ensure_snapshot_loaded_async()
|
|
399
|
+
self.open_tool_view()
|
|
400
|
+
|
|
401
|
+
def _defer_tool_copy(self) -> bool:
|
|
402
|
+
"""Schedule copy handling when a disk-backed snapshot must be loaded first."""
|
|
403
|
+
if self._snapshot_ref is not None and self._after_content is None:
|
|
404
|
+
self.run_worker(self._copy_after_snapshot_load(), exclusive=True, group="file-snapshot-actions")
|
|
405
|
+
return True
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
def _defer_tool_view(self) -> bool:
|
|
409
|
+
"""Schedule view handling when a disk-backed snapshot must be loaded first."""
|
|
410
|
+
if self._snapshot_ref is not None and self._after_content is None:
|
|
411
|
+
self.run_worker(self._open_view_after_snapshot_load(), exclusive=True, group="file-snapshot-actions")
|
|
412
|
+
return True
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
def _tool_copy_full_diff_section(self) -> tuple[str, str, str] | None:
|
|
416
|
+
"""Return the full mutation diff for small snapshots."""
|
|
417
|
+
path = self._file_path or self.tool_name
|
|
418
|
+
after = self._get_after_content()
|
|
419
|
+
show_eol_markers = _line_ending_sensitive_change(self._before_content, after)
|
|
420
|
+
diff_lines = unified_diff(
|
|
421
|
+
_copy_diff_source_lines(self._before_content, show_eol_markers=show_eol_markers),
|
|
422
|
+
_copy_diff_source_lines(after, show_eol_markers=show_eol_markers),
|
|
423
|
+
fromfile=f"before/{path}",
|
|
424
|
+
tofile=f"after/{path}",
|
|
425
|
+
lineterm="",
|
|
426
|
+
)
|
|
427
|
+
diff_text = "\n".join(diff_lines)
|
|
428
|
+
if diff_text:
|
|
429
|
+
return (self._render_message(_FILE_EDIT_DIFF.bind()), "diff", diff_text)
|
|
430
|
+
return None
|
|
431
|
+
|
|
432
|
+
def _tool_copy_preview_diff_section(self) -> tuple[str, str, str] | None:
|
|
433
|
+
"""Return a bounded real diff around the first changed region."""
|
|
434
|
+
after = self._get_after_content()
|
|
435
|
+
before = self._before_content
|
|
436
|
+
path = self._file_path or self.tool_name
|
|
437
|
+
total_chars = len(before) + len(after)
|
|
438
|
+
changed_index = _first_mismatch_index(before, after)
|
|
439
|
+
if changed_index is None:
|
|
440
|
+
return None
|
|
441
|
+
show_eol_markers = _line_ending_sensitive_change(before, after, changed_index=changed_index)
|
|
442
|
+
before_lines = _bounded_changed_window(
|
|
443
|
+
before,
|
|
444
|
+
max_lines=_FILE_COPY_PREVIEW_LINES,
|
|
445
|
+
max_line_chars=_file_preview.WRITE_FILE_PREVIEW_MAX_LINE_CHARS,
|
|
446
|
+
changed_index=changed_index,
|
|
447
|
+
show_eol_markers=show_eol_markers,
|
|
448
|
+
)
|
|
449
|
+
after_lines = _bounded_changed_window(
|
|
450
|
+
after,
|
|
451
|
+
max_lines=_FILE_COPY_PREVIEW_LINES,
|
|
452
|
+
max_line_chars=_file_preview.WRITE_FILE_PREVIEW_MAX_LINE_CHARS,
|
|
453
|
+
changed_index=changed_index,
|
|
454
|
+
show_eol_markers=show_eol_markers,
|
|
455
|
+
)
|
|
456
|
+
lines = [
|
|
457
|
+
f"--- before/{path}",
|
|
458
|
+
f"+++ after/{path}",
|
|
459
|
+
self._render_message(
|
|
460
|
+
_FILE_EDIT_FULL_DIFF_OMITTED.bind(
|
|
461
|
+
total_chars=total_chars,
|
|
462
|
+
limit_chars=_FILE_COPY_FULL_DIFF_MAX_CHARS,
|
|
463
|
+
)
|
|
464
|
+
),
|
|
465
|
+
]
|
|
466
|
+
preview_diff = unified_diff(
|
|
467
|
+
before_lines,
|
|
468
|
+
after_lines,
|
|
469
|
+
lineterm="",
|
|
470
|
+
)
|
|
471
|
+
with suppress(StopIteration):
|
|
472
|
+
next(preview_diff)
|
|
473
|
+
next(preview_diff)
|
|
474
|
+
lines.extend(preview_diff)
|
|
475
|
+
if len(lines) == 3:
|
|
476
|
+
lines.append(self._render_message(_FILE_EDIT_BOUNDED_PREVIEW_UNCHANGED.bind()))
|
|
477
|
+
return (self._render_message(_FILE_EDIT_DIFF_PREVIEW.bind()), "diff", "\n".join(lines))
|
|
478
|
+
|
|
479
|
+
def _tool_copy_diff_section(self) -> tuple[str, str, str] | None:
|
|
480
|
+
"""Return the copy diff section, bounded for oversized snapshots."""
|
|
481
|
+
after = self._get_after_content()
|
|
482
|
+
before = self._before_content
|
|
483
|
+
if len(before) + len(after) > _FILE_COPY_FULL_DIFF_MAX_CHARS:
|
|
484
|
+
return self._tool_copy_preview_diff_section()
|
|
485
|
+
return self._tool_copy_full_diff_section()
|
|
486
|
+
|
|
487
|
+
def _tool_copy_sections(self) -> list[tuple[str, str, str]]:
|
|
488
|
+
"""Include the mutation diff in copy payloads when snapshot data exists."""
|
|
489
|
+
sections = super()._tool_copy_sections()
|
|
490
|
+
if self.has_class("-rejected") or self._result_is_error():
|
|
491
|
+
return sections
|
|
492
|
+
if not self._has_snapshot_data():
|
|
493
|
+
return sections
|
|
494
|
+
|
|
495
|
+
diff_section = self._tool_copy_diff_section()
|
|
496
|
+
if diff_section is not None:
|
|
497
|
+
sections.append(diff_section)
|
|
498
|
+
return sections
|
|
499
|
+
|
|
500
|
+
def _tool_view_output_widgets(self) -> list[Widget]:
|
|
501
|
+
"""Show the full, untruncated mutation diff in the view modal."""
|
|
502
|
+
if self.has_class("-rejected") or self._result_is_error() or not self._has_snapshot_data():
|
|
503
|
+
return super()._tool_view_output_widgets()
|
|
504
|
+
after = self._get_after_content()
|
|
505
|
+
# No content at all (e.g. empty write) — DiffView would render blank.
|
|
506
|
+
if not self._before_content and not after:
|
|
507
|
+
return super()._tool_view_output_widgets()
|
|
508
|
+
# No-op mutation (identical snapshot) — DiffView has no opcodes to show,
|
|
509
|
+
# so fall back to the textual result rather than a blank tab.
|
|
510
|
+
if self._before_content == after:
|
|
511
|
+
return super()._tool_view_output_widgets()
|
|
512
|
+
# Oversized snapshots (e.g. large/minified write_file output, or a big
|
|
513
|
+
# line-ending-only rewrite) would make a full diff run expensive highlight
|
|
514
|
+
# + opcode work and mount thousands of lines. Fall back to the bounded
|
|
515
|
+
# preview diff the copy path already uses for the same threshold. This is
|
|
516
|
+
# checked before the EOL branch so large EOL-only changes are bounded too;
|
|
517
|
+
# _tool_copy_diff_section()'s preview path is itself EOL-aware.
|
|
518
|
+
if len(self._before_content) + len(after) > _FILE_COPY_FULL_DIFF_MAX_CHARS:
|
|
519
|
+
section = self._tool_copy_diff_section()
|
|
520
|
+
if section is not None:
|
|
521
|
+
return build_output_views(
|
|
522
|
+
[section],
|
|
523
|
+
dark=self._view_dark(),
|
|
524
|
+
render_message=self._render_message,
|
|
525
|
+
)
|
|
526
|
+
return super()._tool_view_output_widgets()
|
|
527
|
+
# Line-ending-only changes vanish under DiffView's splitlines(); use the
|
|
528
|
+
# EOL-marked unified diff so the change stays visible.
|
|
529
|
+
if _line_ending_sensitive_change(self._before_content, after):
|
|
530
|
+
section = self._tool_copy_full_diff_section()
|
|
531
|
+
if section is not None:
|
|
532
|
+
return build_output_views(
|
|
533
|
+
[section],
|
|
534
|
+
dark=self._view_dark(),
|
|
535
|
+
render_message=self._render_message,
|
|
536
|
+
)
|
|
537
|
+
return super()._tool_view_output_widgets()
|
|
538
|
+
|
|
539
|
+
return [
|
|
540
|
+
ToolViewDiff(
|
|
541
|
+
self._file_path or self.tool_name,
|
|
542
|
+
self._before_content,
|
|
543
|
+
after,
|
|
544
|
+
render_message=self._render_message,
|
|
545
|
+
)
|
|
546
|
+
]
|
|
547
|
+
|
|
548
|
+
def _build_diff(self) -> Widget | Static | None:
|
|
549
|
+
"""Build the diff widget. Override in subclasses."""
|
|
550
|
+
return None
|
|
551
|
+
|
|
552
|
+
def _is_in_collapsed_tool_group(self) -> bool:
|
|
553
|
+
"""Return true when this tool sits inside a collapsed tool group."""
|
|
554
|
+
from chrys.app.tui.widgets.chat.tool_call import ToolGroup
|
|
555
|
+
|
|
556
|
+
return any(isinstance(ancestor, ToolGroup) and ancestor.collapsed for ancestor in self.ancestors)
|
|
557
|
+
|
|
558
|
+
def _has_inline_diff_child(self) -> bool:
|
|
559
|
+
"""Return true when the inline content container already has a mounted child.
|
|
560
|
+
|
|
561
|
+
A child that ``release_collapsed_content`` already removed stays in the
|
|
562
|
+
node list until its prune completes; it must not count as mounted or a
|
|
563
|
+
rebuild racing that prune would skip itself and strand the card.
|
|
564
|
+
"""
|
|
565
|
+
with suppress(Exception):
|
|
566
|
+
content = self.query_one("#ft-content")
|
|
567
|
+
return any(not getattr(child, "_pruning", False) for child in content.children)
|
|
568
|
+
return False
|
|
569
|
+
|
|
570
|
+
def release_collapsed_content(self) -> None:
|
|
571
|
+
"""Drop expensive inline diff widgets while a completed group is collapsed.
|
|
572
|
+
|
|
573
|
+
The raw snapshots remain on this widget, so expanding the group can
|
|
574
|
+
rebuild the inline preview on demand and the detailed view/copy paths
|
|
575
|
+
still have full content.
|
|
576
|
+
"""
|
|
577
|
+
if (
|
|
578
|
+
self.status != "complete"
|
|
579
|
+
or self.has_class("-rejected")
|
|
580
|
+
or self._result_is_error()
|
|
581
|
+
or not self._has_snapshot_data()
|
|
582
|
+
):
|
|
583
|
+
return
|
|
584
|
+
self._diff_pending = True
|
|
585
|
+
self._content_release_epoch += 1
|
|
586
|
+
if self._diff_timer is not None:
|
|
587
|
+
self._diff_timer.stop()
|
|
588
|
+
self._diff_timer = None
|
|
589
|
+
self.add_class("-done")
|
|
590
|
+
with suppress(Exception):
|
|
591
|
+
content = self.query_one("#ft-content")
|
|
592
|
+
for child in list(content.children):
|
|
593
|
+
child.remove()
|
|
594
|
+
if self._snapshot_ref is not None:
|
|
595
|
+
self._before_content = ""
|
|
596
|
+
self._after_content = None
|
|
597
|
+
|
|
598
|
+
def _update_title(self, result: str, *, defer_snapshot: bool = False) -> None:
|
|
599
|
+
"""Update panel border title on completion. Override in subclasses."""
|
|
600
|
+
|
|
601
|
+
def set_complete(
|
|
602
|
+
self,
|
|
603
|
+
result: str,
|
|
604
|
+
duration_ms: int = 0,
|
|
605
|
+
*,
|
|
606
|
+
file_snapshot: FileSnapshotPayload | None = None,
|
|
607
|
+
lazy: bool = False,
|
|
608
|
+
**kwargs: Any,
|
|
609
|
+
) -> None:
|
|
610
|
+
"""Mark as complete and mount diff view.
|
|
611
|
+
|
|
612
|
+
*file_snapshot* supplies ``(before, after)`` content from the
|
|
613
|
+
mutation tracking system (both live and replay paths).
|
|
614
|
+
|
|
615
|
+
If *lazy* is True, defer diff construction entirely until
|
|
616
|
+
``mount_diff_if_pending()`` is called (used during replay when the
|
|
617
|
+
tool group is collapsed).
|
|
618
|
+
"""
|
|
619
|
+
if file_snapshot is not None:
|
|
620
|
+
if isinstance(file_snapshot, FileSnapshotRef):
|
|
621
|
+
self._snapshot_ref = file_snapshot
|
|
622
|
+
self._before_content = ""
|
|
623
|
+
self._after_content = None
|
|
624
|
+
else:
|
|
625
|
+
self._snapshot_ref = None
|
|
626
|
+
self._before_content = file_snapshot[0]
|
|
627
|
+
self._after_content = file_snapshot[1]
|
|
628
|
+
self.result_text = result
|
|
629
|
+
self.duration_ms = duration_ms
|
|
630
|
+
self.status = "complete"
|
|
631
|
+
metadata = kwargs.get("metadata")
|
|
632
|
+
self.metadata = metadata if isinstance(metadata, dict) else {}
|
|
633
|
+
if self._timer is not None:
|
|
634
|
+
self._timer.stop()
|
|
635
|
+
|
|
636
|
+
# Update label
|
|
637
|
+
with suppress(Exception):
|
|
638
|
+
self.query_one("#ft-label", Static).update(self._label_text(duration_ms))
|
|
639
|
+
|
|
640
|
+
self.approval = kwargs.get("approval")
|
|
641
|
+
render_status = self._result_render_status()
|
|
642
|
+
self.status = render_status
|
|
643
|
+
if render_status == "rejected":
|
|
644
|
+
self.add_class("-rejected")
|
|
645
|
+
panel = self.query_one("#ft-panel")
|
|
646
|
+
panel.border_title = Text(os.path.basename(self._file_path))
|
|
647
|
+
panel.border_subtitle = render_text(widget_localizer(self), TOOL_CARD_REJECTED.bind())
|
|
648
|
+
with suppress(Exception):
|
|
649
|
+
content = self.query_one("#ft-content")
|
|
650
|
+
content.mount(Static(Text(result)))
|
|
651
|
+
self.add_class("-done")
|
|
652
|
+
self._show_tool_copy_button()
|
|
653
|
+
elif render_status == "error":
|
|
654
|
+
self.add_class("-error")
|
|
655
|
+
panel = self.query_one("#ft-panel")
|
|
656
|
+
panel.border_title = Text(os.path.basename(self._file_path))
|
|
657
|
+
panel.border_subtitle = render_text(widget_localizer(self), _FILE_EDIT_ERROR.bind())
|
|
658
|
+
with suppress(Exception):
|
|
659
|
+
content = self.query_one("#ft-content")
|
|
660
|
+
content.mount(Static(Text(result)))
|
|
661
|
+
self.add_class("-done")
|
|
662
|
+
self._show_tool_copy_button()
|
|
663
|
+
else:
|
|
664
|
+
self.add_class("-success")
|
|
665
|
+
defer_snapshot = isinstance(file_snapshot, FileSnapshotRef)
|
|
666
|
+
self._update_title(result, defer_snapshot=defer_snapshot)
|
|
667
|
+
self._show_tool_copy_button()
|
|
668
|
+
if lazy or defer_snapshot:
|
|
669
|
+
# Store flag — diff will be built on first expand
|
|
670
|
+
self._diff_pending = True
|
|
671
|
+
else:
|
|
672
|
+
# Defer diff mounting — syntax highlighting is expensive and
|
|
673
|
+
# blocks the event loop. A short timer lets fast-completing
|
|
674
|
+
# tool groups collapse first, avoiding work for historical
|
|
675
|
+
# diffs that are no longer visible.
|
|
676
|
+
self._diff_timer = self.set_timer(_INLINE_DIFF_MOUNT_DELAY_SECONDS, self._schedule_mount_diff)
|
|
677
|
+
|
|
678
|
+
async def mount_diff_if_pending(self) -> bool:
|
|
679
|
+
"""Build and mount the diff if it was deferred (lazy mode).
|
|
680
|
+
|
|
681
|
+
Called when the parent ToolGroup is first expanded after replay.
|
|
682
|
+
"""
|
|
683
|
+
if not self._diff_pending or self.has_class("-rejected") or self._result_is_error():
|
|
684
|
+
return False
|
|
685
|
+
return await self._mount_diff_async(post_absorb=False)
|
|
686
|
+
|
|
687
|
+
def _result_render_status(self) -> str:
|
|
688
|
+
return tool_result_render_status(
|
|
689
|
+
self.result_text,
|
|
690
|
+
self.approval,
|
|
691
|
+
self.tool_kind or KIND_FILESYSTEM_WRITE,
|
|
692
|
+
self.metadata,
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
def _result_is_error(self) -> bool:
|
|
696
|
+
return self._result_render_status() == "error"
|
|
697
|
+
|
|
698
|
+
async def mount_pending_content(self) -> bool:
|
|
699
|
+
"""Build and mount deferred inline content after the parent group expands."""
|
|
700
|
+
return await self.mount_diff_if_pending()
|
|
701
|
+
|
|
702
|
+
def _schedule_mount_diff(self) -> None:
|
|
703
|
+
"""Schedule async diff mounting after the short collapse grace period."""
|
|
704
|
+
self._diff_timer = None
|
|
705
|
+
if not self.is_attached:
|
|
706
|
+
self._diff_pending = True
|
|
707
|
+
return
|
|
708
|
+
self.call_later(self._mount_live_diff_async)
|
|
709
|
+
|
|
710
|
+
async def _mount_live_diff_async(self) -> None:
|
|
711
|
+
"""Mount a deferred live diff and request absorb after prewarming."""
|
|
712
|
+
await self._mount_diff_async(post_absorb=True)
|
|
713
|
+
|
|
714
|
+
async def _mount_diff_async(self, *, post_absorb: bool) -> bool:
|
|
715
|
+
"""Build and mount the diff widget, computing highlights off-thread."""
|
|
716
|
+
if not self.is_attached:
|
|
717
|
+
self._diff_pending = True
|
|
718
|
+
self.add_class("-done")
|
|
719
|
+
return False
|
|
720
|
+
self._diff_timer = None
|
|
721
|
+
if self._diff_mounting:
|
|
722
|
+
return False
|
|
723
|
+
if self._has_inline_diff_child():
|
|
724
|
+
self._diff_pending = False
|
|
725
|
+
self.add_class("-done")
|
|
726
|
+
return False
|
|
727
|
+
if self._is_in_collapsed_tool_group():
|
|
728
|
+
self._diff_pending = True
|
|
729
|
+
self.add_class("-done")
|
|
730
|
+
return False
|
|
731
|
+
self._diff_mounting = True
|
|
732
|
+
release_epoch = self._content_release_epoch
|
|
733
|
+
mounted = False
|
|
734
|
+
mount_error: Exception | None = None
|
|
735
|
+
try:
|
|
736
|
+
await self._ensure_snapshot_loaded_async()
|
|
737
|
+
if self._is_in_collapsed_tool_group():
|
|
738
|
+
self._diff_pending = True
|
|
739
|
+
return False
|
|
740
|
+
diff_widget = self._build_diff()
|
|
741
|
+
if diff_widget is not None:
|
|
742
|
+
from chrys.app.tui.widgets.diff_view.inline import InlineUnifiedDiffLines
|
|
743
|
+
from chrys.app.tui.widgets.diff_view.protocols import SupportsPrepare
|
|
744
|
+
|
|
745
|
+
# Pre-compute Pygments highlighting + SequenceMatcher in a
|
|
746
|
+
# worker thread so compose() finds cached results instantly.
|
|
747
|
+
if isinstance(diff_widget, SupportsPrepare):
|
|
748
|
+
try:
|
|
749
|
+
await diff_widget.prepare()
|
|
750
|
+
except Exception:
|
|
751
|
+
diff_widget = Static(Text(self.result_text))
|
|
752
|
+
if self._is_in_collapsed_tool_group():
|
|
753
|
+
self._diff_pending = True
|
|
754
|
+
return False
|
|
755
|
+
if self._has_inline_diff_child():
|
|
756
|
+
self._diff_pending = False
|
|
757
|
+
return False
|
|
758
|
+
try:
|
|
759
|
+
content = self.query_one("#ft-content")
|
|
760
|
+
await content.mount(diff_widget)
|
|
761
|
+
mounted = True
|
|
762
|
+
if isinstance(diff_widget, InlineUnifiedDiffLines):
|
|
763
|
+
if post_absorb:
|
|
764
|
+
|
|
765
|
+
def prewarm_and_request_absorb() -> None:
|
|
766
|
+
diff_widget.prewarm_visible_rows()
|
|
767
|
+
if (
|
|
768
|
+
self.is_attached
|
|
769
|
+
and diff_widget.is_attached
|
|
770
|
+
and not self._is_in_collapsed_tool_group()
|
|
771
|
+
):
|
|
772
|
+
self.post_message(GcAbsorbRequested(GcAbsorbReason.STABLE_CONTENT_MOUNTED))
|
|
773
|
+
|
|
774
|
+
self.call_after_refresh(prewarm_and_request_absorb)
|
|
775
|
+
else:
|
|
776
|
+
self.call_after_refresh(diff_widget.prewarm_visible_rows)
|
|
777
|
+
elif post_absorb:
|
|
778
|
+
self.post_message(GcAbsorbRequested(GcAbsorbReason.STABLE_CONTENT_MOUNTED))
|
|
779
|
+
except Exception as exc:
|
|
780
|
+
mount_error = exc
|
|
781
|
+
if release_epoch != self._content_release_epoch:
|
|
782
|
+
# The group collapsed while the mount was in flight and
|
|
783
|
+
# ``release_collapsed_content`` already dropped the widget we
|
|
784
|
+
# just attached. Keep the diff deferred so the next expand
|
|
785
|
+
# rebuilds it, and rebuild right away if the group is already
|
|
786
|
+
# expanded again (the expand pass saw us mid-mount and skipped).
|
|
787
|
+
self._diff_pending = True
|
|
788
|
+
if not self._is_in_collapsed_tool_group():
|
|
789
|
+
self.call_later(self._mount_diff_async, post_absorb=post_absorb)
|
|
790
|
+
return False
|
|
791
|
+
if diff_widget is not None and not mounted:
|
|
792
|
+
# The mount itself failed. Keep the diff deferred so the next
|
|
793
|
+
# expand (or explicit mount request) retries instead of leaving
|
|
794
|
+
# the card without inline content and nothing left to rebuild.
|
|
795
|
+
logger.warning(
|
|
796
|
+
"Inline diff mount failed for %s; deferring the rebuild to the next expand.",
|
|
797
|
+
self.call_id,
|
|
798
|
+
exc_info=mount_error,
|
|
799
|
+
)
|
|
800
|
+
self._diff_pending = True
|
|
801
|
+
return False
|
|
802
|
+
self._diff_pending = False
|
|
803
|
+
return mounted
|
|
804
|
+
finally:
|
|
805
|
+
self._diff_mounting = False
|
|
806
|
+
self.add_class("-done")
|
|
807
|
+
|
|
808
|
+
def set_error(self, error: str) -> None:
|
|
809
|
+
"""Mark as failed."""
|
|
810
|
+
self.result_text = error
|
|
811
|
+
self.status = "error"
|
|
812
|
+
if self._timer is not None:
|
|
813
|
+
self._timer.stop()
|
|
814
|
+
|
|
815
|
+
panel = self.query_one("#ft-panel")
|
|
816
|
+
panel.border_title = Text(os.path.basename(self._file_path))
|
|
817
|
+
panel.border_subtitle = render_text(widget_localizer(self), _FILE_EDIT_ERROR.bind())
|
|
818
|
+
self.add_class("-error")
|
|
819
|
+
with suppress(Exception):
|
|
820
|
+
content = self.query_one("#ft-content")
|
|
821
|
+
content.mount(Static(Text(error)))
|
|
822
|
+
self.add_class("-done")
|
|
823
|
+
self._show_tool_copy_button()
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
# ---------------------------------------------------------------------------
|
|
827
|
+
# EditFileToolCall — split diff view
|
|
828
|
+
# ---------------------------------------------------------------------------
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
class EditFileToolCall(_FileToolCall):
|
|
832
|
+
"""Renderer for ``edit_file`` — full-file split DiffView."""
|
|
833
|
+
|
|
834
|
+
def _render_spinner(self) -> Text:
|
|
835
|
+
t = Text()
|
|
836
|
+
t.append(f"{self._SPINNERS[self._spin_idx]} ", style="yellow")
|
|
837
|
+
t.append(render_str(widget_localizer(self), _FILE_EDIT_EDITING.bind()), style="yellow")
|
|
838
|
+
return t
|
|
839
|
+
|
|
840
|
+
def _build_diff(self) -> Widget | Static | None:
|
|
841
|
+
from chrys.app.tui.widgets.diff_view.inline import InlineUnifiedDiffLines
|
|
842
|
+
|
|
843
|
+
after = self._get_after_content()
|
|
844
|
+
if not self._before_content and not after:
|
|
845
|
+
return Static(Text(self.result_text))
|
|
846
|
+
path = self._file_path
|
|
847
|
+
return InlineUnifiedDiffLines(
|
|
848
|
+
path,
|
|
849
|
+
path,
|
|
850
|
+
self._before_content,
|
|
851
|
+
after,
|
|
852
|
+
auto_height=True,
|
|
853
|
+
max_display_lines=_EDIT_FILE_INLINE_MAX_DISPLAY_LINES,
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
def _update_title(self, result: str, *, defer_snapshot: bool = False) -> None:
|
|
857
|
+
panel = self.query_one("#ft-panel")
|
|
858
|
+
panel.border_title = Text(os.path.basename(self._file_path))
|
|
859
|
+
if defer_snapshot:
|
|
860
|
+
panel.border_subtitle = render_text(widget_localizer(self), _FILE_EDIT_CHANGES.bind())
|
|
861
|
+
return
|
|
862
|
+
# Compute counts cheaply via difflib without creating a full DiffView.
|
|
863
|
+
# The DiffView for display is built separately in _build_diff().
|
|
864
|
+
import difflib
|
|
865
|
+
|
|
866
|
+
after = self._get_after_content()
|
|
867
|
+
before_lines = self._before_content.splitlines()
|
|
868
|
+
after_lines = after.splitlines()
|
|
869
|
+
sm = difflib.SequenceMatcher(lambda c: c in {" ", "\t"}, before_lines, after_lines, autojunk=True)
|
|
870
|
+
additions = 0
|
|
871
|
+
removals = 0
|
|
872
|
+
for tag, i1, i2, j1, j2 in sm.get_opcodes():
|
|
873
|
+
if tag == "delete":
|
|
874
|
+
removals += i2 - i1
|
|
875
|
+
elif tag == "replace":
|
|
876
|
+
additions += j2 - j1
|
|
877
|
+
removals += i2 - i1
|
|
878
|
+
elif tag == "insert":
|
|
879
|
+
additions += j2 - j1
|
|
880
|
+
panel.border_subtitle = render_text(
|
|
881
|
+
widget_localizer(self),
|
|
882
|
+
_FILE_EDIT_CHANGE_COUNTS.bind(additions=additions, removals=removals),
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
# ---------------------------------------------------------------------------
|
|
887
|
+
# WriteFileToolCall — unified diff view
|
|
888
|
+
# ---------------------------------------------------------------------------
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
class WriteFileToolCall(_FileToolCall):
|
|
892
|
+
"""Renderer for ``write_file`` — unified DiffView showing new content."""
|
|
893
|
+
|
|
894
|
+
def _label_suffix(self) -> str:
|
|
895
|
+
overwrite = self.args.get("overwrite", False)
|
|
896
|
+
return render_str(widget_localizer(self), _FILE_EDIT_OVERWRITE.bind()) if overwrite else ""
|
|
897
|
+
|
|
898
|
+
_PREVIEW_LINES = 10
|
|
899
|
+
|
|
900
|
+
def _build_diff(self) -> Widget | Static | None:
|
|
901
|
+
from chrys.app.tui.widgets.diff_view.inline import InlineUnifiedDiffLines
|
|
902
|
+
|
|
903
|
+
after = self._get_after_content()
|
|
904
|
+
if not after:
|
|
905
|
+
return Static(Text(self.result_text)) if self.result_text else None
|
|
906
|
+
preview_text = _file_preview.write_file_preview_text(
|
|
907
|
+
after,
|
|
908
|
+
max_lines=self._PREVIEW_LINES,
|
|
909
|
+
max_line_chars=_file_preview.WRITE_FILE_PREVIEW_MAX_LINE_CHARS,
|
|
910
|
+
)
|
|
911
|
+
path = self._file_path
|
|
912
|
+
return InlineUnifiedDiffLines(path, path, "", preview_text, max_display_lines=10)
|
|
913
|
+
|
|
914
|
+
def _update_title(self, result: str, *, defer_snapshot: bool = False) -> None:
|
|
915
|
+
panel = self.query_one("#ft-panel")
|
|
916
|
+
panel.border_title = Text(os.path.basename(self._file_path))
|
|
917
|
+
if defer_snapshot and _WRITE_FILE_RESULT_LINES_RE.search(result) is None:
|
|
918
|
+
total_lines = "?"
|
|
919
|
+
else:
|
|
920
|
+
after = "" if defer_snapshot else self._get_after_content()
|
|
921
|
+
total_lines = _write_file_line_count(result, after)
|
|
922
|
+
panel.border_subtitle = render_text(
|
|
923
|
+
widget_localizer(self),
|
|
924
|
+
_FILE_EDIT_LINES_WRITTEN.bind(lines=total_lines),
|
|
925
|
+
)
|