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,1107 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""SystemReminderMiddleware — injects ``<system-reminder>`` tags per LLM call.
|
|
4
|
+
|
|
5
|
+
A ``ChatMiddleware`` that fires before each LLM call in the tool loop.
|
|
6
|
+
It replaces user messages in ``context.messages`` (a per-call shallow copy)
|
|
7
|
+
with enriched versions whose original user contents come first, followed by
|
|
8
|
+
``<system-reminder>`` tags. What each reminder says, and the state behind
|
|
9
|
+
it, lives in its source (``reminders/``); this module decides when each one
|
|
10
|
+
is sent, records it and fixes the send order.
|
|
11
|
+
|
|
12
|
+
Reminders stay where they were first sent. Once a request carrying them is
|
|
13
|
+
established, the reminders a user message received are recorded in its
|
|
14
|
+
``additional_properties`` (``HistoryMarkerKind.SYSTEM_REMINDERS_KEY``), and
|
|
15
|
+
every later call re-renders that record byte-identically — in later turns
|
|
16
|
+
too — so the conversation prefix, and the provider's prompt cache over it,
|
|
17
|
+
survives from one turn to the next. The message's own contents never hold a
|
|
18
|
+
reminder: ``ChatContext.messages`` is a **shallow copy** of the tool loop's
|
|
19
|
+
``prepped_messages``, so enrichment replaces list elements, never the history
|
|
20
|
+
message objects.
|
|
21
|
+
|
|
22
|
+
Only reminders not already visible are added to the last user message:
|
|
23
|
+
|
|
24
|
+
- the turn line (turn-start time, previous-turn context usage) once per
|
|
25
|
+
turn;
|
|
26
|
+
- event reminders (hooks, workspace file changes, the context-usage warning,
|
|
27
|
+
which ``reminders.context_usage`` arms) once per turn by text;
|
|
28
|
+
- the profile-switch notice while the latest switch notice the turn shows
|
|
29
|
+
differs — a switch made before a retry reaches the retried request, and
|
|
30
|
+
Code → QA → Code → QA across retries sends the last notice again;
|
|
31
|
+
- catalogs — standing context: runtime environment, sub-agent tip, todo
|
|
32
|
+
list, skill catalog, MCP server instructions, archive-catalog pointer —
|
|
33
|
+
whenever the latest version of that catalog in view differs: a changed
|
|
34
|
+
catalog (changed back included), or one whose carrier a context fold took
|
|
35
|
+
out of view, is sent again, and one no longer offered is replaced by a
|
|
36
|
+
notice that it no longer applies where it has one.
|
|
37
|
+
|
|
38
|
+
A call that continues a service-side conversation (``conversation_id``)
|
|
39
|
+
sends only the new messages, so what that conversation holds is out of view:
|
|
40
|
+
the catalogs the last answered call left in it count as in view when the
|
|
41
|
+
next call continues exactly from the handle that answer returned. Any other
|
|
42
|
+
handle, a failed call, or a fresh middleware sends them again.
|
|
43
|
+
|
|
44
|
+
A continuation poll adds and records nothing: the provider ignores its
|
|
45
|
+
messages.
|
|
46
|
+
|
|
47
|
+
Turn reminders are snapshotted at ``prepare_turn()`` and reused for every LLM
|
|
48
|
+
call in that turn. User-initiated injection/retry may refresh the skill
|
|
49
|
+
catalog before the next model call because a newly added skill is a
|
|
50
|
+
meaningful runtime capability change. LAST_WORDS is different: it is
|
|
51
|
+
generated by mid-turn compaction, remains dynamic and is never recorded. Do
|
|
52
|
+
not reintroduce per-call timestamps or other volatile reminder text.
|
|
53
|
+
|
|
54
|
+
Escaping scope: this middleware defangs ``<system-reminder>`` tags in
|
|
55
|
+
user-role message content only. Tool-result content (web fetches, file
|
|
56
|
+
reads, sub-agent output) is passed through verbatim — treat reminder-shaped
|
|
57
|
+
text in tool results as untrusted in downstream prompts and rely on the
|
|
58
|
+
model's own judgement, not on this middleware, to discount it.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
from __future__ import annotations
|
|
62
|
+
|
|
63
|
+
from collections.abc import Mapping
|
|
64
|
+
from contextvars import ContextVar
|
|
65
|
+
from dataclasses import dataclass, replace
|
|
66
|
+
from pathlib import Path
|
|
67
|
+
from typing import TYPE_CHECKING, Any, Literal, Protocol, cast, get_args
|
|
68
|
+
|
|
69
|
+
from chrys.foundation.models.history_markers import HistoryMarkerKind
|
|
70
|
+
from chrys.foundation.models.turns import opens_turn
|
|
71
|
+
from chrys.kernel import EXCLUDED_KEY, ChatResponse, Message, is_local_history_conversation_id
|
|
72
|
+
from chrys.kernel.middleware import ChatMiddleware
|
|
73
|
+
from chrys.service.profiles.models.schema import DEFAULT_MAX_CONTEXT_TOKENS
|
|
74
|
+
|
|
75
|
+
from .reminders import ReminderSources, build_sources
|
|
76
|
+
|
|
77
|
+
if TYPE_CHECKING:
|
|
78
|
+
from collections.abc import Awaitable, Callable, Iterable, Sequence
|
|
79
|
+
|
|
80
|
+
from chrys.foundation.models.session_env import SessionEnvironment
|
|
81
|
+
from chrys.kernel.middleware import ChatContext
|
|
82
|
+
from chrys.service.context.compaction.last_words_state import LastWordsTurn
|
|
83
|
+
from chrys.service.context.compaction.spill import SpillQuota
|
|
84
|
+
|
|
85
|
+
from .reminders.base import CatalogSource
|
|
86
|
+
from .reminders.file_change import FileChangeTurn
|
|
87
|
+
from .reminders.profile_switch import SwitchSnapshot
|
|
88
|
+
|
|
89
|
+
REMINDER_TAG_OPEN = "<system-reminder>"
|
|
90
|
+
REMINDER_TAG_CLOSE = "</system-reminder>"
|
|
91
|
+
ESCAPED_REMINDER_TAG_OPEN = "<system-reminder>"
|
|
92
|
+
ESCAPED_REMINDER_TAG_CLOSE = "</system-reminder>"
|
|
93
|
+
|
|
94
|
+
LAST_WORDS_PREFIX = "[LAST_WORDS] "
|
|
95
|
+
"""Opens every LAST_WORDS block (rendered by ``compaction.last_words_state``).
|
|
96
|
+
|
|
97
|
+
Load-bearing: ``refresh_last_words_reminder`` finds the blocks to replace by
|
|
98
|
+
it, so it must stay byte-identical."""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def escape_system_reminder_tags(text: str) -> str:
|
|
102
|
+
"""Defang user-authored reminder tags before text reaches the model."""
|
|
103
|
+
return text.replace(REMINDER_TAG_OPEN, ESCAPED_REMINDER_TAG_OPEN).replace(
|
|
104
|
+
REMINDER_TAG_CLOSE,
|
|
105
|
+
ESCAPED_REMINDER_TAG_CLOSE,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def wrap_system_reminder(content: str) -> str:
|
|
110
|
+
"""Wrap *content* in ``<system-reminder>`` tags."""
|
|
111
|
+
return f"{REMINDER_TAG_OPEN}\n{escape_system_reminder_tags(content)}\n{REMINDER_TAG_CLOSE}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
_ReminderKind = Literal["turn", "event", "switch", "catalog"]
|
|
115
|
+
_REMINDER_KINDS: frozenset[str] = frozenset(get_args(_ReminderKind))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True, slots=True)
|
|
119
|
+
class _ReminderItem:
|
|
120
|
+
"""One reminder text and the rule that decides whether it is sent again.
|
|
121
|
+
|
|
122
|
+
``turn``: turn-start snapshot, sent once per turn as a group. ``event``:
|
|
123
|
+
sent once per turn per text. ``switch``: the profile-switch notice, sent
|
|
124
|
+
while the turn's latest switch notice differs. ``catalog``: standing
|
|
125
|
+
context, sent while the latest version of the catalog *name* in view
|
|
126
|
+
differs. Records written before the runtime environment, todo list and
|
|
127
|
+
archive pointer became catalogs hold them as ``turn`` items.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
kind: _ReminderKind
|
|
131
|
+
text: str
|
|
132
|
+
name: str | None = None
|
|
133
|
+
"""Which catalog a ``catalog`` item is a version of; None for other kinds."""
|
|
134
|
+
|
|
135
|
+
def to_record(self) -> dict[str, str]:
|
|
136
|
+
record = {"kind": self.kind, "text": self.text}
|
|
137
|
+
if self.name is not None:
|
|
138
|
+
record["name"] = self.name
|
|
139
|
+
return record
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _recorded_reminders(properties: Mapping[str, Any]) -> list[_ReminderItem]:
|
|
143
|
+
"""Return the reminders recorded on a user message, skipping malformed entries."""
|
|
144
|
+
raw = properties.get(HistoryMarkerKind.SYSTEM_REMINDERS_KEY)
|
|
145
|
+
if not isinstance(raw, list):
|
|
146
|
+
return []
|
|
147
|
+
items: list[_ReminderItem] = []
|
|
148
|
+
for entry in raw:
|
|
149
|
+
if not isinstance(entry, Mapping):
|
|
150
|
+
continue
|
|
151
|
+
kind = entry.get("kind")
|
|
152
|
+
text = entry.get("text")
|
|
153
|
+
name = entry.get("name")
|
|
154
|
+
# Type checks first: a persisted list or object ``kind`` is unhashable.
|
|
155
|
+
if isinstance(kind, str) and kind in _REMINDER_KINDS and isinstance(text, str) and text:
|
|
156
|
+
items.append(_ReminderItem(cast("_ReminderKind", kind), text, name if isinstance(name, str) else None))
|
|
157
|
+
return items
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _records_of(messages: Sequence[Message]) -> list[list[_ReminderItem]]:
|
|
161
|
+
"""Each message's recorded reminders, parsed once per call (none for non-user roles)."""
|
|
162
|
+
return [
|
|
163
|
+
_recorded_reminders(message.additional_properties) if message.role == "user" else [] for message in messages
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _record_reminders(properties: dict[str, Any], items: Sequence[_ReminderItem]) -> None:
|
|
168
|
+
"""Record *items* on a message, always as a new list.
|
|
169
|
+
|
|
170
|
+
Retry rollback restores a props dict from a one-level copy that shares
|
|
171
|
+
its list values, so the stored list itself is never mutated.
|
|
172
|
+
"""
|
|
173
|
+
properties[HistoryMarkerKind.SYSTEM_REMINDERS_KEY] = [item.to_record() for item in items]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _is_visible(message: Message) -> bool:
|
|
177
|
+
return not message.additional_properties.get(EXCLUDED_KEY, False)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _catalog_key(item: _ReminderItem) -> str:
|
|
181
|
+
"""The catalog *item* is a version of; an unnamed entry is its own catalog."""
|
|
182
|
+
return item.name if item.name is not None else item.text
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _latest_switch_notice(items: Iterable[_ReminderItem]) -> str | None:
|
|
186
|
+
"""The last profile-switch notice among *items*, if any."""
|
|
187
|
+
latest: str | None = None
|
|
188
|
+
for item in items:
|
|
189
|
+
if item.kind == "switch":
|
|
190
|
+
latest = item.text
|
|
191
|
+
return latest
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _latest_visible_catalogs(
|
|
195
|
+
messages: Sequence[Message],
|
|
196
|
+
records: Sequence[Sequence[_ReminderItem]],
|
|
197
|
+
) -> dict[str, str]:
|
|
198
|
+
"""The latest version of each catalog recorded on user messages still in view.
|
|
199
|
+
|
|
200
|
+
An older version stays in view after a newer one was sent, so a catalog
|
|
201
|
+
compares with its latest version only: A → B → A must send A again.
|
|
202
|
+
"""
|
|
203
|
+
latest: dict[str, str] = {}
|
|
204
|
+
for message, items in zip(messages, records, strict=True):
|
|
205
|
+
if message.role != "user" or not _is_visible(message):
|
|
206
|
+
continue
|
|
207
|
+
for item in items:
|
|
208
|
+
if item.kind == "catalog":
|
|
209
|
+
latest[_catalog_key(item)] = item.text
|
|
210
|
+
return latest
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _withdrawn_notices(sources: Iterable[CatalogSource]) -> dict[str, str]:
|
|
214
|
+
"""Each catalog's withdrawal notice by record name, for the catalogs that have one.
|
|
215
|
+
|
|
216
|
+
A catalog that disappeared (its provider went away or has nothing to say)
|
|
217
|
+
still shows its last version on an earlier message, so the model is told
|
|
218
|
+
it no longer applies.
|
|
219
|
+
"""
|
|
220
|
+
return {source.name: source.withdrawn for source in sources if source.withdrawn is not None}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _catalog_updates(
|
|
224
|
+
latest: Mapping[str, str],
|
|
225
|
+
current: Sequence[_ReminderItem],
|
|
226
|
+
withdrawn_notices: Mapping[str, str],
|
|
227
|
+
) -> list[_ReminderItem]:
|
|
228
|
+
"""The catalogs to send so that the latest version of each in view is current.
|
|
229
|
+
|
|
230
|
+
A changed catalog (changed back included) goes out again; one no longer
|
|
231
|
+
offered is replaced by its notice in *withdrawn_notices* while an older
|
|
232
|
+
version of it is still in view.
|
|
233
|
+
"""
|
|
234
|
+
updates = [item for item in current if latest.get(_catalog_key(item)) != item.text]
|
|
235
|
+
offered = {_catalog_key(item) for item in current}
|
|
236
|
+
for name, text in latest.items():
|
|
237
|
+
withdrawn = withdrawn_notices.get(name)
|
|
238
|
+
if name not in offered and withdrawn is not None and text != withdrawn:
|
|
239
|
+
updates.append(_ReminderItem("catalog", withdrawn, name))
|
|
240
|
+
return updates
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _is_continuation_poll(context: ChatContext) -> bool:
|
|
244
|
+
"""A poll retrieves an already-created response; the provider ignores its messages."""
|
|
245
|
+
options = context.options
|
|
246
|
+
return options is not None and options.get("continuation_token") is not None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _service_handle(conversation_id: object) -> str | None:
|
|
250
|
+
"""*conversation_id* when it names a service-side conversation."""
|
|
251
|
+
if isinstance(conversation_id, str) and conversation_id and not is_local_history_conversation_id(conversation_id):
|
|
252
|
+
return conversation_id
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@dataclass
|
|
257
|
+
class TurnReminderState:
|
|
258
|
+
"""Intentionally mutable reminder state shared across one logical turn.
|
|
259
|
+
|
|
260
|
+
ContextVar values are copied into child tasks, so the middleware stores one
|
|
261
|
+
state object and mutates it in place. That lets Phase 4 LAST_WORDS updates
|
|
262
|
+
made inside an agent child task remain visible to sibling retry/resume tasks
|
|
263
|
+
that continue the same logical turn.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
turn_reminders: tuple[str, ...] = ()
|
|
267
|
+
"""Turn-start snapshot sent once per turn: time and previous-turn usage."""
|
|
268
|
+
context_warning: str | None = None
|
|
269
|
+
"""The context-usage warning, when this turn crosses the threshold."""
|
|
270
|
+
event_reminders: tuple[str, ...] = ()
|
|
271
|
+
"""Hook and skill-reference reminders queued for this turn."""
|
|
272
|
+
runtime_hint: str | None = None
|
|
273
|
+
sub_agent_tip: str | None = None
|
|
274
|
+
todo: str | None = None
|
|
275
|
+
"""Turn-start todo list (None when empty)."""
|
|
276
|
+
skill_catalog: str | None = None
|
|
277
|
+
mcp_instructions: str | None = None
|
|
278
|
+
switch: SwitchSnapshot | None = None
|
|
279
|
+
"""The switch this turn announces, with its notice: a retry keeps the
|
|
280
|
+
opener's turn group, and the notice must still reach the retried request."""
|
|
281
|
+
file_change: FileChangeTurn | None = None
|
|
282
|
+
"""The workspace file-change notice drained for this turn."""
|
|
283
|
+
last_words: LastWordsTurn | None = None
|
|
284
|
+
"""The Phase 4 slot ``compaction.last_words_state.LastWordsState`` owns; None until it writes one."""
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class LastWordsPort(Protocol):
|
|
288
|
+
"""The LAST_WORDS state this middleware renders (``compaction.last_words_state.LastWordsState``)."""
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def turns(self) -> ReminderTurns:
|
|
292
|
+
"""The turn holder the state reads and writes; the middleware keeps its turns there too."""
|
|
293
|
+
...
|
|
294
|
+
|
|
295
|
+
def begin_turn(self, previous: TurnReminderState | None, *, preserve: bool) -> LastWordsTurn:
|
|
296
|
+
"""The new turn's LAST_WORDS slot, given the previous turn's container."""
|
|
297
|
+
...
|
|
298
|
+
|
|
299
|
+
def discard_restored(self) -> None:
|
|
300
|
+
"""Drop the restored stash: the turn ``begin_turn`` just built took it or discarded it."""
|
|
301
|
+
...
|
|
302
|
+
|
|
303
|
+
def record_paths(self, turn: LastWordsTurn) -> set[str]:
|
|
304
|
+
"""The record paths *turn*'s manifest lists."""
|
|
305
|
+
...
|
|
306
|
+
|
|
307
|
+
def render(self) -> list[str]:
|
|
308
|
+
"""The LAST_WORDS blocks the next call appends, unwrapped."""
|
|
309
|
+
...
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class ReminderTurns:
|
|
313
|
+
"""Where one middleware keeps its per-turn reminder container.
|
|
314
|
+
|
|
315
|
+
The container lives in a ContextVar, so a task sees the turn its caller
|
|
316
|
+
prepared, and in an active-turn fallback: the engine runs one main turn
|
|
317
|
+
per middleware at a time, and a child or sibling task started under an
|
|
318
|
+
empty context recovers that turn's container from it.
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
def __init__(self) -> None:
|
|
322
|
+
self._local: ContextVar[TurnReminderState | None] = ContextVar(
|
|
323
|
+
f"chrys_reminder_turns_{id(self)}",
|
|
324
|
+
default=None,
|
|
325
|
+
)
|
|
326
|
+
self._active: TurnReminderState | None = None
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def active(self) -> TurnReminderState | None:
|
|
330
|
+
"""The prepared turn's container, in any task."""
|
|
331
|
+
return self._active
|
|
332
|
+
|
|
333
|
+
def local(self) -> TurnReminderState | None:
|
|
334
|
+
"""This task's container, without the active-turn fallback."""
|
|
335
|
+
return self._local.get()
|
|
336
|
+
|
|
337
|
+
def current(self) -> TurnReminderState | None:
|
|
338
|
+
"""This task's container, else the active turn's."""
|
|
339
|
+
return self._local.get() or self._active
|
|
340
|
+
|
|
341
|
+
def ensure(self) -> TurnReminderState:
|
|
342
|
+
"""The current container, or a new empty one for this task only."""
|
|
343
|
+
state = self.current()
|
|
344
|
+
if state is None:
|
|
345
|
+
state = TurnReminderState()
|
|
346
|
+
self._local.set(state)
|
|
347
|
+
return state
|
|
348
|
+
|
|
349
|
+
def install(self, state: TurnReminderState) -> None:
|
|
350
|
+
"""Make *state* this task's container and the active turn's."""
|
|
351
|
+
self._local.set(state)
|
|
352
|
+
self._active = state
|
|
353
|
+
|
|
354
|
+
def install_local(self, state: TurnReminderState) -> None:
|
|
355
|
+
"""Make *state* this task's container only."""
|
|
356
|
+
self._local.set(state)
|
|
357
|
+
|
|
358
|
+
def clear(self) -> None:
|
|
359
|
+
"""Forget the active turn and this task's container."""
|
|
360
|
+
self._local.set(None)
|
|
361
|
+
self._active = None
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@dataclass(frozen=True, eq=False)
|
|
365
|
+
class CurrentRunReminderScope:
|
|
366
|
+
"""Opaque service-owned token for one logical main-agent run."""
|
|
367
|
+
|
|
368
|
+
scope_id: int
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@dataclass(frozen=True, eq=False)
|
|
372
|
+
class CurrentRunReminderTarget:
|
|
373
|
+
"""Opaque service-owned current-run reminder target captured by orchestration."""
|
|
374
|
+
|
|
375
|
+
scope: CurrentRunReminderScope
|
|
376
|
+
phase: Literal["pre_prepare", "prepared"]
|
|
377
|
+
generation: int
|
|
378
|
+
state: TurnReminderState | None = None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class SystemReminderMiddleware(ChatMiddleware):
|
|
382
|
+
"""Appends trailing ``<system-reminder>`` content before each LLM call.
|
|
383
|
+
|
|
384
|
+
Each LLM call in the tool loop sees fresh enriched user messages whose
|
|
385
|
+
original user contents come first, followed by the reminders each one
|
|
386
|
+
carried when first sent; the last user message also gets the reminders
|
|
387
|
+
not yet in view. Exact reserved reminder tags in user-role text are
|
|
388
|
+
escaped in the per-call ``ChatContext.messages`` copy; tool results are
|
|
389
|
+
passed through unchanged. Message contents in ``prepped_messages`` and
|
|
390
|
+
session state are never modified — only the reminder record in a user
|
|
391
|
+
message's ``additional_properties``.
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
def __init__(
|
|
395
|
+
self,
|
|
396
|
+
runtime: SessionEnvironment | None = None,
|
|
397
|
+
*,
|
|
398
|
+
max_context_tokens: int = DEFAULT_MAX_CONTEXT_TOKENS,
|
|
399
|
+
warn_threshold_pct: float = 0.50,
|
|
400
|
+
sub_agent_names: list[str] | None = None,
|
|
401
|
+
shell_tool_enabled: bool = False,
|
|
402
|
+
tool_names: list[str] | None = None,
|
|
403
|
+
session_root: Path | None = None,
|
|
404
|
+
file_read_available: bool = False,
|
|
405
|
+
spill_quota: SpillQuota | None = None,
|
|
406
|
+
catalog_pointer_enabled: bool = True,
|
|
407
|
+
skill_catalog_provider: Callable[[], str | None] | None = None,
|
|
408
|
+
todo_state_provider: Callable[[], str | None] | None = None,
|
|
409
|
+
mcp_instructions_provider: Callable[[], str | None] | None = None,
|
|
410
|
+
file_change_provider: Callable[[], str | None] | None = None,
|
|
411
|
+
last_words: LastWordsPort | None = None,
|
|
412
|
+
) -> None:
|
|
413
|
+
"""*last_words* comes from ``create_reminder``; the middleware keeps its turns on the state's holder.
|
|
414
|
+
|
|
415
|
+
Without *last_words* the middleware renders no LAST_WORDS block and
|
|
416
|
+
cannot be bound to a compaction strategy.
|
|
417
|
+
"""
|
|
418
|
+
self._sources = build_sources(
|
|
419
|
+
runtime=runtime,
|
|
420
|
+
max_context_tokens=max_context_tokens,
|
|
421
|
+
warn_threshold_pct=warn_threshold_pct,
|
|
422
|
+
shell_tool_enabled=shell_tool_enabled,
|
|
423
|
+
sub_agent_names=sub_agent_names,
|
|
424
|
+
todo_state_provider=todo_state_provider,
|
|
425
|
+
skill_catalog_provider=skill_catalog_provider,
|
|
426
|
+
mcp_instructions_provider=mcp_instructions_provider,
|
|
427
|
+
file_change_provider=file_change_provider,
|
|
428
|
+
tool_names=tool_names,
|
|
429
|
+
session_root=session_root,
|
|
430
|
+
file_read_available=file_read_available,
|
|
431
|
+
spill_quota=spill_quota,
|
|
432
|
+
catalog_pointer_enabled=catalog_pointer_enabled,
|
|
433
|
+
)
|
|
434
|
+
sources = self._sources
|
|
435
|
+
self._withdrawn_notices = _withdrawn_notices(
|
|
436
|
+
(sources.runtime_env, sources.sub_agents, sources.todo, sources.skills, sources.mcp)
|
|
437
|
+
)
|
|
438
|
+
# The catalogs a service-side conversation holds, keyed by the handle
|
|
439
|
+
# its last answered call returned (see the module docstring).
|
|
440
|
+
self._held_catalogs: tuple[str, dict[str, str]] | None = None
|
|
441
|
+
self._pending_hook_reminders: list[str] = []
|
|
442
|
+
self._next_current_run_scope_id = 1
|
|
443
|
+
self._current_run_scopes: set[CurrentRunReminderScope] = set()
|
|
444
|
+
self._pending_current_run_hook_reminders: dict[CurrentRunReminderScope, list[str]] = {}
|
|
445
|
+
self._active_turn_scope: CurrentRunReminderScope | None = None
|
|
446
|
+
self._current_run_reminder_generation = 0
|
|
447
|
+
# One holder for both, so the note Phase 4 writes is in the container
|
|
448
|
+
# prepare_turn installs.
|
|
449
|
+
self._turns = last_words.turns if last_words is not None else ReminderTurns()
|
|
450
|
+
self._last_words = last_words
|
|
451
|
+
|
|
452
|
+
@property
|
|
453
|
+
def sources(self) -> ReminderSources:
|
|
454
|
+
"""The content sources this middleware composes; callers reach the switch through it."""
|
|
455
|
+
return self._sources
|
|
456
|
+
|
|
457
|
+
def prepare_turn(
|
|
458
|
+
self,
|
|
459
|
+
*,
|
|
460
|
+
reminder_scope: CurrentRunReminderScope | None = None,
|
|
461
|
+
usage: Mapping[str, Any] | None = None,
|
|
462
|
+
preserve_last_words: bool = False,
|
|
463
|
+
preserve_turn_reminders: bool = False,
|
|
464
|
+
) -> None:
|
|
465
|
+
"""Prepare reminder state before ``executor.run()`` or retry/resume.
|
|
466
|
+
|
|
467
|
+
``preserve_last_words`` carries Phase 4 progress across retries.
|
|
468
|
+
``preserve_turn_reminders`` keeps runtime/usage/switch text
|
|
469
|
+
byte-identical for KV-cache continuity on retry.
|
|
470
|
+
"""
|
|
471
|
+
previous = self._turns.current()
|
|
472
|
+
snapshot_catalog_pointer = False
|
|
473
|
+
if preserve_turn_reminders and previous is not None:
|
|
474
|
+
snapshot = TurnReminderState(
|
|
475
|
+
turn_reminders=previous.turn_reminders,
|
|
476
|
+
context_warning=previous.context_warning,
|
|
477
|
+
event_reminders=previous.event_reminders,
|
|
478
|
+
runtime_hint=previous.runtime_hint,
|
|
479
|
+
sub_agent_tip=previous.sub_agent_tip,
|
|
480
|
+
todo=previous.todo,
|
|
481
|
+
skill_catalog=previous.skill_catalog,
|
|
482
|
+
mcp_instructions=previous.mcp_instructions,
|
|
483
|
+
switch=previous.switch,
|
|
484
|
+
)
|
|
485
|
+
else:
|
|
486
|
+
snapshot = self._snapshot_turn_reminders(usage=usage or {})
|
|
487
|
+
snapshot.skill_catalog = self._sources.skills.snapshot()
|
|
488
|
+
snapshot.mcp_instructions = self._sources.mcp.snapshot()
|
|
489
|
+
snapshot_catalog_pointer = True
|
|
490
|
+
event_reminders = snapshot.event_reminders
|
|
491
|
+
self._current_run_reminder_generation += 1
|
|
492
|
+
if reminder_scope is not None:
|
|
493
|
+
scoped_reminders = self._pending_current_run_hook_reminders.pop(reminder_scope, [])
|
|
494
|
+
if scoped_reminders:
|
|
495
|
+
event_reminders = (*event_reminders, *scoped_reminders)
|
|
496
|
+
if self._pending_hook_reminders:
|
|
497
|
+
event_reminders = (*event_reminders, *self._pending_hook_reminders)
|
|
498
|
+
self._pending_hook_reminders.clear()
|
|
499
|
+
last_words = (
|
|
500
|
+
self._last_words.begin_turn(previous, preserve=preserve_last_words)
|
|
501
|
+
if self._last_words is not None
|
|
502
|
+
else None
|
|
503
|
+
)
|
|
504
|
+
pointer = self._sources.archive_pointer
|
|
505
|
+
if snapshot_catalog_pointer:
|
|
506
|
+
restored_pointer_count = pointer.restored_record_count if preserve_last_words and previous is None else None
|
|
507
|
+
if restored_pointer_count is not None:
|
|
508
|
+
pointer.capture(restored_pointer_count)
|
|
509
|
+
else:
|
|
510
|
+
# Legacy restored sessions did not persist the turn-start
|
|
511
|
+
# snapshot. Excluding the bounded active manifest is the best
|
|
512
|
+
# compatible approximation, but new saves never rely on it.
|
|
513
|
+
active_manifest_paths = (
|
|
514
|
+
self._last_words.record_paths(last_words)
|
|
515
|
+
if self._last_words is not None and last_words is not None
|
|
516
|
+
else set()
|
|
517
|
+
)
|
|
518
|
+
pointer.capture_live(excluded_relative_paths=active_manifest_paths)
|
|
519
|
+
# The restored stashes are single-shot either way: a preserving turn
|
|
520
|
+
# consumed them; a fresh turn discards them (matching the in-process
|
|
521
|
+
# new-turn semantics).
|
|
522
|
+
pointer.discard_restored()
|
|
523
|
+
if self._last_words is not None:
|
|
524
|
+
self._last_words.discard_restored()
|
|
525
|
+
state = replace(
|
|
526
|
+
snapshot,
|
|
527
|
+
event_reminders=event_reminders,
|
|
528
|
+
file_change=self._sources.file_change.drain(),
|
|
529
|
+
last_words=last_words,
|
|
530
|
+
)
|
|
531
|
+
self._turns.install(state)
|
|
532
|
+
self._active_turn_scope = reminder_scope
|
|
533
|
+
self._sources.profile_switch.begin_turn()
|
|
534
|
+
|
|
535
|
+
def take_undelivered_file_change(self) -> str | None:
|
|
536
|
+
"""Pop the prepared notice unless a model-bound request already carried it."""
|
|
537
|
+
state = self._turns.current()
|
|
538
|
+
if state is None:
|
|
539
|
+
return None
|
|
540
|
+
notice = state.file_change
|
|
541
|
+
state.file_change = None
|
|
542
|
+
if notice is None or notice.delivered:
|
|
543
|
+
return None
|
|
544
|
+
return notice.text
|
|
545
|
+
|
|
546
|
+
def create_current_run_scope(self) -> CurrentRunReminderScope:
|
|
547
|
+
"""Allocate a service-owned reminder scope for a logical main-agent run."""
|
|
548
|
+
scope = CurrentRunReminderScope(self._next_current_run_scope_id)
|
|
549
|
+
self._next_current_run_scope_id += 1
|
|
550
|
+
self._current_run_scopes.add(scope)
|
|
551
|
+
return scope
|
|
552
|
+
|
|
553
|
+
def capture_current_run_target(
|
|
554
|
+
self,
|
|
555
|
+
reminder_scope: CurrentRunReminderScope,
|
|
556
|
+
) -> CurrentRunReminderTarget | None:
|
|
557
|
+
"""Capture a current-run reminder target if *reminder_scope* is still valid."""
|
|
558
|
+
if reminder_scope not in self._current_run_scopes:
|
|
559
|
+
return None
|
|
560
|
+
active = self._turns.active
|
|
561
|
+
if self._active_turn_scope is reminder_scope and active is not None:
|
|
562
|
+
return CurrentRunReminderTarget(
|
|
563
|
+
scope=reminder_scope,
|
|
564
|
+
phase="prepared",
|
|
565
|
+
generation=self._current_run_reminder_generation,
|
|
566
|
+
state=active,
|
|
567
|
+
)
|
|
568
|
+
return CurrentRunReminderTarget(
|
|
569
|
+
scope=reminder_scope,
|
|
570
|
+
phase="pre_prepare",
|
|
571
|
+
generation=self._current_run_reminder_generation,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
def queue_hook_reminders_for_current_run(
|
|
575
|
+
self,
|
|
576
|
+
target: CurrentRunReminderTarget,
|
|
577
|
+
reminders: list[str],
|
|
578
|
+
) -> bool:
|
|
579
|
+
"""Queue hook reminders only if the captured current-run target is still valid."""
|
|
580
|
+
if not self._is_current_run_target_valid(target):
|
|
581
|
+
return False
|
|
582
|
+
clean = [reminder for reminder in reminders if reminder]
|
|
583
|
+
if not clean:
|
|
584
|
+
return True
|
|
585
|
+
if target.phase == "prepared" and target.state is not None:
|
|
586
|
+
target.state.event_reminders = (*target.state.event_reminders, *clean)
|
|
587
|
+
return True
|
|
588
|
+
active = self._turns.active
|
|
589
|
+
if self._active_turn_scope is target.scope and active is not None:
|
|
590
|
+
active.event_reminders = (*active.event_reminders, *clean)
|
|
591
|
+
return True
|
|
592
|
+
self._pending_current_run_hook_reminders.setdefault(target.scope, []).extend(clean)
|
|
593
|
+
return True
|
|
594
|
+
|
|
595
|
+
def update_skill_catalog_for_current_run(self, target: CurrentRunReminderTarget) -> bool:
|
|
596
|
+
"""Refresh the skill catalog only if the captured current-run target is current."""
|
|
597
|
+
if not self._is_current_run_target_valid(target):
|
|
598
|
+
return False
|
|
599
|
+
active = self._turns.active
|
|
600
|
+
if target.phase == "prepared" and target.state is not None:
|
|
601
|
+
target.state.skill_catalog = self._sources.skills.snapshot()
|
|
602
|
+
elif self._active_turn_scope is target.scope and active is not None:
|
|
603
|
+
active.skill_catalog = self._sources.skills.snapshot()
|
|
604
|
+
return True
|
|
605
|
+
|
|
606
|
+
def set_skill_catalog_for_current_run(
|
|
607
|
+
self,
|
|
608
|
+
target: CurrentRunReminderTarget,
|
|
609
|
+
skill_catalog: str | None,
|
|
610
|
+
) -> bool:
|
|
611
|
+
"""Set the skill catalog only if the captured current-run target is current."""
|
|
612
|
+
if not self._is_current_run_target_valid(target):
|
|
613
|
+
return False
|
|
614
|
+
active = self._turns.active
|
|
615
|
+
if target.phase == "prepared" and target.state is not None:
|
|
616
|
+
target.state.skill_catalog = skill_catalog
|
|
617
|
+
elif self._active_turn_scope is target.scope and active is not None:
|
|
618
|
+
active.skill_catalog = skill_catalog
|
|
619
|
+
return True
|
|
620
|
+
|
|
621
|
+
def is_current_run_target_valid(self, target: CurrentRunReminderTarget) -> bool:
|
|
622
|
+
"""Return whether a captured current-run reminder target is still valid."""
|
|
623
|
+
return self._is_current_run_target_valid(target)
|
|
624
|
+
|
|
625
|
+
def expire_current_run_scope(self, reminder_scope: CurrentRunReminderScope) -> None:
|
|
626
|
+
"""Expire a current-run scope and discard stale reminders for it."""
|
|
627
|
+
self._current_run_scopes.discard(reminder_scope)
|
|
628
|
+
self._pending_current_run_hook_reminders.pop(reminder_scope, None)
|
|
629
|
+
if self._active_turn_scope is reminder_scope:
|
|
630
|
+
self._active_turn_scope = None
|
|
631
|
+
self._turns.clear()
|
|
632
|
+
|
|
633
|
+
def renders_last_words(self, state: object) -> bool:
|
|
634
|
+
"""Whether *state* is the LAST_WORDS state this middleware renders."""
|
|
635
|
+
return self._last_words is not None and self._last_words is state
|
|
636
|
+
|
|
637
|
+
def refresh_last_words_reminder(self, messages: list[Any]) -> int | None:
|
|
638
|
+
"""Rewrite the ``[LAST_WORDS]`` reminder on a per-call message list.
|
|
639
|
+
|
|
640
|
+
Phase 4 compaction runs *below* this middleware (inside the client's
|
|
641
|
+
message prep), so the LLM call that triggered it was enriched before
|
|
642
|
+
the new note existed — without this refresh, that call would go out
|
|
643
|
+
with its tool-call history dropped but only a stale note (or none)
|
|
644
|
+
in its place, and the model would restart the task from scratch.
|
|
645
|
+
|
|
646
|
+
Rebuilds the last user message with the current note rendered
|
|
647
|
+
exactly as the next call's enrichment will render it, keeping the
|
|
648
|
+
request prefix byte-stable across calls. The list entry is
|
|
649
|
+
*replaced* with a fresh ``Message`` — the existing object is never
|
|
650
|
+
mutated, because when enrichment was skipped it is the live history
|
|
651
|
+
message. ``additional_properties`` is shared by reference so
|
|
652
|
+
exclusion flags keep writing through to the history object; the
|
|
653
|
+
caller must recompute any token annotation riding in it, since the
|
|
654
|
+
shared count still describes the pre-refresh contents. Returns the
|
|
655
|
+
index of the replaced entry, or ``None`` when the list was left
|
|
656
|
+
unmodified.
|
|
657
|
+
"""
|
|
658
|
+
message, idx = self._find_user_message(messages)
|
|
659
|
+
if message is None:
|
|
660
|
+
return None
|
|
661
|
+
reminders = self._last_words_reminders()
|
|
662
|
+
kept = [c for c in message.contents if not self._is_last_words_reminder_content(c)]
|
|
663
|
+
if len(kept) == len(message.contents) and not reminders:
|
|
664
|
+
return None
|
|
665
|
+
messages[idx] = self._rebuild(message, [*kept, *(wrap_system_reminder(r) for r in reminders)])
|
|
666
|
+
return idx
|
|
667
|
+
|
|
668
|
+
def restore_folded_reminders(self, messages: list[Message]) -> int | None:
|
|
669
|
+
"""Re-send catalogs (standing context) a context fold just took out of view on this call.
|
|
670
|
+
|
|
671
|
+
A catalog is sent once and then only re-rendered from the user
|
|
672
|
+
message that recorded it. Folds (``compress_context``, Phase 3) run
|
|
673
|
+
*below* this middleware, inside the client's message prep, and
|
|
674
|
+
exclude whole earlier turns: when the carrier goes, the call that
|
|
675
|
+
triggered the fold would otherwise go out without the catalog. The
|
|
676
|
+
missing catalogs are appended to the last user message, before any
|
|
677
|
+
LAST_WORDS block, and recorded on it, so later calls and turns render
|
|
678
|
+
them there.
|
|
679
|
+
|
|
680
|
+
The list entry is replaced with a fresh ``Message`` sharing the
|
|
681
|
+
original's ``additional_properties``, as ``refresh_last_words_reminder``
|
|
682
|
+
does; the caller must recompute token annotations from the returned
|
|
683
|
+
index. Returns ``None`` when nothing was missing.
|
|
684
|
+
"""
|
|
685
|
+
message, idx = self._find_user_message(messages)
|
|
686
|
+
state = self._turns.current()
|
|
687
|
+
if message is None or state is None or not _is_visible(message):
|
|
688
|
+
return None
|
|
689
|
+
missing = _catalog_updates(
|
|
690
|
+
_latest_visible_catalogs(messages, self._rendered_records(messages)),
|
|
691
|
+
self._catalog_items(state),
|
|
692
|
+
self._withdrawn_notices,
|
|
693
|
+
)
|
|
694
|
+
if not missing:
|
|
695
|
+
return None
|
|
696
|
+
kept = [c for c in message.contents if not self._is_last_words_reminder_content(c)]
|
|
697
|
+
last_words = [c for c in message.contents if self._is_last_words_reminder_content(c)]
|
|
698
|
+
messages[idx] = self._rebuild(
|
|
699
|
+
message, [*kept, *(wrap_system_reminder(item.text) for item in missing), *last_words]
|
|
700
|
+
)
|
|
701
|
+
_record_reminders(
|
|
702
|
+
message.additional_properties,
|
|
703
|
+
[*_recorded_reminders(message.additional_properties), *missing],
|
|
704
|
+
)
|
|
705
|
+
return idx
|
|
706
|
+
|
|
707
|
+
@staticmethod
|
|
708
|
+
def _is_last_words_reminder_content(content: Any) -> bool:
|
|
709
|
+
"""Whether a content item is a middleware-authored LAST_WORDS reminder.
|
|
710
|
+
|
|
711
|
+
User-authored reminder tags are escaped during enrichment, so a raw
|
|
712
|
+
wrapped ``[LAST_WORDS]`` block can only come from the LAST_WORDS state.
|
|
713
|
+
"""
|
|
714
|
+
return (
|
|
715
|
+
content.type == "text"
|
|
716
|
+
and content.text is not None
|
|
717
|
+
and content.text.startswith(f"{REMINDER_TAG_OPEN}\n{LAST_WORDS_PREFIX}")
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
def queue_hook_reminders(self, reminders: list[str], *, for_next_turn: bool = False) -> None:
|
|
721
|
+
"""Queue hook-provided reminders for the current or next user turn."""
|
|
722
|
+
clean = [r for r in reminders if r]
|
|
723
|
+
if not clean:
|
|
724
|
+
return
|
|
725
|
+
state = None if for_next_turn else self._turns.current()
|
|
726
|
+
if state is not None:
|
|
727
|
+
state.event_reminders = (*state.event_reminders, *clean)
|
|
728
|
+
return
|
|
729
|
+
self._pending_hook_reminders.extend(clean)
|
|
730
|
+
|
|
731
|
+
def queue_drained_injection_reminders(self, reminders: list[str]) -> None:
|
|
732
|
+
"""Queue a drained injection's hook/skill reminders for the call that sends it.
|
|
733
|
+
|
|
734
|
+
The injection middleware runs before this one in the same call, so
|
|
735
|
+
they land on the injected message; an injection that never reaches a
|
|
736
|
+
call (cancelled, or returned to the input box) never queues them.
|
|
737
|
+
"""
|
|
738
|
+
clean = [reminder for reminder in reminders if reminder]
|
|
739
|
+
if clean:
|
|
740
|
+
state = self._turns.ensure()
|
|
741
|
+
state.event_reminders = (*state.event_reminders, *clean)
|
|
742
|
+
|
|
743
|
+
def update_skill_catalog_for_active_turn(self) -> None:
|
|
744
|
+
"""Refresh the active turn's skill-catalog reminder from the provider."""
|
|
745
|
+
state = self._turns.current()
|
|
746
|
+
if state is None:
|
|
747
|
+
return
|
|
748
|
+
state.skill_catalog = self._sources.skills.snapshot()
|
|
749
|
+
|
|
750
|
+
# ------------------------------------------------------------------
|
|
751
|
+
# ChatMiddleware interface
|
|
752
|
+
# ------------------------------------------------------------------
|
|
753
|
+
|
|
754
|
+
async def process(
|
|
755
|
+
self,
|
|
756
|
+
context: ChatContext,
|
|
757
|
+
call_next: Callable[[], Awaitable[None]],
|
|
758
|
+
) -> None:
|
|
759
|
+
reminder_items = self._build_reminder_items()
|
|
760
|
+
last_words_reminders = self._last_words_reminders()
|
|
761
|
+
applied_switch: SwitchSnapshot | None = None
|
|
762
|
+
# Replace user messages in the per-call shallow copy only. This
|
|
763
|
+
# renders recorded reminders and defangs user-authored reminder tags
|
|
764
|
+
# without modifying the tool loop's prepped_messages or
|
|
765
|
+
# SessionContext.input_messages. Tool-result messages are
|
|
766
|
+
# intentionally not escaped here.
|
|
767
|
+
# ``ChatMiddlewareLayer`` always gives ``ChatContext`` a fresh mutable
|
|
768
|
+
# list; the kernel constructor's public annotation is the broader
|
|
769
|
+
# ``Sequence`` accepted at its external boundary.
|
|
770
|
+
messages = cast("list[Message]", context.messages)
|
|
771
|
+
target, target_idx = self._find_user_message(messages)
|
|
772
|
+
if target is not None and (not _is_visible(target) or _is_continuation_poll(context)):
|
|
773
|
+
# An excluded message never reaches the wire, and a poll's
|
|
774
|
+
# messages are ignored: recording on either would claim a
|
|
775
|
+
# delivery that never happened.
|
|
776
|
+
target, target_idx = None, -1
|
|
777
|
+
records = self._rendered_records(messages)
|
|
778
|
+
handle = _service_handle(context.options.get("conversation_id") if context.options is not None else None)
|
|
779
|
+
held = self._held_catalogs[1] if self._held_catalogs is not None and self._held_catalogs[0] == handle else {}
|
|
780
|
+
in_view = {**held, **_latest_visible_catalogs(messages, records)}
|
|
781
|
+
turn_recorded = self._turn_recorded(messages, records, target_idx) if target is not None else []
|
|
782
|
+
new_items = self._unsent_reminders(in_view, turn_recorded, reminder_items) if target is not None else []
|
|
783
|
+
for msg_idx, message in enumerate(messages):
|
|
784
|
+
if message.role != "user" or not _is_visible(message):
|
|
785
|
+
# Excluded messages never reach the wire; nothing to render.
|
|
786
|
+
continue
|
|
787
|
+
reminders = [item.text for item in records[msg_idx]]
|
|
788
|
+
message_last_words_reminders: list[str] = []
|
|
789
|
+
if msg_idx == target_idx:
|
|
790
|
+
reminders.extend(item.text for item in new_items)
|
|
791
|
+
message_last_words_reminders = last_words_reminders
|
|
792
|
+
if reminders or message_last_words_reminders or self._has_system_reminder_tags(message):
|
|
793
|
+
messages[msg_idx] = self._create_enriched(message, reminders, message_last_words_reminders)
|
|
794
|
+
if target is not None:
|
|
795
|
+
if new_items:
|
|
796
|
+
context.request_message_observers.append(
|
|
797
|
+
self._record_on_delivery(target.additional_properties, tuple(new_items))
|
|
798
|
+
)
|
|
799
|
+
state = self._turns.current()
|
|
800
|
+
if state is not None and any(
|
|
801
|
+
item.kind == "event" and item.text == state.context_warning for item in new_items
|
|
802
|
+
):
|
|
803
|
+
context.request_message_observers.append(self._sources.context_warning.delivered)
|
|
804
|
+
if state is not None and state.file_change is not None:
|
|
805
|
+
# Delivered means an established provider request, not
|
|
806
|
+
# a completed middleware chain: lazy streaming inner
|
|
807
|
+
# middleware returns a proxy from call_next() and only
|
|
808
|
+
# reaches the final handler at first stream
|
|
809
|
+
# consumption. Observers run immediately before every
|
|
810
|
+
# final-handler invocation, so a run cancelled before
|
|
811
|
+
# consumption keeps the notice requeueable.
|
|
812
|
+
context.request_message_observers.append(state.file_change.mark_delivered)
|
|
813
|
+
if (
|
|
814
|
+
state is not None
|
|
815
|
+
and state.switch is not None
|
|
816
|
+
and _latest_switch_notice((*turn_recorded, *new_items)) == state.switch.notice
|
|
817
|
+
):
|
|
818
|
+
# Only a switch whose notice is the latest this request
|
|
819
|
+
# carries (sent now or earlier in the turn) is consumed.
|
|
820
|
+
applied_switch = state.switch
|
|
821
|
+
|
|
822
|
+
polled = _is_continuation_poll(context)
|
|
823
|
+
if not polled:
|
|
824
|
+
# Unknown until this call is answered: a failed call may or may
|
|
825
|
+
# not have left its catalogs in the conversation.
|
|
826
|
+
self._held_catalogs = None
|
|
827
|
+
await call_next()
|
|
828
|
+
if not polled:
|
|
829
|
+
carried = in_view | {_catalog_key(item): item.text for item in new_items if item.kind == "catalog"}
|
|
830
|
+
self._hold_catalogs_on_answer(context, carried)
|
|
831
|
+
if applied_switch is not None:
|
|
832
|
+
self._sources.profile_switch.carried(applied_switch)
|
|
833
|
+
|
|
834
|
+
# ------------------------------------------------------------------
|
|
835
|
+
# Internal helpers
|
|
836
|
+
# ------------------------------------------------------------------
|
|
837
|
+
|
|
838
|
+
def _build_reminders(self) -> list[str]:
|
|
839
|
+
"""Every reminder text this turn offers, whether or not already sent."""
|
|
840
|
+
return [item.text for item in self._build_reminder_items()]
|
|
841
|
+
|
|
842
|
+
def _build_reminder_items(self) -> list[_ReminderItem]:
|
|
843
|
+
state = self._turns.local()
|
|
844
|
+
if state is None:
|
|
845
|
+
# Fallback for direct middleware use without ``prepare_turn()``.
|
|
846
|
+
# It deliberately avoids consuming pending profile switches:
|
|
847
|
+
# switch reminders are turn-scoped and must be snapshotted by
|
|
848
|
+
# the caller that starts the turn.
|
|
849
|
+
state = self._snapshot_turn_reminders(usage={}, include_pending_switch=False)
|
|
850
|
+
state.skill_catalog = self._sources.skills.snapshot()
|
|
851
|
+
state.mcp_instructions = self._sources.mcp.snapshot()
|
|
852
|
+
self._turns.install_local(state)
|
|
853
|
+
return self._compose_items(state)
|
|
854
|
+
|
|
855
|
+
def _compose_items(self, state: TurnReminderState) -> list[_ReminderItem]:
|
|
856
|
+
"""Every reminder *state* offers, in send order, each with the kind that decides its re-send.
|
|
857
|
+
|
|
858
|
+
The one place the send order is written: the turn line, the
|
|
859
|
+
context-usage warning, the profile-switch notice, the event inbox in
|
|
860
|
+
queue order, the workspace file change, then the catalogs in their
|
|
861
|
+
fixed order. ``_unsent_reminders`` keeps this order and appends the
|
|
862
|
+
withdrawals; LAST_WORDS blocks go after everything, unrecorded.
|
|
863
|
+
"""
|
|
864
|
+
items = [_ReminderItem("turn", text) for text in state.turn_reminders]
|
|
865
|
+
if state.context_warning:
|
|
866
|
+
items.append(_ReminderItem("event", state.context_warning))
|
|
867
|
+
if state.switch is not None:
|
|
868
|
+
items.append(_ReminderItem("switch", state.switch.notice))
|
|
869
|
+
items.extend(_ReminderItem("event", text) for text in state.event_reminders)
|
|
870
|
+
if state.file_change is not None:
|
|
871
|
+
items.append(_ReminderItem("event", state.file_change.text))
|
|
872
|
+
items.extend(self._catalog_items(state))
|
|
873
|
+
return items
|
|
874
|
+
|
|
875
|
+
def _catalog_items(self, state: TurnReminderState) -> list[_ReminderItem]:
|
|
876
|
+
"""The standing context this turn offers, one item per catalog name."""
|
|
877
|
+
return [
|
|
878
|
+
_ReminderItem("catalog", text, name)
|
|
879
|
+
for name, text in (
|
|
880
|
+
(self._sources.runtime_env.name, state.runtime_hint),
|
|
881
|
+
(self._sources.sub_agents.name, state.sub_agent_tip),
|
|
882
|
+
(self._sources.todo.name, state.todo),
|
|
883
|
+
(self._sources.skills.name, state.skill_catalog),
|
|
884
|
+
(self._sources.mcp.name, state.mcp_instructions),
|
|
885
|
+
(self._sources.archive_pointer.name, self._sources.archive_pointer.current_text()),
|
|
886
|
+
)
|
|
887
|
+
if text
|
|
888
|
+
]
|
|
889
|
+
|
|
890
|
+
@staticmethod
|
|
891
|
+
def _turn_recorded(
|
|
892
|
+
messages: list[Message],
|
|
893
|
+
records: list[list[_ReminderItem]],
|
|
894
|
+
target_idx: int,
|
|
895
|
+
) -> list[_ReminderItem]:
|
|
896
|
+
"""Reminders recorded on the current turn's user messages still in view.
|
|
897
|
+
|
|
898
|
+
Messages a fold excluded (or left out of the list entirely) no longer
|
|
899
|
+
show theirs. The current turn runs from its opener to the last user
|
|
900
|
+
message, injected and resumed messages included; with no opener in
|
|
901
|
+
the list, from its start.
|
|
902
|
+
"""
|
|
903
|
+
turn_start = next((i for i in range(target_idx, -1, -1) if opens_turn(messages[i])), 0)
|
|
904
|
+
return [
|
|
905
|
+
item
|
|
906
|
+
for idx in range(turn_start, target_idx + 1)
|
|
907
|
+
if messages[idx].role == "user" and _is_visible(messages[idx])
|
|
908
|
+
for item in records[idx]
|
|
909
|
+
]
|
|
910
|
+
|
|
911
|
+
def _unsent_reminders(
|
|
912
|
+
self,
|
|
913
|
+
catalogs_in_view: Mapping[str, str],
|
|
914
|
+
turn_recorded: list[_ReminderItem],
|
|
915
|
+
items: list[_ReminderItem],
|
|
916
|
+
) -> list[_ReminderItem]:
|
|
917
|
+
"""Return the reminders the last user message still has to carry.
|
|
918
|
+
|
|
919
|
+
Only reminders in view count as sent: *turn_recorded* for turn, event
|
|
920
|
+
and switch reminders, *catalogs_in_view* (the latest version of each)
|
|
921
|
+
for catalogs.
|
|
922
|
+
"""
|
|
923
|
+
turn_started = any(item.kind == "turn" for item in turn_recorded)
|
|
924
|
+
turn_texts = {item.text for item in turn_recorded}
|
|
925
|
+
latest_switch = _latest_switch_notice(turn_recorded)
|
|
926
|
+
catalogs = _catalog_updates(
|
|
927
|
+
catalogs_in_view,
|
|
928
|
+
[item for item in items if item.kind == "catalog"],
|
|
929
|
+
self._withdrawn_notices,
|
|
930
|
+
)
|
|
931
|
+
unsent: list[_ReminderItem] = []
|
|
932
|
+
for item in items:
|
|
933
|
+
if item.kind == "turn":
|
|
934
|
+
sent = turn_started
|
|
935
|
+
elif item.kind == "switch":
|
|
936
|
+
# Switches compare with the turn's latest one only: switching
|
|
937
|
+
# Code → QA → Code → QA across retries repeats an earlier text.
|
|
938
|
+
sent = latest_switch == item.text
|
|
939
|
+
latest_switch = item.text
|
|
940
|
+
elif item.kind == "event":
|
|
941
|
+
# Two hooks can queue the same text; it still goes once.
|
|
942
|
+
sent = item.text in turn_texts
|
|
943
|
+
turn_texts.add(item.text)
|
|
944
|
+
else:
|
|
945
|
+
sent = item not in catalogs
|
|
946
|
+
if not sent:
|
|
947
|
+
unsent.append(item)
|
|
948
|
+
# Withdrawal notices for catalogs no longer offered go last.
|
|
949
|
+
unsent.extend(item for item in catalogs if item not in items)
|
|
950
|
+
return unsent
|
|
951
|
+
|
|
952
|
+
def _rendered_records(self, messages: Sequence[Message]) -> list[list[_ReminderItem]]:
|
|
953
|
+
"""Each message's recorded reminders as this session renders them, parsed once per call."""
|
|
954
|
+
return [[self._rendered(item) for item in items] for items in _records_of(messages)]
|
|
955
|
+
|
|
956
|
+
def _rendered(self, item: _ReminderItem) -> _ReminderItem:
|
|
957
|
+
"""A recorded reminder as this session renders it.
|
|
958
|
+
|
|
959
|
+
A recorded archive pointer names this session's catalog
|
|
960
|
+
(``ArchivePointerSource.retarget``). Comparisons read the rendered
|
|
961
|
+
text too, so a forked or moved session does not send it again.
|
|
962
|
+
"""
|
|
963
|
+
pointer = self._sources.archive_pointer
|
|
964
|
+
is_pointer_kind = item.kind == "turn" or (item.kind == "catalog" and item.name == pointer.name)
|
|
965
|
+
retargeted = pointer.retarget(item.text) if is_pointer_kind else None
|
|
966
|
+
if retargeted is None:
|
|
967
|
+
return item
|
|
968
|
+
return replace(item, text=retargeted)
|
|
969
|
+
|
|
970
|
+
def _hold_catalogs_on_answer(self, context: ChatContext, carried: dict[str, str]) -> None:
|
|
971
|
+
"""Remember *carried* under the handle the answer to this call returns."""
|
|
972
|
+
|
|
973
|
+
def _hold(response: ChatResponse) -> ChatResponse:
|
|
974
|
+
handle = _service_handle(response.conversation_id)
|
|
975
|
+
self._held_catalogs = (handle, carried) if handle is not None else None
|
|
976
|
+
return response
|
|
977
|
+
|
|
978
|
+
result = context.result
|
|
979
|
+
if isinstance(result, ChatResponse):
|
|
980
|
+
_hold(result)
|
|
981
|
+
elif result is not None:
|
|
982
|
+
# A stream is answered once it finalizes.
|
|
983
|
+
context.stream_result_hooks.append(_hold)
|
|
984
|
+
|
|
985
|
+
@staticmethod
|
|
986
|
+
def _record_on_delivery(
|
|
987
|
+
properties: dict[str, Any],
|
|
988
|
+
items: tuple[_ReminderItem, ...],
|
|
989
|
+
) -> Callable[[Sequence[Message]], None]:
|
|
990
|
+
"""Return a request observer that records *items* once a request carries them.
|
|
991
|
+
|
|
992
|
+
Observers run before every final-handler invocation, and again with
|
|
993
|
+
the prepared provider request; a validation retry re-sends the same
|
|
994
|
+
enriched list. The record is appended once per enrichment.
|
|
995
|
+
"""
|
|
996
|
+
recorded = False
|
|
997
|
+
|
|
998
|
+
def _observe(_messages: Sequence[Message]) -> None:
|
|
999
|
+
nonlocal recorded
|
|
1000
|
+
if recorded:
|
|
1001
|
+
return
|
|
1002
|
+
recorded = True
|
|
1003
|
+
_record_reminders(properties, [*_recorded_reminders(properties), *items])
|
|
1004
|
+
|
|
1005
|
+
return _observe
|
|
1006
|
+
|
|
1007
|
+
def _is_current_run_target_valid(self, target: CurrentRunReminderTarget) -> bool:
|
|
1008
|
+
if target.scope not in self._current_run_scopes:
|
|
1009
|
+
return False
|
|
1010
|
+
if target.phase == "prepared":
|
|
1011
|
+
return self._active_turn_scope is target.scope and self._turns.active is target.state
|
|
1012
|
+
if self._active_turn_scope is target.scope and self._turns.active is not None:
|
|
1013
|
+
return True
|
|
1014
|
+
return self._active_turn_scope is None and target.generation == self._current_run_reminder_generation
|
|
1015
|
+
|
|
1016
|
+
def _snapshot_turn_reminders(
|
|
1017
|
+
self,
|
|
1018
|
+
*,
|
|
1019
|
+
usage: Mapping[str, Any],
|
|
1020
|
+
include_pending_switch: bool = True,
|
|
1021
|
+
) -> TurnReminderState:
|
|
1022
|
+
"""Snapshot the turn-level reminders, with the pending switch and its notice if included.
|
|
1023
|
+
|
|
1024
|
+
The returned state holds the turn line, the context-usage warning,
|
|
1025
|
+
the standing runtime/sub-agent/todo context and the switch; the
|
|
1026
|
+
caller adds the rest.
|
|
1027
|
+
"""
|
|
1028
|
+
state = TurnReminderState(
|
|
1029
|
+
runtime_hint=self._sources.runtime_env.snapshot(),
|
|
1030
|
+
sub_agent_tip=self._sources.sub_agents.snapshot(),
|
|
1031
|
+
todo=self._sources.todo.snapshot(),
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
# The turn line: time at turn start and the previous turn's usage.
|
|
1035
|
+
turn_line = self._sources.turn_line.snapshot(usage)
|
|
1036
|
+
state.context_warning = self._sources.context_warning.snapshot(usage)
|
|
1037
|
+
if turn_line is not None:
|
|
1038
|
+
state.turn_reminders = (turn_line,)
|
|
1039
|
+
|
|
1040
|
+
# Profile switch (stable for this turn; sent as an event)
|
|
1041
|
+
if include_pending_switch:
|
|
1042
|
+
state.switch = self._sources.profile_switch.snapshot()
|
|
1043
|
+
|
|
1044
|
+
return state
|
|
1045
|
+
|
|
1046
|
+
def _last_words_reminders(self) -> list[str]:
|
|
1047
|
+
"""The LAST_WORDS blocks this call appends after every other reminder."""
|
|
1048
|
+
return self._last_words.render() if self._last_words is not None else []
|
|
1049
|
+
|
|
1050
|
+
@staticmethod
|
|
1051
|
+
def _find_user_message(messages: list[Any]) -> tuple[Message | None, int]:
|
|
1052
|
+
"""Return the last user ``Message`` and its index, or ``(None, -1)``."""
|
|
1053
|
+
for i in range(len(messages) - 1, -1, -1):
|
|
1054
|
+
if messages[i].role == "user":
|
|
1055
|
+
return messages[i], i
|
|
1056
|
+
return None, -1
|
|
1057
|
+
|
|
1058
|
+
@staticmethod
|
|
1059
|
+
def _has_system_reminder_tags(message: Message) -> bool:
|
|
1060
|
+
"""Return true if a message contains raw reserved reminder tags."""
|
|
1061
|
+
for c in message.contents:
|
|
1062
|
+
if (
|
|
1063
|
+
c.type == "text"
|
|
1064
|
+
and c.text is not None
|
|
1065
|
+
and (REMINDER_TAG_OPEN in c.text or REMINDER_TAG_CLOSE in c.text)
|
|
1066
|
+
):
|
|
1067
|
+
return True
|
|
1068
|
+
return False
|
|
1069
|
+
|
|
1070
|
+
@classmethod
|
|
1071
|
+
def _create_enriched(
|
|
1072
|
+
cls,
|
|
1073
|
+
original: Message,
|
|
1074
|
+
turn_reminders: list[str],
|
|
1075
|
+
last_words_reminders: list[str],
|
|
1076
|
+
) -> Message:
|
|
1077
|
+
"""Create a new ``Message`` with user contents first and reminders last.
|
|
1078
|
+
|
|
1079
|
+
This keeps the user's message as the first content item while leaving
|
|
1080
|
+
platform context close to the end of the message.
|
|
1081
|
+
"""
|
|
1082
|
+
reminder_contents = [wrap_system_reminder(r) for r in [*turn_reminders, *last_words_reminders]]
|
|
1083
|
+
# Extract text from Content objects so Message() can rebuild them.
|
|
1084
|
+
# Non-text Content (images, etc.) is passed through as-is.
|
|
1085
|
+
original_items: list[Any] = []
|
|
1086
|
+
for c in original.contents:
|
|
1087
|
+
if c.type == "text" and c.text is not None:
|
|
1088
|
+
original_items.append(escape_system_reminder_tags(c.text))
|
|
1089
|
+
else:
|
|
1090
|
+
original_items.append(c)
|
|
1091
|
+
return cls._rebuild(original, [*original_items, *reminder_contents])
|
|
1092
|
+
|
|
1093
|
+
@staticmethod
|
|
1094
|
+
def _rebuild(original: Message, contents: list[Any]) -> Message:
|
|
1095
|
+
"""A fresh wrapper for *contents* that keeps the original's identity fields.
|
|
1096
|
+
|
|
1097
|
+
``additional_properties`` is shared by reference — the sanctioned
|
|
1098
|
+
write-through channel for exclusion flags and the reminder record.
|
|
1099
|
+
"""
|
|
1100
|
+
rebuilt = Message(
|
|
1101
|
+
original.role,
|
|
1102
|
+
contents,
|
|
1103
|
+
author_name=original.author_name,
|
|
1104
|
+
message_id=original.message_id,
|
|
1105
|
+
)
|
|
1106
|
+
rebuilt.additional_properties = original.additional_properties
|
|
1107
|
+
return rebuilt
|