claude-smart 0.2.45 → 0.2.47

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 (353) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +47 -10
  3. package/bin/claude-smart.js +535 -23
  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 +27 -6
  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/layout.tsx +20 -0
  16. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  17. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  18. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  19. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  20. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  21. package/plugin/dashboard/lib/status.ts +10 -3
  22. package/plugin/dashboard/lib/types.ts +18 -6
  23. package/plugin/hooks/codex-hooks.json +0 -1
  24. package/plugin/opencode/assistant-buffer.ts +108 -0
  25. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  26. package/plugin/opencode/dist/internal.js +12 -0
  27. package/plugin/opencode/dist/payload.js +85 -0
  28. package/plugin/opencode/dist/server.mjs +232 -0
  29. package/plugin/opencode/internal.ts +18 -0
  30. package/plugin/opencode/package.json +3 -0
  31. package/plugin/opencode/payload.ts +90 -0
  32. package/plugin/opencode/server.mts +251 -0
  33. package/plugin/opencode/tsconfig.json +13 -0
  34. package/plugin/pyproject.toml +25 -4
  35. package/plugin/scripts/_lib.sh +5 -1
  36. package/plugin/scripts/backend-service.sh +22 -2
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +1 -1
  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 +17 -9
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +498 -12
  45. package/plugin/src/claude_smart/context_format.py +9 -9
  46. package/plugin/src/claude_smart/cs_cite.py +66 -19
  47. package/plugin/src/claude_smart/env_config.py +12 -4
  48. package/plugin/src/claude_smart/events/session_start.py +26 -7
  49. package/plugin/src/claude_smart/events/stop.py +1 -1
  50. package/plugin/src/claude_smart/ids.py +1 -1
  51. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  52. package/plugin/src/claude_smart/runtime.py +7 -1
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +54 -30
  55. package/plugin/vendor/reflexio/README.md +17 -11
  56. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  57. package/plugin/vendor/reflexio/reflexio/README.md +4 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  59. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  60. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  64. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +212 -52
  65. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  66. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  67. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  68. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  69. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  70. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  71. package/plugin/vendor/reflexio/reflexio/client/client.py +154 -13
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +246 -10
  146. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  147. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  148. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
  149. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  150. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
  151. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  152. package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
  153. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  158. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  159. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  160. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  161. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +97 -3
  162. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  163. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  164. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +104 -7
  165. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  166. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  167. package/plugin/vendor/reflexio/reflexio/server/README.md +58 -33
  168. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  169. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  170. package/plugin/vendor/reflexio/reflexio/server/api.py +899 -103
  171. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  172. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
  173. package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
  174. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  175. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  176. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  177. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  178. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  179. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  180. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  181. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  182. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  183. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  185. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  186. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  187. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  188. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  189. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  198. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +278 -0
  199. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  200. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  201. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  202. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  203. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  204. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  205. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  206. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  207. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  208. package/plugin/vendor/reflexio/reflexio/server/services/README.md +28 -11
  209. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  212. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  215. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  216. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  217. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  218. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  219. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  220. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  221. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  222. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  225. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  226. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  227. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  229. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  230. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  231. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +47 -16
  232. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  233. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  234. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -0
  238. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +18 -11
  241. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  242. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  243. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +334 -178
  244. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  245. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  246. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  248. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  249. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  250. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  252. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  255. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +31 -22
  256. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  257. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  258. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  259. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  261. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  262. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  263. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -6
  264. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  265. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  266. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +31 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +16 -2
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +644 -197
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  277. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +31 -1369
  278. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
  279. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
  280. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  281. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  282. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  283. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
  289. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  290. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +40 -57
  292. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +4 -677
  296. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
  297. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +372 -34
  311. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  312. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  313. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  314. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  315. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  316. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  317. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  318. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  319. package/scripts/setup-claude-smart.sh +8 -3
  320. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  321. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  322. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  323. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  324. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  325. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  326. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  327. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  328. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  329. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  330. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  331. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  332. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  333. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  334. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  335. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  336. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  337. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  338. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  339. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  340. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  341. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  342. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  343. package/plugin/vendor/reflexio/reflexio/server/_auth.py +0 -25
  344. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  345. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  346. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  347. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  348. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  349. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  350. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  351. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  352. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  353. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -0,0 +1,86 @@
1
+ """Integration: hook silently no-ops when OPENCLAW_SMART_INTERNAL=1.
2
+
3
+ This guard prevents an infinite feedback loop where the reflexio backend's
4
+ own openclaw subprocess (the openclaw LLM provider) re-enters openclaw-smart
5
+ and republishes the extractor's system prompt back into reflexio.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import io
11
+ import json
12
+ from pathlib import Path
13
+
14
+ import pytest
15
+
16
+ pytestmark = pytest.mark.integration
17
+
18
+
19
+ @pytest.fixture
20
+ def isolated_state(monkeypatch, tmp_path: Path) -> Path:
21
+ """Force state.py to write under tmp_path so the test never touches ~/.openclaw-smart/."""
22
+ state_dir = tmp_path / "sessions"
23
+ monkeypatch.setenv("OPENCLAW_SMART_STATE_DIR", str(state_dir))
24
+ return state_dir
25
+
26
+
27
+ def test_hook_short_circuits_when_internal_env_set(
28
+ monkeypatch: pytest.MonkeyPatch,
29
+ capsys: pytest.CaptureFixture[str],
30
+ isolated_state: Path,
31
+ ) -> None:
32
+ """With OPENCLAW_SMART_INTERNAL=1, every handler is bypassed and stdout is empty."""
33
+ monkeypatch.setenv("OPENCLAW_SMART_INTERNAL", "1")
34
+
35
+ from openclaw_smart import hook
36
+
37
+ payload = {
38
+ "event": {"prompt": "anything"},
39
+ "ctx": {"sessionKey": "internal-1", "agentId": "a"},
40
+ }
41
+ monkeypatch.setattr("sys.stdin", io.StringIO(json.dumps(payload)))
42
+
43
+ rc = hook.main(["openclaw", "before-prompt-build"])
44
+ assert rc == 0
45
+
46
+ # No session JSONL should have been created — the handler bailed before
47
+ # ever touching state.append().
48
+ assert not isolated_state.exists() or not list(isolated_state.glob("*.jsonl"))
49
+
50
+ out = capsys.readouterr().out
51
+ # The TS shim treats empty stdout as "no return value" per the design.
52
+ assert out.strip() == ""
53
+
54
+
55
+ def test_hook_short_circuits_when_workspace_inside_reflexio(
56
+ monkeypatch: pytest.MonkeyPatch,
57
+ capsys: pytest.CaptureFixture[str],
58
+ isolated_state: Path,
59
+ tmp_path: Path,
60
+ ) -> None:
61
+ """A workspaceDir inside the reflexio install dir is also treated as internal.
62
+
63
+ The reflexio backend cwd's into its own checkout when it spawns the openclaw
64
+ subprocess for the LLM provider. internal_call.is_internal_invocation honours
65
+ this so the recursion guard fires even if the env var got stripped.
66
+ """
67
+ import reflexio
68
+
69
+ reflexio_pkg_dir = Path(reflexio.__file__).resolve().parent
70
+ payload = {
71
+ "event": {"prompt": "anything"},
72
+ "ctx": {
73
+ "sessionKey": "internal-2",
74
+ "agentId": "a",
75
+ "workspaceDir": str(reflexio_pkg_dir / "sub"),
76
+ },
77
+ }
78
+ monkeypatch.setattr("sys.stdin", io.StringIO(json.dumps(payload)))
79
+
80
+ from openclaw_smart import hook
81
+
82
+ rc = hook.main(["openclaw", "before-prompt-build"])
83
+ assert rc == 0
84
+ out = capsys.readouterr().out
85
+ assert out.strip() == ""
86
+ assert not isolated_state.exists() or not list(isolated_state.glob("*.jsonl"))
@@ -0,0 +1,144 @@
1
+ """Integration: search reflexio, render the hits, assert the prependContext envelope.
2
+
3
+ Two flavours of coverage:
4
+
5
+ * **live-backend path** — when reflexio is reachable at ``REFLEXIO_URL``, run
6
+ ``context_inject.emit_context`` end-to-end and assert the expected envelope
7
+ shape. Skipped automatically when the backend isn't running.
8
+ * **stub path** — replace ``Adapter.search_all`` with a fixed return value so
9
+ the rendering + envelope-emit logic is exercised regardless of environment.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ import sys
17
+ from io import StringIO
18
+ from pathlib import Path
19
+ from unittest.mock import MagicMock
20
+
21
+ import pytest
22
+
23
+ pytestmark = pytest.mark.integration
24
+
25
+
26
+ def _reflexio_url() -> str:
27
+ return os.environ.get("REFLEXIO_URL", "http://localhost:8061/")
28
+
29
+
30
+ def _backend_alive(url: str) -> bool:
31
+ import urllib.error
32
+ import urllib.request
33
+
34
+ health = url.rstrip("/") + "/health"
35
+ try:
36
+ with urllib.request.urlopen(health, timeout=1.5) as resp: # nosec — local only
37
+ return resp.status == 200
38
+ except (urllib.error.URLError, TimeoutError, OSError):
39
+ return False
40
+
41
+
42
+ @pytest.fixture
43
+ def isolated_state(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
44
+ state_dir = tmp_path / "sessions"
45
+ monkeypatch.setenv("OPENCLAW_SMART_STATE_DIR", str(state_dir))
46
+ return state_dir
47
+
48
+
49
+ def _parse_emitted(captured: StringIO) -> dict | None:
50
+ raw = captured.getvalue().strip()
51
+ if not raw:
52
+ return None
53
+ return json.loads(raw)
54
+
55
+
56
+ def test_emit_context_returns_false_when_search_empty(
57
+ monkeypatch: pytest.MonkeyPatch, isolated_state: Path
58
+ ) -> None:
59
+ """No hits → no envelope, no JSONL side-effect."""
60
+ from openclaw_smart import context_inject
61
+
62
+ captured = StringIO()
63
+ monkeypatch.setattr(sys, "stdout", captured)
64
+
65
+ fake_adapter = MagicMock()
66
+ fake_adapter.search_all.return_value = ([], [], [])
67
+
68
+ emitted = context_inject.emit_context(
69
+ session_id="sess-empty",
70
+ project_id="proj-1",
71
+ query="anything",
72
+ top_k=3,
73
+ adapter=fake_adapter,
74
+ )
75
+ assert emitted is False
76
+ assert _parse_emitted(captured) is None
77
+ assert not (isolated_state / "sess-empty.injected.jsonl").exists()
78
+
79
+
80
+ def test_emit_context_wraps_hits_in_prependContext_envelope(
81
+ monkeypatch: pytest.MonkeyPatch, isolated_state: Path
82
+ ) -> None:
83
+ """A stubbed user playbook is rendered as markdown under ``prependContext``."""
84
+ from openclaw_smart import context_inject
85
+
86
+ captured = StringIO()
87
+ monkeypatch.setattr(sys, "stdout", captured)
88
+
89
+ user_playbook = {
90
+ "content": "Always validate user input before persistence.",
91
+ "trigger": "any external string lands in the persistence layer",
92
+ "rationale": "blocks a whole class of injection bugs",
93
+ "user_playbook_id": "pb-1",
94
+ }
95
+ fake_adapter = MagicMock()
96
+ fake_adapter.search_all.return_value = ([user_playbook], [], [])
97
+
98
+ emitted = context_inject.emit_context(
99
+ session_id="sess-hit",
100
+ project_id="proj-1",
101
+ query="how do I validate user input",
102
+ top_k=3,
103
+ adapter=fake_adapter,
104
+ )
105
+
106
+ assert emitted is True
107
+ payload = _parse_emitted(captured)
108
+ assert payload is not None
109
+ md = payload.get("prependContext")
110
+ assert md, f"expected prependContext markdown, got {payload!r}"
111
+ # Title text shows up inside the rendered markdown.
112
+ assert "validate user input" in md.lower()
113
+ # Citation registry was persisted for the agent_end hook to consume.
114
+ assert (isolated_state / "sess-hit.injected.jsonl").exists()
115
+
116
+
117
+ def test_emit_context_against_live_backend(
118
+ monkeypatch: pytest.MonkeyPatch, isolated_state: Path
119
+ ) -> None:
120
+ """When a real backend is running, the full pipeline executes without error.
121
+
122
+ No fixture data is seeded — the backend may legitimately return zero hits;
123
+ the assertion is that the call completes, not that there's data to inject.
124
+ """
125
+ if not _backend_alive(_reflexio_url()):
126
+ pytest.skip(f"reflexio backend not reachable at {_reflexio_url()}")
127
+
128
+ from openclaw_smart import context_inject
129
+
130
+ captured = StringIO()
131
+ monkeypatch.setattr(sys, "stdout", captured)
132
+
133
+ # No assertion on emitted bool — backend state is environment-dependent.
134
+ context_inject.emit_context(
135
+ session_id="sess-live",
136
+ project_id="proj-live",
137
+ query="ping",
138
+ top_k=3,
139
+ )
140
+ # If markdown was emitted it should at least be valid JSON with the right key.
141
+ raw = captured.getvalue().strip()
142
+ if raw:
143
+ payload = json.loads(raw)
144
+ assert "prependContext" in payload
@@ -0,0 +1,184 @@
1
+ """Tests for openclaw_smart.cli."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from argparse import Namespace
6
+ from unittest.mock import patch
7
+
8
+ import pytest
9
+ from openclaw_smart import cli
10
+
11
+
12
+ @pytest.fixture(autouse=True)
13
+ def isolate_state_dir(monkeypatch, tmp_path):
14
+ sessions = tmp_path / "sessions"
15
+ monkeypatch.setenv("OPENCLAW_SMART_STATE_DIR", str(sessions))
16
+ return sessions
17
+
18
+
19
+ def test_cmd_show_fetches_all_entities(capsys):
20
+ with (
21
+ patch("openclaw_smart.cli.Adapter") as Ad,
22
+ patch("openclaw_smart.cli.ids.resolve_project_id", return_value="proj-x"),
23
+ ):
24
+ Ad.return_value.fetch_all.return_value = ([], [], [])
25
+ rc = cli.cmd_show(Namespace(project=None))
26
+ assert rc == 0
27
+ out = capsys.readouterr().out
28
+ assert "proj-x" in out
29
+
30
+
31
+ def test_cmd_show_renders_markdown(capsys):
32
+ with (
33
+ patch("openclaw_smart.cli.Adapter") as Ad,
34
+ patch("openclaw_smart.cli.ids.resolve_project_id", return_value="proj-x"),
35
+ ):
36
+ Ad.return_value.fetch_all.return_value = (
37
+ [{"user_playbook_id": "abc", "content": "Test rule"}],
38
+ [],
39
+ [],
40
+ )
41
+ cli.cmd_show(Namespace(project=None))
42
+ out = capsys.readouterr().out
43
+ assert "Test rule" in out
44
+ assert "[oc:" in out
45
+
46
+
47
+ def test_cmd_show_honors_project_override(capsys):
48
+ with (
49
+ patch("openclaw_smart.cli.Adapter") as Ad,
50
+ patch("openclaw_smart.cli.ids.resolve_project_id", return_value="ignored"),
51
+ ):
52
+ Ad.return_value.fetch_all.return_value = ([], [], [])
53
+ cli.cmd_show(Namespace(project="explicit-proj"))
54
+ Ad.return_value.fetch_all.assert_called_once()
55
+ kwargs = Ad.return_value.fetch_all.call_args[1]
56
+ assert kwargs["project_id"] == "explicit-proj"
57
+
58
+
59
+ def test_cmd_learn_no_session_returns_zero(isolate_state_dir):
60
+ rc = cli.cmd_learn(Namespace(session=None, project=None, note=None))
61
+ assert rc == 0
62
+
63
+
64
+ def test_cmd_learn_force_extracts(isolate_state_dir):
65
+ # Seed a session JSONL so _latest_session_id picks it up.
66
+ from openclaw_smart import state
67
+
68
+ state.append("sess-1", {"role": "User", "content": "x"})
69
+ with (
70
+ patch("openclaw_smart.cli.publish") as pub,
71
+ patch("openclaw_smart.cli.ids.resolve_project_id", return_value="proj-x"),
72
+ ):
73
+ pub.publish_unpublished.return_value = ("ok", 1)
74
+ rc = cli.cmd_learn(Namespace(session=None, project=None, note=None))
75
+ assert rc == 0
76
+ kwargs = pub.publish_unpublished.call_args[1]
77
+ assert kwargs["force_extraction"] is True
78
+ assert kwargs["session_id"] == "sess-1"
79
+
80
+
81
+ def test_cmd_learn_appends_note(isolate_state_dir):
82
+ from openclaw_smart import state
83
+
84
+ state.append("sess-2", {"role": "User", "content": "earlier"})
85
+ with (
86
+ patch("openclaw_smart.cli.publish") as pub,
87
+ patch("openclaw_smart.cli.ids.resolve_project_id", return_value="proj-x"),
88
+ ):
89
+ pub.publish_unpublished.return_value = ("ok", 2)
90
+ cli.cmd_learn(Namespace(session="sess-2", project=None, note="key insight"))
91
+ records = state.read_all("sess-2")
92
+ contents = [r.get("content") for r in records]
93
+ assert "key insight" in contents
94
+
95
+
96
+ def test_cmd_learn_handles_unreachable_backend(isolate_state_dir, capsys):
97
+ from openclaw_smart import state
98
+
99
+ state.append("sess-3", {"role": "User", "content": "x"})
100
+ with (
101
+ patch("openclaw_smart.cli.publish") as pub,
102
+ patch("openclaw_smart.cli.ids.resolve_project_id", return_value="proj-x"),
103
+ ):
104
+ pub.publish_unpublished.return_value = ("failed", 0)
105
+ rc = cli.cmd_learn(Namespace(session=None, project=None, note=None))
106
+ assert rc == 1
107
+ assert "Failed to reach reflexio" in capsys.readouterr().out
108
+
109
+
110
+ def test_cmd_clear_all_requires_yes(capsys):
111
+ with patch("openclaw_smart.cli._resolve_clear_all_targets", return_value=[]):
112
+ rc = cli.cmd_clear_all(Namespace(yes=False))
113
+ assert rc != 0
114
+ assert "--yes" in capsys.readouterr().out
115
+
116
+
117
+ def test_cmd_clear_all_with_yes_proceeds(monkeypatch, tmp_path):
118
+ sessions = tmp_path / "sessions"
119
+ sessions.mkdir()
120
+ (sessions / "old.jsonl").write_text('{"role":"User"}\n')
121
+ monkeypatch.setenv("OPENCLAW_SMART_STATE_DIR", str(sessions))
122
+
123
+ with (
124
+ patch("openclaw_smart.cli._resolve_clear_all_targets", return_value=[]),
125
+ patch("openclaw_smart.cli._service_status", return_value="not running"),
126
+ patch("openclaw_smart.cli._run_service", return_value=0),
127
+ ):
128
+ rc = cli.cmd_clear_all(Namespace(yes=True))
129
+ assert rc == 0
130
+ assert not (sessions / "old.jsonl").exists()
131
+
132
+
133
+ def test_cmd_clear_all_restarts_backend_after_delete_failure(tmp_path):
134
+ target = cli._ClearAllTarget(
135
+ path=tmp_path / "reflexio-openclaw-test",
136
+ kind="dir",
137
+ label="test target",
138
+ )
139
+ service_calls: list[str] = []
140
+
141
+ def fake_run_service(_script, command) -> int: # noqa: ANN001
142
+ service_calls.append(command)
143
+ return 0
144
+
145
+ with (
146
+ patch("openclaw_smart.cli._resolve_clear_all_targets", return_value=[target]),
147
+ patch("openclaw_smart.cli._service_status", return_value="running on 8071"),
148
+ patch(
149
+ "openclaw_smart.cli._remove_clear_all_target",
150
+ side_effect=cli._ClearAllError("boom"),
151
+ ),
152
+ patch("openclaw_smart.cli._run_service", side_effect=fake_run_service),
153
+ ):
154
+ rc = cli.cmd_clear_all(Namespace(yes=True))
155
+
156
+ assert rc == 1
157
+ assert service_calls == ["stop", "start"]
158
+
159
+
160
+ def test_build_parser_accepts_show():
161
+ parser = cli._build_parser()
162
+ args = parser.parse_args(["show"])
163
+ assert args.command == "show"
164
+ assert args.project is None
165
+
166
+
167
+ def test_build_parser_accepts_learn_with_note():
168
+ parser = cli._build_parser()
169
+ args = parser.parse_args(["learn", "--note", "hello"])
170
+ assert args.command == "learn"
171
+ assert args.note == "hello"
172
+
173
+
174
+ def test_build_parser_accepts_clear_all_yes():
175
+ parser = cli._build_parser()
176
+ args = parser.parse_args(["clear-all", "--yes"])
177
+ assert args.yes is True
178
+
179
+
180
+ def test_build_parser_accepts_restart_flags():
181
+ parser = cli._build_parser()
182
+ args = parser.parse_args(["restart", "--skip-backend", "--no-rebuild"])
183
+ assert args.skip_backend is True
184
+ assert args.no_rebuild is True
@@ -0,0 +1,142 @@
1
+ """Tests for openclaw_smart.events.after_tool_call."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ import pytest
8
+ from openclaw_smart.events import after_tool_call as atc
9
+
10
+
11
+ @pytest.fixture(autouse=True)
12
+ def isolate_state_dir(monkeypatch, tmp_path):
13
+ sessions = tmp_path / "sessions"
14
+ monkeypatch.setenv("OPENCLAW_SMART_STATE_DIR", str(sessions))
15
+ return sessions
16
+
17
+
18
+ def _read_records(sessions_dir, session_id="s1"):
19
+ path = sessions_dir / f"{session_id}.jsonl"
20
+ if not path.exists():
21
+ return []
22
+ return [json.loads(line) for line in path.read_text().splitlines() if line]
23
+
24
+
25
+ def test_handle_records_tool_invocation_camelcase(isolate_state_dir):
26
+ """openClaw delivers camelCase fields; handler translates to snake_case."""
27
+ atc.handle(
28
+ {
29
+ "sessionKey": "s1",
30
+ "toolName": "Bash",
31
+ "params": {"command": "ls"},
32
+ "result": "file1.txt",
33
+ "agentId": "a",
34
+ }
35
+ )
36
+ records = _read_records(isolate_state_dir)
37
+ assert len(records) == 1
38
+ rec = records[0]
39
+ assert rec["role"] == "Assistant_tool"
40
+ assert rec["tool_name"] == "Bash"
41
+ assert rec["tool_input"] == {"command": "ls"}
42
+ assert rec["tool_output"] == "file1.txt"
43
+ assert rec["status"] == "success"
44
+
45
+
46
+ def test_handle_accepts_snake_case_fallback(isolate_state_dir):
47
+ """Already-translated payloads work too."""
48
+ atc.handle(
49
+ {
50
+ "sessionKey": "s2",
51
+ "tool_name": "Edit",
52
+ "tool_input": {"file_path": "x.py", "new_string": "hi"},
53
+ "tool_response": "ok",
54
+ "status": "success",
55
+ }
56
+ )
57
+ records = _read_records(isolate_state_dir, "s2")
58
+ assert records[0]["tool_name"] == "Edit"
59
+ assert records[0]["tool_input"]["file_path"] == "x.py"
60
+
61
+
62
+ def test_handle_redacts_high_entropy_secrets(isolate_state_dir):
63
+ secret_command = "API_KEY=AbCdEfGhIj1234567890QwErTyUiOp curl ..."
64
+ atc.handle(
65
+ {
66
+ "sessionKey": "s3",
67
+ "toolName": "Bash",
68
+ "params": {"command": secret_command},
69
+ "result": "",
70
+ }
71
+ )
72
+ records = _read_records(isolate_state_dir, "s3")
73
+ command = records[0]["tool_input"]["command"]
74
+ assert "AbCdEfGhIj1234567890QwErTyUiOp" not in command
75
+ assert "<redacted:" in command
76
+
77
+
78
+ def test_handle_does_not_redact_simple_assignments(isolate_state_dir):
79
+ """LOG_LEVEL=INFO should not be redacted — too short and not entropy-y."""
80
+ atc.handle(
81
+ {
82
+ "sessionKey": "s4",
83
+ "toolName": "Bash",
84
+ "params": {"command": "LOG_LEVEL=INFO some_cmd"},
85
+ "result": "",
86
+ }
87
+ )
88
+ records = _read_records(isolate_state_dir, "s4")
89
+ assert "LOG_LEVEL=INFO" in records[0]["tool_input"]["command"]
90
+
91
+
92
+ def test_handle_classifies_error_status(isolate_state_dir):
93
+ atc.handle(
94
+ {
95
+ "sessionKey": "s5",
96
+ "toolName": "Bash",
97
+ "params": {"command": "false"},
98
+ "result": {"is_error": True, "error": "exit 1"},
99
+ }
100
+ )
101
+ records = _read_records(isolate_state_dir, "s5")
102
+ assert records[0]["status"] == "error"
103
+ assert "exit 1" in records[0]["tool_output"]
104
+
105
+
106
+ def test_handle_flattens_bash_dict_output(isolate_state_dir):
107
+ atc.handle(
108
+ {
109
+ "sessionKey": "s6",
110
+ "toolName": "Bash",
111
+ "params": {"command": "echo hi"},
112
+ "result": {"stdout": "hi", "stderr": ""},
113
+ }
114
+ )
115
+ records = _read_records(isolate_state_dir, "s6")
116
+ assert records[0]["tool_output"] == "hi"
117
+
118
+
119
+ def test_handle_skips_when_no_session_id(isolate_state_dir):
120
+ atc.handle({"toolName": "Bash", "params": {"command": "ls"}})
121
+ assert _read_records(isolate_state_dir) == []
122
+
123
+
124
+ def test_handle_skips_when_no_tool_name(isolate_state_dir):
125
+ atc.handle({"sessionKey": "s7", "params": {"command": "ls"}})
126
+ assert _read_records(isolate_state_dir, "s7") == []
127
+
128
+
129
+ def test_handle_truncates_long_output(isolate_state_dir):
130
+ long_output = "x" * 5000
131
+ atc.handle(
132
+ {
133
+ "sessionKey": "s8",
134
+ "toolName": "Bash",
135
+ "params": {"command": "cat large_file"},
136
+ "result": long_output,
137
+ }
138
+ )
139
+ records = _read_records(isolate_state_dir, "s8")
140
+ output = records[0]["tool_output"]
141
+ assert "truncated" in output
142
+ assert len(output) < 5000