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,575 @@
|
|
|
1
|
+
"""File Permission Handler Plugin.
|
|
2
|
+
|
|
3
|
+
This plugin handles user permission prompts for file operations,
|
|
4
|
+
providing a consistent and extensible permission system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import difflib
|
|
8
|
+
import os
|
|
9
|
+
import threading
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from rich.text import Text as RichText
|
|
13
|
+
|
|
14
|
+
from code_puppy.callbacks import register_callback
|
|
15
|
+
from code_puppy.config import get_diff_context_lines, get_yolo_mode
|
|
16
|
+
from code_puppy.tools.file_permission_state import (
|
|
17
|
+
register_file_permission_state_provider,
|
|
18
|
+
)
|
|
19
|
+
from code_puppy.tools.common import (
|
|
20
|
+
_find_best_window,
|
|
21
|
+
get_user_approval,
|
|
22
|
+
get_user_approval_async,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# NOTE: module-level ``_FILE_CONFIRMATION_LOCK`` removed — queueing of parallel
|
|
26
|
+
# approvals now lives inside ``get_user_approval`` (they line up, never auto-rejected).
|
|
27
|
+
|
|
28
|
+
# Thread-local storage for user feedback from permission prompts
|
|
29
|
+
_thread_local = threading.local()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_last_user_feedback() -> str | None:
|
|
33
|
+
"""Get the last user feedback from a permission prompt in this thread.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The user feedback string, or None if no feedback was provided.
|
|
37
|
+
"""
|
|
38
|
+
return getattr(_thread_local, "last_user_feedback", None)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _set_user_feedback(feedback: str | None) -> None:
|
|
42
|
+
"""Store user feedback in thread-local storage."""
|
|
43
|
+
_thread_local.last_user_feedback = feedback
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def clear_user_feedback() -> None:
|
|
47
|
+
"""Clear any stored user feedback."""
|
|
48
|
+
_thread_local.last_user_feedback = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def set_diff_already_shown(shown: bool = True) -> None:
|
|
52
|
+
"""Mark that a diff preview was already shown during permission prompt."""
|
|
53
|
+
_thread_local.diff_already_shown = shown
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def was_diff_already_shown() -> bool:
|
|
57
|
+
"""Check if a diff was already shown during the permission prompt.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
True if diff was shown, False otherwise
|
|
61
|
+
"""
|
|
62
|
+
return getattr(_thread_local, "diff_already_shown", False)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def clear_diff_shown_flag() -> None:
|
|
66
|
+
"""Clear the diff-already-shown flag."""
|
|
67
|
+
_thread_local.diff_already_shown = False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Diff formatting is now handled by common.format_diff_with_colors()
|
|
71
|
+
# Arrow selector and approval UI now handled by common.get_user_approval()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _preview_delete_snippet(file_path: str, snippet: str) -> str | None:
|
|
75
|
+
"""Generate a preview diff for deleting a snippet without modifying the file."""
|
|
76
|
+
try:
|
|
77
|
+
file_path = os.path.abspath(file_path)
|
|
78
|
+
if not os.path.exists(file_path) or not os.path.isfile(file_path):
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
with open(file_path, "r", encoding="utf-8", errors="surrogateescape") as f:
|
|
82
|
+
original = f.read()
|
|
83
|
+
|
|
84
|
+
# Sanitize any surrogate characters
|
|
85
|
+
try:
|
|
86
|
+
original = original.encode("utf-8", errors="surrogatepass").decode(
|
|
87
|
+
"utf-8", errors="replace"
|
|
88
|
+
)
|
|
89
|
+
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
if snippet not in original:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
modified = original.replace(snippet, "")
|
|
96
|
+
diff_text = "".join(
|
|
97
|
+
difflib.unified_diff(
|
|
98
|
+
original.splitlines(keepends=True),
|
|
99
|
+
modified.splitlines(keepends=True),
|
|
100
|
+
fromfile=f"a/{os.path.basename(file_path)}",
|
|
101
|
+
tofile=f"b/{os.path.basename(file_path)}",
|
|
102
|
+
n=get_diff_context_lines(),
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
return diff_text
|
|
106
|
+
except Exception:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _preview_write_to_file(
|
|
111
|
+
file_path: str, content: str, overwrite: bool = False
|
|
112
|
+
) -> str | None:
|
|
113
|
+
"""Generate a preview diff for writing to a file without modifying it."""
|
|
114
|
+
try:
|
|
115
|
+
file_path = os.path.abspath(file_path)
|
|
116
|
+
exists = os.path.exists(file_path)
|
|
117
|
+
|
|
118
|
+
if exists and not overwrite:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
diff_lines = difflib.unified_diff(
|
|
122
|
+
[] if not exists else [""],
|
|
123
|
+
content.splitlines(keepends=True),
|
|
124
|
+
fromfile="/dev/null" if not exists else f"a/{os.path.basename(file_path)}",
|
|
125
|
+
tofile=f"b/{os.path.basename(file_path)}",
|
|
126
|
+
n=get_diff_context_lines(),
|
|
127
|
+
)
|
|
128
|
+
return "".join(diff_lines)
|
|
129
|
+
except Exception:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _preview_replace_in_file(
|
|
134
|
+
file_path: str, replacements: list[dict[str, str]]
|
|
135
|
+
) -> str | None:
|
|
136
|
+
"""Generate a preview diff for replacing text in a file without modifying the file."""
|
|
137
|
+
try:
|
|
138
|
+
file_path = os.path.abspath(file_path)
|
|
139
|
+
|
|
140
|
+
with open(file_path, "r", encoding="utf-8", errors="surrogateescape") as f:
|
|
141
|
+
original = f.read()
|
|
142
|
+
|
|
143
|
+
# Sanitize any surrogate characters
|
|
144
|
+
try:
|
|
145
|
+
original = original.encode("utf-8", errors="surrogatepass").decode(
|
|
146
|
+
"utf-8", errors="replace"
|
|
147
|
+
)
|
|
148
|
+
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
modified = original
|
|
152
|
+
for rep in replacements:
|
|
153
|
+
old_snippet = rep.get("old_str", "")
|
|
154
|
+
new_snippet = rep.get("new_str", "")
|
|
155
|
+
|
|
156
|
+
if old_snippet and old_snippet in modified:
|
|
157
|
+
modified = modified.replace(old_snippet, new_snippet)
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
# Use the same logic as file_modifications for fuzzy matching
|
|
161
|
+
orig_lines = modified.splitlines()
|
|
162
|
+
loc, score = _find_best_window(orig_lines, old_snippet)
|
|
163
|
+
|
|
164
|
+
if score < 0.95 or loc is None:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
start, end = loc
|
|
168
|
+
had_trailing_newline = modified.endswith("\n")
|
|
169
|
+
prefix = "\n".join(orig_lines[:start])
|
|
170
|
+
suffix = "\n".join(orig_lines[end:])
|
|
171
|
+
parts = []
|
|
172
|
+
if prefix:
|
|
173
|
+
parts.append(prefix)
|
|
174
|
+
parts.append(new_snippet.rstrip("\n"))
|
|
175
|
+
if suffix:
|
|
176
|
+
parts.append(suffix)
|
|
177
|
+
modified = "\n".join(parts)
|
|
178
|
+
if had_trailing_newline and not modified.endswith("\n"):
|
|
179
|
+
modified += "\n"
|
|
180
|
+
|
|
181
|
+
if modified == original:
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
diff_text = "".join(
|
|
185
|
+
difflib.unified_diff(
|
|
186
|
+
original.splitlines(keepends=True),
|
|
187
|
+
modified.splitlines(keepends=True),
|
|
188
|
+
fromfile=f"a/{os.path.basename(file_path)}",
|
|
189
|
+
tofile=f"b/{os.path.basename(file_path)}",
|
|
190
|
+
n=get_diff_context_lines(),
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
return diff_text
|
|
194
|
+
except Exception:
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _preview_delete_file(file_path: str) -> str | None:
|
|
199
|
+
"""Generate a preview diff for deleting a file without modifying it."""
|
|
200
|
+
try:
|
|
201
|
+
file_path = os.path.abspath(file_path)
|
|
202
|
+
if not os.path.exists(file_path) or not os.path.isfile(file_path):
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
with open(file_path, "r", encoding="utf-8", errors="surrogateescape") as f:
|
|
206
|
+
original = f.read()
|
|
207
|
+
|
|
208
|
+
# Sanitize any surrogate characters
|
|
209
|
+
try:
|
|
210
|
+
original = original.encode("utf-8", errors="surrogatepass").decode(
|
|
211
|
+
"utf-8", errors="replace"
|
|
212
|
+
)
|
|
213
|
+
except (UnicodeEncodeError, UnicodeDecodeError):
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
diff_text = "".join(
|
|
217
|
+
difflib.unified_diff(
|
|
218
|
+
original.splitlines(keepends=True),
|
|
219
|
+
[],
|
|
220
|
+
fromfile=f"a/{os.path.basename(file_path)}",
|
|
221
|
+
tofile=f"b/{os.path.basename(file_path)}",
|
|
222
|
+
n=get_diff_context_lines(),
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
return diff_text
|
|
226
|
+
except Exception:
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def prompt_for_file_permission(
|
|
231
|
+
file_path: str,
|
|
232
|
+
operation: str,
|
|
233
|
+
preview: str | None = None,
|
|
234
|
+
message_group: str | None = None,
|
|
235
|
+
) -> tuple[bool, str | None]:
|
|
236
|
+
"""Prompt the user for permission to perform a file operation.
|
|
237
|
+
|
|
238
|
+
This function provides a unified permission prompt system for all file operations.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
file_path: Path to the file being modified.
|
|
242
|
+
operation: Description of the operation (e.g., "edit", "delete", "create").
|
|
243
|
+
preview: Optional preview of changes (diff or content preview).
|
|
244
|
+
message_group: Optional message group for organizing output.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Tuple of (confirmed: bool, user_feedback: str | None)
|
|
248
|
+
- confirmed: True if permission is granted, False otherwise
|
|
249
|
+
- user_feedback: Optional feedback message from user to send back to the model
|
|
250
|
+
"""
|
|
251
|
+
yolo_mode = get_yolo_mode()
|
|
252
|
+
|
|
253
|
+
# Skip confirmation only if in yolo mode (removed TTY check for better compatibility)
|
|
254
|
+
if yolo_mode:
|
|
255
|
+
return True, None
|
|
256
|
+
|
|
257
|
+
# Build panel content
|
|
258
|
+
panel_content = RichText()
|
|
259
|
+
panel_content.append("🔒 Requesting permission to ", style="bold yellow")
|
|
260
|
+
panel_content.append(operation, style="bold cyan")
|
|
261
|
+
panel_content.append(":\n", style="bold yellow")
|
|
262
|
+
panel_content.append("📄 ", style="dim")
|
|
263
|
+
panel_content.append(file_path, style="bold white")
|
|
264
|
+
|
|
265
|
+
# Use the common approval function; internal queueing makes parallel callers
|
|
266
|
+
# wait their turn here rather than being silently auto-rejected.
|
|
267
|
+
return get_user_approval(
|
|
268
|
+
title="File Operation",
|
|
269
|
+
content=panel_content,
|
|
270
|
+
preview=preview,
|
|
271
|
+
border_style="dim white",
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def handle_edit_file_permission(
|
|
276
|
+
context: Any,
|
|
277
|
+
file_path: str,
|
|
278
|
+
operation_type: str,
|
|
279
|
+
operation_data: Any,
|
|
280
|
+
message_group: str | None = None,
|
|
281
|
+
) -> bool:
|
|
282
|
+
"""Handle permission for edit_file operations with automatic preview generation.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
context: The operation context
|
|
286
|
+
file_path: Path to the file being operated on
|
|
287
|
+
operation_type: Type of edit operation ('write', 'replace', 'delete_snippet')
|
|
288
|
+
operation_data: Operation-specific data (content, replacements, snippet, etc.)
|
|
289
|
+
message_group: Optional message group
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
True if permission granted, False if denied
|
|
293
|
+
"""
|
|
294
|
+
preview = None
|
|
295
|
+
|
|
296
|
+
if operation_type == "write":
|
|
297
|
+
content = operation_data.get("content", "")
|
|
298
|
+
overwrite = operation_data.get("overwrite", False)
|
|
299
|
+
preview = _preview_write_to_file(file_path, content, overwrite)
|
|
300
|
+
operation_desc = "write to"
|
|
301
|
+
elif operation_type == "replace":
|
|
302
|
+
replacements = operation_data.get("replacements", [])
|
|
303
|
+
preview = _preview_replace_in_file(file_path, replacements)
|
|
304
|
+
operation_desc = "replace text in"
|
|
305
|
+
elif operation_type == "delete_snippet":
|
|
306
|
+
snippet = operation_data.get("delete_snippet", "")
|
|
307
|
+
preview = _preview_delete_snippet(file_path, snippet)
|
|
308
|
+
operation_desc = "delete snippet from"
|
|
309
|
+
else:
|
|
310
|
+
operation_desc = f"perform {operation_type} operation on"
|
|
311
|
+
|
|
312
|
+
confirmed, user_feedback = prompt_for_file_permission(
|
|
313
|
+
file_path, operation_desc, preview, message_group
|
|
314
|
+
)
|
|
315
|
+
# Store feedback in thread-local storage so the tool can access it
|
|
316
|
+
_set_user_feedback(user_feedback)
|
|
317
|
+
return confirmed
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def handle_delete_file_permission(
|
|
321
|
+
context: Any,
|
|
322
|
+
file_path: str,
|
|
323
|
+
message_group: str | None = None,
|
|
324
|
+
) -> bool:
|
|
325
|
+
"""Handle permission for delete_file operations with automatic preview generation.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
context: The operation context
|
|
329
|
+
file_path: Path to the file being deleted
|
|
330
|
+
message_group: Optional message group
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
True if permission granted, False if denied
|
|
334
|
+
"""
|
|
335
|
+
preview = _preview_delete_file(file_path)
|
|
336
|
+
confirmed, user_feedback = prompt_for_file_permission(
|
|
337
|
+
file_path, "delete", preview, message_group
|
|
338
|
+
)
|
|
339
|
+
# Store feedback in thread-local storage so the tool can access it
|
|
340
|
+
_set_user_feedback(user_feedback)
|
|
341
|
+
return confirmed
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def handle_file_permission(
|
|
345
|
+
context: Any,
|
|
346
|
+
file_path: str,
|
|
347
|
+
operation: str,
|
|
348
|
+
preview: str | None = None,
|
|
349
|
+
message_group: str | None = None,
|
|
350
|
+
operation_data: Any = None,
|
|
351
|
+
) -> bool:
|
|
352
|
+
"""Callback handler for file permission checks.
|
|
353
|
+
|
|
354
|
+
This function is called by file operations to check for user permission.
|
|
355
|
+
It returns True if the operation should proceed, False if it should be cancelled.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
context: The operation context
|
|
359
|
+
file_path: Path to the file being operated on
|
|
360
|
+
operation: Description of the operation
|
|
361
|
+
preview: Optional preview of changes (deprecated - use operation_data instead)
|
|
362
|
+
message_group: Optional message group
|
|
363
|
+
operation_data: Operation-specific data for preview generation
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
True if permission granted, False if denied
|
|
367
|
+
"""
|
|
368
|
+
# Generate preview from operation_data if provided
|
|
369
|
+
if operation_data is not None:
|
|
370
|
+
preview = _generate_preview_from_operation_data(
|
|
371
|
+
file_path, operation, operation_data
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
confirmed, user_feedback = prompt_for_file_permission(
|
|
375
|
+
file_path, operation, preview, message_group
|
|
376
|
+
)
|
|
377
|
+
# Store feedback in thread-local storage so the tool can access it
|
|
378
|
+
_set_user_feedback(user_feedback)
|
|
379
|
+
return confirmed
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _generate_preview_from_operation_data(
|
|
383
|
+
file_path: str, operation: str, operation_data: Any
|
|
384
|
+
) -> str | None:
|
|
385
|
+
"""Generate preview diff from operation data.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
file_path: Path to the file
|
|
389
|
+
operation: Type of operation
|
|
390
|
+
operation_data: Operation-specific data
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Preview diff or None if generation fails
|
|
394
|
+
"""
|
|
395
|
+
try:
|
|
396
|
+
if operation == "delete":
|
|
397
|
+
return _preview_delete_file(file_path)
|
|
398
|
+
elif operation == "write":
|
|
399
|
+
content = operation_data.get("content", "")
|
|
400
|
+
overwrite = operation_data.get("overwrite", False)
|
|
401
|
+
return _preview_write_to_file(file_path, content, overwrite)
|
|
402
|
+
elif operation == "delete snippet from":
|
|
403
|
+
snippet = operation_data.get("snippet", "")
|
|
404
|
+
return _preview_delete_snippet(file_path, snippet)
|
|
405
|
+
elif operation == "replace text in":
|
|
406
|
+
replacements = operation_data.get("replacements", [])
|
|
407
|
+
return _preview_replace_in_file(file_path, replacements)
|
|
408
|
+
elif operation == "edit_file":
|
|
409
|
+
# Handle edit_file operations
|
|
410
|
+
if "delete_snippet" in operation_data:
|
|
411
|
+
return _preview_delete_snippet(
|
|
412
|
+
file_path, operation_data["delete_snippet"]
|
|
413
|
+
)
|
|
414
|
+
elif "replacements" in operation_data:
|
|
415
|
+
return _preview_replace_in_file(
|
|
416
|
+
file_path, operation_data["replacements"]
|
|
417
|
+
)
|
|
418
|
+
elif "content" in operation_data:
|
|
419
|
+
content = operation_data.get("content", "")
|
|
420
|
+
overwrite = operation_data.get("overwrite", False)
|
|
421
|
+
return _preview_write_to_file(file_path, content, overwrite)
|
|
422
|
+
|
|
423
|
+
return None
|
|
424
|
+
except Exception:
|
|
425
|
+
return None
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def get_permission_handler_help() -> str:
|
|
429
|
+
"""Return help information for the file permission handler."""
|
|
430
|
+
return """File Permission Handler Plugin:
|
|
431
|
+
- Unified permission prompts for all file operations
|
|
432
|
+
- YOLO mode support for automatic approval
|
|
433
|
+
- Thread-safe confirmation system
|
|
434
|
+
- Consistent user experience across file operations
|
|
435
|
+
- Detailed preview support with diff highlighting
|
|
436
|
+
- Automatic preview generation from operation data"""
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def get_file_permission_prompt_additions() -> str:
|
|
440
|
+
"""Return file permission handling prompt additions for agents."""
|
|
441
|
+
if get_yolo_mode():
|
|
442
|
+
return ""
|
|
443
|
+
|
|
444
|
+
return """
|
|
445
|
+
## User Approval System
|
|
446
|
+
|
|
447
|
+
When file operations are rejected, the response includes a `user_feedback` field:
|
|
448
|
+
- If `user_feedback` has text: implement their suggestion and retry the operation.
|
|
449
|
+
- If `user_feedback` is empty: stop and ask the user what they want instead.
|
|
450
|
+
- Never retry the exact same rejected operation without changes.
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
async def prompt_for_file_permission_async(
|
|
455
|
+
file_path: str,
|
|
456
|
+
operation: str,
|
|
457
|
+
preview: str | None = None,
|
|
458
|
+
message_group: str | None = None,
|
|
459
|
+
) -> tuple[bool, str | None]:
|
|
460
|
+
"""Async sibling of :func:`prompt_for_file_permission`.
|
|
461
|
+
|
|
462
|
+
Uses :func:`get_user_approval_async` so prompt_toolkit cooperates
|
|
463
|
+
with the running asyncio loop instead of bailing out via
|
|
464
|
+
``arrow_select() called from async context``.
|
|
465
|
+
"""
|
|
466
|
+
if get_yolo_mode():
|
|
467
|
+
return True, None
|
|
468
|
+
|
|
469
|
+
panel_content = RichText()
|
|
470
|
+
panel_content.append("\U0001f512 Requesting permission to ", style="bold yellow")
|
|
471
|
+
panel_content.append(operation, style="bold cyan")
|
|
472
|
+
panel_content.append(":\n", style="bold yellow")
|
|
473
|
+
panel_content.append("\U0001f4c4 ", style="dim")
|
|
474
|
+
panel_content.append(file_path, style="bold white")
|
|
475
|
+
|
|
476
|
+
return await get_user_approval_async(
|
|
477
|
+
title="File Operation",
|
|
478
|
+
content=panel_content,
|
|
479
|
+
preview=preview,
|
|
480
|
+
border_style="dim white",
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
async def handle_edit_file_permission_async(
|
|
485
|
+
context: Any,
|
|
486
|
+
file_path: str,
|
|
487
|
+
operation_type: str,
|
|
488
|
+
operation_data: Any,
|
|
489
|
+
message_group: str | None = None,
|
|
490
|
+
) -> bool:
|
|
491
|
+
"""Async sibling of :func:`handle_edit_file_permission`."""
|
|
492
|
+
preview = None
|
|
493
|
+
|
|
494
|
+
if operation_type == "write":
|
|
495
|
+
content = operation_data.get("content", "")
|
|
496
|
+
overwrite = operation_data.get("overwrite", False)
|
|
497
|
+
preview = _preview_write_to_file(file_path, content, overwrite)
|
|
498
|
+
operation_desc = "write to"
|
|
499
|
+
elif operation_type == "replace":
|
|
500
|
+
replacements = operation_data.get("replacements", [])
|
|
501
|
+
preview = _preview_replace_in_file(file_path, replacements)
|
|
502
|
+
operation_desc = "replace text in"
|
|
503
|
+
elif operation_type == "delete_snippet":
|
|
504
|
+
snippet = operation_data.get("delete_snippet", "")
|
|
505
|
+
preview = _preview_delete_snippet(file_path, snippet)
|
|
506
|
+
operation_desc = "delete snippet from"
|
|
507
|
+
else:
|
|
508
|
+
operation_desc = f"perform {operation_type} operation on"
|
|
509
|
+
|
|
510
|
+
confirmed, user_feedback = await prompt_for_file_permission_async(
|
|
511
|
+
file_path, operation_desc, preview, message_group
|
|
512
|
+
)
|
|
513
|
+
_set_user_feedback(user_feedback)
|
|
514
|
+
return confirmed
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
async def handle_delete_file_permission_async(
|
|
518
|
+
context: Any,
|
|
519
|
+
file_path: str,
|
|
520
|
+
message_group: str | None = None,
|
|
521
|
+
) -> bool:
|
|
522
|
+
"""Async sibling of :func:`handle_delete_file_permission`."""
|
|
523
|
+
preview = _preview_delete_file(file_path)
|
|
524
|
+
confirmed, user_feedback = await prompt_for_file_permission_async(
|
|
525
|
+
file_path, "delete", preview, message_group
|
|
526
|
+
)
|
|
527
|
+
_set_user_feedback(user_feedback)
|
|
528
|
+
return confirmed
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
async def handle_file_permission_async(
|
|
532
|
+
context: Any,
|
|
533
|
+
file_path: str,
|
|
534
|
+
operation: str,
|
|
535
|
+
preview: str | None = None,
|
|
536
|
+
message_group: str | None = None,
|
|
537
|
+
operation_data: Any = None,
|
|
538
|
+
) -> bool:
|
|
539
|
+
"""Async sibling of :func:`handle_file_permission`.
|
|
540
|
+
|
|
541
|
+
This is the variant registered on the ``file_permission`` hook so
|
|
542
|
+
callers in async contexts (e.g. ``on_file_permission_async``)
|
|
543
|
+
don't trip the ``arrow_select() called from async context`` guard.
|
|
544
|
+
The async dispatcher awaits the coroutine; the sync dispatcher
|
|
545
|
+
handles it by running it on a fresh loop when invoked from a
|
|
546
|
+
worker thread.
|
|
547
|
+
"""
|
|
548
|
+
if operation_data is not None:
|
|
549
|
+
preview = _generate_preview_from_operation_data(
|
|
550
|
+
file_path, operation, operation_data
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
confirmed, user_feedback = await prompt_for_file_permission_async(
|
|
554
|
+
file_path, operation, preview, message_group
|
|
555
|
+
)
|
|
556
|
+
_set_user_feedback(user_feedback)
|
|
557
|
+
return confirmed
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
# Expose the thread-local UX-state accessors to core via this small provider API
|
|
561
|
+
# (avoids core importing this plugin module to reach diff-shown flag / feedback).
|
|
562
|
+
_STATE_PROVIDER_REGISTRATION = register_file_permission_state_provider(
|
|
563
|
+
set_diff_already_shown=set_diff_already_shown,
|
|
564
|
+
was_diff_already_shown=was_diff_already_shown,
|
|
565
|
+
clear_diff_shown_flag=clear_diff_shown_flag,
|
|
566
|
+
get_last_user_feedback=get_last_user_feedback,
|
|
567
|
+
clear_user_feedback=clear_user_feedback,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Register the async callback: awaited directly by the async dispatcher; the sync
|
|
571
|
+
# dispatcher runs it via ``asyncio.run`` when triggered from a thread without a loop.
|
|
572
|
+
register_callback("file_permission", handle_file_permission_async)
|
|
573
|
+
|
|
574
|
+
# Register the prompt hook for file permission instructions
|
|
575
|
+
register_callback("load_prompt", get_file_permission_prompt_additions)
|