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,138 @@
|
|
|
1
|
+
"""Register callbacks for the ``steer_queue`` plugin.
|
|
2
|
+
|
|
3
|
+
Mid-run Enter now QUEUES by default (see ``line_editor``); this plugin
|
|
4
|
+
supplies the rest of the feature:
|
|
5
|
+
|
|
6
|
+
* ``/steer <text>`` -- inject guidance mid-turn, interrupting the
|
|
7
|
+
agent's current train of thought ASAP (the old Enter default). At
|
|
8
|
+
idle it's a no-op with a hint: just type at the prompt.
|
|
9
|
+
* ``/queue`` -- full-screen queue manager with prompt preview, multiline
|
|
10
|
+
add/edit, guarded delete, and reordering. Works at idle and mid-run: both
|
|
11
|
+
paths already release the terminal around command execution.
|
|
12
|
+
* Status suffix -- a ``(N queued)`` tag rides the bottom bar's status
|
|
13
|
+
row, updated live via the PauseController's steer-queue listeners
|
|
14
|
+
(submit, drain, TUI edits -- every mutation fires them).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from typing import List, Optional, Tuple
|
|
21
|
+
|
|
22
|
+
from code_puppy.callbacks import register_callback
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
_STEER = "steer"
|
|
27
|
+
_QUEUE = "queue"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _emit_info(message: str) -> None:
|
|
31
|
+
from code_puppy.messaging import emit_info
|
|
32
|
+
|
|
33
|
+
emit_info(message)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _emit_warning(message: str) -> None:
|
|
37
|
+
from code_puppy.messaging import emit_warning
|
|
38
|
+
|
|
39
|
+
emit_warning(message)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# /steer
|
|
43
|
+
def _handle_steer(command: str) -> bool:
|
|
44
|
+
text = command.split(" ", 1)[1].strip() if " " in command else ""
|
|
45
|
+
if not text:
|
|
46
|
+
_emit_info("Usage: /steer <message> \u2014 inject guidance mid-turn")
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
from code_puppy.messaging.run_ui import is_run_active
|
|
50
|
+
|
|
51
|
+
if not is_run_active():
|
|
52
|
+
_emit_warning(
|
|
53
|
+
"No agent run in flight \u2014 just type your prompt; "
|
|
54
|
+
"/steer only makes sense while the agent is working."
|
|
55
|
+
)
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
from code_puppy.messaging.pause_controller import get_pause_controller
|
|
59
|
+
|
|
60
|
+
# ``mode='now'`` is announced by the history processor; no duplicate ack.
|
|
61
|
+
get_pause_controller().request_steer(text, mode="now")
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# /queue
|
|
66
|
+
def _handle_queue(command: str) -> bool:
|
|
67
|
+
from .queue_menu import open_queue_menu_blocking
|
|
68
|
+
|
|
69
|
+
open_queue_menu_blocking()
|
|
70
|
+
_emit_queue_summary()
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _emit_queue_summary() -> None:
|
|
75
|
+
from code_puppy.messaging.pause_controller import get_pause_controller
|
|
76
|
+
|
|
77
|
+
count = len(get_pause_controller().peek_pending_steer_queued())
|
|
78
|
+
if count:
|
|
79
|
+
_emit_info(f"\u23ed {count} prompt(s) queued")
|
|
80
|
+
else:
|
|
81
|
+
_emit_info("Queue is empty")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# Status suffix: '(N queued)'
|
|
85
|
+
def _update_status_suffix(count: int) -> None:
|
|
86
|
+
"""Steer-queue listener: paint/clear the pending-count tag.
|
|
87
|
+
|
|
88
|
+
``count`` is the TOTAL across both queues (now + queued), so the
|
|
89
|
+
suffix is on whenever EITHER queue holds something. ``/steer`` uses
|
|
90
|
+
``mode="now"``; that steer sits in the now-queue until the history
|
|
91
|
+
processor drains it, so the suffix tags the whole window between
|
|
92
|
+
submit and injection.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
from code_puppy.messaging.bottom_bar import get_bottom_bar
|
|
96
|
+
|
|
97
|
+
get_bottom_bar().set_status_suffix(f" ({count} pending)" if count else "")
|
|
98
|
+
except Exception:
|
|
99
|
+
logger.debug("pending-count paint failed", exc_info=True)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _on_startup() -> None:
|
|
103
|
+
try:
|
|
104
|
+
from code_puppy.messaging.pause_controller import get_pause_controller
|
|
105
|
+
|
|
106
|
+
get_pause_controller().add_steer_queue_listener(_update_status_suffix)
|
|
107
|
+
except Exception:
|
|
108
|
+
logger.debug("steer_queue startup failed", exc_info=True)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Registration
|
|
112
|
+
def _handle_custom_command(command: str, name: str) -> Optional[bool]:
|
|
113
|
+
if name == _STEER:
|
|
114
|
+
return _handle_steer(command)
|
|
115
|
+
if name == _QUEUE:
|
|
116
|
+
return _handle_queue(command)
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _custom_help() -> List[Tuple[str, str]]:
|
|
121
|
+
return [
|
|
122
|
+
(_STEER, "Inject guidance mid-turn while the agent is running"),
|
|
123
|
+
(_QUEUE, "Manage queued prompts in a full-screen TUI"),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
register_callback("startup", _on_startup)
|
|
128
|
+
register_callback("custom_command", _handle_custom_command)
|
|
129
|
+
register_callback("custom_command_help", _custom_help)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
__all__ = [
|
|
133
|
+
"_handle_custom_command",
|
|
134
|
+
"_handle_queue",
|
|
135
|
+
"_handle_steer",
|
|
136
|
+
"_on_startup",
|
|
137
|
+
"_update_status_suffix",
|
|
138
|
+
]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# subagent_panel
|
|
2
|
+
|
|
3
|
+
A live, two-line status block for each running sub-agent, painted just above the
|
|
4
|
+
bouncing puppy:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
INVOKE AGENT pup-ticket-investigator claude-4-8-opus
|
|
8
|
+
(spin) 00:19 calling read_file
|
|
9
|
+
INVOKE AGENT pup-analysis-adjudicator
|
|
10
|
+
(spin) 00:25 calling grep
|
|
11
|
+
|
|
12
|
+
Milo is thinking... ( o )
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- Line 1 is the `INVOKE AGENT` banner (same color/styling as core).
|
|
16
|
+
- Line 2 is a single-char animated spinner + `mm:ss` elapsed + the current
|
|
17
|
+
activity, color-coded: **yellow** = calling a tool, **magenta** = thinking,
|
|
18
|
+
**green** = writing the response.
|
|
19
|
+
- Parallel sub-agents stack, each with its own live row. There is no arbitrary
|
|
20
|
+
cap: every tracked agent renders as long as it fits. The row count is only
|
|
21
|
+
clamped to the terminal height (the prompt + status always keep their rows) --
|
|
22
|
+
if a swarm is taller than the viewport, the rows that don't fit collapse into a
|
|
23
|
+
single `… +N more` summary rather than blanking the whole bar.
|
|
24
|
+
- **Nested sub-agents render as a true tree.** A sub-agent that itself calls
|
|
25
|
+
`invoke_agent` is shown indented under its parent. Only top-level (depth-0)
|
|
26
|
+
agents get the full two-line `INVOKE AGENT` banner; deeper agents collapse to
|
|
27
|
+
a compact one-liner -- `<elbow> name mm:ss purpose model` -- so the banner
|
|
28
|
+
text + model label aren't repeated at every level:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
INVOKE AGENT regression-runner gpt-5.5
|
|
32
|
+
(spin) 00:12 calling invoke_agent
|
|
33
|
+
\u2514\u2500 pup-ticket-investigator 00:12 calling read_file claude-4-8-opus
|
|
34
|
+
\u2514\u2500 deep-helper 00:12 thinking... gpt-5.5-mini
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The parent link is captured at emit time from an async-safe `ContextVar`
|
|
38
|
+
(`_PARENT_SID`), NOT the bus's single global `_current_session_id`. The
|
|
39
|
+
global is shared across all asyncio tasks, so two `invoke_agent` calls running
|
|
40
|
+
concurrently would clobber it and mis-parent one root under the other (a bogus
|
|
41
|
+
deep chain). A `ContextVar` is copied into each task at `create_task` time, so
|
|
42
|
+
concurrent siblings each see their own correct parent -- see
|
|
43
|
+
`_install_parent_tracking` (mirrors `set_session_context`) + `_install_emit_hook`.
|
|
44
|
+
- **On completion** the block doesn't just vanish: a persistent frozen record
|
|
45
|
+
is printed to the transcript mirroring the live look, with line 2 finalized
|
|
46
|
+
as a green check + `mm:ss completed`:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
INVOKE AGENT pup-ticket-investigator claude-4-8-opus
|
|
50
|
+
(check) 00:45 completed
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
(The redundant core "<check> <name> completed successfully" line is suppressed.)
|
|
54
|
+
Nested agents get a compact one-liner frozen record instead of the full block:
|
|
55
|
+
`<elbow> name (check) mm:ss completed model`.
|
|
56
|
+
|
|
57
|
+
## How it works (3 monkeypatches, 1 callback -- no core edits)
|
|
58
|
+
|
|
59
|
+
The puppy spinner already runs a Rich `Live` that repaints ~20x/second. Rather
|
|
60
|
+
than spin up a second `Live` (which fights the puppy's -- the reason the
|
|
61
|
+
built-in `SubAgentConsoleManager` is dead code), this plugin reuses it:
|
|
62
|
+
|
|
63
|
+
1. **`ConsoleSpinner._generate_spinner_panel`** -> returns
|
|
64
|
+
`Group(status block..., "", puppy)` so the existing loop renders everything.
|
|
65
|
+
2. **`RichConsoleRenderer._render_subagent_invocation`** -> *captures* the exact
|
|
66
|
+
metadata (name / session_type / model / **session_id**) and *suppresses* the
|
|
67
|
+
permanent banner. The live block now owns the banner, so there's no
|
|
68
|
+
duplicate, and registration is exact even for parallel sub-agents (no
|
|
69
|
+
session-id guessing).
|
|
70
|
+
3. **`RichConsoleRenderer._do_render`** -> when the `SubAgentResponseMessage`
|
|
71
|
+
arrives (core skips it), print the persistent frozen two-line record + pop
|
|
72
|
+
the live row, and suppress the redundant completion-text line.
|
|
73
|
+
|
|
74
|
+
Status text is fed by the **`stream_event`** callback, keyed by `session_id`.
|
|
75
|
+
`record_event` is *update-only*: an event for an unregistered session is ignored,
|
|
76
|
+
which cleanly filters out the main agent's own stream events. `coalesce_patch.py`
|
|
77
|
+
batches sub-agent stream-event callbacks (~50ms) so parallel token streams do
|
|
78
|
+
not starve the steer overlay's event-loop continuations.
|
|
79
|
+
|
|
80
|
+
## Config
|
|
81
|
+
|
|
82
|
+
Runtime toggle:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
/set subagent_panel off
|
|
86
|
+
/set subagent_panel on
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Environment startup hard-disable:
|
|
90
|
+
|
|
91
|
+
| Var | Default | Meaning |
|
|
92
|
+
|-----|---------|---------|
|
|
93
|
+
| DISABLE_SUBAGENT_PANEL | unset | Set 1 to skip registration entirely |
|
|
94
|
+
| SUBAGENT_PANEL | 1 | Set 0 to skip registration entirely |
|
|
95
|
+
| SUBAGENT_PANEL_IDLE_S | 600 | Prune after this many idle seconds (safety net) |
|
|
96
|
+
|
|
97
|
+
## Notes
|
|
98
|
+
|
|
99
|
+
- Installs wrappers at **startup**, but `/set subagent_panel off` makes them pass through live.
|
|
100
|
+
- The permanent invocation banner (with Prompt/Session) is intentionally
|
|
101
|
+
replaced by the transient live block; on completion a compact frozen
|
|
102
|
+
two-line record (banner + green check) is left in the transcript.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""subagent_panel -- live two-line status per sub-agent above the puppy.
|
|
2
|
+
|
|
3
|
+
Renders, inside the puppy spinner's own Rich Live region, a block per active
|
|
4
|
+
sub-agent::
|
|
5
|
+
|
|
6
|
+
INVOKE AGENT <name> <model>
|
|
7
|
+
<spin> 00:19 calling read_file
|
|
8
|
+
|
|
9
|
+
<bouncing puppy>
|
|
10
|
+
|
|
11
|
+
Line 1 is the INVOKE AGENT banner (core styling); line 2 is a single-char
|
|
12
|
+
animated spinner + mm:ss elapsed + a color-coded status (yellow=calling,
|
|
13
|
+
magenta=thinking, green=writing). Parallel sub-agents stack.
|
|
14
|
+
|
|
15
|
+
It reuses the puppy spinner's existing Rich ``Live`` (a monkeypatch of
|
|
16
|
+
``_generate_spinner_panel``) rather than a second ``Live`` -- two Live displays
|
|
17
|
+
on one console fight each other (which is why the built-in, fully-built but
|
|
18
|
+
never-wired ``SubAgentConsoleManager`` was dead code). Exact per-agent metadata
|
|
19
|
+
is captured by monkeypatching ``_render_subagent_invocation`` (which also
|
|
20
|
+
suppresses the now-redundant permanent banner). On completion, ``_do_render``
|
|
21
|
+
is wrapped to print a persistent frozen two-line record (banner + green check +
|
|
22
|
+
mm:ss completed) and suppress the redundant core completion line. Status text is
|
|
23
|
+
fed by the ``stream_event`` callback. See register_callbacks.py + state.py.
|
|
24
|
+
"""
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Coalesce sub-agent stream_event callbacks from a user plugin.
|
|
2
|
+
|
|
3
|
+
PUP-301 briefly patched ``code_puppy.agents.subagent_stream_handler`` in core so
|
|
4
|
+
parallel sub-agents don't create one asyncio task per streamed token. That fixed
|
|
5
|
+
Ctrl+T steer-overlay lag under a sub-agent swarm, but the feature belongs in the
|
|
6
|
+
user plugin while this experiment stays out of the repo.
|
|
7
|
+
|
|
8
|
+
This module installs the same behavior as a small monkeypatch:
|
|
9
|
+
|
|
10
|
+
* replace ``subagent_stream_handler._fire_callback`` with a batched version;
|
|
11
|
+
* wrap ``subagent_stream_handler.subagent_stream_handler`` so stream-end flushes
|
|
12
|
+
any trailing events (especially ``part_end``);
|
|
13
|
+
* pass through to the original callback whenever ``subagent_panel`` is
|
|
14
|
+
runtime-disabled via ``/set subagent_panel off``.
|
|
15
|
+
|
|
16
|
+
All state is event-loop local in practice: core calls this from the stream
|
|
17
|
+
handler running on the main loop. No locks, no cute cleverness. Cute cleverness
|
|
18
|
+
is how puppies end up inside washing machines.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import importlib
|
|
25
|
+
import logging
|
|
26
|
+
import time
|
|
27
|
+
from collections.abc import Awaitable, Callable
|
|
28
|
+
from typing import Any, Optional
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
COALESCE_INTERVAL_S: float = 0.050
|
|
33
|
+
_MARKER = "_subagent_panel_coalesced"
|
|
34
|
+
|
|
35
|
+
Event = tuple[str, Any, Optional[str]]
|
|
36
|
+
EnabledChecker = Callable[[], bool]
|
|
37
|
+
FireCallback = Callable[[str, Any, Optional[str]], None]
|
|
38
|
+
|
|
39
|
+
_pending_events: list[Event] = []
|
|
40
|
+
_drain_scheduled = False
|
|
41
|
+
_last_drain_time = 0.0
|
|
42
|
+
_original_fire_callback: FireCallback | None = None
|
|
43
|
+
_original_stream_handler: Callable[..., Awaitable[None]] | None = None
|
|
44
|
+
_enabled_checker: EnabledChecker | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _enabled() -> bool:
|
|
48
|
+
if _enabled_checker is None:
|
|
49
|
+
return True
|
|
50
|
+
try:
|
|
51
|
+
return bool(_enabled_checker())
|
|
52
|
+
except Exception:
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _fire_original(event_type: str, event_data: Any, session_id: Optional[str]) -> None:
|
|
57
|
+
original = _original_fire_callback
|
|
58
|
+
if original is None:
|
|
59
|
+
return
|
|
60
|
+
try:
|
|
61
|
+
original(event_type, event_data, session_id)
|
|
62
|
+
except Exception as exc: # noqa: BLE001 - callback plumbing must not explode
|
|
63
|
+
logger.debug("Original stream_event callback failed: %s", exc)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _coalesced_fire_callback(
|
|
67
|
+
event_type: str,
|
|
68
|
+
event_data: Any,
|
|
69
|
+
session_id: Optional[str],
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Queue a stream-event callback for batched delivery.
|
|
72
|
+
|
|
73
|
+
When runtime-disabled, this delegates to the exact original core callback so
|
|
74
|
+
``/set subagent_panel off`` really behaves like vanilla Code Puppy.
|
|
75
|
+
"""
|
|
76
|
+
global _drain_scheduled, _last_drain_time
|
|
77
|
+
|
|
78
|
+
if not _enabled():
|
|
79
|
+
_fire_original(event_type, event_data, session_id)
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
_pending_events.append((event_type, event_data, session_id))
|
|
83
|
+
if _drain_scheduled:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
loop = asyncio.get_running_loop()
|
|
88
|
+
except RuntimeError:
|
|
89
|
+
clear_pending()
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
now = time.monotonic()
|
|
93
|
+
elapsed = now - _last_drain_time
|
|
94
|
+
_drain_scheduled = True
|
|
95
|
+
if elapsed >= COALESCE_INTERVAL_S:
|
|
96
|
+
loop.call_soon(_drain_pending_task, loop)
|
|
97
|
+
else:
|
|
98
|
+
loop.call_later(COALESCE_INTERVAL_S - elapsed, _drain_pending_task, loop)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _drain_pending_task(loop: asyncio.AbstractEventLoop) -> None:
|
|
102
|
+
"""Drain pending events as one scheduled asyncio task."""
|
|
103
|
+
global _drain_scheduled, _last_drain_time
|
|
104
|
+
|
|
105
|
+
_drain_scheduled = False
|
|
106
|
+
_last_drain_time = time.monotonic()
|
|
107
|
+
if not _pending_events:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
batch = list(_pending_events)
|
|
111
|
+
_pending_events.clear()
|
|
112
|
+
loop.create_task(_fire_batch(batch))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def _fire_batch(batch: list[Event]) -> None:
|
|
116
|
+
try:
|
|
117
|
+
from code_puppy import callbacks
|
|
118
|
+
except ImportError:
|
|
119
|
+
logger.debug("Callbacks module not available for stream event")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
for event_type, event_data, session_id in batch:
|
|
123
|
+
try:
|
|
124
|
+
await callbacks.on_stream_event(event_type, event_data, session_id)
|
|
125
|
+
except Exception as exc: # noqa: BLE001 - match core's fail-safe behavior
|
|
126
|
+
logger.debug("Error in stream_event callback: %s", exc)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def flush_pending_callbacks() -> None:
|
|
130
|
+
"""Synchronously drain any queued events.
|
|
131
|
+
|
|
132
|
+
The stream-handler wrapper calls this in ``finally`` so terminal events do
|
|
133
|
+
not sit behind the coalesce timer after a sub-agent stream completes.
|
|
134
|
+
"""
|
|
135
|
+
global _drain_scheduled, _last_drain_time
|
|
136
|
+
|
|
137
|
+
_drain_scheduled = False
|
|
138
|
+
_last_drain_time = time.monotonic()
|
|
139
|
+
if not _pending_events:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
batch = list(_pending_events)
|
|
143
|
+
_pending_events.clear()
|
|
144
|
+
await _fire_batch(batch)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def clear_pending() -> None:
|
|
148
|
+
"""Drop pending events without firing them; used only for abnormal no-loop paths."""
|
|
149
|
+
global _drain_scheduled
|
|
150
|
+
|
|
151
|
+
_pending_events.clear()
|
|
152
|
+
_drain_scheduled = False
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _make_stream_handler_wrapper(original: Callable[..., Awaitable[None]]):
|
|
156
|
+
async def _wrapped(*args: Any, **kwargs: Any) -> None:
|
|
157
|
+
try:
|
|
158
|
+
await original(*args, **kwargs)
|
|
159
|
+
finally:
|
|
160
|
+
await flush_pending_callbacks()
|
|
161
|
+
|
|
162
|
+
setattr(_wrapped, _MARKER, True)
|
|
163
|
+
setattr(_wrapped, "_subagent_panel_original_stream_handler", original)
|
|
164
|
+
return _wrapped
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def install(enabled_checker: EnabledChecker | None = None) -> bool:
|
|
168
|
+
"""Install the coalescing monkeypatch idempotently.
|
|
169
|
+
|
|
170
|
+
Returns ``True`` when installed/already installed, ``False`` if the core
|
|
171
|
+
module is not importable. Import failure is okay during lightweight tests.
|
|
172
|
+
"""
|
|
173
|
+
global _enabled_checker, _original_fire_callback, _original_stream_handler
|
|
174
|
+
|
|
175
|
+
_enabled_checker = enabled_checker
|
|
176
|
+
try:
|
|
177
|
+
module = importlib.import_module("code_puppy.agents.subagent_stream_handler")
|
|
178
|
+
except Exception as exc: # noqa: BLE001 - plugin startup must be best-effort
|
|
179
|
+
logger.debug("Could not import subagent_stream_handler: %s", exc)
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
current_fire = getattr(module, "_fire_callback", None)
|
|
183
|
+
if current_fire is None:
|
|
184
|
+
return False
|
|
185
|
+
if not getattr(current_fire, _MARKER, False):
|
|
186
|
+
_original_fire_callback = current_fire
|
|
187
|
+
setattr(_coalesced_fire_callback, _MARKER, True)
|
|
188
|
+
setattr(
|
|
189
|
+
_coalesced_fire_callback,
|
|
190
|
+
"_subagent_panel_original_fire_callback",
|
|
191
|
+
current_fire,
|
|
192
|
+
)
|
|
193
|
+
module._fire_callback = _coalesced_fire_callback
|
|
194
|
+
elif _original_fire_callback is None:
|
|
195
|
+
_original_fire_callback = getattr(
|
|
196
|
+
current_fire, "_subagent_panel_original_fire_callback", None
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
current_handler = getattr(module, "subagent_stream_handler", None)
|
|
200
|
+
if current_handler is None:
|
|
201
|
+
return True
|
|
202
|
+
if not getattr(current_handler, _MARKER, False):
|
|
203
|
+
_original_stream_handler = current_handler
|
|
204
|
+
module.subagent_stream_handler = _make_stream_handler_wrapper(current_handler)
|
|
205
|
+
elif _original_stream_handler is None:
|
|
206
|
+
_original_stream_handler = getattr(
|
|
207
|
+
current_handler, "_subagent_panel_original_stream_handler", None
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def uninstall() -> None:
|
|
214
|
+
"""Restore original core callables when this plugin owns the patch."""
|
|
215
|
+
global _original_fire_callback, _original_stream_handler, _enabled_checker
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
module = importlib.import_module("code_puppy.agents.subagent_stream_handler")
|
|
219
|
+
except Exception:
|
|
220
|
+
clear_pending()
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
if getattr(getattr(module, "_fire_callback", None), _MARKER, False):
|
|
224
|
+
if _original_fire_callback is not None:
|
|
225
|
+
module._fire_callback = _original_fire_callback
|
|
226
|
+
if getattr(getattr(module, "subagent_stream_handler", None), _MARKER, False):
|
|
227
|
+
if _original_stream_handler is not None:
|
|
228
|
+
module.subagent_stream_handler = _original_stream_handler
|
|
229
|
+
|
|
230
|
+
clear_pending()
|
|
231
|
+
_original_fire_callback = None
|
|
232
|
+
_original_stream_handler = None
|
|
233
|
+
_enabled_checker = None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
__all__ = [
|
|
237
|
+
"COALESCE_INTERVAL_S",
|
|
238
|
+
"clear_pending",
|
|
239
|
+
"flush_pending_callbacks",
|
|
240
|
+
"install",
|
|
241
|
+
"uninstall",
|
|
242
|
+
]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Rendering helpers for the sub-agent panel (live rows + transcript).
|
|
2
|
+
|
|
3
|
+
Pure presentation: tree ordering, model-name shorthand, and the aligned
|
|
4
|
+
one-line-per-agent row renderer shared by the live bottom-bar panel and
|
|
5
|
+
the frozen transcript records. No I/O, no state mutation — that lives in
|
|
6
|
+
``register_callbacks`` / ``state``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from . import state
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _banner_color():
|
|
16
|
+
try:
|
|
17
|
+
from code_puppy.config import get_banner_color
|
|
18
|
+
|
|
19
|
+
return get_banner_color("invoke_agent")
|
|
20
|
+
except Exception:
|
|
21
|
+
return "blue"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Hierarchy helpers (true parent -> child tree)
|
|
25
|
+
def _ordered_tree(rows):
|
|
26
|
+
"""Return [(entry, depth), ...] in DFS order. A row whose parent is not in
|
|
27
|
+
the set (e.g. the main agent, or None) is a root (depth 0); descendants are
|
|
28
|
+
indented one-liners. Cycle-safe; stable by start time."""
|
|
29
|
+
by_id = {e["session_id"]: e for e in rows if e.get("session_id")}
|
|
30
|
+
children = {}
|
|
31
|
+
roots = []
|
|
32
|
+
for e in rows:
|
|
33
|
+
p = e.get("parent")
|
|
34
|
+
if p and p in by_id:
|
|
35
|
+
children.setdefault(p, []).append(e)
|
|
36
|
+
else:
|
|
37
|
+
roots.append(e)
|
|
38
|
+
out = []
|
|
39
|
+
seen = set()
|
|
40
|
+
|
|
41
|
+
def walk(node, depth):
|
|
42
|
+
sid = node.get("session_id")
|
|
43
|
+
if sid in seen:
|
|
44
|
+
return
|
|
45
|
+
seen.add(sid)
|
|
46
|
+
out.append((node, depth))
|
|
47
|
+
for kid in sorted(children.get(sid, []), key=lambda c: c["start"]):
|
|
48
|
+
walk(kid, depth + 1)
|
|
49
|
+
|
|
50
|
+
for r in sorted(roots, key=lambda e: e["start"]):
|
|
51
|
+
walk(r, 0)
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _model_short(model):
|
|
56
|
+
"""Return the model identifier verbatim for the panel display.
|
|
57
|
+
|
|
58
|
+
Deliberately a passthrough: whatever the user picked with ``/model <key>``
|
|
59
|
+
is what shows up in the subagent panel. No parsing, no title-casing, no
|
|
60
|
+
tier/env extraction -- previous versions built a curated shorthand from
|
|
61
|
+
a hard-coded allowlist of tier tokens (nano/mini/flash/...) plus an env
|
|
62
|
+
allowlist ('stage'), which silently dropped any suffix outside the lists
|
|
63
|
+
(e.g. -stage). Users would rather see the raw config key and trust their
|
|
64
|
+
own eyes; that also removes an allowlist maintenance chore forever.
|
|
65
|
+
Empty / None inputs collapse to an empty string so the panel column
|
|
66
|
+
doesn't render the literal 'None'.
|
|
67
|
+
"""
|
|
68
|
+
return str(model) if model else ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _row_lines(ordered, frame):
|
|
72
|
+
"""Render a list of (entry, depth) as aligned single-line rows:
|
|
73
|
+
<prefix><name> <model> <spin|check> <mm:ss>
|
|
74
|
+
The model + indicator + time columns share a per-tree tab-stop computed
|
|
75
|
+
from the widest (prefix+name) AND the widest model label, so longer model
|
|
76
|
+
names (e.g. 'GPT 5.4-Nano') and deeper-indented names both push the whole
|
|
77
|
+
right block over together -- columns stay aligned no matter what gets added.
|
|
78
|
+
Alignment is done purely with U+0020 spaces (never literal tabs), and widths
|
|
79
|
+
use Rich cell_len, so the layout renders identically on Windows and macOS.
|
|
80
|
+
Root rows carry the INVOKE AGENT badge; nested rows carry the tree elbow.
|
|
81
|
+
Used for BOTH the live block and the transcript.
|
|
82
|
+
"""
|
|
83
|
+
from rich.cells import cell_len
|
|
84
|
+
from rich.text import Text
|
|
85
|
+
|
|
86
|
+
color = _banner_color()
|
|
87
|
+
lefts = []
|
|
88
|
+
models = []
|
|
89
|
+
name_w = 0
|
|
90
|
+
model_w = 0
|
|
91
|
+
for e, depth in ordered:
|
|
92
|
+
left = Text(no_wrap=True, overflow="ellipsis")
|
|
93
|
+
if depth == 0:
|
|
94
|
+
left.append(" \U0001f916 INVOKE AGENT ", style=f"bold white on {color}")
|
|
95
|
+
left.append(" ")
|
|
96
|
+
left.append(e["name"], style="bold cyan")
|
|
97
|
+
else:
|
|
98
|
+
left.append(" " + " " * (depth - 1))
|
|
99
|
+
left.append("\u2514\u2500 ", style="grey50") # tree elbow
|
|
100
|
+
left.append(e["name"], style="bold cyan")
|
|
101
|
+
lefts.append(left)
|
|
102
|
+
ms = _model_short(e.get("model"))
|
|
103
|
+
models.append(ms)
|
|
104
|
+
name_w = max(name_w, left.cell_len)
|
|
105
|
+
model_w = max(model_w, cell_len(ms))
|
|
106
|
+
|
|
107
|
+
lines = []
|
|
108
|
+
for (e, depth), left, ms in zip(ordered, lefts, models):
|
|
109
|
+
done = bool(e.get("done"))
|
|
110
|
+
failed = bool(e.get("failed"))
|
|
111
|
+
line = left.copy()
|
|
112
|
+
# Keep status rows single-line; crop visually for the terminal but retain
|
|
113
|
+
# full status in state.
|
|
114
|
+
line.no_wrap = True
|
|
115
|
+
line.overflow = "ellipsis"
|
|
116
|
+
line.append(" " * (name_w - left.cell_len + 2))
|
|
117
|
+
line.append(ms, style="magenta")
|
|
118
|
+
line.append(" " * (model_w - cell_len(ms) + 2))
|
|
119
|
+
if failed:
|
|
120
|
+
line.append("\u2717 ", style="bold red") # X mark
|
|
121
|
+
elif done:
|
|
122
|
+
line.append("\u2713 ", style="bold green") # check
|
|
123
|
+
else:
|
|
124
|
+
line.append((frame or " ") + " ", style="bold cyan")
|
|
125
|
+
line.append(state.fmt_elapsed_entry(e), style="dim")
|
|
126
|
+
# Current action / status, color-coded (yellow=calling, magenta=thinking,
|
|
127
|
+
# green=writing). Done rows show 'completed' green; failed rows 'failed' red.
|
|
128
|
+
status = e.get("status", "starting")
|
|
129
|
+
line.append(" ")
|
|
130
|
+
if failed:
|
|
131
|
+
line.append("failed", style="bold red")
|
|
132
|
+
elif done:
|
|
133
|
+
line.append("completed", style="green")
|
|
134
|
+
else:
|
|
135
|
+
line.append(status, style=state.status_style(status))
|
|
136
|
+
lines.append(line)
|
|
137
|
+
return lines
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
__all__ = [
|
|
141
|
+
"_banner_color",
|
|
142
|
+
"_model_short",
|
|
143
|
+
"_ordered_tree",
|
|
144
|
+
"_row_lines",
|
|
145
|
+
]
|