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,972 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Single-writer, write-through trajectory event log with written-ack.
|
|
4
|
+
|
|
5
|
+
Contract:
|
|
6
|
+
|
|
7
|
+
* **Write-through** — ``emit`` returns only after the whole line reached the
|
|
8
|
+
file (or after it is certain the event has no persistence guarantee). There
|
|
9
|
+
is no "returned but still queued" state. Normal events are not ``fsync``ed;
|
|
10
|
+
only checkpoints and the graceful-close tail are, and one of those answers
|
|
11
|
+
DEGRADED when its ``fsync`` fails even though its line was written.
|
|
12
|
+
* **Ordered hand-off for synchronous producers** — a call site that cannot
|
|
13
|
+
await (a ``finally`` in a sync function, a callback) uses ``submit`` +
|
|
14
|
+
``wait`` instead: ``submit`` takes the sequence and hands the encoded line
|
|
15
|
+
to the worker before it returns, so ordering against every later ``emit``
|
|
16
|
+
holds, and only the ack is awaited elsewhere. Such a producer learns
|
|
17
|
+
nothing about persistence — but a later awaited ``WRITTEN`` still proves
|
|
18
|
+
its line is on disk or explained by a gap.
|
|
19
|
+
* **Accounted prefix** — the worker writes strictly in ``sequence`` order. A
|
|
20
|
+
``WRITTEN`` ack for ``N`` proves every smaller sequence either has a line
|
|
21
|
+
or is covered by a ``trajectory.gap`` persisted before ``N``.
|
|
22
|
+
* **Bounded wait, terminal degrade** — a producer waits at most
|
|
23
|
+
``write_ack_timeout``; a timeout or write failure flips the writer into
|
|
24
|
+
``TERMINAL_DEGRADED`` (one-way). Queued-not-started events become virtual
|
|
25
|
+
slots with their payload released; the in-flight batch may finish and is
|
|
26
|
+
confirmed line by line; later emits only register slots. Only gap/closure
|
|
27
|
+
records may still be written, by the same worker, on the same descriptor.
|
|
28
|
+
* **Two-state result** — :class:`EmitResult.WRITTEN` carries the persistence
|
|
29
|
+
guarantee; :class:`EmitResult.DEGRADED` carries none.
|
|
30
|
+
|
|
31
|
+
The worker is a dedicated daemon thread (never a ``ThreadPoolExecutor``): a
|
|
32
|
+
thread stuck in ``write()`` on a network disk must not keep the interpreter
|
|
33
|
+
from exiting, and the writer never opens a second descriptor or thread for
|
|
34
|
+
the same session.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import asyncio
|
|
40
|
+
import concurrent.futures
|
|
41
|
+
import contextlib
|
|
42
|
+
import errno
|
|
43
|
+
import logging
|
|
44
|
+
import os
|
|
45
|
+
import threading
|
|
46
|
+
import time
|
|
47
|
+
from collections import deque
|
|
48
|
+
from collections.abc import Callable, Mapping
|
|
49
|
+
from dataclasses import dataclass, field
|
|
50
|
+
from enum import Enum
|
|
51
|
+
from typing import Any, Final, Protocol
|
|
52
|
+
|
|
53
|
+
from chrys.foundation.trajectory.envelope import (
|
|
54
|
+
INT64_MAX,
|
|
55
|
+
LINE_BUDGET_BYTES,
|
|
56
|
+
SYSTEM_ACTOR,
|
|
57
|
+
EventDraft,
|
|
58
|
+
TrajectoryEvent,
|
|
59
|
+
build_event,
|
|
60
|
+
check_int64_range,
|
|
61
|
+
encode_event_line,
|
|
62
|
+
malformed_id_pointers,
|
|
63
|
+
)
|
|
64
|
+
from chrys.foundation.trajectory.event_types import EventType, GapReason
|
|
65
|
+
|
|
66
|
+
logger = logging.getLogger(__name__)
|
|
67
|
+
|
|
68
|
+
DEFAULT_WRITE_ACK_TIMEOUT_SECONDS: Final = 3.0
|
|
69
|
+
DEFAULT_CLOSE_JOIN_TIMEOUT_SECONDS: Final = 2.0
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class EmitResult(Enum):
|
|
73
|
+
"""Outcome of one ``emit``; only ``WRITTEN`` carries a persistence guarantee."""
|
|
74
|
+
|
|
75
|
+
WRITTEN = "written"
|
|
76
|
+
DEGRADED = "degraded"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class WriterState(Enum):
|
|
80
|
+
"""Writer lifecycle; ``TERMINAL_DEGRADED`` is one-way."""
|
|
81
|
+
|
|
82
|
+
ACTIVE = "active"
|
|
83
|
+
TERMINAL_DEGRADED = "terminal_degraded"
|
|
84
|
+
CLOSED = "closed"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class WriteBackend(Protocol):
|
|
88
|
+
"""Minimal byte sink the worker thread drives; injectable for fault tests."""
|
|
89
|
+
|
|
90
|
+
def write(self, data: memoryview) -> int:
|
|
91
|
+
"""Write some prefix of *data*; return the byte count (may be short)."""
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
def fsync(self) -> None: ...
|
|
95
|
+
|
|
96
|
+
def truncate(self, size: int) -> None: ...
|
|
97
|
+
|
|
98
|
+
def close(self) -> None: ...
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class FdWriteBackend:
|
|
102
|
+
"""Backend over a raw descriptor from ``secure_open_owner_only_append``."""
|
|
103
|
+
|
|
104
|
+
def __init__(self, fd: int) -> None:
|
|
105
|
+
self._fd = fd
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def fd(self) -> int:
|
|
109
|
+
return self._fd
|
|
110
|
+
|
|
111
|
+
def write(self, data: memoryview) -> int:
|
|
112
|
+
return os.write(self._fd, data)
|
|
113
|
+
|
|
114
|
+
def fsync(self) -> None:
|
|
115
|
+
os.fsync(self._fd)
|
|
116
|
+
|
|
117
|
+
def truncate(self, size: int) -> None:
|
|
118
|
+
os.ftruncate(self._fd, size)
|
|
119
|
+
|
|
120
|
+
def close(self) -> None:
|
|
121
|
+
fd, self._fd = self._fd, -1
|
|
122
|
+
if fd >= 0:
|
|
123
|
+
os.close(fd)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(frozen=True, slots=True)
|
|
127
|
+
class DroppedRange:
|
|
128
|
+
"""One immutable range of confirmed-unwritten sequences."""
|
|
129
|
+
|
|
130
|
+
first_sequence: int
|
|
131
|
+
last_sequence: int
|
|
132
|
+
dropped_count: int
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(slots=True)
|
|
136
|
+
class _Pending:
|
|
137
|
+
sequence: int
|
|
138
|
+
data: bytes | None
|
|
139
|
+
future: concurrent.futures.Future[EmitResult]
|
|
140
|
+
fsync_after: bool = False
|
|
141
|
+
system: bool = False
|
|
142
|
+
covers: tuple[DroppedRange, ...] = ()
|
|
143
|
+
"""For gap records: compact ranges this gap accounts for."""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(slots=True)
|
|
147
|
+
class WriterSnapshot:
|
|
148
|
+
"""Point-in-time view for diagnostics and tests."""
|
|
149
|
+
|
|
150
|
+
state: WriterState
|
|
151
|
+
next_sequence: int
|
|
152
|
+
last_written_sequence: int
|
|
153
|
+
last_durable_sequence: int
|
|
154
|
+
uncovered_slots: tuple[int, ...]
|
|
155
|
+
uncovered_ranges: tuple[DroppedRange, ...]
|
|
156
|
+
inflight_sequences: tuple[int, ...]
|
|
157
|
+
queued_sequences: tuple[int, ...]
|
|
158
|
+
degraded_reason: str | None
|
|
159
|
+
tail_torn: bool
|
|
160
|
+
runtime_closed: bool
|
|
161
|
+
written_count: int = 0
|
|
162
|
+
written_bytes: int = 0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass(slots=True)
|
|
166
|
+
class _GapBookkeeping:
|
|
167
|
+
pending_by_gap_sequence: dict[int, tuple[DroppedRange, ...]] = field(default_factory=dict)
|
|
168
|
+
attempt_failed: bool = False
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass(frozen=True, slots=True)
|
|
172
|
+
class SubmittedEvent:
|
|
173
|
+
"""A queued line or the immediate result when nothing was queued (degraded/gap)."""
|
|
174
|
+
|
|
175
|
+
result: _Pending | EmitResult
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class TrajectoryWriter:
|
|
179
|
+
"""Per-session writer: one thread, one descriptor, one sequence counter."""
|
|
180
|
+
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
*,
|
|
184
|
+
backend: WriteBackend,
|
|
185
|
+
session_id: str,
|
|
186
|
+
runtime_id: str,
|
|
187
|
+
coverage_id: str,
|
|
188
|
+
branch_id: str,
|
|
189
|
+
initial_sequence: int = 0,
|
|
190
|
+
initial_offset: int = 0,
|
|
191
|
+
recovered_gap: tuple[int, int] | None = None,
|
|
192
|
+
write_ack_timeout: float = DEFAULT_WRITE_ACK_TIMEOUT_SECONDS,
|
|
193
|
+
on_degraded: Callable[[str], None] | None = None,
|
|
194
|
+
on_worker_exit: Callable[[], None] | None = None,
|
|
195
|
+
thread_name: str | None = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
if write_ack_timeout <= 0:
|
|
198
|
+
raise ValueError("write_ack_timeout must be positive.")
|
|
199
|
+
self._backend = backend
|
|
200
|
+
self._session_id = session_id
|
|
201
|
+
self._runtime_id = runtime_id
|
|
202
|
+
self._coverage_id = coverage_id
|
|
203
|
+
self._branch_id = branch_id
|
|
204
|
+
self._write_ack_timeout = write_ack_timeout
|
|
205
|
+
self._on_degraded = on_degraded
|
|
206
|
+
self._on_worker_exit = on_worker_exit
|
|
207
|
+
|
|
208
|
+
self._lock = threading.Lock()
|
|
209
|
+
self._cond = threading.Condition(self._lock)
|
|
210
|
+
self._queue: deque[_Pending] = deque()
|
|
211
|
+
self._inflight: list[_Pending] = []
|
|
212
|
+
self._state = WriterState.ACTIVE
|
|
213
|
+
self._frozen = False
|
|
214
|
+
self._stop_requested = False
|
|
215
|
+
self._next_sequence = initial_sequence + 1
|
|
216
|
+
self._last_written = initial_sequence
|
|
217
|
+
self._last_durable = 0
|
|
218
|
+
self._confirmed_offset = initial_offset
|
|
219
|
+
self._uncovered_slots: list[int] = []
|
|
220
|
+
self._uncovered_ranges: list[DroppedRange] = []
|
|
221
|
+
self._gap = _GapBookkeeping()
|
|
222
|
+
self._degraded_reason: str | None = None
|
|
223
|
+
self._tail_torn = False
|
|
224
|
+
self._runtime_closed = False
|
|
225
|
+
self._written_count = 0
|
|
226
|
+
self._written_bytes = 0
|
|
227
|
+
self._thread = threading.Thread(
|
|
228
|
+
target=self._run,
|
|
229
|
+
name=thread_name or f"chrys-trajectory-writer-{session_id[:8]}",
|
|
230
|
+
daemon=True,
|
|
231
|
+
)
|
|
232
|
+
self._started = False
|
|
233
|
+
self._resources_released = False
|
|
234
|
+
self._abort_truncate_to: int | None = None
|
|
235
|
+
self._abort_rewound = False
|
|
236
|
+
if recovered_gap is not None:
|
|
237
|
+
# Queued here rather than on ``start``: the first emit starts the
|
|
238
|
+
# worker too, so the only place this gap is guaranteed to precede
|
|
239
|
+
# every line of the resumed runtime is before anyone holds the
|
|
240
|
+
# writer at all.
|
|
241
|
+
first, last = recovered_gap
|
|
242
|
+
if 1 <= first <= last:
|
|
243
|
+
with self._lock:
|
|
244
|
+
recovered = DroppedRange(
|
|
245
|
+
first_sequence=first,
|
|
246
|
+
last_sequence=last,
|
|
247
|
+
dropped_count=last - first + 1,
|
|
248
|
+
)
|
|
249
|
+
self._queue_gap_locked(
|
|
250
|
+
first=first,
|
|
251
|
+
last=last,
|
|
252
|
+
reason=GapReason.RECOVERED_UNREADABLE,
|
|
253
|
+
covers=(recovered,),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# ------------------------------------------------------------------ props
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def state(self) -> WriterState:
|
|
260
|
+
return self._state
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def runtime_id(self) -> str:
|
|
264
|
+
return self._runtime_id
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def coverage_id(self) -> str:
|
|
268
|
+
return self._coverage_id
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def session_id(self) -> str:
|
|
272
|
+
return self._session_id
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def branch_id(self) -> str:
|
|
276
|
+
return self._branch_id
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def write_ack_timeout(self) -> float:
|
|
280
|
+
return self._write_ack_timeout
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def is_degraded(self) -> bool:
|
|
284
|
+
return self._state is WriterState.TERMINAL_DEGRADED
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def runtime_closed(self) -> bool:
|
|
288
|
+
return self._runtime_closed
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def thread_alive(self) -> bool:
|
|
292
|
+
return self._thread.is_alive()
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def last_assigned_sequence(self) -> int:
|
|
296
|
+
"""The highest sequence handed out so far (0 before the first event)."""
|
|
297
|
+
with self._lock:
|
|
298
|
+
return self._next_sequence - 1
|
|
299
|
+
|
|
300
|
+
def snapshot(self) -> WriterSnapshot:
|
|
301
|
+
with self._lock:
|
|
302
|
+
return WriterSnapshot(
|
|
303
|
+
state=self._state,
|
|
304
|
+
next_sequence=self._next_sequence,
|
|
305
|
+
last_written_sequence=self._last_written,
|
|
306
|
+
last_durable_sequence=self._last_durable,
|
|
307
|
+
uncovered_slots=tuple(self._uncovered_slots),
|
|
308
|
+
uncovered_ranges=tuple(self._uncovered_ranges),
|
|
309
|
+
inflight_sequences=tuple(p.sequence for p in self._inflight),
|
|
310
|
+
queued_sequences=tuple(p.sequence for p in self._queue),
|
|
311
|
+
degraded_reason=self._degraded_reason,
|
|
312
|
+
tail_torn=self._tail_torn,
|
|
313
|
+
runtime_closed=self._runtime_closed,
|
|
314
|
+
written_count=self._written_count,
|
|
315
|
+
written_bytes=self._written_bytes,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def set_branch_id(self, branch_id: str) -> None:
|
|
319
|
+
"""Switch the branch stamped on subsequent events (rollback)."""
|
|
320
|
+
with self._lock:
|
|
321
|
+
self._branch_id = branch_id
|
|
322
|
+
|
|
323
|
+
def start(self) -> None:
|
|
324
|
+
"""Start the worker thread (idempotent; the first emit also starts it)."""
|
|
325
|
+
with self._lock:
|
|
326
|
+
if self._started:
|
|
327
|
+
return
|
|
328
|
+
# Only a thread that actually started may be joined later: claiming
|
|
329
|
+
# one that the system refused would make ``_stop_worker`` raise on
|
|
330
|
+
# the join and strand the descriptor and the session's lease.
|
|
331
|
+
self._thread.start()
|
|
332
|
+
self._started = True
|
|
333
|
+
|
|
334
|
+
def measure_line(self, draft: EventDraft) -> int:
|
|
335
|
+
"""Return the encoded line length *draft* would have at the next sequence."""
|
|
336
|
+
event = build_event(
|
|
337
|
+
draft,
|
|
338
|
+
sequence=10**9,
|
|
339
|
+
runtime_id=self._runtime_id,
|
|
340
|
+
coverage_id=self._coverage_id,
|
|
341
|
+
session_id=self._session_id,
|
|
342
|
+
branch_id=self._branch_id,
|
|
343
|
+
)
|
|
344
|
+
return len(encode_event_line(event))
|
|
345
|
+
|
|
346
|
+
# ------------------------------------------------------------------ emit
|
|
347
|
+
|
|
348
|
+
def submit(
|
|
349
|
+
self, draft: EventDraft, *, payload_factory: Callable[[int], Mapping[str, Any]] | None = None
|
|
350
|
+
) -> SubmittedEvent:
|
|
351
|
+
"""Assign the sequence and queue the line now; the ack is awaited separately.
|
|
352
|
+
|
|
353
|
+
For producers that cannot await at the point the event happens (a
|
|
354
|
+
synchronous stream hook) but must keep write order: the sequence is
|
|
355
|
+
taken synchronously here, so a later ``emit`` from the same task
|
|
356
|
+
always lands after it. Pass the result to :meth:`wait` to observe
|
|
357
|
+
the ack and keep the bounded-wait contract.
|
|
358
|
+
"""
|
|
359
|
+
return SubmittedEvent(self._prepare(draft, payload_factory=payload_factory))
|
|
360
|
+
|
|
361
|
+
async def wait(self, submitted: SubmittedEvent) -> EmitResult:
|
|
362
|
+
"""Await the ack of a :meth:`submit` (timeout flips the writer degraded)."""
|
|
363
|
+
result = submitted.result
|
|
364
|
+
if isinstance(result, EmitResult):
|
|
365
|
+
return result
|
|
366
|
+
return await self._await_ack(result)
|
|
367
|
+
|
|
368
|
+
async def emit(
|
|
369
|
+
self, draft: EventDraft, *, payload_factory: Callable[[int], Mapping[str, Any]] | None = None
|
|
370
|
+
) -> EmitResult:
|
|
371
|
+
"""Write one event through; returns ``WRITTEN`` only once the line is in the file.
|
|
372
|
+
|
|
373
|
+
The wait is bounded by ``write_ack_timeout``; the write request is
|
|
374
|
+
shielded from the caller's cancellation (a cancelled producer's line is
|
|
375
|
+
still written). Timeout flips the writer into ``TERMINAL_DEGRADED``.
|
|
376
|
+
|
|
377
|
+
``payload_factory`` builds the payload from the assigned sequence for
|
|
378
|
+
events whose payload refers to their own position (``session.rollback``
|
|
379
|
+
closes its superseded range at ``sequence - 1``).
|
|
380
|
+
"""
|
|
381
|
+
prepared = self._prepare(draft, payload_factory=payload_factory)
|
|
382
|
+
if isinstance(prepared, EmitResult):
|
|
383
|
+
return prepared
|
|
384
|
+
return await self._await_ack(prepared)
|
|
385
|
+
|
|
386
|
+
def emit_blocking(
|
|
387
|
+
self, draft: EventDraft, *, payload_factory: Callable[[int], Mapping[str, Any]] | None = None
|
|
388
|
+
) -> EmitResult:
|
|
389
|
+
"""Synchronous ``emit`` for threads that do not run an event loop."""
|
|
390
|
+
prepared = self._prepare(draft, payload_factory=payload_factory)
|
|
391
|
+
if isinstance(prepared, EmitResult):
|
|
392
|
+
return prepared
|
|
393
|
+
return self._wait_ack_blocking(prepared)
|
|
394
|
+
|
|
395
|
+
async def checkpoint(self) -> EmitResult:
|
|
396
|
+
"""Write ``trajectory.checkpoint`` and ``fsync``; ``last_durable`` advances here only."""
|
|
397
|
+
|
|
398
|
+
def payload(sequence: int) -> dict[str, Any]:
|
|
399
|
+
return {
|
|
400
|
+
"last_assigned": sequence,
|
|
401
|
+
"last_written": self._last_written,
|
|
402
|
+
"last_durable": self._last_durable,
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
prepared = self._prepare(
|
|
406
|
+
EventDraft(event_type=EventType.CHECKPOINT, actor=SYSTEM_ACTOR),
|
|
407
|
+
payload_factory=payload,
|
|
408
|
+
fsync_after=True,
|
|
409
|
+
)
|
|
410
|
+
if isinstance(prepared, EmitResult):
|
|
411
|
+
return prepared
|
|
412
|
+
return await self._await_ack(prepared)
|
|
413
|
+
|
|
414
|
+
async def close(self, *, reason: str, join_timeout: float = DEFAULT_CLOSE_JOIN_TIMEOUT_SECONDS) -> bool:
|
|
415
|
+
"""Graceful close: freeze → (final gap) → coverage.ended → runtime.finished → fsync → close.
|
|
416
|
+
|
|
417
|
+
Returns ``True`` when the runtime was closed cleanly (both closure
|
|
418
|
+
markers ``WRITTEN`` and durable). Any failure leaves the runtime
|
|
419
|
+
unclosed on disk, which is exactly the evidence a reader needs.
|
|
420
|
+
"""
|
|
421
|
+
with self._lock:
|
|
422
|
+
self._frozen = True
|
|
423
|
+
closed = False
|
|
424
|
+
try:
|
|
425
|
+
if self._state is WriterState.ACTIVE:
|
|
426
|
+
closed = await self._close_active(reason)
|
|
427
|
+
elif self._state is WriterState.TERMINAL_DEGRADED:
|
|
428
|
+
closed = await self._close_degraded(reason)
|
|
429
|
+
except Exception:
|
|
430
|
+
# Closing is the last thing this writer does, so a failure here has
|
|
431
|
+
# nowhere left to be handled — and an unclosed runtime on disk is
|
|
432
|
+
# already how that reads. What must not be lost is the handback
|
|
433
|
+
# below: the descriptor and the session's lease come back either
|
|
434
|
+
# way, or the session stays leased for the rest of the process.
|
|
435
|
+
logger.warning("Trajectory writer close failed for session %s", self._session_id, exc_info=True)
|
|
436
|
+
finally:
|
|
437
|
+
self._stop_worker(join_timeout)
|
|
438
|
+
return closed
|
|
439
|
+
|
|
440
|
+
def abort(self, *, join_timeout: float = DEFAULT_CLOSE_JOIN_TIMEOUT_SECONDS) -> bool:
|
|
441
|
+
"""Erase and stop an unannounced runtime without writing closure markers.
|
|
442
|
+
|
|
443
|
+
Activation owns this path: if its prelude could not complete, emitting
|
|
444
|
+
``coverage.ended`` or ``runtime.finished`` would create terminals for a
|
|
445
|
+
runtime readers never saw start. The worker rewinds every prelude byte
|
|
446
|
+
it may have written before releasing its descriptor and lease, allowing
|
|
447
|
+
activation to retry from the exact prefix it originally recovered.
|
|
448
|
+
"""
|
|
449
|
+
with self._lock:
|
|
450
|
+
self._frozen = True
|
|
451
|
+
self._abort_truncate_to = self._confirmed_offset - self._written_bytes
|
|
452
|
+
self._stop_worker(join_timeout)
|
|
453
|
+
return not self.thread_alive and self._abort_rewound
|
|
454
|
+
|
|
455
|
+
# --------------------------------------------------------------- internals
|
|
456
|
+
|
|
457
|
+
def _prepare(
|
|
458
|
+
self,
|
|
459
|
+
draft: EventDraft,
|
|
460
|
+
*,
|
|
461
|
+
payload_factory: Callable[[int], Mapping[str, Any]] | None = None,
|
|
462
|
+
fsync_after: bool = False,
|
|
463
|
+
system: bool = False,
|
|
464
|
+
covers: tuple[DroppedRange, ...] = (),
|
|
465
|
+
) -> _Pending | EmitResult:
|
|
466
|
+
with self._lock:
|
|
467
|
+
if (self._frozen and not system) or self._stop_requested:
|
|
468
|
+
return EmitResult.DEGRADED
|
|
469
|
+
sequence = self._next_sequence
|
|
470
|
+
self._next_sequence += 1
|
|
471
|
+
if self._state is not WriterState.ACTIVE and not system:
|
|
472
|
+
# Virtual slot only: no payload is retained, no wait is taken.
|
|
473
|
+
self._uncovered_slots.append(sequence)
|
|
474
|
+
return EmitResult.DEGRADED
|
|
475
|
+
try:
|
|
476
|
+
payload = payload_factory(sequence) if payload_factory is not None else None
|
|
477
|
+
event = build_event(
|
|
478
|
+
draft if payload is None else _with_payload(draft, payload),
|
|
479
|
+
sequence=sequence,
|
|
480
|
+
runtime_id=self._runtime_id,
|
|
481
|
+
coverage_id=self._coverage_id,
|
|
482
|
+
session_id=self._session_id,
|
|
483
|
+
branch_id=self._branch_id,
|
|
484
|
+
)
|
|
485
|
+
encoded = _encode_checked(event)
|
|
486
|
+
except Exception:
|
|
487
|
+
# The slot is spent the moment it is handed out. A factory that
|
|
488
|
+
# raises — or a draft that cannot be built into an event —
|
|
489
|
+
# leaves nothing to write, which is the encode failure below by
|
|
490
|
+
# another route: letting it out of here instead would drop the
|
|
491
|
+
# sequence from the prefix with no gap to explain it.
|
|
492
|
+
logger.warning(
|
|
493
|
+
"Trajectory event for sequence %d could not be built; recording a gap", sequence, exc_info=True
|
|
494
|
+
)
|
|
495
|
+
encoded = GapReason.ENCODE_FAILURE
|
|
496
|
+
if isinstance(encoded, str):
|
|
497
|
+
# The event itself cannot be written (over budget, out of range,
|
|
498
|
+
# unencodable). Its slot is accounted for by a gap that takes the
|
|
499
|
+
# next sequence, so the prefix stays explained.
|
|
500
|
+
self._uncovered_slots.append(sequence)
|
|
501
|
+
self._enqueue_gap_locked(reason=encoded)
|
|
502
|
+
return EmitResult.DEGRADED
|
|
503
|
+
pending = _Pending(
|
|
504
|
+
sequence=sequence,
|
|
505
|
+
data=encoded,
|
|
506
|
+
future=concurrent.futures.Future(),
|
|
507
|
+
fsync_after=fsync_after,
|
|
508
|
+
system=system,
|
|
509
|
+
covers=covers,
|
|
510
|
+
)
|
|
511
|
+
self._queue.append(pending)
|
|
512
|
+
self._cond.notify()
|
|
513
|
+
if not self._started:
|
|
514
|
+
self._thread.start()
|
|
515
|
+
self._started = True
|
|
516
|
+
return pending
|
|
517
|
+
|
|
518
|
+
async def _await_ack(self, pending: _Pending) -> EmitResult:
|
|
519
|
+
loop = asyncio.get_running_loop()
|
|
520
|
+
wrapped = asyncio.wrap_future(pending.future, loop=loop)
|
|
521
|
+
try:
|
|
522
|
+
return await asyncio.wait_for(asyncio.shield(wrapped), self._write_ack_timeout)
|
|
523
|
+
except TimeoutError:
|
|
524
|
+
# The worker resolves the concurrent future from its own thread;
|
|
525
|
+
# ``wrap_future`` then schedules delivery onto this event loop.
|
|
526
|
+
# Under scheduler pressure the timeout callback can run before
|
|
527
|
+
# that delivery callback even though the write already finished.
|
|
528
|
+
# Read the worker-owned result directly before declaring the
|
|
529
|
+
# writer degraded so an event-loop delay cannot turn a persisted
|
|
530
|
+
# lifecycle terminal into a virtual slot.
|
|
531
|
+
if pending.future.done():
|
|
532
|
+
return pending.future.result()
|
|
533
|
+
self._enter_terminal_degraded(GapReason.WRITE_TIMEOUT)
|
|
534
|
+
return EmitResult.DEGRADED
|
|
535
|
+
|
|
536
|
+
def _wait_ack_blocking(self, pending: _Pending) -> EmitResult:
|
|
537
|
+
try:
|
|
538
|
+
return pending.future.result(timeout=self._write_ack_timeout)
|
|
539
|
+
except concurrent.futures.TimeoutError:
|
|
540
|
+
self._enter_terminal_degraded(GapReason.WRITE_TIMEOUT)
|
|
541
|
+
return EmitResult.DEGRADED
|
|
542
|
+
|
|
543
|
+
def _enter_terminal_degraded(self, reason: str) -> None:
|
|
544
|
+
dropped: list[_Pending] = []
|
|
545
|
+
with self._lock:
|
|
546
|
+
if self._state is not WriterState.ACTIVE:
|
|
547
|
+
return
|
|
548
|
+
self._state = WriterState.TERMINAL_DEGRADED
|
|
549
|
+
self._degraded_reason = reason
|
|
550
|
+
# Queue classification at the transition: everything still in the
|
|
551
|
+
# queue is "queued, not started" — the worker moves a batch out of
|
|
552
|
+
# the queue under this lock before touching the descriptor, so the
|
|
553
|
+
# in-flight batch is never here. Those slots are confirmed
|
|
554
|
+
# unwritten; release their payload and answer their waiters now.
|
|
555
|
+
while self._queue:
|
|
556
|
+
item = self._queue.popleft()
|
|
557
|
+
item.data = None
|
|
558
|
+
if item.covers:
|
|
559
|
+
self._uncovered_ranges.extend(self._gap.pending_by_gap_sequence.pop(item.sequence, item.covers))
|
|
560
|
+
self._uncovered_slots.append(item.sequence)
|
|
561
|
+
dropped.append(item)
|
|
562
|
+
self._uncovered_slots.sort()
|
|
563
|
+
self._cond.notify_all()
|
|
564
|
+
for item in dropped:
|
|
565
|
+
_resolve(item.future, EmitResult.DEGRADED)
|
|
566
|
+
logger.warning(
|
|
567
|
+
"Trajectory writer for session %s entered TERMINAL_DEGRADED (%s); "
|
|
568
|
+
"later events carry no persistence guarantee.",
|
|
569
|
+
self._session_id,
|
|
570
|
+
reason,
|
|
571
|
+
)
|
|
572
|
+
if self._on_degraded is not None:
|
|
573
|
+
with contextlib.suppress(Exception):
|
|
574
|
+
self._on_degraded(reason)
|
|
575
|
+
|
|
576
|
+
def _enqueue_gap_locked(self, *, reason: str) -> list[_Pending]:
|
|
577
|
+
"""Queue one gap per contiguous run of uncovered slots (lock held)."""
|
|
578
|
+
if self._tail_torn or not (self._uncovered_slots or self._uncovered_ranges):
|
|
579
|
+
return []
|
|
580
|
+
if self._next_sequence > INT64_MAX:
|
|
581
|
+
# The gap would have to carry a sequence no line may carry, and it
|
|
582
|
+
# is built past the check that refuses an ordinary event for that
|
|
583
|
+
# — it would land as a line no reader can decode. Nothing after
|
|
584
|
+
# this slot is writable either, so the file ends here: an unwritten
|
|
585
|
+
# slot at the end of a file reads as a runtime that stopped, which
|
|
586
|
+
# is a shape readers already handle.
|
|
587
|
+
return []
|
|
588
|
+
ranges = _coalesced_dropped_ranges(
|
|
589
|
+
[
|
|
590
|
+
*self._uncovered_ranges,
|
|
591
|
+
*(
|
|
592
|
+
DroppedRange(first_sequence=run[0], last_sequence=run[-1], dropped_count=len(run))
|
|
593
|
+
for run in _contiguous_runs(self._uncovered_slots)
|
|
594
|
+
),
|
|
595
|
+
]
|
|
596
|
+
)
|
|
597
|
+
self._uncovered_slots = []
|
|
598
|
+
self._uncovered_ranges = []
|
|
599
|
+
return [
|
|
600
|
+
self._queue_gap_locked(
|
|
601
|
+
first=dropped.first_sequence,
|
|
602
|
+
last=dropped.last_sequence,
|
|
603
|
+
reason=reason,
|
|
604
|
+
covers=(dropped,),
|
|
605
|
+
)
|
|
606
|
+
for dropped in ranges
|
|
607
|
+
]
|
|
608
|
+
|
|
609
|
+
def _queue_gap_locked(
|
|
610
|
+
self,
|
|
611
|
+
*,
|
|
612
|
+
first: int,
|
|
613
|
+
last: int,
|
|
614
|
+
reason: str,
|
|
615
|
+
covers: tuple[DroppedRange, ...],
|
|
616
|
+
) -> _Pending:
|
|
617
|
+
"""Queue one gap over ``[first, last]`` (lock held).
|
|
618
|
+
|
|
619
|
+
``covers`` keeps compact intervals to hand back if this gap is never
|
|
620
|
+
written, so the next one explains them again. Recovered corruption can
|
|
621
|
+
claim an enormous sequence range; retaining intervals here avoids the
|
|
622
|
+
unbounded ``range`` expansion that per-slot bookkeeping would require.
|
|
623
|
+
"""
|
|
624
|
+
sequence = self._next_sequence
|
|
625
|
+
self._next_sequence += 1
|
|
626
|
+
draft = EventDraft(
|
|
627
|
+
event_type=EventType.GAP,
|
|
628
|
+
actor=SYSTEM_ACTOR,
|
|
629
|
+
payload={
|
|
630
|
+
"first_sequence": first,
|
|
631
|
+
"last_sequence": last,
|
|
632
|
+
"dropped_count": last - first + 1,
|
|
633
|
+
"reason": reason,
|
|
634
|
+
},
|
|
635
|
+
)
|
|
636
|
+
event = build_event(
|
|
637
|
+
draft,
|
|
638
|
+
sequence=sequence,
|
|
639
|
+
runtime_id=self._runtime_id,
|
|
640
|
+
coverage_id=self._coverage_id,
|
|
641
|
+
session_id=self._session_id,
|
|
642
|
+
branch_id=self._branch_id,
|
|
643
|
+
)
|
|
644
|
+
pending = _Pending(
|
|
645
|
+
sequence=sequence,
|
|
646
|
+
data=encode_event_line(event),
|
|
647
|
+
future=concurrent.futures.Future(),
|
|
648
|
+
system=True,
|
|
649
|
+
covers=covers,
|
|
650
|
+
)
|
|
651
|
+
if covers:
|
|
652
|
+
self._gap.pending_by_gap_sequence[sequence] = covers
|
|
653
|
+
self._queue.append(pending)
|
|
654
|
+
self._cond.notify()
|
|
655
|
+
return pending
|
|
656
|
+
|
|
657
|
+
# --------------------------------------------------------------- worker
|
|
658
|
+
|
|
659
|
+
def _run(self) -> None:
|
|
660
|
+
try:
|
|
661
|
+
self._drain()
|
|
662
|
+
finally:
|
|
663
|
+
# The descriptor and the session's writer lease belong to this
|
|
664
|
+
# thread for as long as it may still write, so this thread is what
|
|
665
|
+
# hands them back — on request, or whenever a blocked ``write()``
|
|
666
|
+
# finally lets go. Nothing outside can observe that moment without
|
|
667
|
+
# a thread of its own, and a thread is exactly what a system out
|
|
668
|
+
# of them cannot give.
|
|
669
|
+
self._release_worker_resources()
|
|
670
|
+
|
|
671
|
+
def _drain(self) -> None:
|
|
672
|
+
while True:
|
|
673
|
+
with self._cond:
|
|
674
|
+
while not self._queue and not self._stop_requested:
|
|
675
|
+
self._cond.wait()
|
|
676
|
+
if not self._queue and self._stop_requested:
|
|
677
|
+
return
|
|
678
|
+
batch = list(self._queue)
|
|
679
|
+
self._queue.clear()
|
|
680
|
+
self._inflight = batch
|
|
681
|
+
self._write_batch(batch)
|
|
682
|
+
with self._lock:
|
|
683
|
+
self._inflight = []
|
|
684
|
+
# ``CLOSED`` counts too: a close that timed out on this thread
|
|
685
|
+
# moved the state on and left, and the slots it could not cover
|
|
686
|
+
# are no less lost for that. This thread still holds the file,
|
|
687
|
+
# so it is the only place left that can say so — without it the
|
|
688
|
+
# next runtime resumes on a slot that was handed out already
|
|
689
|
+
# and nothing on disk records that an event went missing.
|
|
690
|
+
settling = self._state in (WriterState.TERMINAL_DEGRADED, WriterState.CLOSED)
|
|
691
|
+
if settling and not self._gap.attempt_failed and (self._uncovered_slots or self._uncovered_ranges):
|
|
692
|
+
self._enqueue_gap_locked(reason=self._degraded_reason or GapReason.WRITE_FAILURE)
|
|
693
|
+
|
|
694
|
+
def _write_batch(self, batch: list[_Pending]) -> None:
|
|
695
|
+
payload = b"".join(item.data or b"" for item in batch)
|
|
696
|
+
view = memoryview(payload)
|
|
697
|
+
written = 0
|
|
698
|
+
failure: BaseException | None = None
|
|
699
|
+
try:
|
|
700
|
+
while written < len(payload):
|
|
701
|
+
count = self._backend.write(view[written:])
|
|
702
|
+
if not isinstance(count, int) or count <= 0:
|
|
703
|
+
raise OSError(errno.EIO, "Trajectory backend wrote nothing.")
|
|
704
|
+
written += count
|
|
705
|
+
except BaseException as exc: # a stuck/failing backend must never kill the thread
|
|
706
|
+
failure = exc
|
|
707
|
+
if failure is None:
|
|
708
|
+
self._complete_batch(batch, written=written)
|
|
709
|
+
return
|
|
710
|
+
self._fail_batch(batch, written=written, failure=failure)
|
|
711
|
+
|
|
712
|
+
def _complete_batch(self, batch: list[_Pending], *, written: int) -> None:
|
|
713
|
+
fsync_failure: BaseException | None = None
|
|
714
|
+
needs_fsync = any(item.fsync_after for item in batch)
|
|
715
|
+
if needs_fsync:
|
|
716
|
+
try:
|
|
717
|
+
self._backend.fsync()
|
|
718
|
+
except BaseException as exc:
|
|
719
|
+
fsync_failure = exc
|
|
720
|
+
to_resolve: list[tuple[_Pending, EmitResult]] = []
|
|
721
|
+
with self._lock:
|
|
722
|
+
self._confirmed_offset += written
|
|
723
|
+
for item in batch:
|
|
724
|
+
self._last_written = item.sequence
|
|
725
|
+
self._written_count += 1
|
|
726
|
+
self._written_bytes += len(item.data or b"")
|
|
727
|
+
if item.covers:
|
|
728
|
+
self._gap.pending_by_gap_sequence.pop(item.sequence, None)
|
|
729
|
+
item.data = None
|
|
730
|
+
# Every line reached the file. An event that asked to be made
|
|
731
|
+
# durable asked for more than that, so a failed ``fsync`` is
|
|
732
|
+
# answered with DEGRADED: the only callers that set the flag
|
|
733
|
+
# are the checkpoint and the closure tail, and both must be
|
|
734
|
+
# able to tell "written" from "written and durable".
|
|
735
|
+
durable_denied = fsync_failure is not None and item.fsync_after
|
|
736
|
+
to_resolve.append((item, EmitResult.DEGRADED if durable_denied else EmitResult.WRITTEN))
|
|
737
|
+
if needs_fsync and fsync_failure is None:
|
|
738
|
+
self._last_durable = batch[-1].sequence
|
|
739
|
+
for item, result in to_resolve:
|
|
740
|
+
_resolve(item.future, result)
|
|
741
|
+
if fsync_failure is not None:
|
|
742
|
+
logger.warning("Trajectory checkpoint fsync failed for session %s: %s", self._session_id, fsync_failure)
|
|
743
|
+
|
|
744
|
+
def _fail_batch(self, batch: list[_Pending], *, written: int, failure: BaseException) -> None:
|
|
745
|
+
to_resolve: list[tuple[_Pending, EmitResult]] = []
|
|
746
|
+
truncate_to: int | None = None
|
|
747
|
+
with self._lock:
|
|
748
|
+
offset = 0
|
|
749
|
+
confirmed = 0
|
|
750
|
+
torn = False
|
|
751
|
+
for item in batch:
|
|
752
|
+
size = len(item.data or b"")
|
|
753
|
+
end = offset + size
|
|
754
|
+
if end <= written:
|
|
755
|
+
confirmed = end
|
|
756
|
+
self._last_written = item.sequence
|
|
757
|
+
self._written_count += 1
|
|
758
|
+
self._written_bytes += size
|
|
759
|
+
if item.covers:
|
|
760
|
+
self._gap.pending_by_gap_sequence.pop(item.sequence, None)
|
|
761
|
+
to_resolve.append((item, EmitResult.WRITTEN))
|
|
762
|
+
else:
|
|
763
|
+
if offset < written:
|
|
764
|
+
torn = True
|
|
765
|
+
if item.covers:
|
|
766
|
+
self._uncovered_ranges.extend(self._gap.pending_by_gap_sequence.pop(item.sequence, ()))
|
|
767
|
+
self._gap.attempt_failed = True
|
|
768
|
+
self._uncovered_slots.append(item.sequence)
|
|
769
|
+
to_resolve.append((item, EmitResult.DEGRADED))
|
|
770
|
+
item.data = None
|
|
771
|
+
offset = end
|
|
772
|
+
self._uncovered_slots.sort()
|
|
773
|
+
self._confirmed_offset += confirmed
|
|
774
|
+
if torn:
|
|
775
|
+
truncate_to = self._confirmed_offset
|
|
776
|
+
# Transition first so a producer that observes DEGRADED already sees the
|
|
777
|
+
# terminal state (and the on_degraded callback has fired).
|
|
778
|
+
self._enter_terminal_degraded(GapReason.WRITE_FAILURE)
|
|
779
|
+
logger.warning("Trajectory write failed for session %s: %s", self._session_id, failure)
|
|
780
|
+
for item, result in to_resolve:
|
|
781
|
+
_resolve(item.future, result)
|
|
782
|
+
if truncate_to is not None:
|
|
783
|
+
# Repair our own torn tail so a later gap/closure line does not land
|
|
784
|
+
# after a half-written line (which would be mid-file corruption).
|
|
785
|
+
try:
|
|
786
|
+
self._backend.truncate(truncate_to)
|
|
787
|
+
except BaseException as exc:
|
|
788
|
+
with self._lock:
|
|
789
|
+
self._tail_torn = True
|
|
790
|
+
logger.warning("Trajectory torn-tail repair failed for session %s: %s", self._session_id, exc)
|
|
791
|
+
|
|
792
|
+
# --------------------------------------------------------------- closing
|
|
793
|
+
|
|
794
|
+
async def _close_active(self, reason: str) -> bool:
|
|
795
|
+
ended = await self._emit_system(
|
|
796
|
+
EventType.COVERAGE_ENDED,
|
|
797
|
+
payload_factory=lambda seq: {"coverage_id": self._coverage_id, "last_sequence": seq - 1},
|
|
798
|
+
)
|
|
799
|
+
if ended is not EmitResult.WRITTEN:
|
|
800
|
+
return False
|
|
801
|
+
finished = await self._emit_system(
|
|
802
|
+
EventType.RUNTIME_FINISHED,
|
|
803
|
+
payload_factory=lambda _seq: {"reason": reason},
|
|
804
|
+
fsync_after=True,
|
|
805
|
+
)
|
|
806
|
+
if finished is not EmitResult.WRITTEN:
|
|
807
|
+
return False
|
|
808
|
+
with self._lock:
|
|
809
|
+
self._runtime_closed = True
|
|
810
|
+
return True
|
|
811
|
+
|
|
812
|
+
async def _close_degraded(self, reason: str) -> bool:
|
|
813
|
+
# ① emit is frozen (done by close()). ② wait for the in-flight batch to
|
|
814
|
+
# settle — a batch that never returns leaves slots neither written nor
|
|
815
|
+
# dropped, so no final gap can be constructed and the runtime stays open.
|
|
816
|
+
deadline = time.monotonic() + self._write_ack_timeout
|
|
817
|
+
while True:
|
|
818
|
+
with self._lock:
|
|
819
|
+
inflight = bool(self._inflight)
|
|
820
|
+
torn = self._tail_torn
|
|
821
|
+
if not inflight:
|
|
822
|
+
break
|
|
823
|
+
if time.monotonic() >= deadline:
|
|
824
|
+
return False
|
|
825
|
+
await asyncio.sleep(0.01)
|
|
826
|
+
if torn:
|
|
827
|
+
return False
|
|
828
|
+
with self._lock:
|
|
829
|
+
self._gap.attempt_failed = False
|
|
830
|
+
gaps = self._enqueue_gap_locked(reason=GapReason.DEGRADED_CLOSE)
|
|
831
|
+
for pending in gaps:
|
|
832
|
+
if await self._await_ack(pending) is not EmitResult.WRITTEN:
|
|
833
|
+
return False
|
|
834
|
+
with self._lock:
|
|
835
|
+
if self._uncovered_slots or self._uncovered_ranges:
|
|
836
|
+
return False
|
|
837
|
+
return await self._close_active(reason)
|
|
838
|
+
|
|
839
|
+
async def _emit_system(
|
|
840
|
+
self,
|
|
841
|
+
event_type: str,
|
|
842
|
+
*,
|
|
843
|
+
payload_factory: Callable[[int], dict[str, Any]],
|
|
844
|
+
fsync_after: bool = False,
|
|
845
|
+
) -> EmitResult:
|
|
846
|
+
prepared = self._prepare(
|
|
847
|
+
EventDraft(event_type=event_type, actor=SYSTEM_ACTOR),
|
|
848
|
+
payload_factory=payload_factory,
|
|
849
|
+
fsync_after=fsync_after,
|
|
850
|
+
system=True,
|
|
851
|
+
)
|
|
852
|
+
if isinstance(prepared, EmitResult):
|
|
853
|
+
return prepared
|
|
854
|
+
return await self._await_ack(prepared)
|
|
855
|
+
|
|
856
|
+
def _stop_worker(self, join_timeout: float) -> None:
|
|
857
|
+
with self._lock:
|
|
858
|
+
self._stop_requested = True
|
|
859
|
+
self._cond.notify_all()
|
|
860
|
+
if self._started:
|
|
861
|
+
self._thread.join(timeout=max(0.0, join_timeout))
|
|
862
|
+
if self._thread.is_alive():
|
|
863
|
+
# A stuck worker keeps its descriptor: closing it underneath a
|
|
864
|
+
# blocked write() could let the number be reused by another file.
|
|
865
|
+
logger.warning(
|
|
866
|
+
"Trajectory writer thread for session %s did not exit within %.1fs; abandoning it.",
|
|
867
|
+
self._session_id,
|
|
868
|
+
join_timeout,
|
|
869
|
+
)
|
|
870
|
+
with self._lock:
|
|
871
|
+
self._state = WriterState.CLOSED
|
|
872
|
+
# The worker hands its own resources back when it returns.
|
|
873
|
+
return
|
|
874
|
+
with self._lock:
|
|
875
|
+
self._state = WriterState.CLOSED
|
|
876
|
+
self._release_worker_resources()
|
|
877
|
+
|
|
878
|
+
def _release_worker_resources(self) -> None:
|
|
879
|
+
"""Close the descriptor and hand the lease back; runs at most once.
|
|
880
|
+
|
|
881
|
+
Reached from the worker's own exit and from ``_stop_worker`` (which
|
|
882
|
+
covers a worker that never started), so it has to settle who does it.
|
|
883
|
+
"""
|
|
884
|
+
with self._lock:
|
|
885
|
+
if self._resources_released:
|
|
886
|
+
return
|
|
887
|
+
self._resources_released = True
|
|
888
|
+
callback = self._on_worker_exit
|
|
889
|
+
self._on_worker_exit = None
|
|
890
|
+
truncate_to = self._abort_truncate_to
|
|
891
|
+
if truncate_to is not None:
|
|
892
|
+
try:
|
|
893
|
+
self._backend.truncate(truncate_to)
|
|
894
|
+
self._backend.fsync()
|
|
895
|
+
self._abort_rewound = True
|
|
896
|
+
except Exception:
|
|
897
|
+
logger.warning("Trajectory activation rollback failed for session %s", self._session_id, exc_info=True)
|
|
898
|
+
with contextlib.suppress(Exception):
|
|
899
|
+
self._backend.close()
|
|
900
|
+
if callback is not None:
|
|
901
|
+
try:
|
|
902
|
+
callback()
|
|
903
|
+
except Exception:
|
|
904
|
+
logger.debug("Trajectory writer exit callback failed", exc_info=True)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
def _with_payload(draft: EventDraft, payload: Mapping[str, Any]) -> EventDraft:
|
|
908
|
+
return EventDraft(
|
|
909
|
+
event_type=draft.event_type,
|
|
910
|
+
event_id=draft.event_id,
|
|
911
|
+
actor=draft.actor,
|
|
912
|
+
payload=payload,
|
|
913
|
+
measurements=draft.measurements,
|
|
914
|
+
turn_id=draft.turn_id,
|
|
915
|
+
operation_id=draft.operation_id,
|
|
916
|
+
parent_operation_id=draft.parent_operation_id,
|
|
917
|
+
links=draft.links,
|
|
918
|
+
segmented_fields=draft.segmented_fields,
|
|
919
|
+
occurred_at=draft.occurred_at,
|
|
920
|
+
monotonic_ns=draft.monotonic_ns,
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def _encode_checked(event: TrajectoryEvent) -> bytes | str:
|
|
925
|
+
"""Encode *event*; return the line bytes or a gap reason code on failure."""
|
|
926
|
+
data = event.to_dict()
|
|
927
|
+
if check_int64_range(data):
|
|
928
|
+
return GapReason.VALUE_OUT_OF_RANGE
|
|
929
|
+
if malformed_id_pointers(data):
|
|
930
|
+
# A line no reader can decode would read as corruption; a gap says
|
|
931
|
+
# exactly what happened and keeps the accounted prefix honest.
|
|
932
|
+
return GapReason.ENCODE_FAILURE
|
|
933
|
+
try:
|
|
934
|
+
line = encode_event_line(event)
|
|
935
|
+
except TypeError, ValueError:
|
|
936
|
+
return GapReason.ENCODE_FAILURE
|
|
937
|
+
if len(line) > LINE_BUDGET_BYTES:
|
|
938
|
+
return GapReason.LINE_BUDGET_EXCEEDED
|
|
939
|
+
return line
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
def _contiguous_runs(values: list[int]) -> list[list[int]]:
|
|
943
|
+
runs: list[list[int]] = []
|
|
944
|
+
for value in sorted(set(values)):
|
|
945
|
+
if runs and runs[-1][-1] + 1 == value:
|
|
946
|
+
runs[-1].append(value)
|
|
947
|
+
else:
|
|
948
|
+
runs.append([value])
|
|
949
|
+
return runs
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def _coalesced_dropped_ranges(ranges: list[DroppedRange]) -> list[DroppedRange]:
|
|
953
|
+
"""Merge overlapping/adjacent dropped intervals without expanding them."""
|
|
954
|
+
merged: list[DroppedRange] = []
|
|
955
|
+
for dropped in sorted(ranges, key=lambda item: (item.first_sequence, item.last_sequence)):
|
|
956
|
+
if merged and dropped.first_sequence <= merged[-1].last_sequence + 1:
|
|
957
|
+
first = merged[-1].first_sequence
|
|
958
|
+
last = max(merged[-1].last_sequence, dropped.last_sequence)
|
|
959
|
+
merged[-1] = DroppedRange(
|
|
960
|
+
first_sequence=first,
|
|
961
|
+
last_sequence=last,
|
|
962
|
+
dropped_count=last - first + 1,
|
|
963
|
+
)
|
|
964
|
+
else:
|
|
965
|
+
merged.append(dropped)
|
|
966
|
+
return merged
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
def _resolve(future: concurrent.futures.Future[EmitResult], result: EmitResult) -> None:
|
|
970
|
+
with contextlib.suppress(Exception):
|
|
971
|
+
if not future.done():
|
|
972
|
+
future.set_result(result)
|