claude-smart 0.2.44 → 0.2.46

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 (323) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +34 -5
  3. package/bin/claude-smart.js +295 -5
  4. package/package.json +22 -3
  5. package/plugin/.claude-plugin/plugin.json +4 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +22 -2
  8. package/plugin/commands/clear-all.md +1 -0
  9. package/plugin/commands/dashboard.md +1 -0
  10. package/plugin/commands/learn.md +1 -0
  11. package/plugin/commands/restart.md +1 -0
  12. package/plugin/commands/show.md +1 -0
  13. package/plugin/dashboard/app/configure/env/page.tsx +40 -14
  14. package/plugin/dashboard/app/configure/server/page.tsx +51 -1
  15. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  18. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  19. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  20. package/plugin/dashboard/lib/status.ts +10 -3
  21. package/plugin/dashboard/lib/types.ts +18 -6
  22. package/plugin/hooks/codex-hooks.json +0 -1
  23. package/plugin/opencode/assistant-buffer.ts +108 -0
  24. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  25. package/plugin/opencode/dist/internal.js +12 -0
  26. package/plugin/opencode/dist/payload.js +85 -0
  27. package/plugin/opencode/dist/server.mjs +158 -0
  28. package/plugin/opencode/internal.ts +18 -0
  29. package/plugin/opencode/package.json +3 -0
  30. package/plugin/opencode/payload.ts +90 -0
  31. package/plugin/opencode/server.mts +174 -0
  32. package/plugin/opencode/tsconfig.json +13 -0
  33. package/plugin/pyproject.toml +20 -3
  34. package/plugin/scripts/_lib.sh +77 -29
  35. package/plugin/scripts/backend-service.sh +27 -5
  36. package/plugin/scripts/dashboard-service.sh +7 -5
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +3 -3
  39. package/plugin/scripts/opencode-claude-compat +4 -0
  40. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  41. package/plugin/scripts/opencode-claude-compat.js +225 -0
  42. package/plugin/scripts/smart-install.sh +10 -8
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +304 -6
  45. package/plugin/src/claude_smart/env_config.py +12 -4
  46. package/plugin/src/claude_smart/events/session_start.py +26 -7
  47. package/plugin/src/claude_smart/events/stop.py +1 -1
  48. package/plugin/src/claude_smart/ids.py +1 -1
  49. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  50. package/plugin/src/claude_smart/runtime.py +7 -1
  51. package/plugin/uv.lock +1 -1
  52. package/plugin/vendor/reflexio/.env.example +54 -30
  53. package/plugin/vendor/reflexio/README.md +14 -8
  54. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  55. package/plugin/vendor/reflexio/reflexio/README.md +1 -0
  56. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  57. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  64. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  65. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  66. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  67. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  68. package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
  69. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  70. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  71. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  142. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
  143. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  144. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
  145. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  146. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
  147. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  148. package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
  149. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
  150. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  151. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  152. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  153. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  158. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
  159. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  160. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  161. package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
  162. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  163. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  164. package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
  165. package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
  166. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  167. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
  168. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  169. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  170. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  171. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  172. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  173. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  174. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  175. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  176. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  177. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  178. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  179. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  180. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  181. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  182. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  183. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  184. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  185. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  186. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  187. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  188. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
  189. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  198. package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
  199. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  200. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  201. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  202. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  203. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  204. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  205. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  206. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  207. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  208. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  209. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  212. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  215. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  216. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  217. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  218. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  219. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  220. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  221. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
  222. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  223. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
  225. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  226. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  227. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
  228. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  229. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
  230. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  231. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  232. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  233. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  234. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  235. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  238. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  239. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  241. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  242. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
  243. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  244. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  245. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  246. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  248. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  249. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
  250. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  252. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  253. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  255. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  256. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  257. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
  258. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
  259. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  261. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
  262. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
  263. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
  264. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  265. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  266. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  277. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  278. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  279. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  280. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  281. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
  282. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  283. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  284. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  285. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  286. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  287. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  288. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  289. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  290. package/scripts/setup-claude-smart.sh +8 -3
  291. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  292. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  293. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  294. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  295. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  296. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  297. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  298. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  299. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  300. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  301. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  302. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  303. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  304. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  305. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  306. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  307. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  308. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  309. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  310. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  311. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  312. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  313. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  314. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  317. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  318. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  319. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  320. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  321. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  322. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  323. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -0,0 +1,69 @@
1
+ """before_prompt_build hook — buffer the user turn and inject matching context.
2
+
3
+ Two responsibilities, in order:
4
+
5
+ 1. Buffer the prompt into the session JSONL (this is the sole source of
6
+ ``"User"`` role turns downstream — openClaw replays the rest of the
7
+ transcript via tool events, not before_prompt_build).
8
+ 2. Use the prompt text as a search query against reflexio's preferences +
9
+ skills and emit the top hits as ``prependContext`` so the model sees
10
+ relevant rules before planning the response.
11
+
12
+ The shared pipeline lives in ``context_inject.emit_context``. Retrieval is
13
+ best-effort: any failure from search (reflexio unreachable, HTTP timeout,
14
+ unexpected shape) is caught so the buffered-prompt behaviour is always
15
+ preserved.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import time
22
+ from typing import Any
23
+
24
+ from openclaw_smart import context_inject, ids, state
25
+
26
+ _LOGGER = logging.getLogger(__name__)
27
+ _TOP_K = 3
28
+
29
+
30
+ def handle(payload: dict[str, Any]) -> None:
31
+ """before_prompt_build dispatcher — buffers the prompt, then injects context.
32
+
33
+ Args:
34
+ payload (dict[str, Any]): The openClaw event/ctx blob (merged). Expected
35
+ keys ``sessionKey`` (or ``sessionId``), ``prompt``, optionally
36
+ ``agentId`` and ``workspaceDir``.
37
+
38
+ Returns:
39
+ None: Side effects only — appends to the session buffer and may
40
+ write a ``{"prependContext": ...}`` JSON document to stdout.
41
+ """
42
+ session_id = payload.get("sessionKey") or payload.get("sessionId")
43
+ prompt = payload.get("prompt") or ""
44
+ if not session_id or not prompt:
45
+ return
46
+
47
+ project_id = ids.resolve_project_id_with_fallback(
48
+ cwd=payload.get("workspaceDir"),
49
+ agent_id=payload.get("agentId"),
50
+ )
51
+ state.append(
52
+ session_id,
53
+ {
54
+ "ts": int(time.time()),
55
+ "role": "User",
56
+ "content": prompt,
57
+ "user_id": project_id,
58
+ },
59
+ )
60
+
61
+ try:
62
+ context_inject.emit_context(
63
+ session_id=session_id,
64
+ project_id=project_id,
65
+ query=prompt,
66
+ top_k=_TOP_K,
67
+ )
68
+ except Exception as exc: # noqa: BLE001 — never break the user's turn
69
+ _LOGGER.debug("before_prompt_build context inject failed: %s", exc)
@@ -0,0 +1,30 @@
1
+ """before_tool_call hook — observe-only stub.
2
+
3
+ Phase 0 probe finding Q1: openClaw's ``before_tool_call`` hook does NOT
4
+ honor ``prependContext`` returned by plugins, so injecting context here
5
+ would have no effect on the upcoming tool call. (In contrast, Claude
6
+ Code's PreToolUse hook is the primary just-in-time injection point.)
7
+
8
+ This handler is therefore a silent no-op in v1. The TS shim may choose
9
+ not to register it at all — having a stub here keeps the dispatcher
10
+ symmetric and leaves room for future observe-only telemetry.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ from typing import Any
17
+
18
+ _LOGGER = logging.getLogger(__name__)
19
+
20
+
21
+ def handle(payload: dict[str, Any]) -> None:
22
+ """before_tool_call dispatcher — silent no-op.
23
+
24
+ Args:
25
+ payload (dict[str, Any]): Unused. openClaw delivers
26
+ ``{toolName, params, sessionKey, agentId}`` here but we have no
27
+ actionable side effect to apply per Phase 0 finding Q1.
28
+ """
29
+ del payload
30
+ _LOGGER.debug("before_tool_call invoked (observe-only stub)")
@@ -0,0 +1,36 @@
1
+ """session_end hook — flush any remaining interactions with forced extraction.
2
+
3
+ Fires when an openClaw session terminates. We force extraction here (unlike
4
+ ``agent_end``, which lets reflexio queue extraction for its next sweep) so
5
+ the user's distilled lessons from this session are usable in the very next
6
+ session, not after the next scheduled sweep.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from openclaw_smart import ids, publish
14
+
15
+
16
+ def handle(payload: dict[str, Any]) -> None:
17
+ """Drain the buffer and trigger synchronous extraction.
18
+
19
+ Args:
20
+ payload (dict[str, Any]): openClaw event/ctx blob, expected to
21
+ contain ``sessionKey`` (or ``sessionId``) and optionally
22
+ ``agentId`` / ``workspaceDir``.
23
+ """
24
+ session_id = payload.get("sessionKey") or payload.get("sessionId")
25
+ if not session_id:
26
+ return
27
+ project_id = ids.resolve_project_id_with_fallback(
28
+ cwd=payload.get("workspaceDir"),
29
+ agent_id=payload.get("agentId"),
30
+ )
31
+ publish.publish_unpublished(
32
+ session_id=session_id,
33
+ project_id=project_id,
34
+ force_extraction=True,
35
+ skip_aggregation=False,
36
+ )
@@ -0,0 +1,130 @@
1
+ """session_start hook — apply startup defaults without broad memory retrieval.
2
+
3
+ For openClaw, the ``session_start`` event delivers ``{sessionId, sessionKey,
4
+ agentId, workspaceDir}``. The handler:
5
+
6
+ 1. Fetches the current stall state from reflexio and renders a 1-line banner
7
+ to ``prependContext`` if learning is paused.
8
+ 2. Pushes openclaw-smart's preferred extraction defaults (smaller window /
9
+ stride than reflexio's out-of-box 10/5).
10
+ 3. Pushes optimizer defaults so reflexio can evaluate candidate playbooks
11
+ via the local ``openclaw infer model run`` CLI.
12
+
13
+ Output: when there is a banner, emits ``{"prependContext": "<banner>"}`` on
14
+ stdout for the TS shim to relay back to openClaw. Otherwise emits nothing.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import contextlib
20
+ import json
21
+ import os
22
+ import sys
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ from openclaw_smart.reflexio_adapter import Adapter
27
+ from openclaw_smart.stall_banner import render_banner
28
+
29
+ # openclaw-smart's preferred extraction cadence — more frequent, smaller
30
+ # windows than reflexio's out-of-box 10/5. Applied idempotently to the
31
+ # reflexio server on every session_start via
32
+ # Adapter.apply_extraction_defaults.
33
+ _WINDOW_SIZE = 5
34
+ _STRIDE_SIZE = 3
35
+ # Optimizer is on by default. Set this env var to "0" to skip pushing the
36
+ # openclaw-smart optimizer defaults on session_start (kill switch).
37
+ _DISABLE_OPTIMIZER_ENV = "OPENCLAW_SMART_ENABLE_OPTIMIZER"
38
+ _OPTIMIZER_TIMEOUT_SECONDS = 300
39
+
40
+
41
+ def _adapter() -> Adapter:
42
+ """Construct the reflexio adapter for this hook invocation.
43
+
44
+ Indirected through a factory so tests can monkeypatch the adapter
45
+ construction without touching the ``Adapter`` class itself.
46
+
47
+ Returns:
48
+ Adapter: A fresh adapter bound to the current process env.
49
+ """
50
+ return Adapter()
51
+
52
+
53
+ def _stall_banner(adapter: Any) -> str:
54
+ """Return the prepend-able stall banner, or "" if no banner should fire.
55
+
56
+ Reads ``adapter.fetch_stall_state()``; if it reports an active,
57
+ not-yet-notified stall, renders a one-line banner via
58
+ ``stall_banner.render_banner``. All exceptions are absorbed: this is
59
+ defense-in-depth — even though the hook dispatcher already wraps
60
+ ``handle`` in try/except, a stall-path bug must never block the
61
+ existing playbook/profile rendering.
62
+
63
+ Args:
64
+ adapter (Any): The adapter to query. Duck-typed so tests can stub.
65
+
66
+ Returns:
67
+ str: The banner text, or ``""`` when there is nothing to show.
68
+ """
69
+ try:
70
+ state_obj = adapter.fetch_stall_state()
71
+ except Exception: # noqa: BLE001 — stall path must never crash the hook.
72
+ return ""
73
+ if state_obj is None:
74
+ return ""
75
+ if not getattr(state_obj, "stalled", False):
76
+ return ""
77
+ if getattr(state_obj, "notified_in_cc", False):
78
+ return ""
79
+ try:
80
+ return render_banner(
81
+ reason=getattr(state_obj, "reason", None),
82
+ reset_estimate=getattr(state_obj, "reset_estimate", None),
83
+ )
84
+ except Exception: # noqa: BLE001 — render_banner bug must not block playbook injection.
85
+ return ""
86
+
87
+
88
+ def handle(payload: dict[str, Any]) -> None:
89
+ """Handle a session_start hook payload.
90
+
91
+ Args:
92
+ payload (dict[str, Any]): openClaw event/ctx blob, expected to
93
+ contain ``sessionKey`` (or ``sessionId``).
94
+ """
95
+ session_id = payload.get("sessionKey") or payload.get("sessionId")
96
+ if not session_id:
97
+ return
98
+
99
+ adapter = _adapter()
100
+
101
+ # Stall banner — emitted via prependContext, fires at most once per stall
102
+ # event (controlled server-side via mark_stall_notified).
103
+ banner = _stall_banner(adapter)
104
+
105
+ adapter.apply_extraction_defaults(
106
+ window_size=_WINDOW_SIZE,
107
+ stride_size=_STRIDE_SIZE,
108
+ )
109
+ if os.environ.get(_DISABLE_OPTIMIZER_ENV) != "0":
110
+ adapter.apply_optimizer_defaults(
111
+ script_path=_optimizer_assistant_path(),
112
+ timeout_seconds=_OPTIMIZER_TIMEOUT_SECONDS,
113
+ )
114
+
115
+ if not banner:
116
+ return
117
+
118
+ sys.stdout.write(json.dumps({"prependContext": banner}))
119
+ sys.stdout.write("\n")
120
+
121
+ # Telemetry must not break the session.
122
+ with contextlib.suppress(Exception):
123
+ adapter.mark_stall_notified()
124
+
125
+
126
+ def _optimizer_assistant_path() -> str:
127
+ """Return the absolute path to the openclaw-smart-optimizer-assistant binary."""
128
+ executable = Path(sys.executable)
129
+ suffix = ".exe" if os.name == "nt" else ""
130
+ return str(executable.with_name(f"openclaw-smart-optimizer-assistant{suffix}"))
@@ -0,0 +1,131 @@
1
+ """Dispatch table for openclaw-smart hook events.
2
+
3
+ The TS shim spawns ``python -m openclaw_smart.hook <event>`` (or the
4
+ ``openclaw-smart-hook`` console script) once per hook invocation, piping
5
+ the openClaw event payload on stdin. This module reads the JSON, routes
6
+ to the matching handler, and makes sure no unhandled exception ever
7
+ propagates back to openClaw.
8
+
9
+ Handlers emit their own ``{"prependContext": …}`` JSON to stdout when
10
+ they have context to inject; otherwise they emit nothing. The dispatcher
11
+ emits nothing on no-op paths (unknown event, recursion guard, handler
12
+ exception) so the TS shim can treat empty stdout as "no return value".
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ import sys
20
+ from collections.abc import Callable
21
+ from typing import Any
22
+
23
+ from openclaw_smart import runtime
24
+ from openclaw_smart.internal_call import is_internal_invocation
25
+
26
+ _LOGGER = logging.getLogger(__name__)
27
+
28
+
29
+ def _load_handlers() -> dict[str, Callable[[dict[str, Any]], None]]:
30
+ """Import handler modules lazily to keep cold-start cost low."""
31
+ from openclaw_smart.events import (
32
+ after_tool_call,
33
+ agent_end,
34
+ before_prompt_build,
35
+ before_tool_call,
36
+ session_end,
37
+ session_start,
38
+ )
39
+
40
+ return {
41
+ "session-start": session_start.handle,
42
+ "before-prompt-build": before_prompt_build.handle,
43
+ "before-tool-call": before_tool_call.handle,
44
+ "after-tool-call": after_tool_call.handle,
45
+ "agent-end": agent_end.handle,
46
+ "session-end": session_end.handle,
47
+ }
48
+
49
+
50
+ _HANDLERS: dict[str, Callable[[dict[str, Any]], None]] | None = None
51
+
52
+
53
+ def _handlers() -> dict[str, Callable[[dict[str, Any]], None]]:
54
+ global _HANDLERS
55
+ if _HANDLERS is None:
56
+ _HANDLERS = _load_handlers()
57
+ return _HANDLERS
58
+
59
+
60
+ def _read_stdin_json() -> dict[str, Any]:
61
+ """Parse stdin as JSON. Returns ``{}`` on empty or malformed input."""
62
+ try:
63
+ raw = sys.stdin.read()
64
+ except (OSError, ValueError) as exc:
65
+ _LOGGER.debug("stdin read failed: %s", exc)
66
+ return {}
67
+ if not raw.strip():
68
+ return {}
69
+ try:
70
+ parsed = json.loads(raw)
71
+ except json.JSONDecodeError as exc:
72
+ _LOGGER.debug("stdin JSON decode failed: %s", exc)
73
+ return {}
74
+ return parsed if isinstance(parsed, dict) else {}
75
+
76
+
77
+ def _parse_args(argv: list[str]) -> str:
78
+ """Return the event name from ``argv``.
79
+
80
+ Accepts either ``[event]`` or ``[host, event]`` for symmetry with
81
+ claude-smart, but the host arg (if present) must be ``"openclaw"`` and
82
+ is otherwise ignored — runtime.set_host is called either way.
83
+ """
84
+ if not argv:
85
+ return ""
86
+ if len(argv) >= 2 and argv[0] in runtime.VALID_HOSTS:
87
+ return argv[1]
88
+ return argv[0]
89
+
90
+
91
+ def main(argv: list[str] | None = None) -> int:
92
+ """Entry point used by ``python -m openclaw_smart.hook`` and the console script.
93
+
94
+ Args:
95
+ argv (list[str] | None): Command-line args (sans program name).
96
+ Defaults to ``sys.argv[1:]``.
97
+
98
+ Returns:
99
+ int: Always 0 — failures are absorbed so the host never sees a
100
+ non-zero exit and decides to surface a plugin error.
101
+ """
102
+ argv = argv if argv is not None else sys.argv[1:]
103
+ event = _parse_args(argv)
104
+ runtime.set_host(runtime.HOST_OPENCLAW)
105
+ if not event:
106
+ _LOGGER.warning("hook dispatcher called with no event name")
107
+ return 0
108
+
109
+ payload = _read_stdin_json()
110
+
111
+ # Self-feedback guard: when this hook fires inside reflexio's own
112
+ # openclaw subprocess (the openclaw LLM provider), skip all handlers
113
+ # so we don't publish the extractor's system prompt back into reflexio.
114
+ # See openclaw_smart.internal_call for detection logic.
115
+ if is_internal_invocation(payload):
116
+ return 0
117
+
118
+ handler = _handlers().get(event)
119
+ if handler is None:
120
+ _LOGGER.warning("unknown hook event: %s", event)
121
+ return 0
122
+
123
+ try:
124
+ handler(payload)
125
+ except Exception as exc: # noqa: BLE001 — hooks must never crash the session.
126
+ _LOGGER.exception("hook handler %s raised: %s", event, exc)
127
+ return 0
128
+
129
+
130
+ if __name__ == "__main__":
131
+ raise SystemExit(main())
@@ -0,0 +1,94 @@
1
+ """Resolve stable identifiers for openClaw sessions.
2
+
3
+ Two identifiers matter to reflexio:
4
+
5
+ - ``session_id``: openClaw's per-session id (``sessionKey``), passed via the
6
+ TS shim. We forward it to reflexio's interaction ``session_id`` field so
7
+ individual turns remain attributable to their conversation, but it is no
8
+ longer the scope key for extracted preferences.
9
+ - ``project_id``: a stable, cross-session name for the project. We use this
10
+ as reflexio's ``user_id`` for preferences, so user preferences extracted
11
+ in one session are visible to every later session in the same repo.
12
+ ``agent_version`` is hardcoded to ``"openclaw"`` in the adapter so shared
13
+ skills roll up globally across all openclaw projects.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import os
20
+ import shutil
21
+ import subprocess # noqa: S404 — git invocation with a fixed flag set.
22
+ from pathlib import Path
23
+
24
+ _LOGGER = logging.getLogger(__name__)
25
+
26
+
27
+ def _resolve_from_git(cwd: Path) -> str | None:
28
+ """Return the git toplevel basename for *cwd*, or ``None`` if not a repo.
29
+
30
+ Must never raise — callers depend on this returning ``None`` cleanly so
31
+ hook handlers can fall back to other identifiers. Resolves ``git`` to an
32
+ absolute path first to avoid PATH-dependent process spawning, and catches
33
+ any ``OSError`` (e.g., ``cwd`` is unreadable or has been deleted) along
34
+ with the previously-handled ``FileNotFoundError`` / ``TimeoutExpired``.
35
+ """
36
+ git_bin = shutil.which("git")
37
+ if not git_bin:
38
+ return None
39
+ try:
40
+ result = subprocess.run( # noqa: S603 — fixed argv, cwd is a Path.
41
+ [git_bin, "rev-parse", "--show-toplevel"],
42
+ cwd=cwd,
43
+ capture_output=True,
44
+ text=True,
45
+ check=False,
46
+ timeout=5,
47
+ )
48
+ if result.returncode == 0:
49
+ toplevel = result.stdout.strip()
50
+ if toplevel:
51
+ return Path(toplevel).name
52
+ except (OSError, subprocess.TimeoutExpired) as exc:
53
+ # OSError covers FileNotFoundError + PermissionError + missing-cwd.
54
+ _LOGGER.debug("git toplevel resolution failed: %s", exc)
55
+ return None
56
+
57
+
58
+ def resolve_project_id(cwd: str | os.PathLike[str] | None = None) -> str:
59
+ """Return a stable project identifier for the given working directory.
60
+
61
+ Prefers the basename of the git toplevel (so worktrees, submodules, and
62
+ ``cd src/`` all still map to the same project). Falls back to the cwd
63
+ basename when the directory is not inside a git repo.
64
+
65
+ Args:
66
+ cwd: Working directory to resolve. Defaults to ``os.getcwd()``.
67
+
68
+ Returns:
69
+ str: A non-empty identifier. Never raises.
70
+ """
71
+ base = Path(cwd) if cwd is not None else Path.cwd()
72
+ return _resolve_from_git(base) or base.name or "unknown-project"
73
+
74
+
75
+ def resolve_project_id_with_fallback(
76
+ cwd: str | os.PathLike[str] | None,
77
+ agent_id: str | None,
78
+ ) -> str:
79
+ """Resolve project ID, falling back to ``agent_id`` when not in a git repo.
80
+
81
+ openClaw can run from any directory (or none at all), so when there is no
82
+ git context we prefer the openClaw agent id over a fabricated cwd
83
+ basename, which would be brittle across sessions.
84
+
85
+ Args:
86
+ cwd: Working directory of the openClaw session (``ctx.workspaceDir``).
87
+ agent_id: openClaw agent id from ``ctx.agentId``.
88
+
89
+ Returns:
90
+ str: git repo basename if cwd is in a repo; else ``agent_id``; else
91
+ the literal ``"openclaw"``.
92
+ """
93
+ base = Path(cwd) if cwd is not None else Path.cwd()
94
+ return _resolve_from_git(base) or agent_id or "openclaw"
@@ -0,0 +1,75 @@
1
+ """Detect hook invocations that should not be published to reflexio.
2
+
3
+ The single concern for openclaw-smart is reflexio's own LLM provider. The
4
+ ``openclaw`` LiteLLM provider (see
5
+ ``reflexio.server.llm.providers.openclaw_provider``) shells out to the
6
+ ``openclaw`` CLI to answer extractor prompts. That subprocess is a full
7
+ openClaw invocation, so it fires *our* hooks too — and without a guard,
8
+ the ``agent_end`` hook would publish the extractor's own system prompt
9
+ back into reflexio as a user interaction. Reflexio would then train on
10
+ its own internals.
11
+
12
+ Detection signals, OR'd:
13
+ - Env var ``OPENCLAW_SMART_INTERNAL=1``, set by reflexio's provider
14
+ before spawning ``openclaw``.
15
+ - ``payload.cwd`` resolves inside the reflexio repository. Catches
16
+ direct interactive ``openclaw`` runs from inside the reflexio
17
+ checkout (manual debugging) that would otherwise pollute the corpus.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ INTERNAL_ENV = "OPENCLAW_SMART_INTERNAL"
27
+
28
+ # Plugin layout (in-repo / editable):
29
+ # <reflexio_repo>/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py
30
+ # parents[5] = <reflexio_repo>, the directory we want to fence off.
31
+ #
32
+ # In install-mode layouts (PyPI wheel, ~/.openclaw/plugins/cache/...), this
33
+ # fixed depth doesn't hold. Guarding the index lookup keeps module import
34
+ # from crashing on unfamiliar layouts — the env marker is the primary
35
+ # signal there. ``OPENCLAW_SMART_REFLEXIO_DIR`` lets callers (and tests)
36
+ # override the path without touching the module.
37
+ _THIS_DIR = Path(__file__).resolve().parent
38
+ _override_reflexio_dir = os.environ.get("OPENCLAW_SMART_REFLEXIO_DIR")
39
+ if _override_reflexio_dir:
40
+ _REFLEXIO_DIR: Path = Path(_override_reflexio_dir).resolve()
41
+ else:
42
+ _parents = _THIS_DIR.parents
43
+ # In an unexpected layout we fall back to _THIS_DIR itself, which makes
44
+ # the relative_to() check below match (correctly) only when cwd is
45
+ # literally inside this module's own directory — effectively a no-op
46
+ # fence, ceding all detection responsibility to the env marker.
47
+ _REFLEXIO_DIR = _parents[5] if len(_parents) > 5 else _THIS_DIR
48
+
49
+
50
+ def is_internal_invocation(payload: dict[str, Any]) -> bool:
51
+ """True if this hook fire originated from reflexio's own LLM provider.
52
+
53
+ Args:
54
+ payload (dict[str, Any]): Parsed openClaw hook payload. Only ``cwd``
55
+ (or ``workspaceDir``) is inspected.
56
+
57
+ Returns:
58
+ bool: True when the env marker is set or ``cwd`` points inside the
59
+ reflexio repository. False otherwise, including when ``cwd`` is
60
+ missing or unresolvable.
61
+ """
62
+ if os.environ.get(INTERNAL_ENV) == "1":
63
+ return True
64
+ cwd = payload.get("cwd") or payload.get("workspaceDir")
65
+ if not isinstance(cwd, str) or not cwd:
66
+ return False
67
+ try:
68
+ resolved = Path(cwd).resolve()
69
+ except OSError:
70
+ return False
71
+ try:
72
+ resolved.relative_to(_REFLEXIO_DIR)
73
+ except ValueError:
74
+ return False
75
+ return True