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,1116 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Turn-aware four-phase compaction strategy.
|
|
4
|
+
|
|
5
|
+
Progressive compaction with escalating aggressiveness:
|
|
6
|
+
|
|
7
|
+
**Phase 1** — Old turn tool results are summarised oldest-turn-first (keeping
|
|
8
|
+
call arguments, replacing results with one-line summaries).
|
|
9
|
+
|
|
10
|
+
**Phase 2** — Old turn tool-call groups are removed entirely (args + results),
|
|
11
|
+
oldest-turn-first. Only the user/assistant text messages survive per turn.
|
|
12
|
+
|
|
13
|
+
**Phase 3** — Completed text-heavy turns are compressed oldest-turn-first
|
|
14
|
+
when Phase 1/2 cannot reach the request budget because there are no old tool
|
|
15
|
+
groups left to trim.
|
|
16
|
+
|
|
17
|
+
**Phase 4** — All current-turn tool-call and inline assistant-text messages
|
|
18
|
+
are dropped in a single shot and replaced by a ``<system-reminder>[LAST_WORDS]
|
|
19
|
+
…</system-reminder>`` block appended to the user message's content array by
|
|
20
|
+
``SystemReminderMiddleware``. The note is produced by
|
|
21
|
+
:class:`chrys.service.context.compaction.last_words.LastWordsGenerator` (always LLM-generated —
|
|
22
|
+
no programmatic fallback; transient generator failures retry the same
|
|
23
|
+
compaction input with backoff). On subsequent Phase 4 triggers
|
|
24
|
+
within the same turn the previous note + newly-dropped work are fed back
|
|
25
|
+
to the generator so the note accumulates insight without re-summarising
|
|
26
|
+
from scratch.
|
|
27
|
+
|
|
28
|
+
Turn boundaries come from ``_resolve_turns`` (built on
|
|
29
|
+
``chrys.foundation.models.turns``): only real openers — user messages
|
|
30
|
+
without a mid-turn flag — split turns, so injections and synthetic
|
|
31
|
+
``continue`` nudges stay inside the turn they arrived in, and the marker
|
|
32
|
+
region of the provider state decides degraded (opener-less) shapes.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import asyncio
|
|
38
|
+
import logging
|
|
39
|
+
import uuid
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
43
|
+
|
|
44
|
+
from chrys.foundation.errors import context_overflow_limit, iter_explicit_graph
|
|
45
|
+
from chrys.foundation.text.tokenizer import MixedLanguageTokenizer
|
|
46
|
+
from chrys.foundation.trajectory.context import current_trajectory
|
|
47
|
+
from chrys.foundation.trajectory.event_types import CompactionSkipReason
|
|
48
|
+
from chrys.kernel import (
|
|
49
|
+
Message,
|
|
50
|
+
annotate_message_groups,
|
|
51
|
+
annotate_token_counts,
|
|
52
|
+
included_token_count,
|
|
53
|
+
)
|
|
54
|
+
from chrys.service.profiles.models.schema import DEFAULT_MAX_CONTEXT_TOKENS
|
|
55
|
+
from chrys.service.trajectory.compaction import (
|
|
56
|
+
TOKEN_MEASUREMENT_SOURCE,
|
|
57
|
+
CompactionRunTrace,
|
|
58
|
+
bind_compaction_operation,
|
|
59
|
+
record_compaction_skipped,
|
|
60
|
+
reset_compaction_operation,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
from .breaker import DropBreakerController
|
|
64
|
+
from .calibration import TokenCalibration
|
|
65
|
+
from .compression import CompressionEngine
|
|
66
|
+
from .current_turn_drop import CurrentTurnDropRound
|
|
67
|
+
from .events import (
|
|
68
|
+
CachedCompressedContextSummary,
|
|
69
|
+
CachedSummary,
|
|
70
|
+
CompactionInfo,
|
|
71
|
+
CompressInfo,
|
|
72
|
+
PendingCompression,
|
|
73
|
+
PreCompactInfo,
|
|
74
|
+
)
|
|
75
|
+
from .exclusions import ExclusionLedger, _ExclusionAnchor
|
|
76
|
+
from .groups import (
|
|
77
|
+
_any_tool_groups_in_range,
|
|
78
|
+
_dedup_message_ids,
|
|
79
|
+
_group_kind_map,
|
|
80
|
+
_group_messages_by_id,
|
|
81
|
+
_group_start_indices,
|
|
82
|
+
_ordered_group_ids,
|
|
83
|
+
_tool_groups_in_range,
|
|
84
|
+
)
|
|
85
|
+
from .last_words import LastWordsGenerationError
|
|
86
|
+
from .last_words_state import DropRoundBreakerState, LastWordsState, Phase4RetrySnapshot
|
|
87
|
+
from .scoped import ScopedGroup
|
|
88
|
+
from .spill import SpillQuota
|
|
89
|
+
from .summaries import _compact_group, _remove_group
|
|
90
|
+
from .turns import _resolve_turns
|
|
91
|
+
|
|
92
|
+
_log = logging.getLogger(__name__)
|
|
93
|
+
|
|
94
|
+
if TYPE_CHECKING:
|
|
95
|
+
from collections.abc import Awaitable, Callable
|
|
96
|
+
|
|
97
|
+
from chrys.kernel import CompactionCallContext, LastWordsCompleter, TokenizerProtocol
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
# Strategy
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class LastWordsGeneratorLike(Protocol):
|
|
106
|
+
"""Strategy-facing contract of ``LastWordsGenerator`` (mirrored by test stubs)."""
|
|
107
|
+
|
|
108
|
+
async def generate(
|
|
109
|
+
self,
|
|
110
|
+
scoped_groups: list[ScopedGroup],
|
|
111
|
+
previous_last_words: str | None,
|
|
112
|
+
*,
|
|
113
|
+
degraded_opener: bool,
|
|
114
|
+
has_continuation_nudges: bool,
|
|
115
|
+
completer: LastWordsCompleter | None = None,
|
|
116
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
117
|
+
system_overhead_tokens: int = 0,
|
|
118
|
+
tool_definition_tokens: int = 0,
|
|
119
|
+
request_overhead_tokens: int = 0,
|
|
120
|
+
calibration_ratio: float = 1.0,
|
|
121
|
+
spend_side_call_tokens: Callable[[int], bool] | None = None,
|
|
122
|
+
) -> str: ...
|
|
123
|
+
|
|
124
|
+
async def publish_breaker_trip(self, failure_reason: str) -> None: ...
|
|
125
|
+
|
|
126
|
+
async def publish_committed(self) -> None: ...
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ReminderMiddlewareLike(Protocol):
|
|
130
|
+
"""Strategy-facing contract of ``SystemReminderMiddleware`` (mirrored by test stubs)."""
|
|
131
|
+
|
|
132
|
+
def renders_last_words(self, state: object) -> bool: ...
|
|
133
|
+
|
|
134
|
+
def refresh_last_words_reminder(self, messages: list[Any]) -> int | None: ...
|
|
135
|
+
|
|
136
|
+
def restore_folded_reminders(self, messages: list[Message]) -> int | None: ...
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True, slots=True)
|
|
140
|
+
class CompactionRetrySnapshot:
|
|
141
|
+
"""Attempt rollback state split by compaction lifetime.
|
|
142
|
+
|
|
143
|
+
Cross-turn folds published after ``published_fold_count`` are monotonic
|
|
144
|
+
commits and get silently replayed onto restored history. Phase-4 note
|
|
145
|
+
content is attempt-local and rolls back. Breaker/pressure accounting is
|
|
146
|
+
intentionally owned by the LAST_WORDS state's logical-turn slot and is
|
|
147
|
+
not represented here.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
anchors: tuple[_ExclusionAnchor, ...]
|
|
151
|
+
published_fold_count: int
|
|
152
|
+
phase4: Phase4RetrySnapshot | None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class UnifiedContextStrategy:
|
|
156
|
+
"""Unified strategy for intra-turn compaction AND cross-turn compression.
|
|
157
|
+
|
|
158
|
+
Owns **all** context reduction during a tool loop. Compression requests
|
|
159
|
+
from the ``compress_context`` tool are queued via :meth:`queue_compression`
|
|
160
|
+
and processed inside :meth:`__call__` on FunctionInvocationLayer's message list —
|
|
161
|
+
guaranteeing that ``_excluded`` flags are set on shared objects and
|
|
162
|
+
``project_included_messages`` filters them out on the next API call.
|
|
163
|
+
|
|
164
|
+
**Compaction** (4-phase, budget-triggered):
|
|
165
|
+
|
|
166
|
+
1. Estimate context usage as ``(included_tokens * ratio) / max_context``.
|
|
167
|
+
2. If usage < ``trigger_pct``, no-op, unless the provider has reported the
|
|
168
|
+
context window full since the last completed pass
|
|
169
|
+
(:meth:`note_context_overflow`). Past the trigger, a call that cannot
|
|
170
|
+
start a pass (compaction disabled, no resolvable turn) logs and records
|
|
171
|
+
``compaction.skipped`` once per stretch above the trigger.
|
|
172
|
+
3. **Phase 1-4**: trim old tools, compress old text turns, then drop
|
|
173
|
+
current-turn tool work only as the final fallback.
|
|
174
|
+
|
|
175
|
+
**Compression** (agent-requested via ``compress_context`` tool):
|
|
176
|
+
|
|
177
|
+
Queued by :meth:`queue_compression`, processed before the compaction
|
|
178
|
+
phases. Sets ``_excluded`` on fold-range messages and replaces
|
|
179
|
+
``state["messages"]`` for persistence.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
max_context_tokens: Maximum context window size (e.g. 200_000).
|
|
183
|
+
trigger_pct: Usage fraction that triggers compaction (e.g. 0.85).
|
|
184
|
+
target_pct: Usage fraction to compact down to (e.g. 0.50).
|
|
185
|
+
tokenizer: Token estimator. Defaults to ``MixedLanguageTokenizer``.
|
|
186
|
+
on_compaction: Async callback fired after compaction phases.
|
|
187
|
+
on_compress: Async callback fired after compression.
|
|
188
|
+
compaction_enabled: When ``False``, skip budget-triggered compaction
|
|
189
|
+
but still process queued compressions.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
def __init__(
|
|
193
|
+
self,
|
|
194
|
+
*,
|
|
195
|
+
max_context_tokens: int = DEFAULT_MAX_CONTEXT_TOKENS,
|
|
196
|
+
trigger_pct: float = 0.85,
|
|
197
|
+
target_pct: float = 0.50,
|
|
198
|
+
tokenizer: TokenizerProtocol | None = None,
|
|
199
|
+
on_compaction: Callable[[CompactionInfo], Awaitable[None]] | None = None,
|
|
200
|
+
on_pre_compact: Callable[[PreCompactInfo], Awaitable[None]] | None = None,
|
|
201
|
+
on_compress: Callable[[CompressInfo], Awaitable[None]] | None = None,
|
|
202
|
+
on_context_pressure: Callable[[str, DropRoundBreakerState, int], Awaitable[None] | None] | None = None,
|
|
203
|
+
spill_quota: SpillQuota | None = None,
|
|
204
|
+
spill_root: Path | None = None,
|
|
205
|
+
spill_record_dir: Path | None = None,
|
|
206
|
+
spill_session_id: str = "",
|
|
207
|
+
debug_log_dir: Path | None = None,
|
|
208
|
+
phase4_side_call_token_budget: int | None = -1,
|
|
209
|
+
compaction_enabled: bool = True,
|
|
210
|
+
) -> None:
|
|
211
|
+
if max_context_tokens <= 0:
|
|
212
|
+
raise ValueError("max_context_tokens must be positive")
|
|
213
|
+
if compaction_enabled:
|
|
214
|
+
if not 0.0 < trigger_pct <= 1.0:
|
|
215
|
+
raise ValueError("trigger_pct must be in (0.0, 1.0]")
|
|
216
|
+
if not 0.0 < target_pct < trigger_pct:
|
|
217
|
+
raise ValueError("target_pct must be in (0.0, trigger_pct)")
|
|
218
|
+
self.max_context_tokens = max_context_tokens
|
|
219
|
+
self.trigger_pct = trigger_pct
|
|
220
|
+
self.target_pct = target_pct
|
|
221
|
+
self._compaction_enabled = compaction_enabled
|
|
222
|
+
self._tokenizer: TokenizerProtocol = tokenizer or MixedLanguageTokenizer()
|
|
223
|
+
self._on_compaction = on_compaction
|
|
224
|
+
self._on_pre_compact = on_pre_compact
|
|
225
|
+
self._on_compress = on_compress
|
|
226
|
+
# The trajectory run of the compaction pass in flight (one per
|
|
227
|
+
# triggered ``__call__``); phases report into it.
|
|
228
|
+
self._trajectory_run: CompactionRunTrace | None = None
|
|
229
|
+
# The skip (reason, session) already reported for the current
|
|
230
|
+
# stretch above the trigger; cleared when usage drops below it or a
|
|
231
|
+
# pass starts.
|
|
232
|
+
self._reported_skip: tuple[str, str | None] | None = None
|
|
233
|
+
# Set when the provider rejected a request for overflowing the context
|
|
234
|
+
# window; forces the next pass whatever the estimate says and clears
|
|
235
|
+
# only when one completes. Per instance on purpose: retry rollback
|
|
236
|
+
# keeps it, and it is never persisted.
|
|
237
|
+
self._context_overflow_pending = False
|
|
238
|
+
self._on_context_pressure = on_context_pressure
|
|
239
|
+
self._spill_quota = spill_quota
|
|
240
|
+
self._spill_root = spill_root
|
|
241
|
+
self._spill_record_dir = spill_record_dir or Path("compactions") / "dropped"
|
|
242
|
+
self._spill_session_id = spill_session_id
|
|
243
|
+
self._debug_log_dir = debug_log_dir
|
|
244
|
+
# Negative (or None, kept for call-site compatibility) = unlimited:
|
|
245
|
+
# the per-turn round limit is the guard, not a spend cap.
|
|
246
|
+
self._phase4_side_call_token_budget = (
|
|
247
|
+
-1 if phase4_side_call_token_budget is None else phase4_side_call_token_budget
|
|
248
|
+
)
|
|
249
|
+
self._last_included_tokens: int = 0
|
|
250
|
+
# Phase 4 LAST_WORDS collaborators (wired post-construction by
|
|
251
|
+
# AgentBuilder so ContextManager can keep a simple signature).
|
|
252
|
+
self._reminder_middleware: ReminderMiddlewareLike | None = None
|
|
253
|
+
self._last_words_state: LastWordsState | None = None
|
|
254
|
+
self._last_words_generator: LastWordsGeneratorLike | None = None
|
|
255
|
+
self._persist_recovery_now: Callable[[], Awaitable[bool]] | None = None
|
|
256
|
+
# Detached on_compaction deliveries scheduled when cancellation
|
|
257
|
+
# interrupts the committed publish — anchored here because asyncio
|
|
258
|
+
# holds only weak references to tasks.
|
|
259
|
+
self._detached_deliveries: set[asyncio.Future] = set()
|
|
260
|
+
self._calibration = TokenCalibration(self)
|
|
261
|
+
self._exclusions = ExclusionLedger()
|
|
262
|
+
self._compression = CompressionEngine(self, self._exclusions)
|
|
263
|
+
self._breaker = DropBreakerController(self)
|
|
264
|
+
self._current_turn_drop = CurrentTurnDropRound(self, self._breaker, self._exclusions)
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def _calibration_ratio(self) -> float:
|
|
268
|
+
return self._calibration._calibration_ratio
|
|
269
|
+
|
|
270
|
+
@_calibration_ratio.setter
|
|
271
|
+
def _calibration_ratio(self, value: float) -> None:
|
|
272
|
+
self._calibration._calibration_ratio = value
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def _overhead_initialized(self) -> bool:
|
|
276
|
+
return self._calibration._overhead_initialized
|
|
277
|
+
|
|
278
|
+
@_overhead_initialized.setter
|
|
279
|
+
def _overhead_initialized(self, value: bool) -> None:
|
|
280
|
+
self._calibration._overhead_initialized = value
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def _system_overhead(self) -> int:
|
|
284
|
+
return self._calibration._system_overhead
|
|
285
|
+
|
|
286
|
+
@_system_overhead.setter
|
|
287
|
+
def _system_overhead(self, value: int) -> None:
|
|
288
|
+
self._calibration._system_overhead = value
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def _request_overhead_floor(self) -> int:
|
|
292
|
+
return self._calibration._request_overhead_floor
|
|
293
|
+
|
|
294
|
+
@_request_overhead_floor.setter
|
|
295
|
+
def _request_overhead_floor(self, value: int) -> None:
|
|
296
|
+
self._calibration._request_overhead_floor = value
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def _summary_cache(self) -> dict[str, CachedSummary]:
|
|
300
|
+
return self._exclusions._summary_cache
|
|
301
|
+
|
|
302
|
+
@_summary_cache.setter
|
|
303
|
+
def _summary_cache(self, value: dict[str, CachedSummary]) -> None:
|
|
304
|
+
self._exclusions._summary_cache = value
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def _removed_group_ids(self) -> set[str]:
|
|
308
|
+
return self._exclusions._removed_group_ids
|
|
309
|
+
|
|
310
|
+
@_removed_group_ids.setter
|
|
311
|
+
def _removed_group_ids(self, value: set[str]) -> None:
|
|
312
|
+
self._exclusions._removed_group_ids = value
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def _excluded_anchors(self) -> list[_ExclusionAnchor]:
|
|
316
|
+
return self._exclusions._excluded_anchors
|
|
317
|
+
|
|
318
|
+
@_excluded_anchors.setter
|
|
319
|
+
def _excluded_anchors(self, value: list[_ExclusionAnchor]) -> None:
|
|
320
|
+
self._exclusions._excluded_anchors = value
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def _state(self) -> dict[str, Any] | None:
|
|
324
|
+
return self._compression._state
|
|
325
|
+
|
|
326
|
+
@_state.setter
|
|
327
|
+
def _state(self, value: dict[str, Any] | None) -> None:
|
|
328
|
+
self._compression._state = value
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def _pending_compressions(self) -> list[PendingCompression]:
|
|
332
|
+
return self._compression._pending_compressions
|
|
333
|
+
|
|
334
|
+
@_pending_compressions.setter
|
|
335
|
+
def _pending_compressions(self, value: list[PendingCompression]) -> None:
|
|
336
|
+
self._compression._pending_compressions = value
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def _compressed_context_cache(self) -> dict[str, CachedCompressedContextSummary]:
|
|
340
|
+
return self._compression._compressed_context_cache
|
|
341
|
+
|
|
342
|
+
@_compressed_context_cache.setter
|
|
343
|
+
def _compressed_context_cache(self, value: dict[str, CachedCompressedContextSummary]) -> None:
|
|
344
|
+
self._compression._compressed_context_cache = value
|
|
345
|
+
|
|
346
|
+
@property
|
|
347
|
+
def _context_pressure_tasks(self) -> set[asyncio.Future[None]]:
|
|
348
|
+
return self._breaker._context_pressure_tasks
|
|
349
|
+
|
|
350
|
+
@_context_pressure_tasks.setter
|
|
351
|
+
def _context_pressure_tasks(self, value: set[asyncio.Future[None]]) -> None:
|
|
352
|
+
self._breaker._context_pressure_tasks = value
|
|
353
|
+
|
|
354
|
+
# ------------------------------------------------------------------
|
|
355
|
+
# Phase 4 wiring (called by AgentBuilder after middleware + generator exist)
|
|
356
|
+
# ------------------------------------------------------------------
|
|
357
|
+
|
|
358
|
+
def bind_reminder(self, middleware: ReminderMiddlewareLike, last_words: LastWordsState) -> None:
|
|
359
|
+
"""Bind the ``SystemReminderMiddleware`` and the LAST_WORDS state it renders, together.
|
|
360
|
+
|
|
361
|
+
Phase 4 writes its note to *last_words* and drops history only when
|
|
362
|
+
both are bound, so the next request carries the note in place of the
|
|
363
|
+
dropped history. A state the middleware does not render is refused.
|
|
364
|
+
"""
|
|
365
|
+
if not middleware.renders_last_words(last_words):
|
|
366
|
+
raise ValueError("The reminder middleware does not render this LAST_WORDS state.")
|
|
367
|
+
self._reminder_middleware = middleware
|
|
368
|
+
self._last_words_state = last_words
|
|
369
|
+
|
|
370
|
+
def set_last_words_generator(self, generator: LastWordsGeneratorLike) -> None:
|
|
371
|
+
"""Bind the ``LastWordsGenerator`` used by Phase 4."""
|
|
372
|
+
self._last_words_generator = generator
|
|
373
|
+
|
|
374
|
+
def set_recovery_persistence_callback(self, callback: Callable[[], Awaitable[bool]]) -> None:
|
|
375
|
+
"""Bind the main engine's strict recovery-sidecar durability callback."""
|
|
376
|
+
self._persist_recovery_now = callback
|
|
377
|
+
|
|
378
|
+
def _write_reminder_debug(self) -> None:
|
|
379
|
+
"""Dump the LAST_WORDS reminder exactly as the next model call injects it.
|
|
380
|
+
|
|
381
|
+
Owner-only (0600) like the spill records: the rendered reminder
|
|
382
|
+
carries the progress note, which summarises session content.
|
|
383
|
+
"""
|
|
384
|
+
if self._debug_log_dir is None or self._last_words_state is None:
|
|
385
|
+
return
|
|
386
|
+
try:
|
|
387
|
+
text = self._last_words_state.render_last_words_reminder_text()
|
|
388
|
+
if not text:
|
|
389
|
+
return
|
|
390
|
+
import os
|
|
391
|
+
from datetime import UTC, datetime
|
|
392
|
+
|
|
393
|
+
self._debug_log_dir.mkdir(parents=True, exist_ok=True)
|
|
394
|
+
ts = datetime.now(UTC).strftime("%H%M%S_%f")[:-3]
|
|
395
|
+
path = self._debug_log_dir / f"reminder_{ts}_{uuid.uuid4().hex[:6]}.log"
|
|
396
|
+
descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
397
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as output:
|
|
398
|
+
output.write(text)
|
|
399
|
+
except Exception:
|
|
400
|
+
_log.debug("Failed to write LAST_WORDS reminder debug dump", exc_info=True)
|
|
401
|
+
|
|
402
|
+
def _spend_side_call_tokens(self, estimated_tokens: int) -> bool:
|
|
403
|
+
"""Delegate the test-visible side-call charging seam."""
|
|
404
|
+
return self._breaker.spend_side_call_tokens(estimated_tokens)
|
|
405
|
+
|
|
406
|
+
async def _notify_compaction(self, info: CompactionInfo) -> None:
|
|
407
|
+
"""Record a finished phase on the trajectory, then deliver the UI callback."""
|
|
408
|
+
await self._notify_compaction_for_run(info, self._trajectory_run)
|
|
409
|
+
|
|
410
|
+
async def _notify_compaction_for_run(self, info: CompactionInfo, run: CompactionRunTrace | None) -> None:
|
|
411
|
+
"""As :meth:`_notify_compaction`, against the run *info* was measured under.
|
|
412
|
+
|
|
413
|
+
A detached delivery outlives the pass that scheduled it, so reading
|
|
414
|
+
the live handle at delivery time would hang the phase off whatever
|
|
415
|
+
run is current by then — or drop it once the pass has cleared it.
|
|
416
|
+
"""
|
|
417
|
+
if run is not None:
|
|
418
|
+
await run.phase_finished(
|
|
419
|
+
phase=info.phase,
|
|
420
|
+
groups_compacted=info.compacted_groups,
|
|
421
|
+
turn_numbers=info.turn_numbers,
|
|
422
|
+
tool_names=info.tool_names,
|
|
423
|
+
tokens_before=info.tokens_before,
|
|
424
|
+
tokens_after=info.tokens_after,
|
|
425
|
+
last_words_generated=info.last_words_generated,
|
|
426
|
+
consumed_item_ids=info.consumed_item_ids if info.phase == "phase4" else None,
|
|
427
|
+
)
|
|
428
|
+
if self._on_compaction is not None:
|
|
429
|
+
await self._on_compaction(info)
|
|
430
|
+
|
|
431
|
+
async def _notify_compress(
|
|
432
|
+
self,
|
|
433
|
+
info: CompressInfo,
|
|
434
|
+
*,
|
|
435
|
+
trigger: str,
|
|
436
|
+
tokens_before: int,
|
|
437
|
+
tokens_after: int,
|
|
438
|
+
token_source: str = TOKEN_MEASUREMENT_SOURCE,
|
|
439
|
+
) -> None:
|
|
440
|
+
"""Record a cross-turn fold on the trajectory, then deliver the UI callback.
|
|
441
|
+
|
|
442
|
+
Inside a triggered pass the fold is a phase of that run; a standalone
|
|
443
|
+
fold (agent-requested, forced after a run) is a run of its own.
|
|
444
|
+
"""
|
|
445
|
+
run = self._trajectory_run
|
|
446
|
+
turn_numbers = list(range(info.turn_range[0], info.turn_range[1] + 1)) if info.turn_range != (0, 0) else []
|
|
447
|
+
if run is not None:
|
|
448
|
+
await run.phase_finished(
|
|
449
|
+
phase="phase3",
|
|
450
|
+
groups_compacted=1,
|
|
451
|
+
turn_numbers=turn_numbers,
|
|
452
|
+
tool_names=[],
|
|
453
|
+
tokens_before=tokens_before,
|
|
454
|
+
tokens_after=tokens_after,
|
|
455
|
+
messages_freed=info.freed_messages,
|
|
456
|
+
compressed_context_id=info.compressed_context_id,
|
|
457
|
+
)
|
|
458
|
+
else:
|
|
459
|
+
await CompactionRunTrace.record_compression(
|
|
460
|
+
trigger=trigger,
|
|
461
|
+
compressed_context_id=info.compressed_context_id,
|
|
462
|
+
messages_freed=info.freed_messages,
|
|
463
|
+
tokens_before=tokens_before,
|
|
464
|
+
tokens_after=tokens_after,
|
|
465
|
+
token_source=token_source,
|
|
466
|
+
)
|
|
467
|
+
if self._on_compress is not None:
|
|
468
|
+
await self._on_compress(info)
|
|
469
|
+
|
|
470
|
+
async def _fire_pre_compact(self, trigger: str, *, usage_pct: float, tokens_before: int) -> None:
|
|
471
|
+
if self._on_pre_compact is None:
|
|
472
|
+
return
|
|
473
|
+
await self._on_pre_compact(
|
|
474
|
+
PreCompactInfo(
|
|
475
|
+
trigger=trigger,
|
|
476
|
+
usage_pct=usage_pct,
|
|
477
|
+
tokens_before=tokens_before,
|
|
478
|
+
trajectory_operation_id=(self._trajectory_run.run_id if self._trajectory_run is not None else None),
|
|
479
|
+
)
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# ------------------------------------------------------------------
|
|
483
|
+
# State binding (called by CompressibleHistoryProvider.before_run)
|
|
484
|
+
# ------------------------------------------------------------------
|
|
485
|
+
|
|
486
|
+
def bind_state(self, state: dict[str, Any]) -> None:
|
|
487
|
+
"""Bind to the history provider's state dict for this agent.run() cycle."""
|
|
488
|
+
self._compression.bind_state(state)
|
|
489
|
+
|
|
490
|
+
# ------------------------------------------------------------------
|
|
491
|
+
# Cross-turn compression API
|
|
492
|
+
# ------------------------------------------------------------------
|
|
493
|
+
|
|
494
|
+
def queue_compression(self, marker_id: str, summary_text: str) -> tuple[str, int]:
|
|
495
|
+
"""Queue a compression request. Returns ``(compressed_context_id, freed_count)``.
|
|
496
|
+
|
|
497
|
+
Validates the marker exists in state (raises ``ValueError`` if not).
|
|
498
|
+
The actual compression is executed inside :meth:`__call__`.
|
|
499
|
+
"""
|
|
500
|
+
return self._compression.queue_compression(marker_id, summary_text)
|
|
501
|
+
|
|
502
|
+
def list_compressed(self) -> dict[str, Any]:
|
|
503
|
+
"""Return compressed blocks and available markers, accounting for pending compressions."""
|
|
504
|
+
return self._compression.list_compressed()
|
|
505
|
+
|
|
506
|
+
async def force_compress(
|
|
507
|
+
self,
|
|
508
|
+
marker_id: str,
|
|
509
|
+
summary: str,
|
|
510
|
+
source: str = "auto",
|
|
511
|
+
*,
|
|
512
|
+
usage_pct: float = 0.0,
|
|
513
|
+
tokens_before: int = 0,
|
|
514
|
+
) -> str | None:
|
|
515
|
+
"""Execute compression immediately (for force-compress in after_run).
|
|
516
|
+
|
|
517
|
+
No tool loop is active, so ``_excluded`` flags are not needed.
|
|
518
|
+
"""
|
|
519
|
+
return await self._compression.force_compress(
|
|
520
|
+
marker_id,
|
|
521
|
+
summary,
|
|
522
|
+
source,
|
|
523
|
+
usage_pct=usage_pct,
|
|
524
|
+
tokens_before=tokens_before,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
# ------------------------------------------------------------------
|
|
528
|
+
# After-run persistence (called by CompressibleHistoryProvider.after_run)
|
|
529
|
+
# ------------------------------------------------------------------
|
|
530
|
+
|
|
531
|
+
def persist_exclusions_to_state(self, state_messages: list[Message]) -> None:
|
|
532
|
+
"""Propagate tool-loop exclusion flags to state messages.
|
|
533
|
+
|
|
534
|
+
History messages reach the wire as per-call views sharing the
|
|
535
|
+
state original's ``additional_properties`` dict and content
|
|
536
|
+
objects, so a flag set on the wire copy is already on the stored
|
|
537
|
+
message and the anchor's shared-content-ids tier re-sets it
|
|
538
|
+
idempotently. The load-bearing case is streaming: finalizers
|
|
539
|
+
rebuild current-turn response messages from updates before they
|
|
540
|
+
are stored, so their flags only reach state through the
|
|
541
|
+
structural anchors.
|
|
542
|
+
|
|
543
|
+
Anchors are single-use: one snapshot pass, one persist pass. The
|
|
544
|
+
list is cleared after consumption because under-trigger runs return
|
|
545
|
+
before ``_snapshot_excluded_anchors`` — without the clear, a stale
|
|
546
|
+
list replays against ever-longer state on every subsequent
|
|
547
|
+
after_run, and the newest-first structural fallback would bind a
|
|
548
|
+
later structural twin (a repeated identical tool call or
|
|
549
|
+
byte-equal assistant text) and silently exclude a live message.
|
|
550
|
+
The anchors deliberately survive a run whose after_run never fired
|
|
551
|
+
(interrupt before storage): the next after_run is their one chance
|
|
552
|
+
to reach the stored twins.
|
|
553
|
+
"""
|
|
554
|
+
self._exclusions.persist_to_state(state_messages)
|
|
555
|
+
|
|
556
|
+
def snapshot_retry_state(self) -> CompactionRetrySnapshot:
|
|
557
|
+
"""Capture compaction state at retry-attempt entry.
|
|
558
|
+
|
|
559
|
+
A Phase-4 commit inside the attempt records anchors describing the
|
|
560
|
+
current-turn messages it dropped. When a transient error rolls the
|
|
561
|
+
attempt back, history is restored to the pre-attempt snapshot and
|
|
562
|
+
those messages never reach state — but the anchors would survive,
|
|
563
|
+
and the successful retry can emit identical calls/text, so the
|
|
564
|
+
after_run persist's newest-first structural fallback would bind the
|
|
565
|
+
retry's fresh live messages and silently exclude them. The retry
|
|
566
|
+
loop snapshots this alongside history and restores both together.
|
|
567
|
+
Published cross-turn folds are different: Phase 3 only folds completed
|
|
568
|
+
turns, so a transient provider failure must preserve them across the
|
|
569
|
+
restored pre-input history baseline. The journal boundary lets
|
|
570
|
+
restore replay only folds committed by this outer attempt.
|
|
571
|
+
|
|
572
|
+
LAST_WORDS note/todo/manifest content describes current-turn messages
|
|
573
|
+
dropped by Phase 4 and therefore rolls back with those messages.
|
|
574
|
+
Breaker and pressure accounting remain monotonic per logical turn.
|
|
575
|
+
"""
|
|
576
|
+
phase4 = self._last_words_state.snapshot_phase4_retry_state() if self._last_words_state is not None else None
|
|
577
|
+
return CompactionRetrySnapshot(
|
|
578
|
+
anchors=self._exclusions.snapshot_retry_state(),
|
|
579
|
+
published_fold_count=self._compression.published_fold_count(),
|
|
580
|
+
phase4=phase4,
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
def restore_retry_state(self, snapshot: CompactionRetrySnapshot) -> None:
|
|
584
|
+
"""Restore the anchors captured by :meth:`snapshot_retry_state`.
|
|
585
|
+
|
|
586
|
+
Discards anchors created by the rolled-back attempt while keeping
|
|
587
|
+
ones that predate it (anchors surviving an interrupted prior run
|
|
588
|
+
still get their one after_run persist). Phase-4 content is restored
|
|
589
|
+
before published completed-turn folds are silently replayed onto the
|
|
590
|
+
executor-restored provider state.
|
|
591
|
+
"""
|
|
592
|
+
self._exclusions.restore_retry_state(snapshot.anchors)
|
|
593
|
+
if self._last_words_state is not None and snapshot.phase4 is not None:
|
|
594
|
+
self._last_words_state.restore_phase4_retry_state(snapshot.phase4)
|
|
595
|
+
self._compression.replay_published_folds(snapshot.published_fold_count)
|
|
596
|
+
if self._state is not None:
|
|
597
|
+
self._exclusions.discard_unreferenced_summaries(self._state.get("messages", []))
|
|
598
|
+
|
|
599
|
+
async def flush_pending_compressions(self) -> bool:
|
|
600
|
+
"""Process pending cross-turn compressions on bound state.
|
|
601
|
+
|
|
602
|
+
Called by ``after_run`` when the LLM called ``compress_context``
|
|
603
|
+
right before a successful run ended, and by the next executor cycle
|
|
604
|
+
before its retry snapshot when a failed run left that request
|
|
605
|
+
queued. Unlike ``__call__``'s compression path, this does not set
|
|
606
|
+
``_excluded`` on a framework message list; subsequent provider input
|
|
607
|
+
is built from the already-folded state.
|
|
608
|
+
"""
|
|
609
|
+
return await self._compression.flush_pending_compressions()
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def last_included_tokens(self) -> int:
|
|
613
|
+
"""Estimated included token count from the last ``__call__``.
|
|
614
|
+
|
|
615
|
+
Uses the same ``MixedLanguageTokenizer`` as the threshold check,
|
|
616
|
+
so callers can use it instead of the API's ``input_token_count``
|
|
617
|
+
to avoid calibration mismatch.
|
|
618
|
+
"""
|
|
619
|
+
return self._last_included_tokens
|
|
620
|
+
|
|
621
|
+
def calibrate(self, api_input_tokens: int, *, local_included: int = 0) -> None:
|
|
622
|
+
"""Update system overhead and calibration ratio from an API response.
|
|
623
|
+
|
|
624
|
+
Uses an **additive + multiplicative** model::
|
|
625
|
+
|
|
626
|
+
estimated_api_tokens = (local + system_overhead) * ratio
|
|
627
|
+
|
|
628
|
+
* ``system_overhead`` (fixed) captures the system prompt + tool
|
|
629
|
+
definitions — computed once on the first API response and kept
|
|
630
|
+
constant for the session.
|
|
631
|
+
* ``ratio`` (updated each call) captures only tokenizer drift —
|
|
632
|
+
should stay close to 1.0.
|
|
633
|
+
|
|
634
|
+
On the first call the overhead is learned. When the heuristic
|
|
635
|
+
overestimates enough that ``local > api``, overhead is clamped to
|
|
636
|
+
zero and the ratio is calibrated immediately to absorb the full gap.
|
|
637
|
+
|
|
638
|
+
Args:
|
|
639
|
+
api_input_tokens: The API's ``input_token_count``.
|
|
640
|
+
local_included: Optional override for the local baseline.
|
|
641
|
+
"""
|
|
642
|
+
local = local_included if local_included > 0 else self._last_included_tokens
|
|
643
|
+
self._calibration.calibrate(api_input_tokens, local_included=local)
|
|
644
|
+
|
|
645
|
+
@property
|
|
646
|
+
def calibration_ratio(self) -> float:
|
|
647
|
+
"""Tokenizer accuracy ratio (should be close to 1.0).
|
|
648
|
+
|
|
649
|
+
> 1.0 = local tokenizer underestimates.
|
|
650
|
+
< 1.0 = local tokenizer overestimates.
|
|
651
|
+
Updated by :meth:`calibrate` after each API response.
|
|
652
|
+
"""
|
|
653
|
+
return self._calibration._calibration_ratio
|
|
654
|
+
|
|
655
|
+
@property
|
|
656
|
+
def calibration_initialized(self) -> bool:
|
|
657
|
+
"""Whether a provider response initialized calibration state."""
|
|
658
|
+
return self._calibration._overhead_initialized
|
|
659
|
+
|
|
660
|
+
def restore_calibration(self, system_overhead_tokens: object, calibration_ratio: object) -> bool:
|
|
661
|
+
"""Restore validated calibration values from provenance-gated state."""
|
|
662
|
+
return self._calibration.restore(system_overhead_tokens, calibration_ratio)
|
|
663
|
+
|
|
664
|
+
@property
|
|
665
|
+
def tokenizer(self) -> TokenizerProtocol:
|
|
666
|
+
"""Tokenizer shared by compaction and per-call budget estimation."""
|
|
667
|
+
return self._tokenizer
|
|
668
|
+
|
|
669
|
+
@property
|
|
670
|
+
def system_overhead_tokens(self) -> int:
|
|
671
|
+
"""Estimated fixed token overhead (system prompt + tool definitions).
|
|
672
|
+
|
|
673
|
+
Computed as ``api_input_tokens - local_conversation_tokens`` after
|
|
674
|
+
each API response. The true overhead is constant within a session;
|
|
675
|
+
small fluctuations reflect tokenizer inaccuracy on conversation content.
|
|
676
|
+
"""
|
|
677
|
+
return self._calibration._system_overhead
|
|
678
|
+
|
|
679
|
+
@property
|
|
680
|
+
def estimated_context_input_tokens(self) -> int:
|
|
681
|
+
"""Estimated input occupancy for the most recently prepared request.
|
|
682
|
+
|
|
683
|
+
This uses the same request-overhead floor and tokenizer calibration as
|
|
684
|
+
admission and compaction checks, so it remains usable when a provider's
|
|
685
|
+
reported input usage includes hidden hosted-tool execution.
|
|
686
|
+
"""
|
|
687
|
+
return self._calibration.estimated_tokens(self._last_included_tokens)
|
|
688
|
+
|
|
689
|
+
def _usage_pct(self, included: int) -> float:
|
|
690
|
+
"""Compute effective context usage as a fraction of max_context_tokens."""
|
|
691
|
+
return self._calibration.usage_pct(included)
|
|
692
|
+
|
|
693
|
+
def note_context_overflow(self, exc: BaseException | None = None) -> bool:
|
|
694
|
+
"""Make the next pass run even below the trigger: the provider found the window full.
|
|
695
|
+
|
|
696
|
+
Not for a failure of compaction's own last-words call (owner-terminal;
|
|
697
|
+
its fallback text names the context window), nor while compaction is
|
|
698
|
+
off, which would only record a misleading skip. Returns whether to
|
|
699
|
+
compact and resend the same request: not when the provider names a
|
|
700
|
+
smaller window than this one, since requests sized for this window
|
|
701
|
+
keep overflowing until the user sets the right one; the failure then
|
|
702
|
+
tells the user which value to set.
|
|
703
|
+
"""
|
|
704
|
+
if exc is not None and any(isinstance(node, LastWordsGenerationError) for node in iter_explicit_graph(exc)):
|
|
705
|
+
return False
|
|
706
|
+
if not self._compaction_enabled:
|
|
707
|
+
return False
|
|
708
|
+
self._context_overflow_pending = True
|
|
709
|
+
limit = context_overflow_limit(exc) if exc is not None else None
|
|
710
|
+
return limit is None or limit >= self.max_context_tokens
|
|
711
|
+
|
|
712
|
+
async def __call__(self, messages: list[Message], context: CompactionCallContext | None = None) -> bool:
|
|
713
|
+
# max() folds the retained legacy ``tool_definition_tokens`` spelling:
|
|
714
|
+
# external constructors of CompactionCallContext may populate only it.
|
|
715
|
+
self._request_overhead_floor = (
|
|
716
|
+
max(context.request_overhead_tokens, context.tool_definition_tokens) if context is not None else 0
|
|
717
|
+
)
|
|
718
|
+
compressions_processed = await self._run_queued_compressions(messages)
|
|
719
|
+
if compressions_processed:
|
|
720
|
+
refreshed_index = self._restore_folded_reminders(messages)
|
|
721
|
+
if refreshed_index is not None:
|
|
722
|
+
annotate_token_counts(messages, tokenizer=self._tokenizer, from_index=refreshed_index)
|
|
723
|
+
self._reset_stale_exclusions(messages)
|
|
724
|
+
self._reinject_compressed_context_summaries(messages)
|
|
725
|
+
current = self._annotate_and_count(messages)
|
|
726
|
+
usage = self._usage_pct(current)
|
|
727
|
+
below_trigger = usage < self.trigger_pct
|
|
728
|
+
overflow_forced = self._context_overflow_pending
|
|
729
|
+
|
|
730
|
+
if below_trigger and not overflow_forced:
|
|
731
|
+
self._reported_skip = None
|
|
732
|
+
return compressions_processed
|
|
733
|
+
|
|
734
|
+
if not self._compaction_enabled:
|
|
735
|
+
self._report_skip(CompactionSkipReason.DISABLED, current)
|
|
736
|
+
return compressions_processed
|
|
737
|
+
|
|
738
|
+
tokens_before = current
|
|
739
|
+
resolved = _resolve_turns(messages, self._state)
|
|
740
|
+
|
|
741
|
+
if not resolved.spans:
|
|
742
|
+
# ``compaction.skipped`` reports usage past the trigger; a pass the
|
|
743
|
+
# overflow forced below it just waits for the next call.
|
|
744
|
+
if below_trigger:
|
|
745
|
+
self._reported_skip = None
|
|
746
|
+
else:
|
|
747
|
+
self._report_skip(CompactionSkipReason.TURNS_UNRESOLVED, current)
|
|
748
|
+
return compressions_processed
|
|
749
|
+
self._reported_skip = None
|
|
750
|
+
|
|
751
|
+
# Only REAL previous turns are eligible for P1/P2 mechanical
|
|
752
|
+
# truncation — mid-turn user messages (injections, nudges) no longer
|
|
753
|
+
# split the current task into fake "previous" turns. The span COUNT
|
|
754
|
+
# is insertion-stable (summaries are assistant messages and never
|
|
755
|
+
# add or remove turns), so iterating by index over re-resolved
|
|
756
|
+
# snapshots is safe.
|
|
757
|
+
previous_count = len(resolved.spans) - 1
|
|
758
|
+
|
|
759
|
+
run = CompactionRunTrace.open(
|
|
760
|
+
next_exchange_operation_id=context.next_exchange_operation_id if context is not None else None
|
|
761
|
+
)
|
|
762
|
+
self._trajectory_run = run
|
|
763
|
+
compaction_token = bind_compaction_operation(run.run_id if run is not None else None)
|
|
764
|
+
try:
|
|
765
|
+
if run is not None:
|
|
766
|
+
await run.started(
|
|
767
|
+
trigger="context_overflow" if overflow_forced else "usage_threshold",
|
|
768
|
+
tokens_before=tokens_before,
|
|
769
|
+
)
|
|
770
|
+
changed = await self._run_phase1(
|
|
771
|
+
messages,
|
|
772
|
+
previous_count=previous_count,
|
|
773
|
+
usage=usage,
|
|
774
|
+
tokens_before=tokens_before,
|
|
775
|
+
)
|
|
776
|
+
changed |= await self._run_phase2(messages)
|
|
777
|
+
changed |= await self._run_phase3(messages)
|
|
778
|
+
changed |= await self._run_phase4(messages, context)
|
|
779
|
+
except BaseException:
|
|
780
|
+
# Interrupted (or failed) mid-pass: only this path can close the
|
|
781
|
+
# run, and a started run with no terminal marker reads as one that
|
|
782
|
+
# is still going.
|
|
783
|
+
if run is not None:
|
|
784
|
+
run.finished_soon()
|
|
785
|
+
raise
|
|
786
|
+
finally:
|
|
787
|
+
reset_compaction_operation(compaction_token)
|
|
788
|
+
self._trajectory_run = None
|
|
789
|
+
self._finalize_pass(messages, changed, entry_tokens=current)
|
|
790
|
+
self._context_overflow_pending = False
|
|
791
|
+
if run is not None:
|
|
792
|
+
await run.finished(tokens_before=tokens_before, tokens_after=self._last_included_tokens)
|
|
793
|
+
return changed or compressions_processed
|
|
794
|
+
|
|
795
|
+
def _report_skip(self, reason_code: str, included: int) -> None:
|
|
796
|
+
"""Say why usage past the trigger started no pass, once per stretch above it."""
|
|
797
|
+
# Per session too: a restore that keeps this strategy starts a
|
|
798
|
+
# session whose trajectory has not been told yet.
|
|
799
|
+
trajectory = current_trajectory()
|
|
800
|
+
skip = (reason_code, trajectory.session_id if trajectory is not None else None)
|
|
801
|
+
if self._reported_skip == skip:
|
|
802
|
+
return
|
|
803
|
+
self._reported_skip = skip
|
|
804
|
+
estimated = self._calibration.estimated_tokens(included)
|
|
805
|
+
# round(): the derived fraction is trigger_tokens / window, and ceil()
|
|
806
|
+
# would turn its float error into one token too many.
|
|
807
|
+
trigger_tokens = round(self.trigger_pct * self.max_context_tokens)
|
|
808
|
+
_log.log(
|
|
809
|
+
logging.INFO if reason_code == CompactionSkipReason.DISABLED else logging.WARNING,
|
|
810
|
+
"Compaction skipped past its trigger: reason=%s, estimated_input_tokens=%d, trigger_tokens=%d, "
|
|
811
|
+
"max_context_tokens=%d",
|
|
812
|
+
reason_code,
|
|
813
|
+
estimated,
|
|
814
|
+
trigger_tokens,
|
|
815
|
+
self.max_context_tokens,
|
|
816
|
+
)
|
|
817
|
+
record_compaction_skipped(
|
|
818
|
+
reason_code=reason_code,
|
|
819
|
+
estimated_input_tokens=estimated,
|
|
820
|
+
trigger_tokens=trigger_tokens,
|
|
821
|
+
max_context_tokens=self.max_context_tokens,
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
async def _run_queued_compressions(self, messages: list[Message]) -> bool:
|
|
825
|
+
return await self._compression.run_queued(messages)
|
|
826
|
+
|
|
827
|
+
def _restore_folded_reminders(self, messages: list[Message]) -> int | None:
|
|
828
|
+
"""Re-attach catalog reminders whose carrier a fold just excluded.
|
|
829
|
+
|
|
830
|
+
Returns the rebuilt index; token counts from there on are stale.
|
|
831
|
+
"""
|
|
832
|
+
if self._reminder_middleware is None:
|
|
833
|
+
return None
|
|
834
|
+
return self._reminder_middleware.restore_folded_reminders(messages)
|
|
835
|
+
|
|
836
|
+
def _reset_stale_exclusions(self, messages: list[Message]) -> None:
|
|
837
|
+
# ---- Step 1: Handle stale _excluded flags ----
|
|
838
|
+
#
|
|
839
|
+
# ``project_included_messages`` filters messages with _excluded=True,
|
|
840
|
+
# and those flags can outlive a single compaction pass on shared
|
|
841
|
+
# Message objects or restored state.
|
|
842
|
+
#
|
|
843
|
+
# If this strategy has previously compacted groups, re-inject any
|
|
844
|
+
# cached summaries missing from the current list so excluded originals
|
|
845
|
+
# still have a visible replacement. Flags on compacted messages are
|
|
846
|
+
# kept, preventing redundant re-compaction and duplicate events.
|
|
847
|
+
#
|
|
848
|
+
# If this strategy has never compacted (fresh instance or different
|
|
849
|
+
# instance), reset stale foreign _excluded flags while preserving
|
|
850
|
+
# persisted Chrys compaction reasons.
|
|
851
|
+
self._exclusions.reset_stale_exclusions(messages)
|
|
852
|
+
|
|
853
|
+
def _annotate_and_count(self, messages: list[Message]) -> int:
|
|
854
|
+
# De-duplicate message IDs to prevent cross-run group merging
|
|
855
|
+
_dedup_message_ids(messages)
|
|
856
|
+
|
|
857
|
+
# Re-annotate groups with force so group IDs reflect the deduped
|
|
858
|
+
# message IDs.
|
|
859
|
+
annotate_message_groups(messages, force_reannotate=True)
|
|
860
|
+
annotate_token_counts(messages, tokenizer=self._tokenizer)
|
|
861
|
+
|
|
862
|
+
current = included_token_count(messages)
|
|
863
|
+
self._last_included_tokens = current
|
|
864
|
+
|
|
865
|
+
return current
|
|
866
|
+
|
|
867
|
+
def _trim_groups_to_target(
|
|
868
|
+
self,
|
|
869
|
+
messages: list[Message],
|
|
870
|
+
group_ids: list[str],
|
|
871
|
+
grouped: dict[str, list[Message]],
|
|
872
|
+
operation: Callable[[list[Message], str, list[Message]], list[str] | None],
|
|
873
|
+
) -> tuple[bool, int, list[str]]:
|
|
874
|
+
changed = False
|
|
875
|
+
compacted_count = 0
|
|
876
|
+
tool_names: list[str] = []
|
|
877
|
+
for group_id in group_ids:
|
|
878
|
+
group_msgs = grouped.get(group_id, [])
|
|
879
|
+
result = operation(messages, group_id, group_msgs)
|
|
880
|
+
if result:
|
|
881
|
+
changed = True
|
|
882
|
+
compacted_count += 1
|
|
883
|
+
tool_names.extend(result)
|
|
884
|
+
|
|
885
|
+
if self._usage_pct(included_token_count(messages)) <= self.target_pct:
|
|
886
|
+
break
|
|
887
|
+
|
|
888
|
+
return changed, compacted_count, tool_names
|
|
889
|
+
|
|
890
|
+
async def _run_phase1(
|
|
891
|
+
self,
|
|
892
|
+
messages: list[Message],
|
|
893
|
+
*,
|
|
894
|
+
previous_count: int,
|
|
895
|
+
usage: float,
|
|
896
|
+
tokens_before: int,
|
|
897
|
+
) -> bool:
|
|
898
|
+
changed = False
|
|
899
|
+
|
|
900
|
+
# ---- Phase 1: Summarise previous turns' tool results ----
|
|
901
|
+
if previous_count:
|
|
902
|
+
await self._fire_pre_compact("phase1", usage_pct=usage, tokens_before=tokens_before)
|
|
903
|
+
p1_count = 0
|
|
904
|
+
p1_tool_names: list[str] = []
|
|
905
|
+
p1_turn_numbers: list[int] = []
|
|
906
|
+
|
|
907
|
+
for turn_idx in range(previous_count):
|
|
908
|
+
# ONE COHERENT SNAPSHOT per turn iteration: spans and all four
|
|
909
|
+
# group projections recomputed together. _compact_group both
|
|
910
|
+
# inserts a summary message and re-annotates the suffix, so
|
|
911
|
+
# projections from different mutation generations mix stale
|
|
912
|
+
# group ids with fresh indices — stale spans silently skip a
|
|
913
|
+
# later turn's shifted tail groups (under-compaction) and
|
|
914
|
+
# mis-bucket them into the wrong turn's event numbers.
|
|
915
|
+
resolved = _resolve_turns(messages, self._state)
|
|
916
|
+
span = resolved.previous[turn_idx]
|
|
917
|
+
ordered_ids = _ordered_group_ids(messages)
|
|
918
|
+
kinds = _group_kind_map(messages)
|
|
919
|
+
grouped = _group_messages_by_id(messages)
|
|
920
|
+
starts = _group_start_indices(messages)
|
|
921
|
+
turn_groups = _tool_groups_in_range(span.start, span.end, ordered_ids, kinds, starts, grouped)
|
|
922
|
+
|
|
923
|
+
turn_compacted, turn_count, turn_tool_names = self._trim_groups_to_target(
|
|
924
|
+
messages,
|
|
925
|
+
turn_groups,
|
|
926
|
+
grouped,
|
|
927
|
+
self._compact_group,
|
|
928
|
+
)
|
|
929
|
+
if turn_compacted:
|
|
930
|
+
changed = True
|
|
931
|
+
p1_count += turn_count
|
|
932
|
+
p1_tool_names.extend(turn_tool_names)
|
|
933
|
+
|
|
934
|
+
if turn_compacted:
|
|
935
|
+
p1_turn_numbers.append(span.absolute_number)
|
|
936
|
+
|
|
937
|
+
if self._usage_pct(included_token_count(messages)) <= self.target_pct:
|
|
938
|
+
break
|
|
939
|
+
|
|
940
|
+
if p1_count > 0:
|
|
941
|
+
await self._notify_compaction(
|
|
942
|
+
CompactionInfo(
|
|
943
|
+
compacted_groups=p1_count,
|
|
944
|
+
phase="phase1",
|
|
945
|
+
turn_numbers=p1_turn_numbers,
|
|
946
|
+
tool_names=p1_tool_names,
|
|
947
|
+
tokens_before=tokens_before,
|
|
948
|
+
tokens_after=included_token_count(messages),
|
|
949
|
+
)
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
return changed
|
|
953
|
+
|
|
954
|
+
async def _run_phase2(self, messages: list[Message]) -> bool:
|
|
955
|
+
changed = False
|
|
956
|
+
|
|
957
|
+
# ---- Phase 2: Remove previous turns' tool-call groups entirely ----
|
|
958
|
+
p2_count = 0
|
|
959
|
+
p2_tool_names: list[str] = []
|
|
960
|
+
p2_turn_numbers: list[int] = []
|
|
961
|
+
|
|
962
|
+
if self._usage_pct(included_token_count(messages)) > self.target_pct:
|
|
963
|
+
p2_tokens_before = included_token_count(messages)
|
|
964
|
+
# Re-resolve at phase entry — Phase 1 insertions may have shifted indices
|
|
965
|
+
resolved = _resolve_turns(messages, self._state)
|
|
966
|
+
if len(resolved.spans) > 1:
|
|
967
|
+
await self._fire_pre_compact(
|
|
968
|
+
"phase2",
|
|
969
|
+
usage_pct=self._usage_pct(p2_tokens_before),
|
|
970
|
+
tokens_before=p2_tokens_before,
|
|
971
|
+
)
|
|
972
|
+
ordered_ids = _ordered_group_ids(messages)
|
|
973
|
+
kinds = _group_kind_map(messages)
|
|
974
|
+
|
|
975
|
+
# _remove_group does not insert messages, so the phase-entry
|
|
976
|
+
# spans stay valid across iterations.
|
|
977
|
+
for span in resolved.previous:
|
|
978
|
+
grouped = _group_messages_by_id(messages)
|
|
979
|
+
starts = _group_start_indices(messages)
|
|
980
|
+
# Use _any_ variant: Phase 1 already excluded originals,
|
|
981
|
+
# but summaries are still visible and need removal.
|
|
982
|
+
turn_groups = _any_tool_groups_in_range(span.start, span.end, ordered_ids, kinds, starts)
|
|
983
|
+
|
|
984
|
+
turn_removed, turn_count, turn_tool_names = self._trim_groups_to_target(
|
|
985
|
+
messages,
|
|
986
|
+
turn_groups,
|
|
987
|
+
grouped,
|
|
988
|
+
self._remove_group,
|
|
989
|
+
)
|
|
990
|
+
if turn_removed:
|
|
991
|
+
changed = True
|
|
992
|
+
p2_count += turn_count
|
|
993
|
+
p2_tool_names.extend(turn_tool_names)
|
|
994
|
+
|
|
995
|
+
if turn_removed:
|
|
996
|
+
p2_turn_numbers.append(span.absolute_number)
|
|
997
|
+
|
|
998
|
+
if self._usage_pct(included_token_count(messages)) <= self.target_pct:
|
|
999
|
+
break
|
|
1000
|
+
|
|
1001
|
+
if p2_count > 0:
|
|
1002
|
+
await self._notify_compaction(
|
|
1003
|
+
CompactionInfo(
|
|
1004
|
+
compacted_groups=p2_count,
|
|
1005
|
+
phase="phase2",
|
|
1006
|
+
turn_numbers=p2_turn_numbers,
|
|
1007
|
+
tool_names=p2_tool_names,
|
|
1008
|
+
tokens_before=p2_tokens_before,
|
|
1009
|
+
tokens_after=included_token_count(messages),
|
|
1010
|
+
)
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
return changed
|
|
1014
|
+
|
|
1015
|
+
async def _run_phase3(self, messages: list[Message]) -> bool:
|
|
1016
|
+
changed = False
|
|
1017
|
+
|
|
1018
|
+
# ---- Phase 3: fold old text turns before touching current work ----
|
|
1019
|
+
#
|
|
1020
|
+
# Phase 1/2 only reduce tool-call groups. Once old tool results have
|
|
1021
|
+
# been summarised/removed, long-running text-heavy sessions can still
|
|
1022
|
+
# exceed the request window with no remaining tool group to compact.
|
|
1023
|
+
# Fold completed turns by marker here, before Phase 4 considers
|
|
1024
|
+
# dropping current-turn tool work.
|
|
1025
|
+
p3_count = 0
|
|
1026
|
+
if self._usage_pct(included_token_count(messages)) > self.target_pct:
|
|
1027
|
+
while self._usage_pct(included_token_count(messages)) > self.target_pct:
|
|
1028
|
+
p3_tokens_before = included_token_count(messages)
|
|
1029
|
+
progressed, compressed = await self._emergency_compress_oldest_turn(
|
|
1030
|
+
messages,
|
|
1031
|
+
usage_pct=self._usage_pct(p3_tokens_before),
|
|
1032
|
+
tokens_before=p3_tokens_before,
|
|
1033
|
+
)
|
|
1034
|
+
if not progressed:
|
|
1035
|
+
break
|
|
1036
|
+
if not compressed:
|
|
1037
|
+
continue
|
|
1038
|
+
p3_count += 1
|
|
1039
|
+
changed = True
|
|
1040
|
+
self._restore_folded_reminders(messages)
|
|
1041
|
+
_dedup_message_ids(messages)
|
|
1042
|
+
annotate_message_groups(messages, force_reannotate=True)
|
|
1043
|
+
annotate_token_counts(messages, tokenizer=self._tokenizer, force_retokenize=True)
|
|
1044
|
+
|
|
1045
|
+
return changed
|
|
1046
|
+
|
|
1047
|
+
async def _run_phase4(
|
|
1048
|
+
self,
|
|
1049
|
+
messages: list[Message],
|
|
1050
|
+
context: CompactionCallContext | None,
|
|
1051
|
+
) -> bool:
|
|
1052
|
+
return await self._current_turn_drop.run(messages, context)
|
|
1053
|
+
|
|
1054
|
+
def _finalize_pass(self, messages: list[Message], changed: bool, *, entry_tokens: int) -> None:
|
|
1055
|
+
post_compaction = included_token_count(messages)
|
|
1056
|
+
self._last_included_tokens = post_compaction if changed else entry_tokens
|
|
1057
|
+
|
|
1058
|
+
if changed:
|
|
1059
|
+
self._write_reminder_debug()
|
|
1060
|
+
|
|
1061
|
+
# Final snapshot for after_run propagation — Phase-4 rounds already
|
|
1062
|
+
# snapshotted inside their synchronous commit blocks; this pass adds
|
|
1063
|
+
# exclusions from the non-Phase-4 paths above.
|
|
1064
|
+
self._snapshot_excluded_anchors(messages)
|
|
1065
|
+
|
|
1066
|
+
def _snapshot_excluded_anchors(self, messages: list[Message]) -> None:
|
|
1067
|
+
"""Delegate the test-visible exclusion-anchor snapshot seam."""
|
|
1068
|
+
self._exclusions.snapshot_excluded_anchors(messages)
|
|
1069
|
+
|
|
1070
|
+
async def _emergency_compress_oldest_turn(
|
|
1071
|
+
self,
|
|
1072
|
+
messages: list[Message],
|
|
1073
|
+
*,
|
|
1074
|
+
usage_pct: float,
|
|
1075
|
+
tokens_before: int,
|
|
1076
|
+
) -> tuple[bool, bool]:
|
|
1077
|
+
return await self._compression.emergency_compress_oldest_turn(
|
|
1078
|
+
messages,
|
|
1079
|
+
usage_pct=usage_pct,
|
|
1080
|
+
tokens_before=tokens_before,
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
def _reinject_compressed_context_summaries(self, messages: list[Message]) -> None:
|
|
1084
|
+
"""Delegate the test-visible compressed-summary reinjection seam."""
|
|
1085
|
+
self._compression.reinject_summaries(messages)
|
|
1086
|
+
|
|
1087
|
+
def _compact_group(
|
|
1088
|
+
self,
|
|
1089
|
+
messages: list[Message],
|
|
1090
|
+
group_id: str,
|
|
1091
|
+
group_msgs: list[Message],
|
|
1092
|
+
) -> list[str] | None:
|
|
1093
|
+
"""Compact one Phase-1 group and commit its replacement to owned history."""
|
|
1094
|
+
result = _compact_group(
|
|
1095
|
+
messages,
|
|
1096
|
+
group_id,
|
|
1097
|
+
group_msgs,
|
|
1098
|
+
ledger=self._exclusions,
|
|
1099
|
+
tokenizer=self._tokenizer,
|
|
1100
|
+
)
|
|
1101
|
+
if result is not None and self._state is not None:
|
|
1102
|
+
# Phase 1 only replaces completed-turn history. Commit its visible
|
|
1103
|
+
# replacement synchronously with exclusion: a failed/cancelled
|
|
1104
|
+
# provider call skips after_run but still saves the excluded state.
|
|
1105
|
+
self._exclusions.reinject_cached_summaries(self._state.get("messages", []))
|
|
1106
|
+
self._compression._record_pre_output_history_len()
|
|
1107
|
+
return result
|
|
1108
|
+
|
|
1109
|
+
def _remove_group(
|
|
1110
|
+
self,
|
|
1111
|
+
messages: list[Message],
|
|
1112
|
+
group_id: str,
|
|
1113
|
+
group_msgs: list[Message],
|
|
1114
|
+
) -> list[str] | None:
|
|
1115
|
+
"""Delegate the test-visible Phase-2 group operation."""
|
|
1116
|
+
return _remove_group(messages, group_id, group_msgs, ledger=self._exclusions)
|