claude-smart 0.2.45 → 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 (322) 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 +5 -1
  35. package/plugin/scripts/backend-service.sh +22 -2
  36. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  37. package/plugin/scripts/hook_entry.sh +1 -1
  38. package/plugin/scripts/opencode-claude-compat +4 -0
  39. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  40. package/plugin/scripts/opencode-claude-compat.js +225 -0
  41. package/plugin/scripts/smart-install.sh +10 -8
  42. package/plugin/src/README.md +1 -1
  43. package/plugin/src/claude_smart/cli.py +304 -6
  44. package/plugin/src/claude_smart/env_config.py +12 -4
  45. package/plugin/src/claude_smart/events/session_start.py +26 -7
  46. package/plugin/src/claude_smart/events/stop.py +1 -1
  47. package/plugin/src/claude_smart/ids.py +1 -1
  48. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  49. package/plugin/src/claude_smart/runtime.py +7 -1
  50. package/plugin/uv.lock +1 -1
  51. package/plugin/vendor/reflexio/.env.example +54 -30
  52. package/plugin/vendor/reflexio/README.md +14 -8
  53. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  54. package/plugin/vendor/reflexio/reflexio/README.md +1 -0
  55. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  56. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  57. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  58. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  63. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  64. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  65. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  66. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  67. package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
  68. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  69. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  70. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  71. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  141. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
  142. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  143. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
  144. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
  146. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  147. package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
  148. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
  149. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  150. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  151. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  152. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  153. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  157. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
  158. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  159. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  160. package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
  161. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  162. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  163. package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
  164. package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
  165. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  166. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
  167. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  168. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  169. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  170. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  171. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  172. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  173. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  174. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  175. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  176. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  177. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  178. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  179. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  180. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  181. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  182. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  183. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  185. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  186. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  187. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
  188. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  189. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  197. package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
  198. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  199. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  200. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  201. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  202. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  203. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  204. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  205. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  206. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  207. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  208. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  209. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  211. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  212. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  214. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  215. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  216. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  217. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  218. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  219. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  220. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
  221. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  222. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  225. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  226. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
  227. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
  229. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  230. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  231. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  232. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  233. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  234. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  237. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  238. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  241. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
  242. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  243. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  244. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  245. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  246. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  247. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  248. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
  249. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  250. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  251. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  252. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  254. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  255. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  256. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
  257. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
  258. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
  259. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  260. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
  261. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
  262. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
  263. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  264. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  265. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
  266. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  277. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  278. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  279. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  280. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
  281. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  282. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  283. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  284. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  285. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  286. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  287. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  288. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  289. package/scripts/setup-claude-smart.sh +8 -3
  290. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  291. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  292. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  293. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  294. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  295. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  296. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  297. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  298. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  299. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  300. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  301. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  302. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  303. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  304. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  305. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  306. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  307. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  308. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  309. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  310. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  311. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  312. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  313. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  314. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  317. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  318. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  319. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  320. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  321. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  322. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -0,0 +1,233 @@
1
+ """Tests for openclaw_smart.events.agent_end."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from unittest.mock import patch
7
+
8
+ import pytest
9
+ from openclaw_smart import state
10
+ from openclaw_smart.events import agent_end
11
+
12
+
13
+ @pytest.fixture(autouse=True)
14
+ def isolate_state_dir(monkeypatch, tmp_path):
15
+ sessions = tmp_path / "sessions"
16
+ monkeypatch.setenv("OPENCLAW_SMART_STATE_DIR", str(sessions))
17
+ return sessions
18
+
19
+
20
+ def _read_records(sessions_dir, session_id="s1"):
21
+ path = sessions_dir / f"{session_id}.jsonl"
22
+ if not path.exists():
23
+ return []
24
+ return [json.loads(line) for line in path.read_text().splitlines() if line]
25
+
26
+
27
+ def test_extract_assistant_text_plain_string():
28
+ text = agent_end._extract_assistant_text(
29
+ [
30
+ {"role": "user", "content": "hi"},
31
+ {"role": "assistant", "content": "hello"},
32
+ ]
33
+ )
34
+ assert text == "hello"
35
+
36
+
37
+ def test_extract_assistant_text_block_form():
38
+ text = agent_end._extract_assistant_text(
39
+ [
40
+ {"role": "user", "content": "hi"},
41
+ {
42
+ "role": "assistant",
43
+ "content": [
44
+ {"type": "text", "text": "first"},
45
+ {"type": "text", "text": "second"},
46
+ ],
47
+ },
48
+ ]
49
+ )
50
+ assert "first" in text and "second" in text
51
+
52
+
53
+ def test_extract_assistant_text_concatenates_trailing_assistant_turns():
54
+ text = agent_end._extract_assistant_text(
55
+ [
56
+ {"role": "user", "content": "q"},
57
+ {"role": "assistant", "content": "step 1"},
58
+ {"role": "assistant", "content": "step 2"},
59
+ ]
60
+ )
61
+ assert "step 1" in text
62
+ assert "step 2" in text
63
+
64
+
65
+ def test_extract_assistant_text_stops_at_user_boundary():
66
+ text = agent_end._extract_assistant_text(
67
+ [
68
+ {"role": "assistant", "content": "old turn"},
69
+ {"role": "user", "content": "new q"},
70
+ {"role": "assistant", "content": "new answer"},
71
+ ]
72
+ )
73
+ assert "new answer" in text
74
+ assert "old turn" not in text
75
+
76
+
77
+ def test_extract_assistant_text_handles_nested_message_shape():
78
+ """Accept Claude-Code-style ``{message: {role, content}}`` shape too."""
79
+ text = agent_end._extract_assistant_text(
80
+ [{"message": {"role": "assistant", "content": "hi"}}]
81
+ )
82
+ assert text == "hi"
83
+
84
+
85
+ def test_extract_assistant_text_empty_for_no_messages():
86
+ assert agent_end._extract_assistant_text([]) == ""
87
+ assert agent_end._extract_assistant_text(None) == ""
88
+
89
+
90
+ def test_handle_no_session_id_returns_silently(isolate_state_dir):
91
+ with patch("openclaw_smart.events.agent_end.publish") as pub:
92
+ agent_end.handle({})
93
+ pub.publish_unpublished.assert_not_called()
94
+
95
+
96
+ def test_handle_appends_assistant_record(isolate_state_dir):
97
+ with (
98
+ patch("openclaw_smart.events.agent_end.publish") as pub,
99
+ patch(
100
+ "openclaw_smart.events.agent_end.ids.resolve_project_id_with_fallback",
101
+ return_value="proj-x",
102
+ ),
103
+ ):
104
+ pub.publish_unpublished.return_value = ("ok", 1)
105
+ agent_end.handle(
106
+ {
107
+ "sessionKey": "s1",
108
+ "agentId": "a",
109
+ "messages": [
110
+ {"role": "user", "content": "q"},
111
+ {"role": "assistant", "content": "Done."},
112
+ ],
113
+ }
114
+ )
115
+
116
+ records = _read_records(isolate_state_dir)
117
+ assert records[-1]["role"] == "Assistant"
118
+ assert records[-1]["content"] == "Done."
119
+ assert records[-1]["user_id"] == "proj-x"
120
+
121
+
122
+ def test_handle_publishes_unpublished_slice(isolate_state_dir):
123
+ with (
124
+ patch("openclaw_smart.events.agent_end.publish") as pub,
125
+ patch(
126
+ "openclaw_smart.events.agent_end.ids.resolve_project_id_with_fallback",
127
+ return_value="proj-x",
128
+ ),
129
+ ):
130
+ pub.publish_unpublished.return_value = ("ok", 1)
131
+ agent_end.handle(
132
+ {
133
+ "sessionKey": "s1",
134
+ "messages": [{"role": "assistant", "content": "Done."}],
135
+ }
136
+ )
137
+ pub.publish_unpublished.assert_called_once()
138
+ kwargs = pub.publish_unpublished.call_args[1]
139
+ assert kwargs["session_id"] == "s1"
140
+ assert kwargs["project_id"] == "proj-x"
141
+ assert kwargs["force_extraction"] is False
142
+ assert kwargs["skip_aggregation"] is False
143
+
144
+
145
+ def test_handle_resolves_citations(isolate_state_dir, monkeypatch):
146
+ # Seed the injected registry so cited tags resolve.
147
+ state.append_injected(
148
+ "s1",
149
+ [
150
+ {
151
+ "id": "s1-ab12",
152
+ "kind": "playbook",
153
+ "title": "Use OAuth2",
154
+ "real_id": "abc",
155
+ }
156
+ ],
157
+ )
158
+
159
+ with (
160
+ patch("openclaw_smart.events.agent_end.publish") as pub,
161
+ patch(
162
+ "openclaw_smart.events.agent_end.ids.resolve_project_id_with_fallback",
163
+ return_value="proj-x",
164
+ ),
165
+ ):
166
+ pub.publish_unpublished.return_value = ("ok", 1)
167
+ agent_end.handle(
168
+ {
169
+ "sessionKey": "s1",
170
+ "messages": [
171
+ {
172
+ "role": "assistant",
173
+ "content": (
174
+ "Implemented OAuth.\n"
175
+ "✨ 1 openclaw-smart learning applied [oc:s1-ab12]"
176
+ ),
177
+ }
178
+ ],
179
+ }
180
+ )
181
+
182
+ records = _read_records(isolate_state_dir)
183
+ assert "cited_items" in records[-1]
184
+ cited = records[-1]["cited_items"]
185
+ assert cited[0]["id"] == "s1-ab12"
186
+ assert cited[0]["title"] == "Use OAuth2"
187
+ assert cited[0]["real_id"] == "abc"
188
+
189
+
190
+ def test_handle_skips_unknown_citations(isolate_state_dir):
191
+ """Citations for ids not in the registry are dropped, not raised."""
192
+ with (
193
+ patch("openclaw_smart.events.agent_end.publish") as pub,
194
+ patch(
195
+ "openclaw_smart.events.agent_end.ids.resolve_project_id_with_fallback",
196
+ return_value="proj-x",
197
+ ),
198
+ ):
199
+ pub.publish_unpublished.return_value = ("ok", 1)
200
+ agent_end.handle(
201
+ {
202
+ "sessionKey": "s1",
203
+ "messages": [
204
+ {
205
+ "role": "assistant",
206
+ "content": (
207
+ "Done.\n✨ 1 openclaw-smart learning applied [oc:s9-9999]"
208
+ ),
209
+ }
210
+ ],
211
+ }
212
+ )
213
+ records = _read_records(isolate_state_dir)
214
+ assert "cited_items" not in records[-1]
215
+
216
+
217
+ def test_handle_falls_back_to_session_id(isolate_state_dir):
218
+ with (
219
+ patch("openclaw_smart.events.agent_end.publish") as pub,
220
+ patch(
221
+ "openclaw_smart.events.agent_end.ids.resolve_project_id_with_fallback",
222
+ return_value="proj-x",
223
+ ),
224
+ ):
225
+ pub.publish_unpublished.return_value = ("ok", 1)
226
+ agent_end.handle(
227
+ {
228
+ "sessionId": "sess-y",
229
+ "messages": [{"role": "assistant", "content": "ok"}],
230
+ }
231
+ )
232
+ kwargs = pub.publish_unpublished.call_args[1]
233
+ assert kwargs["session_id"] == "sess-y"
@@ -0,0 +1,116 @@
1
+ """Tests for openclaw_smart.events.before_prompt_build."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from unittest.mock import patch
7
+
8
+ import pytest
9
+ from openclaw_smart.events import before_prompt_build as bpb
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_handle_buffers_prompt(isolate_state_dir):
20
+ with (
21
+ patch("openclaw_smart.events.before_prompt_build.context_inject") as ci,
22
+ patch(
23
+ "openclaw_smart.events.before_prompt_build.ids.resolve_project_id_with_fallback",
24
+ return_value="proj-x",
25
+ ),
26
+ ):
27
+ ci.emit_context.return_value = False
28
+ bpb.handle(
29
+ {
30
+ "sessionKey": "s1",
31
+ "prompt": "implement OAuth",
32
+ "agentId": "agent-x",
33
+ "workspaceDir": "/tmp/x",
34
+ }
35
+ )
36
+
37
+ path = isolate_state_dir / "s1.jsonl"
38
+ assert path.exists()
39
+ lines = path.read_text().splitlines()
40
+ record = json.loads(lines[0])
41
+ assert record["role"] == "User"
42
+ assert record["content"] == "implement OAuth"
43
+ assert record["user_id"] == "proj-x"
44
+
45
+
46
+ def test_handle_calls_emit_context_with_top_k():
47
+ with (
48
+ patch("openclaw_smart.events.before_prompt_build.context_inject") as ci,
49
+ patch(
50
+ "openclaw_smart.events.before_prompt_build.ids.resolve_project_id_with_fallback",
51
+ return_value="proj-x",
52
+ ),
53
+ ):
54
+ bpb.handle(
55
+ {
56
+ "sessionKey": "s1",
57
+ "prompt": "implement OAuth",
58
+ "agentId": "agent-x",
59
+ "workspaceDir": "/tmp/x",
60
+ }
61
+ )
62
+ ci.emit_context.assert_called_once()
63
+ kwargs = ci.emit_context.call_args[1]
64
+ assert kwargs["session_id"] == "s1"
65
+ assert kwargs["project_id"] == "proj-x"
66
+ assert kwargs["query"] == "implement OAuth"
67
+ assert kwargs["top_k"] == 3
68
+
69
+
70
+ def test_handle_skips_when_no_session_id(isolate_state_dir):
71
+ with patch("openclaw_smart.events.before_prompt_build.context_inject") as ci:
72
+ bpb.handle({"prompt": "x", "agentId": "a"})
73
+ ci.emit_context.assert_not_called()
74
+ assert not isolate_state_dir.exists() or not any(isolate_state_dir.iterdir())
75
+
76
+
77
+ def test_handle_skips_when_no_prompt(isolate_state_dir):
78
+ with patch("openclaw_smart.events.before_prompt_build.context_inject") as ci:
79
+ bpb.handle({"sessionKey": "s1", "prompt": "", "agentId": "a"})
80
+ ci.emit_context.assert_not_called()
81
+
82
+
83
+ def test_handle_swallows_emit_exceptions(isolate_state_dir):
84
+ with (
85
+ patch("openclaw_smart.events.before_prompt_build.context_inject") as ci,
86
+ patch(
87
+ "openclaw_smart.events.before_prompt_build.ids.resolve_project_id_with_fallback",
88
+ return_value="proj-x",
89
+ ),
90
+ ):
91
+ ci.emit_context.side_effect = ConnectionError("reflexio down")
92
+ # Must not raise
93
+ bpb.handle(
94
+ {
95
+ "sessionKey": "s1",
96
+ "prompt": "implement OAuth",
97
+ "agentId": "a",
98
+ "workspaceDir": "/tmp/x",
99
+ }
100
+ )
101
+ # Prompt still buffered
102
+ path = isolate_state_dir / "s1.jsonl"
103
+ assert path.exists()
104
+
105
+
106
+ def test_handle_falls_back_to_session_id():
107
+ with (
108
+ patch("openclaw_smart.events.before_prompt_build.context_inject") as ci,
109
+ patch(
110
+ "openclaw_smart.events.before_prompt_build.ids.resolve_project_id_with_fallback",
111
+ return_value="proj-x",
112
+ ),
113
+ ):
114
+ bpb.handle({"sessionId": "s2", "prompt": "hello", "agentId": "a"})
115
+ ci.emit_context.assert_called_once()
116
+ assert ci.emit_context.call_args[1]["session_id"] == "s2"
@@ -0,0 +1,35 @@
1
+ """Tests for openclaw_smart.events.before_tool_call (observe-only stub)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from openclaw_smart.events import before_tool_call as btc
6
+
7
+
8
+ def test_handle_is_silent(capsys):
9
+ btc.handle(
10
+ {
11
+ "sessionKey": "s1",
12
+ "tool_name": "Edit",
13
+ "tool_input": {"file_path": "x.py", "new_string": "..."},
14
+ "agentId": "a",
15
+ }
16
+ )
17
+ assert capsys.readouterr().out == ""
18
+
19
+
20
+ def test_handle_accepts_empty_payload(capsys):
21
+ btc.handle({})
22
+ assert capsys.readouterr().out == ""
23
+
24
+
25
+ def test_handle_accepts_camelcase_payload(capsys):
26
+ """openClaw delivers camelCase fields; handler should ignore them silently."""
27
+ btc.handle(
28
+ {
29
+ "sessionKey": "s1",
30
+ "toolName": "Bash",
31
+ "params": {"command": "ls"},
32
+ "agentId": "a",
33
+ }
34
+ )
35
+ assert capsys.readouterr().out == ""
@@ -0,0 +1,47 @@
1
+ """Tests for openclaw_smart.events.session_end."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from unittest.mock import patch
6
+
7
+ from openclaw_smart.events import session_end
8
+
9
+
10
+ def test_handle_no_session_id_returns_silently():
11
+ with patch("openclaw_smart.events.session_end.publish") as pub:
12
+ session_end.handle({})
13
+ pub.publish_unpublished.assert_not_called()
14
+
15
+
16
+ def test_handle_force_extracts():
17
+ with (
18
+ patch("openclaw_smart.events.session_end.publish") as pub,
19
+ patch(
20
+ "openclaw_smart.events.session_end.ids.resolve_project_id_with_fallback",
21
+ return_value="proj-x",
22
+ ),
23
+ ):
24
+ session_end.handle({"sessionKey": "s1", "agentId": "a"})
25
+ pub.publish_unpublished.assert_called_once()
26
+ kwargs = pub.publish_unpublished.call_args[1]
27
+ assert kwargs["session_id"] == "s1"
28
+ assert kwargs["project_id"] == "proj-x"
29
+ assert kwargs["force_extraction"] is True
30
+ assert kwargs["skip_aggregation"] is False
31
+
32
+
33
+ def test_handle_falls_back_to_session_id_key():
34
+ with (
35
+ patch("openclaw_smart.events.session_end.publish") as pub,
36
+ patch(
37
+ "openclaw_smart.events.session_end.ids.resolve_project_id_with_fallback",
38
+ return_value="proj-y",
39
+ ),
40
+ ):
41
+ session_end.handle({"sessionId": "s2"})
42
+ pub.publish_unpublished.assert_called_once()
43
+ kwargs = pub.publish_unpublished.call_args[1]
44
+ assert kwargs["session_id"] == "s2"
45
+ assert kwargs["project_id"] == "proj-y"
46
+ assert kwargs["force_extraction"] is True
47
+ assert kwargs["skip_aggregation"] is False
@@ -0,0 +1,109 @@
1
+ """Tests for openclaw_smart.events.session_start."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from types import SimpleNamespace
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import pytest
10
+ from openclaw_smart.events import session_start
11
+
12
+
13
+ @pytest.fixture
14
+ def fake_adapter():
15
+ adapter = MagicMock()
16
+ adapter.fetch_stall_state.return_value = None
17
+ adapter.apply_extraction_defaults.return_value = True
18
+ adapter.apply_optimizer_defaults.return_value = True
19
+ return adapter
20
+
21
+
22
+ def test_handle_no_session_id_returns_silently(fake_adapter, capsys):
23
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
24
+ session_start.handle({})
25
+ assert capsys.readouterr().out == ""
26
+ fake_adapter.apply_extraction_defaults.assert_not_called()
27
+
28
+
29
+ def test_handle_applies_extraction_defaults(fake_adapter, capsys):
30
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
31
+ session_start.handle({"sessionKey": "s1", "workspaceDir": "/tmp"})
32
+ fake_adapter.apply_extraction_defaults.assert_called_once_with(
33
+ window_size=5, stride_size=3
34
+ )
35
+
36
+
37
+ def test_handle_applies_optimizer_defaults_by_default(fake_adapter, monkeypatch):
38
+ monkeypatch.delenv("OPENCLAW_SMART_ENABLE_OPTIMIZER", raising=False)
39
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
40
+ session_start.handle({"sessionKey": "s1"})
41
+ fake_adapter.apply_optimizer_defaults.assert_called_once()
42
+ kwargs = fake_adapter.apply_optimizer_defaults.call_args[1]
43
+ assert "openclaw-smart-optimizer-assistant" in kwargs["script_path"]
44
+
45
+
46
+ def test_handle_skips_optimizer_when_disabled(fake_adapter, monkeypatch):
47
+ monkeypatch.setenv("OPENCLAW_SMART_ENABLE_OPTIMIZER", "0")
48
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
49
+ session_start.handle({"sessionKey": "s1"})
50
+ fake_adapter.apply_optimizer_defaults.assert_not_called()
51
+
52
+
53
+ def test_handle_emits_banner_when_stall_active(fake_adapter, capsys):
54
+ fake_adapter.fetch_stall_state.return_value = SimpleNamespace(
55
+ stalled=True,
56
+ notified_in_cc=False,
57
+ reason="auth_error",
58
+ reset_estimate=None,
59
+ )
60
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
61
+ session_start.handle({"sessionKey": "s1"})
62
+ out = capsys.readouterr().out.strip()
63
+ assert out, "expected stdout envelope when stall is active"
64
+ parsed = json.loads(out)
65
+ assert "prependContext" in parsed
66
+ assert "openclaw-smart" in parsed["prependContext"]
67
+ fake_adapter.mark_stall_notified.assert_called_once()
68
+
69
+
70
+ def test_handle_skips_banner_when_already_notified(fake_adapter, capsys):
71
+ fake_adapter.fetch_stall_state.return_value = SimpleNamespace(
72
+ stalled=True,
73
+ notified_in_cc=True,
74
+ reason="auth_error",
75
+ reset_estimate=None,
76
+ )
77
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
78
+ session_start.handle({"sessionKey": "s1"})
79
+ assert capsys.readouterr().out == ""
80
+ fake_adapter.mark_stall_notified.assert_not_called()
81
+
82
+
83
+ def test_handle_skips_banner_when_not_stalled(fake_adapter, capsys):
84
+ fake_adapter.fetch_stall_state.return_value = SimpleNamespace(
85
+ stalled=False,
86
+ notified_in_cc=False,
87
+ reason=None,
88
+ reset_estimate=None,
89
+ )
90
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
91
+ session_start.handle({"sessionKey": "s1"})
92
+ assert capsys.readouterr().out == ""
93
+
94
+
95
+ def test_handle_swallows_stall_state_exceptions(fake_adapter, capsys):
96
+ fake_adapter.fetch_stall_state.side_effect = RuntimeError("boom")
97
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
98
+ # Must not raise
99
+ session_start.handle({"sessionKey": "s1"})
100
+ assert capsys.readouterr().out == ""
101
+ # Defaults still applied
102
+ fake_adapter.apply_extraction_defaults.assert_called_once()
103
+
104
+
105
+ def test_handle_falls_back_to_session_id_key(fake_adapter):
106
+ """If only ``sessionId`` is present (openClaw sometimes), it is used."""
107
+ with patch.object(session_start, "_adapter", return_value=fake_adapter):
108
+ session_start.handle({"sessionId": "sess-x"})
109
+ fake_adapter.apply_extraction_defaults.assert_called_once()
@@ -0,0 +1,117 @@
1
+ """Tests for openclaw_smart.hook dispatch."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import json
7
+
8
+ import pytest
9
+ from openclaw_smart import hook
10
+
11
+
12
+ @pytest.fixture(autouse=True)
13
+ def _reset_handlers():
14
+ """Reload handler dispatch dict for each test (state is module-global)."""
15
+ hook._HANDLERS = None
16
+ yield
17
+ hook._HANDLERS = None
18
+
19
+
20
+ def test_main_dispatches_to_handler(monkeypatch):
21
+ called = {}
22
+
23
+ def fake_handler(payload):
24
+ called["payload"] = payload
25
+
26
+ monkeypatch.setattr(
27
+ "sys.stdin",
28
+ io.StringIO(json.dumps({"sessionKey": "s1", "agentId": "a"})),
29
+ )
30
+ monkeypatch.setattr(hook, "_HANDLERS", {"session-start": fake_handler})
31
+
32
+ rc = hook.main(["session-start"])
33
+ assert rc == 0
34
+ assert called["payload"] == {"sessionKey": "s1", "agentId": "a"}
35
+
36
+
37
+ def test_main_accepts_host_arg(monkeypatch):
38
+ called = {}
39
+
40
+ def fake_handler(payload):
41
+ called["payload"] = payload
42
+
43
+ monkeypatch.setattr("sys.stdin", io.StringIO('{"sessionKey":"s1"}'))
44
+ monkeypatch.setattr(hook, "_HANDLERS", {"session-start": fake_handler})
45
+
46
+ rc = hook.main(["openclaw", "session-start"])
47
+ assert rc == 0
48
+ assert called
49
+
50
+
51
+ def test_main_silent_when_recursion_guard_fires(monkeypatch, capsys):
52
+ monkeypatch.setenv("OPENCLAW_SMART_INTERNAL", "1")
53
+ monkeypatch.setattr("sys.stdin", io.StringIO("{}"))
54
+ called = {"count": 0}
55
+
56
+ def boom(payload):
57
+ called["count"] += 1
58
+ raise RuntimeError("should not be called")
59
+
60
+ monkeypatch.setattr(hook, "_HANDLERS", {"session-start": boom})
61
+ rc = hook.main(["session-start"])
62
+ assert rc == 0
63
+ assert called["count"] == 0
64
+ assert capsys.readouterr().out == ""
65
+
66
+
67
+ def test_main_silent_on_handler_exception(monkeypatch, capsys):
68
+ monkeypatch.setattr("sys.stdin", io.StringIO("{}"))
69
+
70
+ def boom(payload):
71
+ raise RuntimeError("x")
72
+
73
+ monkeypatch.setattr(hook, "_HANDLERS", {"session-start": boom})
74
+ rc = hook.main(["session-start"])
75
+ assert rc == 0
76
+ assert capsys.readouterr().out == ""
77
+
78
+
79
+ def test_main_silent_on_unknown_event(monkeypatch, capsys):
80
+ monkeypatch.setattr("sys.stdin", io.StringIO("{}"))
81
+ rc = hook.main(["no-such-event"])
82
+ assert rc == 0
83
+ assert capsys.readouterr().out == ""
84
+
85
+
86
+ def test_main_silent_with_no_event(monkeypatch, capsys):
87
+ monkeypatch.setattr("sys.stdin", io.StringIO("{}"))
88
+ rc = hook.main([])
89
+ assert rc == 0
90
+ assert capsys.readouterr().out == ""
91
+
92
+
93
+ def test_main_handles_malformed_stdin(monkeypatch):
94
+ called = {}
95
+
96
+ def fake_handler(payload):
97
+ called["payload"] = payload
98
+
99
+ monkeypatch.setattr("sys.stdin", io.StringIO("not json {{{"))
100
+ monkeypatch.setattr(hook, "_HANDLERS", {"session-start": fake_handler})
101
+ rc = hook.main(["session-start"])
102
+ assert rc == 0
103
+ # Malformed → empty dict, handler still called
104
+ assert called["payload"] == {}
105
+
106
+
107
+ def test_load_handlers_includes_all_events():
108
+ handlers = hook._load_handlers()
109
+ expected = {
110
+ "session-start",
111
+ "before-prompt-build",
112
+ "before-tool-call",
113
+ "after-tool-call",
114
+ "agent-end",
115
+ "session-end",
116
+ }
117
+ assert set(handlers) == expected