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,918 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Engine-internal companion module for rollback orchestration.
|
|
4
|
+
|
|
5
|
+
Pure rollback-file helpers live in :mod:`chrys.service.mutations.snapshot_files`.
|
|
6
|
+
This module owns the engine-facing workflow: validate the requested
|
|
7
|
+
target, optionally revert file mutations, promote a saved snapshot, and
|
|
8
|
+
reload the session while suppressing stale in-memory saves.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import contextlib
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
from collections.abc import Awaitable, Callable
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import TYPE_CHECKING, Any
|
|
21
|
+
|
|
22
|
+
from chrys.foundation.events.types import Error, RollbackResult, SessionRestore, UserRollback, Warning
|
|
23
|
+
from chrys.foundation.i18n import DisplayBlock, msg
|
|
24
|
+
from chrys.foundation.util.async_tasks import await_task_quiescence
|
|
25
|
+
from chrys.foundation.util.lock import FileLock
|
|
26
|
+
from chrys.orchestration.engine.state.machine import EngineState
|
|
27
|
+
from chrys.service.mutations.snapshot_files import (
|
|
28
|
+
rollback_snapshot_for_target,
|
|
29
|
+
rollback_snapshot_paths,
|
|
30
|
+
snapshot_target_turn,
|
|
31
|
+
snapshot_target_turns,
|
|
32
|
+
write_rollback_snapshot,
|
|
33
|
+
)
|
|
34
|
+
from chrys.service.mutations.snapshot_files import (
|
|
35
|
+
turn_prompt_previews as collect_turn_prompt_previews,
|
|
36
|
+
)
|
|
37
|
+
from chrys.service.mutations.workspace_changes import format_partial_revert_notice, format_retained_changes_notice
|
|
38
|
+
from chrys.service.state.store import SESSION_BACKUP_FILE_NAME, SESSION_RECOVERY_FILE_NAME, recorded_surface
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from chrys.foundation.config.settings_store import SettingsHandle
|
|
42
|
+
from chrys.foundation.events.bus import EventBus
|
|
43
|
+
from chrys.orchestration.engine.run.turn_state import TurnRuntimeState
|
|
44
|
+
from chrys.orchestration.engine.session_lifecycle import SessionLifecycle
|
|
45
|
+
from chrys.orchestration.engine.state.active_session import ActiveSession
|
|
46
|
+
from chrys.orchestration.engine.state.current_agent import CurrentAgent
|
|
47
|
+
from chrys.orchestration.engine.state.lifecycle_permits import LifecyclePermits
|
|
48
|
+
from chrys.orchestration.engine.state.machine import EngineStateMachine
|
|
49
|
+
from chrys.orchestration.engine.state.session_writer import SessionWriter
|
|
50
|
+
from chrys.orchestration.engine.trajectory import TrajectoryRecorder
|
|
51
|
+
from chrys.service.mutations.workspace_changes import WorkspaceChangeTracker
|
|
52
|
+
from chrys.service.session.history import SessionHistoryManager
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger(__name__)
|
|
56
|
+
|
|
57
|
+
_ROLLBACK_NO_SESSION = msg(
|
|
58
|
+
"rollback.no_session",
|
|
59
|
+
fallback="No active session to roll back.",
|
|
60
|
+
)
|
|
61
|
+
_ROLLBACK_CONVERSATION_CHANGED = msg(
|
|
62
|
+
"rollback.conversation_changed",
|
|
63
|
+
fallback="Rollback cancelled because the conversation changed after the picker was loaded.",
|
|
64
|
+
)
|
|
65
|
+
_ROLLBACK_CONVERSATION_ADVANCED = msg(
|
|
66
|
+
"rollback.conversation_advanced",
|
|
67
|
+
fallback="Rollback cancelled because the conversation advanced from turn {expected_turn} to turn {current_turn}.",
|
|
68
|
+
)
|
|
69
|
+
_ROLLBACK_RUNTIME_CHANGED = msg(
|
|
70
|
+
"rollback.runtime_changed",
|
|
71
|
+
fallback="Rollback cancelled because the workspace or runtime changed after the picker was loaded.",
|
|
72
|
+
)
|
|
73
|
+
_ROLLBACK_RELATIVE_TURNS_INVALID = msg(
|
|
74
|
+
"rollback.relative_turns_invalid",
|
|
75
|
+
fallback="relative_turns must be positive.",
|
|
76
|
+
)
|
|
77
|
+
_ROLLBACK_TURNS_UNAVAILABLE = msg(
|
|
78
|
+
"rollback.turns_unavailable",
|
|
79
|
+
fallback="Cannot roll back {requested_turns} turns; the session currently has {current_turns}.",
|
|
80
|
+
)
|
|
81
|
+
_ROLLBACK_TARGET_TURN_INVALID = msg(
|
|
82
|
+
"rollback.target_turn_invalid",
|
|
83
|
+
fallback="target_turn must be >= 0.",
|
|
84
|
+
)
|
|
85
|
+
_ROLLBACK_TURN_UNAVAILABLE = msg(
|
|
86
|
+
"rollback.turn_unavailable",
|
|
87
|
+
fallback="Cannot roll back to turn {target_turn}; available turns: {available}",
|
|
88
|
+
)
|
|
89
|
+
_ROLLBACK_RESET_FAILED = msg(
|
|
90
|
+
"rollback.reset_failed",
|
|
91
|
+
fallback="Rollback to welcome could not reset the session because the session state is busy.",
|
|
92
|
+
)
|
|
93
|
+
_ROLLBACK_SNAPSHOT_MISSING = msg(
|
|
94
|
+
"rollback.snapshot_missing",
|
|
95
|
+
fallback="Snapshot for turn {target_turn} is missing.",
|
|
96
|
+
)
|
|
97
|
+
_ROLLBACK_SWAP_LOCKED = msg(
|
|
98
|
+
"rollback.swap_locked",
|
|
99
|
+
fallback="Timed out waiting for session lock: {detail}",
|
|
100
|
+
multiline=True,
|
|
101
|
+
)
|
|
102
|
+
_ROLLBACK_SWAP_FAILED = msg(
|
|
103
|
+
"rollback.swap_failed",
|
|
104
|
+
fallback="Failed to restore snapshot: {detail}",
|
|
105
|
+
multiline=True,
|
|
106
|
+
)
|
|
107
|
+
_ROLLBACK_SESSION_CHANGED = msg(
|
|
108
|
+
"rollback.session_changed",
|
|
109
|
+
fallback="Rollback cancelled because the active session changed.",
|
|
110
|
+
)
|
|
111
|
+
_ROLLBACK_REFUSED = msg(
|
|
112
|
+
"rollback.refused",
|
|
113
|
+
fallback="Rollback is not allowed in state {state}.",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(slots=True)
|
|
118
|
+
class _RollbackLockLease:
|
|
119
|
+
"""Idempotent owner for a pre-commit rollback write lock."""
|
|
120
|
+
|
|
121
|
+
lock: FileLock | None = None
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def held(self) -> bool:
|
|
125
|
+
"""Return whether the lease still owns its lock."""
|
|
126
|
+
return self.lock is not None
|
|
127
|
+
|
|
128
|
+
def release(self) -> None:
|
|
129
|
+
"""Release the lock once, tolerating defensive cleanup paths."""
|
|
130
|
+
lock = self.lock
|
|
131
|
+
self.lock = None
|
|
132
|
+
if lock is not None:
|
|
133
|
+
lock.release()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _read_envelope_meta(path: Path) -> dict[str, Any]:
|
|
137
|
+
"""The envelope's ``meta``; empty when the file is missing or unreadable."""
|
|
138
|
+
try:
|
|
139
|
+
envelope = json.loads(path.read_text(encoding="utf-8"))
|
|
140
|
+
except OSError, ValueError:
|
|
141
|
+
return {}
|
|
142
|
+
meta = envelope.get("meta") if isinstance(envelope, dict) else None
|
|
143
|
+
return meta if isinstance(meta, dict) else {}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _read_session_overlays(session_file: Path) -> dict[str, str]:
|
|
147
|
+
"""Capture session-scoped meta before a snapshot overwrites ``session.json``.
|
|
148
|
+
|
|
149
|
+
These describe the session, not the turns it holds, so the current
|
|
150
|
+
value wins over whatever the snapshot carried: ``custom_title`` is the
|
|
151
|
+
user's pin (or explicit clear — an empty string must survive too, or a
|
|
152
|
+
pre-clear snapshot would resurrect the pin), and ``last_surface`` names
|
|
153
|
+
the surface whose turn last worked in the session, which a rollback,
|
|
154
|
+
not being a turn, never changes. That turn may be one a crash cut
|
|
155
|
+
short, recorded only in a newer sidecar the restore after the swap
|
|
156
|
+
discards, so the surface is read as the store would read it.
|
|
157
|
+
``generated_title`` is deliberately NOT preserved: it summarizes the
|
|
158
|
+
conversation, and after a rollback the snapshot's own value is the one
|
|
159
|
+
that describes the restored turns — the newer title describes history
|
|
160
|
+
that no longer exists. Each value is captured verbatim; one the files
|
|
161
|
+
never recorded (or an unreadable envelope) yields nothing for it.
|
|
162
|
+
"""
|
|
163
|
+
meta = _read_envelope_meta(session_file)
|
|
164
|
+
overlays = {"custom_title": value} if isinstance(value := meta.get("custom_title"), str) else {}
|
|
165
|
+
surface = recorded_surface(meta, _read_envelope_meta(session_file.with_name(SESSION_RECOVERY_FILE_NAME)))
|
|
166
|
+
if surface is not None:
|
|
167
|
+
overlays["last_surface"] = surface
|
|
168
|
+
return overlays
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _reapply_session_overlays(session_file: Path, overlays: dict[str, str]) -> None:
|
|
172
|
+
"""Merge preserved session-scoped meta back into the restored snapshot, best-effort."""
|
|
173
|
+
if not overlays:
|
|
174
|
+
return
|
|
175
|
+
from chrys.foundation.platform.files import atomic_write_text
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
envelope = json.loads(session_file.read_text(encoding="utf-8"))
|
|
179
|
+
meta = envelope.get("meta") if isinstance(envelope, dict) else None
|
|
180
|
+
if not isinstance(meta, dict):
|
|
181
|
+
return
|
|
182
|
+
meta.update(overlays)
|
|
183
|
+
atomic_write_text(session_file, json.dumps(envelope, indent=2, ensure_ascii=False))
|
|
184
|
+
except OSError, ValueError:
|
|
185
|
+
logger.warning("Failed to preserve session metadata across rollback for %s", session_file, exc_info=True)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def capture_snapshot_writer(session: ActiveSession, settings_handle: SettingsHandle) -> Callable[[], None]:
|
|
189
|
+
"""Freeze snapshot metadata while deferring path resolution and file I/O."""
|
|
190
|
+
session_id = session.session_id
|
|
191
|
+
turn_number = session.turn_number
|
|
192
|
+
keep = settings_handle.settings.rollback_snapshots_keep
|
|
193
|
+
session_dir_for = session.session_dir_for
|
|
194
|
+
lock_path_for = session.session_write_lock_path
|
|
195
|
+
|
|
196
|
+
def write() -> None:
|
|
197
|
+
session_dir = session_dir_for(session_id) if session_id else None
|
|
198
|
+
lock_path = lock_path_for(session_id) if session_id else None
|
|
199
|
+
write_rollback_snapshot(
|
|
200
|
+
session_dir=session_dir,
|
|
201
|
+
session_id=session_id,
|
|
202
|
+
turn_number=turn_number,
|
|
203
|
+
keep=keep,
|
|
204
|
+
lock_path=lock_path,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return write
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class RollbackController:
|
|
211
|
+
"""Validates rollback requests and restores session and workspace state."""
|
|
212
|
+
|
|
213
|
+
def __init__(
|
|
214
|
+
self,
|
|
215
|
+
*,
|
|
216
|
+
session: ActiveSession,
|
|
217
|
+
current: CurrentAgent,
|
|
218
|
+
permits: LifecyclePermits,
|
|
219
|
+
writer: SessionWriter,
|
|
220
|
+
turn_state: TurnRuntimeState,
|
|
221
|
+
lifecycle: SessionLifecycle,
|
|
222
|
+
bus: EventBus,
|
|
223
|
+
history: SessionHistoryManager,
|
|
224
|
+
fsm: EngineStateMachine,
|
|
225
|
+
workspace_change_tracker: WorkspaceChangeTracker,
|
|
226
|
+
trajectory_recorder: TrajectoryRecorder,
|
|
227
|
+
settings_handle: SettingsHandle,
|
|
228
|
+
) -> None:
|
|
229
|
+
self._session = session
|
|
230
|
+
self._current = current
|
|
231
|
+
self._permits = permits
|
|
232
|
+
self._writer = writer
|
|
233
|
+
self._turn_state = turn_state
|
|
234
|
+
self._lifecycle = lifecycle
|
|
235
|
+
self._bus = bus
|
|
236
|
+
self._history = history
|
|
237
|
+
self._fsm = fsm
|
|
238
|
+
self._workspace_change_tracker = workspace_change_tracker
|
|
239
|
+
self._trajectory_recorder = trajectory_recorder
|
|
240
|
+
self._settings_handle = settings_handle
|
|
241
|
+
|
|
242
|
+
async def begin_rollback_projection(
|
|
243
|
+
self,
|
|
244
|
+
*,
|
|
245
|
+
session_id: str | None,
|
|
246
|
+
session_generation: int,
|
|
247
|
+
) -> str | None:
|
|
248
|
+
"""Fence prompt admission while the TUI projects live rollback state."""
|
|
249
|
+
if self._permits.session_generation != session_generation:
|
|
250
|
+
return None
|
|
251
|
+
if session_id is not None and self._session.session_id != session_id:
|
|
252
|
+
return None
|
|
253
|
+
return await self._permits.prepare_session_transition_if_current(
|
|
254
|
+
"rollback_projection",
|
|
255
|
+
session_id=self._session.session_id,
|
|
256
|
+
session_generation=session_generation,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def finish_rollback_projection(self, owner: str) -> None:
|
|
260
|
+
"""Release a read-only rollback projection fence without committing it."""
|
|
261
|
+
self._permits.finish_session_transition(owner)
|
|
262
|
+
|
|
263
|
+
def write_snapshot(self) -> None:
|
|
264
|
+
"""Copy the current ``session.json`` to ``snapshots/turn_N.json``."""
|
|
265
|
+
capture_snapshot_writer(self._session, self._settings_handle)()
|
|
266
|
+
|
|
267
|
+
def turn_prompt_previews(self) -> dict[int, str]:
|
|
268
|
+
"""Return ``{turn_number: first_user_prompt}`` for every known turn."""
|
|
269
|
+
if not self._history.is_bound:
|
|
270
|
+
return {}
|
|
271
|
+
blocks = self._history.state.get("compressed_msgs", []) or []
|
|
272
|
+
return collect_turn_prompt_previews(list(self._history.messages), blocks)
|
|
273
|
+
|
|
274
|
+
def first_rolled_back_user_text(self, target_turn: int) -> str:
|
|
275
|
+
"""Return the first user prompt that rollback to ``target_turn`` discards."""
|
|
276
|
+
previews = self.turn_prompt_previews()
|
|
277
|
+
discarded_turns = [turn for turn in previews if turn > target_turn]
|
|
278
|
+
return previews[min(discarded_turns)] if discarded_turns else ""
|
|
279
|
+
|
|
280
|
+
def available_turns(self) -> list[int]:
|
|
281
|
+
"""Return turn-counts that can be targeted by a rollback."""
|
|
282
|
+
session_dir = self._session.session_dir
|
|
283
|
+
if session_dir is None:
|
|
284
|
+
return []
|
|
285
|
+
turns = snapshot_target_turns(session_dir)
|
|
286
|
+
current_turn = self._session.turn_number
|
|
287
|
+
if current_turn > 0:
|
|
288
|
+
turns = {turn for turn in turns if turn < current_turn}
|
|
289
|
+
if (self._session.mutation_tracker is not None and self._session.mutation_tracker.get_all_turns()) or (
|
|
290
|
+
self._history.is_bound and self._history.messages
|
|
291
|
+
):
|
|
292
|
+
turns.add(0)
|
|
293
|
+
if 0 not in turns:
|
|
294
|
+
# No welcome anchor means every remaining snapshot target would be
|
|
295
|
+
# orphaned in the picker.
|
|
296
|
+
return []
|
|
297
|
+
return sorted(turns)
|
|
298
|
+
|
|
299
|
+
async def on_user_rollback(
|
|
300
|
+
self,
|
|
301
|
+
event: UserRollback,
|
|
302
|
+
*,
|
|
303
|
+
atomic_copy_file: Callable[[Path, Path], None],
|
|
304
|
+
lock_timeout_seconds: float,
|
|
305
|
+
) -> None:
|
|
306
|
+
"""Handle rollback under an exclusive prompt/session-transition boundary."""
|
|
307
|
+
sid = self._session.session_id
|
|
308
|
+
session_generation = self._permits.session_generation
|
|
309
|
+
if sid is None:
|
|
310
|
+
await self._bus.publish(
|
|
311
|
+
Warning(
|
|
312
|
+
code="rollback_no_session",
|
|
313
|
+
message="No active session to roll back.",
|
|
314
|
+
display_message=_ROLLBACK_NO_SESSION.bind(),
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
return
|
|
318
|
+
if await self._cancel_if_rollback_owner_changed(event, sid, session_generation):
|
|
319
|
+
return
|
|
320
|
+
if await self._refuse_if_rollback_state_invalid(sid):
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
transition_owner = await self._permits.prepare_session_transition_if_current(
|
|
324
|
+
"rollback",
|
|
325
|
+
session_id=sid,
|
|
326
|
+
session_generation=session_generation,
|
|
327
|
+
)
|
|
328
|
+
if transition_owner is None:
|
|
329
|
+
await self._cancel_if_rollback_owner_changed(event, sid, session_generation)
|
|
330
|
+
return
|
|
331
|
+
try:
|
|
332
|
+
prepared = await self._prepare_user_rollback_with_permit(
|
|
333
|
+
event,
|
|
334
|
+
sid=sid,
|
|
335
|
+
session_generation=session_generation,
|
|
336
|
+
)
|
|
337
|
+
if prepared is None:
|
|
338
|
+
return
|
|
339
|
+
target_turn, rolled_back_user_text = prepared
|
|
340
|
+
await self._execute_user_rollback_with_permit(
|
|
341
|
+
event,
|
|
342
|
+
sid=sid,
|
|
343
|
+
session_generation=session_generation,
|
|
344
|
+
transition_owner=transition_owner,
|
|
345
|
+
target_turn=target_turn,
|
|
346
|
+
rolled_back_user_text=rolled_back_user_text,
|
|
347
|
+
atomic_copy_file=atomic_copy_file,
|
|
348
|
+
lock_timeout_seconds=lock_timeout_seconds,
|
|
349
|
+
)
|
|
350
|
+
finally:
|
|
351
|
+
self._permits.finish_session_transition(transition_owner)
|
|
352
|
+
|
|
353
|
+
async def _prepare_user_rollback_with_permit(
|
|
354
|
+
self,
|
|
355
|
+
event: UserRollback,
|
|
356
|
+
*,
|
|
357
|
+
sid: str,
|
|
358
|
+
session_generation: int,
|
|
359
|
+
) -> tuple[int, str] | None:
|
|
360
|
+
"""Quiesce and validate rollback without invalidating the current generation."""
|
|
361
|
+
lifecycle_task = self._turn_state.lease.run_task
|
|
362
|
+
await self._permits.wait_for_agent_load_idle()
|
|
363
|
+
target_turn = event.target_turn
|
|
364
|
+
if await self._cancel_if_rollback_owner_changed(event, sid, session_generation):
|
|
365
|
+
return None
|
|
366
|
+
if await self._refuse_if_rollback_state_invalid(sid):
|
|
367
|
+
return None
|
|
368
|
+
|
|
369
|
+
# IDLE is reached before final persistence and after-turn hooks finish.
|
|
370
|
+
# New admission is fenced and older reserved admissions have drained, so
|
|
371
|
+
# this task cannot be replaced while rollback waits for durable quiescence.
|
|
372
|
+
await await_task_quiescence(lifecycle_task)
|
|
373
|
+
if await self._cancel_if_rollback_owner_changed(event, sid, session_generation):
|
|
374
|
+
return None
|
|
375
|
+
if await self._refuse_if_rollback_state_invalid(sid):
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
if (
|
|
379
|
+
event.expected_conversation_revision is not None
|
|
380
|
+
and self._turn_state.lease.conversation_revision != event.expected_conversation_revision
|
|
381
|
+
):
|
|
382
|
+
await self._bus.publish(
|
|
383
|
+
Warning(
|
|
384
|
+
code="rollback_conversation_changed",
|
|
385
|
+
message="Rollback cancelled because the conversation changed after the picker was loaded.",
|
|
386
|
+
display_message=_ROLLBACK_CONVERSATION_CHANGED.bind(),
|
|
387
|
+
session_id=sid,
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
if event.expected_current_turn is not None and self._session.turn_number != event.expected_current_turn:
|
|
393
|
+
await self._bus.publish(
|
|
394
|
+
Warning(
|
|
395
|
+
code="rollback_conversation_changed",
|
|
396
|
+
message=(
|
|
397
|
+
"Rollback cancelled because the conversation advanced "
|
|
398
|
+
f"from turn {event.expected_current_turn} to turn {self._session.turn_number}."
|
|
399
|
+
),
|
|
400
|
+
display_message=_ROLLBACK_CONVERSATION_ADVANCED.bind(
|
|
401
|
+
expected_turn=event.expected_current_turn,
|
|
402
|
+
current_turn=self._session.turn_number,
|
|
403
|
+
),
|
|
404
|
+
session_id=sid,
|
|
405
|
+
),
|
|
406
|
+
)
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
if (
|
|
410
|
+
event.expected_build_generation is not None
|
|
411
|
+
and self._permits.build_generation != event.expected_build_generation
|
|
412
|
+
) or (
|
|
413
|
+
event.expected_workspace_cwd is not None and self._session.workspace_cwd() != event.expected_workspace_cwd
|
|
414
|
+
):
|
|
415
|
+
await self._bus.publish(
|
|
416
|
+
Warning(
|
|
417
|
+
code="rollback_runtime_changed",
|
|
418
|
+
message="Rollback cancelled because the workspace or runtime changed after the picker was loaded.",
|
|
419
|
+
display_message=_ROLLBACK_RUNTIME_CHANGED.bind(),
|
|
420
|
+
session_id=sid,
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
if event.relative_turns is not None:
|
|
426
|
+
if event.relative_turns <= 0:
|
|
427
|
+
await self._bus.publish(
|
|
428
|
+
Warning(
|
|
429
|
+
code="rollback_invalid_turn",
|
|
430
|
+
message="relative_turns must be positive.",
|
|
431
|
+
display_message=_ROLLBACK_RELATIVE_TURNS_INVALID.bind(),
|
|
432
|
+
session_id=sid,
|
|
433
|
+
),
|
|
434
|
+
)
|
|
435
|
+
return None
|
|
436
|
+
target_turn = self._session.turn_number - event.relative_turns
|
|
437
|
+
if target_turn < 0:
|
|
438
|
+
await self._bus.publish(
|
|
439
|
+
Warning(
|
|
440
|
+
code="rollback_unavailable",
|
|
441
|
+
message=(
|
|
442
|
+
f"Cannot roll back {event.relative_turns} turns; "
|
|
443
|
+
f"the session currently has {self._session.turn_number}."
|
|
444
|
+
),
|
|
445
|
+
display_message=_ROLLBACK_TURNS_UNAVAILABLE.bind(
|
|
446
|
+
requested_turns=event.relative_turns,
|
|
447
|
+
current_turns=self._session.turn_number,
|
|
448
|
+
),
|
|
449
|
+
session_id=sid,
|
|
450
|
+
),
|
|
451
|
+
)
|
|
452
|
+
return None
|
|
453
|
+
|
|
454
|
+
if target_turn < 0:
|
|
455
|
+
await self._bus.publish(
|
|
456
|
+
Warning(
|
|
457
|
+
code="rollback_invalid_turn",
|
|
458
|
+
message="target_turn must be >= 0.",
|
|
459
|
+
display_message=_ROLLBACK_TARGET_TURN_INVALID.bind(),
|
|
460
|
+
session_id=sid,
|
|
461
|
+
),
|
|
462
|
+
)
|
|
463
|
+
return None
|
|
464
|
+
|
|
465
|
+
available = self.available_turns()
|
|
466
|
+
if target_turn not in available:
|
|
467
|
+
await self._bus.publish(
|
|
468
|
+
Warning(
|
|
469
|
+
code="rollback_unavailable",
|
|
470
|
+
message=f"Cannot roll back to turn {target_turn}; available turns: {available}",
|
|
471
|
+
display_message=_ROLLBACK_TURN_UNAVAILABLE.bind(
|
|
472
|
+
target_turn=target_turn,
|
|
473
|
+
available=str(available),
|
|
474
|
+
),
|
|
475
|
+
session_id=sid,
|
|
476
|
+
),
|
|
477
|
+
)
|
|
478
|
+
return None
|
|
479
|
+
|
|
480
|
+
return target_turn, self.first_rolled_back_user_text(target_turn)
|
|
481
|
+
|
|
482
|
+
async def _execute_user_rollback_with_permit(
|
|
483
|
+
self,
|
|
484
|
+
event: UserRollback,
|
|
485
|
+
*,
|
|
486
|
+
sid: str,
|
|
487
|
+
session_generation: int,
|
|
488
|
+
transition_owner: str,
|
|
489
|
+
target_turn: int,
|
|
490
|
+
rolled_back_user_text: str,
|
|
491
|
+
atomic_copy_file: Callable[[Path, Path], None],
|
|
492
|
+
lock_timeout_seconds: float,
|
|
493
|
+
) -> None:
|
|
494
|
+
"""Secure rollback resources, then commit immediately before mutation."""
|
|
495
|
+
|
|
496
|
+
welcome_reset_lock: FileLock | None = None
|
|
497
|
+
if target_turn == 0:
|
|
498
|
+
await self._writer.flush()
|
|
499
|
+
reset_lock_acquired, welcome_reset_lock = self._acquire_welcome_reset_write_lock(sid, lock_timeout_seconds)
|
|
500
|
+
if not reset_lock_acquired:
|
|
501
|
+
await self._bus.publish(
|
|
502
|
+
Error(
|
|
503
|
+
code="rollback_reset_failed",
|
|
504
|
+
message="Rollback to welcome could not reset the session because the session state is busy.",
|
|
505
|
+
display_message=_ROLLBACK_RESET_FAILED.bind(),
|
|
506
|
+
session_id=sid,
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
return
|
|
510
|
+
|
|
511
|
+
welcome_lock_lease = _RollbackLockLease(welcome_reset_lock)
|
|
512
|
+
try:
|
|
513
|
+
await self._execute_user_rollback_after_resource_preflight(
|
|
514
|
+
event,
|
|
515
|
+
sid=sid,
|
|
516
|
+
session_generation=session_generation,
|
|
517
|
+
transition_owner=transition_owner,
|
|
518
|
+
target_turn=target_turn,
|
|
519
|
+
rolled_back_user_text=rolled_back_user_text,
|
|
520
|
+
atomic_copy_file=atomic_copy_file,
|
|
521
|
+
lock_timeout_seconds=lock_timeout_seconds,
|
|
522
|
+
welcome_lock_lease=welcome_lock_lease,
|
|
523
|
+
)
|
|
524
|
+
finally:
|
|
525
|
+
welcome_lock_lease.release()
|
|
526
|
+
|
|
527
|
+
async def _execute_user_rollback_after_resource_preflight(
|
|
528
|
+
self,
|
|
529
|
+
event: UserRollback,
|
|
530
|
+
*,
|
|
531
|
+
sid: str,
|
|
532
|
+
session_generation: int,
|
|
533
|
+
transition_owner: str,
|
|
534
|
+
target_turn: int,
|
|
535
|
+
rolled_back_user_text: str,
|
|
536
|
+
atomic_copy_file: Callable[[Path, Path], None],
|
|
537
|
+
lock_timeout_seconds: float,
|
|
538
|
+
welcome_lock_lease: _RollbackLockLease,
|
|
539
|
+
) -> None:
|
|
540
|
+
"""Build the rollback plan and mutate only after secured preflight resources."""
|
|
541
|
+
|
|
542
|
+
tracker = self._session.mutation_tracker
|
|
543
|
+
tracker_turn_ids: list[int] = sorted(t.turn_id for t in tracker.get_all_turns()) if tracker is not None else []
|
|
544
|
+
retained_notice = (
|
|
545
|
+
format_retained_changes_notice(
|
|
546
|
+
tracker,
|
|
547
|
+
cwd=self._session.workspace_cwd(),
|
|
548
|
+
max_entries=self._settings_handle.settings.workspace_change_notice_max_entries,
|
|
549
|
+
)
|
|
550
|
+
if target_turn == 0 and not event.revert_changes
|
|
551
|
+
else None
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
restore_results: list = []
|
|
555
|
+
plan_exclusions: list[tuple[str, str]] = []
|
|
556
|
+
plan_warnings: list[str] = []
|
|
557
|
+
plan_candidates: list[str] = []
|
|
558
|
+
revert_detection_truncated = False
|
|
559
|
+
welcome_file_rollback: Callable[[], Awaitable[None]] | None = None
|
|
560
|
+
post_swap_file_rollback: Callable[[], None] | None = None
|
|
561
|
+
if event.revert_changes and tracker is not None and tracker_turn_ids:
|
|
562
|
+
rollback_turn_ids = {tid for tid in tracker_turn_ids if tid > target_turn}
|
|
563
|
+
only_paths: set[str] | None = set(event.selected_paths) if event.selected_paths else None
|
|
564
|
+
if rollback_turn_ids:
|
|
565
|
+
# Truncated detection means the rolled-back turns may hold
|
|
566
|
+
# writes that never became plan candidates; the flag lives on
|
|
567
|
+
# the turns rollback_turns removes, so capture it here.
|
|
568
|
+
revert_detection_truncated = any(
|
|
569
|
+
turn.detection_truncated for turn in tracker.get_all_turns() if turn.turn_id in rollback_turn_ids
|
|
570
|
+
)
|
|
571
|
+
try:
|
|
572
|
+
# Authoritative peer re-check before the only destructive
|
|
573
|
+
# consumer acts: force-reclassify against the coordination
|
|
574
|
+
# registry (a peer's claim may have landed after our
|
|
575
|
+
# finalize), then let the coordinator exclude paths a peer
|
|
576
|
+
# modified since and warn about in-flight peer commands.
|
|
577
|
+
# Coordination failures must not break rollback itself.
|
|
578
|
+
coordinator = self._session.mutation_coordinator
|
|
579
|
+
loop = asyncio.get_running_loop()
|
|
580
|
+
# The workspace cwd doubles as the registry fallback root:
|
|
581
|
+
# outside a git repo, claims were published under it, and
|
|
582
|
+
# the destructive rollback-time check must rediscover the
|
|
583
|
+
# same peer files.
|
|
584
|
+
workspace_cwd = self._session.workspace_cwd()
|
|
585
|
+
if coordinator is not None:
|
|
586
|
+
try:
|
|
587
|
+
await loop.run_in_executor(
|
|
588
|
+
None,
|
|
589
|
+
lambda: coordinator.reclassify(tracker, force=True, fallback_root=workspace_cwd),
|
|
590
|
+
)
|
|
591
|
+
except Exception:
|
|
592
|
+
logger.debug("Pre-rollback attribution re-check failed", exc_info=True)
|
|
593
|
+
# Build the plan FIRST: rollback_turns removes the turns
|
|
594
|
+
# it rolls back, so exclusions/warnings cannot be
|
|
595
|
+
# reconstructed afterwards — execute from the pre-built
|
|
596
|
+
# plan and thread its report into the result event.
|
|
597
|
+
plan = tracker.get_rollback_plan_for_turns(rollback_turn_ids)
|
|
598
|
+
if coordinator is not None:
|
|
599
|
+
try:
|
|
600
|
+
plan = await loop.run_in_executor(
|
|
601
|
+
None,
|
|
602
|
+
lambda: coordinator.augment_rollback_plan(
|
|
603
|
+
tracker,
|
|
604
|
+
plan,
|
|
605
|
+
scope_paths=[workspace_cwd],
|
|
606
|
+
fallback_root=workspace_cwd,
|
|
607
|
+
),
|
|
608
|
+
)
|
|
609
|
+
except Exception:
|
|
610
|
+
logger.debug("Rollback plan peer augmentation failed", exc_info=True)
|
|
611
|
+
plan_exclusions = [(path, reason.value) for path, reason in plan.exclusions]
|
|
612
|
+
plan_warnings = [w.message for w in plan.warnings]
|
|
613
|
+
# Every path the rolled-back turns touched — entries the
|
|
614
|
+
# restore will attempt plus exclusions it never will. The
|
|
615
|
+
# turns are removed during execution, so the retained set
|
|
616
|
+
# can only be derived from this pre-built list afterwards.
|
|
617
|
+
plan_candidates = [path for path, _ in plan.entries] + [path for path, _ in plan.exclusions]
|
|
618
|
+
|
|
619
|
+
def _apply_file_rollback() -> None:
|
|
620
|
+
nonlocal restore_results
|
|
621
|
+
try:
|
|
622
|
+
restore_results = tracker.rollback_turns(
|
|
623
|
+
rollback_turn_ids,
|
|
624
|
+
only_paths=only_paths,
|
|
625
|
+
plan=plan,
|
|
626
|
+
)
|
|
627
|
+
except Exception:
|
|
628
|
+
logger.exception("Rollback file-restore failed (target_turn=%d)", target_turn)
|
|
629
|
+
restore_results = []
|
|
630
|
+
|
|
631
|
+
if target_turn == 0:
|
|
632
|
+
|
|
633
|
+
async def _apply_welcome_file_rollback() -> None:
|
|
634
|
+
_apply_file_rollback()
|
|
635
|
+
|
|
636
|
+
welcome_file_rollback = _apply_welcome_file_rollback
|
|
637
|
+
else:
|
|
638
|
+
post_swap_file_rollback = _apply_file_rollback
|
|
639
|
+
except Exception:
|
|
640
|
+
logger.exception("Rollback file-restore failed (target_turn=%d)", target_turn)
|
|
641
|
+
restore_results = []
|
|
642
|
+
|
|
643
|
+
if await self._cancel_if_rollback_owner_changed(event, sid, session_generation):
|
|
644
|
+
return
|
|
645
|
+
|
|
646
|
+
session_dir = self._session.session_dir
|
|
647
|
+
if target_turn == 0:
|
|
648
|
+
# Keep the old registry for the audit record: a successful reset
|
|
649
|
+
# replaces the live history before the rollback event is committed.
|
|
650
|
+
rollback_history_state = self._history.state if self._history.is_bound else None
|
|
651
|
+
self._permits.commit_session_transition(transition_owner)
|
|
652
|
+
|
|
653
|
+
try:
|
|
654
|
+
reset_succeeded = await self._lifecycle.reset_session_to_welcome(
|
|
655
|
+
sid,
|
|
656
|
+
write_lock_held=welcome_lock_lease.held,
|
|
657
|
+
after_delete=welcome_file_rollback,
|
|
658
|
+
before_restart=welcome_lock_lease.release,
|
|
659
|
+
)
|
|
660
|
+
finally:
|
|
661
|
+
welcome_lock_lease.release()
|
|
662
|
+
if not reset_succeeded:
|
|
663
|
+
await self._bus.publish(
|
|
664
|
+
Error(
|
|
665
|
+
code="rollback_reset_failed",
|
|
666
|
+
message="Rollback to welcome could not reset the session because the session state is busy.",
|
|
667
|
+
display_message=_ROLLBACK_RESET_FAILED.bind(),
|
|
668
|
+
session_id=sid,
|
|
669
|
+
),
|
|
670
|
+
)
|
|
671
|
+
return
|
|
672
|
+
# The reset has committed and released the welcome write lock. Its
|
|
673
|
+
# restarted recorder is bound lazily to the same log, so activation
|
|
674
|
+
# recovers the old branch and this event atomically opens the new one.
|
|
675
|
+
# A failed reset never reaches here and therefore never supersedes the
|
|
676
|
+
# branch of the session that was restored.
|
|
677
|
+
await self._trajectory_recorder.rollback(
|
|
678
|
+
target_turn=0,
|
|
679
|
+
history_state=rollback_history_state,
|
|
680
|
+
)
|
|
681
|
+
if retained_notice:
|
|
682
|
+
self._workspace_change_tracker.queue_safety_notice(retained_notice, cwd=self._session.workspace_cwd())
|
|
683
|
+
self._queue_partial_revert_notice(
|
|
684
|
+
plan_candidates, restore_results, detection_truncated=revert_detection_truncated
|
|
685
|
+
)
|
|
686
|
+
files_reverted = sum(1 for r in restore_results if r.changed)
|
|
687
|
+
await self._bus.publish(
|
|
688
|
+
RollbackResult(
|
|
689
|
+
session_id=sid,
|
|
690
|
+
target_turn=0,
|
|
691
|
+
rolled_back_user_text=rolled_back_user_text,
|
|
692
|
+
files_reverted=files_reverted,
|
|
693
|
+
restore_results=restore_results,
|
|
694
|
+
exclusions=plan_exclusions,
|
|
695
|
+
warnings=plan_warnings,
|
|
696
|
+
),
|
|
697
|
+
)
|
|
698
|
+
return
|
|
699
|
+
|
|
700
|
+
snapshot_path = rollback_snapshot_for_target(session_dir, target_turn)
|
|
701
|
+
session_file = session_dir / "session.json" if session_dir else None
|
|
702
|
+
if snapshot_path is None or session_file is None or not snapshot_path.exists():
|
|
703
|
+
await self._bus.publish(
|
|
704
|
+
Warning(
|
|
705
|
+
code="rollback_snapshot_missing",
|
|
706
|
+
message=f"Snapshot for turn {target_turn} is missing.",
|
|
707
|
+
display_message=_ROLLBACK_SNAPSHOT_MISSING.bind(target_turn=target_turn),
|
|
708
|
+
session_id=sid,
|
|
709
|
+
),
|
|
710
|
+
)
|
|
711
|
+
return
|
|
712
|
+
|
|
713
|
+
snapshot_missing_after_lock = False
|
|
714
|
+
try:
|
|
715
|
+
lock_path = self._session.session_write_lock_path(sid)
|
|
716
|
+
if lock_path is None:
|
|
717
|
+
raise OSError("session state store is unavailable")
|
|
718
|
+
with FileLock(lock_path, timeout=lock_timeout_seconds):
|
|
719
|
+
if not snapshot_path.exists():
|
|
720
|
+
snapshot_missing_after_lock = True
|
|
721
|
+
else:
|
|
722
|
+
overlays = _read_session_overlays(session_file)
|
|
723
|
+
self._permits.commit_session_transition(transition_owner)
|
|
724
|
+
atomic_copy_file(snapshot_path, session_file)
|
|
725
|
+
_reapply_session_overlays(session_file, overlays)
|
|
726
|
+
backup_file = session_file.with_name(SESSION_BACKUP_FILE_NAME)
|
|
727
|
+
try:
|
|
728
|
+
# Copy the restored (overlay-patched) primary so both files
|
|
729
|
+
# stay identical.
|
|
730
|
+
atomic_copy_file(session_file, backup_file)
|
|
731
|
+
except OSError:
|
|
732
|
+
with contextlib.suppress(OSError):
|
|
733
|
+
backup_file.unlink()
|
|
734
|
+
logger.warning("Failed to update rollback backup %s", backup_file, exc_info=True)
|
|
735
|
+
except TimeoutError as exc:
|
|
736
|
+
await self._bus.publish(
|
|
737
|
+
Error(
|
|
738
|
+
code="rollback_swap_locked",
|
|
739
|
+
message=f"Timed out waiting for session lock: {exc}",
|
|
740
|
+
display_message=_ROLLBACK_SWAP_LOCKED.bind(detail=DisplayBlock(str(exc))),
|
|
741
|
+
session_id=sid,
|
|
742
|
+
),
|
|
743
|
+
)
|
|
744
|
+
return
|
|
745
|
+
except OSError as exc:
|
|
746
|
+
await self._bus.publish(
|
|
747
|
+
Error(
|
|
748
|
+
code="rollback_swap_failed",
|
|
749
|
+
message=f"Failed to restore snapshot: {exc}",
|
|
750
|
+
display_message=_ROLLBACK_SWAP_FAILED.bind(detail=DisplayBlock(str(exc))),
|
|
751
|
+
session_id=sid,
|
|
752
|
+
),
|
|
753
|
+
)
|
|
754
|
+
return
|
|
755
|
+
|
|
756
|
+
if snapshot_missing_after_lock:
|
|
757
|
+
await self._bus.publish(
|
|
758
|
+
Warning(
|
|
759
|
+
code="rollback_snapshot_missing",
|
|
760
|
+
message=f"Snapshot for turn {target_turn} is missing.",
|
|
761
|
+
display_message=_ROLLBACK_SNAPSHOT_MISSING.bind(target_turn=target_turn),
|
|
762
|
+
session_id=sid,
|
|
763
|
+
),
|
|
764
|
+
)
|
|
765
|
+
return
|
|
766
|
+
|
|
767
|
+
if post_swap_file_rollback is not None:
|
|
768
|
+
post_swap_file_rollback()
|
|
769
|
+
|
|
770
|
+
if session_dir is not None:
|
|
771
|
+
snap_dir = session_dir / "snapshots"
|
|
772
|
+
if snap_dir.is_dir():
|
|
773
|
+
for path in rollback_snapshot_paths(session_dir):
|
|
774
|
+
turn = snapshot_target_turn(path)
|
|
775
|
+
if turn is None:
|
|
776
|
+
continue
|
|
777
|
+
if turn <= target_turn:
|
|
778
|
+
continue
|
|
779
|
+
try:
|
|
780
|
+
path.unlink()
|
|
781
|
+
except OSError:
|
|
782
|
+
logger.debug("Failed to prune stale snapshot %s", path, exc_info=True)
|
|
783
|
+
|
|
784
|
+
# The rollback opens a new trajectory branch: recorded on the outgoing
|
|
785
|
+
# runtime (the restore below closes it and the restored session resumes
|
|
786
|
+
# on the new branch), after the swap lock is released — activation
|
|
787
|
+
# takes the same lock — and while the live registry still names the
|
|
788
|
+
# first superseded turn.
|
|
789
|
+
record_cancelled = False
|
|
790
|
+
try:
|
|
791
|
+
await self._trajectory_recorder.rollback(
|
|
792
|
+
target_turn=target_turn,
|
|
793
|
+
history_state=self._history.state if self._history.is_bound else None,
|
|
794
|
+
)
|
|
795
|
+
except asyncio.CancelledError:
|
|
796
|
+
# The swap has committed but the engine still holds the pre-rollback
|
|
797
|
+
# history: leaving here would let the next save write that history
|
|
798
|
+
# back over the file the rollback just restored. The audit record is
|
|
799
|
+
# the only part that can be dropped, so the cancellation waits for the
|
|
800
|
+
# restore below and completes right after it.
|
|
801
|
+
task = asyncio.current_task()
|
|
802
|
+
if task is not None:
|
|
803
|
+
task.uncancel()
|
|
804
|
+
record_cancelled = True
|
|
805
|
+
|
|
806
|
+
with self._session.saves_suppressed():
|
|
807
|
+
await self._lifecycle.on_session_restore(SessionRestore(session_id=sid, ignore_recovery=True))
|
|
808
|
+
if record_cancelled:
|
|
809
|
+
raise asyncio.CancelledError
|
|
810
|
+
|
|
811
|
+
if event.revert_changes:
|
|
812
|
+
self._workspace_change_tracker.invalidate()
|
|
813
|
+
# After the invalidate: the stale baseline can never rediscover
|
|
814
|
+
# files the revert retained, so they must be reported directly.
|
|
815
|
+
self._queue_partial_revert_notice(
|
|
816
|
+
plan_candidates, restore_results, detection_truncated=revert_detection_truncated
|
|
817
|
+
)
|
|
818
|
+
await self._writer.save_current_session()
|
|
819
|
+
|
|
820
|
+
files_reverted = sum(1 for r in restore_results if r.changed)
|
|
821
|
+
await self._bus.publish(
|
|
822
|
+
RollbackResult(
|
|
823
|
+
session_id=sid,
|
|
824
|
+
target_turn=target_turn,
|
|
825
|
+
rolled_back_user_text=rolled_back_user_text,
|
|
826
|
+
files_reverted=files_reverted,
|
|
827
|
+
restore_results=restore_results,
|
|
828
|
+
exclusions=plan_exclusions,
|
|
829
|
+
warnings=plan_warnings,
|
|
830
|
+
),
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
def _queue_partial_revert_notice(
|
|
834
|
+
self,
|
|
835
|
+
plan_candidates: list[str],
|
|
836
|
+
restore_results: list,
|
|
837
|
+
*,
|
|
838
|
+
detection_truncated: bool = False,
|
|
839
|
+
) -> None:
|
|
840
|
+
"""Report rollback candidates the revert did not actually restore.
|
|
841
|
+
|
|
842
|
+
Covers a partial ``selected_paths`` selection (unselected entries are
|
|
843
|
+
never attempted), plan exclusions, per-file restore failures, and a
|
|
844
|
+
restore pass that failed wholesale (empty results). Truncated
|
|
845
|
+
detection on a rolled-back turn means writes may exist that never
|
|
846
|
+
became candidates, so it warns even when every known candidate
|
|
847
|
+
restored. A fully-applied revert with complete detection queues
|
|
848
|
+
nothing.
|
|
849
|
+
"""
|
|
850
|
+
if not plan_candidates and not detection_truncated:
|
|
851
|
+
return
|
|
852
|
+
restored = {result.path for result in restore_results if result.ok}
|
|
853
|
+
retained = [path for path in plan_candidates if path not in restored]
|
|
854
|
+
cwd = self._session.workspace_cwd()
|
|
855
|
+
notice = format_partial_revert_notice(
|
|
856
|
+
retained,
|
|
857
|
+
cwd=cwd,
|
|
858
|
+
max_entries=self._settings_handle.settings.workspace_change_notice_max_entries,
|
|
859
|
+
detection_incomplete=detection_truncated,
|
|
860
|
+
)
|
|
861
|
+
if notice:
|
|
862
|
+
self._workspace_change_tracker.queue_safety_notice(notice, cwd=cwd)
|
|
863
|
+
|
|
864
|
+
async def _cancel_if_rollback_owner_changed(
|
|
865
|
+
self,
|
|
866
|
+
event: UserRollback,
|
|
867
|
+
session_id: str,
|
|
868
|
+
session_generation: int,
|
|
869
|
+
) -> bool:
|
|
870
|
+
"""Reject a deferred rollback that no longer belongs to its requester."""
|
|
871
|
+
if (
|
|
872
|
+
self._session.session_id == session_id
|
|
873
|
+
and self._permits.session_generation == session_generation
|
|
874
|
+
and (event.session_id is None or event.session_id == session_id)
|
|
875
|
+
):
|
|
876
|
+
return False
|
|
877
|
+
await self._bus.publish(
|
|
878
|
+
Warning(
|
|
879
|
+
code="rollback_session_changed",
|
|
880
|
+
message="Rollback cancelled because the active session changed.",
|
|
881
|
+
display_message=_ROLLBACK_SESSION_CHANGED.bind(),
|
|
882
|
+
session_id=session_id,
|
|
883
|
+
)
|
|
884
|
+
)
|
|
885
|
+
return True
|
|
886
|
+
|
|
887
|
+
async def _refuse_if_rollback_state_invalid(self, session_id: str) -> bool:
|
|
888
|
+
"""Preserve the immediate refusal contract for a genuinely active turn."""
|
|
889
|
+
if self._fsm.state in (EngineState.IDLE, EngineState.INTERRUPTED, EngineState.FAILED):
|
|
890
|
+
return False
|
|
891
|
+
await self._bus.publish(
|
|
892
|
+
Warning(
|
|
893
|
+
code="rollback_refused",
|
|
894
|
+
message=f"Rollback is not allowed in state {self._fsm.state.name}.",
|
|
895
|
+
display_message=_ROLLBACK_REFUSED.bind(state=self._fsm.state.name),
|
|
896
|
+
session_id=session_id,
|
|
897
|
+
),
|
|
898
|
+
)
|
|
899
|
+
return True
|
|
900
|
+
|
|
901
|
+
def _acquire_welcome_reset_write_lock(
|
|
902
|
+
self,
|
|
903
|
+
session_id: str,
|
|
904
|
+
timeout: float,
|
|
905
|
+
) -> tuple[bool, FileLock | None]:
|
|
906
|
+
lock_path = self._session.session_write_lock_path(session_id)
|
|
907
|
+
if lock_path is None:
|
|
908
|
+
return True, None
|
|
909
|
+
lock = FileLock(lock_path, timeout=timeout)
|
|
910
|
+
try:
|
|
911
|
+
lock.acquire()
|
|
912
|
+
return True, lock
|
|
913
|
+
except TimeoutError:
|
|
914
|
+
logger.warning("Timed out acquiring session lock for welcome rollback %s", session_id, exc_info=True)
|
|
915
|
+
return False, None
|
|
916
|
+
except OSError:
|
|
917
|
+
logger.warning("Failed to acquire session lock for welcome rollback %s", session_id, exc_info=True)
|
|
918
|
+
return False, None
|