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,206 @@
|
|
|
1
|
+
"""Slash commands for humans interacting with the kennel.
|
|
2
|
+
|
|
3
|
+
Sub-commands under ``/kennel``:
|
|
4
|
+
|
|
5
|
+
* ``/kennel`` — Show stats and recent activity
|
|
6
|
+
* ``/kennel search <query>`` — FTS5 search the current recall scope
|
|
7
|
+
* ``/kennel wings`` — List all wings and drawer counts
|
|
8
|
+
* ``/kennel stats`` — Storage stats and totals
|
|
9
|
+
* ``/kennel help`` — Usage hint
|
|
10
|
+
|
|
11
|
+
All commands return ``True`` to mark them handled (per callback contract)
|
|
12
|
+
or ``None`` to let other plugins try. ``False`` is never returned because
|
|
13
|
+
we own the ``/kennel`` prefix unconditionally.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
|
|
21
|
+
|
|
22
|
+
from . import kennel
|
|
23
|
+
from .config import DB_PATH
|
|
24
|
+
from .state import is_enabled, set_enabled
|
|
25
|
+
from .wings import default_recall_scope, detect_cwd
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _reload_current_agent() -> None:
|
|
29
|
+
"""Rebuild the active agent so its tool list reflects the new kennel state.
|
|
30
|
+
|
|
31
|
+
Toggling memory on/off changes what ``register_agent_tools`` advertises,
|
|
32
|
+
but the live agent has already baked its tools in at construction time.
|
|
33
|
+
Without a reload, ``/kennel disable`` would leave the kennel tools
|
|
34
|
+
dangling on the agent (and ``/kennel enable`` wouldn't add them back)
|
|
35
|
+
until the next natural reload. Fail soft — toggling persisted fine
|
|
36
|
+
even if the reload trips.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
from code_puppy.agents.agent_manager import get_current_agent
|
|
40
|
+
|
|
41
|
+
get_current_agent().reload_code_generation_agent()
|
|
42
|
+
except Exception as exc: # noqa: BLE001
|
|
43
|
+
emit_error(f"Could not reload agent after kennel toggle: {exc!r}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_COMMAND = "kennel"
|
|
47
|
+
_HELP_LINES: tuple[tuple[str, str], ...] = (
|
|
48
|
+
("kennel", "Puppy Kennel — local memory: search, stats, wings"),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse(command: str) -> tuple[str, str]:
|
|
53
|
+
"""Split ``/kennel <sub> <rest>`` into ``(sub, rest)``."""
|
|
54
|
+
body = command.lstrip("/").strip()
|
|
55
|
+
if body.startswith(_COMMAND):
|
|
56
|
+
body = body[len(_COMMAND) :].strip()
|
|
57
|
+
parts = body.split(None, 1)
|
|
58
|
+
sub = parts[0].lower() if parts else ""
|
|
59
|
+
rest = parts[1] if len(parts) > 1 else ""
|
|
60
|
+
return sub, rest
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _humanize_bytes(n: int) -> str:
|
|
64
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
65
|
+
if n < 1024:
|
|
66
|
+
return f"{n:.1f} {unit}"
|
|
67
|
+
n /= 1024 # type: ignore[assignment]
|
|
68
|
+
return f"{n:.1f} TB"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _cmd_stats() -> bool:
|
|
72
|
+
total = kennel.count_drawers()
|
|
73
|
+
wings = kennel.list_wings()
|
|
74
|
+
db_size = DB_PATH.stat().st_size if DB_PATH.exists() else 0
|
|
75
|
+
state = "enabled" if is_enabled() else "DISABLED"
|
|
76
|
+
emit_info(f"Puppy Kennel at `{DB_PATH}`")
|
|
77
|
+
emit_info(f" state : {state}")
|
|
78
|
+
emit_info(f" drawers : {total}")
|
|
79
|
+
emit_info(f" wings : {len(wings)}")
|
|
80
|
+
emit_info(f" on disk : {_humanize_bytes(db_size)}")
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _cmd_status() -> bool:
|
|
85
|
+
if is_enabled():
|
|
86
|
+
emit_success("Puppy Kennel memory is ENABLED.")
|
|
87
|
+
else:
|
|
88
|
+
emit_warning(
|
|
89
|
+
"Puppy Kennel memory is DISABLED. Run /kennel enable to turn it on."
|
|
90
|
+
)
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _cmd_enable() -> bool:
|
|
95
|
+
if is_enabled():
|
|
96
|
+
emit_info("Puppy Kennel memory is already enabled.")
|
|
97
|
+
return True
|
|
98
|
+
try:
|
|
99
|
+
set_enabled(True)
|
|
100
|
+
except Exception as exc: # noqa: BLE001
|
|
101
|
+
emit_warning(f"Could not persist enabled state: {exc!r}")
|
|
102
|
+
return True
|
|
103
|
+
_reload_current_agent()
|
|
104
|
+
emit_success("Puppy Kennel memory ENABLED. New runs will be recorded and recalled.")
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _cmd_disable() -> bool:
|
|
109
|
+
if not is_enabled():
|
|
110
|
+
emit_info("Puppy Kennel memory is already disabled.")
|
|
111
|
+
return True
|
|
112
|
+
try:
|
|
113
|
+
set_enabled(False)
|
|
114
|
+
except Exception as exc: # noqa: BLE001
|
|
115
|
+
emit_warning(f"Could not persist disabled state: {exc!r}")
|
|
116
|
+
return True
|
|
117
|
+
_reload_current_agent()
|
|
118
|
+
emit_success(
|
|
119
|
+
"Puppy Kennel memory DISABLED. Existing drawers remain on disk; "
|
|
120
|
+
"recording and recall are paused. Run /kennel enable to resume."
|
|
121
|
+
)
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _cmd_wings() -> bool:
|
|
126
|
+
wings = kennel.list_wings()
|
|
127
|
+
if not wings:
|
|
128
|
+
emit_warning("No wings in the kennel yet.")
|
|
129
|
+
return True
|
|
130
|
+
emit_info(f"{len(wings)} wing(s):")
|
|
131
|
+
for w in wings:
|
|
132
|
+
n = kennel.count_drawers(wing_name=w)
|
|
133
|
+
emit_info(f" {w} ({n} drawer{'s' if n != 1 else ''})")
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _cmd_search(query: str) -> bool:
|
|
138
|
+
if not query.strip():
|
|
139
|
+
emit_warning("Usage: /kennel search <your query>")
|
|
140
|
+
return True
|
|
141
|
+
wings = default_recall_scope("code-puppy", detect_cwd())
|
|
142
|
+
hits = kennel.search_drawers_multi(query, wing_names=wings, limit=5)
|
|
143
|
+
if not hits:
|
|
144
|
+
emit_warning(f"No hits for '{query}' in scope {wings}")
|
|
145
|
+
return True
|
|
146
|
+
emit_success(f"{len(hits)} hit(s) for '{query}':")
|
|
147
|
+
for d in hits:
|
|
148
|
+
agent = (d.metadata or {}).get("agent", "?")
|
|
149
|
+
preview = d.content[:200].replace("\n", " ")
|
|
150
|
+
emit_info(f" [{d.ts}] {agent}: {preview}{'…' if len(d.content) > 200 else ''}")
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _cmd_help() -> bool:
|
|
155
|
+
emit_info("Puppy Kennel commands:")
|
|
156
|
+
emit_info(" /kennel - stats + recent activity")
|
|
157
|
+
emit_info(" /kennel search <query> - FTS5 search across default scope")
|
|
158
|
+
emit_info(" /kennel wings - list wings with drawer counts")
|
|
159
|
+
emit_info(" /kennel stats - storage stats + enabled state")
|
|
160
|
+
emit_info(" /kennel status - is memory enabled?")
|
|
161
|
+
emit_info(" /kennel enable - turn memory on")
|
|
162
|
+
emit_info(" /kennel disable - turn memory off (drawers preserved)")
|
|
163
|
+
emit_info(" /kennel help - this message")
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _cmd_default_overview() -> bool:
|
|
168
|
+
"""Bare ``/kennel`` — show stats + a tiny preview of the recent block."""
|
|
169
|
+
_cmd_stats()
|
|
170
|
+
from .retriever import build_recall_block
|
|
171
|
+
|
|
172
|
+
block = build_recall_block()
|
|
173
|
+
if block:
|
|
174
|
+
emit_info("")
|
|
175
|
+
emit_info(block)
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def handle(command: str, name: str) -> Any:
|
|
180
|
+
"""Dispatch ``/kennel`` and aliases. Returns ``None`` for non-kennel cmds."""
|
|
181
|
+
if name != _COMMAND:
|
|
182
|
+
return None
|
|
183
|
+
sub, rest = _parse(command)
|
|
184
|
+
if not sub:
|
|
185
|
+
return _cmd_default_overview()
|
|
186
|
+
if sub == "search":
|
|
187
|
+
return _cmd_search(rest)
|
|
188
|
+
if sub == "wings":
|
|
189
|
+
return _cmd_wings()
|
|
190
|
+
if sub == "stats":
|
|
191
|
+
return _cmd_stats()
|
|
192
|
+
if sub == "status":
|
|
193
|
+
return _cmd_status()
|
|
194
|
+
if sub in ("enable", "on"):
|
|
195
|
+
return _cmd_enable()
|
|
196
|
+
if sub in ("disable", "off"):
|
|
197
|
+
return _cmd_disable()
|
|
198
|
+
if sub in ("help", "?"):
|
|
199
|
+
return _cmd_help()
|
|
200
|
+
emit_warning(f"Unknown /kennel subcommand: '{sub}'")
|
|
201
|
+
return _cmd_help()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def help_entries() -> list[tuple[str, str]]:
|
|
205
|
+
"""``custom_command_help`` callback — list ``/kennel`` in /help."""
|
|
206
|
+
return list(_HELP_LINES)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Configuration for the puppy_kennel plugin.
|
|
2
|
+
|
|
3
|
+
Single source of truth for paths, defaults, and tunable knobs. The
|
|
4
|
+
on/off toggle lives in ``state.py`` and is persisted in ``puppy.cfg``
|
|
5
|
+
under ``kennel_enabled`` -- see that module.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# Root directory for the kennel on disk.
|
|
14
|
+
KENNEL_ROOT = Path(
|
|
15
|
+
os.environ.get("PUPPY_KENNEL_ROOT", Path.home() / ".code_puppy" / "kennel")
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# The SQLite database file lives inside the kennel root.
|
|
19
|
+
DB_PATH = KENNEL_ROOT / "kennel.db"
|
|
20
|
+
|
|
21
|
+
# Prompt-packing budget for the recall block; estimate tokens at ~4 chars each
|
|
22
|
+
# to avoid a tokenizer dependency.
|
|
23
|
+
PROMPT_BUDGET_TOKENS = int(os.environ.get("PUPPY_KENNEL_PROMPT_BUDGET", "1500"))
|
|
24
|
+
CHARS_PER_TOKEN = 4
|
|
25
|
+
PROMPT_BUDGET_CHARS = PROMPT_BUDGET_TOKENS * CHARS_PER_TOKEN
|
|
26
|
+
|
|
27
|
+
# Per-class quotas. The remainder goes to recent context (P2).
|
|
28
|
+
USER_PREFS_QUOTA = float(os.environ.get("PUPPY_KENNEL_USER_PREFS_QUOTA", "0.30"))
|
|
29
|
+
STICKY_QUOTA = float(os.environ.get("PUPPY_KENNEL_STICKY_QUOTA", "0.30"))
|
|
30
|
+
|
|
31
|
+
# Drawers shorter than this are noise — skip them in the recall block.
|
|
32
|
+
MIN_DRAWER_CHARS = int(os.environ.get("PUPPY_KENNEL_MIN_DRAWER_CHARS", "80"))
|
|
33
|
+
|
|
34
|
+
# Cap on stored drawer text length (chars). Keeps SQLite happy and FTS indexes
|
|
35
|
+
# from getting comically large. Truncation is fine — verbatim within reason.
|
|
36
|
+
MAX_DRAWER_CHARS = int(os.environ.get("PUPPY_KENNEL_MAX_DRAWER_CHARS", "32000"))
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""The Kennel — storage layer for puppy_kennel.
|
|
2
|
+
|
|
3
|
+
A thin wrapper around SQLite that exposes the wing/room/drawer vocabulary
|
|
4
|
+
without leaking SQL all over the rest of the plugin. Connections are
|
|
5
|
+
short-lived and use a per-call ``with sqlite3.connect(...)`` block so we
|
|
6
|
+
never accidentally hold the write lock across awaits.
|
|
7
|
+
|
|
8
|
+
WAL mode means many readers + one writer is safe across processes.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sqlite3
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Iterator
|
|
20
|
+
|
|
21
|
+
from .config import DB_PATH, MAX_DRAWER_CHARS, KENNEL_ROOT
|
|
22
|
+
from .schema import PRAGMAS, SCHEMA_SQL
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True, frozen=True)
|
|
26
|
+
class Drawer:
|
|
27
|
+
"""A verbatim chunk of remembered content."""
|
|
28
|
+
|
|
29
|
+
id: int
|
|
30
|
+
room_id: int
|
|
31
|
+
role: str | None
|
|
32
|
+
content: str
|
|
33
|
+
ts: str
|
|
34
|
+
session_id: str | None
|
|
35
|
+
metadata: dict[str, Any] | None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _now_iso() -> str:
|
|
39
|
+
return datetime.now(timezone.utc).isoformat(timespec="seconds")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _ensure_root() -> None:
|
|
43
|
+
KENNEL_ROOT.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@contextmanager
|
|
47
|
+
def _connect(path: Path = DB_PATH) -> Iterator[sqlite3.Connection]:
|
|
48
|
+
"""Yield a SQLite connection with the kennel PRAGMAs applied.
|
|
49
|
+
|
|
50
|
+
Short-lived by design: open, do work, close. WAL handles concurrency.
|
|
51
|
+
"""
|
|
52
|
+
_ensure_root()
|
|
53
|
+
conn = sqlite3.connect(path, timeout=10.0, isolation_level=None)
|
|
54
|
+
try:
|
|
55
|
+
conn.row_factory = sqlite3.Row
|
|
56
|
+
for pragma in PRAGMAS:
|
|
57
|
+
conn.execute(pragma)
|
|
58
|
+
yield conn
|
|
59
|
+
finally:
|
|
60
|
+
conn.close()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def initialize() -> None:
|
|
64
|
+
"""Create tables, indexes, and FTS triggers if they don't exist.
|
|
65
|
+
|
|
66
|
+
Idempotent. Safe to call on every plugin load.
|
|
67
|
+
"""
|
|
68
|
+
with _connect() as conn:
|
|
69
|
+
conn.executescript(SCHEMA_SQL)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Wings and rooms (simple upserts)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def ensure_wing(name: str, metadata: dict[str, Any] | None = None) -> int:
|
|
76
|
+
"""Return the id of the wing named ``name``, creating it if missing.
|
|
77
|
+
|
|
78
|
+
Uses ``INSERT OR IGNORE`` to dodge the SELECT-then-INSERT TOCTOU race
|
|
79
|
+
between concurrent processes. The single INSERT is atomic; if another
|
|
80
|
+
writer beat us to it the row already exists and we just re-SELECT.
|
|
81
|
+
"""
|
|
82
|
+
payload = json.dumps(metadata) if metadata else None
|
|
83
|
+
with _connect() as conn:
|
|
84
|
+
conn.execute(
|
|
85
|
+
"INSERT OR IGNORE INTO wings(name, created_at, metadata) VALUES (?, ?, ?)",
|
|
86
|
+
(name, _now_iso(), payload),
|
|
87
|
+
)
|
|
88
|
+
row = conn.execute("SELECT id FROM wings WHERE name = ?", (name,)).fetchone()
|
|
89
|
+
return int(row["id"])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def ensure_room(wing_id: int, name: str, metadata: dict[str, Any] | None = None) -> int:
|
|
93
|
+
"""Return the id of the room (wing_id, name), creating it if missing.
|
|
94
|
+
|
|
95
|
+
Same race-free pattern as ``ensure_wing``.
|
|
96
|
+
"""
|
|
97
|
+
payload = json.dumps(metadata) if metadata else None
|
|
98
|
+
with _connect() as conn:
|
|
99
|
+
conn.execute(
|
|
100
|
+
"INSERT OR IGNORE INTO rooms(wing_id, name, created_at, metadata) "
|
|
101
|
+
"VALUES (?, ?, ?, ?)",
|
|
102
|
+
(wing_id, name, _now_iso(), payload),
|
|
103
|
+
)
|
|
104
|
+
row = conn.execute(
|
|
105
|
+
"SELECT id FROM rooms WHERE wing_id = ? AND name = ?",
|
|
106
|
+
(wing_id, name),
|
|
107
|
+
).fetchone()
|
|
108
|
+
return int(row["id"])
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Drawers — remembered content
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def add_drawer(
|
|
115
|
+
room_id: int,
|
|
116
|
+
content: str,
|
|
117
|
+
role: str | None = None,
|
|
118
|
+
session_id: str | None = None,
|
|
119
|
+
metadata: dict[str, Any] | None = None,
|
|
120
|
+
) -> int:
|
|
121
|
+
"""Append a verbatim drawer to a room. Returns the new drawer id."""
|
|
122
|
+
if not content or not content.strip():
|
|
123
|
+
return 0
|
|
124
|
+
if len(content) > MAX_DRAWER_CHARS:
|
|
125
|
+
content = content[:MAX_DRAWER_CHARS] + "\n...[truncated]"
|
|
126
|
+
payload = json.dumps(metadata) if metadata else None
|
|
127
|
+
with _connect() as conn:
|
|
128
|
+
cur = conn.execute(
|
|
129
|
+
"INSERT INTO drawers(room_id, role, content, ts, session_id, metadata) "
|
|
130
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
131
|
+
(room_id, role, content, _now_iso(), session_id, payload),
|
|
132
|
+
)
|
|
133
|
+
return int(cur.lastrowid)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _row_to_drawer(row: sqlite3.Row) -> Drawer:
|
|
137
|
+
meta_raw = row["metadata"] if "metadata" in row.keys() else None
|
|
138
|
+
meta = json.loads(meta_raw) if meta_raw else None
|
|
139
|
+
return Drawer(
|
|
140
|
+
id=int(row["id"]),
|
|
141
|
+
room_id=int(row["room_id"]),
|
|
142
|
+
role=row["role"],
|
|
143
|
+
content=row["content"],
|
|
144
|
+
ts=row["ts"],
|
|
145
|
+
session_id=row["session_id"],
|
|
146
|
+
metadata=meta,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def recent_drawers(
|
|
151
|
+
wing_name: str, limit: int = 5, role: str | None = None
|
|
152
|
+
) -> list[Drawer]:
|
|
153
|
+
"""Return the most recent drawers in a wing, optionally role-filtered.
|
|
154
|
+
|
|
155
|
+
``role=None`` returns every role (assistant + note + ...). Pass a
|
|
156
|
+
specific value like ``"note"`` to get just sticky writes from
|
|
157
|
+
``kennel_remember``.
|
|
158
|
+
"""
|
|
159
|
+
sql_parts = [
|
|
160
|
+
"SELECT d.* FROM drawers d",
|
|
161
|
+
"JOIN rooms r ON r.id = d.room_id",
|
|
162
|
+
"JOIN wings w ON w.id = r.wing_id",
|
|
163
|
+
"WHERE w.name = ?",
|
|
164
|
+
]
|
|
165
|
+
params: list[Any] = [wing_name]
|
|
166
|
+
if role is not None:
|
|
167
|
+
sql_parts.append("AND d.role = ?")
|
|
168
|
+
params.append(role)
|
|
169
|
+
sql_parts.append("ORDER BY d.ts DESC LIMIT ?")
|
|
170
|
+
params.append(limit)
|
|
171
|
+
with _connect() as conn:
|
|
172
|
+
rows = conn.execute(" ".join(sql_parts), params).fetchall()
|
|
173
|
+
return [_row_to_drawer(r) for r in rows]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def recent_drawers_multi(
|
|
177
|
+
wing_names: list[str] | None = None,
|
|
178
|
+
limit: int = 5,
|
|
179
|
+
) -> list[Drawer]:
|
|
180
|
+
"""Recent drawers across multiple wings, deduplicated by content.
|
|
181
|
+
|
|
182
|
+
Same dual-write workaround as ``search_drawers_multi``: over-fetch then
|
|
183
|
+
dedupe by content, keeping the most recent copy of each unique drawer.
|
|
184
|
+
"""
|
|
185
|
+
sql_parts = [
|
|
186
|
+
"SELECT d.* FROM drawers d",
|
|
187
|
+
"JOIN rooms r ON r.id = d.room_id",
|
|
188
|
+
"JOIN wings w ON w.id = r.wing_id",
|
|
189
|
+
]
|
|
190
|
+
params: list[Any] = []
|
|
191
|
+
if wing_names:
|
|
192
|
+
placeholders = ",".join("?" * len(wing_names))
|
|
193
|
+
sql_parts.append(f"WHERE w.name IN ({placeholders})")
|
|
194
|
+
params.extend(wing_names)
|
|
195
|
+
sql_parts.append("ORDER BY d.ts DESC LIMIT ?")
|
|
196
|
+
params.append(max(limit * 4, limit))
|
|
197
|
+
|
|
198
|
+
with _connect() as conn:
|
|
199
|
+
rows = conn.execute(" ".join(sql_parts), params).fetchall()
|
|
200
|
+
|
|
201
|
+
seen: set[str] = set()
|
|
202
|
+
out: list[Drawer] = []
|
|
203
|
+
for row in rows:
|
|
204
|
+
drawer = _row_to_drawer(row)
|
|
205
|
+
if drawer.content in seen:
|
|
206
|
+
continue
|
|
207
|
+
seen.add(drawer.content)
|
|
208
|
+
out.append(drawer)
|
|
209
|
+
if len(out) >= limit:
|
|
210
|
+
break
|
|
211
|
+
return out
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def write_note(
|
|
215
|
+
wing_name: str,
|
|
216
|
+
room_name: str,
|
|
217
|
+
content: str,
|
|
218
|
+
role: str = "note",
|
|
219
|
+
session_id: str | None = None,
|
|
220
|
+
metadata: dict[str, Any] | None = None,
|
|
221
|
+
) -> int:
|
|
222
|
+
"""High-level helper: ensure (wing, room) exist, then add a drawer.
|
|
223
|
+
|
|
224
|
+
Returns the new drawer id, or 0 if the content was blank.
|
|
225
|
+
"""
|
|
226
|
+
wing_id = ensure_wing(wing_name)
|
|
227
|
+
room_id = ensure_room(wing_id, room_name)
|
|
228
|
+
return add_drawer(
|
|
229
|
+
room_id=room_id,
|
|
230
|
+
content=content,
|
|
231
|
+
role=role,
|
|
232
|
+
session_id=session_id,
|
|
233
|
+
metadata=metadata,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def wings_with_counts() -> list[tuple[str, int]]:
|
|
238
|
+
"""List every wing with its drawer count. Sorted by name."""
|
|
239
|
+
with _connect() as conn:
|
|
240
|
+
rows = conn.execute(
|
|
241
|
+
"""
|
|
242
|
+
SELECT w.name AS name, COUNT(d.id) AS n
|
|
243
|
+
FROM wings w
|
|
244
|
+
LEFT JOIN rooms r ON r.wing_id = w.id
|
|
245
|
+
LEFT JOIN drawers d ON d.room_id = r.id
|
|
246
|
+
GROUP BY w.id, w.name
|
|
247
|
+
ORDER BY w.name
|
|
248
|
+
"""
|
|
249
|
+
).fetchall()
|
|
250
|
+
return [(r["name"], int(r["n"])) for r in rows]
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def search_drawers(
|
|
254
|
+
query: str,
|
|
255
|
+
wing_name: str | None = None,
|
|
256
|
+
limit: int = 5,
|
|
257
|
+
) -> list[Drawer]:
|
|
258
|
+
"""FTS5 BM25 search across drawers, optionally scoped to a single wing."""
|
|
259
|
+
if not query or not query.strip():
|
|
260
|
+
return []
|
|
261
|
+
fts_query = _sanitize_fts_query(query)
|
|
262
|
+
if not fts_query:
|
|
263
|
+
return []
|
|
264
|
+
|
|
265
|
+
sql_parts = [
|
|
266
|
+
"SELECT d.* FROM drawers d",
|
|
267
|
+
"JOIN drawers_fts f ON f.rowid = d.id",
|
|
268
|
+
"JOIN rooms r ON r.id = d.room_id",
|
|
269
|
+
"JOIN wings w ON w.id = r.wing_id",
|
|
270
|
+
"WHERE drawers_fts MATCH ?",
|
|
271
|
+
]
|
|
272
|
+
params: list[Any] = [fts_query]
|
|
273
|
+
if wing_name:
|
|
274
|
+
sql_parts.append("AND w.name = ?")
|
|
275
|
+
params.append(wing_name)
|
|
276
|
+
sql_parts.append("ORDER BY bm25(drawers_fts) ASC LIMIT ?")
|
|
277
|
+
params.append(limit)
|
|
278
|
+
|
|
279
|
+
with _connect() as conn:
|
|
280
|
+
rows = conn.execute(" ".join(sql_parts), params).fetchall()
|
|
281
|
+
return [_row_to_drawer(r) for r in rows]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def search_drawers_multi(
|
|
285
|
+
query: str,
|
|
286
|
+
wing_names: list[str] | None = None,
|
|
287
|
+
limit: int = 5,
|
|
288
|
+
) -> list[Drawer]:
|
|
289
|
+
"""Search multiple wings and dedupe by content.
|
|
290
|
+
|
|
291
|
+
Because the recorder currently dual-writes every response to the repo
|
|
292
|
+
wing AND the agent wing, naive multi-wing search returns the same text
|
|
293
|
+
twice. We over-fetch, then dedupe by content keeping the best-scoring
|
|
294
|
+
drawer per unique text. This is a pragmatic workaround for the schema
|
|
295
|
+
duplication noted in the README; remove once the schema is normalised.
|
|
296
|
+
"""
|
|
297
|
+
if not query or not query.strip():
|
|
298
|
+
return []
|
|
299
|
+
fts_query = _sanitize_fts_query(query)
|
|
300
|
+
if not fts_query:
|
|
301
|
+
return []
|
|
302
|
+
|
|
303
|
+
sql_parts = [
|
|
304
|
+
"SELECT d.* FROM drawers d",
|
|
305
|
+
"JOIN drawers_fts f ON f.rowid = d.id",
|
|
306
|
+
"JOIN rooms r ON r.id = d.room_id",
|
|
307
|
+
"JOIN wings w ON w.id = r.wing_id",
|
|
308
|
+
"WHERE drawers_fts MATCH ?",
|
|
309
|
+
]
|
|
310
|
+
params: list[Any] = [fts_query]
|
|
311
|
+
if wing_names:
|
|
312
|
+
placeholders = ",".join("?" * len(wing_names))
|
|
313
|
+
sql_parts.append(f"AND w.name IN ({placeholders})")
|
|
314
|
+
params.extend(wing_names)
|
|
315
|
+
# Over-fetch so dedup still leaves us with ``limit`` distinct drawers.
|
|
316
|
+
sql_parts.append("ORDER BY bm25(drawers_fts) ASC LIMIT ?")
|
|
317
|
+
params.append(max(limit * 4, limit))
|
|
318
|
+
|
|
319
|
+
with _connect() as conn:
|
|
320
|
+
rows = conn.execute(" ".join(sql_parts), params).fetchall()
|
|
321
|
+
|
|
322
|
+
seen: set[str] = set()
|
|
323
|
+
out: list[Drawer] = []
|
|
324
|
+
for row in rows:
|
|
325
|
+
drawer = _row_to_drawer(row)
|
|
326
|
+
if drawer.content in seen:
|
|
327
|
+
continue
|
|
328
|
+
seen.add(drawer.content)
|
|
329
|
+
out.append(drawer)
|
|
330
|
+
if len(out) >= limit:
|
|
331
|
+
break
|
|
332
|
+
return out
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# Quote each token and OR them: FTS5 punctuation otherwise acts as operators.
|
|
336
|
+
_FTS_BAD = set('"()*:^-+')
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _sanitize_fts_query(query: str) -> str:
|
|
340
|
+
tokens = []
|
|
341
|
+
for raw in query.split():
|
|
342
|
+
stripped = "".join(ch for ch in raw if ch not in _FTS_BAD).strip()
|
|
343
|
+
if len(stripped) >= 2:
|
|
344
|
+
tokens.append(f'"{stripped}"')
|
|
345
|
+
return " OR ".join(tokens)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def list_wings() -> list[str]:
|
|
349
|
+
with _connect() as conn:
|
|
350
|
+
rows = conn.execute("SELECT name FROM wings ORDER BY name").fetchall()
|
|
351
|
+
return [r["name"] for r in rows]
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def count_drawers(wing_name: str | None = None) -> int:
|
|
355
|
+
with _connect() as conn:
|
|
356
|
+
if wing_name is None:
|
|
357
|
+
row = conn.execute("SELECT COUNT(*) AS n FROM drawers").fetchone()
|
|
358
|
+
else:
|
|
359
|
+
row = conn.execute(
|
|
360
|
+
"""
|
|
361
|
+
SELECT COUNT(*) AS n FROM drawers d
|
|
362
|
+
JOIN rooms r ON r.id = d.room_id
|
|
363
|
+
JOIN wings w ON w.id = r.wing_id
|
|
364
|
+
WHERE w.name = ?
|
|
365
|
+
""",
|
|
366
|
+
(wing_name,),
|
|
367
|
+
).fetchone()
|
|
368
|
+
return int(row["n"]) if row else 0
|