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
@@ -17,7 +17,7 @@ from collections import OrderedDict
17
17
  from collections.abc import Callable
18
18
  from concurrent.futures import Future, ThreadPoolExecutor
19
19
  from concurrent.futures import TimeoutError as FuturesTimeoutError
20
- from typing import TYPE_CHECKING, Any
20
+ from typing import TYPE_CHECKING, Any, cast
21
21
 
22
22
  from reflexio.models.api_schema.retriever_schema import (
23
23
  ConversationTurn,
@@ -41,7 +41,7 @@ from reflexio.models.config_schema import (
41
41
  from reflexio.server.llm.litellm_client import LiteLLMClient
42
42
  from reflexio.server.prompt.prompt_manager import PromptManager
43
43
  from reflexio.server.services.pre_retrieval import QueryReformulator
44
- from reflexio.server.services.retrieval.relevance_floor import apply_relevance_floor
44
+ from reflexio.server.services.retrieval.relevance_floor import apply_relevance_floors
45
45
  from reflexio.server.services.storage.storage_base import BaseStorage
46
46
  from reflexio.server.tracing import profile_step, set_span_data
47
47
 
@@ -50,6 +50,7 @@ if TYPE_CHECKING:
50
50
 
51
51
  logger = logging.getLogger(__name__)
52
52
  _DEFAULT_ENTITY_TYPES = frozenset({"profiles", "agent_playbooks", "user_playbooks"})
53
+ _SOURCE_USER_PLAYBOOK_IDS_KEY = "_source_user_playbook_ids"
53
54
  # Statuses returned for agent_playbooks when the caller does not pass an
54
55
  # explicit ``agent_playbook_status_filter``. Excludes REJECTED so that a
55
56
  # rejection in the dashboard immediately suppresses the playbook from search
@@ -66,6 +67,7 @@ _SEARCH_FANOUT_EXECUTOR = ThreadPoolExecutor(
66
67
  max_workers=_SEARCH_FANOUT_MAX_WORKERS,
67
68
  thread_name_prefix="reflexio-search",
68
69
  )
70
+ _ENV_SINGLE_RPC = "REFLEXIO_UNIFIED_SEARCH_SINGLE_RPC"
69
71
  _EMBEDDING_CACHE_TTL_SECONDS = max(
70
72
  0, int(os.getenv("REFLEXIO_QUERY_EMBEDDING_CACHE_TTL_SECONDS", "300") or "300")
71
73
  )
@@ -76,6 +78,20 @@ _embedding_cache_lock = threading.Lock()
76
78
  _embedding_cache: OrderedDict[tuple[str, int, str, str], tuple[float, list[float]]] = (
77
79
  OrderedDict()
78
80
  )
81
+ RetrievalCaptureHook = Callable[
82
+ [UnifiedSearchRequest, UnifiedSearchResponse, BaseStorage, str], None
83
+ ]
84
+ _retrieval_capture_hook: RetrievalCaptureHook | None = None
85
+
86
+
87
+ def configure_retrieval_capture_hook(hook: RetrievalCaptureHook | None) -> None:
88
+ """Register an optional final-response retrieval capture hook.
89
+
90
+ Deployments that capture retrieval logs install the hook; OSS leaves it
91
+ unset so unified search behavior is unchanged by default.
92
+ """
93
+ global _retrieval_capture_hook
94
+ _retrieval_capture_hook = hook
79
95
 
80
96
 
81
97
  def run_unified_search(
@@ -152,7 +168,13 @@ def run_unified_search(
152
168
  cfg=floor_cfg,
153
169
  )
154
170
 
155
- return UnifiedSearchResponse(
171
+ user_playbooks = _suppress_source_user_playbooks(
172
+ storage=storage,
173
+ agent_playbooks=agent_playbooks or [],
174
+ user_playbooks=user_playbooks or [],
175
+ )
176
+
177
+ response = UnifiedSearchResponse(
156
178
  success=True,
157
179
  profiles=profiles,
158
180
  agent_playbooks=agent_playbooks, # type: ignore[reportArgumentType]
@@ -161,6 +183,32 @@ def run_unified_search(
161
183
  if reformulated_query != request.query
162
184
  else None,
163
185
  )
186
+ _maybe_capture_final_response(
187
+ request=request,
188
+ response=response,
189
+ storage=storage,
190
+ org_id=org_id,
191
+ )
192
+ return response
193
+
194
+
195
+ def _maybe_capture_final_response(
196
+ *,
197
+ request: UnifiedSearchRequest,
198
+ response: UnifiedSearchResponse,
199
+ storage: BaseStorage,
200
+ org_id: str,
201
+ ) -> None:
202
+ hook = _retrieval_capture_hook
203
+ if hook is None:
204
+ return
205
+ try:
206
+ hook(request, response, storage, org_id)
207
+ except Exception:
208
+ logger.warning(
209
+ "Unified search retrieval capture hook failed",
210
+ exc_info=True,
211
+ )
164
212
 
165
213
 
166
214
  def _run_phase_a(
@@ -266,6 +314,33 @@ def _run_phase_b(
266
314
  entity_types=sorted(entity_types),
267
315
  top_k=top_k,
268
316
  ) as span:
317
+ if (
318
+ getattr(storage, "supports_unified_hybrid_search", False)
319
+ and _unified_single_rpc_enabled()
320
+ ):
321
+ combined = _run_phase_b_single_rpc(
322
+ request=request,
323
+ storage=storage,
324
+ embedding=embedding,
325
+ query=query,
326
+ top_k=top_k,
327
+ threshold=threshold,
328
+ entity_types=entity_types,
329
+ allowed_agent_statuses=allowed_agent_statuses,
330
+ )
331
+ if combined is not None:
332
+ profiles, agent_playbooks, user_playbooks = combined
333
+ set_span_data(
334
+ span,
335
+ {
336
+ "single_rpc": True,
337
+ "profiles_count": len(profiles),
338
+ "agent_playbooks_count": len(agent_playbooks),
339
+ "user_playbooks_count": len(user_playbooks),
340
+ },
341
+ )
342
+ return profiles, agent_playbooks, user_playbooks
343
+ span.set_data("single_rpc_fallback", True)
269
344
  profiles_future = (
270
345
  _submit_with_current_context(
271
346
  _SEARCH_FANOUT_EXECUTOR,
@@ -347,6 +422,86 @@ def _run_phase_b(
347
422
  return profiles, agent_playbooks, user_playbooks
348
423
 
349
424
 
425
+ def _unified_single_rpc_enabled() -> bool:
426
+ """Kill switch for the combined Phase B RPC (default on)."""
427
+ return os.getenv(_ENV_SINGLE_RPC, "1").strip().lower() not in {"0", "false", "off"}
428
+
429
+
430
+ def _run_phase_b_single_rpc(
431
+ *,
432
+ request: UnifiedSearchRequest,
433
+ storage: BaseStorage,
434
+ embedding: list[float] | None,
435
+ query: str,
436
+ top_k: int,
437
+ threshold: float,
438
+ entity_types: set[str],
439
+ allowed_agent_statuses: list[PlaybookStatus] | None,
440
+ ) -> tuple[list[UserProfile], list[AgentPlaybook], list[UserPlaybook]] | None:
441
+ """Run all Phase B arms through one combined storage round trip.
442
+
443
+ Trades the per-arm round-trip overhead for serialized execution of the
444
+ three queries inside one database session — a win when round-trip
445
+ overhead dominates per-arm query time (toggle via
446
+ ``REFLEXIO_UNIFIED_SEARCH_SINGLE_RPC`` to compare).
447
+
448
+ Returns:
449
+ The three result lists, or None when the combined call fails so the
450
+ caller can fall back to the per-arm fan-out (e.g. the SQL function
451
+ is not yet migrated on this deployment). Timeouts propagate like the
452
+ fan-out path so a hung database is not retried.
453
+ """
454
+ statuses = (
455
+ list(allowed_agent_statuses)
456
+ if allowed_agent_statuses
457
+ else list(_DEFAULT_AGENT_PLAYBOOK_STATUSES)
458
+ )
459
+ # Resolve storage.unified_hybrid_search before submit so missing or stale
460
+ # capability flags can fall back to the fan-out path.
461
+ unified_hybrid_search = getattr(storage, "unified_hybrid_search", None)
462
+ if not callable(unified_hybrid_search):
463
+ return None
464
+
465
+ future = _submit_with_current_context(
466
+ _SEARCH_FANOUT_EXECUTOR,
467
+ unified_hybrid_search,
468
+ query=query,
469
+ query_embedding=embedding,
470
+ top_k=top_k,
471
+ threshold=threshold,
472
+ user_id=request.user_id,
473
+ agent_version=request.agent_version,
474
+ playbook_name=request.playbook_name,
475
+ agent_playbook_statuses=statuses,
476
+ search_mode=request.search_mode,
477
+ include_profiles="profiles" in entity_types and bool(request.user_id),
478
+ include_agent_playbooks="agent_playbooks" in entity_types,
479
+ include_user_playbooks="user_playbooks" in entity_types,
480
+ )
481
+ try:
482
+ profiles, agent_playbooks, user_playbooks = future.result(timeout=30)
483
+ except FuturesTimeoutError:
484
+ raise
485
+ except Exception:
486
+ logger.warning(
487
+ "Unified single-RPC search failed; falling back to per-arm fan-out",
488
+ exc_info=True,
489
+ )
490
+ return None
491
+
492
+ # Mirror _search_agent_playbooks_via_storage: dedupe by id, cap at top_k.
493
+ deduped: list[AgentPlaybook] = []
494
+ seen_ids: set[str] = set()
495
+ for playbook in agent_playbooks:
496
+ playbook_id = str(getattr(playbook, "agent_playbook_id", ""))
497
+ if playbook_id and playbook_id not in seen_ids:
498
+ seen_ids.add(playbook_id)
499
+ deduped.append(playbook)
500
+ if len(deduped) >= top_k:
501
+ break
502
+ return profiles, deduped, user_playbooks
503
+
504
+
350
505
  def _apply_floors(
351
506
  query: str,
352
507
  profiles: list[UserProfile],
@@ -355,36 +510,78 @@ def _apply_floors(
355
510
  top_k: int,
356
511
  cfg: RetrievalFloorConfig,
357
512
  ) -> tuple[list[UserProfile], list[AgentPlaybook], list[UserPlaybook]]:
358
- """Apply the per-arm relevance floor to each entity arm in parallel."""
359
- with ThreadPoolExecutor(max_workers=3) as ex:
360
- f_profiles = _submit_with_current_context(
361
- ex,
362
- apply_relevance_floor,
363
- query,
364
- profiles,
365
- cfg.profile_floor,
366
- top_k,
367
- arm="profiles",
368
- )
369
- f_agent = _submit_with_current_context(
370
- ex,
371
- apply_relevance_floor,
372
- query,
373
- agent_playbooks,
374
- cfg.agent_playbook_floor,
375
- top_k,
376
- arm="agent_playbooks",
377
- )
378
- f_user = _submit_with_current_context(
379
- ex,
380
- apply_relevance_floor,
381
- query,
382
- user_playbooks,
383
- cfg.user_playbook_floor,
384
- top_k,
385
- arm="user_playbooks",
513
+ """Apply the per-arm relevance floor with one batched cross-encoder call."""
514
+ floored_profiles, floored_agent, floored_user = apply_relevance_floors(
515
+ query,
516
+ [
517
+ ("profiles", profiles, cfg.profile_floor),
518
+ ("agent_playbooks", agent_playbooks, cfg.agent_playbook_floor),
519
+ ("user_playbooks", user_playbooks, cfg.user_playbook_floor),
520
+ ],
521
+ top_k,
522
+ )
523
+ return floored_profiles, floored_agent, floored_user
524
+
525
+
526
+ def _suppress_source_user_playbooks(
527
+ *,
528
+ storage: BaseStorage,
529
+ agent_playbooks: list[AgentPlaybook],
530
+ user_playbooks: list[UserPlaybook],
531
+ ) -> list[UserPlaybook]:
532
+ """Drop user playbooks already represented by returned agent playbooks."""
533
+ if not agent_playbooks or not user_playbooks:
534
+ return user_playbooks
535
+
536
+ source_user_playbook_ids: set[int] = set()
537
+ agent_ids_needing_lookup: list[int] = []
538
+ for playbook in agent_playbooks:
539
+ source_ids = getattr(playbook, _SOURCE_USER_PLAYBOOK_IDS_KEY, None)
540
+ if source_ids is None:
541
+ agent_playbook_id = int(getattr(playbook, "agent_playbook_id", 0) or 0)
542
+ if agent_playbook_id:
543
+ agent_ids_needing_lookup.append(agent_playbook_id)
544
+ continue
545
+ source_user_playbook_ids.update(int(source_id) for source_id in source_ids)
546
+
547
+ if agent_ids_needing_lookup:
548
+ lookup = getattr(
549
+ storage, "get_source_user_playbook_ids_for_agent_playbooks", None
386
550
  )
387
- return f_profiles.result(), f_agent.result(), f_user.result()
551
+ if callable(lookup):
552
+ try:
553
+ source_ids_by_agent = cast(
554
+ dict[int, list[int]], lookup(agent_ids_needing_lookup)
555
+ )
556
+ except Exception:
557
+ logger.warning(
558
+ "Failed to resolve source user playbooks for unified search suppression",
559
+ exc_info=True,
560
+ )
561
+ else:
562
+ for source_ids in source_ids_by_agent.values():
563
+ source_user_playbook_ids.update(
564
+ int(source_id) for source_id in source_ids
565
+ )
566
+
567
+ if not source_user_playbook_ids:
568
+ return user_playbooks
569
+
570
+ filtered = [
571
+ playbook
572
+ for playbook in user_playbooks
573
+ if int(getattr(playbook, "user_playbook_id", 0) or 0)
574
+ not in source_user_playbook_ids
575
+ ]
576
+ suppressed_count = len(user_playbooks) - len(filtered)
577
+ if suppressed_count:
578
+ with profile_step(
579
+ "search.suppress_source_user_playbooks",
580
+ suppressed_count=suppressed_count,
581
+ source_user_playbook_count=len(source_user_playbook_ids),
582
+ ):
583
+ pass
584
+ return filtered
388
585
 
389
586
 
390
587
  def _get_cached_query_embedding(
@@ -11,7 +11,7 @@ Description: Global configuration manager for site-wide variables and per-org fe
11
11
 
12
12
  ## Purpose
13
13
 
14
- 1. **Global settings** - Model names, embedding models
14
+ 1. **Global settings** - Model names, embedding models, and retrieval defaults
15
15
  2. **Feature flags** - Per-org feature gating (global enable or per-org allowlist)
16
16
  3. **Dual storage** - File-based with optional Redis caching
17
17
  4. **Auto-fallback** - Redis → File system graceful degradation
@@ -62,6 +62,7 @@ site_var/
62
62
  └── site_var_sources/
63
63
  ├── app_config.json # JSON → parsed dict
64
64
  ├── model_config.json
65
+ ├── search_settings.json # Default retrieval mode and hybrid search weights
65
66
  └── feature_flags.json # Feature flag config (per-flag enable + org allowlist)
66
67
  ```
67
68
 
@@ -55,7 +55,7 @@ def is_feature_enabled(org_id: str, feature_name: str) -> bool:
55
55
  if feature_config.get("enabled", False):
56
56
  return True
57
57
 
58
- enabled_org_ids = feature_config.get("enabled_org_ids", [])
58
+ enabled_org_ids = feature_config.get("enabled_org_ids", []) or []
59
59
  return org_id in enabled_org_ids
60
60
 
61
61
 
@@ -103,6 +103,103 @@ def is_deduplicator_enabled(org_id: str) -> bool:
103
103
  return is_feature_enabled(org_id, "deduplicator")
104
104
 
105
105
 
106
+ def _is_fail_closed_flag_enabled(org_id: str, feature_key: str) -> bool:
107
+ """
108
+ Shared fail-closed helper: returns False if key absent or config malformed.
109
+
110
+ Unlike is_feature_enabled (fail-open), this function returns False when the
111
+ feature key is absent or when its value is not a dict. This is the safety
112
+ invariant for soft-delete flags — unconfigured or malformed entries must
113
+ never accidentally activate tombstone-producing paths.
114
+
115
+ A feature is enabled if:
116
+ - The feature's "enabled" field is True (globally enabled), OR
117
+ - The org_id is in the feature's "enabled_org_ids" list.
118
+
119
+ Args:
120
+ org_id (str): The organization ID to check
121
+ feature_key (str): The feature flag key in the config dict
122
+
123
+ Returns:
124
+ bool: True only if the feature is explicitly enabled for this org
125
+ """
126
+ config = _get_feature_flags_config()
127
+ feature_config = config.get(feature_key)
128
+
129
+ if feature_config is None:
130
+ # Key absent — fail-CLOSED
131
+ return False
132
+
133
+ if not isinstance(feature_config, dict):
134
+ logger.warning(
135
+ "feature_flags[%s] is not a dict (got %s), defaulting to OFF",
136
+ feature_key,
137
+ type(feature_config).__name__,
138
+ )
139
+ return False
140
+
141
+ # Strict bool identity — truthy strings like "false" must not enable (#195).
142
+ enabled = feature_config.get("enabled", False)
143
+ if enabled is True:
144
+ return True
145
+
146
+ # Reject non-list values — a string does substring `in` match, not membership (#195).
147
+ org_ids = feature_config.get("enabled_org_ids", [])
148
+ if not isinstance(org_ids, list):
149
+ return False
150
+ return org_id in org_ids
151
+
152
+
153
+ def _is_default_open_flag_enabled(org_id: str, feature_key: str) -> bool:
154
+ """
155
+ Shared default-open helper for soft-delete flags.
156
+
157
+ Returns True when the feature key is absent from config (default ON), but
158
+ preserves all explicit-disable and per-org override semantics:
159
+ - Key absent or None → True (default ON — GC is also ON by default).
160
+ - Key present but malformed (not a dict) → False with a warning (safe fallback).
161
+ - Key present, enabled=True → True for all orgs.
162
+ - Key present, enabled=False, org in enabled_org_ids → True.
163
+ - Key present, enabled=False, org NOT in enabled_org_ids → False (explicit disable).
164
+
165
+ Strict-bool and strict-list guards from _is_fail_closed_flag_enabled are
166
+ preserved: truthy strings and non-bool ints do NOT enable, and a string
167
+ enabled_org_ids does NOT match via substring (anti-#195).
168
+
169
+ Args:
170
+ org_id (str): The organization ID to check
171
+ feature_key (str): The feature flag key in the config dict
172
+
173
+ Returns:
174
+ bool: True when the flag is on (including when absent/unconfigured)
175
+ """
176
+ config = _get_feature_flags_config()
177
+ feature_config = config.get(feature_key)
178
+
179
+ if feature_config is None:
180
+ # Key absent — default OPEN (soft-delete is on by default; GC runs too).
181
+ return True
182
+
183
+ if not isinstance(feature_config, dict):
184
+ logger.warning(
185
+ "feature_flags[%s] is not a dict (got %s), defaulting to OFF",
186
+ feature_key,
187
+ type(feature_config).__name__,
188
+ )
189
+ return False
190
+
191
+ # Strict bool identity — truthy strings like "false" must not enable (#195).
192
+ enabled = feature_config.get("enabled", False)
193
+ if enabled is True:
194
+ return True
195
+
196
+ # Reject non-list values — a string does substring `in` match, not membership (#195).
197
+ org_ids = feature_config.get("enabled_org_ids", [])
198
+ if not isinstance(org_ids, list):
199
+ return False
200
+ return org_id in org_ids
201
+
202
+
106
203
  def is_resumable_extraction_agent_enabled(org_id: str) -> bool:
107
204
  """
108
205
  Convenience check for whether classic extraction should use the resumable agent.
@@ -114,3 +211,25 @@ def is_resumable_extraction_agent_enabled(org_id: str) -> bool:
114
211
  bool: True if the resumable extraction agent is enabled
115
212
  """
116
213
  return is_feature_enabled(org_id, "resumable_extraction_agent")
214
+
215
+
216
+ def is_lineage_dual_read_diff_enabled(org_id: str) -> bool:
217
+ """
218
+ Check if the lineage dual-read diff path is enabled for a given organization.
219
+
220
+ Defaults to DISABLED when the key is absent from config (default-CLOSED). This
221
+ flag gates an experimental read path that compares new and old lineage query
222
+ results side-by-side; it must never activate accidentally on unconfigured
223
+ deployments.
224
+
225
+ A feature is enabled only if:
226
+ - The feature's "enabled" field is True (globally enabled), OR
227
+ - The org_id is in the feature's "enabled_org_ids" list.
228
+
229
+ Args:
230
+ org_id (str): The organization ID to check
231
+
232
+ Returns:
233
+ bool: True only if the feature is explicitly enabled for this org
234
+ """
235
+ return _is_fail_closed_flag_enabled(org_id, "lineage_dual_read_diff")
@@ -9,5 +9,9 @@
9
9
  "resumable_extraction_agent": {
10
10
  "enabled": true,
11
11
  "enabled_org_ids": []
12
+ },
13
+ "lineage_dual_read_diff": {
14
+ "enabled": true,
15
+ "enabled_org_ids": []
12
16
  }
13
17
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "search_mode": "hybrid",
3
+ "vector_weight": 1.0,
4
+ "fts_weight": 1.0
5
+ }
@@ -156,3 +156,33 @@ def sentry_tags(**tags: Any) -> Iterator[None]:
156
156
  scope_cm.__exit__(None, None, None)
157
157
  except Exception as exc: # noqa: BLE001
158
158
  logger.warning("Failed to close Sentry scope: %s", exc)
159
+
160
+
161
+ def capture_anomaly(message: str, *, level: str = "warning", **tags: Any) -> None:
162
+ """Report a non-fatal anomaly to Sentry, if the SDK is installed.
163
+
164
+ For silent-but-noteworthy conditions that return rather than raise — e.g.
165
+ lineage resolution hitting a cycle or the hop cap, or a best-effort
166
+ provenance append failing. No-op when ``sentry-sdk`` is absent (the OS
167
+ package does not depend on it; enterprise deployments pull it in). Never
168
+ raises — instrumentation must not turn a degraded-but-working path into a
169
+ failure.
170
+
171
+ Args:
172
+ message: short, low-cardinality description of the anomaly (the Sentry
173
+ issue title), e.g. ``"lineage.resolve_current.cycle"``.
174
+ level: Sentry severity ("warning", "error", ...). Defaults to "warning".
175
+ **tags: contextual tag name → value pairs (None values are skipped).
176
+ """
177
+ try:
178
+ import sentry_sdk # type: ignore[import-not-found]
179
+ except ImportError:
180
+ return
181
+ try:
182
+ with sentry_sdk.new_scope() as scope:
183
+ for key, value in tags.items():
184
+ if value is not None:
185
+ scope.set_tag(key, str(value))
186
+ sentry_sdk.capture_message(message, level=level)
187
+ except Exception as exc: # noqa: BLE001
188
+ logger.warning("Failed to capture Sentry anomaly %r: %s", message, exc)
@@ -34,6 +34,12 @@ class UsageEvent:
34
34
  backend: str | None = None
35
35
  outcome: str | None = None
36
36
  count_value: int = 1
37
+ prompt_tokens: int | None = None
38
+ completion_tokens: int | None = None
39
+ billing_input_tokens: int | None = None
40
+ platform_llm: bool | None = None
41
+ platform_storage: bool | None = None
42
+ caller_type: str | None = None
37
43
  duration_ms: int | None = None
38
44
  error_kind: str | None = None
39
45
  metadata: Mapping[str, Any] = field(default_factory=dict)
@@ -73,6 +79,12 @@ def record_usage_event(
73
79
  backend: str | None = None,
74
80
  outcome: str | None = None,
75
81
  count_value: int = 1,
82
+ prompt_tokens: int | None = None,
83
+ completion_tokens: int | None = None,
84
+ billing_input_tokens: int | None = None,
85
+ platform_llm: bool | None = None,
86
+ platform_storage: bool | None = None,
87
+ caller_type: str | None = None,
76
88
  duration_ms: int | None = None,
77
89
  error_kind: str | None = None,
78
90
  metadata: Mapping[str, Any] | None = None,
@@ -104,6 +116,12 @@ def record_usage_event(
104
116
  backend=backend,
105
117
  outcome=outcome,
106
118
  count_value=count_value,
119
+ prompt_tokens=prompt_tokens,
120
+ completion_tokens=completion_tokens,
121
+ billing_input_tokens=billing_input_tokens,
122
+ platform_llm=platform_llm,
123
+ platform_storage=platform_storage,
124
+ caller_type=caller_type,
107
125
  duration_ms=duration_ms,
108
126
  error_kind=error_kind,
109
127
  metadata=metadata or {},