claude-smart 0.2.45 → 0.2.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +34 -5
  3. package/bin/claude-smart.js +295 -5
  4. package/package.json +22 -3
  5. package/plugin/.claude-plugin/plugin.json +4 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +22 -2
  8. package/plugin/commands/clear-all.md +1 -0
  9. package/plugin/commands/dashboard.md +1 -0
  10. package/plugin/commands/learn.md +1 -0
  11. package/plugin/commands/restart.md +1 -0
  12. package/plugin/commands/show.md +1 -0
  13. package/plugin/dashboard/app/configure/env/page.tsx +40 -14
  14. package/plugin/dashboard/app/configure/server/page.tsx +51 -1
  15. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  18. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  19. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  20. package/plugin/dashboard/lib/status.ts +10 -3
  21. package/plugin/dashboard/lib/types.ts +18 -6
  22. package/plugin/hooks/codex-hooks.json +0 -1
  23. package/plugin/opencode/assistant-buffer.ts +108 -0
  24. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  25. package/plugin/opencode/dist/internal.js +12 -0
  26. package/plugin/opencode/dist/payload.js +85 -0
  27. package/plugin/opencode/dist/server.mjs +158 -0
  28. package/plugin/opencode/internal.ts +18 -0
  29. package/plugin/opencode/package.json +3 -0
  30. package/plugin/opencode/payload.ts +90 -0
  31. package/plugin/opencode/server.mts +174 -0
  32. package/plugin/opencode/tsconfig.json +13 -0
  33. package/plugin/pyproject.toml +20 -3
  34. package/plugin/scripts/_lib.sh +5 -1
  35. package/plugin/scripts/backend-service.sh +22 -2
  36. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  37. package/plugin/scripts/hook_entry.sh +1 -1
  38. package/plugin/scripts/opencode-claude-compat +4 -0
  39. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  40. package/plugin/scripts/opencode-claude-compat.js +225 -0
  41. package/plugin/scripts/smart-install.sh +10 -8
  42. package/plugin/src/README.md +1 -1
  43. package/plugin/src/claude_smart/cli.py +304 -6
  44. package/plugin/src/claude_smart/env_config.py +12 -4
  45. package/plugin/src/claude_smart/events/session_start.py +26 -7
  46. package/plugin/src/claude_smart/events/stop.py +1 -1
  47. package/plugin/src/claude_smart/ids.py +1 -1
  48. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  49. package/plugin/src/claude_smart/runtime.py +7 -1
  50. package/plugin/uv.lock +1 -1
  51. package/plugin/vendor/reflexio/.env.example +54 -30
  52. package/plugin/vendor/reflexio/README.md +14 -8
  53. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  54. package/plugin/vendor/reflexio/reflexio/README.md +1 -0
  55. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  56. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  57. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  58. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  59. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  60. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  63. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  64. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  65. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  66. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  67. package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
  68. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  69. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  70. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  71. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  141. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
  142. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  143. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
  144. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
  146. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  147. package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
  148. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
  149. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  150. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  151. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  152. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  153. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  157. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
  158. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  159. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  160. package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
  161. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  162. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  163. package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
  164. package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
  165. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  166. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
  167. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  168. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  169. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  170. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  171. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  172. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  173. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  174. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  175. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  176. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  177. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  178. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  179. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  180. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  181. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  182. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  183. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  185. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  186. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  187. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
  188. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  189. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  197. package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
  198. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  199. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  200. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  201. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  202. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  203. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  204. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  205. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  206. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  207. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  208. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  209. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  211. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  212. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  214. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  215. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  216. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  217. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  218. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  219. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  220. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
  221. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  222. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  225. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  226. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
  227. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
  229. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  230. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  231. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  232. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  233. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  234. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  237. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  238. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  241. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
  242. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  243. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  244. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  245. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  246. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  247. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  248. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
  249. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  250. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  251. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  252. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  254. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  255. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  256. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
  257. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
  258. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
  259. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  260. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
  261. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
  262. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
  263. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  264. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  265. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
  266. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  277. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  278. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  279. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  280. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
  281. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  282. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  283. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  284. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  285. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  286. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  287. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  288. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  289. package/scripts/setup-claude-smart.sh +8 -3
  290. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  291. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  292. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  293. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  294. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  295. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  296. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  297. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  298. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  299. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  300. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  301. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  302. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  303. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  304. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  305. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  306. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  307. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  308. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  309. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  310. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  311. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  312. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  313. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  314. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  315. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  316. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  317. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  318. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  319. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  320. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  321. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  322. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -0,0 +1,18 @@
1
+ import * as path from "node:path";
2
+ import { defineConfig } from "vitest/config";
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ include: ["tests-ts/**/*.test.ts"],
7
+ },
8
+ resolve: {
9
+ alias: {
10
+ // The real openclaw plugin SDK ships with the host. Under vitest we
11
+ // shim only the surface the plugin imports.
12
+ "openclaw/plugin-sdk/plugin-entry": path.resolve(
13
+ __dirname,
14
+ "tests-ts/__mocks__/plugin-entry-stub.ts",
15
+ ),
16
+ },
17
+ },
18
+ });
@@ -1,8 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from collections import defaultdict
5
+ from typing import Literal, cast
6
+
1
7
  from reflexio.lib._base import (
2
8
  STORAGE_NOT_CONFIGURED_MSG,
3
9
  ReflexioBase,
4
10
  _require_storage,
5
11
  )
12
+ from reflexio.models.api_schema.domain.entities import (
13
+ PlaybookAggregationChangeLog,
14
+ agent_playbook_to_snapshot,
15
+ )
16
+ from reflexio.models.api_schema.domain.enums import Status
6
17
  from reflexio.models.api_schema.retriever_schema import (
7
18
  GetAgentPlaybooksRequest,
8
19
  GetAgentPlaybooksResponse,
@@ -24,29 +35,44 @@ from reflexio.models.api_schema.service_schemas import (
24
35
  PlaybookAggregationChangeLogResponse,
25
36
  )
26
37
  from reflexio.models.config_schema import SearchOptions
38
+ from reflexio.server.services.storage.storage_base import BaseStorage
39
+ from reflexio.server.services.storage.storage_base._playbook import (
40
+ AGGREGATE_REASON_PREFIX,
41
+ )
27
42
  from reflexio.server.tracing import profile_step
28
43
 
29
44
 
30
45
  class AgentPlaybookMixin(ReflexioBase):
31
46
  def get_playbook_aggregation_change_logs(
32
- self, playbook_name: str, agent_version: str
47
+ self,
48
+ playbook_name: str,
49
+ agent_version: str,
33
50
  ) -> PlaybookAggregationChangeLogResponse:
34
- """Get playbook aggregation change logs.
51
+ """Get playbook aggregation change logs, served from the lineage reconstruction.
52
+
53
+ The change-log view is rebuilt on demand from ``lineage_event`` rows via
54
+ :func:`reconstruct_playbook_aggregation_change_log`. The legacy
55
+ ``playbook_aggregation_change_logs`` table is no longer read. Results are
56
+ filtered to entries matching ``playbook_name`` and ``agent_version``.
57
+ ``updated_agent_playbooks`` is always ``[]`` (tolerated parity delta — Decision 3).
35
58
 
36
59
  Args:
37
- playbook_name (str): Playbook name to filter by
38
- agent_version (str): Agent version to filter by
60
+ playbook_name (str): Filter — only logs for this playbook name are returned.
61
+ agent_version (str): Filter — only logs for this agent version are returned.
39
62
 
40
63
  Returns:
41
- PlaybookAggregationChangeLogResponse: Response containing change logs
64
+ PlaybookAggregationChangeLogResponse: Response containing the reconstructed
65
+ change logs filtered by playbook_name and agent_version.
42
66
  """
43
67
  if not self._is_storage_configured():
44
68
  return PlaybookAggregationChangeLogResponse(success=True, change_logs=[])
45
- change_logs = self._get_storage().get_playbook_aggregation_change_logs(
46
- playbook_name=playbook_name, agent_version=agent_version
47
- )
48
- return PlaybookAggregationChangeLogResponse(
49
- success=True, change_logs=change_logs
69
+ # Legacy table no longer read; served by reconstruction filtered by
70
+ # playbook_name + agent_version. updated_agent_playbooks is always []
71
+ # (tolerated parity delta).
72
+ return reconstruct_playbook_aggregation_change_log(
73
+ self._get_storage(),
74
+ playbook_name=playbook_name,
75
+ agent_version=agent_version,
50
76
  )
51
77
 
52
78
  @_require_storage(DeleteAgentPlaybookResponse)
@@ -193,6 +219,7 @@ class AgentPlaybookMixin(ReflexioBase):
193
219
  playbook_status_filter=[request.playbook_status_filter]
194
220
  if request.playbook_status_filter
195
221
  else None,
222
+ tags=request.tags,
196
223
  )
197
224
  return GetAgentPlaybooksResponse(
198
225
  success=True,
@@ -308,3 +335,204 @@ class AgentPlaybookMixin(ReflexioBase):
308
335
  return UpdateAgentPlaybookResponse(
309
336
  success=True, msg="Agent playbook updated successfully"
310
337
  )
338
+
339
+
340
+ # ---------------------------------------------------------------------------
341
+ # Standalone read-side reconstruction (Phase B3b Task 2)
342
+ # ---------------------------------------------------------------------------
343
+
344
+ logger = logging.getLogger(__name__)
345
+
346
+ # Prefix for aggregate lineage event reasons — single source of truth in
347
+ # AGGREGATE_REASON_PREFIX (storage_base/_playbook.py); imported here for the parser.
348
+ _PREFIX = AGGREGATE_REASON_PREFIX
349
+
350
+
351
+ def reconstruct_playbook_aggregation_change_log(
352
+ storage: BaseStorage,
353
+ *,
354
+ limit: int = 100,
355
+ playbook_name: str | None = None,
356
+ agent_version: str | None = None,
357
+ ) -> PlaybookAggregationChangeLogResponse:
358
+ """Rebuild the PlaybookAggregationChangeLog view from lineage events.
359
+
360
+ Uses two immutable / stable signals to classify every aggregation run:
361
+
362
+ * **added(R)** — entity_ids of ``aggregate`` lineage events with
363
+ ``request_id == R``. Each ``aggregate`` event records one playbook
364
+ saved in the run. The ``reason`` field encodes the run mode:
365
+ ``"aggregate:full_archive"`` or ``"aggregate:incremental"``; any other
366
+ value defaults to ``"incremental"``.
367
+
368
+ * **removed(R)** — entity_ids of ``status_change`` lineage events with
369
+ ``to_status == "superseded"`` and ``request_id == R``. This is the
370
+ exact signature emitted by ``supersede_agent_playbooks_by_ids`` and
371
+ ``supersede_agent_playbooks_by_playbook_name`` (the soft-delete paths).
372
+ APPROVED playbooks are skipped by those helpers, so they have no removal
373
+ signal and are correctly absent from reconstruction.removed.
374
+
375
+ Groups are formed over the union of request_ids from both signals.
376
+ Request_id ``""`` is skipped — it would merge unrelated runs.
377
+ A row is emitted only when ``added or removed`` is non-empty.
378
+
379
+ When a removed playbook's tombstone has been physically purged (GC),
380
+ it is silently omitted from ``removed_agent_playbooks`` rather than crashing.
381
+
382
+ ``updated_agent_playbooks = []`` (Decision 3 — tolerated delta; updates
383
+ are folded into added/removed in the B3b reconstruction model).
384
+
385
+ An agent_playbook *added* in run R is included in R's
386
+ ``added_agent_playbooks`` even if a later run superseded (tombstoned) it
387
+ (resolved with ``include_tombstones=True``) — so R is not dropped from the
388
+ change log, matching the legacy table and ``reconstruct_profile_change_log``.
389
+ As with the removed side, once a tombstone is physically purged (GC) the
390
+ snapshot resolves to ``None`` and is silently omitted.
391
+
392
+ Version-semantics note (E1): for remove-only runs with mixed agent_version
393
+ across the superseded set, ``agent_version`` is that of the first resolved
394
+ removed snapshot. This is a query-VISIBILITY loss — version-filtered
395
+ queries will omit those removals — not just a label ambiguity.
396
+
397
+ Run-scalars (``playbook_name``, ``agent_version``) are read from the
398
+ reconstructed content: ``added[0]`` is preferred, else ``removed[0]``.
399
+ For a remove-only run whose superseded playbooks span multiple versions,
400
+ ``agent_version`` is that of the first resolved removed snapshot — a
401
+ tolerated, documented ambiguity (the aggregate event carries no version
402
+ since remove-only runs emit no aggregate event).
403
+
404
+ Args:
405
+ storage (BaseStorage): Storage instance to query.
406
+ limit (int): Maximum number of reconstructed entries to return.
407
+ Defaults to 100.
408
+ playbook_name (str | None): When provided, only logs whose
409
+ ``playbook_name`` matches are returned. Defaults to ``None``
410
+ (no filter).
411
+ agent_version (str | None): When provided, only logs whose
412
+ ``agent_version`` matches are returned. Defaults to ``None``
413
+ (no filter).
414
+
415
+ Returns:
416
+ PlaybookAggregationChangeLogResponse: ``success=True`` with
417
+ reconstructed rows ordered most-recent-first (by max event
418
+ ``created_at`` in each request_id group), filtered by
419
+ ``playbook_name``/``agent_version`` when supplied, and capped
420
+ at ``limit``.
421
+ """
422
+ if limit <= 0:
423
+ return PlaybookAggregationChangeLogResponse(success=True, change_logs=[])
424
+
425
+ all_events = storage.get_lineage_events(
426
+ entity_type="agent_playbook", org_id=storage.org_id
427
+ )
428
+
429
+ added_by_req: dict[str, list[str]] = defaultdict(list)
430
+ removed_by_req: dict[str, list[str]] = defaultdict(list)
431
+ run_mode_by_req: dict[str, Literal["full_archive", "incremental"]] = {}
432
+ sort_key: dict[str, tuple[int, int]] = {}
433
+
434
+ for evt in all_events:
435
+ req = evt.request_id
436
+ if not req:
437
+ continue # skip empty — never merge unrelated runs
438
+ cur = sort_key.get(req, (0, 0))
439
+ if (evt.created_at, evt.event_id) > cur:
440
+ sort_key[req] = (evt.created_at, evt.event_id)
441
+ if evt.op == "aggregate":
442
+ added_by_req[req].append(evt.entity_id)
443
+ if req not in run_mode_by_req:
444
+ reason = evt.reason or ""
445
+ if reason.startswith(_PREFIX):
446
+ suffix = reason[len(_PREFIX) :]
447
+ run_mode_by_req[req] = cast(
448
+ Literal["full_archive", "incremental"],
449
+ suffix
450
+ if suffix in ("full_archive", "incremental")
451
+ else "incremental",
452
+ )
453
+ else:
454
+ run_mode_by_req[req] = "incremental"
455
+ elif evt.op == "status_change" and evt.to_status == Status.SUPERSEDED.value:
456
+ removed_by_req[req].append(evt.entity_id)
457
+
458
+ candidate_reqs = set(added_by_req) | set(removed_by_req)
459
+ sorted_reqs = sorted(
460
+ candidate_reqs,
461
+ key=lambda r: sort_key.get(r, (0, 0)),
462
+ reverse=True,
463
+ )
464
+
465
+ logs: list[PlaybookAggregationChangeLog] = []
466
+ # PERFORMANCE NOTE (M3): as of Track B retirement this serves the live
467
+ # ``/api/playbook_aggregation_change_logs`` endpoint (the legacy table is no
468
+ # longer read), so it IS on the request path. Two bounds keep it from scaling
469
+ # with full org history: (1) the lineage-event read is paginated in the
470
+ # Supabase backend (``get_lineage_events`` loops ``.range()`` windows), so
471
+ # PostgREST ``max_rows`` can no longer silently truncate to the oldest rows;
472
+ # (2) name/version filtering AND the ``limit`` are applied INSIDE this loop and
473
+ # we ``break`` once ``limit`` matches are collected, so the per-entity
474
+ # ``get_agent_playbook_by_id`` (N+1) resolution stops at the requested page
475
+ # instead of resolving every run in the org. ``sorted_reqs`` is most-recent
476
+ # first, so breaking after ``limit`` matches yields the ``limit`` most-recent
477
+ # matches. Remaining follow-up (tracked, NOT done here): a batch ``*_by_ids``
478
+ # fetch shared with ``reconstruct_profile_change_log``.
479
+ for req in sorted_reqs:
480
+ added = []
481
+ for eid in added_by_req[req]:
482
+ try:
483
+ # include_tombstones: a playbook added in this run but later
484
+ # superseded must still appear in this run's added side (so the
485
+ # run is not dropped) — mirrors the removed-side resolve below.
486
+ pb = storage.get_agent_playbook_by_id(int(eid), include_tombstones=True)
487
+ except (ValueError, TypeError):
488
+ logger.warning(
489
+ "reconstruct: malformed entity_id %r in added_by_req, skipping", eid
490
+ )
491
+ continue
492
+ if pb is not None:
493
+ added.append(agent_playbook_to_snapshot(pb))
494
+
495
+ removed = []
496
+ for eid in removed_by_req[req]:
497
+ try:
498
+ pb = storage.get_agent_playbook_by_id(int(eid), include_tombstones=True)
499
+ except (ValueError, TypeError):
500
+ logger.warning(
501
+ "reconstruct: malformed entity_id %r in removed_by_req, skipping",
502
+ eid,
503
+ )
504
+ continue
505
+ if pb is not None:
506
+ removed.append(agent_playbook_to_snapshot(pb))
507
+
508
+ if not added and not removed:
509
+ continue
510
+
511
+ # agent_version is the run's REPRESENTATIVE snapshot version (added[0], else
512
+ # removed[0]). For a remove-only run whose superseded playbooks span multiple
513
+ # versions, the label is that of the first resolved removed snapshot — a
514
+ # tolerated, documented ambiguity (the aggregate event carries no version).
515
+ first = added[0] if added else removed[0]
516
+ # Filter INSIDE the loop so the ``limit`` is applied to the post-filter
517
+ # set (not a pre-filter slice) AND we can stop once the page is full.
518
+ if playbook_name is not None and first.playbook_name != playbook_name:
519
+ continue
520
+ if agent_version is not None and first.agent_version != agent_version:
521
+ continue
522
+
523
+ ts, _ = sort_key.get(req, (0, 0))
524
+ logs.append(
525
+ PlaybookAggregationChangeLog(
526
+ playbook_name=first.playbook_name,
527
+ agent_version=first.agent_version,
528
+ run_mode=run_mode_by_req.get(req, "incremental"),
529
+ added_agent_playbooks=added,
530
+ removed_agent_playbooks=removed,
531
+ updated_agent_playbooks=[],
532
+ created_at=ts,
533
+ )
534
+ )
535
+ if len(logs) >= limit: # limit > 0 guaranteed above
536
+ break
537
+
538
+ return PlaybookAggregationChangeLogResponse(success=True, change_logs=logs)
@@ -2,7 +2,7 @@ from typing import Any
2
2
 
3
3
  from reflexio.lib._base import ReflexioBase
4
4
  from reflexio.models.api_schema.retriever_schema import SetConfigResponse
5
- from reflexio.models.config_schema import Config
5
+ from reflexio.models.config_schema import Config, StorageConfigManagedSupabase
6
6
 
7
7
 
8
8
  class ConfigMixin(ReflexioBase):
@@ -22,10 +22,17 @@ class ConfigMixin(ReflexioBase):
22
22
  config = Config(**config)
23
23
 
24
24
  # Validate storage connection before setting config.
25
- # If no storage_config provided, preserve the existing one (callers
26
- # like get_config() don't expose storage_config for security).
25
+ # If no storage_config provided, or the caller round-tripped the
26
+ # redacted platform-managed marker that get_config() emits in place
27
+ # of real credentials, preserve the existing storage configuration
28
+ # (callers like get_config() don't expose storage_config for
29
+ # security). Without this, the marker reaches the readiness check as
30
+ # an unfillable StorageConfigManagedSupabase and fails with
31
+ # "Storage configuration is incomplete".
27
32
  storage_config = config.storage_config
28
- if storage_config is None:
33
+ if storage_config is None or isinstance(
34
+ storage_config, StorageConfigManagedSupabase
35
+ ):
29
36
  storage_config = configurator.get_current_storage_configuration()
30
37
  config.storage_config = storage_config
31
38
 
@@ -38,17 +38,28 @@ class GenerationMixin(ReflexioBase):
38
38
  """
39
39
  if not self._is_storage_configured():
40
40
  raise ValueError(STORAGE_NOT_CONFIGURED_MSG)
41
- from reflexio.server.services.playbook.playbook_aggregator import (
41
+ from reflexio.server.services.playbook.components.aggregator import (
42
42
  PlaybookAggregator,
43
43
  )
44
44
  from reflexio.server.services.playbook.playbook_service_utils import (
45
45
  PlaybookAggregatorRequest,
46
46
  )
47
+ from reflexio.server.services.playbook.user_detail_stripping import (
48
+ create_aggregation_user_detail_stripper,
49
+ )
50
+
51
+ user_detail_stripper = create_aggregation_user_detail_stripper(
52
+ self.request_context.configurator
53
+ )
54
+ aggregator_kwargs = {}
55
+ if user_detail_stripper is not None:
56
+ aggregator_kwargs["user_detail_stripper"] = user_detail_stripper
47
57
 
48
58
  playbook_aggregator = PlaybookAggregator(
49
59
  llm_client=self.llm_client,
50
60
  request_context=self.request_context,
51
61
  agent_version=agent_version,
62
+ **aggregator_kwargs,
52
63
  )
53
64
  aggregator_request = PlaybookAggregatorRequest(
54
65
  agent_version=agent_version,
@@ -88,7 +99,7 @@ class GenerationMixin(ReflexioBase):
88
99
  Returns:
89
100
  RerunProfileGenerationResponse: Response containing success status, message, and count of profiles generated
90
101
  """
91
- from reflexio.server.services.profile.profile_generation_service import (
102
+ from reflexio.server.services.profile.service import (
92
103
  ProfileGenerationService,
93
104
  )
94
105
 
@@ -113,7 +124,7 @@ class GenerationMixin(ReflexioBase):
113
124
  Returns:
114
125
  ManualProfileGenerationResponse: Response containing success status, message, and count of profiles generated
115
126
  """
116
- from reflexio.server.services.profile.profile_generation_service import (
127
+ from reflexio.server.services.profile.service import (
117
128
  ProfileGenerationService,
118
129
  )
119
130
 
@@ -138,7 +149,7 @@ class GenerationMixin(ReflexioBase):
138
149
  Returns:
139
150
  RerunPlaybookGenerationResponse: Response containing success status, message, and count of playbooks generated
140
151
  """
141
- from reflexio.server.services.playbook.playbook_generation_service import (
152
+ from reflexio.server.services.playbook.service import (
142
153
  PlaybookGenerationService,
143
154
  )
144
155
 
@@ -163,7 +174,7 @@ class GenerationMixin(ReflexioBase):
163
174
  Returns:
164
175
  ManualPlaybookGenerationResponse: Response containing success status, message, and count of playbooks generated
165
176
  """
166
- from reflexio.server.services.playbook.playbook_generation_service import (
177
+ from reflexio.server.services.playbook.service import (
167
178
  PlaybookGenerationService,
168
179
  )
169
180
 
@@ -0,0 +1,187 @@
1
+ """Read-only PostgREST storage reader for change-log reconstruction.
2
+
3
+ ``RestStorageReader`` exposes only the read methods that
4
+ ``reconstruct_profile_change_log`` calls (the ``ChangeLogReadStorage``
5
+ protocol), sourced from Supabase **PostgREST GETs**. It is deliberately NOT a
6
+ ``BaseStorage`` subclass and performs **no writes** — so it can be pointed at a
7
+ production data ref to run the reconstruction without the side effects of
8
+ constructing the real ``SupabaseStorage`` (whose ``__init__`` can mutate
9
+ PostgREST schema config).
10
+
11
+ The HTTP layer is injectable (``fetch``) so the reconstruction pipeline can be
12
+ unit-tested with canned rows and no network.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Callable
18
+ from urllib.parse import urlparse
19
+
20
+ from reflexio.models.api_schema.domain.entities import (
21
+ LineageEvent,
22
+ UserProfile,
23
+ )
24
+
25
+ # Page size for unbounded reads. A read returning a full page may be truncated,
26
+ # which would silently skew the reconstruction — so reaching it flips
27
+ # ``truncated`` and callers can surface the run as untrustworthy.
28
+ _FETCH_CAP = 10_000
29
+
30
+ _LINEAGE_EVENT_FIELDS = set(LineageEvent.model_fields)
31
+ _USER_PROFILE_FIELDS = set(UserProfile.model_fields)
32
+
33
+ # Explicit column list for profiles — avoids pulling the embedding/FTS columns.
34
+ _PROFILE_COLUMNS = (
35
+ "profile_id,user_id,content,last_modified_timestamp,"
36
+ "generated_from_request_id,status,superseded_by,merged_into,source"
37
+ )
38
+
39
+ Fetch = Callable[[str, dict], list[dict]]
40
+
41
+
42
+ def _model[M](cls: type[M], fields: set[str], row: dict) -> M:
43
+ """Build a Pydantic model from a row, ignoring columns it does not declare."""
44
+ return cls(**{k: v for k, v in row.items() if k in fields})
45
+
46
+
47
+ class RestStorageReader:
48
+ """Read-only PostgREST reader implementing the parity-check read surface."""
49
+
50
+ def __init__(
51
+ self,
52
+ base_url: str,
53
+ api_key: str,
54
+ *,
55
+ org_id: str,
56
+ schema: str = "public",
57
+ fetch: Fetch | None = None,
58
+ ) -> None:
59
+ # The api_key is a service_role credential (RLS-bypassing). Refuse to
60
+ # attach it to anything but an https Supabase host, so a typo'd or
61
+ # hostile --supabase-url cannot exfiltrate it.
62
+ host = urlparse(base_url).hostname or ""
63
+ if urlparse(base_url).scheme != "https" or not host.endswith(".supabase.co"):
64
+ raise ValueError(
65
+ f"base_url must be an https://<ref>.supabase.co URL; got {base_url!r}"
66
+ )
67
+ self.org_id = org_id
68
+ self._base = base_url.rstrip("/")
69
+ self._key = api_key
70
+ self._schema = schema
71
+ self._fetch: Fetch = fetch or self._http_fetch
72
+ # Set when any unbounded read returns a full _FETCH_CAP page: the
73
+ # reconstruction inputs may be truncated, so the verdict is untrustworthy.
74
+ self.truncated = False
75
+
76
+ def _http_fetch(self, table: str, params: dict) -> list[dict]:
77
+ import requests # noqa: PLC0415 — only needed on the live path
78
+
79
+ headers = {"apikey": self._key, "Authorization": f"Bearer {self._key}"}
80
+ # Non-public schemas (the shared org_<id> cohort) are selected per-request
81
+ # via Accept-Profile; dedicated refs keep data in public.
82
+ if self._schema and self._schema != "public":
83
+ headers["Accept-Profile"] = self._schema
84
+ resp = requests.get(
85
+ f"{self._base}/rest/v1/{table}",
86
+ headers=headers,
87
+ params=params,
88
+ timeout=30,
89
+ allow_redirects=False, # never forward the service_role key on a 3xx
90
+ )
91
+ if resp.is_redirect:
92
+ raise RuntimeError(
93
+ f"PostgREST returned a redirect for {table!r} — refusing to follow it "
94
+ "and forward credentials. Check --supabase-url."
95
+ )
96
+ if resp.status_code in (401, 403):
97
+ raise RuntimeError(
98
+ f"PostgREST {resp.status_code} for {table!r}: the service_role key is "
99
+ "not valid for this data ref (keys are per-ref / per-server)."
100
+ )
101
+ if resp.status_code in (404, 406):
102
+ raise RuntimeError(
103
+ f"PostgREST {resp.status_code} for {table!r}: table/schema not exposed "
104
+ f"— check --schema (got {self._schema!r}; use org_<id> for the shared cohort)."
105
+ )
106
+ resp.raise_for_status()
107
+ return resp.json()
108
+
109
+ def _note_page(self, rows: list[dict]) -> list[dict]:
110
+ """Flag a possibly-truncated read (a full page may have more behind it)."""
111
+ if len(rows) >= _FETCH_CAP:
112
+ self.truncated = True
113
+ return rows
114
+
115
+ def get_lineage_events(
116
+ self,
117
+ *,
118
+ entity_type: str | None = None,
119
+ entity_id: str | None = None,
120
+ org_id: str | None = None,
121
+ request_id: str | None = None,
122
+ ) -> list[LineageEvent]:
123
+ params: dict = {"select": "*", "order": "event_id.asc", "limit": _FETCH_CAP}
124
+ if entity_type:
125
+ params["entity_type"] = f"eq.{entity_type}"
126
+ if entity_id:
127
+ params["entity_id"] = f"eq.{entity_id}"
128
+ if request_id:
129
+ params["request_id"] = f"eq.{request_id}"
130
+ if org_id:
131
+ params["org_id"] = f"eq.{org_id}"
132
+ rows = self._note_page(self._fetch("lineage_event", params))
133
+ return [_model(LineageEvent, _LINEAGE_EVENT_FIELDS, r) for r in rows]
134
+
135
+ def get_distinct_generated_from_request_ids(self) -> list[str]:
136
+ rows = self._note_page(
137
+ self._fetch(
138
+ "profiles",
139
+ {"select": "generated_from_request_id", "limit": _FETCH_CAP},
140
+ )
141
+ )
142
+ return sorted(
143
+ {
144
+ r["generated_from_request_id"]
145
+ for r in rows
146
+ if r.get("generated_from_request_id")
147
+ }
148
+ )
149
+
150
+ def get_profiles_by_generated_from_request_id(
151
+ self, request_id: str
152
+ ) -> list[UserProfile]:
153
+ rows = self._note_page(
154
+ self._fetch(
155
+ "profiles",
156
+ {
157
+ "select": _PROFILE_COLUMNS,
158
+ "generated_from_request_id": f"eq.{request_id}",
159
+ "limit": _FETCH_CAP,
160
+ },
161
+ )
162
+ )
163
+ return [_model(UserProfile, _USER_PROFILE_FIELDS, r) for r in rows]
164
+
165
+ def get_all_generated_profiles(self) -> list[UserProfile]:
166
+ # Fetch all profiles in one page and filter non-empty gfr client-side
167
+ # (mirrors get_distinct; avoids PostgREST empty-string operator ambiguity).
168
+ rows = self._note_page(
169
+ self._fetch("profiles", {"select": _PROFILE_COLUMNS, "limit": _FETCH_CAP})
170
+ )
171
+ return [
172
+ _model(UserProfile, _USER_PROFILE_FIELDS, r)
173
+ for r in rows
174
+ if r.get("generated_from_request_id")
175
+ ]
176
+
177
+ def get_profile_by_id(
178
+ self,
179
+ profile_id: str,
180
+ *,
181
+ include_tombstones: bool = False, # noqa: ARG002 — id lookup always returns tombstones
182
+ ) -> UserProfile | None:
183
+ rows = self._fetch(
184
+ "profiles",
185
+ {"select": _PROFILE_COLUMNS, "profile_id": f"eq.{profile_id}", "limit": 1},
186
+ )
187
+ return _model(UserProfile, _USER_PROFILE_FIELDS, rows[0]) if rows else None