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,487 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Locked read-modify-write primitives for chrys' YAML config documents.
|
|
4
|
+
|
|
5
|
+
Every persisted YAML config under ``~/.chrys/`` goes through
|
|
6
|
+
:func:`update_yaml_doc`. There is deliberately **no** bare ``write_yaml_doc``:
|
|
7
|
+
a lock-free read followed by a locked whole-file write is only safe when a
|
|
8
|
+
single object owns the whole document. Once several panels (or processes)
|
|
9
|
+
patch different keys of the same file, the last writer would silently clobber
|
|
10
|
+
keys it never read.
|
|
11
|
+
|
|
12
|
+
Layering the other way round — take the lock in the caller, then call a
|
|
13
|
+
function that takes it again — self-deadlocks: ``FileLock`` first grabs a
|
|
14
|
+
**non-reentrant** ``threading.Lock`` keyed by lock path
|
|
15
|
+
(:func:`chrys.foundation.util.lock._get_path_lock`). Hence one public entry
|
|
16
|
+
point that owns the whole critical section, and unlocked helpers stay private.
|
|
17
|
+
|
|
18
|
+
Reads take the lock too. On Windows an open read handle blocks ``os.replace``,
|
|
19
|
+
so an unlocked reader would make a concurrent writer fail rather than merely
|
|
20
|
+
observe a stale document.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import contextlib
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
import stat
|
|
29
|
+
from collections.abc import Callable, Iterator
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
import yaml
|
|
35
|
+
|
|
36
|
+
from chrys.foundation.platform.files import atomic_write_text, digest_bytes
|
|
37
|
+
from chrys.foundation.util.lock import FileLock
|
|
38
|
+
|
|
39
|
+
LOCK_TIMEOUT_SECONDS = 10.0
|
|
40
|
+
|
|
41
|
+
UNTRUSTED_DOC_MAX_BYTES = 1024 * 1024
|
|
42
|
+
"""Upper bound for a config document Chrys reads but does not own.
|
|
43
|
+
|
|
44
|
+
A repository's ``.chrys/settings.yaml`` is attacker-supplied the moment a
|
|
45
|
+
repo is cloned, and the plain readers here trust the filesystem: they follow
|
|
46
|
+
symlinks and read to end-of-file. A settings document is a handful of short
|
|
47
|
+
keys, so anything near this bound is not a settings document.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# ``O_NOFOLLOW`` refuses a symlinked final component at ``open`` time and
|
|
51
|
+
# ``O_NONBLOCK`` keeps a FIFO from blocking the open; neither exists on
|
|
52
|
+
# Windows, where the ``lstat`` screen below is the only line of defense —
|
|
53
|
+
# creating symlinks there requires elevated rights, so the residual
|
|
54
|
+
# check-to-open race is accepted.
|
|
55
|
+
_UNTRUSTED_OPEN_FLAGS = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
|
|
56
|
+
|
|
57
|
+
# ``openat``-style resolution (``dir_fd``) is what makes the parent check and
|
|
58
|
+
# the file open one atomic act; POSIX has it, Windows does not.
|
|
59
|
+
_OPENAT_AVAILABLE = os.open in os.supports_dir_fd and hasattr(os, "O_DIRECTORY")
|
|
60
|
+
|
|
61
|
+
logger = logging.getLogger(__name__)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def backup_path_for(path: Path) -> Path:
|
|
65
|
+
"""Return the sibling ``.bak`` path for *path*."""
|
|
66
|
+
return path.with_name(path.name + ".bak")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def lock_path_for(path: Path) -> Path:
|
|
70
|
+
"""Return the sibling lock path for *path*.
|
|
71
|
+
|
|
72
|
+
Derived from the target name (``settings.yaml`` -> ``settings.lock``) and
|
|
73
|
+
never hardcoded: a fixed name would let a process that guards one document
|
|
74
|
+
exclude writers of an unrelated one — and, worse, let two processes guard
|
|
75
|
+
the *same* document with different lock files.
|
|
76
|
+
"""
|
|
77
|
+
return path.with_suffix(".lock")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@contextlib.contextmanager
|
|
81
|
+
def _read_guard(path: Path, timeout: float) -> Iterator[None]:
|
|
82
|
+
"""Hold the document's lock for a read, or read without it.
|
|
83
|
+
|
|
84
|
+
Reads take the lock as a courtesy to writers, not because the reader needs
|
|
85
|
+
it: on Windows an open handle blocks a concurrent ``os.replace``. Acquiring
|
|
86
|
+
it creates a sidecar, which needs a writable directory — and a config
|
|
87
|
+
directory can be read-only, on a locked-down machine or under an owner the
|
|
88
|
+
user is not. Failing the read there would abort every entry point over a
|
|
89
|
+
document we can read perfectly well, so the courtesy is dropped instead.
|
|
90
|
+
|
|
91
|
+
A lock that exists but is *held* is the opposite case and still propagates.
|
|
92
|
+
``TimeoutError`` is an ``OSError``, so the two would otherwise collapse into
|
|
93
|
+
one: "nobody can take this lock" would be answered by reading behind an
|
|
94
|
+
active writer, which is the single thing the lock is there to prevent.
|
|
95
|
+
"""
|
|
96
|
+
lock = FileLock(lock_path_for(path), timeout=timeout)
|
|
97
|
+
try:
|
|
98
|
+
lock.acquire()
|
|
99
|
+
except TimeoutError:
|
|
100
|
+
raise
|
|
101
|
+
except OSError:
|
|
102
|
+
logger.warning("Config lock unavailable; reading %s without it", path, exc_info=True)
|
|
103
|
+
yield
|
|
104
|
+
return
|
|
105
|
+
try:
|
|
106
|
+
yield
|
|
107
|
+
finally:
|
|
108
|
+
lock.release()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _read_bytes(path: Path) -> bytes | None:
|
|
112
|
+
"""The file's bytes, or ``None`` when it is absent or unreadable.
|
|
113
|
+
|
|
114
|
+
Bytes rather than text because the digest is taken over exactly what was
|
|
115
|
+
parsed, and a text-mode read has already translated line endings by the
|
|
116
|
+
time it hands anything back — two readers of one file would then disagree
|
|
117
|
+
about its identity.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
return path.read_bytes()
|
|
121
|
+
except OSError:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _parse(raw: bytes | None) -> dict[str, Any] | None:
|
|
126
|
+
"""Parse *raw* as a YAML mapping, or ``None`` if undecodable/invalid.
|
|
127
|
+
|
|
128
|
+
Both catches are deliberately wider than the obvious one. A truncated or
|
|
129
|
+
mis-encoded file fails decoding with ``UnicodeDecodeError``, which is not an
|
|
130
|
+
``OSError``. And PyYAML only wraps *parser* failures in ``YAMLError``: its
|
|
131
|
+
constructors let the underlying exception out, so ``2026-99-99`` raises a
|
|
132
|
+
bare ``ValueError``, an integer past ``sys.get_int_max_str_digits()`` the
|
|
133
|
+
same, and deeply nested flow collections a ``RecursionError``.
|
|
134
|
+
|
|
135
|
+
Every one of those is reachable by hand-editing the file — which is exactly
|
|
136
|
+
the case the ``.bak`` fallback exists for, and it only runs if this returns
|
|
137
|
+
``None`` instead of propagating.
|
|
138
|
+
"""
|
|
139
|
+
if raw is None:
|
|
140
|
+
return None
|
|
141
|
+
try:
|
|
142
|
+
text = raw.decode("utf-8")
|
|
143
|
+
except UnicodeError:
|
|
144
|
+
return None
|
|
145
|
+
try:
|
|
146
|
+
data = yaml.safe_load(text)
|
|
147
|
+
except yaml.YAMLError, ValueError, RecursionError:
|
|
148
|
+
return None
|
|
149
|
+
if data is None:
|
|
150
|
+
# An empty file, a file of only comments, and an explicit ``null`` all
|
|
151
|
+
# land here, and all three mean "this document holds no settings".
|
|
152
|
+
# Only ``None`` may: ``or {}`` would have swallowed ``false``, ``0``
|
|
153
|
+
# and ``[]`` too, and a truncated file that happens to parse as one of
|
|
154
|
+
# those would then count as a valid empty document — skipping the
|
|
155
|
+
# ``.bak`` fallback and letting the next write mirror the emptiness
|
|
156
|
+
# over the one good copy that was left.
|
|
157
|
+
return {}
|
|
158
|
+
if not isinstance(data, dict) or not _container_tree_within_bounds(data):
|
|
159
|
+
return None
|
|
160
|
+
return data
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_MAX_EXPANDED_CONTAINERS = 100_000
|
|
164
|
+
"""Ceiling on the container tree a parsed document may expand to.
|
|
165
|
+
|
|
166
|
+
The byte caps bound the *text*, not the graph: anchors and aliases let a
|
|
167
|
+
few hundred bytes parse into a self-referential structure or one whose
|
|
168
|
+
shared branches multiply into millions of tree nodes. Consumers walk the
|
|
169
|
+
document as a tree — the flattener recurses into mapping values, and an
|
|
170
|
+
invalid raw value is rendered with ``str()``, which walks *every* container
|
|
171
|
+
it holds — so the walkable expansion is what has to be bounded, over every
|
|
172
|
+
container kind ``safe_load`` can produce, and a document past either bound
|
|
173
|
+
is refused whole, like any other document that does not parse.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
_MAX_EXPANDED_CONTAINER_DEPTH = 100
|
|
177
|
+
"""Ceiling on the expanded nesting depth of a parsed document.
|
|
178
|
+
|
|
179
|
+
Deep *literal* nesting already dies inside the parser (the ``RecursionError``
|
|
180
|
+
catch above), but an alias chain expands deeper than the text ever nested,
|
|
181
|
+
and a tree walk's recursion would be the thing that hits the limit.
|
|
182
|
+
Settings keys are a handful of segments; one hundred is beyond any document
|
|
183
|
+
with an owner.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
_MAX_EXPANDED_SCALAR_CHARS = 10_000_000
|
|
187
|
+
"""Ceiling on the scalar characters an expanded document may render to.
|
|
188
|
+
|
|
189
|
+
Bounding the container count bounds the *walk*, not the *output*: one large
|
|
190
|
+
string anchored once and aliased through a shared DAG is a single scalar in
|
|
191
|
+
the graph, yet every expanded path renders its own copy — a few hundred
|
|
192
|
+
kilobytes of text asking ``str()`` for gigabytes while staying under the
|
|
193
|
+
container ceiling. Scalars are therefore priced per expanded occurrence, by
|
|
194
|
+
size, estimated without rendering them. Ten million characters is far past
|
|
195
|
+
any settings document and still harmless to actually render.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
_CONTAINER_TYPES = (dict, list, tuple, set, frozenset)
|
|
199
|
+
"""Every container ``yaml.safe_load`` can construct.
|
|
200
|
+
|
|
201
|
+
Tuples come from ``!!omap``/``!!pairs``, sets from ``!!set``; mapping *keys*
|
|
202
|
+
can be containers too. All of them are walked by ``str()`` when a value is
|
|
203
|
+
rendered for a warning, so all of them count toward the expansion bounds.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _container_children(node: Any) -> Iterator[Any]:
|
|
208
|
+
if isinstance(node, dict):
|
|
209
|
+
for key, value in node.items():
|
|
210
|
+
yield key
|
|
211
|
+
yield value
|
|
212
|
+
else:
|
|
213
|
+
yield from node
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _scalar_render_cost(value: Any) -> int:
|
|
217
|
+
"""Chars *value* contributes to a rendered warning, without rendering it.
|
|
218
|
+
|
|
219
|
+
An estimate within a small constant factor is enough: the budget's job is
|
|
220
|
+
to make the render proportional to a fixed number, not to predict it.
|
|
221
|
+
"""
|
|
222
|
+
if isinstance(value, (str, bytes)):
|
|
223
|
+
return len(value) + 2
|
|
224
|
+
if isinstance(value, int): # bools included; ~3 bits per decimal digit
|
|
225
|
+
return value.bit_length() // 3 + 2
|
|
226
|
+
return 32
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _container_tree_within_bounds(doc: dict[str, Any]) -> bool:
|
|
230
|
+
"""Whether *doc* expands to a finite container tree inside all bounds.
|
|
231
|
+
|
|
232
|
+
Walks each distinct container once (iteratively — the walk must not be
|
|
233
|
+
the recursion that overflows) and measures the tree it would expand to:
|
|
234
|
+
container count, nesting depth, and rendered scalar volume.
|
|
235
|
+
"""
|
|
236
|
+
# Post-order over distinct containers: measurements by identity, an
|
|
237
|
+
# ancestor set for cycle detection. A ``(node, True)`` entry pops only
|
|
238
|
+
# after the node's whole subtree was measured, so a ``(node, False)``
|
|
239
|
+
# entry popping while the node is still an ancestor is a true cycle.
|
|
240
|
+
measured: dict[int, tuple[int, int, int]] = {}
|
|
241
|
+
ancestors: set[int] = set()
|
|
242
|
+
stack: list[tuple[Any, bool]] = [(doc, False)]
|
|
243
|
+
while stack:
|
|
244
|
+
node, children_measured = stack.pop()
|
|
245
|
+
node_id = id(node)
|
|
246
|
+
if children_measured:
|
|
247
|
+
ancestors.discard(node_id)
|
|
248
|
+
size = 1
|
|
249
|
+
depth = 1
|
|
250
|
+
chars = 0
|
|
251
|
+
for child in _container_children(node):
|
|
252
|
+
if isinstance(child, _CONTAINER_TYPES):
|
|
253
|
+
child_size, child_depth, child_chars = measured[id(child)]
|
|
254
|
+
size += child_size
|
|
255
|
+
depth = max(depth, child_depth + 1)
|
|
256
|
+
chars += child_chars
|
|
257
|
+
else:
|
|
258
|
+
chars += _scalar_render_cost(child)
|
|
259
|
+
if (
|
|
260
|
+
size > _MAX_EXPANDED_CONTAINERS
|
|
261
|
+
or depth > _MAX_EXPANDED_CONTAINER_DEPTH
|
|
262
|
+
or chars > _MAX_EXPANDED_SCALAR_CHARS
|
|
263
|
+
):
|
|
264
|
+
return False
|
|
265
|
+
measured[node_id] = (size, depth, chars)
|
|
266
|
+
continue
|
|
267
|
+
if node_id in measured:
|
|
268
|
+
continue
|
|
269
|
+
if node_id in ancestors:
|
|
270
|
+
return False
|
|
271
|
+
ancestors.add(node_id)
|
|
272
|
+
stack.append((node, True))
|
|
273
|
+
stack.extend(
|
|
274
|
+
(child, False)
|
|
275
|
+
for child in _container_children(node)
|
|
276
|
+
if isinstance(child, _CONTAINER_TYPES) and id(child) not in measured
|
|
277
|
+
)
|
|
278
|
+
return True
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _read_locked(path: Path, *, backup: bool) -> tuple[dict[str, Any] | None, bytes | None, bool]:
|
|
282
|
+
"""Read *path* (caller holds the lock): ``(doc, raw, from_backup)``.
|
|
283
|
+
|
|
284
|
+
``raw`` is the primary's bytes as this read saw them — including when they
|
|
285
|
+
did not parse — so a caller that has to prove what it read digests those
|
|
286
|
+
rather than re-reading the path afterwards.
|
|
287
|
+
"""
|
|
288
|
+
raw = _read_bytes(path)
|
|
289
|
+
doc = _parse(raw)
|
|
290
|
+
if doc is not None:
|
|
291
|
+
return doc, raw, False
|
|
292
|
+
if not backup:
|
|
293
|
+
return None, raw, False
|
|
294
|
+
return _parse(_read_bytes(backup_path_for(path))), raw, True
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _dump(doc: dict[str, Any]) -> str:
|
|
298
|
+
return yaml.safe_dump(doc, sort_keys=True, allow_unicode=True)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _commit(path: Path, payload: str) -> None:
|
|
302
|
+
"""Write *payload* to the primary, then mirror it to the backup.
|
|
303
|
+
|
|
304
|
+
Primary first, on purpose: writing the backup first would leave a recovery
|
|
305
|
+
copy of a state that was never committed if the primary write then fails.
|
|
306
|
+
The backup is best-effort — a full disk must not fail an otherwise good
|
|
307
|
+
primary write.
|
|
308
|
+
"""
|
|
309
|
+
atomic_write_text(path, payload)
|
|
310
|
+
with contextlib.suppress(OSError):
|
|
311
|
+
atomic_write_text(backup_path_for(path), payload)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@dataclass(frozen=True, slots=True)
|
|
315
|
+
class YamlRead:
|
|
316
|
+
"""One read of a document, and the identity of what was read."""
|
|
317
|
+
|
|
318
|
+
doc: dict[str, Any] | None
|
|
319
|
+
fingerprint: str | None
|
|
320
|
+
"""Digest of the primary as this read left it; ``None`` when absent."""
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def read_yaml_document(
|
|
324
|
+
path: Path,
|
|
325
|
+
*,
|
|
326
|
+
backup: bool = True,
|
|
327
|
+
lock_timeout: float = LOCK_TIMEOUT_SECONDS,
|
|
328
|
+
) -> YamlRead:
|
|
329
|
+
"""Read the YAML mapping at *path* together with the file's fingerprint.
|
|
330
|
+
|
|
331
|
+
For callers that later act destructively on the file they read — retiring
|
|
332
|
+
a migrated legacy document, say — and must be able to prove it is still
|
|
333
|
+
the same file. The fingerprint is over the very bytes ``doc`` was parsed
|
|
334
|
+
from, not over a second read of the path: an editor that ignores the
|
|
335
|
+
sidecar lock needs only one write to land between a parse and a re-read,
|
|
336
|
+
and the fingerprint would then certify a file whose contents nobody has
|
|
337
|
+
seen — the next phase retires the newer file and records the older state as
|
|
338
|
+
migrated. Re-entering this function under a caller-held lock self-deadlocks,
|
|
339
|
+
which is why the fingerprint has to come out of the same call.
|
|
340
|
+
|
|
341
|
+
A repaired primary is the one case where the two differ legitimately: this
|
|
342
|
+
call rewrites it from the backup, and the fingerprint then describes the
|
|
343
|
+
payload it just wrote, which is what the next reader will find there.
|
|
344
|
+
"""
|
|
345
|
+
if not path.exists() and not (backup and backup_path_for(path).exists()):
|
|
346
|
+
return YamlRead(doc=None, fingerprint=None)
|
|
347
|
+
# No ``mkdir`` here: one of those two exists, so its directory does too, and
|
|
348
|
+
# a read must not be what creates the config directory.
|
|
349
|
+
with _read_guard(path, lock_timeout):
|
|
350
|
+
doc, raw, from_backup = _read_locked(path, backup=backup)
|
|
351
|
+
if doc is not None and from_backup:
|
|
352
|
+
try:
|
|
353
|
+
raw = atomic_write_text(path, _dump(doc))
|
|
354
|
+
except OSError:
|
|
355
|
+
# The repair failed, so the primary still holds what was read
|
|
356
|
+
# from it — unparseable, but that is its identity, and the
|
|
357
|
+
# caller's own guard decides what an unreadable one means.
|
|
358
|
+
logger.warning("Could not restore %s from its backup", path, exc_info=True)
|
|
359
|
+
return YamlRead(doc=doc, fingerprint=digest_bytes(raw))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def read_yaml_doc(
|
|
363
|
+
path: Path,
|
|
364
|
+
*,
|
|
365
|
+
backup: bool = True,
|
|
366
|
+
lock_timeout: float = LOCK_TIMEOUT_SECONDS,
|
|
367
|
+
) -> dict[str, Any] | None:
|
|
368
|
+
"""Read the YAML mapping at *path*, falling back to its backup.
|
|
369
|
+
|
|
370
|
+
Returns ``None`` when neither copy yields a mapping, so callers can tell
|
|
371
|
+
"no config yet" from "empty config". When the primary is missing or corrupt
|
|
372
|
+
and the backup parses, the primary is restored from it before returning.
|
|
373
|
+
|
|
374
|
+
Nothing is created when neither file exists — a read must not materialise
|
|
375
|
+
the config directory.
|
|
376
|
+
"""
|
|
377
|
+
return read_yaml_document(path, backup=backup, lock_timeout=lock_timeout).doc
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def read_yaml_doc_readonly(path: Path) -> dict[str, Any] | None:
|
|
381
|
+
"""Read the YAML mapping at *path* with no lock, no backup and no repair.
|
|
382
|
+
|
|
383
|
+
For documents Chrys does not own — a repository's ``.chrys/settings.yaml``
|
|
384
|
+
lives in someone's working tree, where the other readers' side effects are
|
|
385
|
+
exactly wrong: acquiring the courtesy lock creates a sidecar file in their
|
|
386
|
+
repo, and the backup repair *writes* into it. There is also no Chrys writer
|
|
387
|
+
to be courteous to, because nothing in Chrys ever writes these files.
|
|
388
|
+
|
|
389
|
+
Because the file is also not the *user's*, the read is defensive where the
|
|
390
|
+
owned readers are trusting: only a regular file, never through a symlink,
|
|
391
|
+
and never past :data:`UNTRUSTED_DOC_MAX_BYTES` — opening a cloned
|
|
392
|
+
repository must not hang on a FIFO or feed ``/dev/zero`` to the parser.
|
|
393
|
+
The distrust covers both hops a repository controls: the file itself and
|
|
394
|
+
the directory that holds it — a committed ``.chrys`` symlink must not
|
|
395
|
+
carry the read out of the tree. Ancestors above the parent are the
|
|
396
|
+
caller's trust domain (the project root the user chose to open).
|
|
397
|
+
|
|
398
|
+
Returns ``None`` when the file is absent, refused, or does not parse as a
|
|
399
|
+
mapping.
|
|
400
|
+
"""
|
|
401
|
+
return _parse(_read_bytes_untrusted(path))
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _read_bytes_untrusted(path: Path) -> bytes | None:
|
|
405
|
+
"""Bounded, regular-file-only, no-follow read; ``None`` on any refusal."""
|
|
406
|
+
# The parent must itself be a real directory: ``O_NOFOLLOW`` refuses only
|
|
407
|
+
# the final component, and a symlinked (or, on Windows, a reparse-pointed)
|
|
408
|
+
# containing directory would resolve the rest of the path outside the tree
|
|
409
|
+
# before that refusal could bite.
|
|
410
|
+
parent_fd: int | None = None
|
|
411
|
+
try:
|
|
412
|
+
if _OPENAT_AVAILABLE:
|
|
413
|
+
# Bind the final open to the directory that was checked:
|
|
414
|
+
# ``O_DIRECTORY | O_NOFOLLOW`` refuses a symlinked parent in the
|
|
415
|
+
# same call that opens it, and ``dir_fd`` resolves the file inside
|
|
416
|
+
# that very directory — a rename slipped between two path lookups
|
|
417
|
+
# cannot swap the tree out underneath the read.
|
|
418
|
+
parent_fd = os.open(path.parent, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0))
|
|
419
|
+
fd = os.open(path.name, _UNTRUSTED_OPEN_FLAGS, dir_fd=parent_fd)
|
|
420
|
+
else:
|
|
421
|
+
# Windows has no ``openat``, so the screen is two lookups with a
|
|
422
|
+
# window between them — best effort, backed by the ``fstat``
|
|
423
|
+
# re-check on the descriptor below.
|
|
424
|
+
parent = os.lstat(path.parent)
|
|
425
|
+
if not stat.S_ISDIR(parent.st_mode):
|
|
426
|
+
return None
|
|
427
|
+
if getattr(parent, "st_file_attributes", 0) & stat.FILE_ATTRIBUTE_REPARSE_POINT:
|
|
428
|
+
return None
|
|
429
|
+
if not stat.S_ISREG(os.lstat(path).st_mode):
|
|
430
|
+
return None
|
|
431
|
+
fd = os.open(path, _UNTRUSTED_OPEN_FLAGS)
|
|
432
|
+
except OSError:
|
|
433
|
+
return None
|
|
434
|
+
finally:
|
|
435
|
+
if parent_fd is not None:
|
|
436
|
+
os.close(parent_fd)
|
|
437
|
+
try:
|
|
438
|
+
described = os.fstat(fd)
|
|
439
|
+
# Re-checked on the open descriptor: the pre-``lstat`` only screens
|
|
440
|
+
# what the path named a moment ago, and it is what let us get here
|
|
441
|
+
# without blocking on a FIFO.
|
|
442
|
+
if not stat.S_ISREG(described.st_mode) or described.st_size > UNTRUSTED_DOC_MAX_BYTES:
|
|
443
|
+
return None
|
|
444
|
+
# Read past the stat by one byte so a file growing under us is caught
|
|
445
|
+
# by size, not trusted because its stat was taken early.
|
|
446
|
+
chunks: list[bytes] = []
|
|
447
|
+
remaining = UNTRUSTED_DOC_MAX_BYTES + 1
|
|
448
|
+
while remaining > 0:
|
|
449
|
+
chunk = os.read(fd, min(remaining, 65536))
|
|
450
|
+
if not chunk:
|
|
451
|
+
break
|
|
452
|
+
chunks.append(chunk)
|
|
453
|
+
remaining -= len(chunk)
|
|
454
|
+
if remaining <= 0:
|
|
455
|
+
return None
|
|
456
|
+
return b"".join(chunks)
|
|
457
|
+
except OSError:
|
|
458
|
+
return None
|
|
459
|
+
finally:
|
|
460
|
+
os.close(fd)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def update_yaml_doc(
|
|
464
|
+
path: Path,
|
|
465
|
+
mutator: Callable[[dict[str, Any]], dict[str, Any]],
|
|
466
|
+
*,
|
|
467
|
+
lock_timeout: float = LOCK_TIMEOUT_SECONDS,
|
|
468
|
+
) -> dict[str, Any]:
|
|
469
|
+
"""Apply *mutator* to the document at *path* under an exclusive lock.
|
|
470
|
+
|
|
471
|
+
The whole read-modify-write happens inside one lock acquisition, so
|
|
472
|
+
concurrent writers merge instead of clobbering: same key is
|
|
473
|
+
last-writer-wins, different keys both survive.
|
|
474
|
+
|
|
475
|
+
*mutator* receives the freshly read document (an empty dict when neither
|
|
476
|
+
the primary nor the backup parses) and returns the document to commit. If
|
|
477
|
+
it raises, the lock is released and **neither** file is touched.
|
|
478
|
+
|
|
479
|
+
Returns the committed document.
|
|
480
|
+
"""
|
|
481
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
482
|
+
with FileLock(lock_path_for(path), timeout=lock_timeout):
|
|
483
|
+
current, _, _ = _read_locked(path, backup=True)
|
|
484
|
+
updated = mutator(dict(current) if current is not None else {})
|
|
485
|
+
payload = _dump(updated)
|
|
486
|
+
_commit(path, payload)
|
|
487
|
+
return updated
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Error classification and formatting shared by every layer.
|
|
4
|
+
|
|
5
|
+
Provider and network failures are classified here, once: retry policy
|
|
6
|
+
(:func:`is_retryable`), semantic predicates and user-facing descriptions all
|
|
7
|
+
read :func:`classify_error`. Don't add another chain walk or phrase table
|
|
8
|
+
elsewhere. :func:`clean_error_message` stays the raw English text for the
|
|
9
|
+
model, logs, ACP and headless output.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from ._legacy import RETRYABLE_STATUS_CODES, RETRYABLE_TYPE_NAMES
|
|
15
|
+
from ._walk import iter_explicit_graph
|
|
16
|
+
from .classify import (
|
|
17
|
+
ErrorClassification,
|
|
18
|
+
classify_error,
|
|
19
|
+
context_overflow_limit,
|
|
20
|
+
invalidates_continuation_token,
|
|
21
|
+
is_context_overflow,
|
|
22
|
+
is_read_timeout,
|
|
23
|
+
is_retryable,
|
|
24
|
+
is_thinking_binding_rejection,
|
|
25
|
+
may_be_context_overflow,
|
|
26
|
+
)
|
|
27
|
+
from .formatting import EMPTY_EXCEPTION_MESSAGES, clean_error_message
|
|
28
|
+
from .kinds import ErrorKind, TimeoutPhase
|
|
29
|
+
from .network import RETRYABLE_PHRASES, is_deterministic_connection_error
|
|
30
|
+
from .provider import (
|
|
31
|
+
NON_RETRYABLE_PROVIDER_ERROR_CODES,
|
|
32
|
+
ContinuationVerdictError,
|
|
33
|
+
ProviderResponseError,
|
|
34
|
+
ProviderSignal,
|
|
35
|
+
in_band_failure_retryable,
|
|
36
|
+
)
|
|
37
|
+
from .route import ROUTE_EXTENSION_KEY, Origin, RouteFacts, origin_of, route_of
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"EMPTY_EXCEPTION_MESSAGES",
|
|
41
|
+
"NON_RETRYABLE_PROVIDER_ERROR_CODES",
|
|
42
|
+
"RETRYABLE_PHRASES",
|
|
43
|
+
"RETRYABLE_STATUS_CODES",
|
|
44
|
+
"RETRYABLE_TYPE_NAMES",
|
|
45
|
+
"ROUTE_EXTENSION_KEY",
|
|
46
|
+
"ContinuationVerdictError",
|
|
47
|
+
"ErrorClassification",
|
|
48
|
+
"ErrorKind",
|
|
49
|
+
"Origin",
|
|
50
|
+
"ProviderResponseError",
|
|
51
|
+
"ProviderSignal",
|
|
52
|
+
"RouteFacts",
|
|
53
|
+
"TimeoutPhase",
|
|
54
|
+
"classify_error",
|
|
55
|
+
"clean_error_message",
|
|
56
|
+
"context_overflow_limit",
|
|
57
|
+
"in_band_failure_retryable",
|
|
58
|
+
"invalidates_continuation_token",
|
|
59
|
+
"is_context_overflow",
|
|
60
|
+
"is_deterministic_connection_error",
|
|
61
|
+
"is_read_timeout",
|
|
62
|
+
"is_retryable",
|
|
63
|
+
"is_thinking_binding_rejection",
|
|
64
|
+
"iter_explicit_graph",
|
|
65
|
+
"may_be_context_overflow",
|
|
66
|
+
"origin_of",
|
|
67
|
+
"route_of",
|
|
68
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Legacy retryability rules, kept verbatim behind :func:`classify_error`.
|
|
4
|
+
|
|
5
|
+
The three transient layers (type name, status code, phrase) read the full
|
|
6
|
+
chain, implicit ``__context__`` included; new rules read only explicit
|
|
7
|
+
evidence and live in the classifier. The deterministic-connection veto moved
|
|
8
|
+
to :mod:`.network`, which reads only explicit evidence.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
|
|
15
|
+
from ._text import _clean_exception_text
|
|
16
|
+
from ._walk import iter_full_chain
|
|
17
|
+
from .network import RETRYABLE_PHRASES, is_deterministic_connection_error
|
|
18
|
+
|
|
19
|
+
# HTTP status codes that are transient and worth retrying after SDK
|
|
20
|
+
# retries are exhausted.
|
|
21
|
+
RETRYABLE_STATUS_CODES = frozenset({429, 500, 502, 503, 504, 529})
|
|
22
|
+
|
|
23
|
+
# SDK exception class names (checked by name to avoid hard imports).
|
|
24
|
+
RETRYABLE_TYPE_NAMES = frozenset(
|
|
25
|
+
{
|
|
26
|
+
# OpenAI SDK
|
|
27
|
+
"APIConnectionError",
|
|
28
|
+
"APITimeoutError",
|
|
29
|
+
"RateLimitError",
|
|
30
|
+
"InternalServerError",
|
|
31
|
+
# Bare ``openai.APIError`` is only raised in ``openai/_streaming.py``
|
|
32
|
+
# when the upstream emits an in-band SSE ``error`` event mid-stream.
|
|
33
|
+
# ``type(exc).__name__`` returns the most-derived class, so this name
|
|
34
|
+
# match catches only the base class — subclasses (APIStatusError,
|
|
35
|
+
# BadRequestError, etc.) keep their existing classification.
|
|
36
|
+
"APIError",
|
|
37
|
+
# httpx / httpcore transport failures. These often carry no
|
|
38
|
+
# message when wrapping a bare OS/timeout exception on Windows
|
|
39
|
+
# (e.g. ``ReadTimeout(TimeoutError())``), so string fallback alone
|
|
40
|
+
# cannot classify them.
|
|
41
|
+
"CloseError",
|
|
42
|
+
"ConnectError",
|
|
43
|
+
"ConnectTimeout",
|
|
44
|
+
"DecodingError",
|
|
45
|
+
"NetworkError",
|
|
46
|
+
"PoolTimeout",
|
|
47
|
+
"ProxyError",
|
|
48
|
+
"ReadError",
|
|
49
|
+
"ReadTimeout",
|
|
50
|
+
"RemoteProtocolError",
|
|
51
|
+
"TimeoutException",
|
|
52
|
+
"WriteError",
|
|
53
|
+
"WriteTimeout",
|
|
54
|
+
# Anthropic SDK
|
|
55
|
+
"OverloadedError",
|
|
56
|
+
"ServiceUnavailableError",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_OWNER_TERMINAL_TYPE_NAMES = frozenset({"LastWordsGenerationError", "TerminalResponseValidationError"})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def is_owner_terminal(chain: Iterable[BaseException]) -> bool:
|
|
65
|
+
"""Return whether an owning component already concluded this failure.
|
|
66
|
+
|
|
67
|
+
These failures have already exhausted or terminally concluded their owning
|
|
68
|
+
component's policy, so an outer retry would replay a larger unit of work
|
|
69
|
+
without making the failure recoverable. Reads the full chain.
|
|
70
|
+
"""
|
|
71
|
+
return any(type(exc).__name__ in _OWNER_TERMINAL_TYPE_NAMES for exc in chain)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def is_transient(chain: Iterable[BaseException]) -> bool:
|
|
75
|
+
"""Apply the three transient layers to a full chain.
|
|
76
|
+
|
|
77
|
+
1. Exception type — Python builtins and SDK types (via class name)
|
|
78
|
+
2. HTTP status code — ``status_code`` attribute on SDK exceptions
|
|
79
|
+
3. String matching — fallback for transport-level errors
|
|
80
|
+
"""
|
|
81
|
+
chain = tuple(chain)
|
|
82
|
+
for exc in chain:
|
|
83
|
+
# Python built-in transient exceptions
|
|
84
|
+
if isinstance(exc, (ConnectionError, TimeoutError)):
|
|
85
|
+
return True
|
|
86
|
+
# SDK exception types (by name to avoid hard imports)
|
|
87
|
+
if type(exc).__name__ in RETRYABLE_TYPE_NAMES:
|
|
88
|
+
return True
|
|
89
|
+
# HTTP status code (works for both OpenAI and Anthropic SDKs)
|
|
90
|
+
status = getattr(exc, "status_code", None)
|
|
91
|
+
if status is not None and status in RETRYABLE_STATUS_CODES:
|
|
92
|
+
return True
|
|
93
|
+
# Fallback: string matching for transport-level errors
|
|
94
|
+
for exc in chain:
|
|
95
|
+
error_msg = _clean_exception_text(exc).lower()
|
|
96
|
+
if any(phrase in error_msg for phrase in RETRYABLE_PHRASES):
|
|
97
|
+
return True
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def is_retryable(e: BaseException) -> bool:
|
|
102
|
+
"""The legacy retry decision: owner veto, deterministic veto, transient layers.
|
|
103
|
+
|
|
104
|
+
Deterministic non-retryable root causes take precedence over the
|
|
105
|
+
three-layer transient-error strategy. The classifier decides both vetoes
|
|
106
|
+
itself and calls :func:`is_transient`; the corpus pins this whole rule as
|
|
107
|
+
the reference for every case the classifier left unchanged.
|
|
108
|
+
"""
|
|
109
|
+
chain = tuple(iter_full_chain(e))
|
|
110
|
+
if is_owner_terminal(chain):
|
|
111
|
+
return False
|
|
112
|
+
if is_deterministic_connection_error(e):
|
|
113
|
+
# SDKs wrap deterministic transport, protocol, DNS, and TLS failures
|
|
114
|
+
# in retryable APIConnectionError/ConnectError types. The root cause
|
|
115
|
+
# must win: repeating the same request cannot fix it.
|
|
116
|
+
return False
|
|
117
|
+
return is_transient(chain)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Exception text as the formatter and every classification rule read it."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _clean_exception_text(exc: BaseException) -> str:
|
|
9
|
+
"""Return ``str(exc)`` normalized for client wrapper prefixes."""
|
|
10
|
+
msg = str(exc).strip()
|
|
11
|
+
if msg.startswith("<class "):
|
|
12
|
+
msg = msg.split("> ", 1)[-1]
|
|
13
|
+
return msg
|