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,1115 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""ACP update translation and permission protocols owned by the ACP backend."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import base64
|
|
9
|
+
import binascii
|
|
10
|
+
import contextlib
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import math
|
|
15
|
+
import time
|
|
16
|
+
from collections import deque
|
|
17
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
20
|
+
|
|
21
|
+
from acp.exceptions import RequestError
|
|
22
|
+
from acp.schema import (
|
|
23
|
+
AgentMessageChunk,
|
|
24
|
+
AudioContentBlock,
|
|
25
|
+
ContentToolCallContent,
|
|
26
|
+
EmbeddedResourceContentBlock,
|
|
27
|
+
FileEditToolCallContent,
|
|
28
|
+
ImageContentBlock,
|
|
29
|
+
PermissionOption,
|
|
30
|
+
ResourceContentBlock,
|
|
31
|
+
SessionNotification,
|
|
32
|
+
TerminalToolCallContent,
|
|
33
|
+
TextContentBlock,
|
|
34
|
+
ToolCallProgress,
|
|
35
|
+
ToolCallStart,
|
|
36
|
+
ToolCallUpdate,
|
|
37
|
+
UsageUpdate,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from chrys.foundation.events.types import (
|
|
41
|
+
ApprovalCancelled,
|
|
42
|
+
ApprovalRequest,
|
|
43
|
+
ApprovalResponse,
|
|
44
|
+
AskUserResponse,
|
|
45
|
+
AskUserTimedOut,
|
|
46
|
+
InvocationMessage,
|
|
47
|
+
InvocationProgress,
|
|
48
|
+
InvocationToolCallResult,
|
|
49
|
+
InvocationToolCallStart,
|
|
50
|
+
QuestionToUser,
|
|
51
|
+
)
|
|
52
|
+
from chrys.foundation.models.ask_user import Cancelled as AskUserCancelled
|
|
53
|
+
from chrys.foundation.models.ask_user import (
|
|
54
|
+
parse_ask_user_answers,
|
|
55
|
+
validate_request_input_params,
|
|
56
|
+
validate_request_input_response,
|
|
57
|
+
)
|
|
58
|
+
from chrys.foundation.models.invocations import InvocationOrigin
|
|
59
|
+
from chrys.foundation.text.images import MAX_IMAGE_BYTES
|
|
60
|
+
from chrys.foundation.tool_result_metadata import TOOL_FAILED_METADATA_KEY, TOOL_INTERRUPTED_METADATA_KEY
|
|
61
|
+
from chrys.foundation.trajectory.context import TrajectoryContext
|
|
62
|
+
from chrys.foundation.trajectory.event_types import WaitCategory
|
|
63
|
+
from chrys.kernel import Content
|
|
64
|
+
from chrys.service.acp_client.errors import (
|
|
65
|
+
AcpTransportError,
|
|
66
|
+
)
|
|
67
|
+
from chrys.service.acp_client.spec import PermissionDecision
|
|
68
|
+
from chrys.service.approval.arbitration import ApprovalDecisionArbiter, ApprovalJudgeInput
|
|
69
|
+
from chrys.service.approval.correlation import OneShotCorrelation
|
|
70
|
+
from chrys.service.approval.policy import ApprovalMode
|
|
71
|
+
from chrys.service.trajectory.approvals import ApprovalDecider, ApprovalTrace
|
|
72
|
+
from chrys.service.trajectory.waits import WaitOutcome, WaitTrace
|
|
73
|
+
|
|
74
|
+
if TYPE_CHECKING:
|
|
75
|
+
from chrys.foundation.events.bus import EventBus
|
|
76
|
+
from chrys.service.approval.judge import ApprovalJudge
|
|
77
|
+
from chrys.service.approval.turn_context import TurnContextReader
|
|
78
|
+
|
|
79
|
+
logger = logging.getLogger(__name__)
|
|
80
|
+
|
|
81
|
+
_MAX_CALLS = 5_000
|
|
82
|
+
_MAX_FIELD_CHARS = 2_000
|
|
83
|
+
_MAX_VALUE_DEPTH = 16
|
|
84
|
+
_MAX_COLLECTION_ITEMS = 256
|
|
85
|
+
_AUDIT_RING_ITEMS = 500
|
|
86
|
+
_AUDIT_RING_BYTES = 256 * 1024
|
|
87
|
+
_ALLOW_KINDS = ("allow_once", "allow_always")
|
|
88
|
+
_REJECT_KINDS = ("reject_once", "reject_always")
|
|
89
|
+
_MAX_BASE64_IMAGE_CHARS = ((MAX_IMAGE_BYTES + 2) // 3) * 4
|
|
90
|
+
# A maximum-size accepted image expands to 4 MiB in base64. Keep one
|
|
91
|
+
# additional MiB for its data-URI prefix and the surrounding bounded record.
|
|
92
|
+
_MAX_ASSEMBLED_BYTES = _MAX_BASE64_IMAGE_CHARS + 1024 * 1024
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def display_tool_kind(kind: str | None) -> str:
|
|
96
|
+
"""Map ACP presentation kinds to Chrys display/judge kinds."""
|
|
97
|
+
if kind == "execute":
|
|
98
|
+
return "shell"
|
|
99
|
+
if kind == "read":
|
|
100
|
+
return "filesystem.read"
|
|
101
|
+
if kind in {"edit", "delete", "move"}:
|
|
102
|
+
return "filesystem.write"
|
|
103
|
+
if kind == "search":
|
|
104
|
+
return "search"
|
|
105
|
+
return ""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def preview_text(value: Any, *, limit: int = _MAX_FIELD_CHARS) -> str:
|
|
109
|
+
if isinstance(value, str):
|
|
110
|
+
text = value
|
|
111
|
+
else:
|
|
112
|
+
try:
|
|
113
|
+
text = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
|
|
114
|
+
except Exception:
|
|
115
|
+
text = str(value)
|
|
116
|
+
if len(text) <= limit:
|
|
117
|
+
return text
|
|
118
|
+
digest = hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest()[:16]
|
|
119
|
+
return f"{text[: limit - 28]}… [sha256:{digest}]"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _bounded_value(value: Any, *, depth: int = 0) -> Any:
|
|
123
|
+
"""Return a JSON-safe bounded value before it enters events or persistence."""
|
|
124
|
+
if depth >= _MAX_VALUE_DEPTH:
|
|
125
|
+
return "[depth limit]"
|
|
126
|
+
if value is None or type(value) is bool:
|
|
127
|
+
return value
|
|
128
|
+
if type(value) is int:
|
|
129
|
+
rendered = str(value)
|
|
130
|
+
return value if len(rendered) <= _MAX_FIELD_CHARS else preview_text(rendered)
|
|
131
|
+
if type(value) is float:
|
|
132
|
+
return value if math.isfinite(value) else preview_text(value)
|
|
133
|
+
if isinstance(value, str):
|
|
134
|
+
return preview_text(value)
|
|
135
|
+
if hasattr(value, "model_dump"):
|
|
136
|
+
return _bounded_value(value.model_dump(mode="json", by_alias=True), depth=depth)
|
|
137
|
+
if isinstance(value, Mapping):
|
|
138
|
+
result: dict[str, Any] = {}
|
|
139
|
+
for index, (key, item) in enumerate(value.items()):
|
|
140
|
+
if index >= _MAX_COLLECTION_ITEMS:
|
|
141
|
+
result["…"] = "[item limit]"
|
|
142
|
+
break
|
|
143
|
+
result[preview_text(key, limit=200)] = _bounded_value(item, depth=depth + 1)
|
|
144
|
+
return result
|
|
145
|
+
if isinstance(value, Sequence) and not isinstance(value, bytes | bytearray):
|
|
146
|
+
return [_bounded_value(item, depth=depth + 1) for item in value[:_MAX_COLLECTION_ITEMS]]
|
|
147
|
+
return preview_text(value)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def canonical_raw_input(value: Any) -> dict[str, Any]:
|
|
151
|
+
bounded = _bounded_value(value)
|
|
152
|
+
if isinstance(bounded, dict):
|
|
153
|
+
return bounded
|
|
154
|
+
return {"input": preview_text(bounded)}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _raw_args_dict(value: Any) -> dict[str, Any]:
|
|
158
|
+
"""Dict-shaped raw arguments for approval/judge inputs — never truncated.
|
|
159
|
+
|
|
160
|
+
The judge and the approval dialog must see the arguments the remote will
|
|
161
|
+
execute (§9.2): a preview cap here would let a destructive suffix hide
|
|
162
|
+
beyond the truncation point and be approved unseen. Size is already
|
|
163
|
+
bounded upstream — permission frames ride the client's per-payload caps
|
|
164
|
+
before SDK routing — so the untruncated form is safe to publish.
|
|
165
|
+
canonical_raw_input stays for retained records and audit appends (§7.4).
|
|
166
|
+
"""
|
|
167
|
+
if isinstance(value, Mapping):
|
|
168
|
+
return dict(value)
|
|
169
|
+
return {"input": value}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _content_block_text(block: Any) -> str:
|
|
173
|
+
if isinstance(block, TextContentBlock):
|
|
174
|
+
return block.text
|
|
175
|
+
if isinstance(block, ResourceContentBlock):
|
|
176
|
+
return f"[resource: {block.uri}]"
|
|
177
|
+
if isinstance(block, EmbeddedResourceContentBlock):
|
|
178
|
+
resource = block.resource
|
|
179
|
+
uri = getattr(resource, "uri", "")
|
|
180
|
+
return f"[resource: {uri}]" if uri else "[resource]"
|
|
181
|
+
if isinstance(block, ImageContentBlock | AudioContentBlock):
|
|
182
|
+
return ""
|
|
183
|
+
return preview_text(block)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _structured_tool_content(content: Sequence[Any]) -> tuple[list[Content], list[dict[str, Any]]]:
|
|
187
|
+
"""Extract images and resource descriptors before bounded text projection."""
|
|
188
|
+
image_contents: list[Content] = []
|
|
189
|
+
artifacts: list[dict[str, Any]] = []
|
|
190
|
+
for item in content:
|
|
191
|
+
if not isinstance(item, ContentToolCallContent):
|
|
192
|
+
continue
|
|
193
|
+
block = item.content
|
|
194
|
+
if isinstance(block, ImageContentBlock):
|
|
195
|
+
media_type = block.mime_type.strip().lower() or "image/png"
|
|
196
|
+
if block.data:
|
|
197
|
+
if len(block.data) > _MAX_BASE64_IMAGE_CHARS:
|
|
198
|
+
raise AcpTransportError("The ACP agent returned an image that exceeds the supported size limit.")
|
|
199
|
+
try:
|
|
200
|
+
data = base64.b64decode(block.data, validate=True)
|
|
201
|
+
except (binascii.Error, ValueError) as exc:
|
|
202
|
+
raise AcpTransportError("The ACP agent returned invalid base64 image content.") from exc
|
|
203
|
+
if not data:
|
|
204
|
+
raise AcpTransportError("The ACP agent returned empty image content.")
|
|
205
|
+
if len(data) > MAX_IMAGE_BYTES:
|
|
206
|
+
raise AcpTransportError("The ACP agent returned an image that exceeds the supported size limit.")
|
|
207
|
+
image_contents.append(Content.from_data(data=data, media_type=media_type))
|
|
208
|
+
elif block.uri:
|
|
209
|
+
image_contents.append(Content.from_uri(uri=block.uri, media_type=media_type))
|
|
210
|
+
continue
|
|
211
|
+
if isinstance(block, ResourceContentBlock):
|
|
212
|
+
descriptor: dict[str, Any] = {"name": block.name, "path": block.uri}
|
|
213
|
+
if block.mime_type:
|
|
214
|
+
descriptor["mime"] = block.mime_type
|
|
215
|
+
if block.size is not None:
|
|
216
|
+
descriptor["size"] = block.size
|
|
217
|
+
artifacts.append(descriptor)
|
|
218
|
+
continue
|
|
219
|
+
if isinstance(block, EmbeddedResourceContentBlock):
|
|
220
|
+
resource = block.resource
|
|
221
|
+
uri = resource.uri
|
|
222
|
+
descriptor = {"name": uri.rstrip("/").rsplit("/", 1)[-1] or uri, "path": uri}
|
|
223
|
+
if resource.mime_type:
|
|
224
|
+
descriptor["mime"] = resource.mime_type
|
|
225
|
+
artifacts.append(descriptor)
|
|
226
|
+
return image_contents, artifacts
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _tool_content_text(content: Sequence[Any] | None) -> str:
|
|
230
|
+
rendered: list[str] = []
|
|
231
|
+
for item in content or ():
|
|
232
|
+
if isinstance(item, ContentToolCallContent):
|
|
233
|
+
text = _content_block_text(item.content)
|
|
234
|
+
elif isinstance(item, FileEditToolCallContent):
|
|
235
|
+
text = f"edited {item.path}"
|
|
236
|
+
elif isinstance(item, TerminalToolCallContent):
|
|
237
|
+
text = f"[terminal {item.terminal_id}]"
|
|
238
|
+
else:
|
|
239
|
+
text = preview_text(item)
|
|
240
|
+
if text:
|
|
241
|
+
rendered.append(text)
|
|
242
|
+
return preview_text("\n".join(rendered))
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@dataclass(slots=True)
|
|
246
|
+
class _CallRecord:
|
|
247
|
+
local_call_id: str
|
|
248
|
+
title: str = "tool"
|
|
249
|
+
kind: str = ""
|
|
250
|
+
raw_input: dict[str, Any] = field(default_factory=dict)
|
|
251
|
+
raw_output: Any = None
|
|
252
|
+
content: list[Any] = field(default_factory=list)
|
|
253
|
+
image_contents: list[Content] = field(default_factory=list)
|
|
254
|
+
artifacts: list[dict[str, Any]] = field(default_factory=list)
|
|
255
|
+
locations: list[Any] = field(default_factory=list)
|
|
256
|
+
status: str = ""
|
|
257
|
+
started_at: float = field(default_factory=time.monotonic)
|
|
258
|
+
start_published: bool = False
|
|
259
|
+
terminal_published: bool = False
|
|
260
|
+
last_start_signature: str = ""
|
|
261
|
+
provider_hosted: bool = False
|
|
262
|
+
hosted_family: str = ""
|
|
263
|
+
provider: str = ""
|
|
264
|
+
provider_item_type: str = ""
|
|
265
|
+
provider_call_id: str = ""
|
|
266
|
+
provider_status: str = ""
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _explicit_hosted_extension(update: ToolCallStart | ToolCallProgress) -> dict[str, Any]:
|
|
270
|
+
"""Read hosted presentation metadata only when the remote explicitly sends it."""
|
|
271
|
+
metadata = update.field_meta or {}
|
|
272
|
+
chrys_meta = metadata.get("chrys")
|
|
273
|
+
if not isinstance(chrys_meta, Mapping):
|
|
274
|
+
return {}
|
|
275
|
+
family = chrys_meta.get("hosted_family", chrys_meta.get("hostedFamily"))
|
|
276
|
+
provider_hosted = chrys_meta.get("provider_hosted", chrys_meta.get("providerHosted")) is True
|
|
277
|
+
if not provider_hosted and not isinstance(family, str):
|
|
278
|
+
return {}
|
|
279
|
+
|
|
280
|
+
extension: dict[str, Any] = {"provider_hosted": provider_hosted}
|
|
281
|
+
if isinstance(family, str):
|
|
282
|
+
extension["hosted_family"] = family
|
|
283
|
+
|
|
284
|
+
def _copy_text_value(target_key: str, snake_key: str, camel_key: str) -> None:
|
|
285
|
+
value = chrys_meta.get(snake_key, chrys_meta.get(camel_key))
|
|
286
|
+
if isinstance(value, str):
|
|
287
|
+
extension[target_key] = value
|
|
288
|
+
|
|
289
|
+
_copy_text_value("provider", "provider", "provider")
|
|
290
|
+
_copy_text_value("provider_item_type", "provider_item_type", "providerItemType")
|
|
291
|
+
_copy_text_value("provider_call_id", "provider_call_id", "providerCallId")
|
|
292
|
+
_copy_text_value("provider_status", "provider_status", "providerStatus")
|
|
293
|
+
return extension
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class AcpUpdateTranslator:
|
|
297
|
+
"""Merge ACP partial updates into bounded Chrys events and final text."""
|
|
298
|
+
|
|
299
|
+
def __init__(
|
|
300
|
+
self,
|
|
301
|
+
*,
|
|
302
|
+
event_bus: EventBus | None,
|
|
303
|
+
session_id: str | None,
|
|
304
|
+
agent_name: str,
|
|
305
|
+
invocation_id: str,
|
|
306
|
+
attempt: int,
|
|
307
|
+
origin: InvocationOrigin,
|
|
308
|
+
result_mode: Literal["last_segment", "transcript"] = "last_segment",
|
|
309
|
+
completed_before: int = 0,
|
|
310
|
+
spend_before: int = 0,
|
|
311
|
+
unreported_before: int = 0,
|
|
312
|
+
) -> None:
|
|
313
|
+
self._bus = event_bus
|
|
314
|
+
self._session_id = session_id
|
|
315
|
+
self.origin = origin
|
|
316
|
+
self.agent_name = agent_name
|
|
317
|
+
self.invocation_id = invocation_id
|
|
318
|
+
self.attempt = attempt
|
|
319
|
+
self.result_mode = result_mode
|
|
320
|
+
self.calls: dict[str, _CallRecord] = {}
|
|
321
|
+
self._current_call_ids: dict[str, str] = {}
|
|
322
|
+
self._call_occurrences: dict[str, int] = {}
|
|
323
|
+
self._next_fallback_call_id = 1
|
|
324
|
+
self._completed_before = completed_before
|
|
325
|
+
self._completed_here = 0
|
|
326
|
+
self._spend = spend_before
|
|
327
|
+
self._unreported = unreported_before
|
|
328
|
+
self.latest_context_used = 0
|
|
329
|
+
self.latest_context_size: int | None = None
|
|
330
|
+
self._segments: list[str] = []
|
|
331
|
+
self._open_segment = ""
|
|
332
|
+
self._text_finalized = False
|
|
333
|
+
self._unpublished_final_text = ""
|
|
334
|
+
self._message_id: str | None = None
|
|
335
|
+
self._retained_bytes = 0
|
|
336
|
+
self._retained_image_bytes_by_call: dict[str, int] = {}
|
|
337
|
+
self._audit: deque[dict[str, Any]] = deque()
|
|
338
|
+
# Include the JSON list delimiters so the persisted ring itself,
|
|
339
|
+
# rather than only the sum of its entries, stays within the cap.
|
|
340
|
+
self._audit_bytes = 2
|
|
341
|
+
self.on_audit_changed: Callable[[], None] | None = None
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def completed_count(self) -> int:
|
|
345
|
+
return self._completed_before + self._completed_here
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def translated_updates(self) -> list[dict[str, Any]]:
|
|
349
|
+
return list(self._audit)
|
|
350
|
+
|
|
351
|
+
async def put(self, seq: int, notification: SessionNotification) -> None:
|
|
352
|
+
await self.on_update(seq, notification)
|
|
353
|
+
|
|
354
|
+
async def on_update(self, seq: int, notification: SessionNotification) -> None:
|
|
355
|
+
update = notification.update
|
|
356
|
+
self._append_audit(seq, update)
|
|
357
|
+
if isinstance(update, ToolCallStart | ToolCallProgress):
|
|
358
|
+
# Only a ToolCallStart seals the open segment (§7.5): progress
|
|
359
|
+
# merges tool state and must not split an assistant message that
|
|
360
|
+
# keeps streaming around a running call — last_segment would
|
|
361
|
+
# otherwise return just the suffix.
|
|
362
|
+
if isinstance(update, ToolCallStart):
|
|
363
|
+
await self._seal_segment(publish=True)
|
|
364
|
+
await self._merge_tool(update)
|
|
365
|
+
elif isinstance(update, AgentMessageChunk):
|
|
366
|
+
await self._consume_message(update)
|
|
367
|
+
elif isinstance(update, UsageUpdate):
|
|
368
|
+
# PR-1 validates raw exact-int usage before SDK coercion. Keep this
|
|
369
|
+
# defensive check for direct translator tests and custom sinks.
|
|
370
|
+
if type(update.used) is int and type(update.size) is int and update.used >= 0 and update.size >= 0:
|
|
371
|
+
self.latest_context_used = update.used
|
|
372
|
+
self.latest_context_size = update.size
|
|
373
|
+
await self.publish_progress()
|
|
374
|
+
|
|
375
|
+
def _append_audit(self, seq: int, update: Any) -> None:
|
|
376
|
+
self._append_audit_item({"seq": seq, "update": _bounded_value(update.model_dump(mode="json", by_alias=True))})
|
|
377
|
+
|
|
378
|
+
def record_permission_request(self, *, title: str, kind: str, raw_input: Any, outcome: str) -> None:
|
|
379
|
+
"""Append a permission request to the audit ring.
|
|
380
|
+
|
|
381
|
+
ACP permits ``session/request_permission`` before any ``ToolCallStart``;
|
|
382
|
+
without this entry a standalone request's rawInput would reach no
|
|
383
|
+
durable artifact (judge logging is deliberately ``log_dir=None``, §9.2
|
|
384
|
+
— the owner-only envelope is the place remote rawInput is audited).
|
|
385
|
+
"""
|
|
386
|
+
self._append_audit_item(
|
|
387
|
+
{
|
|
388
|
+
"seq": None,
|
|
389
|
+
"update": {
|
|
390
|
+
"sessionUpdate": "permission_request",
|
|
391
|
+
"title": preview_text(title),
|
|
392
|
+
"kind": preview_text(kind),
|
|
393
|
+
"rawInput": _bounded_value(raw_input),
|
|
394
|
+
"outcome": outcome,
|
|
395
|
+
},
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def _append_audit_item(self, item: dict[str, Any]) -> None:
|
|
400
|
+
item["attempt"] = self.attempt
|
|
401
|
+
size = len(json.dumps(item, ensure_ascii=False, separators=(",", ":"), default=str).encode())
|
|
402
|
+
if size > _AUDIT_RING_BYTES:
|
|
403
|
+
item = {"attempt": self.attempt, "seq": item.get("seq"), "update": preview_text(item)}
|
|
404
|
+
size = len(json.dumps(item, ensure_ascii=False).encode())
|
|
405
|
+
added = size + (1 if self._audit else 0)
|
|
406
|
+
while self._audit and (len(self._audit) >= _AUDIT_RING_ITEMS or self._audit_bytes + added > _AUDIT_RING_BYTES):
|
|
407
|
+
removed = self._audit.popleft()
|
|
408
|
+
removed_size = len(json.dumps(removed, ensure_ascii=False, separators=(",", ":")).encode())
|
|
409
|
+
self._audit_bytes -= removed_size + (1 if self._audit else 0)
|
|
410
|
+
added = size + (1 if self._audit else 0)
|
|
411
|
+
if self._audit_bytes + added <= _AUDIT_RING_BYTES:
|
|
412
|
+
self._audit.append(item)
|
|
413
|
+
self._audit_bytes += added
|
|
414
|
+
if self.on_audit_changed is not None:
|
|
415
|
+
self.on_audit_changed()
|
|
416
|
+
|
|
417
|
+
def _charge(self, value: Any) -> None:
|
|
418
|
+
self._retained_bytes += len(json.dumps(value, ensure_ascii=False, default=str).encode())
|
|
419
|
+
if self._retained_bytes > _MAX_ASSEMBLED_BYTES:
|
|
420
|
+
raise AcpTransportError("The ACP agent exceeded the translated update budget.")
|
|
421
|
+
|
|
422
|
+
def _charge_tool_update(self, call_id: str, value: Any, image_uris: Sequence[str]) -> None:
|
|
423
|
+
update_bytes = len(json.dumps(value, ensure_ascii=False, default=str).encode())
|
|
424
|
+
image_bytes = len(json.dumps(image_uris, ensure_ascii=False).encode()) if image_uris else 0
|
|
425
|
+
previous_image_bytes = self._retained_image_bytes_by_call.get(call_id, 0)
|
|
426
|
+
retained_bytes = self._retained_bytes - previous_image_bytes + image_bytes + update_bytes
|
|
427
|
+
if retained_bytes > _MAX_ASSEMBLED_BYTES:
|
|
428
|
+
raise AcpTransportError("The ACP agent exceeded the translated update budget.")
|
|
429
|
+
self._retained_bytes = retained_bytes
|
|
430
|
+
if image_bytes:
|
|
431
|
+
self._retained_image_bytes_by_call[call_id] = image_bytes
|
|
432
|
+
else:
|
|
433
|
+
self._retained_image_bytes_by_call.pop(call_id, None)
|
|
434
|
+
|
|
435
|
+
async def _merge_tool(self, update: ToolCallStart | ToolCallProgress) -> None:
|
|
436
|
+
remote_id = update.tool_call_id
|
|
437
|
+
local_id = self._current_call_ids.get(remote_id, "")
|
|
438
|
+
record = self.calls.get(local_id)
|
|
439
|
+
status = getattr(update, "status", None)
|
|
440
|
+
starts_new_occurrence = record is None or (
|
|
441
|
+
record.terminal_published
|
|
442
|
+
and (
|
|
443
|
+
isinstance(update, ToolCallStart) or (isinstance(status, str) and status not in {"completed", "failed"})
|
|
444
|
+
)
|
|
445
|
+
)
|
|
446
|
+
if starts_new_occurrence:
|
|
447
|
+
if len(self.calls) >= _MAX_CALLS:
|
|
448
|
+
raise AcpTransportError("The ACP agent exceeded the tool-call update budget.")
|
|
449
|
+
occurrence = self._call_occurrences.get(remote_id, 0) + 1
|
|
450
|
+
self._call_occurrences[remote_id] = occurrence
|
|
451
|
+
preferred_id = f"a{self.attempt}:{remote_id}" + (f":{occurrence}" if occurrence > 1 else "")
|
|
452
|
+
local_id = preferred_id
|
|
453
|
+
while local_id in self.calls:
|
|
454
|
+
local_id = f"a{self.attempt}:occurrence:{self._next_fallback_call_id}"
|
|
455
|
+
self._next_fallback_call_id += 1
|
|
456
|
+
record = _CallRecord(local_call_id=local_id)
|
|
457
|
+
self.calls[local_id] = record
|
|
458
|
+
self._current_call_ids[remote_id] = local_id
|
|
459
|
+
|
|
460
|
+
title = getattr(update, "title", None)
|
|
461
|
+
kind = getattr(update, "kind", None)
|
|
462
|
+
raw_input = getattr(update, "raw_input", None)
|
|
463
|
+
raw_output = getattr(update, "raw_output", None)
|
|
464
|
+
content = getattr(update, "content", None)
|
|
465
|
+
locations = getattr(update, "locations", None)
|
|
466
|
+
if title is not None:
|
|
467
|
+
record.title = preview_text(title) or "tool"
|
|
468
|
+
if kind is not None:
|
|
469
|
+
record.kind = display_tool_kind(kind)
|
|
470
|
+
if raw_input is not None:
|
|
471
|
+
record.raw_input = canonical_raw_input(raw_input)
|
|
472
|
+
if raw_output is not None:
|
|
473
|
+
record.raw_output = _bounded_value(raw_output)
|
|
474
|
+
if content is not None:
|
|
475
|
+
record.image_contents, record.artifacts = _structured_tool_content(content)
|
|
476
|
+
record.content = list(_bounded_value(content) or [])
|
|
477
|
+
if locations is not None:
|
|
478
|
+
record.locations = list(_bounded_value(locations) or [])
|
|
479
|
+
if status is not None:
|
|
480
|
+
record.status = status
|
|
481
|
+
hosted_extension = _explicit_hosted_extension(update)
|
|
482
|
+
if hosted_extension:
|
|
483
|
+
record.provider_hosted = bool(hosted_extension["provider_hosted"])
|
|
484
|
+
if "hosted_family" in hosted_extension:
|
|
485
|
+
record.hosted_family = str(hosted_extension["hosted_family"])
|
|
486
|
+
if "provider" in hosted_extension:
|
|
487
|
+
record.provider = str(hosted_extension["provider"])
|
|
488
|
+
if "provider_item_type" in hosted_extension:
|
|
489
|
+
record.provider_item_type = str(hosted_extension["provider_item_type"])
|
|
490
|
+
if "provider_call_id" in hosted_extension:
|
|
491
|
+
record.provider_call_id = str(hosted_extension["provider_call_id"])
|
|
492
|
+
if "provider_status" in hosted_extension:
|
|
493
|
+
record.provider_status = str(hosted_extension["provider_status"])
|
|
494
|
+
self._charge_tool_update(
|
|
495
|
+
record.local_call_id,
|
|
496
|
+
{
|
|
497
|
+
"title": record.title,
|
|
498
|
+
"kind": record.kind,
|
|
499
|
+
"input": record.raw_input,
|
|
500
|
+
"output": record.raw_output,
|
|
501
|
+
"content": record.content,
|
|
502
|
+
"artifacts": record.artifacts,
|
|
503
|
+
"locations": record.locations,
|
|
504
|
+
},
|
|
505
|
+
[image.uri or "" for image in record.image_contents],
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
await self._publish_start(record)
|
|
509
|
+
if record.status in {"completed", "failed"}:
|
|
510
|
+
await self._publish_terminal(record)
|
|
511
|
+
|
|
512
|
+
async def _publish_start(self, record: _CallRecord, *, ensure: bool = False) -> None:
|
|
513
|
+
signature = json.dumps(
|
|
514
|
+
[record.title, record.kind, record.raw_input],
|
|
515
|
+
ensure_ascii=False,
|
|
516
|
+
sort_keys=True,
|
|
517
|
+
separators=(",", ":"),
|
|
518
|
+
)
|
|
519
|
+
if not ensure and (
|
|
520
|
+
record.terminal_published or (record.start_published and signature == record.last_start_signature)
|
|
521
|
+
):
|
|
522
|
+
return
|
|
523
|
+
record.start_published = True
|
|
524
|
+
record.last_start_signature = signature
|
|
525
|
+
if self._bus is not None:
|
|
526
|
+
await self._bus.publish(
|
|
527
|
+
InvocationToolCallStart(
|
|
528
|
+
origin=self.origin,
|
|
529
|
+
agent_name=self.agent_name,
|
|
530
|
+
tool_name=record.title,
|
|
531
|
+
tool_kind=record.kind,
|
|
532
|
+
args=dict(record.raw_input),
|
|
533
|
+
call_id=record.local_call_id,
|
|
534
|
+
provider_hosted=record.provider_hosted,
|
|
535
|
+
hosted_family=record.hosted_family,
|
|
536
|
+
provider=record.provider,
|
|
537
|
+
provider_item_type=record.provider_item_type,
|
|
538
|
+
provider_call_id=record.provider_call_id,
|
|
539
|
+
provider_status=record.provider_status,
|
|
540
|
+
session_id=self._session_id,
|
|
541
|
+
)
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
async def _publish_terminal(self, record: _CallRecord, *, interrupted: bool = False) -> None:
|
|
545
|
+
if record.terminal_published:
|
|
546
|
+
return
|
|
547
|
+
# Claim the terminal synchronously, before any await. The live update
|
|
548
|
+
# consumer and a cancel-path flush_interrupted() run as separate tasks;
|
|
549
|
+
# if the guard above and the claim below straddled an await (the
|
|
550
|
+
# _publish_start / bus.publish points), both could pass the guard and
|
|
551
|
+
# double-deliver a InvocationToolCallResult (and double-count). Claiming
|
|
552
|
+
# up front serializes them — the loser returns at the guard. The
|
|
553
|
+
# ensure=True start below intentionally bypasses this same flag.
|
|
554
|
+
record.terminal_published = True
|
|
555
|
+
self._completed_here += 1
|
|
556
|
+
try:
|
|
557
|
+
await self._publish_start(record, ensure=True)
|
|
558
|
+
if interrupted:
|
|
559
|
+
result = "(interrupted)"
|
|
560
|
+
metadata = {TOOL_INTERRUPTED_METADATA_KEY: True}
|
|
561
|
+
provider_status = "interrupted"
|
|
562
|
+
else:
|
|
563
|
+
result = _tool_content_text_from_bounded(record.content)
|
|
564
|
+
if not result:
|
|
565
|
+
result = preview_text(record.raw_output)
|
|
566
|
+
failed = record.status == "failed"
|
|
567
|
+
metadata = {TOOL_FAILED_METADATA_KEY: True} if failed else {}
|
|
568
|
+
provider_status = record.provider_status
|
|
569
|
+
if self._bus is not None:
|
|
570
|
+
await self._bus.publish(
|
|
571
|
+
InvocationToolCallResult(
|
|
572
|
+
origin=self.origin,
|
|
573
|
+
agent_name=self.agent_name,
|
|
574
|
+
tool_name=record.title,
|
|
575
|
+
call_id=record.local_call_id,
|
|
576
|
+
result=result,
|
|
577
|
+
image_contents=list(record.image_contents),
|
|
578
|
+
duration_ms=max(0, int((time.monotonic() - record.started_at) * 1_000)),
|
|
579
|
+
metadata=metadata,
|
|
580
|
+
artifacts=[dict(artifact) for artifact in record.artifacts],
|
|
581
|
+
provider_hosted=record.provider_hosted,
|
|
582
|
+
hosted_family=record.hosted_family,
|
|
583
|
+
provider=record.provider,
|
|
584
|
+
provider_item_type=record.provider_item_type,
|
|
585
|
+
provider_call_id=record.provider_call_id,
|
|
586
|
+
provider_status=provider_status,
|
|
587
|
+
session_id=self._session_id,
|
|
588
|
+
)
|
|
589
|
+
)
|
|
590
|
+
except asyncio.CancelledError:
|
|
591
|
+
# A cancellation mid-publish must not eat the terminal: release the
|
|
592
|
+
# claim so the cancel path's flush_interrupted() re-delivers (a
|
|
593
|
+
# duplicate for early subscribers beats a permanently dangling card).
|
|
594
|
+
record.terminal_published = False
|
|
595
|
+
self._completed_here -= 1
|
|
596
|
+
raise
|
|
597
|
+
await self.publish_progress()
|
|
598
|
+
|
|
599
|
+
async def flush_interrupted(self) -> None:
|
|
600
|
+
for record in list(self.calls.values()):
|
|
601
|
+
if not record.terminal_published:
|
|
602
|
+
await self._publish_terminal(record, interrupted=True)
|
|
603
|
+
|
|
604
|
+
async def _consume_message(self, update: AgentMessageChunk) -> None:
|
|
605
|
+
if update.message_id is not None and self._message_id is not None and update.message_id != self._message_id:
|
|
606
|
+
await self._seal_segment(publish=True)
|
|
607
|
+
if update.message_id is not None:
|
|
608
|
+
self._message_id = update.message_id
|
|
609
|
+
text = _content_block_text(update.content)
|
|
610
|
+
if text:
|
|
611
|
+
self._charge(text)
|
|
612
|
+
self._open_segment += text
|
|
613
|
+
|
|
614
|
+
def _take_open_segment(self) -> str:
|
|
615
|
+
segment = self._open_segment
|
|
616
|
+
self._open_segment = ""
|
|
617
|
+
if segment.strip():
|
|
618
|
+
self._segments.append(segment)
|
|
619
|
+
return segment
|
|
620
|
+
|
|
621
|
+
async def _seal_segment(self, *, publish: bool) -> None:
|
|
622
|
+
segment = self._take_open_segment()
|
|
623
|
+
if publish and segment.strip() and self._bus is not None:
|
|
624
|
+
await self._bus.publish(
|
|
625
|
+
InvocationMessage(
|
|
626
|
+
is_final=False,
|
|
627
|
+
is_intermediate=True,
|
|
628
|
+
origin=self.origin,
|
|
629
|
+
agent_name=self.agent_name,
|
|
630
|
+
text=segment,
|
|
631
|
+
session_id=self._session_id,
|
|
632
|
+
)
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
def result_text(self) -> str:
|
|
636
|
+
self._finalize_text()
|
|
637
|
+
segments = [segment for segment in self._segments if segment.strip()]
|
|
638
|
+
if not segments:
|
|
639
|
+
return ""
|
|
640
|
+
if self.result_mode == "transcript":
|
|
641
|
+
return "\n\n".join(segments)
|
|
642
|
+
return segments[-1]
|
|
643
|
+
|
|
644
|
+
def unpublished_final_text(self) -> str:
|
|
645
|
+
"""Return the final segment that was not emitted as an intermediate event."""
|
|
646
|
+
self._finalize_text()
|
|
647
|
+
return self._unpublished_final_text
|
|
648
|
+
|
|
649
|
+
def _finalize_text(self) -> None:
|
|
650
|
+
if self._text_finalized:
|
|
651
|
+
return
|
|
652
|
+
self._text_finalized = True
|
|
653
|
+
segment = self._take_open_segment()
|
|
654
|
+
self._unpublished_final_text = segment if segment.strip() else ""
|
|
655
|
+
|
|
656
|
+
def partial_text(self) -> str:
|
|
657
|
+
segments = [*self._segments]
|
|
658
|
+
if self._open_segment.strip():
|
|
659
|
+
segments.append(self._open_segment)
|
|
660
|
+
return "\n\n".join(segment for segment in segments if segment.strip())
|
|
661
|
+
|
|
662
|
+
async def finalize_usage(self, *, spend: int, unreported_attempts: int) -> None:
|
|
663
|
+
self._spend = spend
|
|
664
|
+
self._unreported = unreported_attempts
|
|
665
|
+
await self.publish_progress()
|
|
666
|
+
|
|
667
|
+
async def publish_progress(self) -> None:
|
|
668
|
+
if self._bus is not None:
|
|
669
|
+
await self._bus.publish(
|
|
670
|
+
InvocationProgress(
|
|
671
|
+
origin=self.origin,
|
|
672
|
+
agent_name=self.agent_name,
|
|
673
|
+
tool_call_count=self.completed_count,
|
|
674
|
+
total_tokens=self.latest_context_used,
|
|
675
|
+
total_usage_tokens=self._spend,
|
|
676
|
+
usage_unreported_attempts=self._unreported,
|
|
677
|
+
session_id=self._session_id,
|
|
678
|
+
)
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _tool_content_text_from_bounded(content: Sequence[Any]) -> str:
|
|
683
|
+
rendered: list[str] = []
|
|
684
|
+
for item in content:
|
|
685
|
+
if not isinstance(item, Mapping):
|
|
686
|
+
rendered.append(preview_text(item))
|
|
687
|
+
continue
|
|
688
|
+
item_type = item.get("type")
|
|
689
|
+
if item_type == "content":
|
|
690
|
+
block = item.get("content")
|
|
691
|
+
if isinstance(block, Mapping):
|
|
692
|
+
if block.get("type") == "text":
|
|
693
|
+
rendered.append(str(block.get("text", "")))
|
|
694
|
+
elif block.get("type") == "resource_link":
|
|
695
|
+
rendered.append(f"[resource: {block.get('uri', '')}]")
|
|
696
|
+
elif block.get("type") == "resource":
|
|
697
|
+
rendered.append("[resource]")
|
|
698
|
+
elif item_type == "diff":
|
|
699
|
+
rendered.append(f"edited {item.get('path', '')}")
|
|
700
|
+
elif item_type == "terminal":
|
|
701
|
+
rendered.append(f"[terminal {item.get('terminalId', item.get('terminal_id', ''))}]")
|
|
702
|
+
return preview_text("\n".join(part for part in rendered if part))
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
class AcpPermissionBroker:
|
|
706
|
+
"""Translate remote permissions and ask-user requests into Chrys events."""
|
|
707
|
+
|
|
708
|
+
def __init__(
|
|
709
|
+
self,
|
|
710
|
+
*,
|
|
711
|
+
event_bus: EventBus,
|
|
712
|
+
session_id: str | None,
|
|
713
|
+
caller_name: str,
|
|
714
|
+
mode_getter: Callable[[], ApprovalMode],
|
|
715
|
+
turn_context: TurnContextReader,
|
|
716
|
+
workspace_roots: Sequence[str],
|
|
717
|
+
workspace_cwd: str,
|
|
718
|
+
approval_judge: ApprovalJudge | None,
|
|
719
|
+
ask_user_timeout_seconds: float | None,
|
|
720
|
+
allow_user_interaction: bool = True,
|
|
721
|
+
trajectory_context: TrajectoryContext | None = None,
|
|
722
|
+
trajectory_boundary_operation_id: str | None = None,
|
|
723
|
+
) -> None:
|
|
724
|
+
self._bus = event_bus
|
|
725
|
+
self._session_id = session_id
|
|
726
|
+
self._caller_name = caller_name
|
|
727
|
+
self._mode_getter = mode_getter
|
|
728
|
+
self._turn_context = turn_context
|
|
729
|
+
self._workspace_roots = list(workspace_roots)
|
|
730
|
+
self._workspace_cwd = workspace_cwd
|
|
731
|
+
self._judge = approval_judge
|
|
732
|
+
self._ask_timeout = ask_user_timeout_seconds
|
|
733
|
+
self._allow_user_interaction = allow_user_interaction
|
|
734
|
+
self._trajectory_context = trajectory_context
|
|
735
|
+
self._trajectory_boundary_operation_id = trajectory_boundary_operation_id
|
|
736
|
+
self._translator: AcpUpdateTranslator | None = None
|
|
737
|
+
self._arbiter = ApprovalDecisionArbiter(event_bus, session_id=session_id)
|
|
738
|
+
self._permission_waits: dict[str, asyncio.Future[ApprovalResponse]] = {}
|
|
739
|
+
self._ask_waits: dict[str, asyncio.Future[AskUserResponse]] = {}
|
|
740
|
+
# Latched by cascade_abort() synchronously, ahead of the backgrounded
|
|
741
|
+
# cancel_pending_waits(). Any in-flight or freshly-arriving permission
|
|
742
|
+
# decision consults it so an already-settled "allow" cannot slip
|
|
743
|
+
# through after the global interrupt.
|
|
744
|
+
self._aborted = False
|
|
745
|
+
|
|
746
|
+
def mark_aborted(self) -> None:
|
|
747
|
+
"""Latch the abort flag synchronously (called from cascade_abort)."""
|
|
748
|
+
self._aborted = True
|
|
749
|
+
|
|
750
|
+
def set_translator(self, translator: AcpUpdateTranslator) -> None:
|
|
751
|
+
self._translator = translator
|
|
752
|
+
|
|
753
|
+
def bind_trajectory(self, context: TrajectoryContext | None, *, boundary_operation_id: str | None) -> None:
|
|
754
|
+
"""Bind the caller's current pass before any remote permission requests arrive."""
|
|
755
|
+
self._trajectory_context = context
|
|
756
|
+
self._trajectory_boundary_operation_id = boundary_operation_id
|
|
757
|
+
|
|
758
|
+
async def on_update(self, seq: int, notification: SessionNotification) -> None:
|
|
759
|
+
translator = self._translator
|
|
760
|
+
if translator is not None:
|
|
761
|
+
await translator.on_update(seq, notification)
|
|
762
|
+
|
|
763
|
+
async def on_permission_request(
|
|
764
|
+
self,
|
|
765
|
+
tool_call: ToolCallUpdate,
|
|
766
|
+
options: Sequence[PermissionOption],
|
|
767
|
+
) -> PermissionDecision:
|
|
768
|
+
# Every permission request lands in the owner-only audit ring with its
|
|
769
|
+
# outcome — including ones the remote sends before any ToolCallStart,
|
|
770
|
+
# which would otherwise reach no durable artifact.
|
|
771
|
+
decision = PermissionDecision.cancelled()
|
|
772
|
+
try:
|
|
773
|
+
decision = await self._decide_permission(tool_call, options)
|
|
774
|
+
return decision
|
|
775
|
+
finally:
|
|
776
|
+
self._record_permission_audit(tool_call, decision)
|
|
777
|
+
|
|
778
|
+
def _record_permission_audit(self, tool_call: ToolCallUpdate, decision: PermissionDecision) -> None:
|
|
779
|
+
translator = self._translator
|
|
780
|
+
if translator is None:
|
|
781
|
+
return
|
|
782
|
+
outcome = {"allow": "allowed", "deny": "denied"}.get(decision.action, "cancelled")
|
|
783
|
+
translator.record_permission_request(
|
|
784
|
+
title=str(tool_call.title or "tool"),
|
|
785
|
+
kind=display_tool_kind(tool_call.kind),
|
|
786
|
+
raw_input=tool_call.raw_input,
|
|
787
|
+
outcome=outcome,
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
async def _decide_permission(
|
|
791
|
+
self,
|
|
792
|
+
tool_call: ToolCallUpdate,
|
|
793
|
+
options: Sequence[PermissionOption],
|
|
794
|
+
) -> PermissionDecision:
|
|
795
|
+
# A cascade abort already fired: never open a fresh permission — even a
|
|
796
|
+
# BYPASS-mode auto-allow — after the global interrupt.
|
|
797
|
+
if self._aborted:
|
|
798
|
+
return PermissionDecision.cancelled()
|
|
799
|
+
allow = _select_option(options, _ALLOW_KINDS)
|
|
800
|
+
reject = _select_option(options, _REJECT_KINDS)
|
|
801
|
+
# Polarity is checked before every mode, including BYPASS.
|
|
802
|
+
if allow is None:
|
|
803
|
+
return PermissionDecision.deny(reject.option_id) if reject is not None else PermissionDecision.cancelled()
|
|
804
|
+
mode = self._mode_getter()
|
|
805
|
+
if mode == ApprovalMode.BYPASS:
|
|
806
|
+
return PermissionDecision.allow(allow.option_id)
|
|
807
|
+
|
|
808
|
+
approval_trace = ApprovalTrace.open_for_operation(
|
|
809
|
+
context=self._trajectory_context,
|
|
810
|
+
target_operation_id=self._trajectory_boundary_operation_id,
|
|
811
|
+
)
|
|
812
|
+
async with OneShotCorrelation(self._bus, ApprovalResponse) as correlation:
|
|
813
|
+
request_id = correlation.request_id
|
|
814
|
+
future = correlation.future
|
|
815
|
+
self._permission_waits[request_id] = future
|
|
816
|
+
judging = mode == ApprovalMode.AUTO and self._judge is not None
|
|
817
|
+
judge_title, judge_kind, raw_args = _permission_judge_fields(tool_call)
|
|
818
|
+
presentation_title = preview_text(tool_call.title or "tool", limit=_MAX_FIELD_CHARS - 4)
|
|
819
|
+
published_args = _raw_args_dict(tool_call.raw_input)
|
|
820
|
+
request_publish_started = False
|
|
821
|
+
try:
|
|
822
|
+
if judging:
|
|
823
|
+
await self._arbiter.ensure_subscription()
|
|
824
|
+
if approval_trace is not None:
|
|
825
|
+
await approval_trace.requested(
|
|
826
|
+
tool_name=f"acp:{presentation_title}",
|
|
827
|
+
approval_mode=mode.value,
|
|
828
|
+
approval_level="require",
|
|
829
|
+
)
|
|
830
|
+
request_publish_started = True
|
|
831
|
+
await self._bus.publish(
|
|
832
|
+
ApprovalRequest(
|
|
833
|
+
request_id=request_id,
|
|
834
|
+
caller_name=self._caller_name,
|
|
835
|
+
tool_name=f"acp:{presentation_title}",
|
|
836
|
+
tool_kind="",
|
|
837
|
+
# judge_kind folds in the chrys _meta enhancement (nested
|
|
838
|
+
# chrys declares kind precisely); the "remote" fallback
|
|
839
|
+
# keeps this non-empty for every bridged request so the
|
|
840
|
+
# dialog renders the friendly header even when the remote
|
|
841
|
+
# claims no recognizable kind.
|
|
842
|
+
presentation_kind=judge_kind or "remote",
|
|
843
|
+
args=published_args,
|
|
844
|
+
intent_summary=f"Allow remote tool: {presentation_title}",
|
|
845
|
+
user_message=self._turn_context.user_message,
|
|
846
|
+
workspace_roots=list(self._workspace_roots),
|
|
847
|
+
workspace_cwd=self._workspace_cwd,
|
|
848
|
+
judging=judging,
|
|
849
|
+
session_id=self._session_id,
|
|
850
|
+
)
|
|
851
|
+
)
|
|
852
|
+
except asyncio.CancelledError:
|
|
853
|
+
self._permission_waits.pop(request_id, None)
|
|
854
|
+
if approval_trace is not None:
|
|
855
|
+
approval_trace.interrupted_soon(reason_code="cancelled")
|
|
856
|
+
if request_publish_started:
|
|
857
|
+
await self._publish_initial_dialog_cleanup(
|
|
858
|
+
ApprovalCancelled(request_id=request_id, session_id=self._session_id)
|
|
859
|
+
)
|
|
860
|
+
return PermissionDecision.cancelled()
|
|
861
|
+
except BaseException:
|
|
862
|
+
self._permission_waits.pop(request_id, None)
|
|
863
|
+
if approval_trace is not None:
|
|
864
|
+
approval_trace.interrupted_soon(reason_code="failed")
|
|
865
|
+
raise
|
|
866
|
+
judge_task: asyncio.Task[None] | None = None
|
|
867
|
+
try:
|
|
868
|
+
if judging:
|
|
869
|
+
judge_task = asyncio.create_task(
|
|
870
|
+
self._arbiter.judge(
|
|
871
|
+
request_id=request_id,
|
|
872
|
+
judge=self._judge,
|
|
873
|
+
judge_input=ApprovalJudgeInput(
|
|
874
|
+
tool_name=judge_title,
|
|
875
|
+
tool_kind=judge_kind,
|
|
876
|
+
args=raw_args,
|
|
877
|
+
user_message=self._turn_context.user_message,
|
|
878
|
+
user_messages=self._turn_context.user_messages,
|
|
879
|
+
workspace_roots=list(self._workspace_roots),
|
|
880
|
+
),
|
|
881
|
+
decision_future=future,
|
|
882
|
+
approved_value=ApprovalResponse(
|
|
883
|
+
request_id=request_id,
|
|
884
|
+
approved=True,
|
|
885
|
+
session_id=self._session_id,
|
|
886
|
+
),
|
|
887
|
+
log_dir=None,
|
|
888
|
+
),
|
|
889
|
+
)
|
|
890
|
+
except BaseException:
|
|
891
|
+
self._permission_waits.pop(request_id, None)
|
|
892
|
+
if approval_trace is not None:
|
|
893
|
+
approval_trace.interrupted_soon(reason_code="failed")
|
|
894
|
+
raise
|
|
895
|
+
try:
|
|
896
|
+
response = await future
|
|
897
|
+
except asyncio.CancelledError:
|
|
898
|
+
should_notify = self._permission_waits.pop(request_id, None) is not None
|
|
899
|
+
if approval_trace is not None:
|
|
900
|
+
approval_trace.interrupted_soon(reason_code="cancelled")
|
|
901
|
+
if should_notify:
|
|
902
|
+
await self._bus.publish(ApprovalCancelled(request_id=request_id, session_id=self._session_id))
|
|
903
|
+
return PermissionDecision.cancelled()
|
|
904
|
+
finally:
|
|
905
|
+
self._permission_waits.pop(request_id, None)
|
|
906
|
+
if judge_task is not None and not judge_task.done():
|
|
907
|
+
judge_task.cancel()
|
|
908
|
+
with contextlib.suppress(asyncio.CancelledError, Exception):
|
|
909
|
+
await judge_task
|
|
910
|
+
|
|
911
|
+
# A cascade abort that landed while this permission was in flight must
|
|
912
|
+
# never resolve to allow, even when the ApprovalResponse future was
|
|
913
|
+
# already settled (or its resolving continuation raced ahead of the
|
|
914
|
+
# backgrounded cancel_pending_waits): cascade_abort calls
|
|
915
|
+
# broker.mark_aborted() synchronously before yielding, so by the time
|
|
916
|
+
# `await future` returns the flag is visible. Honoring the approval
|
|
917
|
+
# here would let the remote run a tool after the global interrupt.
|
|
918
|
+
if self._aborted:
|
|
919
|
+
if approval_trace is not None:
|
|
920
|
+
approval_trace.interrupted_soon(reason_code="cascade_abort")
|
|
921
|
+
return PermissionDecision.cancelled()
|
|
922
|
+
if response.approved:
|
|
923
|
+
if approval_trace is not None:
|
|
924
|
+
await approval_trace.resolved(
|
|
925
|
+
approved=True,
|
|
926
|
+
decider=ApprovalDecider.USER if correlation.resolved_by_event else ApprovalDecider.JUDGE,
|
|
927
|
+
reason_code="approved",
|
|
928
|
+
arguments_modified=False,
|
|
929
|
+
)
|
|
930
|
+
return PermissionDecision.allow(allow.option_id)
|
|
931
|
+
if approval_trace is not None:
|
|
932
|
+
await approval_trace.resolved(
|
|
933
|
+
approved=False,
|
|
934
|
+
decider=ApprovalDecider.USER if correlation.resolved_by_event else ApprovalDecider.JUDGE,
|
|
935
|
+
reason_code="rejected",
|
|
936
|
+
arguments_modified=False,
|
|
937
|
+
)
|
|
938
|
+
return PermissionDecision.deny(reject.option_id) if reject is not None else PermissionDecision.cancelled()
|
|
939
|
+
|
|
940
|
+
async def on_ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
|
941
|
+
if method != "chrys/request_input":
|
|
942
|
+
raise RequestError.method_not_found(method)
|
|
943
|
+
try:
|
|
944
|
+
questions = validate_request_input_params(params)
|
|
945
|
+
except ValueError as exc:
|
|
946
|
+
raise RequestError.invalid_params({"method": method}) from exc
|
|
947
|
+
if not self._allow_user_interaction:
|
|
948
|
+
# The same cancellation reply serves aborted and expired requests.
|
|
949
|
+
# Do not publish QuestionToUser: noninteractive owners have no
|
|
950
|
+
# response channel to service it.
|
|
951
|
+
return {"cancelled": True}
|
|
952
|
+
caller = params.get("callerName")
|
|
953
|
+
caller_name = preview_text(caller, limit=_MAX_FIELD_CHARS) if isinstance(caller, str) else self._caller_name
|
|
954
|
+
|
|
955
|
+
# A cascade abort already fired: do not open a fresh question dialog
|
|
956
|
+
# (symmetric with _decide_permission). Return the same cancellation the
|
|
957
|
+
# timeout/cancel paths use so the remote cannot proceed post-interrupt.
|
|
958
|
+
if self._aborted:
|
|
959
|
+
return {"cancelled": True}
|
|
960
|
+
wait = WaitTrace.open(
|
|
961
|
+
WaitCategory.USER_INPUT,
|
|
962
|
+
target_operation_id=self._trajectory_boundary_operation_id,
|
|
963
|
+
context=self._trajectory_context,
|
|
964
|
+
)
|
|
965
|
+
async with OneShotCorrelation(self._bus, AskUserResponse) as correlation:
|
|
966
|
+
self._ask_waits[correlation.request_id] = correlation.future
|
|
967
|
+
question_publish_started = False
|
|
968
|
+
try:
|
|
969
|
+
if wait is not None:
|
|
970
|
+
await wait.started()
|
|
971
|
+
question_publish_started = True
|
|
972
|
+
await self._bus.publish(
|
|
973
|
+
QuestionToUser(
|
|
974
|
+
questions=questions,
|
|
975
|
+
request_id=correlation.request_id,
|
|
976
|
+
caller_name=caller_name or self._caller_name,
|
|
977
|
+
session_id=self._session_id,
|
|
978
|
+
)
|
|
979
|
+
)
|
|
980
|
+
except asyncio.CancelledError:
|
|
981
|
+
self._ask_waits.pop(correlation.request_id, None)
|
|
982
|
+
if wait is not None:
|
|
983
|
+
wait.finished_soon(outcome=WaitOutcome.CANCELLED)
|
|
984
|
+
if question_publish_started:
|
|
985
|
+
await self._publish_initial_dialog_cleanup(
|
|
986
|
+
AskUserTimedOut(request_id=correlation.request_id, session_id=self._session_id)
|
|
987
|
+
)
|
|
988
|
+
return {"cancelled": True}
|
|
989
|
+
except BaseException:
|
|
990
|
+
self._ask_waits.pop(correlation.request_id, None)
|
|
991
|
+
if wait is not None:
|
|
992
|
+
wait.finished_soon(outcome=WaitOutcome.FAILED)
|
|
993
|
+
raise
|
|
994
|
+
try:
|
|
995
|
+
if self._ask_timeout is None:
|
|
996
|
+
response = await correlation.future
|
|
997
|
+
else:
|
|
998
|
+
async with asyncio.timeout(self._ask_timeout):
|
|
999
|
+
response = await correlation.future
|
|
1000
|
+
except TimeoutError:
|
|
1001
|
+
if wait is not None:
|
|
1002
|
+
wait.finished_soon(outcome=WaitOutcome.TIMED_OUT)
|
|
1003
|
+
await self._bus.publish(AskUserTimedOut(request_id=correlation.request_id, session_id=self._session_id))
|
|
1004
|
+
return {"cancelled": True}
|
|
1005
|
+
except asyncio.CancelledError:
|
|
1006
|
+
should_notify = self._ask_waits.pop(correlation.request_id, None) is not None
|
|
1007
|
+
if wait is not None:
|
|
1008
|
+
wait.finished_soon(outcome=WaitOutcome.CANCELLED)
|
|
1009
|
+
if should_notify:
|
|
1010
|
+
await self._bus.publish(
|
|
1011
|
+
AskUserTimedOut(
|
|
1012
|
+
request_id=correlation.request_id,
|
|
1013
|
+
session_id=self._session_id,
|
|
1014
|
+
)
|
|
1015
|
+
)
|
|
1016
|
+
return {"cancelled": True}
|
|
1017
|
+
finally:
|
|
1018
|
+
self._ask_waits.pop(correlation.request_id, None)
|
|
1019
|
+
# An abort that latched while the answer future was settling (its
|
|
1020
|
+
# continuation racing ahead of cancel_pending_waits) must not hand the
|
|
1021
|
+
# remote a real answer after the global interrupt.
|
|
1022
|
+
if self._aborted:
|
|
1023
|
+
if wait is not None:
|
|
1024
|
+
await wait.finished(outcome=WaitOutcome.CANCELLED)
|
|
1025
|
+
return {"cancelled": True}
|
|
1026
|
+
if wait is not None:
|
|
1027
|
+
await wait.finished()
|
|
1028
|
+
if response.cancelled:
|
|
1029
|
+
return {"cancelled": True}
|
|
1030
|
+
answers = parse_ask_user_answers(response.answers or (), question_count=len(questions))
|
|
1031
|
+
payload: dict[str, Any] = {
|
|
1032
|
+
"answers": [{"values": list(answer.values), "note": answer.note} for answer in answers],
|
|
1033
|
+
"cancelled": False,
|
|
1034
|
+
}
|
|
1035
|
+
if isinstance(validate_request_input_response(payload, questions=questions), AskUserCancelled):
|
|
1036
|
+
return {"cancelled": True}
|
|
1037
|
+
return payload
|
|
1038
|
+
|
|
1039
|
+
async def _publish_initial_dialog_cleanup(self, event: ApprovalCancelled | AskUserTimedOut) -> None:
|
|
1040
|
+
"""Finish cleanup even when cancellation interrupted the opening publish."""
|
|
1041
|
+
task = asyncio.create_task(self._bus.publish(event))
|
|
1042
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
1043
|
+
await drain_acp_task(task)
|
|
1044
|
+
|
|
1045
|
+
async def cancel_pending_waits(self) -> None:
|
|
1046
|
+
permission_waits = list(self._permission_waits.items())
|
|
1047
|
+
ask_waits = list(self._ask_waits.items())
|
|
1048
|
+
for request_id, future in permission_waits:
|
|
1049
|
+
if self._permission_waits.pop(request_id, None) is None:
|
|
1050
|
+
continue
|
|
1051
|
+
if not future.done():
|
|
1052
|
+
future.cancel()
|
|
1053
|
+
await self._bus.publish(ApprovalCancelled(request_id=request_id, session_id=self._session_id))
|
|
1054
|
+
for request_id, future in ask_waits:
|
|
1055
|
+
if self._ask_waits.pop(request_id, None) is None:
|
|
1056
|
+
continue
|
|
1057
|
+
if not future.done():
|
|
1058
|
+
future.cancel()
|
|
1059
|
+
await self._bus.publish(AskUserTimedOut(request_id=request_id, session_id=self._session_id))
|
|
1060
|
+
|
|
1061
|
+
async def close(self) -> None:
|
|
1062
|
+
await self.cancel_pending_waits()
|
|
1063
|
+
await self._arbiter.close()
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
def _select_option(
|
|
1067
|
+
options: Sequence[PermissionOption],
|
|
1068
|
+
preferred_kinds: Sequence[str],
|
|
1069
|
+
) -> PermissionOption | None:
|
|
1070
|
+
for kind in preferred_kinds:
|
|
1071
|
+
for option in options:
|
|
1072
|
+
if option.kind == kind:
|
|
1073
|
+
return option
|
|
1074
|
+
return None
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def _permission_judge_fields(tool_call: ToolCallUpdate) -> tuple[str, str, dict[str, Any]]:
|
|
1078
|
+
title = preview_text(tool_call.title or "tool", limit=_MAX_FIELD_CHARS)
|
|
1079
|
+
kind = display_tool_kind(tool_call.kind)
|
|
1080
|
+
metadata = tool_call.field_meta or {}
|
|
1081
|
+
chrys_meta = metadata.get("chrys")
|
|
1082
|
+
if isinstance(chrys_meta, Mapping):
|
|
1083
|
+
remote_name = chrys_meta.get("tool_name", chrys_meta.get("toolName"))
|
|
1084
|
+
remote_kind = chrys_meta.get("tool_kind", chrys_meta.get("toolKind"))
|
|
1085
|
+
if isinstance(remote_name, str) and remote_name:
|
|
1086
|
+
title = preview_text(remote_name, limit=_MAX_FIELD_CHARS)
|
|
1087
|
+
if isinstance(remote_kind, str):
|
|
1088
|
+
kind = preview_text(remote_kind, limit=_MAX_FIELD_CHARS)
|
|
1089
|
+
return title, kind, _raw_args_dict(tool_call.raw_input)
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
async def drain_acp_task(task: asyncio.Future[Any]) -> None:
|
|
1093
|
+
"""Await *task* to completion, absorbing external cancels of the awaiter.
|
|
1094
|
+
|
|
1095
|
+
``asyncio.shield`` only stops an external cancel from cancelling the SHIELDED
|
|
1096
|
+
task — the awaiter still receives ``CancelledError``, so a single
|
|
1097
|
+
``await asyncio.shield(task)`` abandons *task* the instant a SECOND interrupt
|
|
1098
|
+
(or shutdown) lands on the awaiter. Re-await in a loop until the task is
|
|
1099
|
+
genuinely done, then re-raise ``CancelledError`` if any external cancel was
|
|
1100
|
+
absorbed. A cancel raised inside the task's own body still surfaces via
|
|
1101
|
+
``task.result()``.
|
|
1102
|
+
|
|
1103
|
+
This is the single shield-loop the ACP teardown/flush/cascade-publish paths
|
|
1104
|
+
all funnel through, so no future call site can reintroduce the naive
|
|
1105
|
+
single-shield hazard.
|
|
1106
|
+
"""
|
|
1107
|
+
cancelled = False
|
|
1108
|
+
while not task.done():
|
|
1109
|
+
try:
|
|
1110
|
+
await asyncio.shield(task)
|
|
1111
|
+
except asyncio.CancelledError:
|
|
1112
|
+
cancelled = True
|
|
1113
|
+
task.result()
|
|
1114
|
+
if cancelled:
|
|
1115
|
+
raise asyncio.CancelledError
|