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
@@ -1,27 +1,31 @@
1
1
  """Aggregator that composes hero, tiles, rule attribution, and distribution.
2
2
 
3
- The service does three reads (results, citations, playbook stats)
4
- and returns a GetEvaluationOverviewResponse. It's invoked from the FastAPI
5
- route handler; the storage is the same BaseStorage the rest of the server
6
- uses, so the same instance is reused via request_context.
3
+ The service bulk-loads the requested evaluation window plus related source,
4
+ citation, Braintrust, and optional shadow verdict rows, then returns a
5
+ GetEvaluationOverviewResponse. It's invoked from the FastAPI route handler; the
6
+ storage is the same BaseStorage the rest of the server uses, so the same
7
+ instance is reused via request_context.
7
8
  """
8
9
 
9
10
  from __future__ import annotations
10
11
 
12
+ import logging
13
+ import time
11
14
  from collections import defaultdict
12
15
  from collections.abc import Iterable
13
16
  from dataclasses import dataclass
14
17
  from datetime import UTC, datetime
18
+ from typing import Any
15
19
 
16
20
  from reflexio.models.api_schema.braintrust_schema import ImportedScore
17
21
  from reflexio.models.api_schema.domain.entities import (
18
22
  AgentSuccessEvaluationResult,
19
- Request,
20
23
  )
21
24
  from reflexio.models.api_schema.eval_overview_schema import (
22
25
  BraintrustTileRow,
23
26
  BucketLiteral,
24
27
  ContextTile,
28
+ EvaluationSourceSetRequest,
25
29
  GetEvaluationOverviewRequest,
26
30
  GetEvaluationOverviewResponse,
27
31
  HeroBlock,
@@ -31,29 +35,29 @@ from reflexio.models.api_schema.eval_overview_schema import (
31
35
  RuleAttributionRow,
32
36
  ScoreDistribution,
33
37
  ShadowWinRateTrend,
34
- SuccessRateTrendByGroup,
38
+ SourceSetComparison,
39
+ SourceSetEvaluationMetrics,
35
40
  )
36
41
  from reflexio.models.config_schema import Config
37
- from reflexio.server.services.evaluation_overview.distribution import (
42
+ from reflexio.server.services.evaluation_overview.components.distribution import (
38
43
  BUCKET_LABELS,
39
44
  bucket_corrections,
40
45
  )
41
- from reflexio.server.services.evaluation_overview.group_aggregation import (
42
- compute_trend_by_group,
43
- )
44
- from reflexio.server.services.evaluation_overview.hero_state import (
46
+ from reflexio.server.services.evaluation_overview.components.hero_state import (
45
47
  compute_hero_state,
46
48
  )
47
- from reflexio.server.services.evaluation_overview.rule_attribution import (
49
+ from reflexio.server.services.evaluation_overview.components.rule_attribution import (
48
50
  compute_net_sessions,
49
51
  )
50
- from reflexio.server.services.evaluation_overview.shadow_aggregation import (
52
+ from reflexio.server.services.evaluation_overview.components.shadow_aggregation import (
51
53
  compute_shadow_win_rate_trend,
52
54
  )
53
55
 
54
56
  _DAY_SECONDS = 24 * 60 * 60
55
57
  _WEEK_SECONDS = 7 * 24 * 60 * 60
56
58
  _TOP_N_RULES = 5
59
+ _LOGGER = logging.getLogger(__name__)
60
+ ResultKey = tuple[str, str]
57
61
 
58
62
 
59
63
  @dataclass
@@ -65,7 +69,7 @@ class EvaluationOverviewService:
65
69
  Stateless across calls — safe to reuse the instance across requests.
66
70
  """
67
71
 
68
- storage: object
72
+ storage: Any
69
73
  config: Config
70
74
 
71
75
  def run(
@@ -85,18 +89,25 @@ class EvaluationOverviewService:
85
89
  so every tile would display "no baseline" regardless of how much
86
90
  data the org has.
87
91
  """
88
- all_results = self.storage.get_agent_success_evaluation_results( # type: ignore[attr-defined]
89
- agent_version=None, limit=10_000
92
+ # Tile + distribution baselines are always last-7d-vs-prior-7d,
93
+ # anchored to ``request.to_ts`` (which is "now" from the frontend's
94
+ # perspective).
95
+ cur_7d_from = max(request.from_ts, request.to_ts - _WEEK_SECONDS)
96
+ prev_to = cur_7d_from
97
+ prev_from = max(0, prev_to - _WEEK_SECONDS)
98
+ load_from = min(request.from_ts, prev_from)
99
+
100
+ start = time.perf_counter()
101
+ all_results = self.storage.get_agent_success_evaluation_results_in_window( # type: ignore[attr-defined]
102
+ from_ts=load_from,
103
+ to_ts=request.to_ts,
104
+ agent_version=None,
90
105
  )
106
+ self._log_phase("eval_results", start, rows=len(all_results))
107
+
91
108
  results = [
92
109
  r for r in all_results if request.from_ts <= r.created_at <= request.to_ts
93
110
  ]
94
-
95
- # Tile + distribution baselines are always last-7d-vs-prior-7d, anchored
96
- # to ``request.to_ts`` (which is "now" from the frontend's perspective).
97
- cur_7d_from = max(request.from_ts, request.to_ts - _WEEK_SECONDS)
98
- prev_to = cur_7d_from
99
- prev_from = max(0, prev_to - _WEEK_SECONDS)
100
111
  results_current_7d = [
101
112
  r for r in all_results if cur_7d_from <= r.created_at <= request.to_ts
102
113
  ]
@@ -105,23 +116,76 @@ class EvaluationOverviewService:
105
116
  ]
106
117
 
107
118
  earliest_eval_ts = min((r.created_at for r in all_results), default=None)
119
+ result_keys = list(
120
+ dict.fromkeys((r.user_id, r.session_id) for r in results if r.session_id)
121
+ )
122
+ if request.source_sets:
123
+ source_keys = list(
124
+ dict.fromkeys(
125
+ (r.user_id, r.session_id)
126
+ for r in (*results, *results_current_7d, *results_prev_7d)
127
+ if r.session_id
128
+ )
129
+ )
130
+ else:
131
+ source_keys = result_keys
132
+ start = time.perf_counter()
133
+ session_sources = self._build_first_request_sources(source_keys)
134
+ self._log_phase(
135
+ "session_sources",
136
+ start,
137
+ sessions=len(set(source_keys)),
138
+ rows=len(session_sources),
139
+ )
140
+
141
+ start = time.perf_counter()
142
+ citations_by_session, rule_titles = self._load_citations(result_keys)
143
+ self._log_phase(
144
+ "citations",
145
+ start,
146
+ sessions=len(set(result_keys)),
147
+ rows=sum(len(v) for v in citations_by_session.values()),
148
+ )
149
+
108
150
  hero = self._build_hero(request, results, earliest_eval_ts)
109
151
  tiles = self._build_tiles(results_current_7d, results_prev_7d)
110
- attribution = self._build_attribution(results)
152
+ attribution = self._build_attribution(
153
+ results, citations_by_session, rule_titles
154
+ )
111
155
  distribution = self._build_distribution(results_current_7d, results_prev_7d)
112
- braintrust_tiles = self._build_braintrust_tiles(
156
+ start = time.perf_counter()
157
+ current_scores, prior_scores = self._load_braintrust_scores(
113
158
  cur_7d_from, request.to_ts, prev_from, prev_to
114
159
  )
115
- # F2: group-aware success-rate trend curves. Joins each eval result
116
- # with its session's first request metadata to bucket sessions into
117
- # treatment / control / untagged, then time-buckets each curve at
118
- # the same granularity as the hero chart.
119
- success_rate_trend_by_group = self._build_group_trend(results, request.bucket)
120
- # F1: per-turn shadow win-rate trend. Filters verdicts to the org's
121
- # pinned judge prompt version so a future rubric bump doesn't
122
- # silently mix epochs into the headline.
123
- shadow_win_rate_trend = self._build_shadow_win_rate_trend(
124
- request.from_ts, request.to_ts
160
+ self._log_phase(
161
+ "braintrust",
162
+ start,
163
+ current_rows=len(current_scores),
164
+ prior_rows=len(prior_scores),
165
+ )
166
+ braintrust_tiles = self._build_braintrust_tiles_from_scores(
167
+ current_scores, prior_scores
168
+ )
169
+ start = time.perf_counter()
170
+ shadow_win_rate_trend = (
171
+ self._build_shadow_win_rate_trend(request.from_ts, request.to_ts)
172
+ if request.include_shadow
173
+ else ShadowWinRateTrend(
174
+ judge_prompt_version=self.config.shadow_comparison_judge_prompt_version
175
+ )
176
+ )
177
+ self._log_phase("shadow", start, enabled=request.include_shadow)
178
+ source_set_comparison = self._build_source_set_comparison(
179
+ source_sets=request.source_sets,
180
+ results=results,
181
+ current=results_current_7d,
182
+ previous=results_prev_7d,
183
+ session_sources=session_sources,
184
+ bucket=request.bucket,
185
+ citations_by_session=citations_by_session,
186
+ rule_titles=rule_titles,
187
+ current_scores=current_scores,
188
+ prior_scores=prior_scores,
125
189
  )
126
190
 
127
191
  return GetEvaluationOverviewResponse(
@@ -130,8 +194,8 @@ class EvaluationOverviewService:
130
194
  rule_attribution=attribution,
131
195
  score_distribution=distribution,
132
196
  braintrust_tiles=braintrust_tiles,
133
- success_rate_trend_by_group=success_rate_trend_by_group,
134
197
  shadow_win_rate_trend=shadow_win_rate_trend,
198
+ source_set_comparison=source_set_comparison,
135
199
  )
136
200
 
137
201
  # --- private helpers ---
@@ -198,12 +262,14 @@ class EvaluationOverviewService:
198
262
  )
199
263
 
200
264
  def _build_attribution(
201
- self, results: list[AgentSuccessEvaluationResult]
265
+ self,
266
+ results: list[AgentSuccessEvaluationResult],
267
+ citations_by_session: dict[ResultKey, list[tuple[str, str]]],
268
+ rule_titles: dict[tuple[str, str], str],
202
269
  ) -> list[RuleAttributionRow]:
203
- is_success_by_session = {r.session_id: r.is_success for r in results}
204
- citations_by_session, rule_titles = self._load_citations(
205
- list(is_success_by_session.keys())
206
- )
270
+ is_success_by_session = {
271
+ (r.user_id, r.session_id): r.is_success for r in results
272
+ }
207
273
  rows = compute_net_sessions(
208
274
  citations_by_session=citations_by_session,
209
275
  is_success_by_session=is_success_by_session,
@@ -224,51 +290,57 @@ class EvaluationOverviewService:
224
290
  ]
225
291
 
226
292
  def _load_citations(
227
- self, session_ids: list[str]
228
- ) -> tuple[dict[str, list[tuple[str, str]]], dict[tuple[str, str], str]]:
229
- """Pull `Interaction.citations` keyed by session, with title lookup.
293
+ self, result_keys: list[ResultKey]
294
+ ) -> tuple[dict[ResultKey, list[tuple[str, str]]], dict[tuple[str, str], str]]:
295
+ """Pull `Interaction.citations` keyed by user/session, with title lookup.
230
296
 
231
297
  Falls back to empty data when the underlying storage method returns
232
298
  no interactions (default behavior on backends that haven't yet
233
299
  implemented `get_interactions_by_session`).
234
300
  """
235
- citations_by_session: dict[str, list[tuple[str, str]]] = defaultdict(list)
236
- for sid in session_ids:
237
- interactions = self.storage.get_interactions_by_session(sid) # type: ignore[attr-defined]
238
- for interaction in interactions:
239
- for cite in getattr(interaction, "citations", []) or []:
240
- # Citations may arrive as Pydantic Citation objects (from
241
- # the normal storage path) or as plain dicts (e.g. when
242
- # tests stub the storage). Handle both shapes.
243
- if isinstance(cite, dict):
244
- kind = cite.get("kind")
245
- rid = cite.get("real_id")
246
- else:
247
- kind = getattr(cite, "kind", None)
248
- rid = getattr(cite, "real_id", None)
249
- if kind and rid:
250
- citations_by_session[sid].append((kind, str(rid)))
251
- # Titles via existing playbook_application_stats lookup
252
- stats = self.storage.get_playbook_application_stats(days_back=30) # type: ignore[attr-defined]
253
- rule_titles = {(s.kind, s.real_id): s.title for s in stats}
301
+ wanted = set(result_keys)
302
+ session_ids = sorted({session_id for _, session_id in result_keys})
303
+ citations_by_session: dict[ResultKey, list[tuple[str, str]]] = defaultdict(list)
304
+ rule_titles: dict[tuple[str, str], str] = {}
305
+ for citation in self.storage.get_citations_by_session_ids(session_ids): # type: ignore[attr-defined]
306
+ result_key = (citation.user_id, citation.session_id)
307
+ if result_key not in wanted:
308
+ continue
309
+ key = (citation.kind, citation.real_id)
310
+ citations_by_session[result_key].append(key)
311
+ if citation.title and key not in rule_titles:
312
+ rule_titles[key] = citation.title
254
313
  return citations_by_session, rule_titles
255
314
 
256
- def _build_braintrust_tiles(
257
- self, from_ts: int, to_ts: int, prev_from: int, prev_to: int
258
- ) -> list[BraintrustTileRow]:
259
- """Aggregate imported_score rows per scorer_name for current + prior windows.
260
-
261
- Returns [] when the org has no Braintrust connection (default no-op
262
- storage returns []). The frontend treats an empty list as "not
263
- connected" and hides the Braintrust strip entirely.
264
- """
315
+ def _load_braintrust_scores(
316
+ self,
317
+ from_ts: int,
318
+ to_ts: int,
319
+ prev_from: int,
320
+ prev_to: int,
321
+ ) -> tuple[list[ImportedScore], list[ImportedScore]]:
265
322
  org_id = self._org_id()
266
323
  if not org_id:
267
- return []
324
+ return [], []
268
325
  current = self.storage.get_imported_scores(org_id, from_ts, to_ts) # type: ignore[attr-defined]
269
326
  if not current:
270
- return []
327
+ return [], []
271
328
  prior = self.storage.get_imported_scores(org_id, prev_from, prev_to) # type: ignore[attr-defined]
329
+ return current, prior
330
+
331
+ def _build_braintrust_tiles_from_scores(
332
+ self,
333
+ current: list[ImportedScore],
334
+ prior: list[ImportedScore],
335
+ session_ids: set[str] | None = None,
336
+ ) -> list[BraintrustTileRow]:
337
+ """Aggregate imported_score rows per scorer_name for current + prior windows."""
338
+ if session_ids is not None:
339
+ current = [s for s in current if s.session_id in session_ids]
340
+ if not current:
341
+ return []
342
+ if session_ids is not None:
343
+ prior = [s for s in prior if s.session_id in session_ids]
272
344
  cur_agg = _aggregate_imported_scores(current)
273
345
  prior_agg = _aggregate_imported_scores(prior)
274
346
  rows: list[BraintrustTileRow] = []
@@ -288,6 +360,81 @@ class EvaluationOverviewService:
288
360
  )
289
361
  return rows
290
362
 
363
+ def _build_source_set_comparison(
364
+ self,
365
+ *,
366
+ source_sets: list[EvaluationSourceSetRequest],
367
+ results: list[AgentSuccessEvaluationResult],
368
+ current: list[AgentSuccessEvaluationResult],
369
+ previous: list[AgentSuccessEvaluationResult],
370
+ session_sources: dict[ResultKey, str],
371
+ bucket: BucketLiteral,
372
+ citations_by_session: dict[ResultKey, list[tuple[str, str]]],
373
+ rule_titles: dict[tuple[str, str], str],
374
+ current_scores: list[ImportedScore],
375
+ prior_scores: list[ImportedScore],
376
+ ) -> SourceSetComparison:
377
+ """Build request-source cohort metrics for the evaluation page."""
378
+ available_sources = sorted(
379
+ {session_sources.get((r.user_id, r.session_id), "") for r in results}
380
+ )
381
+ if not source_sets:
382
+ return SourceSetComparison(available_sources=available_sources)
383
+
384
+ requested_sources = {source for s in source_sets for source in s.sources}
385
+ unmatched = sum(
386
+ 1
387
+ for r in results
388
+ if session_sources.get((r.user_id, r.session_id), "")
389
+ not in requested_sources
390
+ )
391
+ rows: list[SourceSetEvaluationMetrics] = []
392
+ for source_set in source_sets:
393
+ source_values = set(source_set.sources)
394
+ set_results = [
395
+ r
396
+ for r in results
397
+ if session_sources.get((r.user_id, r.session_id), "") in source_values
398
+ ]
399
+ set_current = [
400
+ r
401
+ for r in current
402
+ if session_sources.get((r.user_id, r.session_id), "") in source_values
403
+ ]
404
+ set_previous = [
405
+ r
406
+ for r in previous
407
+ if session_sources.get((r.user_id, r.session_id), "") in source_values
408
+ ]
409
+ session_ids = {r.session_id for r in set_results if r.session_id}
410
+ rows.append(
411
+ SourceSetEvaluationMetrics(
412
+ label=source_set.label,
413
+ sources=list(source_set.sources),
414
+ session_count=len(set_results),
415
+ session_ids=sorted(session_ids),
416
+ success_rate_pp=_success_rate(set_results) * 100,
417
+ buckets=_buckets(set_results, bucket),
418
+ context_tiles=self._build_tiles(set_current, set_previous),
419
+ score_distribution=self._build_distribution(
420
+ set_current, set_previous
421
+ ),
422
+ rule_attribution=self._build_attribution(
423
+ set_results, citations_by_session, rule_titles
424
+ ),
425
+ braintrust_tiles=self._build_braintrust_tiles_from_scores(
426
+ current_scores,
427
+ prior_scores,
428
+ session_ids=session_ids,
429
+ ),
430
+ )
431
+ )
432
+ return SourceSetComparison(
433
+ available_sources=available_sources,
434
+ sets=rows,
435
+ unmatched_session_count=unmatched,
436
+ )
437
+
291
438
  def _org_id(self) -> str:
292
439
  """Resolve org_id from request_context when available; else empty string."""
293
440
  # The service is constructed with `storage` + `config`; org_id isn't a
@@ -331,97 +478,14 @@ class EvaluationOverviewService:
331
478
  verdicts, judge_prompt_version=pinned_version
332
479
  )
333
480
 
334
- def _build_group_trend(
335
- self,
336
- results: list[AgentSuccessEvaluationResult],
337
- bucket: str,
338
- ) -> SuccessRateTrendByGroup:
339
- """Build the dual+untagged-curve trend payload for the window.
340
-
341
- Joins each eval result with the first request of its session to read
342
- ``metadata.reflexio_retrieval_enabled``, then delegates to the pure
343
- ``compute_trend_by_group`` aggregator. Returns the default empty
344
- ``SuccessRateTrendByGroup`` when ``results`` is empty.
345
-
346
- Args:
347
- results (list[AgentSuccessEvaluationResult]): Eval results in the
348
- trend window.
349
- bucket (str): Bucket granularity literal (``"week"`` or ``"day"``).
350
-
351
- Returns:
352
- SuccessRateTrendByGroup: Three curves (any of which may be empty).
353
- """
354
- if not results:
355
- return SuccessRateTrendByGroup()
356
- bucket_seconds = _WEEK_SECONDS if bucket == "week" else _DAY_SECONDS
357
- outcomes = self._build_group_outcomes(results)
358
- return compute_trend_by_group(outcomes, bucket_seconds)
359
-
360
- def _build_group_outcomes(
361
- self,
362
- results: list[AgentSuccessEvaluationResult],
363
- ) -> list[tuple[str, int, bool, dict]]:
364
- """Pair each eval result with its session's first-request metadata.
365
-
366
- Caches ``session_id → first_request_metadata`` so storage is hit
367
- once per distinct session, not once per result. Sessions without a
368
- matching request fall through with an empty-dict metadata, which the
369
- downstream aggregator routes to the UNTAGGED bucket.
370
-
371
- Args:
372
- results (list[AgentSuccessEvaluationResult]): Eval results to
373
- annotate with their session's first-request metadata.
374
-
375
- Returns:
376
- list[tuple[str, int, bool, dict]]: Tuples of
377
- ``(session_id, created_at, is_success, first_request_metadata)``
378
- suitable for :func:`compute_trend_by_group`.
379
- """
380
- session_ids = {r.session_id for r in results if r.session_id}
381
- first_request_metadata: dict[str, dict] = {}
382
- for sid in session_ids:
383
- reqs = self._get_session_requests(sid)
384
- if reqs:
385
- first = min(reqs, key=lambda r: r.created_at)
386
- first_request_metadata[sid] = first.metadata or {}
387
- else:
388
- first_request_metadata[sid] = {}
389
-
390
- return [
391
- (
392
- r.session_id or "",
393
- r.created_at,
394
- r.is_success,
395
- first_request_metadata.get(r.session_id or "", {}),
396
- )
397
- for r in results
398
- ]
399
-
400
- def _get_session_requests(self, session_id: str) -> list[Request]:
401
- """Return every request in ``session_id`` regardless of user.
402
-
403
- Uses ``BaseStorage.get_sessions(session_id=...)`` because the
404
- per-session, user-id-required ``get_requests_by_session`` doesn't
405
- fit our caller (eval results don't carry ``user_id``). All locally
406
- testable backends ignore the ``user_id`` filter when it's ``None``,
407
- and ``top_k`` is raised well above realistic per-session counts so
408
- we don't truncate.
409
-
410
- Args:
411
- session_id (str): The session whose requests to fetch.
412
-
413
- Returns:
414
- list[Request]: All requests in the session, in storage order.
415
- """
416
- grouped = self.storage.get_sessions( # type: ignore[attr-defined]
417
- session_id=session_id, top_k=1000
418
- )
419
- return [
420
- entry.request
421
- for entries in grouped.values()
422
- for entry in entries
423
- if entry.request is not None
424
- ]
481
+ def _build_first_request_sources(
482
+ self, result_keys: list[ResultKey]
483
+ ) -> dict[ResultKey, str]:
484
+ """Map each user/session slice to its earliest request's source."""
485
+ first_requests = self.storage.get_first_requests_by_user_session_pairs(
486
+ result_keys
487
+ ) # type: ignore[attr-defined]
488
+ return {key: row.source or "" for key, row in first_requests.items()}
425
489
 
426
490
  def _build_distribution(
427
491
  self,
@@ -440,6 +504,16 @@ class EvaluationOverviewService:
440
504
  labels=list(BUCKET_LABELS),
441
505
  )
442
506
 
507
+ def _log_phase(self, phase: str, started_at: float, **metadata: object) -> None:
508
+ duration_ms = int((time.perf_counter() - started_at) * 1000)
509
+ fields = " ".join(f"{key}={value}" for key, value in metadata.items())
510
+ _LOGGER.info(
511
+ "evaluation_overview phase=%s duration_ms=%d %s",
512
+ phase,
513
+ duration_ms,
514
+ fields,
515
+ )
516
+
443
517
 
444
518
  # --- module-level helpers (pure) ---
445
519
 
@@ -0,0 +1,31 @@
1
+ # services/extraction
2
+
3
+ Shared async extraction runtime for profile and playbook pipelines.
4
+
5
+ This package is intentionally domain-neutral. Profile and playbook modules own
6
+ their prompts, schemas, extractor semantics, and storage decisions; this package
7
+ owns the reusable runtime needed when extraction pauses, waits for more
8
+ information, and resumes outside the request path.
9
+
10
+ ## Files
11
+
12
+ | File | Responsibility |
13
+ |------|----------------|
14
+ | `resumable_agent.py` | Runs extraction agents, records agent usage, injects resolved pending-tool context, and exposes pending tool-call helpers. |
15
+ | `pending_tool_call_dispatch.py` | Implements the `ask_human` and pending-info tool dispatch flow. |
16
+ | `prior_answer_search.py` | Finds and formats previous human answers for async extraction context. |
17
+ | `agent_run_records.py` | Builds durable extraction-agent run records and source interaction identity. |
18
+ | `resume_scheduler.py` | Schedules due paused extraction runs in a background singleton. |
19
+ | `resume_worker.py` | Resumes paused runs, rebuilds request context, and records retry state. |
20
+ | `outcome.py` | Provides the generic extraction outcome wrapper used by callers. |
21
+
22
+ ## Boundary Rules
23
+
24
+ - Keep profile-specific and playbook-specific extraction behavior in their own
25
+ modules; call this package only for shared async runtime concerns.
26
+ - Add a new file here when the behavior is shared by more than one extraction
27
+ caller or is part of the resumable runtime itself.
28
+ - Split into subpackages only when a responsibility grows large enough that a
29
+ flat package stops being easier to scan.
30
+ - Do not recreate removed legacy modules such as `tools.py`, `plan.py`, or
31
+ `invariants.py`; use the current focused files above.
@@ -3,7 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass, field
6
- from typing import Literal
6
+ from typing import TYPE_CHECKING, Literal
7
+
8
+ if TYPE_CHECKING:
9
+ from reflexio.server.llm.token_accounting import RunTokenTotals
7
10
 
8
11
 
9
12
  @dataclass(frozen=True)
@@ -13,12 +16,19 @@ class ExtractionOutcome[T]:
13
16
  status: Literal["completed", "empty"]
14
17
  items: list[T] = field(default_factory=list)
15
18
  run_id: str | None = None
19
+ token_totals: RunTokenTotals | None = None
16
20
 
17
21
  @classmethod
18
22
  def completed(
19
- cls, items: list[T], *, run_id: str | None = None
23
+ cls,
24
+ items: list[T],
25
+ *,
26
+ run_id: str | None = None,
27
+ token_totals: RunTokenTotals | None = None,
20
28
  ) -> ExtractionOutcome[T]:
21
- return cls(status="completed", items=items, run_id=run_id)
29
+ return cls(
30
+ status="completed", items=items, run_id=run_id, token_totals=token_totals
31
+ )
22
32
 
23
33
  @classmethod
24
34
  def empty(cls, *, run_id: str | None = None) -> ExtractionOutcome[T]:
@@ -11,6 +11,7 @@ from typing import Annotated, Any
11
11
  from pydantic import BaseModel, Field, field_validator
12
12
 
13
13
  from reflexio.models.config_schema import PendingToolCallConfig
14
+ from reflexio.models.structured_output import StrictStructuredOutput
14
15
  from reflexio.server.llm.tools import (
15
16
  AsyncAccepted,
16
17
  AsyncInfoTool,
@@ -31,7 +32,7 @@ from reflexio.server.usage_metrics import record_usage_event
31
32
  logger = logging.getLogger(__name__)
32
33
 
33
34
 
34
- class AskHumanArgs(BaseModel):
35
+ class AskHumanArgs(StrictStructuredOutput):
35
36
  """Ask Agent Builder for missing extraction information and continue now."""
36
37
 
37
38
  question: Annotated[str, Field(min_length=1)]
@@ -46,7 +47,7 @@ class AskHumanArgs(BaseModel):
46
47
  return value
47
48
 
48
49
 
49
- class AttachPendingInfoRequestArgs(BaseModel):
50
+ class AttachPendingInfoRequestArgs(StrictStructuredOutput):
50
51
  """Attach this run to a relevant pending Prior Knowledge request."""
51
52
 
52
53
  pending_tool_call_id: Annotated[str, Field(min_length=1)]