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,168 @@
|
|
|
1
|
+
# herdr integration
|
|
2
|
+
|
|
3
|
+
Makes code-puppy a first-class citizen in
|
|
4
|
+
[**herdr**](https://herdr.dev), a terminal workspace manager for coding
|
|
5
|
+
agents. When you run several agents at once, herdr's sidebar rolls each
|
|
6
|
+
one up to a single glanceable state -- who's **working**, who's
|
|
7
|
+
**blocked** waiting on you, and who's **done** -- so you always know
|
|
8
|
+
which pane needs attention.
|
|
9
|
+
|
|
10
|
+
This plugin teaches code-puppy to report that state authoritatively.
|
|
11
|
+
|
|
12
|
+
## What it does
|
|
13
|
+
|
|
14
|
+
herdr injects three environment variables into every pane it owns:
|
|
15
|
+
|
|
16
|
+
| variable | meaning |
|
|
17
|
+
| ------------------- | ----------------------------------------- |
|
|
18
|
+
| `HERDR_ENV=1` | this shell is running inside a herdr pane |
|
|
19
|
+
| `HERDR_SOCKET_PATH` | herdr's local control socket |
|
|
20
|
+
| `HERDR_PANE_ID` | the pane this process owns (e.g. `w1:p1`) |
|
|
21
|
+
|
|
22
|
+
On startup the plugin checks for those. If they're absent it does
|
|
23
|
+
**nothing** -- zero overhead, zero output, no behaviour change, no socket,
|
|
24
|
+
no worker thread. If they're present it opens a background reporter that
|
|
25
|
+
reports code-puppy's state **authoritatively**: herdr never has to infer
|
|
26
|
+
it from the screen.
|
|
27
|
+
|
|
28
|
+
## State is authoritative
|
|
29
|
+
|
|
30
|
+
State is a pure function of two facts the plugin observes directly:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
blocked if awaiting the human
|
|
34
|
+
working elif a run is in flight (run_depth > 0)
|
|
35
|
+
idle otherwise
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
* **run depth** comes from `agent_run_start` / `agent_run_end`, refcounted
|
|
39
|
+
so a finishing sub-agent doesn't flip the pane `idle` mid-turn (the same
|
|
40
|
+
pattern the `puppy_spinner` plugin uses).
|
|
41
|
+
* **awaiting** comes from the `awaiting_user_input` callback, which fires
|
|
42
|
+
from the single process-wide choke-point
|
|
43
|
+
(`command_runner.set_awaiting_user_input`) that *every* interactive wait
|
|
44
|
+
already passes through -- shell-command approval, file-permission
|
|
45
|
+
approval, `ask_user_question`, and every menu/picker. One hook captures
|
|
46
|
+
every block, so there is nothing left for herdr to guess.
|
|
47
|
+
|
|
48
|
+
User-initiated menus carry `notify=False`; the plugin suppresses their
|
|
49
|
+
`blocked` report entirely so quick pickers don't spam attention.
|
|
50
|
+
|
|
51
|
+
## Activity text is best-effort
|
|
52
|
+
|
|
53
|
+
Alongside the authoritative state, the plugin attaches a short activity
|
|
54
|
+
`message` (`thinking`, `running <tool>`, `awaiting input`) driven by
|
|
55
|
+
`pre_tool_call` / `post_tool_call`. This is decorative: it rides a separate
|
|
56
|
+
lane, deduplicates on the `(state, message)` pair, and can **never** delay
|
|
57
|
+
or override the authoritative state, the session reference, or the final
|
|
58
|
+
release. If a hook-blocked tool misses its completion callback, the next
|
|
59
|
+
run / tool / wait / turn event corrects the message; state stays correct
|
|
60
|
+
regardless.
|
|
61
|
+
|
|
62
|
+
## Pane metadata (model / context / tokens)
|
|
63
|
+
|
|
64
|
+
At the end of every interactive turn the plugin also reports pane
|
|
65
|
+
metadata via `pane.report_metadata`: the current model, context-window
|
|
66
|
+
fill percentage, and a compact token count. herdr stores these under the
|
|
67
|
+
pane with a 24h TTL (so stale numbers self-clear after an abrupt exit),
|
|
68
|
+
but it only *renders* them when your sidebar layout references the
|
|
69
|
+
matching `$name` fields. Add them to `rows_by_agent` in your herdr
|
|
70
|
+
config:
|
|
71
|
+
|
|
72
|
+
```toml
|
|
73
|
+
[ui.sidebar.agents.rows_by_agent]
|
|
74
|
+
codepuppy = [
|
|
75
|
+
["state_icon", "workspace", "tab"],
|
|
76
|
+
["agent", "$model"],
|
|
77
|
+
["$context", "$tokens"],
|
|
78
|
+
]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The payload uses static keys (`model`, `context`, `tokens`) with
|
|
82
|
+
string values and no indicator glyphs -- herdr rows already carry their
|
|
83
|
+
own state icons. If context usage can't be computed for a turn, no
|
|
84
|
+
metadata is sent and the pane keeps its last good values until the TTL
|
|
85
|
+
expires.
|
|
86
|
+
|
|
87
|
+
## Session reference
|
|
88
|
+
|
|
89
|
+
On each prompt the plugin reports a **stable** session reference (the
|
|
90
|
+
durable autosave name and pickle path) via `pane.report_agent_session` --
|
|
91
|
+
not the per-run `group_id`, which changes every turn. It re-reports only
|
|
92
|
+
when the reference actually changes (after `/clear`, `/session new`,
|
|
93
|
+
`/autosave_load`, `/load_context`, a quick resume, or an agent switch).
|
|
94
|
+
This is a stable *reference*; automatic process restoration from it is
|
|
95
|
+
**unverified** and not claimed here.
|
|
96
|
+
|
|
97
|
+
## Notifications are herdr's job
|
|
98
|
+
|
|
99
|
+
The plugin sends no notifications itself. herdr derives attention and
|
|
100
|
+
completion notifications from the agent-state transitions the plugin
|
|
101
|
+
reports, and herdr's own toast / sound settings control delivery. This
|
|
102
|
+
plugin's only job is to report accurate state.
|
|
103
|
+
|
|
104
|
+
## Release on exit
|
|
105
|
+
|
|
106
|
+
On `session_end` / `shutdown` the plugin calls `pane.release_agent` once
|
|
107
|
+
(idempotent, bounded) so herdr knows code-puppy has let go of the pane --
|
|
108
|
+
no lingering stale `working`. There is no intermediate `idle` report on
|
|
109
|
+
shutdown; if herdr is unavailable the release is bounded and can never
|
|
110
|
+
delay process exit.
|
|
111
|
+
|
|
112
|
+
## No install needed on the herdr side
|
|
113
|
+
|
|
114
|
+
Because this plugin ships with code-puppy and self-activates inside a
|
|
115
|
+
pane, there is nothing to run -- `herdr integration install` is **not**
|
|
116
|
+
required for code-puppy. Just start code-puppy inside herdr:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
herdr # start / attach herdr
|
|
120
|
+
code-puppy # (or: pup) -- herdr picks up its state automatically
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
herdr also recognises the `code-puppy` / `pup` process on its own, so even
|
|
124
|
+
with this plugin disabled you still get basic detection. The plugin
|
|
125
|
+
upgrades that from screen-scraped guessing to authoritative, event-driven
|
|
126
|
+
state, metadata, and activity.
|
|
127
|
+
|
|
128
|
+
## Design notes
|
|
129
|
+
|
|
130
|
+
* **Never disturbs the agent.** All socket I/O happens on a daemon
|
|
131
|
+
worker thread; the sync file-permission and tool hot-paths just enqueue
|
|
132
|
+
and return. The tool observers always return `None`, so they can never
|
|
133
|
+
block or transform a tool call.
|
|
134
|
+
* **Critical vs decorative.** State edges, session references, and the
|
|
135
|
+
release ride a critical lane that always overtakes decorative traffic
|
|
136
|
+
(activity messages, metadata). Decorative saturation can never displace
|
|
137
|
+
a critical report.
|
|
138
|
+
* **Edge-triggered + deduped.** Only genuine `(state, message)` changes
|
|
139
|
+
hit the socket.
|
|
140
|
+
* **Fail-soft.** A missing/closed socket, a departed herdr, an
|
|
141
|
+
unresolvable source -- all are swallowed to the debug log. Reporting
|
|
142
|
+
your state is never worth crashing your agent over.
|
|
143
|
+
|
|
144
|
+
## Files
|
|
145
|
+
|
|
146
|
+
| file | responsibility |
|
|
147
|
+
| ----------------------- | ------------------------------------------- |
|
|
148
|
+
| `client.py` | herdr socket transport (JSON, worker thread)|
|
|
149
|
+
| `reporter.py` | event -> state machine (refcount + dedup) |
|
|
150
|
+
| `sources.py` | fail-soft adapters (tokens / session / msg) |
|
|
151
|
+
| `register_callbacks.py` | callback wiring + env activation guard |
|
|
152
|
+
| `smoke.py` | manual live smoke test (disposable pane) |
|
|
153
|
+
|
|
154
|
+
## Live smoke test
|
|
155
|
+
|
|
156
|
+
The unit tests (`tests/plugins/test_herdr_*.py`) are the CI contract. For
|
|
157
|
+
an end-to-end check against a real herdr server, run the manual smoke test
|
|
158
|
+
from inside a herdr pane:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
python -m code_puppy_core_plugins.herdr.smoke
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
It creates its **own** disposable pane, drives the real client through
|
|
165
|
+
every protocol-16 method (state edges, session reference, metadata,
|
|
166
|
+
release), reads each result back with `herdr pane get`, then closes the
|
|
167
|
+
disposable pane -- your working pane is never touched. Exits non-zero if
|
|
168
|
+
any check fails.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""herdr integration plugin for code-puppy.
|
|
2
|
+
|
|
3
|
+
Reports semantic agent state (working / blocked / idle) to a herdr pane
|
|
4
|
+
over herdr's local socket. Inert unless running inside herdr.
|
|
5
|
+
|
|
6
|
+
See ``register_callbacks.py`` for the wiring and ``README.md`` for docs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
r"""Fire-and-forget client for the herdr pane socket.
|
|
2
|
+
|
|
3
|
+
herdr (https://herdr.dev) is a terminal workspace manager for coding
|
|
4
|
+
agents. When code-puppy runs inside a herdr pane, herdr injects three
|
|
5
|
+
environment variables:
|
|
6
|
+
|
|
7
|
+
* ``HERDR_ENV=1`` -- marks the pane as herdr-managed
|
|
8
|
+
* ``HERDR_SOCKET_PATH`` -- path to herdr's local control socket
|
|
9
|
+
* ``HERDR_PANE_ID`` -- the pane this process owns (e.g. ``w1:p1``)
|
|
10
|
+
|
|
11
|
+
This module speaks herdr's newline-delimited JSON socket protocol far
|
|
12
|
+
enough to call ``pane.report_agent`` / ``pane.report_agent_session`` /
|
|
13
|
+
``pane.report_metadata`` / ``pane.release_agent``. It reads herdr's ack
|
|
14
|
+
(and retries a few times if it doesn't come) so an authoritative state
|
|
15
|
+
edge is never silently lost, but it never raises into the caller:
|
|
16
|
+
reporting agent state must never be able to disturb the agent itself.
|
|
17
|
+
|
|
18
|
+
Delivery runs on a single daemon worker thread fed by **coalescing
|
|
19
|
+
mailbox slots** rather than an unbounded queue:
|
|
20
|
+
|
|
21
|
+
* **Critical lane** -- state transitions, session references, and the
|
|
22
|
+
final release. The worker drains these first.
|
|
23
|
+
* **Decorative lane** -- message-only state refreshes and pane metadata.
|
|
24
|
+
These may be replaced by newer decorative work, and they are discarded
|
|
25
|
+
entirely once a release is scheduled.
|
|
26
|
+
|
|
27
|
+
Each slot keeps only its newest value, so memory is bounded and the pane
|
|
28
|
+
always converges on the latest authoritative facts. Request ``id`` and
|
|
29
|
+
``seq`` are assigned immediately before the first wire attempt, so wire
|
|
30
|
+
order stays monotonic even when critical work overtakes decorative work;
|
|
31
|
+
retries reuse the same serialized envelope (herdr dedupes on ``seq``).
|
|
32
|
+
|
|
33
|
+
Transport is ``AF_UNIX`` everywhere it exists. On Windows (where CPython
|
|
34
|
+
still has no ``socket.AF_UNIX``), herdr's build -- via Rust's
|
|
35
|
+
``interprocess`` crate -- maps the file-style ``HERDR_SOCKET_PATH`` onto a
|
|
36
|
+
named pipe whose name *is* the full path (``\\.\pipe\C:\...\herdr.sock``),
|
|
37
|
+
so plain stdlib file I/O on that pipe path gives the same bidirectional
|
|
38
|
+
byte stream. When neither transport is available the client is simply
|
|
39
|
+
inactive.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import json
|
|
45
|
+
import logging
|
|
46
|
+
import os
|
|
47
|
+
import socket
|
|
48
|
+
import threading
|
|
49
|
+
import time
|
|
50
|
+
from typing import Any, Dict, Optional, Tuple
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
#: source tag herdr attributes these reports to. The ``herdr:`` prefix is
|
|
55
|
+
#: the convention herdr uses for its own official integrations.
|
|
56
|
+
SOURCE = "herdr:codepuppy"
|
|
57
|
+
#: agent label; must match herdr's ``agent_label(Agent::CodePuppy)``.
|
|
58
|
+
AGENT = "codepuppy"
|
|
59
|
+
|
|
60
|
+
_CONNECT_TIMEOUT_S = 0.5
|
|
61
|
+
|
|
62
|
+
# Retry until herdr acks: a silently-dropped report strands the pane on a stale state.
|
|
63
|
+
# Retrying the *same* envelope is safe — herdr dedupes on ``seq``, so an already-applied
|
|
64
|
+
# report is harmlessly ignored.
|
|
65
|
+
_SEND_ATTEMPTS = 3
|
|
66
|
+
_SEND_BACKOFF_S = 0.05
|
|
67
|
+
_ACK_BYTES = 4096
|
|
68
|
+
|
|
69
|
+
#: Pane metadata TTL (24h) — bounds how long an abrupt process death can leave
|
|
70
|
+
#: stale token/model numbers on the sidebar.
|
|
71
|
+
_METADATA_TTL_MS = 86_400_000
|
|
72
|
+
|
|
73
|
+
# herdr request methods.
|
|
74
|
+
_M_STATE = "pane.report_agent"
|
|
75
|
+
_M_SESSION = "pane.report_agent_session"
|
|
76
|
+
_M_METADATA = "pane.report_metadata"
|
|
77
|
+
_M_RELEASE = "pane.release_agent"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class HerdrClient:
|
|
81
|
+
"""Coalescing, two-lane reporter drained on a single worker thread."""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
socket_path: Optional[str] = None,
|
|
86
|
+
pane_id: Optional[str] = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
self._socket_path = socket_path or os.environ.get("HERDR_SOCKET_PATH")
|
|
89
|
+
self._pane_id = pane_id or os.environ.get("HERDR_PANE_ID")
|
|
90
|
+
self._active = bool(
|
|
91
|
+
os.environ.get("HERDR_ENV") == "1"
|
|
92
|
+
and self._socket_path
|
|
93
|
+
and self._pane_id
|
|
94
|
+
and (hasattr(socket, "AF_UNIX") or os.name == "nt")
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# One condition guards every mailbox slot and the lifecycle flags.
|
|
98
|
+
self._cond = threading.Condition()
|
|
99
|
+
# Critical lane (latest-wins). ``None`` == empty.
|
|
100
|
+
self._state: Optional[Dict[str, Any]] = None
|
|
101
|
+
self._session: Optional[Dict[str, Any]] = None
|
|
102
|
+
# Decorative lane (latest-wins, discarded on release).
|
|
103
|
+
self._message: Optional[Dict[str, Any]] = None
|
|
104
|
+
self._metadata: Optional[Dict[str, Any]] = None
|
|
105
|
+
# Terminal release slot.
|
|
106
|
+
self._release: Optional[Dict[str, Any]] = None
|
|
107
|
+
|
|
108
|
+
self._closing = False
|
|
109
|
+
self._release_scheduled = False
|
|
110
|
+
self._released = threading.Event()
|
|
111
|
+
|
|
112
|
+
self._seq_lock = threading.Lock()
|
|
113
|
+
# Monotonic, process-unique sequence. herdr uses seq to discard
|
|
114
|
+
# out-of-order reports, so it must only ever increase.
|
|
115
|
+
self._seq = int(time.time() * 1000) * 1000
|
|
116
|
+
self._worker: Optional[threading.Thread] = None
|
|
117
|
+
if self._active:
|
|
118
|
+
self._start_worker()
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def active(self) -> bool:
|
|
122
|
+
return self._active
|
|
123
|
+
|
|
124
|
+
def _start_worker(self) -> None:
|
|
125
|
+
self._worker = threading.Thread(
|
|
126
|
+
target=self._run,
|
|
127
|
+
name="herdr-reporter",
|
|
128
|
+
daemon=True,
|
|
129
|
+
)
|
|
130
|
+
self._worker.start()
|
|
131
|
+
|
|
132
|
+
def _next_seq(self) -> int:
|
|
133
|
+
with self._seq_lock:
|
|
134
|
+
self._seq += 1
|
|
135
|
+
return self._seq
|
|
136
|
+
|
|
137
|
+
# -- public API ----------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def report_state(
|
|
140
|
+
self,
|
|
141
|
+
state: str,
|
|
142
|
+
agent_session_id: Optional[str] = None,
|
|
143
|
+
*,
|
|
144
|
+
message: Optional[str] = None,
|
|
145
|
+
critical: bool = True,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Report a semantic state (``working`` / ``blocked`` / ``idle``).
|
|
148
|
+
|
|
149
|
+
A genuine state *edge* is ``critical`` (default) and jumps the queue.
|
|
150
|
+
A message-only refresh (same state, new activity text) should pass
|
|
151
|
+
``critical=False`` so it rides the decorative lane and can never
|
|
152
|
+
delay an authoritative edge, session reference, or release.
|
|
153
|
+
"""
|
|
154
|
+
params: Dict[str, Any] = {"state": state}
|
|
155
|
+
if agent_session_id:
|
|
156
|
+
params["agent_session_id"] = agent_session_id
|
|
157
|
+
if message:
|
|
158
|
+
params["message"] = message
|
|
159
|
+
self._put("_state" if critical else "_message", params)
|
|
160
|
+
|
|
161
|
+
def report_session(
|
|
162
|
+
self,
|
|
163
|
+
agent_session_id: Optional[str] = None,
|
|
164
|
+
session_path: Optional[str] = None,
|
|
165
|
+
) -> None:
|
|
166
|
+
"""Report a stable session reference (critical lane)."""
|
|
167
|
+
params: Dict[str, Any] = {}
|
|
168
|
+
if agent_session_id:
|
|
169
|
+
params["agent_session_id"] = agent_session_id
|
|
170
|
+
if session_path:
|
|
171
|
+
params["agent_session_path"] = session_path
|
|
172
|
+
if not params:
|
|
173
|
+
return
|
|
174
|
+
self._put("_session", params)
|
|
175
|
+
|
|
176
|
+
def report_metadata(self, tokens: Dict[str, Any]) -> None:
|
|
177
|
+
"""Report pane metadata (model / context / tokens) on the decorative lane."""
|
|
178
|
+
if not tokens:
|
|
179
|
+
return
|
|
180
|
+
params: Dict[str, Any] = {
|
|
181
|
+
"applies_to_source": SOURCE,
|
|
182
|
+
"ttl_ms": _METADATA_TTL_MS,
|
|
183
|
+
"tokens": dict(tokens),
|
|
184
|
+
}
|
|
185
|
+
self._put("_metadata", params)
|
|
186
|
+
|
|
187
|
+
def _put(self, slot: str, params: Dict[str, Any]) -> None:
|
|
188
|
+
if not self._active:
|
|
189
|
+
return
|
|
190
|
+
with self._cond:
|
|
191
|
+
# Once a release is scheduled the pane is shutting down; refuse
|
|
192
|
+
# new work so the release stays the final wire event.
|
|
193
|
+
if self._closing:
|
|
194
|
+
return
|
|
195
|
+
setattr(self, slot, params)
|
|
196
|
+
self._cond.notify()
|
|
197
|
+
|
|
198
|
+
def release_and_close(self, timeout_s: float = 1.0) -> None:
|
|
199
|
+
"""Schedule one ``pane.release_agent`` and wait up to ``timeout_s``.
|
|
200
|
+
|
|
201
|
+
Idempotent: repeated calls schedule a single release. Stops
|
|
202
|
+
decorative input, discards pending decorative work, lets any pending
|
|
203
|
+
critical work flush, then releases. The wait is bounded on the
|
|
204
|
+
caller side, so an unavailable herdr can never exceed ``timeout_s``.
|
|
205
|
+
"""
|
|
206
|
+
if not self._active:
|
|
207
|
+
return
|
|
208
|
+
self._schedule_release()
|
|
209
|
+
self._released.wait(timeout=timeout_s)
|
|
210
|
+
|
|
211
|
+
def close(self) -> None:
|
|
212
|
+
"""Best-effort, non-blocking shutdown: schedule the release and return."""
|
|
213
|
+
if not self._active:
|
|
214
|
+
return
|
|
215
|
+
self._schedule_release()
|
|
216
|
+
|
|
217
|
+
def _schedule_release(self) -> None:
|
|
218
|
+
with self._cond:
|
|
219
|
+
if self._release_scheduled:
|
|
220
|
+
return
|
|
221
|
+
self._release_scheduled = True
|
|
222
|
+
self._closing = True
|
|
223
|
+
# Decorative work is not worth delaying shutdown for.
|
|
224
|
+
self._message = None
|
|
225
|
+
self._metadata = None
|
|
226
|
+
self._release = {} # release_agent needs only the base params
|
|
227
|
+
self._cond.notify()
|
|
228
|
+
|
|
229
|
+
# -- worker --------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
def _take_next_locked(self) -> Optional[Tuple[str, Dict[str, Any]]]:
|
|
232
|
+
"""Pop the next job by priority. Caller holds ``self._cond``.
|
|
233
|
+
|
|
234
|
+
Order: critical state -> critical session -> (decorative message ->
|
|
235
|
+
decorative metadata, only while not closing) -> terminal release.
|
|
236
|
+
Critical work therefore always overtakes decorative work, and the
|
|
237
|
+
release is only taken once the critical lane is drained.
|
|
238
|
+
"""
|
|
239
|
+
if self._state is not None:
|
|
240
|
+
params, self._state = self._state, None
|
|
241
|
+
return _M_STATE, params
|
|
242
|
+
if self._session is not None:
|
|
243
|
+
params, self._session = self._session, None
|
|
244
|
+
return _M_SESSION, params
|
|
245
|
+
if not self._closing:
|
|
246
|
+
if self._message is not None:
|
|
247
|
+
params, self._message = self._message, None
|
|
248
|
+
return _M_STATE, params
|
|
249
|
+
if self._metadata is not None:
|
|
250
|
+
params, self._metadata = self._metadata, None
|
|
251
|
+
return _M_METADATA, params
|
|
252
|
+
if self._closing and self._release is not None:
|
|
253
|
+
params, self._release = self._release, None
|
|
254
|
+
return _M_RELEASE, params
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
def _run(self) -> None:
|
|
258
|
+
while True:
|
|
259
|
+
with self._cond:
|
|
260
|
+
job = self._take_next_locked()
|
|
261
|
+
while job is None:
|
|
262
|
+
if self._closing:
|
|
263
|
+
# Nothing left and shutting down -> unblock waiters.
|
|
264
|
+
self._released.set()
|
|
265
|
+
return
|
|
266
|
+
self._cond.wait()
|
|
267
|
+
job = self._take_next_locked()
|
|
268
|
+
method, params = job
|
|
269
|
+
self._send(method, params)
|
|
270
|
+
if method == _M_RELEASE:
|
|
271
|
+
self._released.set()
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
def _send(self, method: str, params: Dict[str, Any]) -> None:
|
|
275
|
+
# Assign id + seq at wire time so critical work that overtook
|
|
276
|
+
# decorative work still gets a higher (later) sequence number.
|
|
277
|
+
seq = self._next_seq()
|
|
278
|
+
envelope = {
|
|
279
|
+
"id": f"{SOURCE}:{seq}",
|
|
280
|
+
"method": method,
|
|
281
|
+
"params": {
|
|
282
|
+
"pane_id": self._pane_id,
|
|
283
|
+
"source": SOURCE,
|
|
284
|
+
"agent": AGENT,
|
|
285
|
+
"seq": seq,
|
|
286
|
+
**params,
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
payload = (json.dumps(envelope) + "\n").encode("utf-8")
|
|
290
|
+
last_exc: Optional[Exception] = None
|
|
291
|
+
for attempt in range(_SEND_ATTEMPTS):
|
|
292
|
+
try:
|
|
293
|
+
if self._deliver(payload):
|
|
294
|
+
return
|
|
295
|
+
except (OSError, ValueError) as exc:
|
|
296
|
+
last_exc = exc
|
|
297
|
+
if attempt + 1 < _SEND_ATTEMPTS:
|
|
298
|
+
time.sleep(_SEND_BACKOFF_S)
|
|
299
|
+
# herdr may have exited or the pane was closed. Nothing left to do but
|
|
300
|
+
# note it on the diagnostic channel -- never raise into the agent.
|
|
301
|
+
logger.debug(
|
|
302
|
+
"herdr %s undelivered after %d attempts: %s",
|
|
303
|
+
method,
|
|
304
|
+
_SEND_ATTEMPTS,
|
|
305
|
+
last_exc,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def _deliver(self, payload: bytes) -> bool:
|
|
309
|
+
"""One wire attempt. ``True`` means herdr acked the report.
|
|
310
|
+
|
|
311
|
+
Any nonempty response is an ack: herdr already treats the report as
|
|
312
|
+
taken (even a busy ``shown:false`` reply). No bytes (closed /
|
|
313
|
+
timed-out) means it may not have applied -> caller retries the
|
|
314
|
+
identical envelope.
|
|
315
|
+
"""
|
|
316
|
+
if os.name == "nt":
|
|
317
|
+
return self._deliver_pipe(payload)
|
|
318
|
+
return self._deliver_unix(payload)
|
|
319
|
+
|
|
320
|
+
def _deliver_unix(self, payload: bytes) -> bool:
|
|
321
|
+
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
|
322
|
+
sock.settimeout(_CONNECT_TIMEOUT_S)
|
|
323
|
+
sock.connect(self._socket_path) # type: ignore[arg-type]
|
|
324
|
+
sock.sendall(payload)
|
|
325
|
+
return bool(sock.recv(_ACK_BYTES))
|
|
326
|
+
|
|
327
|
+
def _deliver_pipe(self, payload: bytes) -> bool:
|
|
328
|
+
# Windows herdr (Rust ``interprocess``) exposes ``\\.\pipe\<full path>``; the
|
|
329
|
+
# .sock file is just a pid marker. CreateFile fails fast when herdr's gone
|
|
330
|
+
# (OSError -> retry). The ack read has no timeout, but replies are immediate,
|
|
331
|
+
# the worker is a daemon thread, and release_and_close is bounded, so a wedged
|
|
332
|
+
# herdr can't hang the agent or its shutdown.
|
|
333
|
+
pipe_name = "\\\\.\\pipe\\" + str(self._socket_path)
|
|
334
|
+
with open(pipe_name, "r+b", buffering=0) as pipe:
|
|
335
|
+
pipe.write(payload)
|
|
336
|
+
return bool(pipe.readline(_ACK_BYTES))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
__all__ = ["HerdrClient", "SOURCE", "AGENT"]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""herdr integration -- report code-puppy's state to a herdr pane.
|
|
2
|
+
|
|
3
|
+
This built-in plugin is a no-op unless code-puppy is running inside a
|
|
4
|
+
`herdr <https://herdr.dev>`_ pane (detected via the ``HERDR_ENV`` /
|
|
5
|
+
``HERDR_SOCKET_PATH`` / ``HERDR_PANE_ID`` environment variables herdr
|
|
6
|
+
injects). When it is, it reports semantic agent state -- ``working`` /
|
|
7
|
+
``blocked`` / ``idle`` -- over herdr's local socket so herdr's sidebar
|
|
8
|
+
can roll code-puppy up alongside every other agent in the fleet.
|
|
9
|
+
|
|
10
|
+
code-puppy reports its state **authoritatively**: herdr never infers it from
|
|
11
|
+
the screen. State is a pure function of two facts the plugin observes
|
|
12
|
+
directly (see reporter.py):
|
|
13
|
+
|
|
14
|
+
* run depth, from ``agent_run_start`` / ``agent_run_end`` -> ``working``
|
|
15
|
+
* awaiting-human, from the ``awaiting_user_input`` callback -> ``blocked``
|
|
16
|
+
|
|
17
|
+
The ``awaiting_user_input`` callback is the key: it fires from the single
|
|
18
|
+
process-wide choke-point (``command_runner.set_awaiting_user_input``) that
|
|
19
|
+
*every* interactive wait already passes through -- shell-command approval,
|
|
20
|
+
file-permission approval, ``ask_user_question``, and every menu/picker -- so
|
|
21
|
+
one hook captures every block. There is no per-prompt special-casing and
|
|
22
|
+
nothing for herdr to guess.
|
|
23
|
+
|
|
24
|
+
Callback -> effect:
|
|
25
|
+
|
|
26
|
+
* ``startup`` ......................................... resync (-> idle)
|
|
27
|
+
* ``session_end`` / ``shutdown`` ..................... release pane authority
|
|
28
|
+
* ``user_prompt_submit`` ............................. refresh durable session
|
|
29
|
+
* ``agent_run_start`` / ``agent_run_end`` ............ run-depth +/- 1
|
|
30
|
+
* ``pre_tool_call`` / ``post_tool_call`` ............. decorative activity msg
|
|
31
|
+
* ``agent_run_cancel`` / ``interactive_turn_end`` .... reset -> idle
|
|
32
|
+
* ``interactive_turn_cancel`` ........................ reset -> idle
|
|
33
|
+
* ``awaiting_user_input`` ............................ blocked <-> not
|
|
34
|
+
|
|
35
|
+
Session identity is the durable autosave (name, path) resolved by
|
|
36
|
+
``sources.current_session_ref`` -- NOT the per-run ``group_id`` UUID, which
|
|
37
|
+
changes every turn. Pane metadata (model / context / tokens) and the
|
|
38
|
+
best-effort activity ``message`` are decorative: they never perturb the
|
|
39
|
+
authoritative state and never delay a state edge, session reference, or the
|
|
40
|
+
final ``pane.release_agent`` on exit.
|
|
41
|
+
|
|
42
|
+
Handlers are plain sync functions that swallow every argument: the callback
|
|
43
|
+
dispatcher passes hook args positionally and runs sync callbacks happily
|
|
44
|
+
from both async and worker-thread contexts, so ``*args`` is the robust
|
|
45
|
+
choice.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
import logging
|
|
51
|
+
|
|
52
|
+
from code_puppy.callbacks import register_callback
|
|
53
|
+
|
|
54
|
+
from .client import HerdrClient
|
|
55
|
+
from .reporter import HerdrReporter
|
|
56
|
+
|
|
57
|
+
logger = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
_client = HerdrClient()
|
|
60
|
+
_reporter = HerdrReporter(_client)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _arg(args: tuple, index: int):
|
|
64
|
+
return args[index] if len(args) > index else None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _on_startup(*_args, **_kw) -> None:
|
|
68
|
+
_reporter.on_startup()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _on_user_prompt(*_args, **_kw) -> None:
|
|
72
|
+
# The callback's per-run group_id is ignored; the reporter resolves the
|
|
73
|
+
# durable session reference itself.
|
|
74
|
+
_reporter.on_user_prompt()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _on_run_start(*_args, **_kw) -> None:
|
|
78
|
+
_reporter.on_run_start()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _on_run_end(*_args, **_kw) -> None:
|
|
82
|
+
_reporter.on_run_end()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _on_run_cancel(*_args, **_kw) -> None:
|
|
86
|
+
_reporter.on_run_cancel()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _on_turn_end(*_args, **_kw) -> None:
|
|
90
|
+
_reporter.on_turn_end()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _on_tool_start(*args, **kwargs):
|
|
94
|
+
# (tool_name, tool_args, context=None). Decorative only -> return None so
|
|
95
|
+
# the tool is never blocked or transformed.
|
|
96
|
+
tool_name = _arg(args, 0) if args else kwargs.get("tool_name")
|
|
97
|
+
_reporter.on_tool_start(str(tool_name) if tool_name is not None else "")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _on_tool_complete(*_args, **_kw):
|
|
102
|
+
# (tool_name, tool_args, result, duration_ms, context=None). Decorative.
|
|
103
|
+
_reporter.on_tool_complete()
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _on_awaiting_user_input(*args, **_kw) -> None:
|
|
108
|
+
# Keep the public callback's historical one-argument signature. Notification
|
|
109
|
+
# intent is read synchronously from command_runner's current wait state.
|
|
110
|
+
from code_puppy.tools.command_runner import should_notify_awaiting_user_input
|
|
111
|
+
|
|
112
|
+
_reporter.on_awaiting_user_input(
|
|
113
|
+
bool(_arg(args, 0)),
|
|
114
|
+
notify=should_notify_awaiting_user_input(),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _on_shutdown(*_args, **_kw) -> None:
|
|
119
|
+
_reporter.on_shutdown()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if _reporter.active:
|
|
123
|
+
register_callback("startup", _on_startup)
|
|
124
|
+
register_callback("user_prompt_submit", _on_user_prompt)
|
|
125
|
+
register_callback("agent_run_start", _on_run_start)
|
|
126
|
+
register_callback("agent_run_end", _on_run_end)
|
|
127
|
+
register_callback("agent_run_cancel", _on_run_cancel)
|
|
128
|
+
register_callback("interactive_turn_end", _on_turn_end)
|
|
129
|
+
register_callback("interactive_turn_cancel", _on_turn_end)
|
|
130
|
+
register_callback("pre_tool_call", _on_tool_start)
|
|
131
|
+
register_callback("post_tool_call", _on_tool_complete)
|
|
132
|
+
register_callback("awaiting_user_input", _on_awaiting_user_input)
|
|
133
|
+
register_callback("session_end", _on_shutdown)
|
|
134
|
+
register_callback("shutdown", _on_shutdown)
|
|
135
|
+
logger.debug("herdr plugin active for pane %s", _client._pane_id)
|
|
136
|
+
else:
|
|
137
|
+
logger.debug("herdr plugin inactive (not running inside a herdr pane)")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
__all__ = ["HerdrClient", "HerdrReporter"]
|