code-puppy-core-plugins 0.0.2__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.
- code_puppy_core_plugins/__init__.py +5 -0
- code_puppy_core_plugins/acp/README.md +266 -0
- code_puppy_core_plugins/acp/__init__.py +8 -0
- code_puppy_core_plugins/acp/agent.py +494 -0
- code_puppy_core_plugins/acp/bridge.py +309 -0
- code_puppy_core_plugins/acp/capabilities.py +73 -0
- code_puppy_core_plugins/acp/commands.py +116 -0
- code_puppy_core_plugins/acp/content.py +115 -0
- code_puppy_core_plugins/acp/io_delegation.py +231 -0
- code_puppy_core_plugins/acp/mcp_config.py +66 -0
- code_puppy_core_plugins/acp/permissions.py +159 -0
- code_puppy_core_plugins/acp/persistence.py +197 -0
- code_puppy_core_plugins/acp/register_callbacks.py +110 -0
- code_puppy_core_plugins/acp/replay.py +114 -0
- code_puppy_core_plugins/acp/session.py +329 -0
- code_puppy_core_plugins/acp/session_config.py +179 -0
- code_puppy_core_plugins/acp/state.py +135 -0
- code_puppy_core_plugins/agent_skills/__init__.py +22 -0
- code_puppy_core_plugins/agent_skills/config.py +208 -0
- code_puppy_core_plugins/agent_skills/discovery.py +337 -0
- code_puppy_core_plugins/agent_skills/downloader.py +392 -0
- code_puppy_core_plugins/agent_skills/enabled_skills.py +82 -0
- code_puppy_core_plugins/agent_skills/installer.py +22 -0
- code_puppy_core_plugins/agent_skills/metadata.py +219 -0
- code_puppy_core_plugins/agent_skills/prompt_builder.py +49 -0
- code_puppy_core_plugins/agent_skills/provider.py +52 -0
- code_puppy_core_plugins/agent_skills/register_callbacks.py +309 -0
- code_puppy_core_plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy_core_plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy_core_plugins/agent_skills/skill_commands.py +102 -0
- code_puppy_core_plugins/agent_skills/skills_install_menu.py +698 -0
- code_puppy_core_plugins/agent_skills/skills_menu.py +794 -0
- code_puppy_core_plugins/aws_bedrock/__init__.py +14 -0
- code_puppy_core_plugins/aws_bedrock/config.py +101 -0
- code_puppy_core_plugins/aws_bedrock/register_callbacks.py +243 -0
- code_puppy_core_plugins/aws_bedrock/utils.py +186 -0
- code_puppy_core_plugins/azure_foundry/README.md +238 -0
- code_puppy_core_plugins/azure_foundry/__init__.py +15 -0
- code_puppy_core_plugins/azure_foundry/config.py +126 -0
- code_puppy_core_plugins/azure_foundry/discovery.py +189 -0
- code_puppy_core_plugins/azure_foundry/register_callbacks.py +506 -0
- code_puppy_core_plugins/azure_foundry/token.py +182 -0
- code_puppy_core_plugins/azure_foundry/utils.py +375 -0
- code_puppy_core_plugins/btw/__init__.py +1 -0
- code_puppy_core_plugins/btw/inline_view.py +135 -0
- code_puppy_core_plugins/btw/register_callbacks.py +106 -0
- code_puppy_core_plugins/btw/side_query.py +91 -0
- code_puppy_core_plugins/chatgpt_oauth/IMAGEGEN_SKILL.md +83 -0
- code_puppy_core_plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy_core_plugins/chatgpt_oauth/config.py +52 -0
- code_puppy_core_plugins/chatgpt_oauth/image_generation.py +253 -0
- code_puppy_core_plugins/chatgpt_oauth/image_tool.py +70 -0
- code_puppy_core_plugins/chatgpt_oauth/oauth_flow.py +461 -0
- code_puppy_core_plugins/chatgpt_oauth/register_callbacks.py +288 -0
- code_puppy_core_plugins/chatgpt_oauth/test_plugin.py +663 -0
- code_puppy_core_plugins/chatgpt_oauth/usage.py +113 -0
- code_puppy_core_plugins/chatgpt_oauth/utils.py +534 -0
- code_puppy_core_plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy_core_plugins/claude_code_hooks/config.py +137 -0
- code_puppy_core_plugins/claude_code_hooks/register_callbacks.py +383 -0
- code_puppy_core_plugins/claude_code_oauth/README.md +167 -0
- code_puppy_core_plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy_core_plugins/claude_code_oauth/__init__.py +25 -0
- code_puppy_core_plugins/claude_code_oauth/config.py +59 -0
- code_puppy_core_plugins/claude_code_oauth/fast_mode.py +122 -0
- code_puppy_core_plugins/claude_code_oauth/prompt_handler.py +64 -0
- code_puppy_core_plugins/claude_code_oauth/register_callbacks.py +719 -0
- code_puppy_core_plugins/claude_code_oauth/test_plugin.py +285 -0
- code_puppy_core_plugins/claude_code_oauth/token_refresh_heartbeat.py +240 -0
- code_puppy_core_plugins/claude_code_oauth/utils.py +663 -0
- code_puppy_core_plugins/code_puppy_agent/AGENTS_AND_TOOLS.md +155 -0
- code_puppy_core_plugins/code_puppy_agent/MODELS_AND_MCP.md +104 -0
- code_puppy_core_plugins/code_puppy_agent/PLUGINS_AND_CALLBACKS.md +147 -0
- code_puppy_core_plugins/code_puppy_agent/SESSIONS_AND_HISTORY.md +39 -0
- code_puppy_core_plugins/code_puppy_agent/SKILL.md +180 -0
- code_puppy_core_plugins/code_puppy_agent/SKILLS_SYSTEM.md +136 -0
- code_puppy_core_plugins/code_puppy_agent/SYSTEM_PROMPT_CONFIG_AND_I18N.md +160 -0
- code_puppy_core_plugins/code_puppy_agent/__init__.py +5 -0
- code_puppy_core_plugins/code_puppy_agent/register_callbacks.py +46 -0
- code_puppy_core_plugins/computer_use/NativeCapture.swift +123 -0
- code_puppy_core_plugins/computer_use/README.md +109 -0
- code_puppy_core_plugins/computer_use/__init__.py +1 -0
- code_puppy_core_plugins/computer_use/accessibility.py +163 -0
- code_puppy_core_plugins/computer_use/activation.py +75 -0
- code_puppy_core_plugins/computer_use/backend.py +584 -0
- code_puppy_core_plugins/computer_use/backend_types.py +5 -0
- code_puppy_core_plugins/computer_use/capture.py +110 -0
- code_puppy_core_plugins/computer_use/commands.py +62 -0
- code_puppy_core_plugins/computer_use/geometry.py +81 -0
- code_puppy_core_plugins/computer_use/inline_image.py +56 -0
- code_puppy_core_plugins/computer_use/keycodes.py +47 -0
- code_puppy_core_plugins/computer_use/policy.py +144 -0
- code_puppy_core_plugins/computer_use/register_callbacks.py +96 -0
- code_puppy_core_plugins/computer_use/safety.py +18 -0
- code_puppy_core_plugins/computer_use/settle.py +64 -0
- code_puppy_core_plugins/computer_use/state.py +86 -0
- code_puppy_core_plugins/computer_use/tools.py +469 -0
- code_puppy_core_plugins/context_indicator/__init__.py +14 -0
- code_puppy_core_plugins/context_indicator/register_callbacks.py +249 -0
- code_puppy_core_plugins/context_indicator/usage.py +40 -0
- code_puppy_core_plugins/copilot_auth/__init__.py +11 -0
- code_puppy_core_plugins/copilot_auth/config.py +93 -0
- code_puppy_core_plugins/copilot_auth/reasoning_client.py +409 -0
- code_puppy_core_plugins/copilot_auth/register_callbacks.py +460 -0
- code_puppy_core_plugins/copilot_auth/utils.py +587 -0
- code_puppy_core_plugins/customizable_commands/__init__.py +0 -0
- code_puppy_core_plugins/customizable_commands/register_callbacks.py +464 -0
- code_puppy_core_plugins/dbos_durable_exec/__init__.py +1 -0
- code_puppy_core_plugins/dbos_durable_exec/cancel.py +15 -0
- code_puppy_core_plugins/dbos_durable_exec/commands.py +31 -0
- code_puppy_core_plugins/dbos_durable_exec/config.py +31 -0
- code_puppy_core_plugins/dbos_durable_exec/lifecycle.py +83 -0
- code_puppy_core_plugins/dbos_durable_exec/register_callbacks.py +45 -0
- code_puppy_core_plugins/dbos_durable_exec/runtime.py +65 -0
- code_puppy_core_plugins/dbos_durable_exec/startup_lock.py +155 -0
- code_puppy_core_plugins/dbos_durable_exec/workflow_ids.py +16 -0
- code_puppy_core_plugins/dbos_durable_exec/wrapper.py +58 -0
- code_puppy_core_plugins/destructive_command_guard/AGENTS.md +79 -0
- code_puppy_core_plugins/destructive_command_guard/__init__.py +14 -0
- code_puppy_core_plugins/destructive_command_guard/detector.py +374 -0
- code_puppy_core_plugins/destructive_command_guard/register_callbacks.py +165 -0
- code_puppy_core_plugins/emoji_filter/__init__.py +12 -0
- code_puppy_core_plugins/emoji_filter/config.py +26 -0
- code_puppy_core_plugins/emoji_filter/register_callbacks.py +238 -0
- code_puppy_core_plugins/emoji_filter/stripper.py +45 -0
- code_puppy_core_plugins/empty_html_comment_filter/register_callbacks.py +116 -0
- code_puppy_core_plugins/example_custom_command/README.md +328 -0
- code_puppy_core_plugins/example_custom_command/register_callbacks.py +66 -0
- code_puppy_core_plugins/file_permission_handler/__init__.py +4 -0
- code_puppy_core_plugins/file_permission_handler/register_callbacks.py +575 -0
- code_puppy_core_plugins/flux_bootstrap/__init__.py +5 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/README.md +173 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/cheatsheet.md +86 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/pipeline.md +167 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/synopsis.md +392 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/about.md +51 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/address-feedback.md +129 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/ask.md +211 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/aug.md +181 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/auto-pilot.md +71 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/cheatsheet.md +16 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/commit.md +103 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/config.md +108 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/create-pr.md +195 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/exec.md +221 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/new.md +151 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/qa.md +208 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/rebase.md +180 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/review.md +304 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/split.md +120 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/squash-commits.md +179 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/status.md +46 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/tests.md +155 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_about.py +154 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_cheatsheet.py +248 -0
- code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_status.py +345 -0
- code_puppy_core_plugins/flux_bootstrap/installer.py +256 -0
- code_puppy_core_plugins/flux_bootstrap/register_callbacks.py +71 -0
- code_puppy_core_plugins/force_push_guard/__init__.py +5 -0
- code_puppy_core_plugins/force_push_guard/detector.py +96 -0
- code_puppy_core_plugins/force_push_guard/register_callbacks.py +161 -0
- code_puppy_core_plugins/fork/__init__.py +7 -0
- code_puppy_core_plugins/fork/register_callbacks.py +445 -0
- code_puppy_core_plugins/frontend_emitter/__init__.py +25 -0
- code_puppy_core_plugins/frontend_emitter/emitter.py +249 -0
- code_puppy_core_plugins/frontend_emitter/register_callbacks.py +412 -0
- code_puppy_core_plugins/frontend_emitter/session_context.py +50 -0
- code_puppy_core_plugins/grok_oauth/__init__.py +6 -0
- code_puppy_core_plugins/grok_oauth/config.py +49 -0
- code_puppy_core_plugins/grok_oauth/oauth_flow.py +213 -0
- code_puppy_core_plugins/grok_oauth/register_callbacks.py +132 -0
- code_puppy_core_plugins/grok_oauth/utils.py +195 -0
- code_puppy_core_plugins/herdr/README.md +168 -0
- code_puppy_core_plugins/herdr/__init__.py +9 -0
- code_puppy_core_plugins/herdr/client.py +339 -0
- code_puppy_core_plugins/herdr/register_callbacks.py +140 -0
- code_puppy_core_plugins/herdr/reporter.py +212 -0
- code_puppy_core_plugins/herdr/smoke.py +143 -0
- code_puppy_core_plugins/herdr/sources.py +133 -0
- code_puppy_core_plugins/hook_creator/__init__.py +1 -0
- code_puppy_core_plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy_core_plugins/hook_manager/__init__.py +1 -0
- code_puppy_core_plugins/hook_manager/config.py +290 -0
- code_puppy_core_plugins/hook_manager/hooks_menu.py +571 -0
- code_puppy_core_plugins/hook_manager/register_callbacks.py +227 -0
- code_puppy_core_plugins/meta_oauth/__init__.py +1 -0
- code_puppy_core_plugins/meta_oauth/config.py +55 -0
- code_puppy_core_plugins/meta_oauth/oauth_flow.py +109 -0
- code_puppy_core_plugins/meta_oauth/register_callbacks.py +154 -0
- code_puppy_core_plugins/meta_oauth/utils.py +252 -0
- code_puppy_core_plugins/namespace_skill_search/README.md +118 -0
- code_puppy_core_plugins/namespace_skill_search/__init__.py +15 -0
- code_puppy_core_plugins/namespace_skill_search/namespaces.py +125 -0
- code_puppy_core_plugins/namespace_skill_search/register_callbacks.py +99 -0
- code_puppy_core_plugins/namespace_skill_search/search_tool.py +158 -0
- code_puppy_core_plugins/no_tools/__init__.py +0 -0
- code_puppy_core_plugins/no_tools/register_callbacks.py +42 -0
- code_puppy_core_plugins/oauth_pasteback.py +110 -0
- code_puppy_core_plugins/oauth_puppy_html.py +194 -0
- code_puppy_core_plugins/obsidian_agent/README.md +52 -0
- code_puppy_core_plugins/obsidian_agent/__init__.py +8 -0
- code_puppy_core_plugins/obsidian_agent/agent_obsidian.py +147 -0
- code_puppy_core_plugins/obsidian_agent/register_callbacks.py +13 -0
- code_puppy_core_plugins/ollama/__init__.py +1 -0
- code_puppy_core_plugins/ollama/register_callbacks.py +127 -0
- code_puppy_core_plugins/ollama_setup/__init__.py +5 -0
- code_puppy_core_plugins/ollama_setup/completer.py +38 -0
- code_puppy_core_plugins/ollama_setup/register_callbacks.py +419 -0
- code_puppy_core_plugins/plugin_list/__init__.py +0 -0
- code_puppy_core_plugins/plugin_list/plugin_contributions.py +348 -0
- code_puppy_core_plugins/plugin_list/plugin_meta.py +67 -0
- code_puppy_core_plugins/plugin_list/plugin_text_utils.py +173 -0
- code_puppy_core_plugins/plugin_list/plugins_menu.py +455 -0
- code_puppy_core_plugins/plugin_list/plugins_menu_layout.py +194 -0
- code_puppy_core_plugins/plugin_list/plugins_menu_render.py +431 -0
- code_puppy_core_plugins/plugin_list/project_trust_flow.py +162 -0
- code_puppy_core_plugins/plugin_list/register_callbacks.py +281 -0
- code_puppy_core_plugins/pop_command/__init__.py +1 -0
- code_puppy_core_plugins/pop_command/register_callbacks.py +191 -0
- code_puppy_core_plugins/prompt_newline/__init__.py +13 -0
- code_puppy_core_plugins/prompt_newline/config.py +21 -0
- code_puppy_core_plugins/prompt_newline/register_callbacks.py +163 -0
- code_puppy_core_plugins/prune/__init__.py +1 -0
- code_puppy_core_plugins/prune/prune_menu.py +308 -0
- code_puppy_core_plugins/prune/prune_model.py +517 -0
- code_puppy_core_plugins/prune/prune_render.py +354 -0
- code_puppy_core_plugins/prune/register_callbacks.py +388 -0
- code_puppy_core_plugins/puppy_kennel/README.md +203 -0
- code_puppy_core_plugins/puppy_kennel/__init__.py +6 -0
- code_puppy_core_plugins/puppy_kennel/commands.py +206 -0
- code_puppy_core_plugins/puppy_kennel/config.py +36 -0
- code_puppy_core_plugins/puppy_kennel/kennel.py +368 -0
- code_puppy_core_plugins/puppy_kennel/packer.py +152 -0
- code_puppy_core_plugins/puppy_kennel/recorder.py +83 -0
- code_puppy_core_plugins/puppy_kennel/register_callbacks.py +109 -0
- code_puppy_core_plugins/puppy_kennel/retriever.py +28 -0
- code_puppy_core_plugins/puppy_kennel/schema.py +78 -0
- code_puppy_core_plugins/puppy_kennel/state.py +68 -0
- code_puppy_core_plugins/puppy_kennel/tools.py +449 -0
- code_puppy_core_plugins/puppy_kennel/wings.py +110 -0
- code_puppy_core_plugins/puppy_spinner/README.md +93 -0
- code_puppy_core_plugins/puppy_spinner/__init__.py +11 -0
- code_puppy_core_plugins/puppy_spinner/builtin_frames.py +172 -0
- code_puppy_core_plugins/puppy_spinner/commands.py +116 -0
- code_puppy_core_plugins/puppy_spinner/picker.py +379 -0
- code_puppy_core_plugins/puppy_spinner/register_callbacks.py +193 -0
- code_puppy_core_plugins/puppy_spinner/spinners.py +444 -0
- code_puppy_core_plugins/quick_resume/__init__.py +0 -0
- code_puppy_core_plugins/quick_resume/register_callbacks.py +77 -0
- code_puppy_core_plugins/review_pr/__init__.py +1 -0
- code_puppy_core_plugins/review_pr/register_callbacks.py +162 -0
- code_puppy_core_plugins/shell_safety/__init__.py +6 -0
- code_puppy_core_plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy_core_plugins/shell_safety/command_cache.py +156 -0
- code_puppy_core_plugins/shell_safety/register_callbacks.py +203 -0
- code_puppy_core_plugins/stack_dump/__init__.py +1 -0
- code_puppy_core_plugins/stack_dump/register_callbacks.py +79 -0
- code_puppy_core_plugins/statusline/__init__.py +14 -0
- code_puppy_core_plugins/statusline/config.py +68 -0
- code_puppy_core_plugins/statusline/payload.py +138 -0
- code_puppy_core_plugins/statusline/prompt_patch.py +76 -0
- code_puppy_core_plugins/statusline/register_callbacks.py +45 -0
- code_puppy_core_plugins/statusline/runner.py +122 -0
- code_puppy_core_plugins/statusline/statusline_command.py +197 -0
- code_puppy_core_plugins/steer_queue/__init__.py +10 -0
- code_puppy_core_plugins/steer_queue/queue_menu.py +444 -0
- code_puppy_core_plugins/steer_queue/register_callbacks.py +138 -0
- code_puppy_core_plugins/subagent_panel/README.md +102 -0
- code_puppy_core_plugins/subagent_panel/__init__.py +24 -0
- code_puppy_core_plugins/subagent_panel/coalesce_patch.py +242 -0
- code_puppy_core_plugins/subagent_panel/panel_render.py +145 -0
- code_puppy_core_plugins/subagent_panel/register_callbacks.py +535 -0
- code_puppy_core_plugins/subagent_panel/resume_repaint.py +112 -0
- code_puppy_core_plugins/subagent_panel/state.py +231 -0
- code_puppy_core_plugins/switch_agent_resume/__init__.py +5 -0
- code_puppy_core_plugins/switch_agent_resume/register_callbacks.py +314 -0
- code_puppy_core_plugins/theme/README.md +86 -0
- code_puppy_core_plugins/theme/__init__.py +14 -0
- code_puppy_core_plugins/theme/bundled_palettes.py +346 -0
- code_puppy_core_plugins/theme/content_styles.py +146 -0
- code_puppy_core_plugins/theme/osc_palette.py +178 -0
- code_puppy_core_plugins/theme/picker.py +358 -0
- code_puppy_core_plugins/theme/prompt_toolkit_theme.py +113 -0
- code_puppy_core_plugins/theme/register_callbacks.py +348 -0
- code_puppy_core_plugins/theme/rich_themes.py +202 -0
- code_puppy_core_plugins/theme/themes.py +720 -0
- code_puppy_core_plugins/timestamp_heartbeat/__init__.py +1 -0
- code_puppy_core_plugins/timestamp_heartbeat/register_callbacks.py +114 -0
- code_puppy_core_plugins/token_ratio_learner/__init__.py +5 -0
- code_puppy_core_plugins/token_ratio_learner/ratios.py +208 -0
- code_puppy_core_plugins/token_ratio_learner/register_callbacks.py +213 -0
- code_puppy_core_plugins/universal_constructor/__init__.py +13 -0
- code_puppy_core_plugins/universal_constructor/models.py +138 -0
- code_puppy_core_plugins/universal_constructor/provider.py +62 -0
- code_puppy_core_plugins/universal_constructor/register_callbacks.py +75 -0
- code_puppy_core_plugins/universal_constructor/registry.py +302 -0
- code_puppy_core_plugins/universal_constructor/sandbox.py +584 -0
- code_puppy_core_plugins/wide_completion_menu/__init__.py +6 -0
- code_puppy_core_plugins/wide_completion_menu/register_callbacks.py +165 -0
- code_puppy_core_plugins/wiggum/__init__.py +1 -0
- code_puppy_core_plugins/wiggum/goal_runs.py +91 -0
- code_puppy_core_plugins/wiggum/judge.py +299 -0
- code_puppy_core_plugins/wiggum/judge_config.py +249 -0
- code_puppy_core_plugins/wiggum/judges_menu.py +814 -0
- code_puppy_core_plugins/wiggum/register_callbacks.py +569 -0
- code_puppy_core_plugins/wiggum/state.py +89 -0
- code_puppy_core_plugins/yolo_cli/__init__.py +1 -0
- code_puppy_core_plugins/yolo_cli/register_callbacks.py +41 -0
- code_puppy_core_plugins-0.0.2.dist-info/METADATA +64 -0
- code_puppy_core_plugins-0.0.2.dist-info/RECORD +313 -0
- code_puppy_core_plugins-0.0.2.dist-info/WHEEL +4 -0
- code_puppy_core_plugins-0.0.2.dist-info/entry_points.txt +55 -0
- code_puppy_core_plugins-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Plugin registration: the ``--acp`` entry point.
|
|
2
|
+
|
|
3
|
+
This is the only place the ACP plugin touches Code Puppy's CLI. It:
|
|
4
|
+
1. contributes a ``--acp`` flag via the ``register_cli_args`` hook, and
|
|
5
|
+
2. in ``handle_cli_args``, when ``--acp`` is present, boots the ACP server
|
|
6
|
+
over stdio and returns the short-circuit sentinel so the normal
|
|
7
|
+
interactive TUI never starts.
|
|
8
|
+
|
|
9
|
+
Per the contributing guide, all of this rides on existing hooks — no edits to
|
|
10
|
+
``code_puppy/command_line`` or the core CLI runner.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Any, Dict, Optional
|
|
18
|
+
|
|
19
|
+
from code_puppy.callbacks import register_callback
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _register_cli_args(parser: Any) -> None:
|
|
25
|
+
"""Add the ``--acp`` flag to Code Puppy's argument parser."""
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--acp",
|
|
28
|
+
action="store_true",
|
|
29
|
+
help="Run as a native agent over the Agent Client Protocol (ACP), speaking JSON-RPC over stdio.",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _handle_cli_args(args: Any) -> Optional[Dict[str, Any]]:
|
|
34
|
+
"""Boot the ACP server when ``--acp`` is set; otherwise stand down.
|
|
35
|
+
|
|
36
|
+
Returns the ``{"handled": True, "exit_code": ...}`` sentinel so the CLI
|
|
37
|
+
runner exits after the ACP session ends instead of falling through to the
|
|
38
|
+
interactive TUI. Any other invocation returns ``None`` (not ours).
|
|
39
|
+
|
|
40
|
+
This hook is invoked *synchronously from within* Code Puppy's already-
|
|
41
|
+
running ``asyncio.run(main())`` loop, so we cannot start a nested loop
|
|
42
|
+
here. Instead we run the server on its own event loop in a dedicated
|
|
43
|
+
thread and block until it finishes — which keeps ``main()`` parked so the
|
|
44
|
+
TUI never starts.
|
|
45
|
+
"""
|
|
46
|
+
if not getattr(args, "acp", False):
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
import threading
|
|
50
|
+
|
|
51
|
+
box: Dict[str, int] = {"exit_code": 0}
|
|
52
|
+
|
|
53
|
+
def _run() -> None:
|
|
54
|
+
try:
|
|
55
|
+
box["exit_code"] = asyncio.run(_serve())
|
|
56
|
+
except Exception: # noqa: BLE001 - never leak a traceback onto stdout
|
|
57
|
+
logger.exception("ACP server crashed")
|
|
58
|
+
box["exit_code"] = 1
|
|
59
|
+
|
|
60
|
+
thread = threading.Thread(target=_run, name="acp-server")
|
|
61
|
+
thread.start()
|
|
62
|
+
thread.join()
|
|
63
|
+
return {"handled": True, "exit_code": box["exit_code"]}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def _serve() -> int:
|
|
67
|
+
"""Bind stdio via the ACP SDK and run one connection to completion.
|
|
68
|
+
|
|
69
|
+
Before serving we protect stdout — the JSON-RPC channel — by pointing
|
|
70
|
+
Code Puppy's streaming console and the root logger at stderr. In ACP mode
|
|
71
|
+
the interactive path that normally calls ``set_streaming_console`` is
|
|
72
|
+
short-circuited, so without this the streaming console would fall back to a
|
|
73
|
+
plain ``Console()`` on stdout and corrupt the protocol.
|
|
74
|
+
|
|
75
|
+
``acp.run_agent`` owns the wire (stdio binding, JSON-RPC framing, param
|
|
76
|
+
parsing); we hand it a ``CodePuppyAgent`` and tear down the approval + I/O
|
|
77
|
+
backends afterwards so the process leaves no global seams set.
|
|
78
|
+
"""
|
|
79
|
+
import sys
|
|
80
|
+
|
|
81
|
+
from acp import run_agent
|
|
82
|
+
from rich.console import Console
|
|
83
|
+
|
|
84
|
+
from code_puppy.agents.event_stream_handler import set_streaming_console
|
|
85
|
+
from code_puppy_core_plugins.acp import io_delegation, permissions, state
|
|
86
|
+
from code_puppy_core_plugins.acp.agent import CodePuppyAgent
|
|
87
|
+
|
|
88
|
+
# Route all incidental console output to stderr; stdout is JSON-RPC only.
|
|
89
|
+
set_streaming_console(Console(file=sys.stderr))
|
|
90
|
+
logging.basicConfig(stream=sys.stderr, level=logging.WARNING)
|
|
91
|
+
|
|
92
|
+
agent = CodePuppyAgent()
|
|
93
|
+
try:
|
|
94
|
+
await run_agent(agent)
|
|
95
|
+
finally:
|
|
96
|
+
agent.shutdown()
|
|
97
|
+
permissions.uninstall()
|
|
98
|
+
io_delegation.uninstall()
|
|
99
|
+
state.set_connection(None, None)
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def register() -> None:
|
|
104
|
+
"""Register the ACP CLI hooks."""
|
|
105
|
+
register_callback("register_cli_args", _register_cli_args)
|
|
106
|
+
register_callback("handle_cli_args", _handle_cli_args)
|
|
107
|
+
logger.debug("ACP plugin callbacks registered")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
register()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Replay a rehydrated session's history back to the client on load/resume.
|
|
2
|
+
|
|
3
|
+
The ACP ``session/load`` (and ``session/resume``) contract is not just "rebuild
|
|
4
|
+
the agent's memory" -- the client rebuilds its *own* thread UI from the
|
|
5
|
+
``session/update`` notifications the agent streams while handling the request.
|
|
6
|
+
An agent that only rehydrates its internal ``_message_history`` (and streams
|
|
7
|
+
nothing) leaves the client with an empty thread: the conversation looks gone,
|
|
8
|
+
and clients that treat an empty replayed thread as dead will discard it
|
|
9
|
+
outright.
|
|
10
|
+
|
|
11
|
+
So on load/resume we walk the rehydrated pydantic-ai message history and emit
|
|
12
|
+
one ``session/update`` per visible turn -- user prompts, assistant text,
|
|
13
|
+
assistant thinking, and past tool calls (as already-``completed`` entries) --
|
|
14
|
+
in order, before the load response returns. System prompts and tool-return
|
|
15
|
+
plumbing are intentionally skipped: they are model-plumbing, not conversation
|
|
16
|
+
the user needs to see re-rendered.
|
|
17
|
+
|
|
18
|
+
Everything here is best-effort: a replay hiccup must never fail the load, so
|
|
19
|
+
per-update send failures are logged and swallowed.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import uuid
|
|
26
|
+
from typing import Any, Iterable, List
|
|
27
|
+
|
|
28
|
+
from acp.helpers import (
|
|
29
|
+
start_tool_call,
|
|
30
|
+
update_agent_message_text,
|
|
31
|
+
update_agent_thought_text,
|
|
32
|
+
update_user_message_text,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from code_puppy_core_plugins.acp import state
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def replay_history(session_id: str, history: Iterable[Any]) -> None:
|
|
41
|
+
"""Stream ``history`` back to the client as ordered ``session/update``s.
|
|
42
|
+
|
|
43
|
+
No-op when there is no connection or nothing to replay.
|
|
44
|
+
"""
|
|
45
|
+
connection = state.get_connection()
|
|
46
|
+
if connection is None:
|
|
47
|
+
return
|
|
48
|
+
for message in history or []:
|
|
49
|
+
for update in _updates_for_message(message):
|
|
50
|
+
try:
|
|
51
|
+
await connection.session_update(session_id, update)
|
|
52
|
+
except Exception: # noqa: BLE001
|
|
53
|
+
logger.debug("ACP: history replay update failed", exc_info=True)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _updates_for_message(message: Any) -> List[Any]:
|
|
57
|
+
"""Map one pydantic-ai message to zero or more ACP updates.
|
|
58
|
+
|
|
59
|
+
Discriminates on each part's ``part_kind`` (stable across pydantic-ai
|
|
60
|
+
versions) rather than importing concrete part classes, so a version bump
|
|
61
|
+
that renames a class doesn't silently drop history.
|
|
62
|
+
"""
|
|
63
|
+
updates: List[Any] = []
|
|
64
|
+
for part in getattr(message, "parts", None) or []:
|
|
65
|
+
kind = getattr(part, "part_kind", None)
|
|
66
|
+
if kind == "user-prompt":
|
|
67
|
+
text = _text_of(getattr(part, "content", None))
|
|
68
|
+
if text:
|
|
69
|
+
updates.append(update_user_message_text(text))
|
|
70
|
+
elif kind == "text":
|
|
71
|
+
text = _text_of(getattr(part, "content", None))
|
|
72
|
+
if text:
|
|
73
|
+
updates.append(update_agent_message_text(text))
|
|
74
|
+
elif kind == "thinking":
|
|
75
|
+
text = _text_of(getattr(part, "content", None))
|
|
76
|
+
if text:
|
|
77
|
+
updates.append(update_agent_thought_text(text))
|
|
78
|
+
elif kind == "tool-call":
|
|
79
|
+
updates.append(_tool_call_update(part))
|
|
80
|
+
# system-prompt / tool-return: model plumbing, not replayed.
|
|
81
|
+
return updates
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _tool_call_update(part: Any) -> Any:
|
|
85
|
+
"""Render a past tool call as an already-``completed`` tool-call entry.
|
|
86
|
+
|
|
87
|
+
Replayed tool calls are historical, so they open and close in one update
|
|
88
|
+
(status ``completed``). A fresh id is minted -- the original call id isn't
|
|
89
|
+
needed since nothing will update this entry again.
|
|
90
|
+
"""
|
|
91
|
+
tool_name = str(getattr(part, "tool_name", "") or "tool")
|
|
92
|
+
return start_tool_call(
|
|
93
|
+
f"tc_{uuid.uuid4().hex[:12]}",
|
|
94
|
+
tool_name,
|
|
95
|
+
kind="other",
|
|
96
|
+
status="completed",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _text_of(content: Any) -> str:
|
|
101
|
+
"""Flatten a part's content to plain text.
|
|
102
|
+
|
|
103
|
+
Handles the string case and pydantic-ai's multimodal list form (where a
|
|
104
|
+
user prompt is a list of str + binary/image blocks): string items are
|
|
105
|
+
kept, non-text blocks are dropped, since replay is a text reconstruction.
|
|
106
|
+
"""
|
|
107
|
+
if content is None:
|
|
108
|
+
return ""
|
|
109
|
+
if isinstance(content, str):
|
|
110
|
+
return content.strip()
|
|
111
|
+
if isinstance(content, (list, tuple)):
|
|
112
|
+
parts = [item for item in content if isinstance(item, str)]
|
|
113
|
+
return "\n".join(p for p in parts if p).strip()
|
|
114
|
+
return str(content).strip()
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""``ACPSession`` — one client thread bound to one Code Puppy agent.
|
|
2
|
+
|
|
3
|
+
Each ACP session id maps to a single ``BaseAgent`` instance whose
|
|
4
|
+
``_message_history`` persists across ``session/prompt`` calls, giving native
|
|
5
|
+
multi-turn conversations. The session also carries the ``cwd`` the client
|
|
6
|
+
opened the thread in, so tools resolve paths relative to the right project.
|
|
7
|
+
|
|
8
|
+
Cancellation (``session/cancel``) is real: the prompt runs as an
|
|
9
|
+
``asyncio.Task`` we hold a handle to, so a cancel notification cancels that
|
|
10
|
+
task. pydantic-ai surfaces the ``CancelledError``, which we translate into the
|
|
11
|
+
``cancelled`` stop reason for the in-flight ``session/prompt`` reply.
|
|
12
|
+
|
|
13
|
+
Each turn also reports token usage (mapped from the pydantic-ai run result)
|
|
14
|
+
back on the ``PromptResponse`` so the client can show cost/usage.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import logging
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import TYPE_CHECKING, Any, List, Optional
|
|
23
|
+
|
|
24
|
+
from acp.schema import Usage
|
|
25
|
+
|
|
26
|
+
from code_puppy_core_plugins.acp import commands, content, persistence, state
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from code_puppy.agents.base_agent import BaseAgent
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
# ACP stop reasons a ``session/prompt`` may resolve to.
|
|
34
|
+
STOP_END_TURN = "end_turn"
|
|
35
|
+
STOP_CANCELLED = "cancelled"
|
|
36
|
+
STOP_REFUSAL = "refusal"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class PromptResult:
|
|
41
|
+
"""Outcome of one turn: an ACP stop reason plus optional token usage."""
|
|
42
|
+
|
|
43
|
+
stop_reason: str
|
|
44
|
+
usage: Optional[Usage] = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ACPSession:
|
|
48
|
+
"""State + run lifecycle for a single client agent thread."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
session_id: str,
|
|
53
|
+
agent: "BaseAgent",
|
|
54
|
+
cwd: Optional[str] = None,
|
|
55
|
+
additional_directories: Optional[List[str]] = None,
|
|
56
|
+
mcp_specs: Optional[List[Any]] = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
self.session_id = session_id
|
|
59
|
+
self.agent = agent
|
|
60
|
+
self.cwd = cwd
|
|
61
|
+
self.additional_directories = list(additional_directories or [])
|
|
62
|
+
# Raw client-injected ACP MCP specs, retained so they can be re-attached
|
|
63
|
+
# if the session's agent is rebuilt (e.g. on ``session/set_mode``).
|
|
64
|
+
self.mcp_specs = list(mcp_specs or [])
|
|
65
|
+
# Set while a ``session/prompt`` is in flight so ``cancel`` has a task
|
|
66
|
+
# to cancel. ``None`` means idle.
|
|
67
|
+
self._task: Optional["asyncio.Task[Any]"] = None
|
|
68
|
+
|
|
69
|
+
async def prompt(self, blocks: List[Any]) -> PromptResult:
|
|
70
|
+
"""Run the agent on a user turn and return the stop reason + usage.
|
|
71
|
+
|
|
72
|
+
Flow:
|
|
73
|
+
1. Flatten ACP content blocks into the user text.
|
|
74
|
+
2. Point the messaging session context + run context at this session
|
|
75
|
+
so streamed deltas and tool calls are tagged/correlated.
|
|
76
|
+
3. Run ``agent.run_with_mcp(text)`` as a cancellable task — history
|
|
77
|
+
stays on the agent instance, so the next turn continues.
|
|
78
|
+
4. If nothing streamed (streaming disabled/empty), send the final
|
|
79
|
+
result text as one ``agent_message_chunk`` so the client sees it.
|
|
80
|
+
"""
|
|
81
|
+
from code_puppy.messaging import set_session_context
|
|
82
|
+
|
|
83
|
+
parsed = content.parse_prompt(blocks)
|
|
84
|
+
text = parsed.text
|
|
85
|
+
if not text.strip() and not parsed.attachments and not parsed.link_attachments:
|
|
86
|
+
return PromptResult(STOP_END_TURN)
|
|
87
|
+
# A leading /slash command runs via Code Puppy's command handler (not the
|
|
88
|
+
# model); if it expands into a prompt (string result), run the model on it,
|
|
89
|
+
# exactly as cli_runner does with a string command result.
|
|
90
|
+
if (
|
|
91
|
+
not parsed.attachments
|
|
92
|
+
and not parsed.link_attachments
|
|
93
|
+
and commands.is_command(text)
|
|
94
|
+
):
|
|
95
|
+
expanded = await commands.run(self.session_id, text)
|
|
96
|
+
if not expanded:
|
|
97
|
+
return PromptResult(STOP_END_TURN)
|
|
98
|
+
text = expanded
|
|
99
|
+
# Resolve relative paths against this session's own cwd -- no process-global
|
|
100
|
+
# os.chdir (would corrupt the SDK's I/O + any concurrent session).
|
|
101
|
+
from code_puppy.tools.common import (
|
|
102
|
+
reset_working_directory,
|
|
103
|
+
set_working_directory,
|
|
104
|
+
)
|
|
105
|
+
from code_puppy.tools.subagent_context import (
|
|
106
|
+
reset_conversation_root_id,
|
|
107
|
+
set_conversation_root_id,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
cwd_token = set_working_directory(self.cwd) if self.cwd else None
|
|
111
|
+
# ContextVar, not set_session_context: conversation_scope reads it and needs
|
|
112
|
+
# real per-task isolation across concurrent ACP sessions, which the shared
|
|
113
|
+
# mutable attribute can't provide.
|
|
114
|
+
root_id_token = set_conversation_root_id(self.session_id)
|
|
115
|
+
|
|
116
|
+
set_session_context(self.session_id)
|
|
117
|
+
state.begin_run(self.session_id)
|
|
118
|
+
result: Any = None
|
|
119
|
+
error: Optional[BaseException] = None
|
|
120
|
+
try:
|
|
121
|
+
self._task = asyncio.ensure_future(
|
|
122
|
+
self.agent.run_with_mcp(
|
|
123
|
+
text,
|
|
124
|
+
attachments=parsed.attachments or None,
|
|
125
|
+
link_attachments=parsed.link_attachments or None,
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
result = await self._task
|
|
129
|
+
self._absorb_history(result)
|
|
130
|
+
stop_reason = STOP_END_TURN
|
|
131
|
+
except asyncio.CancelledError:
|
|
132
|
+
# Two ways here: our own cancel cancelled the inner task (report
|
|
133
|
+
# ``cancelled``), or THIS coroutine was cancelled (SDK teardown) while the
|
|
134
|
+
# task lives on -> cancel it so it can't outlive us, and propagate.
|
|
135
|
+
task = self._task
|
|
136
|
+
if task is not None and not task.cancelled():
|
|
137
|
+
task.cancel()
|
|
138
|
+
raise
|
|
139
|
+
stop_reason = STOP_CANCELLED
|
|
140
|
+
except Exception as exc: # noqa: BLE001
|
|
141
|
+
logger.exception("ACP: agent run failed")
|
|
142
|
+
error = exc
|
|
143
|
+
stop_reason = STOP_REFUSAL
|
|
144
|
+
finally:
|
|
145
|
+
streamed = state.streamed_text()
|
|
146
|
+
self._task = None
|
|
147
|
+
state.end_run()
|
|
148
|
+
set_session_context(None)
|
|
149
|
+
reset_conversation_root_id(root_id_token)
|
|
150
|
+
if cwd_token is not None:
|
|
151
|
+
reset_working_directory(cwd_token)
|
|
152
|
+
|
|
153
|
+
# Persist off the event loop so pickling a large history can't stall other
|
|
154
|
+
# sessions' streaming. Best-effort; never fails the turn.
|
|
155
|
+
try:
|
|
156
|
+
await asyncio.to_thread(
|
|
157
|
+
persistence.save,
|
|
158
|
+
self.session_id,
|
|
159
|
+
self.agent,
|
|
160
|
+
self.cwd,
|
|
161
|
+
self.additional_directories,
|
|
162
|
+
)
|
|
163
|
+
except Exception: # noqa: BLE001
|
|
164
|
+
logger.debug("ACP: async persist failed", exc_info=True)
|
|
165
|
+
|
|
166
|
+
if stop_reason == STOP_END_TURN and not streamed and result is not None:
|
|
167
|
+
await self._send_final_result(result)
|
|
168
|
+
elif stop_reason == STOP_REFUSAL:
|
|
169
|
+
await self._send_error_notice(error)
|
|
170
|
+
return PromptResult(stop_reason, _to_acp_usage(result))
|
|
171
|
+
|
|
172
|
+
def _absorb_history(self, result: Any) -> None:
|
|
173
|
+
"""Fold a completed run's full message list back into the agent.
|
|
174
|
+
|
|
175
|
+
``run_with_mcp`` (the shared runtime) does NOT write the turn's
|
|
176
|
+
request+response back onto ``agent._message_history`` on the normal
|
|
177
|
+
path -- the caller must, exactly as ``cli_runner`` does after each
|
|
178
|
+
interactive turn. Without this the agent forgets every turn the moment
|
|
179
|
+
it ends: the next prompt runs with empty history, and persistence saves
|
|
180
|
+
a history containing only the user's prompt (no assistant reply). So we
|
|
181
|
+
replace the agent's history with ``result.all_messages()`` here, which
|
|
182
|
+
is what makes ACP multi-turn memory *and* load/resume replay real.
|
|
183
|
+
"""
|
|
184
|
+
if result is None:
|
|
185
|
+
return
|
|
186
|
+
try:
|
|
187
|
+
messages = result.all_messages()
|
|
188
|
+
except Exception: # noqa: BLE001
|
|
189
|
+
return
|
|
190
|
+
if messages:
|
|
191
|
+
self.agent.set_message_history(list(messages))
|
|
192
|
+
|
|
193
|
+
def cancel(self) -> None:
|
|
194
|
+
"""Cancel the in-flight run, if any. No-op when idle.
|
|
195
|
+
|
|
196
|
+
Besides cancelling the asyncio task, force-kill any local shell
|
|
197
|
+
processes the run spawned so they don't orphan. (Shells delegated to
|
|
198
|
+
the client run client-side and are unaffected.)
|
|
199
|
+
|
|
200
|
+
Only *requests* cancellation -- delivery happens at the task's next
|
|
201
|
+
await point, whenever that is, not immediately. Callers that need
|
|
202
|
+
the run to have actually finished unwinding (its ``finally`` block
|
|
203
|
+
included) before they act should use :meth:`cancel_and_wait` instead.
|
|
204
|
+
"""
|
|
205
|
+
task = self._task
|
|
206
|
+
if task is not None and not task.done():
|
|
207
|
+
task.cancel()
|
|
208
|
+
try:
|
|
209
|
+
from code_puppy.tools.command_runner import (
|
|
210
|
+
kill_all_running_shell_processes,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
kill_all_running_shell_processes()
|
|
214
|
+
except Exception: # noqa: BLE001
|
|
215
|
+
logger.debug("ACP: shell kill on cancel failed", exc_info=True)
|
|
216
|
+
|
|
217
|
+
async def cancel_and_wait(self, timeout: float = 2.0) -> None:
|
|
218
|
+
"""Cancel the in-flight run and wait for it to actually unwind.
|
|
219
|
+
|
|
220
|
+
``cancel()`` alone only *requests* cancellation; asyncio only
|
|
221
|
+
delivers it at the task's next await point, with no guarantee of
|
|
222
|
+
when that is. A caller that needs the run's own cleanup to have
|
|
223
|
+
genuinely finished first -- notably ``close_session`` purging this
|
|
224
|
+
session's warn-dedup bucket, which would otherwise race a
|
|
225
|
+
still-in-flight ``_invoke_agent_impl`` synchronously writing into
|
|
226
|
+
that same bucket a moment later -- must await this instead of the
|
|
227
|
+
bare ``cancel()``.
|
|
228
|
+
|
|
229
|
+
Bounded by ``timeout`` so a run stuck ignoring cancellation (blocked
|
|
230
|
+
in genuinely non-cancellable I/O, say) can't hang session close
|
|
231
|
+
forever; on timeout we give up waiting and return anyway -- the task
|
|
232
|
+
is still cancelled, just not confirmed finished.
|
|
233
|
+
"""
|
|
234
|
+
task = self._task
|
|
235
|
+
self.cancel()
|
|
236
|
+
if task is None:
|
|
237
|
+
return
|
|
238
|
+
try:
|
|
239
|
+
await asyncio.wait_for(task, timeout=timeout)
|
|
240
|
+
except (asyncio.CancelledError, asyncio.TimeoutError):
|
|
241
|
+
pass
|
|
242
|
+
except Exception: # noqa: BLE001
|
|
243
|
+
# prompt()'s own except block already logs/handles run failures;
|
|
244
|
+
# a close shouldn't raise because the run it interrupted failed.
|
|
245
|
+
logger.debug("ACP: cancel_and_wait observed run failure", exc_info=True)
|
|
246
|
+
|
|
247
|
+
async def _send_error_notice(self, error: Optional[BaseException]) -> None:
|
|
248
|
+
"""Tell the client a turn failed, so it isn't a silent empty response.
|
|
249
|
+
|
|
250
|
+
A ``refusal`` stop reason alone renders as nothing in most clients; a
|
|
251
|
+
short message chunk makes the failure visible without leaking a stack
|
|
252
|
+
trace (the full traceback is logged to stderr).
|
|
253
|
+
"""
|
|
254
|
+
from acp.helpers import update_agent_message_text
|
|
255
|
+
|
|
256
|
+
connection = state.get_connection()
|
|
257
|
+
if connection is None:
|
|
258
|
+
return
|
|
259
|
+
detail = f": {error}" if error else "."
|
|
260
|
+
try:
|
|
261
|
+
await connection.session_update(
|
|
262
|
+
self.session_id,
|
|
263
|
+
update_agent_message_text(f"\u26a0\ufe0f The agent run failed{detail}"),
|
|
264
|
+
)
|
|
265
|
+
except Exception: # noqa: BLE001
|
|
266
|
+
logger.debug("ACP: failed to send error notice", exc_info=True)
|
|
267
|
+
|
|
268
|
+
async def _send_final_result(self, result: Any) -> None:
|
|
269
|
+
"""Emit the run's final text when nothing streamed during the turn."""
|
|
270
|
+
from acp.helpers import update_agent_message_text
|
|
271
|
+
|
|
272
|
+
final_text = self._extract_result_text(result)
|
|
273
|
+
if not final_text:
|
|
274
|
+
return
|
|
275
|
+
connection = state.get_connection()
|
|
276
|
+
if connection is None:
|
|
277
|
+
return
|
|
278
|
+
try:
|
|
279
|
+
await connection.session_update(
|
|
280
|
+
self.session_id, update_agent_message_text(final_text)
|
|
281
|
+
)
|
|
282
|
+
except Exception: # noqa: BLE001
|
|
283
|
+
logger.exception("ACP: failed to send final result")
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def _extract_result_text(result: Any) -> str:
|
|
287
|
+
"""Pull display text out of a pydantic-ai run result.
|
|
288
|
+
|
|
289
|
+
Mirrors ``_runtime``'s output extraction: prefer ``output``, then the
|
|
290
|
+
legacy ``data`` attribute, then ``str()``.
|
|
291
|
+
"""
|
|
292
|
+
for attr in ("output", "data"):
|
|
293
|
+
value = getattr(result, attr, None)
|
|
294
|
+
if value:
|
|
295
|
+
return str(value)
|
|
296
|
+
return str(result) if result else ""
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _to_acp_usage(result: Any) -> Optional[Usage]:
|
|
300
|
+
"""Map a pydantic-ai run result's usage onto ACP's ``Usage`` model.
|
|
301
|
+
|
|
302
|
+
Field names have drifted across pydantic-ai versions, so each ACP field is
|
|
303
|
+
resolved from a list of candidate attributes. Returns ``None`` when no
|
|
304
|
+
usage is available so we simply omit it from the response.
|
|
305
|
+
"""
|
|
306
|
+
if result is None:
|
|
307
|
+
return None
|
|
308
|
+
try:
|
|
309
|
+
usage = result.usage()
|
|
310
|
+
except Exception: # noqa: BLE001
|
|
311
|
+
return None
|
|
312
|
+
if usage is None:
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
def pick(*names: str) -> Optional[int]:
|
|
316
|
+
for name in names:
|
|
317
|
+
value = getattr(usage, name, None)
|
|
318
|
+
if value is not None:
|
|
319
|
+
return int(value)
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
return Usage(
|
|
323
|
+
input_tokens=pick("input_tokens", "request_tokens", "prompt_tokens"),
|
|
324
|
+
output_tokens=pick("output_tokens", "response_tokens", "completion_tokens"),
|
|
325
|
+
total_tokens=pick("total_tokens"),
|
|
326
|
+
cached_read_tokens=pick("cache_read_tokens", "cached_read_tokens"),
|
|
327
|
+
cached_write_tokens=pick("cache_write_tokens", "cached_write_tokens"),
|
|
328
|
+
thought_tokens=pick("thinking_tokens", "thought_tokens", "reasoning_tokens"),
|
|
329
|
+
)
|