claude-smart 0.2.45 → 0.2.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +47 -10
  3. package/bin/claude-smart.js +535 -23
  4. package/package.json +22 -3
  5. package/plugin/.claude-plugin/plugin.json +4 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +27 -6
  8. package/plugin/commands/clear-all.md +1 -0
  9. package/plugin/commands/dashboard.md +1 -0
  10. package/plugin/commands/learn.md +1 -0
  11. package/plugin/commands/restart.md +1 -0
  12. package/plugin/commands/show.md +1 -0
  13. package/plugin/dashboard/app/configure/env/page.tsx +40 -14
  14. package/plugin/dashboard/app/configure/server/page.tsx +51 -1
  15. package/plugin/dashboard/app/layout.tsx +20 -0
  16. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  17. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  18. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  19. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  20. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  21. package/plugin/dashboard/lib/status.ts +10 -3
  22. package/plugin/dashboard/lib/types.ts +18 -6
  23. package/plugin/hooks/codex-hooks.json +0 -1
  24. package/plugin/opencode/assistant-buffer.ts +108 -0
  25. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  26. package/plugin/opencode/dist/internal.js +12 -0
  27. package/plugin/opencode/dist/payload.js +85 -0
  28. package/plugin/opencode/dist/server.mjs +232 -0
  29. package/plugin/opencode/internal.ts +18 -0
  30. package/plugin/opencode/package.json +3 -0
  31. package/plugin/opencode/payload.ts +90 -0
  32. package/plugin/opencode/server.mts +251 -0
  33. package/plugin/opencode/tsconfig.json +13 -0
  34. package/plugin/pyproject.toml +25 -4
  35. package/plugin/scripts/_lib.sh +5 -1
  36. package/plugin/scripts/backend-service.sh +22 -2
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +1 -1
  39. package/plugin/scripts/opencode-claude-compat +4 -0
  40. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  41. package/plugin/scripts/opencode-claude-compat.js +225 -0
  42. package/plugin/scripts/smart-install.sh +17 -9
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +498 -12
  45. package/plugin/src/claude_smart/context_format.py +9 -9
  46. package/plugin/src/claude_smart/cs_cite.py +66 -19
  47. package/plugin/src/claude_smart/env_config.py +12 -4
  48. package/plugin/src/claude_smart/events/session_start.py +26 -7
  49. package/plugin/src/claude_smart/events/stop.py +1 -1
  50. package/plugin/src/claude_smart/ids.py +1 -1
  51. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  52. package/plugin/src/claude_smart/runtime.py +7 -1
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +54 -30
  55. package/plugin/vendor/reflexio/README.md +17 -11
  56. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  57. package/plugin/vendor/reflexio/reflexio/README.md +4 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  59. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  60. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  64. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +212 -52
  65. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  66. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  67. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  68. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  69. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  70. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  71. package/plugin/vendor/reflexio/reflexio/client/client.py +154 -13
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +246 -10
  146. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  147. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  148. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
  149. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  150. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
  151. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  152. package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
  153. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  158. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  159. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  160. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  161. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +97 -3
  162. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  163. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  164. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +104 -7
  165. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  166. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  167. package/plugin/vendor/reflexio/reflexio/server/README.md +58 -33
  168. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  169. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  170. package/plugin/vendor/reflexio/reflexio/server/api.py +899 -103
  171. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  172. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
  173. package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
  174. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  175. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  176. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  177. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  178. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  179. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  180. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  181. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  182. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  183. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  185. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  186. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  187. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  188. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  189. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  198. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +278 -0
  199. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  200. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  201. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  202. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  203. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  204. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  205. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  206. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  207. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  208. package/plugin/vendor/reflexio/reflexio/server/services/README.md +28 -11
  209. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  212. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  215. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  216. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  217. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  218. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  219. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  220. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  221. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  222. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  225. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  226. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  227. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  229. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  230. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  231. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +47 -16
  232. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  233. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  234. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -0
  238. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +18 -11
  241. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  242. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  243. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +334 -178
  244. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  245. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  246. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  248. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  249. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  250. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  252. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  255. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +31 -22
  256. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  257. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  258. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  259. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  261. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  262. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  263. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -6
  264. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  265. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  266. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +31 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +16 -2
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +644 -197
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  277. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +31 -1369
  278. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
  279. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
  280. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  281. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  282. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  283. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
  289. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  290. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +40 -57
  292. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +4 -677
  296. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
  297. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +372 -34
  311. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  312. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  313. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  314. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  315. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  316. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  317. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  318. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  319. package/scripts/setup-claude-smart.sh +8 -3
  320. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  321. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  322. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  323. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  324. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  325. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  326. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  327. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  328. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  329. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  330. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  331. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  332. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  333. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  334. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  335. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  336. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  337. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  338. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  339. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  340. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  341. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  342. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  343. package/plugin/vendor/reflexio/reflexio/server/_auth.py +0 -25
  344. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  345. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  346. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  347. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  348. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  349. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  350. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  351. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  352. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  353. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -20,6 +20,13 @@ def validate_publish_user_interaction_request(
20
20
  if not request.interaction_data_list:
21
21
  return False, "No interaction data provided"
22
22
 
23
+ # Defense-in-depth: session_id is a required NonEmptyStr on
24
+ # PublishUserInteractionRequest, so the normal validated API path already
25
+ # rejects empty/missing values with a 422. This guard additionally covers
26
+ # paths that bypass Pydantic validation (e.g. ``model_construct``).
27
+ if not request.session_id or not request.session_id.strip():
28
+ return False, "session_id is required and cannot be empty"
29
+
23
30
  for interaction_data in request.interaction_data_list:
24
31
  if (
25
32
  interaction_data.user_action != UserActionType.NONE
@@ -1,6 +1,6 @@
1
1
  from fastapi import Depends
2
2
 
3
- from reflexio.server._auth import default_get_org_id
3
+ from reflexio.server.auth import default_get_org_id
4
4
  from reflexio.server.prompt.prompt_manager import PromptManager
5
5
  from reflexio.server.services.configurator.base_configurator import BaseConfigurator
6
6
  from reflexio.server.services.configurator.configurator import get_configurator_class
@@ -17,7 +17,9 @@ class RequestContext:
17
17
  self.storage_base_dir = storage_base_dir
18
18
  cls = get_configurator_class()
19
19
  self.configurator = configurator or cls(org_id, base_dir=storage_base_dir)
20
- self.prompt_manager = PromptManager()
20
+ self.prompt_manager = PromptManager(
21
+ extra_prompt_bank_paths=self.configurator.get_prompt_bank_paths()
22
+ )
21
23
  self.storage = self.configurator.create_storage(
22
24
  storage_config=self.configurator.get_current_storage_configuration(),
23
25
  )
@@ -0,0 +1,90 @@
1
+ """Auth dependency primitives shared between :mod:`reflexio.server.api` and
2
+ endpoint helpers.
3
+
4
+ Lives in its own module to avoid an import cycle: api endpoints reference
5
+ :func:`default_get_org_id` (e.g. via FastAPI ``Depends``) and are themselves
6
+ imported by :mod:`reflexio.server.api`. Putting the dependency here keeps the
7
+ import graph acyclic.
8
+
9
+ Public module path: :mod:`reflexio.server.auth`.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Callable
15
+ from functools import cache
16
+
17
+ DEFAULT_ORG_ID = "self-host-org"
18
+
19
+
20
+ def default_get_org_id() -> str:
21
+ """Return the default organization ID for local hosting.
22
+
23
+ Reads ``REFLEXIO_DEFAULT_ORG_ID`` (unset/blank falls back to
24
+ :data:`DEFAULT_ORG_ID`) so a no-auth deployment can run under a distinct
25
+ org id without colliding with another local backend that shares
26
+ ``~/.reflexio/configs/``. claude-smart and the self-host backend otherwise
27
+ both default to ``self-host-org`` and fight over the same
28
+ ``config_self-host-org.json``.
29
+
30
+ Enterprise deployments never reach this: they override the dependency via
31
+ ``app.dependency_overrides[default_get_org_id] = <bearer_auth_resolver>``
32
+ inside :func:`reflexio.server.api.create_app`.
33
+
34
+ Returns:
35
+ str: The default org identifier used for self-hosted deployments.
36
+ """
37
+ # Lazy import keeps this deliberately cycle-free module independent of the
38
+ # ``reflexio.server`` package init (see module docstring).
39
+ from reflexio.server.env_utils import env_str
40
+
41
+ return env_str("REFLEXIO_DEFAULT_ORG_ID", DEFAULT_ORG_ID)
42
+
43
+
44
+ def default_get_caller_type() -> str:
45
+ """Return the default caller type for local / self-hosted deployments.
46
+
47
+ Every call is treated as ``"internal"`` (no billing discrimination) in the
48
+ OSS build. Enterprise deployments override this via
49
+ ``app.dependency_overrides[default_get_caller_type] = <classifier>``
50
+ inside :func:`reflexio.server.api.create_app`, exactly like
51
+ :func:`default_get_org_id`.
52
+
53
+ Returns:
54
+ str: The literal ``"internal"`` — equals ``BillingCallerType.INTERNAL.value``
55
+ (kept as a plain string here so OSS stays free of reflexio_ext imports).
56
+ """
57
+ return "internal" # == BillingCallerType.INTERNAL.value; literal keeps OSS clean
58
+
59
+
60
+ @cache
61
+ def default_billing_gate(line: str) -> Callable[..., None]: # noqa: ARG001
62
+ """Return a stable no-op FastAPI dependency for the billing gate.
63
+
64
+ OSS / self-hosted deployments run without enforcement — this factory
65
+ always returns a dependency that does nothing. Enterprise deployments
66
+ supply a real gate factory via the ``get_billing_gate`` parameter of
67
+ :func:`reflexio.server.api.create_app`, exactly mirroring how
68
+ ``get_caller_type`` overrides :func:`default_get_caller_type`.
69
+
70
+ ``lru_cache`` ensures the **same callable object** is returned for the
71
+ same ``line`` value. FastAPI uses callable identity for
72
+ ``dependency_overrides``, so caching is essential: without it, each
73
+ ``Depends(default_billing_gate("application"))`` would create a fresh
74
+ closure and overrides would silently never fire.
75
+
76
+ OSS code must never import from ``reflexio_ext`` — the gate wiring is
77
+ purely additive and backwards-compatible.
78
+
79
+ Args:
80
+ line (str): Billing line (e.g. ``"application"``, ``"learnings_generated"``).
81
+ Accepted but unused in the default no-op implementation.
82
+
83
+ Returns:
84
+ Callable[..., None]: A FastAPI dependency that always passes through.
85
+ """
86
+
87
+ def _noop() -> None:
88
+ """No-op billing gate for OSS / self-hosted deployments."""
89
+
90
+ return _noop
@@ -0,0 +1,173 @@
1
+ """OSS emission helpers: translate billing signals into usage_events.
2
+
3
+ The single source of truth for each billing event's name/category/fields. Plain
4
+ primitive signatures (no reflexio_ext types) so OSS call sites — the generation
5
+ service and search endpoints — can use them directly. Each is a thin, non-blocking
6
+ wrapper over the ``record_usage_event`` hook (which only enqueues). No DB I/O.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from reflexio.server.usage_metrics import record_usage_event
12
+
13
+ _INTERNAL = (
14
+ "internal" # == BillingCallerType.INTERNAL.value (kept literal; OSS stays clean)
15
+ )
16
+
17
+
18
+ def record_extraction_tokens(
19
+ *,
20
+ org_id: str,
21
+ billing_input_tokens: int,
22
+ prompt_tokens: int,
23
+ completion_tokens: int,
24
+ platform_llm: bool | None,
25
+ platform_storage: bool | None,
26
+ pipeline: str | None = None,
27
+ request_id: str | None = None,
28
+ session_id: str | None = None,
29
+ ) -> None:
30
+ """Emit the Learning cost facet — call only when extraction fired.
31
+
32
+ No-op when ``billing_input_tokens <= 0``.
33
+
34
+ Args:
35
+ org_id: Organisation identifier.
36
+ billing_input_tokens: Input-anchored token count (the metered basis).
37
+ prompt_tokens: Real provider prompt tokens (COGS; not billed to customer).
38
+ completion_tokens: Real provider completion tokens (COGS; not billed).
39
+ platform_llm: True iff the platform supplies the LLM for this org.
40
+ platform_storage: True iff the platform supplies storage; None defers to rollup.
41
+ pipeline: Optional pipeline tag (e.g. ``"profile"``).
42
+ request_id: Optional request correlation ID.
43
+ session_id: Optional session ID.
44
+ """
45
+ if billing_input_tokens <= 0:
46
+ return
47
+ record_usage_event(
48
+ org_id=org_id,
49
+ event_name="extraction_tokens",
50
+ event_category="learning",
51
+ pipeline=pipeline,
52
+ request_id=request_id,
53
+ session_id=session_id,
54
+ count_value=billing_input_tokens,
55
+ prompt_tokens=prompt_tokens,
56
+ completion_tokens=completion_tokens,
57
+ billing_input_tokens=billing_input_tokens,
58
+ platform_llm=platform_llm,
59
+ platform_storage=platform_storage,
60
+ caller_type=_INTERNAL,
61
+ )
62
+
63
+
64
+ def record_learnings_generated(
65
+ *,
66
+ org_id: str,
67
+ count: int,
68
+ platform_llm: bool | None,
69
+ platform_storage: bool | None,
70
+ pipeline: str | None = None,
71
+ request_id: str | None = None,
72
+ session_id: str | None = None,
73
+ ) -> None:
74
+ """Emit the Learning value facet — number of profiles/playbooks generated.
75
+
76
+ No-op when ``count <= 0``.
77
+
78
+ Args:
79
+ org_id: Organisation identifier.
80
+ count: Number of learnings generated in this run.
81
+ platform_llm: True iff the platform supplies the LLM for this org.
82
+ platform_storage: True iff the platform supplies storage; None defers to rollup.
83
+ pipeline: Optional pipeline tag (e.g. ``"playbook"``).
84
+ request_id: Optional request correlation ID.
85
+ session_id: Optional session ID.
86
+ """
87
+ if count <= 0:
88
+ return
89
+ record_usage_event(
90
+ org_id=org_id,
91
+ event_name="learnings_generated",
92
+ event_category="learning",
93
+ pipeline=pipeline,
94
+ request_id=request_id,
95
+ session_id=session_id,
96
+ count_value=count,
97
+ platform_llm=platform_llm,
98
+ platform_storage=platform_storage,
99
+ caller_type=_INTERNAL,
100
+ )
101
+
102
+
103
+ def record_applied_learnings(
104
+ *,
105
+ org_id: str,
106
+ surfaced_count: int,
107
+ caller_type: str,
108
+ platform_llm: bool | None,
109
+ platform_storage: bool | None,
110
+ pipeline: str | None = None,
111
+ request_id: str | None = None,
112
+ session_id: str | None = None,
113
+ ) -> None:
114
+ """Emit the Application line — surfaced top-K learnings.
115
+
116
+ No-op unless ``caller_type == "production_agent"`` AND ``surfaced_count > 0``.
117
+
118
+ Args:
119
+ org_id: Organisation identifier.
120
+ surfaced_count: Number of learnings surfaced in the search response.
121
+ caller_type: Caller classification string (e.g. ``"production_agent"``).
122
+ platform_llm: True iff the platform supplies the LLM for this org.
123
+ platform_storage: True iff the platform supplies storage; None defers to rollup.
124
+ pipeline: Optional pipeline tag.
125
+ request_id: Optional request correlation ID.
126
+ session_id: Optional session ID.
127
+ """
128
+ if caller_type != "production_agent" or surfaced_count <= 0:
129
+ return
130
+ record_usage_event(
131
+ org_id=org_id,
132
+ event_name="learning_applied",
133
+ event_category="application",
134
+ pipeline=pipeline,
135
+ request_id=request_id,
136
+ session_id=session_id,
137
+ count_value=surfaced_count,
138
+ platform_llm=platform_llm,
139
+ platform_storage=platform_storage,
140
+ caller_type=caller_type,
141
+ )
142
+
143
+
144
+ def record_search_request(
145
+ *,
146
+ org_id: str,
147
+ caller_type: str,
148
+ request_id: str | None = None,
149
+ session_id: str | None = None,
150
+ ) -> None:
151
+ """Emit one analytics-only search request count for production agents.
152
+
153
+ No-op unless ``caller_type == "production_agent"``. Unlike
154
+ :func:`record_applied_learnings`, empty search responses still count because
155
+ this measures requests made, not learnings surfaced.
156
+
157
+ Args:
158
+ org_id: Organisation identifier.
159
+ caller_type: Caller classification string.
160
+ request_id: Optional request correlation ID.
161
+ session_id: Optional session ID.
162
+ """
163
+ if caller_type != "production_agent":
164
+ return
165
+ record_usage_event(
166
+ org_id=org_id,
167
+ event_name="search_request",
168
+ event_category="application",
169
+ request_id=request_id,
170
+ session_id=session_id,
171
+ count_value=1,
172
+ caller_type=caller_type,
173
+ )
@@ -0,0 +1,64 @@
1
+ """OSS-pure billing signal helpers (no reflexio_ext imports).
2
+
3
+ Single source of truth for whether the platform supplies the LLM, so the OSS
4
+ generation service and the enterprise attribution resolver never diverge.
5
+ Also provides the one canonical input-anchored tokenizer for the whole platform.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Mapping
11
+ from typing import Any
12
+
13
+ import tiktoken
14
+
15
+ # One fixed canonical encoding for the whole platform. Changing this is a
16
+ # price-book-affecting decision; do not make it model-dependent.
17
+ _CANONICAL_ENCODING_NAME = "cl100k_base"
18
+ _encoding = tiktoken.get_encoding(_CANONICAL_ENCODING_NAME)
19
+
20
+
21
+ def count_input_tokens(text: str) -> int:
22
+ """Count billable input tokens for ``text`` under the canonical cl100k_base encoding.
23
+
24
+ This is the single canonical tokenizer for the whole platform. Enterprise
25
+ code delegates here so OSS and enterprise always use the same encoding.
26
+
27
+ Args:
28
+ text (str): The input text to tokenize.
29
+
30
+ Returns:
31
+ int: Number of tokens under cl100k_base, or 0 for empty input.
32
+ """
33
+ if not text:
34
+ return 0
35
+ return len(_encoding.encode(text, disallowed_special=()))
36
+
37
+
38
+ def platform_llm_from_config(config: Any) -> bool:
39
+ """Return True iff Reflexio (not the customer) supplies the LLM for ``config``.
40
+
41
+ A populated ``api_key_config`` provider sub-config means BYO-LLM → False.
42
+ A missing/empty ``api_key_config`` means platform-supplied → True.
43
+
44
+ Args:
45
+ config: The org's resolved ``Config`` object, or None.
46
+
47
+ Returns:
48
+ bool: True when the platform supplies the LLM; False when the customer
49
+ has configured a BYO provider key.
50
+ """
51
+ api_key_config = getattr(config, "api_key_config", None)
52
+ if api_key_config is None:
53
+ return True
54
+ data = (
55
+ api_key_config.model_dump(exclude_none=True)
56
+ if hasattr(api_key_config, "model_dump")
57
+ else {}
58
+ )
59
+ # Guard against a model_dump that returns a non-mapping: without this,
60
+ # `.values()` would raise. Default to platform-supplied (True) on a shape
61
+ # we don't understand rather than misclassifying the org as BYO-LLM.
62
+ if not isinstance(data, Mapping):
63
+ return True
64
+ return not any(bool(v) for v in data.values())
@@ -8,6 +8,7 @@ from typing import Any, Final
8
8
  from cachetools import TTLCache
9
9
 
10
10
  from reflexio.lib.reflexio_lib import Reflexio
11
+ from reflexio.server.tracing import profile_step
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
@@ -112,8 +113,14 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
112
113
 
113
114
  # Cache lookup — held briefly to extract the entry, then released
114
115
  # before doing any I/O (file stat, DB query) for the version probe.
115
- with _reflexio_cache_lock:
116
- entry: _CacheEntry | None = _reflexio_cache.get(cache_key)
116
+ with profile_step("reflexio.cache.lookup") as span:
117
+ with _reflexio_cache_lock:
118
+ entry: _CacheEntry | None = _reflexio_cache.get(cache_key)
119
+ span.set_data("cache_hit", entry is not None)
120
+ span.set_data(
121
+ "has_cached_version",
122
+ entry is not None and entry.cached_version is not None,
123
+ )
117
124
 
118
125
  if entry is not None:
119
126
  cached_version = entry.cached_version
@@ -122,7 +129,13 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
122
129
  # rely on TTL + explicit invalidation instead.
123
130
  if cached_version is None:
124
131
  return entry.reflexio
125
- current_version = _probe_version_safe(entry.reflexio)
132
+ with profile_step("reflexio.cache.version_probe", cache_state="hit") as span:
133
+ current_version = _probe_version_safe(entry.reflexio)
134
+ span.set_data("probe_failed", current_version is _PROBE_FAILED)
135
+ span.set_data(
136
+ "probe_supported",
137
+ current_version is not None and current_version is not _PROBE_FAILED,
138
+ )
126
139
  # Transient probe failure: keep the cached instance for this
127
140
  # request but DON'T mutate the stamp — the next request will
128
141
  # try again, so a brief outage doesn't permanently disable
@@ -134,15 +147,27 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
134
147
  # Stale entry. Evict only if the cached version still matches
135
148
  # the one we just compared against — another thread may have
136
149
  # already replaced the entry while we were probing.
137
- with _reflexio_cache_lock:
138
- existing = _reflexio_cache.get(cache_key)
139
- if existing is not None and existing.cached_version == cached_version:
140
- del _reflexio_cache[cache_key]
150
+ with profile_step("reflexio.cache.evict_stale") as span:
151
+ with _reflexio_cache_lock:
152
+ existing = _reflexio_cache.get(cache_key)
153
+ evicted = (
154
+ existing is not None and existing.cached_version == cached_version
155
+ )
156
+ if evicted:
157
+ del _reflexio_cache[cache_key]
158
+ span.set_data("evicted", evicted)
141
159
 
142
160
  # Cache miss (or just-evicted stale entry) - create a new instance
143
161
  # outside the lock to avoid blocking concurrent requests for other orgs.
144
- reflexio = Reflexio(org_id=org_id, storage_base_dir=storage_base_dir)
145
- new_version = _probe_version_safe(reflexio)
162
+ with profile_step("reflexio.cache.construct"):
163
+ reflexio = Reflexio(org_id=org_id, storage_base_dir=storage_base_dir)
164
+ with profile_step("reflexio.cache.version_probe", cache_state="miss") as span:
165
+ new_version = _probe_version_safe(reflexio)
166
+ span.set_data("probe_failed", new_version is _PROBE_FAILED)
167
+ span.set_data(
168
+ "probe_supported",
169
+ new_version is not None and new_version is not _PROBE_FAILED,
170
+ )
146
171
 
147
172
  # Construction-time probe failure: serve this request from the
148
173
  # newly-built instance but DON'T cache it. Caching with
@@ -159,13 +184,18 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
159
184
  cached_version=new_version, # type: ignore[arg-type]
160
185
  )
161
186
 
162
- with _reflexio_cache_lock:
163
- # Double-check in case another thread populated while we were constructing.
164
- existing = _reflexio_cache.get(cache_key)
165
- if existing is None:
166
- _reflexio_cache[cache_key] = new_entry
167
- return reflexio
168
- return existing.reflexio
187
+ with profile_step("reflexio.cache.store") as span:
188
+ with _reflexio_cache_lock:
189
+ # Double-check in case another thread populated while we were constructing.
190
+ existing = _reflexio_cache.get(cache_key)
191
+ stored = existing is None
192
+ if stored:
193
+ _reflexio_cache[cache_key] = new_entry
194
+ result = reflexio
195
+ else:
196
+ result = existing.reflexio
197
+ span.set_data("stored", stored)
198
+ return result
169
199
 
170
200
 
171
201
  def invalidate_reflexio_cache(org_id: str, storage_base_dir: str | None = None) -> bool:
@@ -0,0 +1,65 @@
1
+ """Small helpers for parsing environment-backed settings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Mapping
7
+
8
+
9
+ def env_get(env: Mapping[str, str], name: str) -> str:
10
+ """Return a stripped environment value or an empty string."""
11
+ return str(env.get(name, "")).strip()
12
+
13
+
14
+ def env_str(
15
+ name: str,
16
+ default: str = "",
17
+ *,
18
+ env: Mapping[str, str] | None = None,
19
+ ) -> str:
20
+ """Return an environment value, treating unset OR blank as the default.
21
+
22
+ Unlike ``os.getenv(name, default)``, an empty / whitespace-only value
23
+ resolves to ``default`` rather than passing the empty string through. This
24
+ closes the "set to blank behaves differently from unset" gap: a ``KEY=``
25
+ line (which python-dotenv exports as ``""``) and an absent key both yield
26
+ the default, so callers never silently receive ``""`` where they expected a
27
+ real fallback (e.g. an ``int()``/``float()`` parse that would crash on ``""``).
28
+
29
+ Args:
30
+ name: Environment variable name.
31
+ default: Value returned when the variable is unset or blank.
32
+ env: Mapping to read from; defaults to ``os.environ``.
33
+
34
+ Returns:
35
+ The stripped environment value, or ``default`` when unset/blank.
36
+ """
37
+ raw = (env if env is not None else os.environ).get(name)
38
+ return raw.strip() if raw and raw.strip() else default
39
+
40
+
41
+ def env_required(env: Mapping[str, str], name: str) -> str:
42
+ """Return a required environment value or raise a startup error."""
43
+ value = env_get(env, name)
44
+ if not value:
45
+ raise RuntimeError(f"{name} is required for enterprise startup")
46
+ return value
47
+
48
+
49
+ def env_required_literal(
50
+ env: Mapping[str, str],
51
+ name: str,
52
+ *,
53
+ allowed: tuple[str, ...],
54
+ ) -> str:
55
+ """Return a required lowercased value constrained to allowed literals."""
56
+ value = env_required(env, name).lower()
57
+ if value not in allowed:
58
+ allowed_text = " or ".join(repr(item) for item in allowed)
59
+ raise RuntimeError(f"{name} must be {allowed_text} (got {value!r})")
60
+ return value
61
+
62
+
63
+ def env_truthy(value: str) -> bool:
64
+ """Return whether a string environment value is truthy."""
65
+ return value.strip().lower() in {"1", "true", "yes", "on"}