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,388 @@
|
|
|
1
|
+
"""Plugin that adds /prune for surgical history pruning.
|
|
2
|
+
|
|
3
|
+
/prune opens a multi-select TUI of conversation history. Unlike /pop
|
|
4
|
+
(which slices a contiguous tail), /prune lets the user cherry-pick
|
|
5
|
+
arbitrary whole messages and rip them out. Selection granularity is
|
|
6
|
+
at the message level only — individual tool calls within a message
|
|
7
|
+
cannot be removed in isolation; they always travel with their parent
|
|
8
|
+
message (and their matching tool returns elsewhere in history are
|
|
9
|
+
cascaded out via the orphan-cleanup pass).
|
|
10
|
+
|
|
11
|
+
The system prompt is always preserved.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
/prune Open interactive multi-select TUI
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any, List, Optional, Set, Tuple
|
|
20
|
+
|
|
21
|
+
from code_puppy.callbacks import register_callback
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── messaging wrappers ──────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def emit_error(message: Any) -> None:
|
|
28
|
+
from code_puppy.messaging import emit_error as _emit_error
|
|
29
|
+
|
|
30
|
+
_emit_error(message)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def emit_info(message: Any) -> None:
|
|
34
|
+
from code_puppy.messaging import emit_info as _emit_info
|
|
35
|
+
|
|
36
|
+
_emit_info(message)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def emit_success(message: Any) -> None:
|
|
40
|
+
from code_puppy.messaging import emit_success as _emit_success
|
|
41
|
+
|
|
42
|
+
_emit_success(message)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def emit_warning(message: Any) -> None:
|
|
46
|
+
from code_puppy.messaging import emit_warning as _emit_warning
|
|
47
|
+
|
|
48
|
+
_emit_warning(message)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ── /help integration ───────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _custom_help() -> List[Tuple[str, str]]:
|
|
55
|
+
return [
|
|
56
|
+
(
|
|
57
|
+
"prune",
|
|
58
|
+
"Multi-select pruner — cherry-pick whole messages to remove",
|
|
59
|
+
)
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Tool-fragment pruning: remove unmatched tool parts after message drops.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _collect_tool_ids(history: List[Any]) -> Tuple[Set[str], Set[str]]:
|
|
67
|
+
call_ids: Set[str] = set()
|
|
68
|
+
return_ids: Set[str] = set()
|
|
69
|
+
try:
|
|
70
|
+
from pydantic_ai.messages import ToolCallPart, ToolReturnPart
|
|
71
|
+
except Exception:
|
|
72
|
+
return call_ids, return_ids
|
|
73
|
+
|
|
74
|
+
for message in history:
|
|
75
|
+
for part in getattr(message, "parts", []) or []:
|
|
76
|
+
if isinstance(part, ToolCallPart):
|
|
77
|
+
tcid = getattr(part, "tool_call_id", None)
|
|
78
|
+
if tcid:
|
|
79
|
+
call_ids.add(tcid)
|
|
80
|
+
elif isinstance(part, ToolReturnPart):
|
|
81
|
+
tcid = getattr(part, "tool_call_id", None)
|
|
82
|
+
if tcid:
|
|
83
|
+
return_ids.add(tcid)
|
|
84
|
+
return call_ids, return_ids
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _has_orphaned_returns(message: Any, call_ids: Set[str]) -> bool:
|
|
88
|
+
try:
|
|
89
|
+
from pydantic_ai.messages import ModelRequest, ToolReturnPart
|
|
90
|
+
|
|
91
|
+
if not isinstance(message, ModelRequest):
|
|
92
|
+
return False
|
|
93
|
+
parts = getattr(message, "parts", []) or []
|
|
94
|
+
if not parts:
|
|
95
|
+
return False
|
|
96
|
+
if not all(isinstance(p, ToolReturnPart) for p in parts):
|
|
97
|
+
return False
|
|
98
|
+
return any(
|
|
99
|
+
not getattr(p, "tool_call_id", None) or p.tool_call_id not in call_ids
|
|
100
|
+
for p in parts
|
|
101
|
+
)
|
|
102
|
+
except Exception:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _has_orphaned_calls(message: Any, return_ids: Set[str]) -> bool:
|
|
107
|
+
try:
|
|
108
|
+
from pydantic_ai.messages import ModelResponse, ToolCallPart
|
|
109
|
+
|
|
110
|
+
if not isinstance(message, ModelResponse):
|
|
111
|
+
return False
|
|
112
|
+
for part in getattr(message, "parts", []) or []:
|
|
113
|
+
if isinstance(part, ToolCallPart):
|
|
114
|
+
tcid = getattr(part, "tool_call_id", None)
|
|
115
|
+
if not tcid or tcid not in return_ids:
|
|
116
|
+
return True
|
|
117
|
+
return False
|
|
118
|
+
except Exception:
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _prune_dangling_tool_fragments(history: List[Any]) -> Tuple[List[Any], int]:
|
|
123
|
+
"""Strip genuinely orphaned tool-call sequences from the tail."""
|
|
124
|
+
pruned = 0
|
|
125
|
+
while history:
|
|
126
|
+
call_ids, return_ids = _collect_tool_ids(history)
|
|
127
|
+
tail = history[-1]
|
|
128
|
+
if _has_orphaned_returns(tail, call_ids):
|
|
129
|
+
history.pop()
|
|
130
|
+
pruned += 1
|
|
131
|
+
continue
|
|
132
|
+
if _has_orphaned_calls(tail, return_ids):
|
|
133
|
+
history.pop()
|
|
134
|
+
pruned += 1
|
|
135
|
+
continue
|
|
136
|
+
break
|
|
137
|
+
return history, pruned
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ── core mutation ──────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _collect_removed_tool_call_ids(
|
|
144
|
+
history: List[Any],
|
|
145
|
+
drop_indices: Set[int],
|
|
146
|
+
) -> Set[str]:
|
|
147
|
+
"""Compute the full set of tool_call_ids whose returns must also go.
|
|
148
|
+
|
|
149
|
+
Collects every ToolCallPart id living inside the messages we're
|
|
150
|
+
dropping wholesale, so the matching ToolReturnPart fragments
|
|
151
|
+
elsewhere in history can be cascaded out as orphans.
|
|
152
|
+
"""
|
|
153
|
+
removed: Set[str] = set()
|
|
154
|
+
try:
|
|
155
|
+
from pydantic_ai.messages import ModelResponse, ToolCallPart
|
|
156
|
+
except Exception:
|
|
157
|
+
return removed
|
|
158
|
+
|
|
159
|
+
for hist_idx in drop_indices:
|
|
160
|
+
if hist_idx < 0 or hist_idx >= len(history):
|
|
161
|
+
continue
|
|
162
|
+
msg = history[hist_idx]
|
|
163
|
+
if not isinstance(msg, ModelResponse):
|
|
164
|
+
continue
|
|
165
|
+
for part in getattr(msg, "parts", []) or []:
|
|
166
|
+
if isinstance(part, ToolCallPart):
|
|
167
|
+
tcid = getattr(part, "tool_call_id", None)
|
|
168
|
+
if tcid:
|
|
169
|
+
removed.add(tcid)
|
|
170
|
+
return removed
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _message_carries_system_prompt(message: Any) -> bool:
|
|
174
|
+
"""True if ``message`` is a ModelRequest containing a SystemPromptPart.
|
|
175
|
+
|
|
176
|
+
Matches both a standalone system message and pydantic-ai's bundled
|
|
177
|
+
``SystemPromptPart + UserPromptPart`` request.
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
from pydantic_ai.messages import ModelRequest, SystemPromptPart
|
|
181
|
+
except Exception:
|
|
182
|
+
return False
|
|
183
|
+
if not isinstance(message, ModelRequest):
|
|
184
|
+
return False
|
|
185
|
+
for part in getattr(message, "parts", []) or []:
|
|
186
|
+
if isinstance(part, SystemPromptPart):
|
|
187
|
+
return True
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _message_has_orphan_tool_return(message: Any, orphan_call_ids: Set[str]) -> bool:
|
|
192
|
+
"""True if ``message`` is a ModelRequest carrying a ToolReturnPart
|
|
193
|
+
whose ``tool_call_id`` is in ``orphan_call_ids``.
|
|
194
|
+
|
|
195
|
+
Used to cascade-drop messages that would otherwise leave the model
|
|
196
|
+
looking at a tool result with no matching tool call (which providers
|
|
197
|
+
like Anthropic reject outright).
|
|
198
|
+
"""
|
|
199
|
+
if not orphan_call_ids:
|
|
200
|
+
return False
|
|
201
|
+
try:
|
|
202
|
+
from pydantic_ai.messages import ModelRequest, ToolReturnPart
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
from pydantic_ai.messages import RetryPromptPart # type: ignore
|
|
206
|
+
except Exception: # pragma: no cover — older pydantic-ai
|
|
207
|
+
RetryPromptPart = None # type: ignore[assignment]
|
|
208
|
+
except Exception:
|
|
209
|
+
return False
|
|
210
|
+
if not isinstance(message, ModelRequest):
|
|
211
|
+
return False
|
|
212
|
+
reply_kinds: tuple = (ToolReturnPart,)
|
|
213
|
+
if RetryPromptPart is not None:
|
|
214
|
+
reply_kinds = (ToolReturnPart, RetryPromptPart)
|
|
215
|
+
for part in getattr(message, "parts", []) or []:
|
|
216
|
+
if isinstance(part, reply_kinds):
|
|
217
|
+
tcid = getattr(part, "tool_call_id", None)
|
|
218
|
+
if tcid and tcid in orphan_call_ids:
|
|
219
|
+
return True
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _perform_prune(drop_indices: Set[int]) -> None:
|
|
224
|
+
"""Apply the prune selection to current agent history.
|
|
225
|
+
|
|
226
|
+
Operates on whole messages only: each index in ``drop_indices`` is
|
|
227
|
+
removed from history, any ToolReturnPart messages whose matching
|
|
228
|
+
ToolCallPart was dropped are cascade-removed, and orphaned
|
|
229
|
+
tool-call/return fragments at the tail are cleaned up.
|
|
230
|
+
"""
|
|
231
|
+
from code_puppy.agents.agent_manager import get_current_agent
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
agent = get_current_agent()
|
|
235
|
+
except Exception as exc:
|
|
236
|
+
emit_error(f"/prune: could not get current agent – {exc}")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
history: List[Any] = list(agent.get_message_history())
|
|
240
|
+
if not history:
|
|
241
|
+
emit_warning("/prune: conversation history is empty – nothing to remove")
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
# Ignore invalid indices and SystemPromptPart messages; the system identity
|
|
245
|
+
# must never be pruned.
|
|
246
|
+
drop_indices = {
|
|
247
|
+
i
|
|
248
|
+
for i in drop_indices
|
|
249
|
+
if 0 <= i < len(history) and not _message_carries_system_prompt(history[i])
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if not drop_indices:
|
|
253
|
+
emit_info("/prune: nothing selected – history unchanged")
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
# First pass: figure out which tool_call_ids belonged to dropped
|
|
257
|
+
# messages so we can cascade-drop their orphaned returns elsewhere.
|
|
258
|
+
orphan_call_ids = _collect_removed_tool_call_ids(history, drop_indices)
|
|
259
|
+
|
|
260
|
+
before_count = len(history)
|
|
261
|
+
|
|
262
|
+
new_history: List[Any] = []
|
|
263
|
+
msgs_dropped = 0
|
|
264
|
+
cascade_dropped = 0
|
|
265
|
+
|
|
266
|
+
for hist_idx, msg in enumerate(history):
|
|
267
|
+
if hist_idx in drop_indices:
|
|
268
|
+
msgs_dropped += 1
|
|
269
|
+
continue
|
|
270
|
+
if _message_has_orphan_tool_return(msg, orphan_call_ids):
|
|
271
|
+
cascade_dropped += 1
|
|
272
|
+
continue
|
|
273
|
+
new_history.append(msg)
|
|
274
|
+
|
|
275
|
+
new_history, extra_pruned = _prune_dangling_tool_fragments(new_history)
|
|
276
|
+
after_count = len(new_history)
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
agent.set_message_history(new_history)
|
|
280
|
+
except Exception as exc:
|
|
281
|
+
emit_error(f"/prune: failed to update message history – {exc}")
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
summary_lines = [
|
|
285
|
+
":scissors: Prune complete.",
|
|
286
|
+
f" · {msgs_dropped} message(s) removed by selection",
|
|
287
|
+
]
|
|
288
|
+
if cascade_dropped:
|
|
289
|
+
summary_lines.append(
|
|
290
|
+
f" · {cascade_dropped} message(s) cascade-dropped (orphaned tool returns)"
|
|
291
|
+
)
|
|
292
|
+
if extra_pruned:
|
|
293
|
+
summary_lines.append(
|
|
294
|
+
f" · {extra_pruned} dangling tool fragment(s) cleaned from tail"
|
|
295
|
+
)
|
|
296
|
+
summary_lines.append(
|
|
297
|
+
f":scroll: History: {before_count - 1} → {max(after_count - 1, 0)} message(s) "
|
|
298
|
+
f"(excluding system prompt)"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
emit_success("\n".join(summary_lines))
|
|
302
|
+
|
|
303
|
+
if after_count <= 1:
|
|
304
|
+
emit_info(":bulb: History is now empty (system prompt only). Starting fresh!")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# ── /prune dispatch ────────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _handle_prune_command(command: str) -> bool:
|
|
311
|
+
del command # /prune takes no arguments anymore
|
|
312
|
+
_launch_menu()
|
|
313
|
+
return True
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _launch_menu() -> None:
|
|
317
|
+
from code_puppy.agents.agent_manager import get_current_agent
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
agent = get_current_agent()
|
|
321
|
+
except Exception as exc:
|
|
322
|
+
emit_error(f"/prune: could not get current agent – {exc}")
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
raw_history: List[Any] = list(agent.get_message_history())
|
|
326
|
+
|
|
327
|
+
# Sibling modules within the same package.
|
|
328
|
+
from code_puppy_core_plugins.prune.prune_menu import PruneMenu
|
|
329
|
+
from code_puppy_core_plugins.prune.prune_model import (
|
|
330
|
+
ContextBudget,
|
|
331
|
+
annotate_context_window,
|
|
332
|
+
build_message_entries,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
entries = build_message_entries(raw_history)
|
|
336
|
+
# Locked rows are non-prunable, so an all-locked list has nothing to toggle.
|
|
337
|
+
if not entries or all(e.is_locked for e in entries):
|
|
338
|
+
emit_info("/prune: no prunable messages")
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
# Annotate token counts and in-context flags. Failures are silent —
|
|
342
|
+
# the menu just won't show the indicators.
|
|
343
|
+
try:
|
|
344
|
+
budget = annotate_context_window(entries, raw_history, agent)
|
|
345
|
+
except Exception:
|
|
346
|
+
budget = ContextBudget()
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
menu = PruneMenu(entries=entries, budget=budget)
|
|
350
|
+
except ValueError as exc:
|
|
351
|
+
emit_info(f"/prune: {exc}")
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
selection = menu.run()
|
|
355
|
+
|
|
356
|
+
if selection is None:
|
|
357
|
+
emit_info("/prune: cancelled")
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
if selection.is_empty:
|
|
361
|
+
emit_info("/prune: nothing selected – history unchanged")
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
_perform_prune(selection.history_indices_to_drop)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# ── custom_command plumbing ────────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _handle_custom_command(command: str, name: str) -> Optional[bool]:
|
|
371
|
+
if name != "prune":
|
|
372
|
+
return None
|
|
373
|
+
return _handle_prune_command(command)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
register_callback("custom_command_help", _custom_help)
|
|
377
|
+
register_callback("custom_command", _handle_custom_command)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
__all__ = [
|
|
381
|
+
"_collect_removed_tool_call_ids",
|
|
382
|
+
"_custom_help",
|
|
383
|
+
"_handle_custom_command",
|
|
384
|
+
"_handle_prune_command",
|
|
385
|
+
"_message_has_orphan_tool_return",
|
|
386
|
+
"_perform_prune",
|
|
387
|
+
"_prune_dangling_tool_fragments",
|
|
388
|
+
]
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Puppy Kennel
|
|
2
|
+
|
|
3
|
+
Local-first memory for Code Puppy. Inspired by [MemKennel](https://github.com/MemKennel/memkennel)'s
|
|
4
|
+
wings → rooms → drawers model, but backed by **SQLite + FTS5** instead of
|
|
5
|
+
ChromaDB. No daemon, no API key, no cloud, multi-process safe via WAL mode.
|
|
6
|
+
|
|
7
|
+
## Why not just use MemKennel / Mem0 / Chroma?
|
|
8
|
+
|
|
9
|
+
We looked. Hard. The summary:
|
|
10
|
+
|
|
11
|
+
- **MemKennel** — Beautiful concepts, but ChromaDB's embedded `PersistentClient`
|
|
12
|
+
is not safe across multiple processes hitting the same kennel. Upstream issues
|
|
13
|
+
#1581, #948, #1646 are all flavors of "we need a daemon to make this work."
|
|
14
|
+
We run 20 puppies sometimes. A daemon is not in the cards.
|
|
15
|
+
- **Mem0** — Open issue #4892: "concurrent AsyncMemory writes corrupt Qdrant
|
|
16
|
+
HNSW index." Same disease. Also requires an LLM API key just to *store*
|
|
17
|
+
memories, and phones home to PostHog by default. No thanks.
|
|
18
|
+
- **ChromaDB / Qdrant / Faiss directly** — All ANN-index-backed; all multi-
|
|
19
|
+
process-hostile in embedded mode. ANN is only worth the trouble at >100K
|
|
20
|
+
vectors. We're nowhere near that.
|
|
21
|
+
|
|
22
|
+
SQLite has had production-grade full-text search (FTS5, BM25-ranked) since
|
|
23
|
+
2015 and has been multi-process safe via WAL since 2010. We do not need
|
|
24
|
+
anything fancier.
|
|
25
|
+
|
|
26
|
+
## Wing namespacing — compartmentalization without isolation walls
|
|
27
|
+
|
|
28
|
+
Three wing namespaces, all in one shared kennel:
|
|
29
|
+
|
|
30
|
+
| Wing | Example | Purpose |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `repo:<path>` | `repo:/Users/mike/code/foo` | Project memory, shared across agents |
|
|
33
|
+
| `agent:<name>` | `agent:code-puppy` | Per-agent diary, private by convention |
|
|
34
|
+
| `user:default` | `user:default` | Cross-cutting user preferences |
|
|
35
|
+
|
|
36
|
+
Privacy is by convention, not encryption. If you need cryptographic
|
|
37
|
+
isolation for a sensitive-data agent, run that agent with
|
|
38
|
+
`PUPPY_KENNEL_ROOT` pointed at a private directory.
|
|
39
|
+
|
|
40
|
+
## What it does today
|
|
41
|
+
|
|
42
|
+
**Phase 1 — passive memory:**
|
|
43
|
+
- **`load_prompt`** — injects a tiered, budget-aware recall block (see
|
|
44
|
+
[the packer](#the-packer) below) into the system prompt.
|
|
45
|
+
- **`agent_run_end`** — writes the agent's response to a single drawer
|
|
46
|
+
in the current ``repo:<cwd>`` wing.
|
|
47
|
+
|
|
48
|
+
## Wing semantics (Phase 5)
|
|
49
|
+
|
|
50
|
+
The three wings model **who the memory is for**, not who wrote it:
|
|
51
|
+
|
|
52
|
+
| Wing | Active when... | Filled by... |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| ``user:default`` | always (cross-cutting) | explicit ``kennel_remember(wing="user")`` only |
|
|
55
|
+
| ``repo:<cwd>`` | cwd matches | autosave + ``kennel_remember(wing="repo")`` (default) |
|
|
56
|
+
| ``agent:<name>`` | selected agent matches | explicit ``kennel_remember(wing="agent")`` only |
|
|
57
|
+
|
|
58
|
+
Autosave goes **only** to the repo wing — every response is "a
|
|
59
|
+
conversation that happened in this project," which is genuinely
|
|
60
|
+
repo-scoped. The agent wing is reserved for deliberate cross-project
|
|
61
|
+
reflections; ``user:default`` is for facts about the human.
|
|
62
|
+
|
|
63
|
+
This kills the previous dual-write design (where each response wrote
|
|
64
|
+
twice, once per wing). Search-time dedup is still in place for any
|
|
65
|
+
legacy duplicates and for content the agent explicitly mirrors into a
|
|
66
|
+
second wing.
|
|
67
|
+
|
|
68
|
+
## The packer
|
|
69
|
+
|
|
70
|
+
The recall block is assembled by `packer.py` under a configurable token
|
|
71
|
+
budget. Three priority classes, no LLM, no embeddings:
|
|
72
|
+
|
|
73
|
+
| Tier | Source | Quota (default) | Why |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| **P0** User Preferences | `user:default` wing, any role | ~30% | Short, durable, pervasive ('Mike hates emojis') |
|
|
76
|
+
| **P1** Project Decisions | `repo:<cwd>` wing, `role='note'` | ~30% | Sticky writes from `kennel_remember` — highest signal-to-token ratio |
|
|
77
|
+
| **P2** Recent Context | `repo:<cwd>` wing, `role='assistant'` | remainder | Orientation, freshness |
|
|
78
|
+
|
|
79
|
+
Drawers below `PUPPY_KENNEL_MIN_DRAWER_CHARS` (default 80) are skipped
|
|
80
|
+
as noise. Token estimation uses the well-known 1-token ≈ 4-chars
|
|
81
|
+
heuristic — accurate to ±20%, zero deps.
|
|
82
|
+
|
|
83
|
+
**Config knobs:**
|
|
84
|
+
|
|
85
|
+
| Env var | Default | Effect |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `PUPPY_KENNEL_PROMPT_BUDGET` | `1500` | Total token budget for the block |
|
|
88
|
+
| `PUPPY_KENNEL_USER_PREFS_QUOTA` | `0.30` | P0 fraction |
|
|
89
|
+
| `PUPPY_KENNEL_STICKY_QUOTA` | `0.30` | P1 fraction |
|
|
90
|
+
| `PUPPY_KENNEL_MIN_DRAWER_CHARS` | `80` | Noise filter |
|
|
91
|
+
|
|
92
|
+
**Phase 2 — active tooling:**
|
|
93
|
+
|
|
94
|
+
Five agent-callable tools registered via ``register_tools``:
|
|
95
|
+
|
|
96
|
+
| Tool | Purpose |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `kennel_recall(query, wing?, top_k=5, scope=?)` | BM25 search across wings, deduplicated. |
|
|
99
|
+
| `kennel_remember(content, wing="repo", room="notes")` | Explicit verbatim write to a chosen wing. |
|
|
100
|
+
| `kennel_recent(wing?, top_k=5, scope=?)` | Time-ordered drawer browsing (no query needed). |
|
|
101
|
+
| `kennel_list_wings()` | Discover all wings + drawer counts. |
|
|
102
|
+
| `kennel_stats()` | Kennel-wide totals + on-disk size. |
|
|
103
|
+
|
|
104
|
+
Wing shortcuts accepted by every tool: ``"repo"`` (current project,
|
|
105
|
+
default for writes), ``"agent"`` (this agent's diary), ``"user"``
|
|
106
|
+
(cross-cutting preferences), or an explicit wing name like
|
|
107
|
+
``"team:platform"``.
|
|
108
|
+
|
|
109
|
+
Scope shortcuts for multi-wing reads: ``"default"`` (repo+agent+user),
|
|
110
|
+
``"repo"``, ``"agent"``, ``"user"``, ``"all"``.
|
|
111
|
+
|
|
112
|
+
Plus a **`/kennel`** slash command suite for humans:
|
|
113
|
+
|
|
114
|
+
| Command | What it does |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `/kennel` | stats + recent context preview |
|
|
117
|
+
| `/kennel search <q>` | BM25 search across default scope |
|
|
118
|
+
| `/kennel wings` | list wings + drawer counts |
|
|
119
|
+
| `/kennel stats` | DB size, totals, current enabled state |
|
|
120
|
+
| `/kennel status` | is memory currently on or off? |
|
|
121
|
+
| `/kennel enable` (or `on`) | turn memory on at runtime |
|
|
122
|
+
| `/kennel disable` (or `off`) | turn memory off at runtime (drawers preserved) |
|
|
123
|
+
| `/kennel help` | usage hint |
|
|
124
|
+
|
|
125
|
+
## Enable / disable
|
|
126
|
+
|
|
127
|
+
The plugin is **disabled by default** — running in the background on
|
|
128
|
+
noise is off until you opt in. Flip it with the slash commands:
|
|
129
|
+
|
|
130
|
+
- ``/kennel enable`` (or ``/kennel on``) — turn memory on
|
|
131
|
+
- ``/kennel disable`` (or ``/kennel off``) — turn memory off
|
|
132
|
+
|
|
133
|
+
State is persisted to ``puppy.cfg`` under ``kennel_enabled`` and read on
|
|
134
|
+
every callback, so the toggle is live — no restart needed, and the
|
|
135
|
+
front end can read or write the same value.
|
|
136
|
+
|
|
137
|
+
**When disabled:**
|
|
138
|
+
|
|
139
|
+
- ``load_prompt`` returns ``None`` (no recall block in system prompt)
|
|
140
|
+
- ``agent_run_end`` is a no-op (nothing recorded)
|
|
141
|
+
- Agent-facing tools return a friendly ``error`` field explaining memory is off
|
|
142
|
+
- **The ``/kennel`` slash suite stays available** — ``/kennel stats``,
|
|
143
|
+
``/kennel wings``, ``/kennel search`` all still work so the operator
|
|
144
|
+
can see what's stored, and ``/kennel enable`` is always reachable to
|
|
145
|
+
turn it back on.
|
|
146
|
+
|
|
147
|
+
## Known tech debt
|
|
148
|
+
|
|
149
|
+
- **Legacy duplicates.** Kennels that pre-date Phase 5 may contain
|
|
150
|
+
dual-written drawers (same content in repo + agent wings).
|
|
151
|
+
``search_drawers_multi`` deduplicates by content so reads are sane;
|
|
152
|
+
they'll age out naturally once retention/pruning ships.
|
|
153
|
+
|
|
154
|
+
## What's still coming
|
|
155
|
+
|
|
156
|
+
- `stream_event` hook to also capture user input (not just agent output).
|
|
157
|
+
- Optional `fastembed` semantic re-ranking on top of FTS5 BM25.
|
|
158
|
+
- `/kennel prune` retention policy.
|
|
159
|
+
|
|
160
|
+
## Environment
|
|
161
|
+
|
|
162
|
+
| Variable | Default | Effect |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `PUPPY_KENNEL_ROOT` | `~/.code_puppy/kennel` | Where the SQLite file lives |
|
|
165
|
+
| `PUPPY_KENNEL_PASSIVE_LIMIT` | `5` | Drawers surfaced in passive recall |
|
|
166
|
+
| `PUPPY_KENNEL_MAX_DRAWER_CHARS` | `32000` | Cap on stored drawer size |
|
|
167
|
+
|
|
168
|
+
## puppy.cfg keys
|
|
169
|
+
|
|
170
|
+
| Key | Default | Effect |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `kennel_enabled` | unset → disabled | Single source of truth for the on/off toggle. Set to `true`/`1`/`yes`/`on` (case-insensitive) to enable. Anything else — missing, blank, or garbage — leaves the kennel off. Flipped live by `/kennel enable` and `/kennel disable`. |
|
|
173
|
+
|
|
174
|
+
## How tools reach the agent
|
|
175
|
+
|
|
176
|
+
Code Puppy agents expose a hardcoded ``get_available_tools()`` list. To get
|
|
177
|
+
plugin tools onto that list without editing every agent, this plugin uses
|
|
178
|
+
the ``register_agent_tools`` hook — a small piece of core architecture
|
|
179
|
+
added alongside this plugin specifically to avoid that pattern.
|
|
180
|
+
|
|
181
|
+
``register_tools`` defines tools and drops them into ``TOOL_REGISTRY``;
|
|
182
|
+
``register_agent_tools`` says *which* tools to advertise to *which* agent.
|
|
183
|
+
The puppy_kennel plugin always returns all five tool names regardless of
|
|
184
|
+
agent — memory is universally useful. Other plugins can scope per-agent by
|
|
185
|
+
branching on the ``agent_name`` argument.
|
|
186
|
+
|
|
187
|
+
## Files
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
puppy_kennel/
|
|
191
|
+
├── __init__.py
|
|
192
|
+
├── config.py # paths, env vars, limits
|
|
193
|
+
├── schema.py # CREATE TABLE + PRAGMA strings
|
|
194
|
+
├── kennel.py # storage layer (wings, rooms, drawers)
|
|
195
|
+
├── wings.py # wing-naming convention
|
|
196
|
+
├── recorder.py # agent_run_end -> drawer
|
|
197
|
+
├── retriever.py # load_prompt -> recall block
|
|
198
|
+
├── register_callbacks.py # hook wiring (entry point)
|
|
199
|
+
└── README.md # you are here
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
All files under 200 lines, every concern its own module, no SQL leaking
|
|
203
|
+
outside `kennel.py`. SOLID, DRY, Zen-of-Python-compatible. Cheers.
|