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,625 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""SnapshotStore — content-addressable blob persistence for file snapshots.
|
|
4
|
+
|
|
5
|
+
Stores original file content blobs under the session directory, keyed
|
|
6
|
+
by SHA-256 hash. Identical files (across different turns or paths)
|
|
7
|
+
share storage automatically.
|
|
8
|
+
|
|
9
|
+
Blob writes are gated by a :class:`SnapshotPolicy` — oversized or
|
|
10
|
+
binary (non-text) file contents are not backed up. The mutation
|
|
11
|
+
metadata is still recorded upstream; only the content blob is withheld
|
|
12
|
+
(marked via :class:`SnapshotSkipReason`).
|
|
13
|
+
|
|
14
|
+
Layout::
|
|
15
|
+
|
|
16
|
+
{session_dir}/mutations/{sha256_hex}
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import contextlib
|
|
22
|
+
import errno
|
|
23
|
+
import hashlib
|
|
24
|
+
import logging
|
|
25
|
+
import os
|
|
26
|
+
import shutil
|
|
27
|
+
import stat
|
|
28
|
+
import uuid
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import TYPE_CHECKING, Final
|
|
32
|
+
|
|
33
|
+
from chrys.foundation.text.encoding import EncodingDetector
|
|
34
|
+
from chrys.service.mutations.types import FileSnapshot, RestoreOutcome, RestoreResult, SnapshotSkipReason
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from chrys.foundation.config.settings import Settings
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
DEFAULT_MAX_SNAPSHOT_FILE_BYTES: Final[int] = 50 * 1024 * 1024
|
|
42
|
+
"""Default per-file cap (50 MiB) on mutation backup blobs."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def on_disk_link_kind(path: str) -> bool | None:
|
|
46
|
+
"""The file-vs-directory kind of the link *itself*, where links are typed.
|
|
47
|
+
|
|
48
|
+
Windows reparse points carry the kind (dangling links included);
|
|
49
|
+
POSIX links are untyped, so there is nothing to report — ``None``,
|
|
50
|
+
never a value inferred from the current target.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
st = os.lstat(path)
|
|
54
|
+
except OSError:
|
|
55
|
+
return None
|
|
56
|
+
attrs = getattr(st, "st_file_attributes", None)
|
|
57
|
+
if attrs is None:
|
|
58
|
+
return None
|
|
59
|
+
return bool(attrs & stat.FILE_ATTRIBUTE_DIRECTORY)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def link_targets_directory(path: str) -> bool:
|
|
63
|
+
"""Whether ``path`` is a symlink of the directory kind.
|
|
64
|
+
|
|
65
|
+
Windows types the link itself (``FILE_ATTRIBUTE_DIRECTORY`` on the
|
|
66
|
+
reparse point, present even when the link dangles) and demands the
|
|
67
|
+
same answer back at creation time. POSIX links are untyped, so the
|
|
68
|
+
fallback consults the resolved target — a dangling link reads as
|
|
69
|
+
False there, which is harmless because POSIX creation ignores the
|
|
70
|
+
flag entirely.
|
|
71
|
+
"""
|
|
72
|
+
if not os.path.islink(path):
|
|
73
|
+
return False
|
|
74
|
+
kind = on_disk_link_kind(path)
|
|
75
|
+
if kind is not None:
|
|
76
|
+
return kind
|
|
77
|
+
return os.path.isdir(path)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def link_target_kind(link_path: str, target_text: str) -> bool | None:
|
|
81
|
+
"""Best-effort file-vs-directory kind of a link's *target text*.
|
|
82
|
+
|
|
83
|
+
Anchors a relative target at the link's parent (the way the platform
|
|
84
|
+
resolves it) and reports what sits there now: True for a directory,
|
|
85
|
+
False for any other entry, None when the destination is absent — the
|
|
86
|
+
kind is then unprovable and must not be asserted either way.
|
|
87
|
+
"""
|
|
88
|
+
resolved = target_text if os.path.isabs(target_text) else os.path.join(os.path.dirname(link_path), target_text)
|
|
89
|
+
if os.path.isdir(resolved):
|
|
90
|
+
return True
|
|
91
|
+
if os.path.lexists(resolved):
|
|
92
|
+
return False
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class SnapshotPolicy:
|
|
98
|
+
"""Decides which file contents may be persisted as backup blobs.
|
|
99
|
+
|
|
100
|
+
Applied by :class:`SnapshotStore` at every blob-write entry point,
|
|
101
|
+
so all mutation sources (file tools, shell scan, git calibration)
|
|
102
|
+
are gated uniformly. Skipped files keep their mutation *records*;
|
|
103
|
+
they just have no content backup (no diff rendering, no rollback
|
|
104
|
+
restore for that side).
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
max_file_bytes: int = DEFAULT_MAX_SNAPSHOT_FILE_BYTES
|
|
108
|
+
"""Per-file size cap in bytes. ``<= 0`` disables the size cap."""
|
|
109
|
+
|
|
110
|
+
skip_binary: bool = True
|
|
111
|
+
"""Whether binary (non-text) file contents are excluded from backup."""
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_settings(cls, settings: Settings) -> SnapshotPolicy:
|
|
115
|
+
"""Build the policy from application :class:`Settings`."""
|
|
116
|
+
return cls(
|
|
117
|
+
max_file_bytes=settings.mutation_snapshot_max_file_mb * 1024 * 1024,
|
|
118
|
+
skip_binary=settings.mutation_snapshot_skip_binary,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def classify_size(self, size: int) -> SnapshotSkipReason | None:
|
|
122
|
+
"""Size-only gate — usable from a bare ``stat`` before any read."""
|
|
123
|
+
if self.max_file_bytes > 0 and size > self.max_file_bytes:
|
|
124
|
+
return SnapshotSkipReason.TOO_LARGE
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def classify_sample(self, sample: bytes) -> SnapshotSkipReason | None:
|
|
128
|
+
"""Content gate on an in-memory sample (prefix is sufficient)."""
|
|
129
|
+
if self.skip_binary and EncodingDetector.looks_binary(sample):
|
|
130
|
+
return SnapshotSkipReason.BINARY
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def classify_data(self, data: bytes) -> SnapshotSkipReason | None:
|
|
134
|
+
"""Full gate for content already held in memory."""
|
|
135
|
+
return self.classify_size(len(data)) or self.classify_sample(data)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@dataclass(frozen=True)
|
|
139
|
+
class BlobSaveResult:
|
|
140
|
+
"""Outcome of a blob-write attempt.
|
|
141
|
+
|
|
142
|
+
``content_hash`` is ``None`` when nothing was persisted — either the
|
|
143
|
+
file was confirmed missing (``skip_reason`` is ``None`` too), or the
|
|
144
|
+
content was withheld/unreadable (``skip_reason`` says why).
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
content_hash: str | None
|
|
148
|
+
skip_reason: SnapshotSkipReason | None = None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass(frozen=True)
|
|
152
|
+
class FileProbe:
|
|
153
|
+
"""An endpoint's existence and content identity, without storing a backup blob."""
|
|
154
|
+
|
|
155
|
+
existed: bool
|
|
156
|
+
content_hash: str | None = None
|
|
157
|
+
skip_reason: SnapshotSkipReason | None = None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class SnapshotStore:
|
|
161
|
+
"""Persists original file content blobs to disk.
|
|
162
|
+
|
|
163
|
+
Content-addressable: blobs are named by their SHA-256 hash, so
|
|
164
|
+
identical files deduplicate automatically.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
def __init__(self, session_dir: Path, *, policy: SnapshotPolicy | None = None) -> None:
|
|
168
|
+
self._dir = session_dir / "mutations"
|
|
169
|
+
self._policy = policy if policy is not None else SnapshotPolicy()
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def mutations_dir(self) -> Path:
|
|
173
|
+
"""The directory where blobs are stored."""
|
|
174
|
+
return self._dir
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def policy(self) -> SnapshotPolicy:
|
|
178
|
+
"""The blob-write gate applied by this store."""
|
|
179
|
+
return self._policy
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def content_hash(data: bytes) -> str:
|
|
183
|
+
"""SHA-256 hex digest of raw bytes."""
|
|
184
|
+
return hashlib.sha256(data).hexdigest()
|
|
185
|
+
|
|
186
|
+
def probe(self, path: str) -> FileProbe:
|
|
187
|
+
"""Hash the current endpoint with bounded reads and this store's snapshot policy.
|
|
188
|
+
|
|
189
|
+
Symlinks hash their target text, as snapshots do. Unreadable endpoints
|
|
190
|
+
conservatively count as existing; only confirmed absence allows deletion.
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
entry = os.lstat(path)
|
|
194
|
+
except ValueError:
|
|
195
|
+
return FileProbe(False)
|
|
196
|
+
except OSError as exc:
|
|
197
|
+
if exc.errno in (errno.ENOENT, errno.ENOTDIR):
|
|
198
|
+
return FileProbe(False)
|
|
199
|
+
return FileProbe(True, skip_reason=SnapshotSkipReason.UNREADABLE)
|
|
200
|
+
try:
|
|
201
|
+
if stat.S_ISLNK(entry.st_mode):
|
|
202
|
+
return FileProbe(True, self.content_hash(os.fsencode(os.readlink(path))))
|
|
203
|
+
if not stat.S_ISREG(entry.st_mode):
|
|
204
|
+
return FileProbe(True, skip_reason=SnapshotSkipReason.UNREADABLE)
|
|
205
|
+
skip = self._policy.classify_size(entry.st_size)
|
|
206
|
+
if skip is not None:
|
|
207
|
+
return FileProbe(True, skip_reason=skip)
|
|
208
|
+
digest = hashlib.sha256()
|
|
209
|
+
size = 0
|
|
210
|
+
with Path(path).open("rb") as stream:
|
|
211
|
+
while block := stream.read(64 * 1024):
|
|
212
|
+
skip = self._policy.classify_sample(block) if size == 0 else None
|
|
213
|
+
size += len(block)
|
|
214
|
+
skip = self._policy.classify_size(size) or skip
|
|
215
|
+
if skip is not None:
|
|
216
|
+
return FileProbe(True, skip_reason=skip)
|
|
217
|
+
digest.update(block)
|
|
218
|
+
return FileProbe(True, digest.hexdigest())
|
|
219
|
+
except OSError:
|
|
220
|
+
return FileProbe(True, skip_reason=SnapshotSkipReason.UNREADABLE)
|
|
221
|
+
|
|
222
|
+
def save(self, path: str, period_index: int) -> FileSnapshot:
|
|
223
|
+
"""Snapshot a file's current content before mutation.
|
|
224
|
+
|
|
225
|
+
Reads the file, stores its content as a blob, and returns
|
|
226
|
+
a :class:`FileSnapshot`. If the file does not exist, returns
|
|
227
|
+
a snapshot with ``existed=False`` (no blob written). If the
|
|
228
|
+
content is withheld by :class:`SnapshotPolicy` (too large /
|
|
229
|
+
binary), returns ``existed=True`` with ``content_hash=None``
|
|
230
|
+
and ``skip_reason`` set — oversized files are rejected from a
|
|
231
|
+
bare ``stat`` without ever being read into memory. Unreadable or
|
|
232
|
+
unsupported endpoints use ``UNREADABLE`` and conservatively keep
|
|
233
|
+
``existed=True`` so rollback can never turn a failed probe into deletion.
|
|
234
|
+
|
|
235
|
+
A symlink entry (including a dangling one or a link to a
|
|
236
|
+
directory) is snapshotted as the link itself: the blob holds
|
|
237
|
+
its target text and ``is_symlink`` is set, so restore can
|
|
238
|
+
recreate the link rather than materializing its destination.
|
|
239
|
+
"""
|
|
240
|
+
norm = os.path.normpath(os.path.abspath(path))
|
|
241
|
+
try:
|
|
242
|
+
entry = os.lstat(norm)
|
|
243
|
+
except ValueError:
|
|
244
|
+
# Invalid filesystem identities (e.g. embedded NUL) cannot exist.
|
|
245
|
+
return FileSnapshot(path=norm, period_index=period_index, existed=False)
|
|
246
|
+
except OSError as exc:
|
|
247
|
+
if exc.errno in (errno.ENOENT, errno.ENOTDIR):
|
|
248
|
+
return FileSnapshot(path=norm, period_index=period_index, existed=False)
|
|
249
|
+
logger.warning("Failed to stat file for snapshot: %s — %s", norm, exc)
|
|
250
|
+
return self._skipped_snapshot(norm, period_index, 0, SnapshotSkipReason.UNREADABLE)
|
|
251
|
+
if stat.S_ISLNK(entry.st_mode):
|
|
252
|
+
try:
|
|
253
|
+
data = os.fsencode(os.readlink(norm))
|
|
254
|
+
except OSError as exc:
|
|
255
|
+
logger.warning("Failed to read symlink for snapshot: %s — %s", norm, exc)
|
|
256
|
+
return self._skipped_snapshot(norm, period_index, entry.st_size, SnapshotSkipReason.UNREADABLE)
|
|
257
|
+
h = self.content_hash(data)
|
|
258
|
+
blob_path = self._dir / h
|
|
259
|
+
if not blob_path.exists():
|
|
260
|
+
self._dir.mkdir(parents=True, exist_ok=True)
|
|
261
|
+
blob_path.write_bytes(data)
|
|
262
|
+
return FileSnapshot(
|
|
263
|
+
path=norm,
|
|
264
|
+
period_index=period_index,
|
|
265
|
+
existed=True,
|
|
266
|
+
content_hash=h,
|
|
267
|
+
size=len(data),
|
|
268
|
+
is_symlink=True,
|
|
269
|
+
symlink_target_is_dir=link_targets_directory(norm),
|
|
270
|
+
)
|
|
271
|
+
if not stat.S_ISREG(entry.st_mode):
|
|
272
|
+
return self._skipped_snapshot(norm, period_index, entry.st_size, SnapshotSkipReason.UNREADABLE)
|
|
273
|
+
size = entry.st_size
|
|
274
|
+
|
|
275
|
+
skip = self._policy.classify_size(size)
|
|
276
|
+
if skip is not None:
|
|
277
|
+
return self._skipped_snapshot(norm, period_index, size, skip)
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
data = Path(norm).read_bytes()
|
|
281
|
+
except OSError as exc:
|
|
282
|
+
logger.warning("Failed to read file for snapshot: %s — %s", norm, exc)
|
|
283
|
+
return self._skipped_snapshot(norm, period_index, size, SnapshotSkipReason.UNREADABLE)
|
|
284
|
+
|
|
285
|
+
skip = self._policy.classify_data(data)
|
|
286
|
+
if skip is not None:
|
|
287
|
+
return self._skipped_snapshot(norm, period_index, len(data), skip)
|
|
288
|
+
|
|
289
|
+
h = self.content_hash(data)
|
|
290
|
+
blob_path = self._dir / h
|
|
291
|
+
if not blob_path.exists():
|
|
292
|
+
self._dir.mkdir(parents=True, exist_ok=True)
|
|
293
|
+
blob_path.write_bytes(data)
|
|
294
|
+
|
|
295
|
+
return FileSnapshot(
|
|
296
|
+
path=norm,
|
|
297
|
+
period_index=period_index,
|
|
298
|
+
existed=True,
|
|
299
|
+
content_hash=h,
|
|
300
|
+
size=len(data),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def _skipped_snapshot(self, norm: str, period_index: int, size: int, skip: SnapshotSkipReason) -> FileSnapshot:
|
|
304
|
+
"""Build an ``existed=True`` snapshot whose content was withheld."""
|
|
305
|
+
logger.debug("Snapshot content withheld for %s (%s, %d bytes)", norm, skip.value, size)
|
|
306
|
+
return FileSnapshot(path=norm, period_index=period_index, existed=True, size=size, skip_reason=skip)
|
|
307
|
+
|
|
308
|
+
def read_content(self, snapshot: FileSnapshot) -> bytes | None:
|
|
309
|
+
"""Read stored blob content for a snapshot.
|
|
310
|
+
|
|
311
|
+
Returns ``None`` if the file didn't exist at snapshot time or
|
|
312
|
+
the blob is missing from disk.
|
|
313
|
+
"""
|
|
314
|
+
if not snapshot.existed or snapshot.content_hash is None:
|
|
315
|
+
return None
|
|
316
|
+
blob_path = self._dir / snapshot.content_hash
|
|
317
|
+
if not blob_path.exists():
|
|
318
|
+
return None
|
|
319
|
+
return blob_path.read_bytes()
|
|
320
|
+
|
|
321
|
+
def restore(self, snapshot: FileSnapshot) -> RestoreResult:
|
|
322
|
+
"""Restore a file to its snapshotted pre-turn state.
|
|
323
|
+
|
|
324
|
+
CONTRACT (agreed design, keep this intact unless you're renegotiating it):
|
|
325
|
+
|
|
326
|
+
The rollback modal already shows the user exactly what each
|
|
327
|
+
checked path will look like after the revert (diff-view "after"
|
|
328
|
+
side = snapshot target state). The user confirms by pressing
|
|
329
|
+
the discard button. From that point on, revert is unconditional:
|
|
330
|
+
bring each selected file to the target state regardless of what
|
|
331
|
+
sits on disk now. We deliberately do NOT drift-check or prompt
|
|
332
|
+
— if the user modified the file manually after the agent's turn,
|
|
333
|
+
they either accept the overwrite (file stays checked) or they
|
|
334
|
+
de-select it in the change-list tree. Simpler logic, guaranteed
|
|
335
|
+
outcome. A pre-press drift *warning* in the modal (badge only,
|
|
336
|
+
doesn't block) is planned as a follow-up; it belongs in the UI,
|
|
337
|
+
not here.
|
|
338
|
+
|
|
339
|
+
Behaviour by snapshot shape:
|
|
340
|
+
|
|
341
|
+
- ``existed=False``: the file was created during the rolled-back
|
|
342
|
+
turn → target state is "absent". Unlink if present; no-op if
|
|
343
|
+
already absent. Refuses to ``rmtree`` a directory that now
|
|
344
|
+
sits at the path (returns :data:`RestoreOutcome.FAILED`) — the
|
|
345
|
+
blast radius of that operation is far beyond the single path
|
|
346
|
+
the user consented to.
|
|
347
|
+
- ``existed=True``: the file existed at turn start → target
|
|
348
|
+
state is the stored blob content. Overwrites whatever is at
|
|
349
|
+
the path; creates missing parent directories. If the path is
|
|
350
|
+
currently a symlink we unlink it first so the replacement is
|
|
351
|
+
a real file (``write_bytes`` would otherwise follow the link
|
|
352
|
+
and corrupt its target). A directory at the path is likewise
|
|
353
|
+
treated as FAILED.
|
|
354
|
+
- ``is_symlink=True``: the entry was a symlink at turn start →
|
|
355
|
+
target state is a link pointing at the stored target text.
|
|
356
|
+
The replacement link is built at a temporary sibling path and
|
|
357
|
+
swapped over whatever sits at the path (file or link) with
|
|
358
|
+
``os.replace``, so a failed creation (Windows without
|
|
359
|
+
privilege, filesystems without symlink support) reports
|
|
360
|
+
FAILED with the existing entry preserved — never degraded to
|
|
361
|
+
a regular file, never destroyed. The temporary name is
|
|
362
|
+
unique per call; an existing entry at a candidate name is
|
|
363
|
+
never unlinked. A directory at the path is FAILED.
|
|
364
|
+
``symlink_target_is_dir`` is forwarded as
|
|
365
|
+
``target_is_directory`` so Windows directory links recreate
|
|
366
|
+
with the right kind (POSIX ignores it); on platforms whose
|
|
367
|
+
links are typed, NOOP additionally requires the on-disk
|
|
368
|
+
link's kind to match the recorded one — POSIX links are
|
|
369
|
+
untyped, so the text match alone is NOOP there. An
|
|
370
|
+
unprovable recorded kind (``None``) is inferred from what
|
|
371
|
+
the target text resolves to at restore time.
|
|
372
|
+
|
|
373
|
+
Returns a :class:`RestoreResult` — APPLIED when we mutated disk,
|
|
374
|
+
NOOP when it was already in target state, FAILED otherwise with
|
|
375
|
+
``reason`` populated.
|
|
376
|
+
"""
|
|
377
|
+
target = Path(snapshot.path)
|
|
378
|
+
|
|
379
|
+
# -- Delete branch: target state is "file does not exist".
|
|
380
|
+
if not snapshot.existed:
|
|
381
|
+
if target.is_symlink():
|
|
382
|
+
# Symlinks (even broken ones) are removed cleanly by
|
|
383
|
+
# ``unlink`` — and their ``exists()`` may be False when
|
|
384
|
+
# the link is dangling, so handle them first.
|
|
385
|
+
try:
|
|
386
|
+
target.unlink()
|
|
387
|
+
logger.debug(
|
|
388
|
+
"Rollback: removed symlink %s (created after turn %d)", snapshot.path, snapshot.period_index
|
|
389
|
+
)
|
|
390
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.APPLIED)
|
|
391
|
+
except OSError as exc:
|
|
392
|
+
logger.warning("Rollback: failed to remove symlink %s — %s", snapshot.path, exc)
|
|
393
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=str(exc))
|
|
394
|
+
if not target.exists():
|
|
395
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.NOOP)
|
|
396
|
+
if target.is_dir():
|
|
397
|
+
reason = "refusing to remove directory at path (expected file)"
|
|
398
|
+
logger.warning("Rollback: %s for %s", reason, snapshot.path)
|
|
399
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=reason)
|
|
400
|
+
try:
|
|
401
|
+
target.unlink()
|
|
402
|
+
logger.debug("Rollback: deleted %s (created after turn %d)", snapshot.path, snapshot.period_index)
|
|
403
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.APPLIED)
|
|
404
|
+
except OSError as exc:
|
|
405
|
+
logger.warning("Rollback: failed to delete %s — %s", snapshot.path, exc)
|
|
406
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=str(exc))
|
|
407
|
+
|
|
408
|
+
# -- Write branch: target state is the snapshotted blob content.
|
|
409
|
+
content = self.read_content(snapshot)
|
|
410
|
+
if content is None:
|
|
411
|
+
# Rollback plans exclude non-restorable snapshots, so a
|
|
412
|
+
# policy-skipped snapshot reaching here is defense-in-depth.
|
|
413
|
+
if snapshot.skip_reason is not None:
|
|
414
|
+
reason = f"snapshot content not stored ({snapshot.skip_reason.value})"
|
|
415
|
+
else:
|
|
416
|
+
reason = f"blob missing (hash={snapshot.content_hash})"
|
|
417
|
+
logger.warning("Rollback: %s for %s", reason, snapshot.path)
|
|
418
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=reason)
|
|
419
|
+
|
|
420
|
+
# -- Symlink branch: target state is "a link pointing at the
|
|
421
|
+
# stored target text" — never a regular file with that text.
|
|
422
|
+
if snapshot.is_symlink:
|
|
423
|
+
target_text = os.fsdecode(content)
|
|
424
|
+
recorded_kind = snapshot.symlink_target_is_dir
|
|
425
|
+
if target.is_symlink():
|
|
426
|
+
try:
|
|
427
|
+
if os.readlink(target) == target_text:
|
|
428
|
+
# Where links are typed (Windows), the same text
|
|
429
|
+
# can exist as either kind — NOOP also requires
|
|
430
|
+
# the on-disk kind to match the recorded one.
|
|
431
|
+
# POSIX links are untyped: the text match is the
|
|
432
|
+
# whole identity, and rebuilding could not change
|
|
433
|
+
# anything anyway.
|
|
434
|
+
disk_kind = on_disk_link_kind(str(target))
|
|
435
|
+
if recorded_kind is None or disk_kind is None or disk_kind == recorded_kind:
|
|
436
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.NOOP)
|
|
437
|
+
except OSError:
|
|
438
|
+
pass
|
|
439
|
+
elif target.is_dir():
|
|
440
|
+
reason = "refusing to overwrite directory at path (expected symlink)"
|
|
441
|
+
logger.warning("Rollback: %s for %s", reason, snapshot.path)
|
|
442
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=reason)
|
|
443
|
+
if recorded_kind is None:
|
|
444
|
+
# Kind unprovable at record time: infer from whatever the
|
|
445
|
+
# target text resolves to now (POSIX ignores it anyway).
|
|
446
|
+
recorded_kind = link_target_kind(str(target), target_text)
|
|
447
|
+
# Build the replacement link beside the target and swap it in
|
|
448
|
+
# atomically — a failed creation (Windows without privilege,
|
|
449
|
+
# filesystems without symlink support) must leave whatever
|
|
450
|
+
# currently sits at the path intact. The temporary name is
|
|
451
|
+
# unique per call: a colliding name belongs to someone else
|
|
452
|
+
# and is never unlinked.
|
|
453
|
+
tmp: Path | None = None
|
|
454
|
+
try:
|
|
455
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
456
|
+
for _ in range(10):
|
|
457
|
+
candidate = target.parent / f".{target.name}.{uuid.uuid4().hex}.chrys-link-tmp"
|
|
458
|
+
try:
|
|
459
|
+
os.symlink(target_text, candidate, target_is_directory=bool(recorded_kind))
|
|
460
|
+
except FileExistsError:
|
|
461
|
+
continue
|
|
462
|
+
tmp = candidate
|
|
463
|
+
break
|
|
464
|
+
except OSError as exc:
|
|
465
|
+
logger.warning("Rollback: failed to recreate symlink %s — %s", snapshot.path, exc)
|
|
466
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=str(exc))
|
|
467
|
+
if tmp is None:
|
|
468
|
+
reason = "could not allocate a unique temporary link name"
|
|
469
|
+
logger.warning("Rollback: %s for %s", reason, snapshot.path)
|
|
470
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=reason)
|
|
471
|
+
try:
|
|
472
|
+
os.replace(tmp, target)
|
|
473
|
+
logger.debug("Rollback: recreated symlink %s -> %s", snapshot.path, target_text)
|
|
474
|
+
except OSError as exc:
|
|
475
|
+
with contextlib.suppress(OSError):
|
|
476
|
+
os.unlink(tmp)
|
|
477
|
+
logger.warning("Rollback: failed to swap in symlink %s — %s", snapshot.path, exc)
|
|
478
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=str(exc))
|
|
479
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.APPLIED)
|
|
480
|
+
|
|
481
|
+
# Guard against a directory sitting where we'd write the file —
|
|
482
|
+
# ``write_bytes`` would raise ``IsADirectoryError`` and we do
|
|
483
|
+
# NOT want to rmtree a user directory as collateral damage.
|
|
484
|
+
if target.is_dir() and not target.is_symlink():
|
|
485
|
+
reason = "refusing to overwrite directory at path (expected file)"
|
|
486
|
+
logger.warning("Rollback: %s for %s", reason, snapshot.path)
|
|
487
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=reason)
|
|
488
|
+
|
|
489
|
+
# No-op short-circuit: if disk already matches the target blob,
|
|
490
|
+
# skip the write. Avoids unnecessary mtime churn and makes
|
|
491
|
+
# repeated rollbacks cheap. Symlinks always take the slow path
|
|
492
|
+
# since a link pointing elsewhere isn't "the same content" even
|
|
493
|
+
# if it resolves to matching bytes — we still want to replace
|
|
494
|
+
# the link with a real file below.
|
|
495
|
+
if not target.is_symlink() and target.is_file():
|
|
496
|
+
try:
|
|
497
|
+
if self.content_hash(target.read_bytes()) == snapshot.content_hash:
|
|
498
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.NOOP)
|
|
499
|
+
except OSError:
|
|
500
|
+
# Fall through to the write path — it will report a
|
|
501
|
+
# cleaner FAILED if the write itself can't proceed.
|
|
502
|
+
pass
|
|
503
|
+
|
|
504
|
+
# If the path is currently a symlink, unlink it so the write
|
|
505
|
+
# below lands on a real file at ``target`` rather than writing
|
|
506
|
+
# through the link into some unrelated destination.
|
|
507
|
+
if target.is_symlink():
|
|
508
|
+
try:
|
|
509
|
+
target.unlink()
|
|
510
|
+
except OSError as exc:
|
|
511
|
+
logger.warning("Rollback: failed to remove symlink before restore: %s — %s", snapshot.path, exc)
|
|
512
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=str(exc))
|
|
513
|
+
|
|
514
|
+
try:
|
|
515
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
516
|
+
target.write_bytes(content)
|
|
517
|
+
logger.debug("Rollback: restored %s to turn %d state", snapshot.path, snapshot.period_index)
|
|
518
|
+
except OSError as exc:
|
|
519
|
+
logger.warning("Rollback: failed to restore %s — %s", snapshot.path, exc)
|
|
520
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.FAILED, reason=str(exc))
|
|
521
|
+
return RestoreResult(path=snapshot.path, outcome=RestoreOutcome.APPLIED)
|
|
522
|
+
|
|
523
|
+
def save_blob(self, path: str) -> BlobSaveResult:
|
|
524
|
+
"""Read a file and save its content as a blob.
|
|
525
|
+
|
|
526
|
+
Returns a :class:`BlobSaveResult` — ``content_hash=None`` if the
|
|
527
|
+
file is confirmed missing. Unreadable content carries ``UNREADABLE``;
|
|
528
|
+
content withheld by :class:`SnapshotPolicy` also sets ``skip_reason`` (the
|
|
529
|
+
size gate runs on a bare ``stat`` so oversized files are never
|
|
530
|
+
read into memory). Unlike :meth:`save`, this does not create a
|
|
531
|
+
``FileSnapshot`` record — it only stores the blob for later
|
|
532
|
+
retrieval via :meth:`read_blob`.
|
|
533
|
+
|
|
534
|
+
A symlink entry hashes as its target text (the Git symlink-blob
|
|
535
|
+
representation), never as the destination's content — the
|
|
536
|
+
entry's identity is the link.
|
|
537
|
+
"""
|
|
538
|
+
norm = os.path.normpath(os.path.abspath(path))
|
|
539
|
+
try:
|
|
540
|
+
entry = os.lstat(norm)
|
|
541
|
+
except ValueError:
|
|
542
|
+
return BlobSaveResult(content_hash=None)
|
|
543
|
+
except OSError as exc:
|
|
544
|
+
if exc.errno in (errno.ENOENT, errno.ENOTDIR):
|
|
545
|
+
return BlobSaveResult(content_hash=None)
|
|
546
|
+
logger.warning("Failed to stat file for blob: %s — %s", norm, exc)
|
|
547
|
+
return BlobSaveResult(content_hash=None, skip_reason=SnapshotSkipReason.UNREADABLE)
|
|
548
|
+
if stat.S_ISLNK(entry.st_mode):
|
|
549
|
+
try:
|
|
550
|
+
return self.save_data_as_blob(os.fsencode(os.readlink(norm)))
|
|
551
|
+
except OSError as exc:
|
|
552
|
+
logger.warning("Failed to read symlink for blob: %s — %s", norm, exc)
|
|
553
|
+
return BlobSaveResult(content_hash=None, skip_reason=SnapshotSkipReason.UNREADABLE)
|
|
554
|
+
if not stat.S_ISREG(entry.st_mode):
|
|
555
|
+
return BlobSaveResult(content_hash=None, skip_reason=SnapshotSkipReason.UNREADABLE)
|
|
556
|
+
size = entry.st_size
|
|
557
|
+
skip = self._policy.classify_size(size)
|
|
558
|
+
if skip is not None:
|
|
559
|
+
logger.debug("Blob content withheld for %s (%s, %d bytes)", norm, skip.value, size)
|
|
560
|
+
return BlobSaveResult(content_hash=None, skip_reason=skip)
|
|
561
|
+
try:
|
|
562
|
+
data = Path(norm).read_bytes()
|
|
563
|
+
except OSError as exc:
|
|
564
|
+
logger.warning("Failed to read file for blob: %s — %s", norm, exc)
|
|
565
|
+
return BlobSaveResult(content_hash=None, skip_reason=SnapshotSkipReason.UNREADABLE)
|
|
566
|
+
skip = self._policy.classify_data(data)
|
|
567
|
+
if skip is not None:
|
|
568
|
+
logger.debug("Blob content withheld for %s (%s, %d bytes)", norm, skip.value, len(data))
|
|
569
|
+
return BlobSaveResult(content_hash=None, skip_reason=skip)
|
|
570
|
+
h = self.content_hash(data)
|
|
571
|
+
blob_path = self._dir / h
|
|
572
|
+
if not blob_path.exists():
|
|
573
|
+
self._dir.mkdir(parents=True, exist_ok=True)
|
|
574
|
+
blob_path.write_bytes(data)
|
|
575
|
+
return BlobSaveResult(content_hash=h)
|
|
576
|
+
|
|
577
|
+
def save_data_as_blob(self, data: bytes) -> BlobSaveResult:
|
|
578
|
+
"""Store arbitrary bytes as a blob, subject to :class:`SnapshotPolicy`.
|
|
579
|
+
|
|
580
|
+
Used by git calibration to store HEAD content for files that
|
|
581
|
+
were clean at turn start but became dirty during the turn.
|
|
582
|
+
"""
|
|
583
|
+
skip = self._policy.classify_data(data)
|
|
584
|
+
if skip is not None:
|
|
585
|
+
logger.debug("Blob content withheld for in-memory data (%s, %d bytes)", skip.value, len(data))
|
|
586
|
+
return BlobSaveResult(content_hash=None, skip_reason=skip)
|
|
587
|
+
h = self.content_hash(data)
|
|
588
|
+
blob_path = self._dir / h
|
|
589
|
+
if not blob_path.exists():
|
|
590
|
+
self._dir.mkdir(parents=True, exist_ok=True)
|
|
591
|
+
blob_path.write_bytes(data)
|
|
592
|
+
return BlobSaveResult(content_hash=h)
|
|
593
|
+
|
|
594
|
+
def read_blob(self, content_hash: str) -> bytes | None:
|
|
595
|
+
"""Read blob content by its SHA-256 hash.
|
|
596
|
+
|
|
597
|
+
Returns ``None`` if the blob is not found on disk.
|
|
598
|
+
"""
|
|
599
|
+
blob_path = self._dir / content_hash
|
|
600
|
+
if not blob_path.exists():
|
|
601
|
+
return None
|
|
602
|
+
return blob_path.read_bytes()
|
|
603
|
+
|
|
604
|
+
# -- Cleanup --
|
|
605
|
+
|
|
606
|
+
def remove_blobs(self, hashes: set[str]) -> None:
|
|
607
|
+
"""Delete specific blob files by their content hashes.
|
|
608
|
+
|
|
609
|
+
Silently skips hashes whose blob files do not exist on disk.
|
|
610
|
+
"""
|
|
611
|
+
for h in hashes:
|
|
612
|
+
blob_path = self._dir / h
|
|
613
|
+
if blob_path.is_file():
|
|
614
|
+
try:
|
|
615
|
+
blob_path.unlink()
|
|
616
|
+
except OSError as exc:
|
|
617
|
+
logger.warning("Failed to delete blob %s — %s", h, exc)
|
|
618
|
+
|
|
619
|
+
def remove_all(self) -> None:
|
|
620
|
+
"""Delete the entire mutations blob directory.
|
|
621
|
+
|
|
622
|
+
Called when a session is deleted to reclaim disk space.
|
|
623
|
+
"""
|
|
624
|
+
if self._dir.is_dir():
|
|
625
|
+
shutil.rmtree(self._dir, ignore_errors=True)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Tool-name sets used by mutation tracking and event middleware."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
_FILE_TOOLS = frozenset({"edit_file", "write_file"})
|
|
8
|
+
"""Tool names whose target files should be snapshotted before/after execution."""
|
|
9
|
+
|
|
10
|
+
_IMPLICIT_WRITE_TOOLS = frozenset({"run_skill_script"})
|
|
11
|
+
"""Tools that run subprocesses which may implicitly modify workspace files.
|
|
12
|
+
|
|
13
|
+
These get git calibration (before/after diff) but NOT heuristic shell
|
|
14
|
+
scanning (no command string to parse). Shell tools get both.
|
|
15
|
+
"""
|