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.
Files changed (313) hide show
  1. code_puppy_core_plugins/__init__.py +5 -0
  2. code_puppy_core_plugins/acp/README.md +266 -0
  3. code_puppy_core_plugins/acp/__init__.py +8 -0
  4. code_puppy_core_plugins/acp/agent.py +494 -0
  5. code_puppy_core_plugins/acp/bridge.py +309 -0
  6. code_puppy_core_plugins/acp/capabilities.py +73 -0
  7. code_puppy_core_plugins/acp/commands.py +116 -0
  8. code_puppy_core_plugins/acp/content.py +115 -0
  9. code_puppy_core_plugins/acp/io_delegation.py +231 -0
  10. code_puppy_core_plugins/acp/mcp_config.py +66 -0
  11. code_puppy_core_plugins/acp/permissions.py +159 -0
  12. code_puppy_core_plugins/acp/persistence.py +197 -0
  13. code_puppy_core_plugins/acp/register_callbacks.py +110 -0
  14. code_puppy_core_plugins/acp/replay.py +114 -0
  15. code_puppy_core_plugins/acp/session.py +329 -0
  16. code_puppy_core_plugins/acp/session_config.py +179 -0
  17. code_puppy_core_plugins/acp/state.py +135 -0
  18. code_puppy_core_plugins/agent_skills/__init__.py +22 -0
  19. code_puppy_core_plugins/agent_skills/config.py +208 -0
  20. code_puppy_core_plugins/agent_skills/discovery.py +337 -0
  21. code_puppy_core_plugins/agent_skills/downloader.py +392 -0
  22. code_puppy_core_plugins/agent_skills/enabled_skills.py +82 -0
  23. code_puppy_core_plugins/agent_skills/installer.py +22 -0
  24. code_puppy_core_plugins/agent_skills/metadata.py +219 -0
  25. code_puppy_core_plugins/agent_skills/prompt_builder.py +49 -0
  26. code_puppy_core_plugins/agent_skills/provider.py +52 -0
  27. code_puppy_core_plugins/agent_skills/register_callbacks.py +309 -0
  28. code_puppy_core_plugins/agent_skills/remote_catalog.py +322 -0
  29. code_puppy_core_plugins/agent_skills/skill_catalog.py +257 -0
  30. code_puppy_core_plugins/agent_skills/skill_commands.py +102 -0
  31. code_puppy_core_plugins/agent_skills/skills_install_menu.py +698 -0
  32. code_puppy_core_plugins/agent_skills/skills_menu.py +794 -0
  33. code_puppy_core_plugins/aws_bedrock/__init__.py +14 -0
  34. code_puppy_core_plugins/aws_bedrock/config.py +101 -0
  35. code_puppy_core_plugins/aws_bedrock/register_callbacks.py +243 -0
  36. code_puppy_core_plugins/aws_bedrock/utils.py +186 -0
  37. code_puppy_core_plugins/azure_foundry/README.md +238 -0
  38. code_puppy_core_plugins/azure_foundry/__init__.py +15 -0
  39. code_puppy_core_plugins/azure_foundry/config.py +126 -0
  40. code_puppy_core_plugins/azure_foundry/discovery.py +189 -0
  41. code_puppy_core_plugins/azure_foundry/register_callbacks.py +506 -0
  42. code_puppy_core_plugins/azure_foundry/token.py +182 -0
  43. code_puppy_core_plugins/azure_foundry/utils.py +375 -0
  44. code_puppy_core_plugins/btw/__init__.py +1 -0
  45. code_puppy_core_plugins/btw/inline_view.py +135 -0
  46. code_puppy_core_plugins/btw/register_callbacks.py +106 -0
  47. code_puppy_core_plugins/btw/side_query.py +91 -0
  48. code_puppy_core_plugins/chatgpt_oauth/IMAGEGEN_SKILL.md +83 -0
  49. code_puppy_core_plugins/chatgpt_oauth/__init__.py +8 -0
  50. code_puppy_core_plugins/chatgpt_oauth/config.py +52 -0
  51. code_puppy_core_plugins/chatgpt_oauth/image_generation.py +253 -0
  52. code_puppy_core_plugins/chatgpt_oauth/image_tool.py +70 -0
  53. code_puppy_core_plugins/chatgpt_oauth/oauth_flow.py +461 -0
  54. code_puppy_core_plugins/chatgpt_oauth/register_callbacks.py +288 -0
  55. code_puppy_core_plugins/chatgpt_oauth/test_plugin.py +663 -0
  56. code_puppy_core_plugins/chatgpt_oauth/usage.py +113 -0
  57. code_puppy_core_plugins/chatgpt_oauth/utils.py +534 -0
  58. code_puppy_core_plugins/claude_code_hooks/__init__.py +1 -0
  59. code_puppy_core_plugins/claude_code_hooks/config.py +137 -0
  60. code_puppy_core_plugins/claude_code_hooks/register_callbacks.py +383 -0
  61. code_puppy_core_plugins/claude_code_oauth/README.md +167 -0
  62. code_puppy_core_plugins/claude_code_oauth/SETUP.md +93 -0
  63. code_puppy_core_plugins/claude_code_oauth/__init__.py +25 -0
  64. code_puppy_core_plugins/claude_code_oauth/config.py +59 -0
  65. code_puppy_core_plugins/claude_code_oauth/fast_mode.py +122 -0
  66. code_puppy_core_plugins/claude_code_oauth/prompt_handler.py +64 -0
  67. code_puppy_core_plugins/claude_code_oauth/register_callbacks.py +719 -0
  68. code_puppy_core_plugins/claude_code_oauth/test_plugin.py +285 -0
  69. code_puppy_core_plugins/claude_code_oauth/token_refresh_heartbeat.py +240 -0
  70. code_puppy_core_plugins/claude_code_oauth/utils.py +663 -0
  71. code_puppy_core_plugins/code_puppy_agent/AGENTS_AND_TOOLS.md +155 -0
  72. code_puppy_core_plugins/code_puppy_agent/MODELS_AND_MCP.md +104 -0
  73. code_puppy_core_plugins/code_puppy_agent/PLUGINS_AND_CALLBACKS.md +147 -0
  74. code_puppy_core_plugins/code_puppy_agent/SESSIONS_AND_HISTORY.md +39 -0
  75. code_puppy_core_plugins/code_puppy_agent/SKILL.md +180 -0
  76. code_puppy_core_plugins/code_puppy_agent/SKILLS_SYSTEM.md +136 -0
  77. code_puppy_core_plugins/code_puppy_agent/SYSTEM_PROMPT_CONFIG_AND_I18N.md +160 -0
  78. code_puppy_core_plugins/code_puppy_agent/__init__.py +5 -0
  79. code_puppy_core_plugins/code_puppy_agent/register_callbacks.py +46 -0
  80. code_puppy_core_plugins/computer_use/NativeCapture.swift +123 -0
  81. code_puppy_core_plugins/computer_use/README.md +109 -0
  82. code_puppy_core_plugins/computer_use/__init__.py +1 -0
  83. code_puppy_core_plugins/computer_use/accessibility.py +163 -0
  84. code_puppy_core_plugins/computer_use/activation.py +75 -0
  85. code_puppy_core_plugins/computer_use/backend.py +584 -0
  86. code_puppy_core_plugins/computer_use/backend_types.py +5 -0
  87. code_puppy_core_plugins/computer_use/capture.py +110 -0
  88. code_puppy_core_plugins/computer_use/commands.py +62 -0
  89. code_puppy_core_plugins/computer_use/geometry.py +81 -0
  90. code_puppy_core_plugins/computer_use/inline_image.py +56 -0
  91. code_puppy_core_plugins/computer_use/keycodes.py +47 -0
  92. code_puppy_core_plugins/computer_use/policy.py +144 -0
  93. code_puppy_core_plugins/computer_use/register_callbacks.py +96 -0
  94. code_puppy_core_plugins/computer_use/safety.py +18 -0
  95. code_puppy_core_plugins/computer_use/settle.py +64 -0
  96. code_puppy_core_plugins/computer_use/state.py +86 -0
  97. code_puppy_core_plugins/computer_use/tools.py +469 -0
  98. code_puppy_core_plugins/context_indicator/__init__.py +14 -0
  99. code_puppy_core_plugins/context_indicator/register_callbacks.py +249 -0
  100. code_puppy_core_plugins/context_indicator/usage.py +40 -0
  101. code_puppy_core_plugins/copilot_auth/__init__.py +11 -0
  102. code_puppy_core_plugins/copilot_auth/config.py +93 -0
  103. code_puppy_core_plugins/copilot_auth/reasoning_client.py +409 -0
  104. code_puppy_core_plugins/copilot_auth/register_callbacks.py +460 -0
  105. code_puppy_core_plugins/copilot_auth/utils.py +587 -0
  106. code_puppy_core_plugins/customizable_commands/__init__.py +0 -0
  107. code_puppy_core_plugins/customizable_commands/register_callbacks.py +464 -0
  108. code_puppy_core_plugins/dbos_durable_exec/__init__.py +1 -0
  109. code_puppy_core_plugins/dbos_durable_exec/cancel.py +15 -0
  110. code_puppy_core_plugins/dbos_durable_exec/commands.py +31 -0
  111. code_puppy_core_plugins/dbos_durable_exec/config.py +31 -0
  112. code_puppy_core_plugins/dbos_durable_exec/lifecycle.py +83 -0
  113. code_puppy_core_plugins/dbos_durable_exec/register_callbacks.py +45 -0
  114. code_puppy_core_plugins/dbos_durable_exec/runtime.py +65 -0
  115. code_puppy_core_plugins/dbos_durable_exec/startup_lock.py +155 -0
  116. code_puppy_core_plugins/dbos_durable_exec/workflow_ids.py +16 -0
  117. code_puppy_core_plugins/dbos_durable_exec/wrapper.py +58 -0
  118. code_puppy_core_plugins/destructive_command_guard/AGENTS.md +79 -0
  119. code_puppy_core_plugins/destructive_command_guard/__init__.py +14 -0
  120. code_puppy_core_plugins/destructive_command_guard/detector.py +374 -0
  121. code_puppy_core_plugins/destructive_command_guard/register_callbacks.py +165 -0
  122. code_puppy_core_plugins/emoji_filter/__init__.py +12 -0
  123. code_puppy_core_plugins/emoji_filter/config.py +26 -0
  124. code_puppy_core_plugins/emoji_filter/register_callbacks.py +238 -0
  125. code_puppy_core_plugins/emoji_filter/stripper.py +45 -0
  126. code_puppy_core_plugins/empty_html_comment_filter/register_callbacks.py +116 -0
  127. code_puppy_core_plugins/example_custom_command/README.md +328 -0
  128. code_puppy_core_plugins/example_custom_command/register_callbacks.py +66 -0
  129. code_puppy_core_plugins/file_permission_handler/__init__.py +4 -0
  130. code_puppy_core_plugins/file_permission_handler/register_callbacks.py +575 -0
  131. code_puppy_core_plugins/flux_bootstrap/__init__.py +5 -0
  132. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/README.md +173 -0
  133. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/cheatsheet.md +86 -0
  134. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/pipeline.md +167 -0
  135. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/synopsis.md +392 -0
  136. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/about.md +51 -0
  137. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/address-feedback.md +129 -0
  138. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/ask.md +211 -0
  139. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/aug.md +181 -0
  140. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/auto-pilot.md +71 -0
  141. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/cheatsheet.md +16 -0
  142. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/commit.md +103 -0
  143. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/config.md +108 -0
  144. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/create-pr.md +195 -0
  145. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/exec.md +221 -0
  146. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/new.md +151 -0
  147. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/qa.md +208 -0
  148. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/rebase.md +180 -0
  149. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/review.md +304 -0
  150. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/split.md +120 -0
  151. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/squash-commits.md +179 -0
  152. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/status.md +46 -0
  153. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/tests.md +155 -0
  154. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_about.py +154 -0
  155. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_cheatsheet.py +248 -0
  156. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_status.py +345 -0
  157. code_puppy_core_plugins/flux_bootstrap/installer.py +256 -0
  158. code_puppy_core_plugins/flux_bootstrap/register_callbacks.py +71 -0
  159. code_puppy_core_plugins/force_push_guard/__init__.py +5 -0
  160. code_puppy_core_plugins/force_push_guard/detector.py +96 -0
  161. code_puppy_core_plugins/force_push_guard/register_callbacks.py +161 -0
  162. code_puppy_core_plugins/fork/__init__.py +7 -0
  163. code_puppy_core_plugins/fork/register_callbacks.py +445 -0
  164. code_puppy_core_plugins/frontend_emitter/__init__.py +25 -0
  165. code_puppy_core_plugins/frontend_emitter/emitter.py +249 -0
  166. code_puppy_core_plugins/frontend_emitter/register_callbacks.py +412 -0
  167. code_puppy_core_plugins/frontend_emitter/session_context.py +50 -0
  168. code_puppy_core_plugins/grok_oauth/__init__.py +6 -0
  169. code_puppy_core_plugins/grok_oauth/config.py +49 -0
  170. code_puppy_core_plugins/grok_oauth/oauth_flow.py +213 -0
  171. code_puppy_core_plugins/grok_oauth/register_callbacks.py +132 -0
  172. code_puppy_core_plugins/grok_oauth/utils.py +195 -0
  173. code_puppy_core_plugins/herdr/README.md +168 -0
  174. code_puppy_core_plugins/herdr/__init__.py +9 -0
  175. code_puppy_core_plugins/herdr/client.py +339 -0
  176. code_puppy_core_plugins/herdr/register_callbacks.py +140 -0
  177. code_puppy_core_plugins/herdr/reporter.py +212 -0
  178. code_puppy_core_plugins/herdr/smoke.py +143 -0
  179. code_puppy_core_plugins/herdr/sources.py +133 -0
  180. code_puppy_core_plugins/hook_creator/__init__.py +1 -0
  181. code_puppy_core_plugins/hook_creator/register_callbacks.py +33 -0
  182. code_puppy_core_plugins/hook_manager/__init__.py +1 -0
  183. code_puppy_core_plugins/hook_manager/config.py +290 -0
  184. code_puppy_core_plugins/hook_manager/hooks_menu.py +571 -0
  185. code_puppy_core_plugins/hook_manager/register_callbacks.py +227 -0
  186. code_puppy_core_plugins/meta_oauth/__init__.py +1 -0
  187. code_puppy_core_plugins/meta_oauth/config.py +55 -0
  188. code_puppy_core_plugins/meta_oauth/oauth_flow.py +109 -0
  189. code_puppy_core_plugins/meta_oauth/register_callbacks.py +154 -0
  190. code_puppy_core_plugins/meta_oauth/utils.py +252 -0
  191. code_puppy_core_plugins/namespace_skill_search/README.md +118 -0
  192. code_puppy_core_plugins/namespace_skill_search/__init__.py +15 -0
  193. code_puppy_core_plugins/namespace_skill_search/namespaces.py +125 -0
  194. code_puppy_core_plugins/namespace_skill_search/register_callbacks.py +99 -0
  195. code_puppy_core_plugins/namespace_skill_search/search_tool.py +158 -0
  196. code_puppy_core_plugins/no_tools/__init__.py +0 -0
  197. code_puppy_core_plugins/no_tools/register_callbacks.py +42 -0
  198. code_puppy_core_plugins/oauth_pasteback.py +110 -0
  199. code_puppy_core_plugins/oauth_puppy_html.py +194 -0
  200. code_puppy_core_plugins/obsidian_agent/README.md +52 -0
  201. code_puppy_core_plugins/obsidian_agent/__init__.py +8 -0
  202. code_puppy_core_plugins/obsidian_agent/agent_obsidian.py +147 -0
  203. code_puppy_core_plugins/obsidian_agent/register_callbacks.py +13 -0
  204. code_puppy_core_plugins/ollama/__init__.py +1 -0
  205. code_puppy_core_plugins/ollama/register_callbacks.py +127 -0
  206. code_puppy_core_plugins/ollama_setup/__init__.py +5 -0
  207. code_puppy_core_plugins/ollama_setup/completer.py +38 -0
  208. code_puppy_core_plugins/ollama_setup/register_callbacks.py +419 -0
  209. code_puppy_core_plugins/plugin_list/__init__.py +0 -0
  210. code_puppy_core_plugins/plugin_list/plugin_contributions.py +348 -0
  211. code_puppy_core_plugins/plugin_list/plugin_meta.py +67 -0
  212. code_puppy_core_plugins/plugin_list/plugin_text_utils.py +173 -0
  213. code_puppy_core_plugins/plugin_list/plugins_menu.py +455 -0
  214. code_puppy_core_plugins/plugin_list/plugins_menu_layout.py +194 -0
  215. code_puppy_core_plugins/plugin_list/plugins_menu_render.py +431 -0
  216. code_puppy_core_plugins/plugin_list/project_trust_flow.py +162 -0
  217. code_puppy_core_plugins/plugin_list/register_callbacks.py +281 -0
  218. code_puppy_core_plugins/pop_command/__init__.py +1 -0
  219. code_puppy_core_plugins/pop_command/register_callbacks.py +191 -0
  220. code_puppy_core_plugins/prompt_newline/__init__.py +13 -0
  221. code_puppy_core_plugins/prompt_newline/config.py +21 -0
  222. code_puppy_core_plugins/prompt_newline/register_callbacks.py +163 -0
  223. code_puppy_core_plugins/prune/__init__.py +1 -0
  224. code_puppy_core_plugins/prune/prune_menu.py +308 -0
  225. code_puppy_core_plugins/prune/prune_model.py +517 -0
  226. code_puppy_core_plugins/prune/prune_render.py +354 -0
  227. code_puppy_core_plugins/prune/register_callbacks.py +388 -0
  228. code_puppy_core_plugins/puppy_kennel/README.md +203 -0
  229. code_puppy_core_plugins/puppy_kennel/__init__.py +6 -0
  230. code_puppy_core_plugins/puppy_kennel/commands.py +206 -0
  231. code_puppy_core_plugins/puppy_kennel/config.py +36 -0
  232. code_puppy_core_plugins/puppy_kennel/kennel.py +368 -0
  233. code_puppy_core_plugins/puppy_kennel/packer.py +152 -0
  234. code_puppy_core_plugins/puppy_kennel/recorder.py +83 -0
  235. code_puppy_core_plugins/puppy_kennel/register_callbacks.py +109 -0
  236. code_puppy_core_plugins/puppy_kennel/retriever.py +28 -0
  237. code_puppy_core_plugins/puppy_kennel/schema.py +78 -0
  238. code_puppy_core_plugins/puppy_kennel/state.py +68 -0
  239. code_puppy_core_plugins/puppy_kennel/tools.py +449 -0
  240. code_puppy_core_plugins/puppy_kennel/wings.py +110 -0
  241. code_puppy_core_plugins/puppy_spinner/README.md +93 -0
  242. code_puppy_core_plugins/puppy_spinner/__init__.py +11 -0
  243. code_puppy_core_plugins/puppy_spinner/builtin_frames.py +172 -0
  244. code_puppy_core_plugins/puppy_spinner/commands.py +116 -0
  245. code_puppy_core_plugins/puppy_spinner/picker.py +379 -0
  246. code_puppy_core_plugins/puppy_spinner/register_callbacks.py +193 -0
  247. code_puppy_core_plugins/puppy_spinner/spinners.py +444 -0
  248. code_puppy_core_plugins/quick_resume/__init__.py +0 -0
  249. code_puppy_core_plugins/quick_resume/register_callbacks.py +77 -0
  250. code_puppy_core_plugins/review_pr/__init__.py +1 -0
  251. code_puppy_core_plugins/review_pr/register_callbacks.py +162 -0
  252. code_puppy_core_plugins/shell_safety/__init__.py +6 -0
  253. code_puppy_core_plugins/shell_safety/agent_shell_safety.py +69 -0
  254. code_puppy_core_plugins/shell_safety/command_cache.py +156 -0
  255. code_puppy_core_plugins/shell_safety/register_callbacks.py +203 -0
  256. code_puppy_core_plugins/stack_dump/__init__.py +1 -0
  257. code_puppy_core_plugins/stack_dump/register_callbacks.py +79 -0
  258. code_puppy_core_plugins/statusline/__init__.py +14 -0
  259. code_puppy_core_plugins/statusline/config.py +68 -0
  260. code_puppy_core_plugins/statusline/payload.py +138 -0
  261. code_puppy_core_plugins/statusline/prompt_patch.py +76 -0
  262. code_puppy_core_plugins/statusline/register_callbacks.py +45 -0
  263. code_puppy_core_plugins/statusline/runner.py +122 -0
  264. code_puppy_core_plugins/statusline/statusline_command.py +197 -0
  265. code_puppy_core_plugins/steer_queue/__init__.py +10 -0
  266. code_puppy_core_plugins/steer_queue/queue_menu.py +444 -0
  267. code_puppy_core_plugins/steer_queue/register_callbacks.py +138 -0
  268. code_puppy_core_plugins/subagent_panel/README.md +102 -0
  269. code_puppy_core_plugins/subagent_panel/__init__.py +24 -0
  270. code_puppy_core_plugins/subagent_panel/coalesce_patch.py +242 -0
  271. code_puppy_core_plugins/subagent_panel/panel_render.py +145 -0
  272. code_puppy_core_plugins/subagent_panel/register_callbacks.py +535 -0
  273. code_puppy_core_plugins/subagent_panel/resume_repaint.py +112 -0
  274. code_puppy_core_plugins/subagent_panel/state.py +231 -0
  275. code_puppy_core_plugins/switch_agent_resume/__init__.py +5 -0
  276. code_puppy_core_plugins/switch_agent_resume/register_callbacks.py +314 -0
  277. code_puppy_core_plugins/theme/README.md +86 -0
  278. code_puppy_core_plugins/theme/__init__.py +14 -0
  279. code_puppy_core_plugins/theme/bundled_palettes.py +346 -0
  280. code_puppy_core_plugins/theme/content_styles.py +146 -0
  281. code_puppy_core_plugins/theme/osc_palette.py +178 -0
  282. code_puppy_core_plugins/theme/picker.py +358 -0
  283. code_puppy_core_plugins/theme/prompt_toolkit_theme.py +113 -0
  284. code_puppy_core_plugins/theme/register_callbacks.py +348 -0
  285. code_puppy_core_plugins/theme/rich_themes.py +202 -0
  286. code_puppy_core_plugins/theme/themes.py +720 -0
  287. code_puppy_core_plugins/timestamp_heartbeat/__init__.py +1 -0
  288. code_puppy_core_plugins/timestamp_heartbeat/register_callbacks.py +114 -0
  289. code_puppy_core_plugins/token_ratio_learner/__init__.py +5 -0
  290. code_puppy_core_plugins/token_ratio_learner/ratios.py +208 -0
  291. code_puppy_core_plugins/token_ratio_learner/register_callbacks.py +213 -0
  292. code_puppy_core_plugins/universal_constructor/__init__.py +13 -0
  293. code_puppy_core_plugins/universal_constructor/models.py +138 -0
  294. code_puppy_core_plugins/universal_constructor/provider.py +62 -0
  295. code_puppy_core_plugins/universal_constructor/register_callbacks.py +75 -0
  296. code_puppy_core_plugins/universal_constructor/registry.py +302 -0
  297. code_puppy_core_plugins/universal_constructor/sandbox.py +584 -0
  298. code_puppy_core_plugins/wide_completion_menu/__init__.py +6 -0
  299. code_puppy_core_plugins/wide_completion_menu/register_callbacks.py +165 -0
  300. code_puppy_core_plugins/wiggum/__init__.py +1 -0
  301. code_puppy_core_plugins/wiggum/goal_runs.py +91 -0
  302. code_puppy_core_plugins/wiggum/judge.py +299 -0
  303. code_puppy_core_plugins/wiggum/judge_config.py +249 -0
  304. code_puppy_core_plugins/wiggum/judges_menu.py +814 -0
  305. code_puppy_core_plugins/wiggum/register_callbacks.py +569 -0
  306. code_puppy_core_plugins/wiggum/state.py +89 -0
  307. code_puppy_core_plugins/yolo_cli/__init__.py +1 -0
  308. code_puppy_core_plugins/yolo_cli/register_callbacks.py +41 -0
  309. code_puppy_core_plugins-0.0.2.dist-info/METADATA +64 -0
  310. code_puppy_core_plugins-0.0.2.dist-info/RECORD +313 -0
  311. code_puppy_core_plugins-0.0.2.dist-info/WHEEL +4 -0
  312. code_puppy_core_plugins-0.0.2.dist-info/entry_points.txt +55 -0
  313. code_puppy_core_plugins-0.0.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,464 @@
1
+ import os
2
+ import re
3
+ import shlex
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Tuple
8
+
9
+ from code_puppy.callbacks import CustomCommandResult, register_callback
10
+ from code_puppy.config import CONFIG_DIR
11
+ from code_puppy.messaging import emit_error, emit_info, emit_shell_line, emit_warning
12
+
13
+ # Reuse the skills plugin's lightweight (dependency-free) frontmatter parser
14
+ # instead of rolling our own -- one source of truth for YAML frontmatter.
15
+ from code_puppy_core_plugins.agent_skills.metadata import (
16
+ FRONTMATTER_PATTERN,
17
+ parse_yaml_frontmatter,
18
+ )
19
+
20
+ # Global cache for loaded commands
21
+ _custom_commands: Dict[str, str] = {}
22
+ _command_descriptions: Dict[str, str] = {}
23
+ # Commands whose frontmatter declares `exec: <shell command>` run directly instead of
24
+ # being agent-forwarded; value is the raw shell string (shell=True => ~/pipes/env work).
25
+ _command_exec_directives: Dict[str, str] = {}
26
+ _commands_loaded: bool = False # Sentinel to track if commands have been loaded
27
+
28
+ # Soft cap so a runaway script can't wedge the prompt forever.
29
+ _EXEC_TIMEOUT_SECONDS = 30
30
+
31
+ # Scan dirs (priority order; later overrides earlier). The global dir reuses the
32
+ # XDG-aware CONFIG_DIR so it stays in lockstep with code-puppy's file layout.
33
+ _GLOBAL_COMMANDS_DIR = os.path.join(CONFIG_DIR, "commands")
34
+ _COMMAND_DIRECTORIES = [
35
+ _GLOBAL_COMMANDS_DIR, # Global commands (all projects)
36
+ ".claude/commands",
37
+ ".github/prompts",
38
+ ".agents/commands",
39
+ ]
40
+
41
+ # `exec:` runs arbitrary shell, so honor it only from the global user-owned dir; a hostile
42
+ # repo could otherwise drop an exec: command that runs on invocation, bypassing the trust
43
+ # ceremony. Bodies still load from project dirs; only the exec fast-path is gated.
44
+ _TRUSTED_EXEC_DIRECTORIES = frozenset({_GLOBAL_COMMANDS_DIR})
45
+
46
+
47
+ def _is_trusted_exec_dir(dir_path: Path) -> bool:
48
+ """True if ``exec:`` directives from ``dir_path`` are allowed to run.
49
+
50
+ Compares the *resolved* path against the trusted set so symlinks / ``..``
51
+ tricks can't smuggle a project dir past the gate. Fails closed on any
52
+ resolution error.
53
+ """
54
+ try:
55
+ resolved = dir_path.resolve()
56
+ except OSError:
57
+ return False
58
+ for trusted in _TRUSTED_EXEC_DIRECTORIES:
59
+ try:
60
+ if resolved == Path(trusted).expanduser().resolve():
61
+ return True
62
+ except OSError:
63
+ continue
64
+ return False
65
+
66
+
67
+ # Backwards-compatible name for consumers that imported the plugin's marker.
68
+ MarkdownCommandResult = CustomCommandResult
69
+
70
+
71
+ # Namespace directories beginning with these prefixes are skipped entirely
72
+ # (docs, build artefacts, private helpers) so they don't leak as commands.
73
+ _SKIP_DIR_PREFIXES = ("_", ".")
74
+
75
+
76
+ def _is_in_skipped_namespace(md_file: Path, root: Path) -> bool:
77
+ """True if any parent directory beneath ``root`` is "hidden".
78
+
79
+ Files under directories like ``_docs/``, ``__pycache__/`` or ``.git/`` are
80
+ excluded so they never register as slash commands. Only the namespace
81
+ (parent) parts are checked -- a leaf file named ``_foo.md`` is still loaded.
82
+ """
83
+ namespace_parts = md_file.relative_to(root).parts[:-1]
84
+ return any(part.startswith(_SKIP_DIR_PREFIXES) for part in namespace_parts)
85
+
86
+
87
+ # Wibey uses a "//" prefix for commands; code-puppy uses "/". Collapse a line-leading
88
+ # (optionally indented) "//" at load time, leaving URLs like ``fetch //cdn.example.com``
89
+ # alone — only a ``//command`` at line start is rewritten.
90
+ _DOUBLE_SLASH_PREFIX_RE = re.compile(r"(?m)^(\s*)//(?=[\w-])")
91
+
92
+
93
+ def _normalize_command_prefixes(text: str) -> str:
94
+ """Rewrite wibey-style ``//cmd`` command references to ``/cmd``.
95
+
96
+ Only a ``//command`` at the start of a line (optionally indented) is
97
+ normalized; inline / whitespace-preceded ``//host`` sequences (e.g.
98
+ protocol-relative URLs) are left untouched. Applied in-memory to command
99
+ bodies and descriptions; the original markdown files are never modified.
100
+ """
101
+ return _DOUBLE_SLASH_PREFIX_RE.sub(r"\1/", text)
102
+
103
+
104
+ def _derive_description(meta: dict, body: str, base_name: str) -> str:
105
+ """Pick a human-readable description for the /help + completion picker.
106
+
107
+ Priority:
108
+ 1. The ``description:`` field from YAML frontmatter (used verbatim).
109
+ 2. The first non-empty, non-heading line of the body (truncated).
110
+ 3. A title-cased version of the command name.
111
+ """
112
+ desc = meta.get("description")
113
+ if isinstance(desc, str) and desc.strip():
114
+ return desc.strip()
115
+
116
+ for line in body.split("\n"):
117
+ stripped = line.strip()
118
+ if stripped and not stripped.startswith("#"):
119
+ return stripped[:50] + ("..." if len(stripped) > 50 else "")
120
+
121
+ return base_name.replace("_", " ").replace("-", " ").title()
122
+
123
+
124
+ def _command_name_from_path(md_file: Path, root: Path) -> str:
125
+ """Derive a (possibly namespaced) command name from a markdown file path.
126
+
127
+ The name mirrors the directory structure beneath ``root``, joined with
128
+ ``/``. Examples (root = ``~/.code_puppy/commands``)::
129
+
130
+ hello-world.md -> "hello-world"
131
+ flux/new.md -> "flux/new"
132
+ flux/sub/thing.md -> "flux/sub/thing"
133
+
134
+ The ``.prompt.md`` double-suffix (used by ``.github/prompts``) is stripped
135
+ the same way the legacy flat loader did.
136
+ """
137
+ rel = md_file.relative_to(root)
138
+ if md_file.name.endswith(".prompt.md"):
139
+ leaf = md_file.name[: -len(".prompt.md")]
140
+ else:
141
+ leaf = md_file.stem
142
+ # Join any parent directories (the namespace) with the leaf command name.
143
+ parts = [*rel.parts[:-1], leaf]
144
+ return "/".join(parts)
145
+
146
+
147
+ def _load_markdown_commands() -> None:
148
+ """Load markdown command files from the configured directories.
149
+
150
+ Recursively scans for *.md files in the configured directories and loads
151
+ them as custom commands. Subdirectories become namespaces, so
152
+ ``flux/new.md`` is invoked as ``/flux/new``. Later directories override
153
+ earlier ones with the same command name (project commands override global).
154
+ """
155
+ global _custom_commands, _command_descriptions, _commands_loaded
156
+
157
+ _custom_commands.clear()
158
+ _command_descriptions.clear()
159
+ _command_exec_directives.clear()
160
+ _commands_loaded = True # Mark as loaded even if directories are empty
161
+
162
+ # Process directories in order - later directories override earlier ones
163
+ for directory in _COMMAND_DIRECTORIES:
164
+ dir_path = Path(directory).expanduser()
165
+ if not dir_path.exists():
166
+ continue
167
+
168
+ # `exec:` only runs from user-owned trusted dirs (see module docstring
169
+ # on _TRUSTED_EXEC_DIRECTORIES). Compute once per directory.
170
+ exec_allowed = _is_trusted_exec_dir(dir_path)
171
+
172
+ # Look for markdown files (recursively, so subdirs become namespaces)
173
+ pattern = "*.md" if directory != ".github/prompts" else "*.prompt.md"
174
+ # Sort within directory for consistent ordering
175
+ md_files = sorted(dir_path.rglob(pattern))
176
+
177
+ for md_file in md_files:
178
+ try:
179
+ # Skip files under hidden/underscore namespaces (_docs, .git, ...)
180
+ if _is_in_skipped_namespace(md_file, dir_path):
181
+ continue
182
+
183
+ # Command name mirrors the path beneath dir_path (flux/new.md
184
+ # -> "flux/new"); top-level files stay un-namespaced.
185
+ base_name = _command_name_from_path(md_file, dir_path)
186
+
187
+ # NB: do NOT strip `raw` before parsing -- FRONTMATTER_PATTERN needs a
188
+ # trailing newline after `---`, so a body-less exec-only file would
189
+ # otherwise drop its exec: directive and leak raw YAML as the body.
190
+ raw = md_file.read_text(encoding="utf-8")
191
+ if not raw.strip():
192
+ continue
193
+
194
+ # Strip the name/description frontmatter so the agent gets clean
195
+ # instructions (else the model treats the YAML block as context noise).
196
+ meta = parse_yaml_frontmatter(raw)
197
+ body = FRONTMATTER_PATTERN.sub("", raw, count=1).strip()
198
+ exec_directive = meta.get("exec")
199
+ has_exec = isinstance(exec_directive, str) and bool(
200
+ exec_directive.strip()
201
+ )
202
+ # Skip only when there's no body AND no exec directive — an exec-only
203
+ # (frontmatter only) command is legitimate and must still load.
204
+ if not body and not has_exec:
205
+ continue
206
+
207
+ # Normalize wibey //cmd -> /cmd in the body the agent receives.
208
+ body = _normalize_command_prefixes(body)
209
+
210
+ description = _normalize_command_prefixes(
211
+ _derive_description(meta, body, base_name)
212
+ )
213
+
214
+ # Later directories override earlier ones (project > global)
215
+ _custom_commands[base_name] = body
216
+ _command_descriptions[base_name] = description
217
+
218
+ # Optional `exec:` runs a script instead of forwarding the body (e.g.
219
+ # /flux/status renders its own colored output). Only honored from trusted
220
+ # user-owned dirs; a project-relative directive is downgraded to normal.
221
+ if has_exec:
222
+ if exec_allowed:
223
+ _command_exec_directives[base_name] = exec_directive.strip()
224
+ else:
225
+ emit_warning(
226
+ f"Ignoring exec: directive on /{base_name} from "
227
+ f"untrusted dir '{directory}' (only "
228
+ f"{_GLOBAL_COMMANDS_DIR} may run exec)."
229
+ )
230
+ # Downgrade to a normal agent-forwarded command; make
231
+ # sure no earlier trusted exec lingers under this name.
232
+ _command_exec_directives.pop(base_name, None)
233
+ else:
234
+ # Later dirs without exec should clear an earlier one for
235
+ # the same name (override semantics for the exec hook too).
236
+ _command_exec_directives.pop(base_name, None)
237
+
238
+ except Exception as e:
239
+ emit_error(f"Failed to load command from {md_file}: {e}")
240
+
241
+
242
+ _EXEC_TOKEN_RE = re.compile(
243
+ r"\{(python|scripts|commands|script|command)(?::([^}]+))?\}"
244
+ )
245
+
246
+
247
+ def _shell_quote(value: str) -> str:
248
+ """Quote a single argument for the shell ``subprocess`` will spawn.
249
+
250
+ ``_run_exec_directive`` uses ``shell=True``; the shell differs by OS, and so
251
+ does its quoting. ``shlex.quote`` is POSIX-only (it emits single-quotes that
252
+ ``cmd.exe`` does not understand), so on Windows we defer to
253
+ ``subprocess.list2cmdline`` which produces cmd-compatible quoting.
254
+ """
255
+ if os.name == "nt":
256
+ return subprocess.list2cmdline([value])
257
+ return shlex.quote(value)
258
+
259
+
260
+ def _expand_exec_tokens(directive: str) -> str:
261
+ """Substitute exec tokens ({python}/{script:...}/...) in a directive.
262
+
263
+ Command files reference these tokens so they never hard-code an interpreter,
264
+ a shell-expansion (~), or an OS-specific path -- all three of which break on
265
+ Windows and ignore code-puppy's XDG-configured CONFIG_DIR:
266
+
267
+ {python} -> sys.executable (the venv interpreter running us)
268
+ {scripts} -> <CONFIG_DIR>/scripts (absolute)
269
+ {commands} -> <CONFIG_DIR>/commands (absolute)
270
+ {script:foo.py} -> <CONFIG_DIR>/scripts/foo.py (absolute)
271
+ {command:a/b.md} -> <CONFIG_DIR>/commands/a/b.md (absolute)
272
+
273
+ Each expansion is quoted for the current platform's shell so paths with
274
+ spaces survive. CONFIG_DIR is read fresh on each call so an env override is
275
+ honored. Unknown-shaped tokens are left untouched.
276
+ """
277
+
278
+ def _replace(match):
279
+ kind, arg = match.group(1), match.group(2)
280
+ if kind == "python":
281
+ return _shell_quote(sys.executable or "python3")
282
+ if kind == "scripts":
283
+ return _shell_quote(os.path.join(CONFIG_DIR, "scripts"))
284
+ if kind == "commands":
285
+ return _shell_quote(os.path.join(CONFIG_DIR, "commands"))
286
+ if kind == "script":
287
+ rel = (arg or "").strip()
288
+ return _shell_quote(os.path.join(CONFIG_DIR, "scripts", *rel.split("/")))
289
+ if kind == "command":
290
+ rel = (arg or "").strip()
291
+ return _shell_quote(os.path.join(CONFIG_DIR, "commands", *rel.split("/")))
292
+ return match.group(0)
293
+
294
+ return _EXEC_TOKEN_RE.sub(_replace, directive)
295
+
296
+
297
+ def _run_exec_directive(directive: str, name: str, args: str = "") -> None:
298
+ """Run an ``exec:`` directive and stream its output via the message bus.
299
+
300
+ Uses ``shell=True`` so directives can use ``~``, env vars, and pipes. This
301
+ is only reachable for commands loaded from a trusted (user-owned) dir --
302
+ the loader refuses to register exec directives from project-relative dirs
303
+ (see :data:`_TRUSTED_EXEC_DIRECTORIES`), so a hostile repo can't smuggle
304
+ one in.
305
+ Sets ``FORCE_COLOR=1`` so scripts emit ANSI even though stdout is a pipe;
306
+ :func:`emit_shell_line` then renders the ANSI via Rich's ``Text.from_ansi``.
307
+ Also sets ``PYTHONIOENCODING=utf-8`` and decodes the captured output as
308
+ UTF-8 explicitly: on Windows a child Python attached to a pipe otherwise
309
+ defaults its stdout to the legacy codepage (cp1252) and dies with
310
+ ``UnicodeEncodeError`` the moment a script prints an emoji -- and the
311
+ parent-side ``text=True`` decode would mojibake for the same reason.
312
+ UTF-8 on both ends of the pipe, no exceptions. Deliberately NOT setting
313
+ ``PYTHONUTF8=1``: full UTF-8 mode also flips the default ``open()``
314
+ encoding for every Python child of every exec command -- a much broader
315
+ semantic change than the stdio bug calls for, and ``PYTHONIOENCODING``
316
+ alone is verified sufficient to prevent the crash (even for older
317
+ installed scripts still using Rich's legacy-Windows path).
318
+
319
+ Any extra tokens the user typed after the command (e.g. ``/flux/status todo``)
320
+ are shell-quoted and appended to the directive so dangerous shell chars in
321
+ user input can't escape into the command line.
322
+
323
+ ``{python}`` / ``{script:...}`` / ``{command:...}`` tokens in the directive
324
+ are expanded first (see :func:`_expand_exec_tokens`) so command files stay
325
+ interpreter-, path-, and OS-agnostic.
326
+ """
327
+ directive = _expand_exec_tokens(directive)
328
+ full_cmd = directive
329
+ if args:
330
+ try:
331
+ tokens = shlex.split(args)
332
+ except ValueError:
333
+ # Fall back to a single shell-quoted blob if shlex can't parse it
334
+ # (e.g. unbalanced quotes). Keeps the user's intent without crashing.
335
+ tokens = [args]
336
+ if tokens:
337
+ full_cmd = directive + " " + " ".join(shlex.quote(t) for t in tokens)
338
+
339
+ emit_info(f" Running exec for /{name}: {full_cmd}")
340
+ env = {
341
+ **os.environ,
342
+ "FORCE_COLOR": "1",
343
+ # Force UTF-8 stdio in child Pythons; harmless for non-Python tools.
344
+ # (Narrowest lever on purpose -- see docstring for why not PYTHONUTF8.)
345
+ "PYTHONIOENCODING": "utf-8",
346
+ }
347
+ try:
348
+ result = subprocess.run(
349
+ full_cmd,
350
+ shell=True,
351
+ capture_output=True,
352
+ encoding="utf-8",
353
+ errors="replace",
354
+ timeout=_EXEC_TIMEOUT_SECONDS,
355
+ env=env,
356
+ )
357
+ except subprocess.TimeoutExpired:
358
+ emit_error(f"exec for /{name} timed out after {_EXEC_TIMEOUT_SECONDS}s")
359
+ return
360
+ except Exception as exc:
361
+ emit_error(f"exec for /{name} failed to start: {exc}")
362
+ return
363
+
364
+ for line in result.stdout.splitlines():
365
+ emit_shell_line(line, stream="stdout")
366
+ for line in result.stderr.splitlines():
367
+ emit_shell_line(line, stream="stderr")
368
+
369
+ if result.returncode != 0:
370
+ emit_warning(f"exec for /{name} exited with code {result.returncode}")
371
+
372
+
373
+ def reload_commands() -> None:
374
+ """Force a fresh rescan of the command directories.
375
+
376
+ The cache is populated once at plugin import time. Anything that writes new
377
+ command files *after* import (e.g. the flux_bootstrap plugin installing the
378
+ Flux suite during the ``startup`` hook) must call this so the freshly
379
+ installed commands become dispatchable immediately -- otherwise ``/flux/...``
380
+ is treated as ordinary input until the next restart. This is the explicit
381
+ loader API the review asked for; prefer it over poking the private cache.
382
+ """
383
+ _load_markdown_commands()
384
+
385
+
386
+ def is_custom_command(name: str) -> bool:
387
+ """True if ``name`` is a loaded custom command (namespaced or flat).
388
+
389
+ Exposed so the host's file-path disambiguation heuristic can tell a
390
+ genuine path (``/Users/me/file.py``) apart from a namespaced command
391
+ (``/flux/status``) -- both have multiple slashes. Loads commands lazily
392
+ on first use so it works even before the first dispatch.
393
+ """
394
+ if not _commands_loaded:
395
+ _load_markdown_commands()
396
+ return name in _custom_commands or name in _command_exec_directives
397
+
398
+
399
+ def _custom_help() -> List[Tuple[str, str]]:
400
+ """Return help entries for loaded markdown commands."""
401
+ # Reload commands to pick up any changes
402
+ _load_markdown_commands()
403
+
404
+ help_entries = []
405
+ for name, description in sorted(_command_descriptions.items()):
406
+ help_entries.append((name, description))
407
+
408
+ return help_entries
409
+
410
+
411
+ def _handle_custom_command(command: str, name: str) -> Optional[Any]:
412
+ """Handle a markdown-based custom command.
413
+
414
+ Args:
415
+ command: The full command string
416
+ name: The command name without leading slash
417
+
418
+ Returns:
419
+ MarkdownCommandResult with content to be processed as input,
420
+ or None if not found
421
+ """
422
+ if not name:
423
+ return None
424
+
425
+ # Ensure commands are loaded (use sentinel, not dict emptiness)
426
+ if not _commands_loaded:
427
+ _load_markdown_commands()
428
+
429
+ # Extract any additional arguments from the command (shared by both paths)
430
+ parts = command.split(maxsplit=1)
431
+ args = parts[1] if len(parts) > 1 else ""
432
+
433
+ # exec: directive wins -- run the script, bypass the agent entirely.
434
+ exec_directive = _command_exec_directives.get(name)
435
+ if exec_directive:
436
+ _run_exec_directive(exec_directive, name, args=args)
437
+ return True # handled; do not invoke the model
438
+
439
+ # Look up the command
440
+ content = _custom_commands.get(name)
441
+ if content is None:
442
+ return None
443
+
444
+ # If there are arguments, append them to the prompt
445
+ if args:
446
+ prompt = f"{content}\n\nAdditional context: {args}"
447
+ else:
448
+ prompt = content
449
+
450
+ # Emit info message and return the special marker
451
+ emit_info(f"📝 Executing markdown command: {name}")
452
+ return MarkdownCommandResult(prompt)
453
+
454
+
455
+ # Register callbacks
456
+ register_callback("custom_command_help", _custom_help)
457
+ register_callback("custom_command", _handle_custom_command)
458
+
459
+ # Make the result class available for the command handler
460
+ # Import this in command_handler.py to check for this type
461
+ __all__ = ["MarkdownCommandResult", "reload_commands"]
462
+
463
+ # Load commands at import time
464
+ _load_markdown_commands()
@@ -0,0 +1 @@
1
+ """DBOS durable-execution plugin for Code Puppy."""
@@ -0,0 +1,15 @@
1
+ """Cancel an in-flight DBOS workflow, registered via agent_run_cancel hook."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ async def cancel_workflow(group_id: str) -> None:
7
+ """Best-effort cancel of a DBOS workflow. Never raises into core."""
8
+ try:
9
+ from dbos import DBOS
10
+ except ImportError:
11
+ return
12
+ try:
13
+ await DBOS.cancel_workflow_async(group_id)
14
+ except Exception:
15
+ pass
@@ -0,0 +1,31 @@
1
+ """Slash command: /dbos on|off|status to toggle the durable-execution plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .config import is_enabled, set_enabled
6
+
7
+
8
+ def handle_dbos_command(command: str, name: str):
9
+ if name != "dbos":
10
+ return None
11
+ parts = command.strip().split()
12
+ if len(parts) < 2:
13
+ status = "ON" if is_enabled() else "OFF"
14
+ return (
15
+ f"DBOS durable execution is currently {status}. Usage: /dbos on|off|status"
16
+ )
17
+ sub = parts[1].lower()
18
+ if sub == "status":
19
+ status = "ON" if is_enabled() else "OFF"
20
+ return f"DBOS durable execution: {status}"
21
+ if sub == "on":
22
+ set_enabled(True)
23
+ return "DBOS durable execution enabled. Restart code-puppy to apply."
24
+ if sub == "off":
25
+ set_enabled(False)
26
+ return "DBOS durable execution disabled. Restart code-puppy to apply."
27
+ return f"Unknown /dbos subcommand: {sub}. Usage: /dbos on|off|status"
28
+
29
+
30
+ def dbos_command_help():
31
+ return [("dbos", "Toggle DBOS durable execution: /dbos on|off|status")]
@@ -0,0 +1,31 @@
1
+ """Plugin-local config accessors for DBOS durable execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from code_puppy.config import DATA_DIR, get_value, set_config_value
8
+
9
+ _DEFAULT_SQLITE_FILE = os.path.join(DATA_DIR, "dbos_store.sqlite")
10
+
11
+ DBOS_DATABASE_URL = os.environ.get(
12
+ "DBOS_SYSTEM_DATABASE_URL", f"sqlite:///{_DEFAULT_SQLITE_FILE}"
13
+ )
14
+
15
+
16
+ def is_enabled() -> bool:
17
+ """Return True if 'enable_dbos' in puppy.cfg is truthy. Default: True."""
18
+ cfg_val = get_value("enable_dbos")
19
+ if cfg_val is None:
20
+ return True
21
+ return str(cfg_val).strip().lower() in ("true", "1", "yes", "on")
22
+
23
+
24
+ def feature_capability(name: str) -> bool | None:
25
+ """Expose plugin state through the generic feature-capability callback."""
26
+ return is_enabled() if name == "dbos_durable_exec" else None
27
+
28
+
29
+ def set_enabled(enabled: bool) -> None:
30
+ """Persist the 'enable_dbos' switch to puppy.cfg."""
31
+ set_config_value("enable_dbos", "true" if enabled else "false")
@@ -0,0 +1,83 @@
1
+ """DBOS launch/destroy lifecycle, lifted from cli_runner.py."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import time
7
+ import traceback
8
+
9
+ from code_puppy import __version__ as current_version
10
+ from code_puppy.config import DATA_DIR
11
+ from code_puppy.messaging import emit_error, emit_info
12
+
13
+ from .config import _DEFAULT_SQLITE_FILE, DBOS_DATABASE_URL
14
+ from .startup_lock import enable_sqlite_wal, interprocess_lock
15
+
16
+ # Flipped True only after a successful DBOS.launch(). Other modules (wrapper/runtime)
17
+ # MUST check this — `dbos` importable != launched. Stays False in pytest (import w/o
18
+ # on_startup), so the wrapper passes through unmodified.
19
+ _LAUNCHED = False
20
+
21
+
22
+ def is_launched() -> bool:
23
+ """True iff this plugin successfully called DBOS.launch()."""
24
+ return _LAUNCHED
25
+
26
+
27
+ def on_startup() -> None:
28
+ """Initialize and launch DBOS for durable execution."""
29
+ global _LAUNCHED
30
+ try:
31
+ from dbos import DBOS, DBOSConfig
32
+ except ImportError:
33
+ # Shouldn't happen — hook only registers when dbos is importable; kept
34
+ # defensive in case install state shifts between module-load and startup.
35
+ emit_error(
36
+ "[dbos_durable_exec] dbos package not installed; durable exec disabled."
37
+ )
38
+ return
39
+
40
+ dbos_app_version = os.environ.get(
41
+ "DBOS_APP_VERSION", f"{current_version}-{int(time.time() * 1000)}"
42
+ )
43
+ dbos_config: DBOSConfig = {
44
+ "name": "dbos-code-puppy",
45
+ "system_database_url": DBOS_DATABASE_URL,
46
+ "run_admin_server": False,
47
+ "conductor_key": os.environ.get("DBOS_CONDUCTOR_KEY"),
48
+ "log_level": os.environ.get("DBOS_LOG_LEVEL", "ERROR"),
49
+ "application_version": dbos_app_version,
50
+ }
51
+ try:
52
+ emit_info(f"Initializing DBOS with database at: {DBOS_DATABASE_URL}")
53
+ # Multiple instances share one SQLite system DB; DBOS.launch() migration +
54
+ # recovery take SQLite's single-writer lock, so racers collide unless serialized.
55
+ # We: (1) flip to WAL (persistent) so concurrent access rarely blocks, and
56
+ # (2) hold a cross-process file lock so launch/migration runs one process at a
57
+ # time — late starters wait instead of crashing.
58
+ if DBOS_DATABASE_URL.startswith("sqlite"):
59
+ enable_sqlite_wal(_DEFAULT_SQLITE_FILE)
60
+ launch_lock = os.path.join(DATA_DIR, "dbos_store.launch.lock")
61
+ with interprocess_lock(launch_lock, timeout=60.0):
62
+ DBOS(config=dbos_config)
63
+ DBOS.launch()
64
+ _LAUNCHED = True
65
+ except Exception as e:
66
+ emit_error(
67
+ f"[dbos_durable_exec] Error initializing DBOS: {e}\n{traceback.format_exc()}"
68
+ )
69
+
70
+
71
+ def on_shutdown() -> None:
72
+ """Tear DBOS down. Best-effort, never raises."""
73
+ global _LAUNCHED
74
+ try:
75
+ from dbos import DBOS
76
+ except ImportError:
77
+ return
78
+ try:
79
+ DBOS.destroy()
80
+ except Exception:
81
+ pass
82
+ finally:
83
+ _LAUNCHED = False
@@ -0,0 +1,45 @@
1
+ """Wire the DBOS durable-execution plugin into core via callback hooks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ from code_puppy.callbacks import register_callback
8
+
9
+ from .cancel import cancel_workflow
10
+ from .commands import dbos_command_help, handle_dbos_command
11
+ from .config import feature_capability, is_enabled
12
+ from .lifecycle import on_shutdown, on_startup
13
+ from .runtime import dbos_run_context, skip_fallback_render
14
+ from .wrapper import wrap_with_dbos_agent
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ register_callback("feature_capability", feature_capability)
20
+
21
+
22
+ # Slash command is always available so users can /dbos on even when the
23
+ # package isn't installed yet (we tell them to install + restart).
24
+ register_callback("custom_command", handle_dbos_command)
25
+ register_callback("custom_command_help", dbos_command_help)
26
+
27
+
28
+ if is_enabled():
29
+ try:
30
+ import dbos # noqa: F401 -- early import check
31
+ except ImportError:
32
+ # logger.info (not debug) so a silently-skipped enabled feature surfaces;
33
+ # install with `pip install code-puppy[durable]` to fix.
34
+ logger.info(
35
+ "DBOS plugin enabled but `dbos` package not installed; "
36
+ "durable-exec hooks not registered. "
37
+ "Install with: pip install 'code-puppy[durable]'"
38
+ )
39
+ else:
40
+ register_callback("startup", on_startup)
41
+ register_callback("shutdown", on_shutdown)
42
+ register_callback("wrap_pydantic_agent", wrap_with_dbos_agent)
43
+ register_callback("agent_run_context", dbos_run_context)
44
+ register_callback("agent_run_cancel", cancel_workflow)
45
+ register_callback("should_skip_fallback_render", skip_fallback_render)