claude-smart 0.2.45 → 0.2.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +47 -10
  3. package/bin/claude-smart.js +535 -23
  4. package/package.json +22 -3
  5. package/plugin/.claude-plugin/plugin.json +4 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +27 -6
  8. package/plugin/commands/clear-all.md +1 -0
  9. package/plugin/commands/dashboard.md +1 -0
  10. package/plugin/commands/learn.md +1 -0
  11. package/plugin/commands/restart.md +1 -0
  12. package/plugin/commands/show.md +1 -0
  13. package/plugin/dashboard/app/configure/env/page.tsx +40 -14
  14. package/plugin/dashboard/app/configure/server/page.tsx +51 -1
  15. package/plugin/dashboard/app/layout.tsx +20 -0
  16. package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
  17. package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
  18. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
  19. package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
  20. package/plugin/dashboard/lib/reflexio-client.ts +16 -0
  21. package/plugin/dashboard/lib/status.ts +10 -3
  22. package/plugin/dashboard/lib/types.ts +18 -6
  23. package/plugin/hooks/codex-hooks.json +0 -1
  24. package/plugin/opencode/assistant-buffer.ts +108 -0
  25. package/plugin/opencode/dist/assistant-buffer.js +96 -0
  26. package/plugin/opencode/dist/internal.js +12 -0
  27. package/plugin/opencode/dist/payload.js +85 -0
  28. package/plugin/opencode/dist/server.mjs +232 -0
  29. package/plugin/opencode/internal.ts +18 -0
  30. package/plugin/opencode/package.json +3 -0
  31. package/plugin/opencode/payload.ts +90 -0
  32. package/plugin/opencode/server.mts +251 -0
  33. package/plugin/opencode/tsconfig.json +13 -0
  34. package/plugin/pyproject.toml +25 -4
  35. package/plugin/scripts/_lib.sh +5 -1
  36. package/plugin/scripts/backend-service.sh +22 -2
  37. package/plugin/scripts/ensure-plugin-root.sh +4 -4
  38. package/plugin/scripts/hook_entry.sh +1 -1
  39. package/plugin/scripts/opencode-claude-compat +4 -0
  40. package/plugin/scripts/opencode-claude-compat.cmd +3 -0
  41. package/plugin/scripts/opencode-claude-compat.js +225 -0
  42. package/plugin/scripts/smart-install.sh +17 -9
  43. package/plugin/src/README.md +1 -1
  44. package/plugin/src/claude_smart/cli.py +498 -12
  45. package/plugin/src/claude_smart/context_format.py +9 -9
  46. package/plugin/src/claude_smart/cs_cite.py +66 -19
  47. package/plugin/src/claude_smart/env_config.py +12 -4
  48. package/plugin/src/claude_smart/events/session_start.py +26 -7
  49. package/plugin/src/claude_smart/events/stop.py +1 -1
  50. package/plugin/src/claude_smart/ids.py +1 -1
  51. package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
  52. package/plugin/src/claude_smart/runtime.py +7 -1
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +54 -30
  55. package/plugin/vendor/reflexio/README.md +17 -11
  56. package/plugin/vendor/reflexio/pyproject.toml +13 -1
  57. package/plugin/vendor/reflexio/reflexio/README.md +4 -1
  58. package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
  59. package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
  60. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
  61. package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
  62. package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
  63. package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
  64. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +212 -52
  65. package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
  66. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
  67. package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
  68. package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
  69. package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
  70. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  71. package/plugin/vendor/reflexio/reflexio/client/client.py +154 -13
  72. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
  73. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
  74. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
  75. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
  76. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
  77. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
  78. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
  79. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
  80. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
  81. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
  82. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
  83. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
  84. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
  85. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
  86. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
  87. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
  88. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
  89. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
  90. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
  91. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
  92. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
  93. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
  94. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
  95. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
  96. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
  97. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
  98. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
  99. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
  100. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
  101. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
  102. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
  103. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
  104. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
  105. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
  106. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
  107. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
  108. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
  109. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
  110. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
  111. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
  112. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
  113. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
  114. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
  115. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
  116. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
  117. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
  118. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
  119. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
  120. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
  121. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
  122. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
  123. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
  124. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
  125. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
  126. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
  127. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
  128. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
  129. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
  130. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
  131. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
  132. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
  133. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
  134. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
  135. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
  136. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
  137. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
  138. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
  139. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
  140. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
  141. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
  142. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
  143. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
  144. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
  145. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +246 -10
  146. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  147. package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
  148. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
  149. package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
  150. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
  151. package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
  152. package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
  153. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
  154. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  155. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
  156. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
  157. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  158. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
  159. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
  160. package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
  161. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +97 -3
  162. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
  163. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
  164. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +104 -7
  165. package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
  166. package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
  167. package/plugin/vendor/reflexio/reflexio/server/README.md +58 -33
  168. package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
  169. package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
  170. package/plugin/vendor/reflexio/reflexio/server/api.py +899 -103
  171. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
  172. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
  173. package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
  174. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
  175. package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
  176. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
  177. package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
  178. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  179. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
  180. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
  181. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
  182. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  183. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
  184. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
  185. package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
  186. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  187. package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
  188. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
  189. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
  190. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
  191. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
  192. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
  193. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
  194. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
  195. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
  196. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
  197. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
  198. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +278 -0
  199. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
  200. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
  201. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
  202. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
  203. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
  204. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
  205. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
  206. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
  207. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
  208. package/plugin/vendor/reflexio/reflexio/server/services/README.md +28 -11
  209. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
  210. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
  211. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
  212. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
  213. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
  214. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
  215. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
  216. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
  217. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
  218. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
  219. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
  220. package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
  221. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
  222. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
  223. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
  224. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
  225. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
  226. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
  227. package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
  228. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
  229. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
  230. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
  231. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +47 -16
  232. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
  233. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  234. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  235. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  236. package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
  237. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -0
  238. package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
  239. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
  240. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +18 -11
  241. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  242. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
  243. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +334 -178
  244. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
  245. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
  246. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
  247. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
  248. package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
  249. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
  250. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
  251. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
  252. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
  253. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
  254. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
  255. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +31 -22
  256. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
  257. package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
  258. package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
  259. package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
  260. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
  261. package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
  262. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  263. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -6
  264. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
  265. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
  266. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
  267. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +31 -0
  268. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  269. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
  270. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +16 -2
  271. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
  272. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +644 -197
  273. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
  274. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
  275. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -0
  276. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
  277. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +31 -1369
  278. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
  279. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
  280. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
  281. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
  282. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  283. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
  284. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  285. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  286. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  287. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
  288. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
  289. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
  290. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
  291. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +40 -57
  292. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
  293. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
  294. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
  295. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +4 -677
  296. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
  297. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -0
  298. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
  299. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
  300. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  301. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  302. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  303. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  304. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  305. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  306. package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
  307. package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
  308. package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
  309. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
  310. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +372 -34
  311. package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
  312. package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
  313. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
  314. package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
  315. package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
  316. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
  317. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
  318. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
  319. package/scripts/setup-claude-smart.sh +8 -3
  320. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
  321. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
  322. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
  323. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
  324. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
  325. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
  326. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
  327. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
  328. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
  329. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
  330. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
  331. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
  332. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
  333. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
  334. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
  335. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
  336. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
  337. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
  338. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
  339. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
  340. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
  341. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
  342. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
  343. package/plugin/vendor/reflexio/reflexio/server/_auth.py +0 -25
  344. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
  345. package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
  346. package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
  347. package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
  348. /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
  349. /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
  350. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
  351. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
  352. /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
  353. /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
@@ -0,0 +1,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)
@@ -187,12 +213,21 @@ class AgentPlaybookMixin(ReflexioBase):
187
213
  try:
188
214
  agent_playbooks = self._get_storage().get_agent_playbooks(
189
215
  limit=request.limit or 100,
216
+ agent_playbook_id=request.agent_playbook_id,
217
+ query=request.query,
190
218
  playbook_name=request.playbook_name,
191
219
  agent_version=request.agent_version,
220
+ start_time=(
221
+ int(request.start_time.timestamp()) if request.start_time else None
222
+ ),
223
+ end_time=(
224
+ int(request.end_time.timestamp()) if request.end_time else None
225
+ ),
192
226
  status_filter=request.status_filter,
193
227
  playbook_status_filter=[request.playbook_status_filter]
194
228
  if request.playbook_status_filter
195
229
  else None,
230
+ tags=request.tags,
196
231
  )
197
232
  return GetAgentPlaybooksResponse(
198
233
  success=True,
@@ -308,3 +343,204 @@ class AgentPlaybookMixin(ReflexioBase):
308
343
  return UpdateAgentPlaybookResponse(
309
344
  success=True, msg="Agent playbook updated successfully"
310
345
  )
346
+
347
+
348
+ # ---------------------------------------------------------------------------
349
+ # Standalone read-side reconstruction (Phase B3b Task 2)
350
+ # ---------------------------------------------------------------------------
351
+
352
+ logger = logging.getLogger(__name__)
353
+
354
+ # Prefix for aggregate lineage event reasons — single source of truth in
355
+ # AGGREGATE_REASON_PREFIX (storage_base/_playbook.py); imported here for the parser.
356
+ _PREFIX = AGGREGATE_REASON_PREFIX
357
+
358
+
359
+ def reconstruct_playbook_aggregation_change_log(
360
+ storage: BaseStorage,
361
+ *,
362
+ limit: int = 100,
363
+ playbook_name: str | None = None,
364
+ agent_version: str | None = None,
365
+ ) -> PlaybookAggregationChangeLogResponse:
366
+ """Rebuild the PlaybookAggregationChangeLog view from lineage events.
367
+
368
+ Uses two immutable / stable signals to classify every aggregation run:
369
+
370
+ * **added(R)** — entity_ids of ``aggregate`` lineage events with
371
+ ``request_id == R``. Each ``aggregate`` event records one playbook
372
+ saved in the run. The ``reason`` field encodes the run mode:
373
+ ``"aggregate:full_archive"`` or ``"aggregate:incremental"``; any other
374
+ value defaults to ``"incremental"``.
375
+
376
+ * **removed(R)** — entity_ids of ``status_change`` lineage events with
377
+ ``to_status == "superseded"`` and ``request_id == R``. This is the
378
+ exact signature emitted by ``supersede_agent_playbooks_by_ids`` and
379
+ ``supersede_agent_playbooks_by_playbook_name`` (the soft-delete paths).
380
+ APPROVED playbooks are skipped by those helpers, so they have no removal
381
+ signal and are correctly absent from reconstruction.removed.
382
+
383
+ Groups are formed over the union of request_ids from both signals.
384
+ Request_id ``""`` is skipped — it would merge unrelated runs.
385
+ A row is emitted only when ``added or removed`` is non-empty.
386
+
387
+ When a removed playbook's tombstone has been physically purged (GC),
388
+ it is silently omitted from ``removed_agent_playbooks`` rather than crashing.
389
+
390
+ ``updated_agent_playbooks = []`` (Decision 3 — tolerated delta; updates
391
+ are folded into added/removed in the B3b reconstruction model).
392
+
393
+ An agent_playbook *added* in run R is included in R's
394
+ ``added_agent_playbooks`` even if a later run superseded (tombstoned) it
395
+ (resolved with ``include_tombstones=True``) — so R is not dropped from the
396
+ change log, matching the legacy table and ``reconstruct_profile_change_log``.
397
+ As with the removed side, once a tombstone is physically purged (GC) the
398
+ snapshot resolves to ``None`` and is silently omitted.
399
+
400
+ Version-semantics note (E1): for remove-only runs with mixed agent_version
401
+ across the superseded set, ``agent_version`` is that of the first resolved
402
+ removed snapshot. This is a query-VISIBILITY loss — version-filtered
403
+ queries will omit those removals — not just a label ambiguity.
404
+
405
+ Run-scalars (``playbook_name``, ``agent_version``) are read from the
406
+ reconstructed content: ``added[0]`` is preferred, else ``removed[0]``.
407
+ For a remove-only run whose superseded playbooks span multiple versions,
408
+ ``agent_version`` is that of the first resolved removed snapshot — a
409
+ tolerated, documented ambiguity (the aggregate event carries no version
410
+ since remove-only runs emit no aggregate event).
411
+
412
+ Args:
413
+ storage (BaseStorage): Storage instance to query.
414
+ limit (int): Maximum number of reconstructed entries to return.
415
+ Defaults to 100.
416
+ playbook_name (str | None): When provided, only logs whose
417
+ ``playbook_name`` matches are returned. Defaults to ``None``
418
+ (no filter).
419
+ agent_version (str | None): When provided, only logs whose
420
+ ``agent_version`` matches are returned. Defaults to ``None``
421
+ (no filter).
422
+
423
+ Returns:
424
+ PlaybookAggregationChangeLogResponse: ``success=True`` with
425
+ reconstructed rows ordered most-recent-first (by max event
426
+ ``created_at`` in each request_id group), filtered by
427
+ ``playbook_name``/``agent_version`` when supplied, and capped
428
+ at ``limit``.
429
+ """
430
+ if limit <= 0:
431
+ return PlaybookAggregationChangeLogResponse(success=True, change_logs=[])
432
+
433
+ all_events = storage.get_lineage_events(
434
+ entity_type="agent_playbook", org_id=storage.org_id
435
+ )
436
+
437
+ added_by_req: dict[str, list[str]] = defaultdict(list)
438
+ removed_by_req: dict[str, list[str]] = defaultdict(list)
439
+ run_mode_by_req: dict[str, Literal["full_archive", "incremental"]] = {}
440
+ sort_key: dict[str, tuple[int, int]] = {}
441
+
442
+ for evt in all_events:
443
+ req = evt.request_id
444
+ if not req:
445
+ continue # skip empty — never merge unrelated runs
446
+ cur = sort_key.get(req, (0, 0))
447
+ if (evt.created_at, evt.event_id) > cur:
448
+ sort_key[req] = (evt.created_at, evt.event_id)
449
+ if evt.op == "aggregate":
450
+ added_by_req[req].append(evt.entity_id)
451
+ if req not in run_mode_by_req:
452
+ reason = evt.reason or ""
453
+ if reason.startswith(_PREFIX):
454
+ suffix = reason[len(_PREFIX) :]
455
+ run_mode_by_req[req] = cast(
456
+ Literal["full_archive", "incremental"],
457
+ suffix
458
+ if suffix in ("full_archive", "incremental")
459
+ else "incremental",
460
+ )
461
+ else:
462
+ run_mode_by_req[req] = "incremental"
463
+ elif evt.op == "status_change" and evt.to_status == Status.SUPERSEDED.value:
464
+ removed_by_req[req].append(evt.entity_id)
465
+
466
+ candidate_reqs = set(added_by_req) | set(removed_by_req)
467
+ sorted_reqs = sorted(
468
+ candidate_reqs,
469
+ key=lambda r: sort_key.get(r, (0, 0)),
470
+ reverse=True,
471
+ )
472
+
473
+ logs: list[PlaybookAggregationChangeLog] = []
474
+ # PERFORMANCE NOTE (M3): as of Track B retirement this serves the live
475
+ # ``/api/playbook_aggregation_change_logs`` endpoint (the legacy table is no
476
+ # longer read), so it IS on the request path. Two bounds keep it from scaling
477
+ # with full org history: (1) the lineage-event read is paginated in the
478
+ # Supabase backend (``get_lineage_events`` loops ``.range()`` windows), so
479
+ # PostgREST ``max_rows`` can no longer silently truncate to the oldest rows;
480
+ # (2) name/version filtering AND the ``limit`` are applied INSIDE this loop and
481
+ # we ``break`` once ``limit`` matches are collected, so the per-entity
482
+ # ``get_agent_playbook_by_id`` (N+1) resolution stops at the requested page
483
+ # instead of resolving every run in the org. ``sorted_reqs`` is most-recent
484
+ # first, so breaking after ``limit`` matches yields the ``limit`` most-recent
485
+ # matches. Remaining follow-up (tracked, NOT done here): a batch ``*_by_ids``
486
+ # fetch shared with ``reconstruct_profile_change_log``.
487
+ for req in sorted_reqs:
488
+ added = []
489
+ for eid in added_by_req[req]:
490
+ try:
491
+ # include_tombstones: a playbook added in this run but later
492
+ # superseded must still appear in this run's added side (so the
493
+ # run is not dropped) — mirrors the removed-side resolve below.
494
+ pb = storage.get_agent_playbook_by_id(int(eid), include_tombstones=True)
495
+ except (ValueError, TypeError):
496
+ logger.warning(
497
+ "reconstruct: malformed entity_id %r in added_by_req, skipping", eid
498
+ )
499
+ continue
500
+ if pb is not None:
501
+ added.append(agent_playbook_to_snapshot(pb))
502
+
503
+ removed = []
504
+ for eid in removed_by_req[req]:
505
+ try:
506
+ pb = storage.get_agent_playbook_by_id(int(eid), include_tombstones=True)
507
+ except (ValueError, TypeError):
508
+ logger.warning(
509
+ "reconstruct: malformed entity_id %r in removed_by_req, skipping",
510
+ eid,
511
+ )
512
+ continue
513
+ if pb is not None:
514
+ removed.append(agent_playbook_to_snapshot(pb))
515
+
516
+ if not added and not removed:
517
+ continue
518
+
519
+ # agent_version is the run's REPRESENTATIVE snapshot version (added[0], else
520
+ # removed[0]). For a remove-only run whose superseded playbooks span multiple
521
+ # versions, the label is that of the first resolved removed snapshot — a
522
+ # tolerated, documented ambiguity (the aggregate event carries no version).
523
+ first = added[0] if added else removed[0]
524
+ # Filter INSIDE the loop so the ``limit`` is applied to the post-filter
525
+ # set (not a pre-filter slice) AND we can stop once the page is full.
526
+ if playbook_name is not None and first.playbook_name != playbook_name:
527
+ continue
528
+ if agent_version is not None and first.agent_version != agent_version:
529
+ continue
530
+
531
+ ts, _ = sort_key.get(req, (0, 0))
532
+ logs.append(
533
+ PlaybookAggregationChangeLog(
534
+ playbook_name=first.playbook_name,
535
+ agent_version=first.agent_version,
536
+ run_mode=run_mode_by_req.get(req, "incremental"),
537
+ added_agent_playbooks=added,
538
+ removed_agent_playbooks=removed,
539
+ updated_agent_playbooks=[],
540
+ created_at=ts,
541
+ )
542
+ )
543
+ if len(logs) >= limit: # limit > 0 guaranteed above
544
+ break
545
+
546
+ return PlaybookAggregationChangeLogResponse(success=True, change_logs=logs)
@@ -127,6 +127,21 @@ class ReflexioBase:
127
127
  raise RuntimeError(STORAGE_NOT_CONFIGURED_MSG)
128
128
  return storage
129
129
 
130
+ def get_storage(self) -> BaseStorage:
131
+ """Return the configured storage backend.
132
+
133
+ Public accessor for consumers (including enterprise) that need the
134
+ underlying ``BaseStorage``. Prefer ``request_context.storage`` inside
135
+ OSS pipeline code; this exists for callers that hold a ``Reflexio``.
136
+
137
+ Returns:
138
+ BaseStorage: The configured storage backend.
139
+
140
+ Raises:
141
+ RuntimeError: If storage is not configured.
142
+ """
143
+ return self._get_storage()
144
+
130
145
  def _get_query_reformulator(self) -> QueryReformulator:
131
146
  """Lazily create and cache a QueryReformulator instance.
132
147
 
@@ -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,29 @@ 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.extensions import get_service
42
+ from reflexio.server.services.playbook.aggregation_prompt_processing import (
43
+ AGGREGATION_PROMPT_PROCESSOR,
44
+ )
45
+ from reflexio.server.services.playbook.components.aggregator import (
42
46
  PlaybookAggregator,
43
47
  )
44
48
  from reflexio.server.services.playbook.playbook_service_utils import (
45
49
  PlaybookAggregatorRequest,
46
50
  )
47
51
 
52
+ aggregation_prompt_processor = get_service(AGGREGATION_PROMPT_PROCESSOR)
53
+ aggregator_kwargs = {}
54
+ if aggregation_prompt_processor is not None:
55
+ aggregator_kwargs["aggregation_prompt_processor"] = (
56
+ aggregation_prompt_processor
57
+ )
58
+
48
59
  playbook_aggregator = PlaybookAggregator(
49
60
  llm_client=self.llm_client,
50
61
  request_context=self.request_context,
51
62
  agent_version=agent_version,
63
+ **aggregator_kwargs,
52
64
  )
53
65
  aggregator_request = PlaybookAggregatorRequest(
54
66
  agent_version=agent_version,
@@ -88,7 +100,7 @@ class GenerationMixin(ReflexioBase):
88
100
  Returns:
89
101
  RerunProfileGenerationResponse: Response containing success status, message, and count of profiles generated
90
102
  """
91
- from reflexio.server.services.profile.profile_generation_service import (
103
+ from reflexio.server.services.profile.service import (
92
104
  ProfileGenerationService,
93
105
  )
94
106
 
@@ -113,7 +125,7 @@ class GenerationMixin(ReflexioBase):
113
125
  Returns:
114
126
  ManualProfileGenerationResponse: Response containing success status, message, and count of profiles generated
115
127
  """
116
- from reflexio.server.services.profile.profile_generation_service import (
128
+ from reflexio.server.services.profile.service import (
117
129
  ProfileGenerationService,
118
130
  )
119
131
 
@@ -138,7 +150,7 @@ class GenerationMixin(ReflexioBase):
138
150
  Returns:
139
151
  RerunPlaybookGenerationResponse: Response containing success status, message, and count of playbooks generated
140
152
  """
141
- from reflexio.server.services.playbook.playbook_generation_service import (
153
+ from reflexio.server.services.playbook.service import (
142
154
  PlaybookGenerationService,
143
155
  )
144
156
 
@@ -163,7 +175,7 @@ class GenerationMixin(ReflexioBase):
163
175
  Returns:
164
176
  ManualPlaybookGenerationResponse: Response containing success status, message, and count of playbooks generated
165
177
  """
166
- from reflexio.server.services.playbook.playbook_generation_service import (
178
+ from reflexio.server.services.playbook.service import (
167
179
  PlaybookGenerationService,
168
180
  )
169
181
 
@@ -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