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,1385 @@
|
|
|
1
|
+
# Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Renderer for sub-agent tool calls with a reusable nested transcript."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import time
|
|
10
|
+
from collections.abc import Awaitable, Callable
|
|
11
|
+
from contextlib import suppress
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
|
14
|
+
|
|
15
|
+
from rich.text import Text
|
|
16
|
+
from textual.containers import Horizontal, Vertical
|
|
17
|
+
from textual.message import Message
|
|
18
|
+
from textual.timer import Timer
|
|
19
|
+
from textual.widget import Widget
|
|
20
|
+
from textual.widgets import Button, Static
|
|
21
|
+
|
|
22
|
+
from chrys.app.tui.i18n import render_str, render_text, widget_localizer
|
|
23
|
+
from chrys.app.tui.util.invocation_progress import invocation_progress_parts
|
|
24
|
+
from chrys.app.tui.util.visibility import is_widget_shown_on_active_screen
|
|
25
|
+
from chrys.app.tui.widgets.chat.agent_transcript_surface import (
|
|
26
|
+
AgentTranscriptJournal,
|
|
27
|
+
AgentTranscriptSurface,
|
|
28
|
+
TranscriptAssistantOp,
|
|
29
|
+
TranscriptCompactionFinishedOp,
|
|
30
|
+
TranscriptCompactionStartOp,
|
|
31
|
+
TranscriptPresentationAcceptedOp,
|
|
32
|
+
TranscriptPresentationRejectedOp,
|
|
33
|
+
TranscriptToolArgsOp,
|
|
34
|
+
TranscriptToolProgressOp,
|
|
35
|
+
TranscriptToolResultOp,
|
|
36
|
+
TranscriptToolStartOp,
|
|
37
|
+
TranscriptToolStatusOp,
|
|
38
|
+
)
|
|
39
|
+
from chrys.app.tui.widgets.chat.messages import process_think_tags
|
|
40
|
+
from chrys.app.tui.widgets.chat.renderers.sleep import SleepSkipClicked
|
|
41
|
+
from chrys.app.tui.widgets.chat.tool_call import (
|
|
42
|
+
TOOL_CARD_COMPLETED,
|
|
43
|
+
TOOL_CARD_ERRORED,
|
|
44
|
+
TOOL_CARD_INTERRUPTED,
|
|
45
|
+
TOOL_CARD_REJECTED,
|
|
46
|
+
BaseToolCard,
|
|
47
|
+
ToolCardHeader,
|
|
48
|
+
fmt_duration,
|
|
49
|
+
tool_activity_reference,
|
|
50
|
+
tool_result_render_status,
|
|
51
|
+
)
|
|
52
|
+
from chrys.app.tui.widgets.chat.tool_view_builders import (
|
|
53
|
+
TOOL_VIEW_EMPTY,
|
|
54
|
+
TOOL_VIEW_OUTPUT,
|
|
55
|
+
build_code_view,
|
|
56
|
+
build_params_view,
|
|
57
|
+
)
|
|
58
|
+
from chrys.app.tui.widgets.loading import ChrysLoadingIndicator
|
|
59
|
+
from chrys.app.tui.widgets.markdown import VirtualizedMarkdown
|
|
60
|
+
from chrys.foundation.i18n import MessageDef, MessageRef, msg
|
|
61
|
+
from chrys.foundation.i18n.formatting import sanitize_legacy_scalar
|
|
62
|
+
from chrys.foundation.platform.files import surrogate_safe_text
|
|
63
|
+
from chrys.foundation.tool_kinds import KIND_SLEEP, KIND_SUB_AGENT
|
|
64
|
+
from chrys.foundation.tool_result_metadata import TOOL_INTERRUPTED_METADATA_KEY
|
|
65
|
+
from chrys.foundation.util.sub_agent_context import SUB_AGENT_TRANSCRIPT_FINAL_TEXT_METADATA_KEY
|
|
66
|
+
|
|
67
|
+
if TYPE_CHECKING:
|
|
68
|
+
from textual.app import ComposeResult
|
|
69
|
+
|
|
70
|
+
from chrys.foundation.events.types import ProvisionalPresentation
|
|
71
|
+
from chrys.service.session.sub_agent_transcript import PersistedSubAgentTranscript
|
|
72
|
+
|
|
73
|
+
_SUB_AGENT_TASK = msg("tui.tool_card.sub_agent.task", fallback="Task")
|
|
74
|
+
_SUB_AGENT_TASK_PROMPT = msg("tui.tool_card.sub_agent.task_prompt", fallback="Task Prompt")
|
|
75
|
+
_SUB_AGENT_DURATION = msg("tui.tool_card.sub_agent.duration", fallback="Duration: {duration}")
|
|
76
|
+
_SUB_AGENT_RETRYING = msg(
|
|
77
|
+
"tui.tool_card.sub_agent.retrying",
|
|
78
|
+
fallback="↻ Retrying in {delay_seconds}s ({attempt}/{max_attempts}): {message}",
|
|
79
|
+
)
|
|
80
|
+
_SUB_AGENT_REASON_STREAM_STALLED = msg(
|
|
81
|
+
"tui.tool_card.sub_agent.reason.stream_stalled",
|
|
82
|
+
fallback="Stream stalled",
|
|
83
|
+
)
|
|
84
|
+
_SUB_AGENT_REASON_COMPACTION_FAILED = msg(
|
|
85
|
+
"tui.tool_card.sub_agent.reason.compaction_failed",
|
|
86
|
+
fallback="Compaction failed",
|
|
87
|
+
)
|
|
88
|
+
_SUB_AGENT_REASON_FAILED = msg("tui.tool_card.sub_agent.reason.failed", fallback="Sub-agent failed")
|
|
89
|
+
_SUB_AGENT_REASON_ACP_INTERRUPTED = msg(
|
|
90
|
+
"tui.tool_card.sub_agent.reason.acp_interrupted",
|
|
91
|
+
fallback="External ACP transport interrupted",
|
|
92
|
+
)
|
|
93
|
+
_SUB_AGENT_REASON_PAUSED = msg("tui.tool_card.sub_agent.reason.paused", fallback="Sub-agent paused")
|
|
94
|
+
_SUB_AGENT_AFTER_RETRIES = msg(
|
|
95
|
+
"tui.tool_card.sub_agent.after_retries",
|
|
96
|
+
fallback="(after {count} auto-retry attempt)",
|
|
97
|
+
plural_fallback="(after {count} auto-retry attempts)",
|
|
98
|
+
)
|
|
99
|
+
_SUB_AGENT_DIAGNOSTICS = msg("tui.tool_card.sub_agent.diagnostics", fallback="Diagnostics: {path}")
|
|
100
|
+
_SUB_AGENT_ERRORED_WITH_REASON = msg("tui.tool_card.sub_agent.errored_with_reason", fallback="Errored: {reason}")
|
|
101
|
+
_SUB_AGENT_PAUSED = msg(
|
|
102
|
+
"tui.tool_card.sub_agent.paused",
|
|
103
|
+
fallback="Paused — awaiting user",
|
|
104
|
+
)
|
|
105
|
+
_SUB_AGENT_SKIP_SLEEP = msg("tui.tool_card.sub_agent.button.skip_sleep", fallback="Skip sleep")
|
|
106
|
+
_SUB_AGENT_RETRY = msg("tui.tool_card.sub_agent.button.retry", fallback="Retry")
|
|
107
|
+
_SUB_AGENT_ABORT = msg("tui.tool_card.sub_agent.button.abort", fallback="Abort")
|
|
108
|
+
_SUB_AGENT_VIEW_DETAILS = msg("tui.tool_card.sub_agent.button.view_details", fallback="View details")
|
|
109
|
+
_SUB_AGENT_SKIP_SLEEP_TOOLTIP = msg(
|
|
110
|
+
"tui.tool_card.sub_agent.button.skip_sleep_tooltip",
|
|
111
|
+
fallback="Skip the active sleep in this sub-agent",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
_SUB_AGENT_REASON_MESSAGES: dict[str, MessageDef] = {
|
|
115
|
+
"stream_stall": _SUB_AGENT_REASON_STREAM_STALLED,
|
|
116
|
+
"last_words": _SUB_AGENT_REASON_COMPACTION_FAILED,
|
|
117
|
+
"framework_exc": _SUB_AGENT_REASON_FAILED,
|
|
118
|
+
"acp_transport": _SUB_AGENT_REASON_ACP_INTERRUPTED,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_COMPACTION_ENTRY_KIND = "compaction"
|
|
122
|
+
"""Synthetic ``tool_kind`` for the Phase-4 compaction progress line."""
|
|
123
|
+
|
|
124
|
+
# The failure results the sub-agent policies write name the card's own agent
|
|
125
|
+
# ("sub-agent 'X' failed — <cause>"); the card line keeps only what the card
|
|
126
|
+
# does not already say.
|
|
127
|
+
_OWN_AGENT_PREFIX = re.compile(r"sub-agent '[^']*' (?:failed —\s*)?")
|
|
128
|
+
# Far wider than any card: the activity line ellipsizes to its own width.
|
|
129
|
+
_ERROR_REASON_MAX_CHARS = 400
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _error_reason(result: str) -> str:
|
|
133
|
+
"""One display line naming why the sub-agent call failed, or ``""``."""
|
|
134
|
+
text = result.strip().removeprefix("Error:").lstrip()
|
|
135
|
+
text = _OWN_AGENT_PREFIX.sub("", text, count=1) if text.startswith("sub-agent '") else text
|
|
136
|
+
reason = sanitize_legacy_scalar(surrogate_safe_text(" ".join(text.split())))
|
|
137
|
+
if len(reason) > _ERROR_REASON_MAX_CHARS:
|
|
138
|
+
reason = reason[: _ERROR_REASON_MAX_CHARS - 1] + "…"
|
|
139
|
+
return reason
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class _InnerToolEntry:
|
|
144
|
+
"""State for a single inner tool call."""
|
|
145
|
+
|
|
146
|
+
call_id: str
|
|
147
|
+
tool_name: str
|
|
148
|
+
tool_kind: str
|
|
149
|
+
args: dict[str, Any]
|
|
150
|
+
status: str = "running"
|
|
151
|
+
completion_counted: bool = False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class _ActivityHistoryEntry:
|
|
156
|
+
"""One rollback-aware candidate for the compact latest-activity line."""
|
|
157
|
+
|
|
158
|
+
activity: MessageRef | str
|
|
159
|
+
presentation: ProvisionalPresentation | None = None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class _SubAgentActivityText(Static):
|
|
163
|
+
"""Single-line latest activity with a real narrow-width ellipsis."""
|
|
164
|
+
|
|
165
|
+
def __init__(self, text: Text) -> None:
|
|
166
|
+
super().__init__(text, markup=False, id="sa-activity-text")
|
|
167
|
+
self._full_text = text.copy()
|
|
168
|
+
|
|
169
|
+
def set_text(self, text: Text) -> None:
|
|
170
|
+
self._full_text = text.copy()
|
|
171
|
+
self.refresh(layout=False)
|
|
172
|
+
|
|
173
|
+
def render(self) -> Text:
|
|
174
|
+
text = self._full_text.copy()
|
|
175
|
+
available = self.content_size.width or self.size.width
|
|
176
|
+
if available > 0:
|
|
177
|
+
text.truncate(available, overflow="ellipsis")
|
|
178
|
+
return text
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# Custom Textual messages — bubble up to MainScreen so it can publish the
|
|
182
|
+
# corresponding bus event. Keeping a translation layer between widget
|
|
183
|
+
# clicks and event bus events means the widget doesn't need to hold a
|
|
184
|
+
# reference to the bus.
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class SubAgentRetryClicked(Message):
|
|
188
|
+
"""User clicked Retry on a paused :class:`SubAgentToolCall` card."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, invocation_id: str) -> None:
|
|
191
|
+
super().__init__()
|
|
192
|
+
self.invocation_id = invocation_id
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class SubAgentAbortClicked(Message):
|
|
196
|
+
"""User clicked Abort on a paused :class:`SubAgentToolCall` card."""
|
|
197
|
+
|
|
198
|
+
def __init__(self, invocation_id: str) -> None:
|
|
199
|
+
super().__init__()
|
|
200
|
+
self.invocation_id = invocation_id
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class SubAgentToolCall(BaseToolCard):
|
|
204
|
+
"""Renderer for sub-agent tool invocations.
|
|
205
|
+
|
|
206
|
+
Implements the ToolCall protocol so it can be used in ToolGroup.
|
|
207
|
+
A dedicated :class:`VirtualizedMarkdown` renders the original prompt in a
|
|
208
|
+
``Task`` panel. The second panel keeps only the newest assistant/tool
|
|
209
|
+
activity; a dedicated details action opens :class:`AgentTranscriptSurface`
|
|
210
|
+
in the existing modal, reusing the main-agent transcript renderers.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
DEFAULT_CSS = """
|
|
214
|
+
SubAgentToolCall {
|
|
215
|
+
padding: 0 0 0 2;
|
|
216
|
+
height: auto;
|
|
217
|
+
margin: 0 0 1 0;
|
|
218
|
+
}
|
|
219
|
+
SubAgentToolCall #sa-label {
|
|
220
|
+
height: auto;
|
|
221
|
+
}
|
|
222
|
+
SubAgentToolCall > #sa-task-panel {
|
|
223
|
+
height: auto;
|
|
224
|
+
margin: 0 0 0 2;
|
|
225
|
+
border: round $tui-border-neutral-128 $border-opacity;
|
|
226
|
+
border-title-color: $text-muted;
|
|
227
|
+
border-title-style: not bold;
|
|
228
|
+
padding: 0 1 0 1;
|
|
229
|
+
}
|
|
230
|
+
SubAgentToolCall > #sa-panel {
|
|
231
|
+
height: auto;
|
|
232
|
+
margin: 0 0 0 2;
|
|
233
|
+
border: round $tui-border-warning 50%;
|
|
234
|
+
border-title-color: $warning;
|
|
235
|
+
border-title-style: bold;
|
|
236
|
+
border-title-align: left;
|
|
237
|
+
border-subtitle-color: $warning;
|
|
238
|
+
padding: 0 1 0 1;
|
|
239
|
+
}
|
|
240
|
+
SubAgentToolCall.-complete > #sa-panel {
|
|
241
|
+
border: round $tui-border-success 30%;
|
|
242
|
+
border-title-color: $success;
|
|
243
|
+
border-title-style: not bold;
|
|
244
|
+
border-subtitle-color: $success;
|
|
245
|
+
}
|
|
246
|
+
SubAgentToolCall.-error > #sa-panel {
|
|
247
|
+
border: round $tui-border-error 50%;
|
|
248
|
+
border-title-color: $error;
|
|
249
|
+
border-title-style: not bold;
|
|
250
|
+
border-subtitle-color: $error;
|
|
251
|
+
}
|
|
252
|
+
SubAgentToolCall.-rejected > #sa-panel {
|
|
253
|
+
border: round $tui-border-warning 50%;
|
|
254
|
+
border-title-color: $warning;
|
|
255
|
+
border-title-style: not bold;
|
|
256
|
+
border-subtitle-color: $warning;
|
|
257
|
+
}
|
|
258
|
+
SubAgentToolCall.-paused > #sa-panel {
|
|
259
|
+
border: round $tui-border-warning $border-opacity;
|
|
260
|
+
border-title-color: $warning;
|
|
261
|
+
border-title-style: bold;
|
|
262
|
+
border-subtitle-color: $warning;
|
|
263
|
+
}
|
|
264
|
+
SubAgentToolCall #sa-latest {
|
|
265
|
+
height: 1;
|
|
266
|
+
width: 100%;
|
|
267
|
+
}
|
|
268
|
+
SubAgentToolCall #sa-latest > ChrysLoadingIndicator {
|
|
269
|
+
color: $tui-tool-group-title;
|
|
270
|
+
height: 1;
|
|
271
|
+
width: 6;
|
|
272
|
+
min-width: 6;
|
|
273
|
+
max-width: 6;
|
|
274
|
+
}
|
|
275
|
+
SubAgentToolCall #sa-activity-text {
|
|
276
|
+
color: $text-muted;
|
|
277
|
+
height: 1;
|
|
278
|
+
width: 1fr;
|
|
279
|
+
overflow: hidden;
|
|
280
|
+
}
|
|
281
|
+
SubAgentToolCall #sa-task {
|
|
282
|
+
height: auto;
|
|
283
|
+
min-height: 1;
|
|
284
|
+
padding: 0;
|
|
285
|
+
background: transparent;
|
|
286
|
+
}
|
|
287
|
+
SubAgentToolCall #sa-retry-banner {
|
|
288
|
+
height: auto;
|
|
289
|
+
display: none;
|
|
290
|
+
color: $warning;
|
|
291
|
+
text-style: italic;
|
|
292
|
+
}
|
|
293
|
+
SubAgentToolCall.-retrying #sa-retry-banner {
|
|
294
|
+
display: block;
|
|
295
|
+
}
|
|
296
|
+
SubAgentToolCall #sa-pause-info {
|
|
297
|
+
height: auto;
|
|
298
|
+
display: none;
|
|
299
|
+
color: $error;
|
|
300
|
+
}
|
|
301
|
+
SubAgentToolCall.-paused #sa-pause-info {
|
|
302
|
+
display: block;
|
|
303
|
+
}
|
|
304
|
+
SubAgentToolCall #sa-actions {
|
|
305
|
+
height: auto;
|
|
306
|
+
display: none;
|
|
307
|
+
margin: 1 0 0 0;
|
|
308
|
+
}
|
|
309
|
+
SubAgentToolCall #sa-sleep-actions {
|
|
310
|
+
height: auto;
|
|
311
|
+
display: none;
|
|
312
|
+
margin: 1 0 0 0;
|
|
313
|
+
}
|
|
314
|
+
SubAgentToolCall.-sleeping #sa-sleep-actions {
|
|
315
|
+
display: block;
|
|
316
|
+
}
|
|
317
|
+
SubAgentToolCall.-done #sa-sleep-actions {
|
|
318
|
+
display: none;
|
|
319
|
+
}
|
|
320
|
+
SubAgentToolCall.-paused #sa-actions {
|
|
321
|
+
display: block;
|
|
322
|
+
}
|
|
323
|
+
SubAgentToolCall #sa-actions Button {
|
|
324
|
+
margin: 0 1 0 0;
|
|
325
|
+
min-width: 10;
|
|
326
|
+
height: 1;
|
|
327
|
+
border: none;
|
|
328
|
+
}
|
|
329
|
+
SubAgentToolCall #sa-retry-btn {
|
|
330
|
+
background: $warning;
|
|
331
|
+
color: $text;
|
|
332
|
+
text-style: bold;
|
|
333
|
+
}
|
|
334
|
+
SubAgentToolCall #sa-abort-btn {
|
|
335
|
+
background: $error;
|
|
336
|
+
color: $text;
|
|
337
|
+
text-style: bold;
|
|
338
|
+
}
|
|
339
|
+
SubAgentToolCall #sa-skip-sleep-btn {
|
|
340
|
+
min-width: 12;
|
|
341
|
+
height: 1;
|
|
342
|
+
border: none;
|
|
343
|
+
background: $warning;
|
|
344
|
+
color: $text;
|
|
345
|
+
text-style: bold;
|
|
346
|
+
}
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
_SPINNERS: ClassVar[str] = "\u25d0\u25d3\u25d1\u25d2"
|
|
350
|
+
|
|
351
|
+
def __init__(
|
|
352
|
+
self,
|
|
353
|
+
call_id: str,
|
|
354
|
+
tool_name: str,
|
|
355
|
+
args_summary: str = "",
|
|
356
|
+
args: dict[str, Any] | None = None,
|
|
357
|
+
) -> None:
|
|
358
|
+
super().__init__(call_id, tool_name, args_summary, args=args)
|
|
359
|
+
self._spin_idx = 0
|
|
360
|
+
self._transcript_journal = AgentTranscriptJournal()
|
|
361
|
+
self._final_transcript_recorded = False
|
|
362
|
+
self._transcript_loader: Callable[[str], Awaitable[PersistedSubAgentTranscript | None]] | None = None
|
|
363
|
+
self._transcript_log_file = ""
|
|
364
|
+
self._transcript_profile_name = tool_name
|
|
365
|
+
self._linked_display_name = ""
|
|
366
|
+
initial_activity = tool_activity_reference(tool_name, KIND_SUB_AGENT, {"agent_name": tool_name})
|
|
367
|
+
self._activity_history = [_ActivityHistoryEntry(initial_activity)]
|
|
368
|
+
self._latest_activity: MessageRef | str = initial_activity
|
|
369
|
+
|
|
370
|
+
self._invocation_id: str | None = None
|
|
371
|
+
self._inner_tools: dict[str, _InnerToolEntry] = {}
|
|
372
|
+
self._seen_inner_call_ids: set[str] = set()
|
|
373
|
+
self._terminal_inner_call_ids: set[str] = set()
|
|
374
|
+
self._total_inner_calls = 0
|
|
375
|
+
self._completed_inner_calls = 0
|
|
376
|
+
self._progress_tool_calls = 0
|
|
377
|
+
self._progress_ctx_tokens = 0
|
|
378
|
+
self._progress_total_usage_tokens = 0
|
|
379
|
+
self._usage_unreported_attempts = 0
|
|
380
|
+
# Committed Phase-4 compactions — persistent, unlike the compaction
|
|
381
|
+
# entries in ``_inner_tools`` which the visibility cap can evict.
|
|
382
|
+
# Driven by the committed signal, not finished(ok): a generated note
|
|
383
|
+
# whose spill write fails is abandoned and must not count.
|
|
384
|
+
self._compaction_count = 0
|
|
385
|
+
|
|
386
|
+
self._start_time = time.monotonic()
|
|
387
|
+
self._timer: Timer | None = None
|
|
388
|
+
# NOTE: name must avoid ``_task`` — that attribute is owned by
|
|
389
|
+
# :class:`textual.message_pump.MessagePump`, which assigns the
|
|
390
|
+
# widget's running ``asyncio.Task`` to ``self._task`` before
|
|
391
|
+
# :meth:`compose` runs. Storing the prompt under that name makes
|
|
392
|
+
# it look like a string in ``__init__`` and an ``asyncio.Task`` by
|
|
393
|
+
# the time ``compose()`` reads it, which crashes ``Text(...)``.
|
|
394
|
+
self._task_prompt = self._extract_prompt(self.args_summary, self.args)
|
|
395
|
+
|
|
396
|
+
def _render_message(self, reference: MessageRef) -> str:
|
|
397
|
+
return render_str(widget_localizer(self), reference)
|
|
398
|
+
|
|
399
|
+
def _activity_text(self) -> Text:
|
|
400
|
+
activity = self._latest_activity
|
|
401
|
+
plain = activity if isinstance(activity, str) else self._render_message(activity)
|
|
402
|
+
return Text(plain, no_wrap=True, overflow="ellipsis")
|
|
403
|
+
|
|
404
|
+
def _remember_activity(
|
|
405
|
+
self,
|
|
406
|
+
activity: MessageRef | str,
|
|
407
|
+
*,
|
|
408
|
+
presentation: ProvisionalPresentation | None = None,
|
|
409
|
+
) -> MessageRef | str:
|
|
410
|
+
if presentation is None:
|
|
411
|
+
self._activity_history = [_ActivityHistoryEntry(activity)]
|
|
412
|
+
else:
|
|
413
|
+
self._activity_history.append(_ActivityHistoryEntry(activity, presentation))
|
|
414
|
+
self._latest_activity = activity
|
|
415
|
+
self._sync_activity_summary()
|
|
416
|
+
return activity
|
|
417
|
+
|
|
418
|
+
def _resolve_activity_presentation(
|
|
419
|
+
self,
|
|
420
|
+
attempt_id: str,
|
|
421
|
+
accepted_segment_ids: set[str],
|
|
422
|
+
) -> MessageRef | str:
|
|
423
|
+
resolved: list[_ActivityHistoryEntry] = []
|
|
424
|
+
for entry in self._activity_history:
|
|
425
|
+
presentation = entry.presentation
|
|
426
|
+
if presentation is None or presentation.attempt_id != attempt_id:
|
|
427
|
+
resolved.append(entry)
|
|
428
|
+
elif presentation.segment_id in accepted_segment_ids:
|
|
429
|
+
resolved.append(_ActivityHistoryEntry(entry.activity))
|
|
430
|
+
if not resolved:
|
|
431
|
+
fallback = tool_activity_reference(
|
|
432
|
+
self.tool_name,
|
|
433
|
+
KIND_SUB_AGENT,
|
|
434
|
+
{"agent_name": self.tool_name},
|
|
435
|
+
)
|
|
436
|
+
resolved = [_ActivityHistoryEntry(fallback)]
|
|
437
|
+
if not any(entry.presentation is not None for entry in resolved):
|
|
438
|
+
resolved = [resolved[-1]]
|
|
439
|
+
self._activity_history = resolved
|
|
440
|
+
self._latest_activity = resolved[-1].activity
|
|
441
|
+
self._sync_activity_summary()
|
|
442
|
+
return self._latest_activity
|
|
443
|
+
|
|
444
|
+
def _sync_activity_summary(self) -> None:
|
|
445
|
+
with suppress(Exception):
|
|
446
|
+
self.query_one("#sa-activity-text", _SubAgentActivityText).set_text(self._activity_text())
|
|
447
|
+
with suppress(Exception):
|
|
448
|
+
indicator = self.query_one("#sa-activity-indicator", ChrysLoadingIndicator)
|
|
449
|
+
active = self.status == "running"
|
|
450
|
+
indicator.display = active
|
|
451
|
+
if active:
|
|
452
|
+
indicator.resume_animation()
|
|
453
|
+
else:
|
|
454
|
+
indicator.pause_animation()
|
|
455
|
+
|
|
456
|
+
def _label_text(self, duration_ms: int = 0) -> Text:
|
|
457
|
+
t = Text()
|
|
458
|
+
t.append("• ", style="bold")
|
|
459
|
+
t.append("SubAgent", style="bold")
|
|
460
|
+
if duration_ms:
|
|
461
|
+
t.append(f" ({fmt_duration(duration_ms)})", style="dim")
|
|
462
|
+
return t
|
|
463
|
+
|
|
464
|
+
def _running_label_text(self) -> Text:
|
|
465
|
+
"""Label with live elapsed time while running."""
|
|
466
|
+
t = Text()
|
|
467
|
+
t.append("• ", style="bold")
|
|
468
|
+
t.append("SubAgent", style="bold")
|
|
469
|
+
elapsed_ms = int((time.monotonic() - self._start_time) * 1000)
|
|
470
|
+
if elapsed_ms >= 1000:
|
|
471
|
+
t.append(f" ({fmt_duration(elapsed_ms)})", style="dim")
|
|
472
|
+
return t
|
|
473
|
+
|
|
474
|
+
def compose(self) -> ComposeResult:
|
|
475
|
+
header = ToolCardHeader(
|
|
476
|
+
self._running_label_text(),
|
|
477
|
+
id="sa-label",
|
|
478
|
+
view_only_label=_SUB_AGENT_VIEW_DETAILS.bind() if self.status in {"running", "paused"} else None,
|
|
479
|
+
)
|
|
480
|
+
if self.status not in {"running", "paused"}:
|
|
481
|
+
header.show_actions()
|
|
482
|
+
yield header
|
|
483
|
+
if self._task_prompt:
|
|
484
|
+
yield self._build_task_panel()
|
|
485
|
+
with Vertical(id="sa-panel") as panel:
|
|
486
|
+
panel.border_title = Text(self._render_title())
|
|
487
|
+
with Horizontal(id="sa-latest"):
|
|
488
|
+
yield ChrysLoadingIndicator(id="sa-activity-indicator")
|
|
489
|
+
yield _SubAgentActivityText(self._activity_text())
|
|
490
|
+
with Horizontal(id="sa-sleep-actions"):
|
|
491
|
+
skip = Button(
|
|
492
|
+
render_text(widget_localizer(self), _SUB_AGENT_SKIP_SLEEP.bind()),
|
|
493
|
+
id="sa-skip-sleep-btn",
|
|
494
|
+
compact=True,
|
|
495
|
+
)
|
|
496
|
+
skip.tooltip = render_text(widget_localizer(self), _SUB_AGENT_SKIP_SLEEP_TOOLTIP.bind())
|
|
497
|
+
yield skip
|
|
498
|
+
yield Static("", id="sa-retry-banner")
|
|
499
|
+
yield Static("", id="sa-pause-info")
|
|
500
|
+
with Horizontal(id="sa-actions"):
|
|
501
|
+
yield Button(
|
|
502
|
+
render_text(widget_localizer(self), _SUB_AGENT_RETRY.bind()),
|
|
503
|
+
id="sa-retry-btn",
|
|
504
|
+
compact=True,
|
|
505
|
+
)
|
|
506
|
+
yield Button(
|
|
507
|
+
render_text(widget_localizer(self), _SUB_AGENT_ABORT.bind()),
|
|
508
|
+
id="sa-abort-btn",
|
|
509
|
+
compact=True,
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
def on_mount(self) -> None:
|
|
513
|
+
self._timer = self.set_interval(0.12, self._spin)
|
|
514
|
+
self._sync_activity_summary()
|
|
515
|
+
|
|
516
|
+
def on_unmount(self) -> None:
|
|
517
|
+
if self._timer is not None:
|
|
518
|
+
self._timer.stop()
|
|
519
|
+
self._timer = None
|
|
520
|
+
|
|
521
|
+
@staticmethod
|
|
522
|
+
def _extract_prompt(args_summary: str, args: dict[str, Any]) -> str:
|
|
523
|
+
"""Extract prompt string safely from args_summary or args dict."""
|
|
524
|
+
if args_summary:
|
|
525
|
+
try:
|
|
526
|
+
parsed = json.loads(args_summary)
|
|
527
|
+
except Exception:
|
|
528
|
+
parsed = None
|
|
529
|
+
if isinstance(parsed, dict):
|
|
530
|
+
val = parsed.get("prompt", "")
|
|
531
|
+
if isinstance(val, str):
|
|
532
|
+
return val
|
|
533
|
+
val = args.get("prompt")
|
|
534
|
+
if isinstance(val, str):
|
|
535
|
+
return val
|
|
536
|
+
return ""
|
|
537
|
+
|
|
538
|
+
def _build_task_panel(self) -> Widget:
|
|
539
|
+
task_panel = Widget(VirtualizedMarkdown(self._task_prompt, id="sa-task"), id="sa-task-panel")
|
|
540
|
+
task_panel.border_title = render_text(widget_localizer(self), _SUB_AGENT_TASK.bind())
|
|
541
|
+
return task_panel
|
|
542
|
+
|
|
543
|
+
def update_args(self, args: dict[str, Any]) -> None:
|
|
544
|
+
"""Refresh the task prompt after approval edits the sub-agent handoff."""
|
|
545
|
+
self.args = args
|
|
546
|
+
self.args_summary = json.dumps(args, ensure_ascii=False) if args else ""
|
|
547
|
+
self._task_prompt = self._extract_prompt(self.args_summary, self.args)
|
|
548
|
+
if self._task_prompt:
|
|
549
|
+
if self.query("#sa-task-panel"):
|
|
550
|
+
with suppress(Exception):
|
|
551
|
+
self.query_one("#sa-task", VirtualizedMarkdown).update(self._task_prompt)
|
|
552
|
+
else:
|
|
553
|
+
with suppress(Exception):
|
|
554
|
+
self.mount(self._build_task_panel(), before=self.query_one("#sa-panel", Vertical))
|
|
555
|
+
return
|
|
556
|
+
with suppress(Exception):
|
|
557
|
+
self.query_one("#sa-task-panel", Widget).remove()
|
|
558
|
+
|
|
559
|
+
def _spin(self) -> None:
|
|
560
|
+
if self.status == "running":
|
|
561
|
+
self._spin_idx = (self._spin_idx + 1) % len(self._SPINNERS)
|
|
562
|
+
# Only a shown card repaints (see ``ToolCall._spin``); the first tick after it shows paints it.
|
|
563
|
+
if not is_widget_shown_on_active_screen(self):
|
|
564
|
+
return
|
|
565
|
+
with suppress(Exception):
|
|
566
|
+
# The label shows whole seconds: most ticks leave it as it is, and
|
|
567
|
+
# rewriting an equal label would only repaint the header.
|
|
568
|
+
label = self._running_label_text()
|
|
569
|
+
header = self.query_one("#sa-label", ToolCardHeader)
|
|
570
|
+
if header.content != label:
|
|
571
|
+
header.update(label)
|
|
572
|
+
self._update_title()
|
|
573
|
+
|
|
574
|
+
def _update_title(self) -> None:
|
|
575
|
+
"""Update border title (top-left) and subtitle (bottom-right)."""
|
|
576
|
+
panel = self.query_one("#sa-panel", Vertical)
|
|
577
|
+
panel.border_title = Text(self._render_title())
|
|
578
|
+
panel.border_subtitle = Text(self._render_subtitle())
|
|
579
|
+
|
|
580
|
+
@staticmethod
|
|
581
|
+
def _fmt_duration(ms: int) -> str:
|
|
582
|
+
return fmt_duration(ms)
|
|
583
|
+
|
|
584
|
+
def _render_title(self) -> str:
|
|
585
|
+
if self.status == "running":
|
|
586
|
+
return f"{self._SPINNERS[self._spin_idx]} {self.tool_name}"
|
|
587
|
+
if self.status == "complete":
|
|
588
|
+
return f"\u2713 {self.tool_name}"
|
|
589
|
+
return f"\u2717 {self.tool_name}"
|
|
590
|
+
|
|
591
|
+
def _render_subtitle(self) -> str:
|
|
592
|
+
tool_calls = self._total_inner_calls if self.status != "running" else self._progress_tool_calls
|
|
593
|
+
parts = invocation_progress_parts(
|
|
594
|
+
tool_calls=tool_calls,
|
|
595
|
+
context_tokens=self._progress_ctx_tokens,
|
|
596
|
+
usage_tokens=self._progress_total_usage_tokens,
|
|
597
|
+
unreported_attempts=self._usage_unreported_attempts,
|
|
598
|
+
compactions=self._compaction_count,
|
|
599
|
+
render=self._render_message,
|
|
600
|
+
)
|
|
601
|
+
duration_ms = self.duration_ms
|
|
602
|
+
if self.status == "running":
|
|
603
|
+
duration_ms = int((time.monotonic() - self._start_time) * 1000)
|
|
604
|
+
if duration_ms < 1000:
|
|
605
|
+
duration_ms = 0
|
|
606
|
+
if duration_ms:
|
|
607
|
+
parts.append(self._render_message(_SUB_AGENT_DURATION.bind(duration=self._fmt_duration(duration_ms))))
|
|
608
|
+
return " \u00b7 ".join(parts)
|
|
609
|
+
|
|
610
|
+
def _running_sleep_call_id(self) -> str:
|
|
611
|
+
"""Return the newest running inner sleep call id, if one is visible."""
|
|
612
|
+
for entry in reversed(self._inner_tools.values()):
|
|
613
|
+
is_sleep = entry.tool_kind == KIND_SLEEP or (not entry.tool_kind and entry.tool_name == "sleep")
|
|
614
|
+
if entry.status == "running" and is_sleep:
|
|
615
|
+
return entry.call_id
|
|
616
|
+
return ""
|
|
617
|
+
|
|
618
|
+
def _refresh_sleep_action_state(self) -> None:
|
|
619
|
+
if self.status == "running" and self._running_sleep_call_id():
|
|
620
|
+
self.add_class("-sleeping")
|
|
621
|
+
else:
|
|
622
|
+
self.remove_class("-sleeping")
|
|
623
|
+
|
|
624
|
+
def _show_completion_controls(self) -> None:
|
|
625
|
+
"""Expose copy/details affordances without mounting the final answer inline."""
|
|
626
|
+
self.add_class("-done")
|
|
627
|
+
self._show_tool_copy_button()
|
|
628
|
+
|
|
629
|
+
async def mount_pending_content(self) -> bool:
|
|
630
|
+
"""The compact activity line has no deferred inline content."""
|
|
631
|
+
return False
|
|
632
|
+
|
|
633
|
+
def release_collapsed_content(self) -> None:
|
|
634
|
+
"""The one-line activity summary remains mounted when its parent collapses."""
|
|
635
|
+
return
|
|
636
|
+
|
|
637
|
+
def _tool_copy_input(self) -> tuple[str, str]:
|
|
638
|
+
"""Copy the sub-agent prompt without duplicating the full args dict."""
|
|
639
|
+
return "markdown", self._task_prompt or self._render_message(TOOL_VIEW_EMPTY.bind())
|
|
640
|
+
|
|
641
|
+
def _tool_view_input_widgets(self) -> list[Widget]:
|
|
642
|
+
"""Render the sub-agent prompt as markdown in the detail modal."""
|
|
643
|
+
dark = self._view_dark()
|
|
644
|
+
label = Static(
|
|
645
|
+
Text(render_str(widget_localizer(self), _SUB_AGENT_TASK_PROMPT.bind()), style="bold"),
|
|
646
|
+
classes="tool-view-section-title tool-view-section-title-first",
|
|
647
|
+
)
|
|
648
|
+
widgets: list[Widget] = [
|
|
649
|
+
label,
|
|
650
|
+
build_code_view(
|
|
651
|
+
"markdown",
|
|
652
|
+
self._task_prompt or self._render_message(TOOL_VIEW_EMPTY.bind()),
|
|
653
|
+
dark=dark,
|
|
654
|
+
render_message=self._render_message,
|
|
655
|
+
),
|
|
656
|
+
]
|
|
657
|
+
|
|
658
|
+
args = self._tool_input_args()
|
|
659
|
+
extra = {key: value for key, value in args.items() if key != "prompt"}
|
|
660
|
+
if extra:
|
|
661
|
+
widgets.extend(build_params_view(extra, dark=dark, render_message=self._render_message))
|
|
662
|
+
return widgets
|
|
663
|
+
|
|
664
|
+
def _tool_copy_sections(self) -> list[tuple[str, str, str]]:
|
|
665
|
+
"""Preserve the sub-agent's markdown final answer in the copy payload."""
|
|
666
|
+
return [
|
|
667
|
+
(
|
|
668
|
+
self._render_message(TOOL_VIEW_OUTPUT.bind()),
|
|
669
|
+
"markdown",
|
|
670
|
+
self.result_text or self._render_message(TOOL_VIEW_EMPTY.bind()),
|
|
671
|
+
)
|
|
672
|
+
]
|
|
673
|
+
|
|
674
|
+
def _tool_view_copy_enabled(self) -> bool:
|
|
675
|
+
"""Disable stale whole-output copying while the sub-agent is live."""
|
|
676
|
+
return self.status not in {"running", "paused"}
|
|
677
|
+
|
|
678
|
+
def _tool_view_output_widgets(self) -> list[Widget]:
|
|
679
|
+
"""Mount a full live transcript projection in the existing detail modal."""
|
|
680
|
+
persisted_replay_loader: Callable[[], Awaitable[PersistedSubAgentTranscript | None]] | None = None
|
|
681
|
+
metadata = self.metadata or {}
|
|
682
|
+
transcript_final = metadata.get(SUB_AGENT_TRANSCRIPT_FINAL_TEXT_METADATA_KEY)
|
|
683
|
+
fallback_final_text = self.result_text
|
|
684
|
+
if transcript_final == "" and self._transcript_journal.has_activity:
|
|
685
|
+
# The empty override means the parent result was already emitted
|
|
686
|
+
# as an intermediate segment. Suppress fallback only while that
|
|
687
|
+
# live evidence is retained; a failed durable replay must still
|
|
688
|
+
# fall back to the authoritative parent result.
|
|
689
|
+
fallback_final_text = ""
|
|
690
|
+
metadata_log_file = metadata.get("sub_agent_log_file")
|
|
691
|
+
log_file = (
|
|
692
|
+
metadata_log_file if isinstance(metadata_log_file, str) and metadata_log_file else self._transcript_log_file
|
|
693
|
+
)
|
|
694
|
+
# A running/paused card's journal is the live authority. A terminal
|
|
695
|
+
# audit may be written just before the parent result is published; if
|
|
696
|
+
# a live modal consumed that audit, it could seal the journal before
|
|
697
|
+
# the authoritative result arrived. Persisted replay is therefore a
|
|
698
|
+
# terminal-card restoration path only.
|
|
699
|
+
if (
|
|
700
|
+
self.status in {"complete", "error", "rejected", "interrupted"}
|
|
701
|
+
and log_file
|
|
702
|
+
and self._transcript_loader is not None
|
|
703
|
+
):
|
|
704
|
+
transcript_loader = self._transcript_loader
|
|
705
|
+
|
|
706
|
+
async def load_persisted_replay() -> PersistedSubAgentTranscript | None:
|
|
707
|
+
return await transcript_loader(log_file)
|
|
708
|
+
|
|
709
|
+
persisted_replay_loader = load_persisted_replay
|
|
710
|
+
return [
|
|
711
|
+
AgentTranscriptSurface(
|
|
712
|
+
self._transcript_journal,
|
|
713
|
+
profile_name=self._transcript_profile_name,
|
|
714
|
+
opening_prompt=self._task_prompt,
|
|
715
|
+
fallback_final_text=fallback_final_text,
|
|
716
|
+
persisted_replay_loader=persisted_replay_loader,
|
|
717
|
+
)
|
|
718
|
+
]
|
|
719
|
+
|
|
720
|
+
def configure_transcript_loader(
|
|
721
|
+
self,
|
|
722
|
+
loader: Callable[[str], Awaitable[PersistedSubAgentTranscript | None]],
|
|
723
|
+
) -> None:
|
|
724
|
+
"""Install the screen-owned safe persisted-transcript loader."""
|
|
725
|
+
self._transcript_loader = loader
|
|
726
|
+
|
|
727
|
+
def add_assistant_message(
|
|
728
|
+
self,
|
|
729
|
+
text: str,
|
|
730
|
+
*,
|
|
731
|
+
presentation: ProvisionalPresentation | None = None,
|
|
732
|
+
profile_name: str = "",
|
|
733
|
+
) -> MessageRef | str:
|
|
734
|
+
"""Append an invocation-scoped intermediate assistant message."""
|
|
735
|
+
if profile_name and not self._linked_display_name:
|
|
736
|
+
self._transcript_profile_name = profile_name
|
|
737
|
+
self._transcript_journal.record(TranscriptAssistantOp(text=text, final=False, presentation=presentation))
|
|
738
|
+
visible = process_think_tags(text, intermediate=True)
|
|
739
|
+
if not visible:
|
|
740
|
+
return self._latest_activity
|
|
741
|
+
single_line = sanitize_legacy_scalar(" ".join(visible.splitlines()).strip())
|
|
742
|
+
return self._remember_activity(single_line, presentation=presentation)
|
|
743
|
+
|
|
744
|
+
def accept_presentation_attempt(self, attempt_id: str, segment_ids: tuple[str, ...]) -> MessageRef | str:
|
|
745
|
+
"""Commit provisional transcript messages for one provider attempt."""
|
|
746
|
+
self._transcript_journal.record(TranscriptPresentationAcceptedOp(attempt_id, segment_ids))
|
|
747
|
+
return self._resolve_activity_presentation(attempt_id, set(segment_ids))
|
|
748
|
+
|
|
749
|
+
def reject_presentation_attempt(self, attempt_id: str) -> MessageRef | str:
|
|
750
|
+
"""Retract provisional transcript messages for one provider attempt."""
|
|
751
|
+
self._transcript_journal.record(TranscriptPresentationRejectedOp(attempt_id))
|
|
752
|
+
return self._resolve_activity_presentation(attempt_id, set())
|
|
753
|
+
|
|
754
|
+
def _record_final_transcript(self, text: str) -> None:
|
|
755
|
+
if self._final_transcript_recorded:
|
|
756
|
+
return
|
|
757
|
+
self._final_transcript_recorded = True
|
|
758
|
+
self._transcript_journal.record(TranscriptAssistantOp(text=text, final=True))
|
|
759
|
+
|
|
760
|
+
# --- Invocation linking ---
|
|
761
|
+
|
|
762
|
+
def claim_invocation(self, invocation_id: str, agent_name: str = "", sub_agent_log_file: str = "") -> None:
|
|
763
|
+
"""Link this widget and apply the invocation's display name."""
|
|
764
|
+
self._invocation_id = invocation_id
|
|
765
|
+
if sub_agent_log_file:
|
|
766
|
+
self._transcript_log_file = sub_agent_log_file
|
|
767
|
+
if not agent_name:
|
|
768
|
+
return
|
|
769
|
+
self._linked_display_name = agent_name
|
|
770
|
+
self._transcript_profile_name = agent_name
|
|
771
|
+
self._remember_activity(
|
|
772
|
+
tool_activity_reference(
|
|
773
|
+
self.tool_name,
|
|
774
|
+
KIND_SUB_AGENT,
|
|
775
|
+
{"agent_name": agent_name},
|
|
776
|
+
)
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
def update_progress(
|
|
780
|
+
self,
|
|
781
|
+
tool_call_count: int,
|
|
782
|
+
total_tokens: int,
|
|
783
|
+
total_usage_tokens: int = 0,
|
|
784
|
+
usage_unreported_attempts: int = 0,
|
|
785
|
+
) -> None:
|
|
786
|
+
"""Update cumulative progress stats and refresh the border title."""
|
|
787
|
+
self._progress_tool_calls = tool_call_count
|
|
788
|
+
self._progress_ctx_tokens = total_tokens
|
|
789
|
+
self._progress_total_usage_tokens = total_usage_tokens
|
|
790
|
+
self._usage_unreported_attempts = usage_unreported_attempts
|
|
791
|
+
if self.status == "running":
|
|
792
|
+
self._update_title()
|
|
793
|
+
|
|
794
|
+
# --- Inner tool management ---
|
|
795
|
+
|
|
796
|
+
async def add_inner_tool_start(
|
|
797
|
+
self,
|
|
798
|
+
call_id: str,
|
|
799
|
+
tool_name: str,
|
|
800
|
+
args: dict[str, Any],
|
|
801
|
+
*,
|
|
802
|
+
tool_kind: str = "",
|
|
803
|
+
provider_hosted: bool = False,
|
|
804
|
+
hosted_family: str = "",
|
|
805
|
+
provider: str = "",
|
|
806
|
+
provider_item_type: str = "",
|
|
807
|
+
provider_status: str = "",
|
|
808
|
+
provider_call_id: str = "",
|
|
809
|
+
) -> None:
|
|
810
|
+
"""Insert or refresh a running inner tool call by its lifetime id."""
|
|
811
|
+
if call_id in self._terminal_inner_call_ids:
|
|
812
|
+
return
|
|
813
|
+
self._transcript_journal.record(
|
|
814
|
+
TranscriptToolStartOp(
|
|
815
|
+
call_id=call_id,
|
|
816
|
+
tool_name=tool_name,
|
|
817
|
+
tool_kind=tool_kind,
|
|
818
|
+
args=dict(args),
|
|
819
|
+
provider_hosted=provider_hosted,
|
|
820
|
+
hosted_family=hosted_family,
|
|
821
|
+
provider=provider,
|
|
822
|
+
provider_item_type=provider_item_type,
|
|
823
|
+
provider_status=provider_status,
|
|
824
|
+
provider_call_id=provider_call_id,
|
|
825
|
+
)
|
|
826
|
+
)
|
|
827
|
+
self._remember_activity(tool_activity_reference(tool_name, tool_kind, args))
|
|
828
|
+
# A new inner tool call means the sub-agent is making forward
|
|
829
|
+
# progress — if a retry banner is still visible from a prior
|
|
830
|
+
# transient failure, the retry has clearly succeeded. Drop it
|
|
831
|
+
# now instead of letting it linger until the parent tool call
|
|
832
|
+
# finally resolves.
|
|
833
|
+
if self.has_class("-retrying"):
|
|
834
|
+
self.remove_class("-retrying")
|
|
835
|
+
with suppress(Exception):
|
|
836
|
+
self.query_one("#sa-retry-banner", Static).update("")
|
|
837
|
+
entry = self._inner_tools.get(call_id)
|
|
838
|
+
if entry is None:
|
|
839
|
+
entry = _InnerToolEntry(
|
|
840
|
+
call_id=call_id,
|
|
841
|
+
tool_name=tool_name,
|
|
842
|
+
tool_kind=tool_kind,
|
|
843
|
+
args=dict(args),
|
|
844
|
+
)
|
|
845
|
+
self._inner_tools[call_id] = entry
|
|
846
|
+
else:
|
|
847
|
+
entry.tool_name = tool_name
|
|
848
|
+
entry.tool_kind = tool_kind
|
|
849
|
+
entry.args = dict(args)
|
|
850
|
+
if call_id not in self._seen_inner_call_ids:
|
|
851
|
+
self._seen_inner_call_ids.add(call_id)
|
|
852
|
+
self._total_inner_calls += 1
|
|
853
|
+
self._refresh_sleep_action_state()
|
|
854
|
+
|
|
855
|
+
def update_inner_tool_args(self, call_id: str, args: dict[str, Any]) -> None:
|
|
856
|
+
"""Refresh arguments on a running nested tool entry."""
|
|
857
|
+
entry = self._inner_tools.get(call_id)
|
|
858
|
+
if entry is None or call_id in self._terminal_inner_call_ids:
|
|
859
|
+
return
|
|
860
|
+
self._transcript_journal.record(TranscriptToolArgsOp(call_id, dict(args)))
|
|
861
|
+
entry.args = dict(args)
|
|
862
|
+
self._remember_activity(tool_activity_reference(entry.tool_name, entry.tool_kind, entry.args))
|
|
863
|
+
|
|
864
|
+
def update_inner_tool_progress(
|
|
865
|
+
self,
|
|
866
|
+
call_id: str,
|
|
867
|
+
lines: list[str],
|
|
868
|
+
*,
|
|
869
|
+
image_contents: list[Any] | None = None,
|
|
870
|
+
snapshot_metadata: dict[str, Any] | None = None,
|
|
871
|
+
provider_status: str = "",
|
|
872
|
+
) -> None:
|
|
873
|
+
"""Apply the latest bounded progress snapshot to a nested entry."""
|
|
874
|
+
entry = self._inner_tools.get(call_id)
|
|
875
|
+
if entry is None or call_id in self._terminal_inner_call_ids:
|
|
876
|
+
return
|
|
877
|
+
self._transcript_journal.record(
|
|
878
|
+
TranscriptToolProgressOp(
|
|
879
|
+
call_id,
|
|
880
|
+
list(lines),
|
|
881
|
+
list(image_contents or []),
|
|
882
|
+
dict(snapshot_metadata or {}),
|
|
883
|
+
provider_status,
|
|
884
|
+
)
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
def update_inner_tool_status(
|
|
888
|
+
self,
|
|
889
|
+
call_id: str,
|
|
890
|
+
status: str,
|
|
891
|
+
*,
|
|
892
|
+
metadata: dict[str, Any] | None = None,
|
|
893
|
+
provider_status: str = "",
|
|
894
|
+
) -> None:
|
|
895
|
+
"""Apply one canonical lifecycle status to a nested entry."""
|
|
896
|
+
entry = self._inner_tools.get(call_id)
|
|
897
|
+
if entry is None or call_id in self._terminal_inner_call_ids:
|
|
898
|
+
return
|
|
899
|
+
if status not in {"completed", "failed", "interrupted"}:
|
|
900
|
+
return
|
|
901
|
+
self._transcript_journal.record(TranscriptToolStatusOp(call_id, status, provider_status, dict(metadata or {})))
|
|
902
|
+
if status == "completed":
|
|
903
|
+
# A completed provider snapshot can precede output_item.done. Show
|
|
904
|
+
# it as complete now, but leave the id open for one authoritative
|
|
905
|
+
# result carrying final images, duration, and metadata.
|
|
906
|
+
self._apply_inner_tool_completion(
|
|
907
|
+
call_id,
|
|
908
|
+
entry,
|
|
909
|
+
str((metadata or {}).get("result_text", "")),
|
|
910
|
+
0,
|
|
911
|
+
metadata=metadata,
|
|
912
|
+
authoritative_result=False,
|
|
913
|
+
)
|
|
914
|
+
return
|
|
915
|
+
entry.status = "interrupted" if status == "interrupted" else "error"
|
|
916
|
+
if not entry.completion_counted:
|
|
917
|
+
entry.completion_counted = True
|
|
918
|
+
self._completed_inner_calls += 1
|
|
919
|
+
self._refresh_sleep_action_state()
|
|
920
|
+
|
|
921
|
+
def add_compaction_start(self, compaction_id: str) -> None:
|
|
922
|
+
"""Show a live "Compacting conversation..." line in the inner feed.
|
|
923
|
+
|
|
924
|
+
Phase-4 compaction is not a tool call, so this lifecycle entry is not
|
|
925
|
+
counted in ``_total_inner_calls``. The visible card lives in the
|
|
926
|
+
shared transcript journal.
|
|
927
|
+
"""
|
|
928
|
+
self._transcript_journal.record(TranscriptCompactionStartOp(compaction_id))
|
|
929
|
+
entry = _InnerToolEntry(
|
|
930
|
+
call_id=f"compaction:{compaction_id}",
|
|
931
|
+
tool_name="compaction",
|
|
932
|
+
tool_kind=_COMPACTION_ENTRY_KIND,
|
|
933
|
+
args={},
|
|
934
|
+
)
|
|
935
|
+
self._inner_tools[entry.call_id] = entry
|
|
936
|
+
|
|
937
|
+
def complete_compaction(
|
|
938
|
+
self,
|
|
939
|
+
compaction_id: str,
|
|
940
|
+
*,
|
|
941
|
+
outcome: str,
|
|
942
|
+
duration_ms: int = 0,
|
|
943
|
+
format_violation: str = "",
|
|
944
|
+
failure_reason: str = "",
|
|
945
|
+
) -> None:
|
|
946
|
+
"""Flip the compaction line to its terminal state."""
|
|
947
|
+
entry = self._inner_tools.get(f"compaction:{compaction_id}")
|
|
948
|
+
if entry is None:
|
|
949
|
+
return
|
|
950
|
+
self._transcript_journal.record(
|
|
951
|
+
TranscriptCompactionFinishedOp(
|
|
952
|
+
compaction_id,
|
|
953
|
+
outcome,
|
|
954
|
+
duration_ms,
|
|
955
|
+
format_violation,
|
|
956
|
+
failure_reason,
|
|
957
|
+
)
|
|
958
|
+
)
|
|
959
|
+
if outcome == "ok":
|
|
960
|
+
entry.status = "complete"
|
|
961
|
+
# A successful finish proves the LAST_WORDS retry loop
|
|
962
|
+
# recovered — drop a lingering "Retrying in …" banner, same
|
|
963
|
+
# forward-progress rule as add_inner_tool_start. (Terminal
|
|
964
|
+
# failure pauses the sub-agent, whose flow clears it instead.)
|
|
965
|
+
if self.has_class("-retrying"):
|
|
966
|
+
self.remove_class("-retrying")
|
|
967
|
+
with suppress(Exception):
|
|
968
|
+
self.query_one("#sa-retry-banner", Static).update("")
|
|
969
|
+
elif outcome == "canceled":
|
|
970
|
+
entry.status = "interrupted"
|
|
971
|
+
else:
|
|
972
|
+
entry.status = "error"
|
|
973
|
+
self._inner_tools.pop(entry.call_id, None)
|
|
974
|
+
|
|
975
|
+
def record_compaction_committed(self, compaction_id: str) -> None:
|
|
976
|
+
"""Count a durably committed compaction round in the subtitle.
|
|
977
|
+
|
|
978
|
+
Fires on the committed signal, which trails ``complete_compaction``'s
|
|
979
|
+
finished(ok) — a successful note generation whose spill write later
|
|
980
|
+
failed never commits, so counting here (not on finished-ok) keeps
|
|
981
|
+
"Compactions: N" honest. Independent of the feed entry: the line
|
|
982
|
+
may already be evicted by the visibility cap.
|
|
983
|
+
"""
|
|
984
|
+
self._compaction_count += 1
|
|
985
|
+
with suppress(Exception):
|
|
986
|
+
self._update_title()
|
|
987
|
+
|
|
988
|
+
def complete_inner_tool(
|
|
989
|
+
self,
|
|
990
|
+
call_id: str,
|
|
991
|
+
result: str,
|
|
992
|
+
duration_ms: int,
|
|
993
|
+
image_contents: list[Any] | None = None,
|
|
994
|
+
artifacts: list[dict[str, Any]] | None = None,
|
|
995
|
+
approval: str | None = None,
|
|
996
|
+
metadata: dict[str, Any] | None = None,
|
|
997
|
+
provider_status: str = "",
|
|
998
|
+
canonical_status: str = "completed",
|
|
999
|
+
) -> None:
|
|
1000
|
+
"""Mark an inner tool call as complete."""
|
|
1001
|
+
entry = self._inner_tools.get(call_id)
|
|
1002
|
+
if not entry or call_id in self._terminal_inner_call_ids:
|
|
1003
|
+
return
|
|
1004
|
+
self._transcript_journal.record(
|
|
1005
|
+
TranscriptToolResultOp(
|
|
1006
|
+
call_id=call_id,
|
|
1007
|
+
tool_name=entry.tool_name,
|
|
1008
|
+
result=result,
|
|
1009
|
+
duration_ms=duration_ms,
|
|
1010
|
+
image_contents=list(image_contents or []),
|
|
1011
|
+
artifacts=[dict(artifact) for artifact in artifacts or []],
|
|
1012
|
+
approval=approval,
|
|
1013
|
+
metadata=dict(metadata or {}),
|
|
1014
|
+
provider_status=provider_status,
|
|
1015
|
+
canonical_status=canonical_status,
|
|
1016
|
+
)
|
|
1017
|
+
)
|
|
1018
|
+
self._apply_inner_tool_completion(
|
|
1019
|
+
call_id,
|
|
1020
|
+
entry,
|
|
1021
|
+
result,
|
|
1022
|
+
duration_ms,
|
|
1023
|
+
image_contents=image_contents,
|
|
1024
|
+
artifacts=artifacts,
|
|
1025
|
+
approval=approval,
|
|
1026
|
+
metadata=metadata,
|
|
1027
|
+
authoritative_result=True,
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
def _apply_inner_tool_completion(
|
|
1031
|
+
self,
|
|
1032
|
+
call_id: str,
|
|
1033
|
+
entry: _InnerToolEntry,
|
|
1034
|
+
result: str,
|
|
1035
|
+
duration_ms: int,
|
|
1036
|
+
*,
|
|
1037
|
+
image_contents: list[Any] | None = None,
|
|
1038
|
+
artifacts: list[dict[str, Any]] | None = None,
|
|
1039
|
+
approval: str | None = None,
|
|
1040
|
+
metadata: dict[str, Any] | None = None,
|
|
1041
|
+
authoritative_result: bool,
|
|
1042
|
+
) -> None:
|
|
1043
|
+
"""Render completion while keeping status-only snapshots replaceable."""
|
|
1044
|
+
if authoritative_result:
|
|
1045
|
+
self._terminal_inner_call_ids.add(call_id)
|
|
1046
|
+
# A terminal id short-circuits add_inner_tool_start before the
|
|
1047
|
+
# seen-check, so its _seen membership is dead weight — drop it to
|
|
1048
|
+
# keep the dedupe set bounded by in-flight calls.
|
|
1049
|
+
self._seen_inner_call_ids.discard(call_id)
|
|
1050
|
+
render_status = tool_result_render_status(result, approval, entry.tool_kind, metadata, entry.tool_name)
|
|
1051
|
+
if render_status == "rejected":
|
|
1052
|
+
entry.status = "rejected"
|
|
1053
|
+
elif isinstance(metadata, dict) and metadata.get("sleep_interrupted") is True:
|
|
1054
|
+
entry.status = "interrupted"
|
|
1055
|
+
elif isinstance(metadata, dict) and metadata.get("sleep_skipped") is True:
|
|
1056
|
+
entry.status = "skipped"
|
|
1057
|
+
elif render_status == "error":
|
|
1058
|
+
entry.status = "error"
|
|
1059
|
+
else:
|
|
1060
|
+
entry.status = "complete"
|
|
1061
|
+
if not entry.completion_counted:
|
|
1062
|
+
entry.completion_counted = True
|
|
1063
|
+
self._completed_inner_calls += 1
|
|
1064
|
+
if authoritative_result:
|
|
1065
|
+
self._inner_tools.pop(call_id, None)
|
|
1066
|
+
self._refresh_sleep_action_state()
|
|
1067
|
+
|
|
1068
|
+
# --- ToolCall protocol ---
|
|
1069
|
+
|
|
1070
|
+
def _settle_running_inner_tools(self, *, completed: bool) -> None:
|
|
1071
|
+
"""Close transcript cards whose terminal event lost the race with the parent result."""
|
|
1072
|
+
canonical_status = "completed" if completed else "interrupted"
|
|
1073
|
+
for entry in tuple(self._inner_tools.values()):
|
|
1074
|
+
if entry.status != "running":
|
|
1075
|
+
continue
|
|
1076
|
+
if entry.tool_kind == _COMPACTION_ENTRY_KIND:
|
|
1077
|
+
self._transcript_journal.record(
|
|
1078
|
+
TranscriptCompactionFinishedOp(
|
|
1079
|
+
entry.call_id.removeprefix("compaction:"),
|
|
1080
|
+
"canceled",
|
|
1081
|
+
)
|
|
1082
|
+
)
|
|
1083
|
+
else:
|
|
1084
|
+
self._transcript_journal.record(TranscriptToolStatusOp(entry.call_id, canonical_status))
|
|
1085
|
+
entry.status = "complete" if completed else "interrupted"
|
|
1086
|
+
if not entry.completion_counted:
|
|
1087
|
+
entry.completion_counted = True
|
|
1088
|
+
self._completed_inner_calls += 1
|
|
1089
|
+
|
|
1090
|
+
def _finish_transcript(
|
|
1091
|
+
self,
|
|
1092
|
+
result: str,
|
|
1093
|
+
status: Literal["complete", "error", "rejected", "interrupted"],
|
|
1094
|
+
) -> None:
|
|
1095
|
+
"""Finalize active nested cards, append the answer, and stop compact activity UI."""
|
|
1096
|
+
self._settle_running_inner_tools(completed=status == "complete")
|
|
1097
|
+
metadata = self.metadata or {}
|
|
1098
|
+
transcript_final = metadata.get(SUB_AGENT_TRANSCRIPT_FINAL_TEXT_METADATA_KEY)
|
|
1099
|
+
has_transcript_override = status == "complete" and isinstance(transcript_final, str)
|
|
1100
|
+
if not has_transcript_override or transcript_final:
|
|
1101
|
+
self._record_final_transcript(transcript_final if has_transcript_override else result)
|
|
1102
|
+
self._transcript_journal.finalize_retention(
|
|
1103
|
+
durable_replay_available=(
|
|
1104
|
+
bool(metadata.get("sub_agent_log_file")) and metadata.get("sub_agent_audit_complete") is True
|
|
1105
|
+
)
|
|
1106
|
+
)
|
|
1107
|
+
status_message = {
|
|
1108
|
+
"complete": TOOL_CARD_COMPLETED,
|
|
1109
|
+
"error": TOOL_CARD_ERRORED,
|
|
1110
|
+
"rejected": TOOL_CARD_REJECTED,
|
|
1111
|
+
"interrupted": TOOL_CARD_INTERRUPTED,
|
|
1112
|
+
}[status]
|
|
1113
|
+
reason = _error_reason(result) if status == "error" else ""
|
|
1114
|
+
self._remember_activity(_SUB_AGENT_ERRORED_WITH_REASON.bind(reason=reason) if reason else status_message.bind())
|
|
1115
|
+
|
|
1116
|
+
def _drop_inner_call_tracking(self) -> None:
|
|
1117
|
+
"""Free per-call id bookkeeping once the parent call is terminal.
|
|
1118
|
+
|
|
1119
|
+
Completed cards stay mounted for the session; keeping every inner
|
|
1120
|
+
call id (up to thousands per ACP attempt, fresh ids per retry)
|
|
1121
|
+
would grow the seen/terminal sets without bound across a long
|
|
1122
|
+
session. Late inner events are already inert after a parent
|
|
1123
|
+
terminal: completions need a live ``_inner_tools`` entry, and
|
|
1124
|
+
session replay rebuilds a fresh card from scratch.
|
|
1125
|
+
"""
|
|
1126
|
+
self._inner_tools.clear()
|
|
1127
|
+
self._seen_inner_call_ids.clear()
|
|
1128
|
+
self._terminal_inner_call_ids.clear()
|
|
1129
|
+
|
|
1130
|
+
def _clear_transient_state(self) -> None:
|
|
1131
|
+
"""Drop stale retry/pause CSS classes + banner text before a terminal transition.
|
|
1132
|
+
|
|
1133
|
+
Both ``-retrying`` and ``-paused`` belong to *in-flight* states.
|
|
1134
|
+
When the sub-agent finally completes or errors out, the banners
|
|
1135
|
+
those classes reveal should not remain visible — otherwise the
|
|
1136
|
+
card shows a "Retrying in 7s…" line *after* a successful
|
|
1137
|
+
"Recovered" result, or a "Stream stalled" pause-info line *after*
|
|
1138
|
+
the user has already aborted.
|
|
1139
|
+
"""
|
|
1140
|
+
self.remove_class("-retrying")
|
|
1141
|
+
self.remove_class("-paused")
|
|
1142
|
+
self.remove_class("-sleeping")
|
|
1143
|
+
with suppress(Exception):
|
|
1144
|
+
self.query_one("#sa-retry-banner", Static).update("")
|
|
1145
|
+
self.query_one("#sa-pause-info", Static).update("")
|
|
1146
|
+
|
|
1147
|
+
def set_complete(self, result: str, duration_ms: int = 0, **kwargs: Any) -> None:
|
|
1148
|
+
"""Mark this sub-agent call as complete (parent tool call finished).
|
|
1149
|
+
|
|
1150
|
+
If structured metadata or legacy result text indicates a sub-agent
|
|
1151
|
+
error, redirect to ``set_error()`` so the widget renders with the
|
|
1152
|
+
error style.
|
|
1153
|
+
Rejected tools (approval declined) get a separate warning style.
|
|
1154
|
+
"""
|
|
1155
|
+
self.approval = kwargs.get("approval")
|
|
1156
|
+
metadata = kwargs.get("metadata")
|
|
1157
|
+
self.metadata = metadata if isinstance(metadata, dict) else {}
|
|
1158
|
+
if self.metadata.get(TOOL_INTERRUPTED_METADATA_KEY) is True:
|
|
1159
|
+
self.result_text = result
|
|
1160
|
+
self.duration_ms = duration_ms
|
|
1161
|
+
self.status = "interrupted"
|
|
1162
|
+
self._finish_transcript(result, "interrupted")
|
|
1163
|
+
self._drop_inner_call_tracking()
|
|
1164
|
+
self._clear_transient_state()
|
|
1165
|
+
self.add_class("-rejected")
|
|
1166
|
+
if self._timer is not None:
|
|
1167
|
+
self._timer.stop()
|
|
1168
|
+
with suppress(Exception):
|
|
1169
|
+
panel = self.query_one("#sa-panel")
|
|
1170
|
+
panel.border_title = Text(self.tool_name)
|
|
1171
|
+
panel.border_subtitle = render_text(widget_localizer(self), TOOL_CARD_INTERRUPTED.bind())
|
|
1172
|
+
self.query_one("#sa-label", Static).update(self._label_text(duration_ms))
|
|
1173
|
+
self._show_completion_controls()
|
|
1174
|
+
return
|
|
1175
|
+
render_status = tool_result_render_status(
|
|
1176
|
+
result,
|
|
1177
|
+
self.approval,
|
|
1178
|
+
self.tool_kind or KIND_SUB_AGENT,
|
|
1179
|
+
self.metadata,
|
|
1180
|
+
self.tool_name,
|
|
1181
|
+
)
|
|
1182
|
+
if render_status == "rejected":
|
|
1183
|
+
self.result_text = result
|
|
1184
|
+
self.duration_ms = duration_ms
|
|
1185
|
+
self.status = "rejected"
|
|
1186
|
+
self._finish_transcript(result, "rejected")
|
|
1187
|
+
self._drop_inner_call_tracking()
|
|
1188
|
+
self._clear_transient_state()
|
|
1189
|
+
self.add_class("-rejected")
|
|
1190
|
+
if self._timer is not None:
|
|
1191
|
+
self._timer.stop()
|
|
1192
|
+
with suppress(Exception):
|
|
1193
|
+
panel = self.query_one("#sa-panel")
|
|
1194
|
+
panel.border_title = Text(self.tool_name)
|
|
1195
|
+
panel.border_subtitle = render_text(widget_localizer(self), TOOL_CARD_REJECTED.bind())
|
|
1196
|
+
self.query_one("#sa-label", Static).update(self._label_text(duration_ms))
|
|
1197
|
+
self._show_completion_controls()
|
|
1198
|
+
return
|
|
1199
|
+
if render_status == "error":
|
|
1200
|
+
self._set_error(result, duration_ms)
|
|
1201
|
+
return
|
|
1202
|
+
self.result_text = result
|
|
1203
|
+
self.duration_ms = duration_ms
|
|
1204
|
+
self.status = "complete"
|
|
1205
|
+
self._finish_transcript(result, "complete")
|
|
1206
|
+
self._drop_inner_call_tracking()
|
|
1207
|
+
self._clear_transient_state()
|
|
1208
|
+
self.add_class("-complete")
|
|
1209
|
+
if self._timer is not None:
|
|
1210
|
+
self._timer.stop()
|
|
1211
|
+
with suppress(Exception):
|
|
1212
|
+
self.query_one("#sa-label", Static).update(self._label_text(duration_ms))
|
|
1213
|
+
self._update_title()
|
|
1214
|
+
self._show_completion_controls()
|
|
1215
|
+
|
|
1216
|
+
def set_error(self, error: str) -> None:
|
|
1217
|
+
"""Mark this sub-agent call as failed."""
|
|
1218
|
+
self._set_error(error, int((time.monotonic() - self._start_time) * 1000))
|
|
1219
|
+
|
|
1220
|
+
def _set_error(self, error: str, duration_ms: int) -> None:
|
|
1221
|
+
"""Apply an error with either live elapsed or persisted duration."""
|
|
1222
|
+
self.result_text = error
|
|
1223
|
+
self.duration_ms = duration_ms
|
|
1224
|
+
self.status = "error"
|
|
1225
|
+
self._finish_transcript(error, "error")
|
|
1226
|
+
self._drop_inner_call_tracking()
|
|
1227
|
+
self._clear_transient_state()
|
|
1228
|
+
self.add_class("-error")
|
|
1229
|
+
if self._timer is not None:
|
|
1230
|
+
self._timer.stop()
|
|
1231
|
+
with suppress(Exception):
|
|
1232
|
+
self.query_one("#sa-label", Static).update(self._label_text(duration_ms))
|
|
1233
|
+
self._update_title()
|
|
1234
|
+
self._show_completion_controls()
|
|
1235
|
+
|
|
1236
|
+
# --- Paused / retry / resumed state ---
|
|
1237
|
+
|
|
1238
|
+
def set_retry_attempt(self, message: str, attempt: int, max_attempts: int, delay_seconds: int) -> None:
|
|
1239
|
+
"""Show an inline retry banner for an auto-retry attempt.
|
|
1240
|
+
|
|
1241
|
+
Called for :class:`InvocationRetryAttempt` events — the controller
|
|
1242
|
+
is retrying transient failures without user intervention. The
|
|
1243
|
+
banner gives the user visibility into the retry cadence. The
|
|
1244
|
+
``↻`` prefix visually marks the line as a retry state sub-entry,
|
|
1245
|
+
distinct from the result line that follows it on recovery.
|
|
1246
|
+
"""
|
|
1247
|
+
self.add_class("-retrying")
|
|
1248
|
+
retry_message = self._render_message(
|
|
1249
|
+
_SUB_AGENT_RETRYING.bind(
|
|
1250
|
+
delay_seconds=delay_seconds,
|
|
1251
|
+
attempt=attempt,
|
|
1252
|
+
max_attempts=max_attempts,
|
|
1253
|
+
message=message,
|
|
1254
|
+
)
|
|
1255
|
+
)
|
|
1256
|
+
with suppress(Exception):
|
|
1257
|
+
self.query_one("#sa-retry-banner", Static).update(Text(retry_message, style="italic"))
|
|
1258
|
+
|
|
1259
|
+
def set_paused(
|
|
1260
|
+
self,
|
|
1261
|
+
reason: str,
|
|
1262
|
+
last_error: str,
|
|
1263
|
+
retry_attempts: int,
|
|
1264
|
+
diagnostic_path: str | None = None,
|
|
1265
|
+
last_error_display: str | None = None,
|
|
1266
|
+
) -> None:
|
|
1267
|
+
"""Transition the card to a paused state with Retry/Abort buttons.
|
|
1268
|
+
|
|
1269
|
+
Called when :class:`InvocationPaused` arrives. Auto-retry (if any
|
|
1270
|
+
happened first) is now finished; the banner is replaced with the
|
|
1271
|
+
pause-info block and the action row is revealed.
|
|
1272
|
+
``last_error_display`` is the rendered meaning of ``last_error``,
|
|
1273
|
+
shown above the raw text.
|
|
1274
|
+
"""
|
|
1275
|
+
self.status = "paused"
|
|
1276
|
+
# Paused supersedes the retrying banner — the retry run completed
|
|
1277
|
+
# (unsuccessfully). Keep visual history of the last error only.
|
|
1278
|
+
self.remove_class("-retrying")
|
|
1279
|
+
self.remove_class("-sleeping")
|
|
1280
|
+
self.add_class("-paused")
|
|
1281
|
+
self.remove_class("-complete")
|
|
1282
|
+
self.remove_class("-error")
|
|
1283
|
+
self._sync_activity_summary()
|
|
1284
|
+
if self._timer is not None:
|
|
1285
|
+
self._timer.stop()
|
|
1286
|
+
elapsed_ms = int((time.monotonic() - self._start_time) * 1000)
|
|
1287
|
+
reason_definition = _SUB_AGENT_REASON_MESSAGES.get(reason, _SUB_AGENT_REASON_PAUSED)
|
|
1288
|
+
reason_label = self._render_message(reason_definition.bind())
|
|
1289
|
+
info_lines = [reason_label]
|
|
1290
|
+
if retry_attempts:
|
|
1291
|
+
info_lines.append(self._render_message(_SUB_AGENT_AFTER_RETRIES.bind(count=retry_attempts)))
|
|
1292
|
+
if last_error_display:
|
|
1293
|
+
info_lines.append(last_error_display)
|
|
1294
|
+
if last_error:
|
|
1295
|
+
info_lines.append(last_error)
|
|
1296
|
+
if diagnostic_path:
|
|
1297
|
+
# Display copy only — the operational path stays raw on the event.
|
|
1298
|
+
info_lines.append(
|
|
1299
|
+
self._render_message(_SUB_AGENT_DIAGNOSTICS.bind(path=surrogate_safe_text(diagnostic_path)))
|
|
1300
|
+
)
|
|
1301
|
+
info = "\n".join(info_lines)
|
|
1302
|
+
with suppress(Exception):
|
|
1303
|
+
self.query_one("#sa-label", Static).update(self._label_text(elapsed_ms))
|
|
1304
|
+
panel = self.query_one("#sa-panel", Vertical)
|
|
1305
|
+
panel.border_title = Text(f"\u25aa {self.tool_name}") # small square = paused
|
|
1306
|
+
panel.border_subtitle = render_text(widget_localizer(self), _SUB_AGENT_PAUSED.bind())
|
|
1307
|
+
self.query_one("#sa-pause-info", Static).update(Text(info, style="red"))
|
|
1308
|
+
self.query_one("#sa-retry-banner", Static).update("")
|
|
1309
|
+
|
|
1310
|
+
def set_resumed_after_pause(self) -> None:
|
|
1311
|
+
"""Clear the paused state and reset the card to a live running view.
|
|
1312
|
+
|
|
1313
|
+
Called when :class:`InvocationResumed` arrives — the user
|
|
1314
|
+
clicked Retry and the controller has re-entered running state.
|
|
1315
|
+
"""
|
|
1316
|
+
if self.status != "paused":
|
|
1317
|
+
return
|
|
1318
|
+
self.status = "running"
|
|
1319
|
+
self.remove_class("-paused")
|
|
1320
|
+
self._sync_activity_summary()
|
|
1321
|
+
self._refresh_sleep_action_state()
|
|
1322
|
+
self._start_time = time.monotonic() # reset elapsed clock for the new attempt
|
|
1323
|
+
self._spin_idx = 0
|
|
1324
|
+
# Inner-call tracking is attempt-local: the dead attempt's stream is
|
|
1325
|
+
# fully drained (the bus delivers synchronously), and a fresh attempt
|
|
1326
|
+
# may legitimately reuse raw call ids (the ACP translator scopes ids
|
|
1327
|
+
# with an attempt prefix for exactly that reason). Keeping the old
|
|
1328
|
+
# feed would pin its ids forever across unlimited manual retries and
|
|
1329
|
+
# make the stale terminal-guard swallow a reused id's new call.
|
|
1330
|
+
self._drop_inner_call_tracking()
|
|
1331
|
+
with suppress(Exception):
|
|
1332
|
+
self.query_one("#sa-pause-info", Static).update("")
|
|
1333
|
+
self.query_one("#sa-retry-banner", Static).update("")
|
|
1334
|
+
self.query_one("#sa-label", Static).update(self._running_label_text())
|
|
1335
|
+
self._update_title()
|
|
1336
|
+
self.remove_class("-retrying")
|
|
1337
|
+
# Restart the spinner timer.
|
|
1338
|
+
if self._timer is not None:
|
|
1339
|
+
self._timer.stop()
|
|
1340
|
+
self._timer = self.set_interval(0.12, self._spin)
|
|
1341
|
+
|
|
1342
|
+
def set_cascade_aborted(self) -> None:
|
|
1343
|
+
"""Mark the card as cascade-aborted by a global interrupt."""
|
|
1344
|
+
self.remove_class("-paused")
|
|
1345
|
+
self.remove_class("-retrying")
|
|
1346
|
+
self.set_error("Error: cancelled (global interrupt)")
|
|
1347
|
+
|
|
1348
|
+
def set_aborted(self, last_error: str) -> None:
|
|
1349
|
+
"""Mark the card as aborted by the user after a pause.
|
|
1350
|
+
|
|
1351
|
+
Fired on :class:`InvocationAborted`. Clears the paused banner (so
|
|
1352
|
+
the Retry/Abort buttons and pause-info disappear immediately) and
|
|
1353
|
+
routes through :meth:`set_error` so the card gets the standard
|
|
1354
|
+
error styling + a message indicating the abort was user-driven.
|
|
1355
|
+
"""
|
|
1356
|
+
self.remove_class("-paused")
|
|
1357
|
+
self.remove_class("-retrying")
|
|
1358
|
+
error_text = (
|
|
1359
|
+
f"Error: sub-agent aborted by user after failure — {last_error}"
|
|
1360
|
+
if last_error
|
|
1361
|
+
else "Error: sub-agent aborted by user"
|
|
1362
|
+
)
|
|
1363
|
+
self.set_error(error_text)
|
|
1364
|
+
|
|
1365
|
+
# --- Button actions ---
|
|
1366
|
+
|
|
1367
|
+
def on_button_pressed(self, event: Button.Pressed) -> None: # type: ignore[name-defined]
|
|
1368
|
+
"""Translate a Retry/Abort click into a bubbled message.
|
|
1369
|
+
|
|
1370
|
+
Guarded by ``_invocation_id`` being set — we should never
|
|
1371
|
+
dispatch a retry/abort for a card that hasn't been linked yet
|
|
1372
|
+
(that would mean no controller exists to receive it).
|
|
1373
|
+
"""
|
|
1374
|
+
if event.button.id == "sa-skip-sleep-btn":
|
|
1375
|
+
if call_id := self._running_sleep_call_id():
|
|
1376
|
+
self.post_message(SleepSkipClicked(call_id))
|
|
1377
|
+
event.stop()
|
|
1378
|
+
return
|
|
1379
|
+
if self._invocation_id is None:
|
|
1380
|
+
return
|
|
1381
|
+
if event.button.id == "sa-retry-btn":
|
|
1382
|
+
self.post_message(SubAgentRetryClicked(self._invocation_id))
|
|
1383
|
+
elif event.button.id == "sa-abort-btn":
|
|
1384
|
+
self.post_message(SubAgentAbortClicked(self._invocation_id))
|
|
1385
|
+
event.stop()
|