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,196 @@
1
+ """Support helpers for openclaw-smart citation tracking.
2
+
3
+ Context injected by before_prompt_build / before_tool_call tags each skill
4
+ and preference bullet with a rank-based id fingerprinted by the underlying
5
+ real id (``[oc:s1-1a2b]`` for the first skill whose ``user_playbook_id``
6
+ starts with ``1a2b``, ``[oc:p2-c3d4]`` for the second preference). The
7
+ injected instruction asks the assistant to end impactful replies with a
8
+ marker like::
9
+
10
+ ✨ 1 openclaw-smart learning applied [oc:s1-1a2b]
11
+
12
+ The agent_end hook later scans the assistant text for those markers and
13
+ resolves the ids against a per-session registry persisted at
14
+ ``~/.openclaw-smart/sessions/<session_id>.injected.jsonl``. Legacy
15
+ ``oc-cite`` Bash tool calls are still accepted as a fallback for older
16
+ instructions.
17
+
18
+ Why rank + fingerprint: rank alone resets at every injection, so a later
19
+ injection's ``s1`` would silently overwrite an earlier entry in the
20
+ append-only registry — if openClaw cited ``s1`` across a turn boundary,
21
+ the resolver would pick the wrong skill. Appending the first four
22
+ alphanumeric chars of the real id makes the id stable across injections
23
+ in the common case (distinct real ids → distinct fingerprints), so
24
+ cross-injection collisions become rare.
25
+
26
+ This module holds:
27
+
28
+ - ``rank_id``: ``p{n}-{fp}`` / ``s{n}-{fp}`` tag for a given (kind, rank,
29
+ real_id) tuple. Fingerprint is omitted when no real id is available.
30
+ ``p`` is preference, ``s`` is skill.
31
+ - ``CITATION_CMD_RE``: regex matching a valid legacy ``oc-cite`` command line.
32
+ - ``ensure_installed``: idempotent copy of ``plugin/bin/oc-cite`` to
33
+ ``~/.openclaw-smart/bin/oc-cite`` with the executable bit set.
34
+ - ``CITATION_INSTRUCTION``: the trailer text appended to injected context
35
+ so the assistant knows when and how to emit the citation marker.
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import logging
41
+ import re
42
+ import shutil
43
+ import stat as stat_
44
+ from pathlib import Path
45
+ from typing import Any
46
+
47
+ _LOGGER = logging.getLogger(__name__)
48
+
49
+ # plugin/src/openclaw_smart/oc_cite.py -> parents[2] is plugin/
50
+ _THIS_DIR = Path(__file__).resolve().parent
51
+ _PLUGIN_ROOT = _THIS_DIR.parents[2]
52
+ _SOURCE_SCRIPT = _PLUGIN_ROOT / "bin" / "oc-cite"
53
+ _INSTALL_DIR = Path.home() / ".openclaw-smart" / "bin"
54
+ INSTALL_PATH = _INSTALL_DIR / "oc-cite"
55
+
56
+ _FINGERPRINT_LEN = 4
57
+
58
+ # Match a bare `oc-cite <ids>` invocation. Ids are rank tokens of the form
59
+ # `p<N>` (preference) or `s<N>` (skill) with an optional `-<fp>`
60
+ # fingerprint (1-4 alphanumeric chars), optionally `oc:`-prefixed (since
61
+ # bullets render as `[oc:p1-ab12]` and the model often copies the tag
62
+ # verbatim). The `(?i:...)` inline flags make the prefix, kind letter,
63
+ # and fingerprint case-insensitive so `OC:P1-AB12` is accepted. Tokens
64
+ # may be comma- and/or whitespace-separated. Chained commands (&&, |, ;)
65
+ # and extra trailing tokens remain rejected by the anchored `\s*$`
66
+ # terminator so accidental mentions don't register as citations.
67
+ _ID_TOKEN = r"(?i:oc:)?(?i:[ps])\d+(?:-(?i:[a-z0-9]){1,4})?" # noqa: S105 — regex pattern, not a secret.
68
+ _ID_SEP = r"[,\s]+"
69
+ CITATION_CMD_RE = re.compile(
70
+ rf"^\s*(?:[^\s]*/)?oc-cite\s+({_ID_TOKEN}(?:{_ID_SEP}{_ID_TOKEN})*)\s*$"
71
+ )
72
+ _CLEAN_ID_RE = re.compile(r"^(?i:oc:)?((?i:[ps])\d+(?:-(?i:[a-z0-9]){1,4})?)$")
73
+ _SPLIT_RE = re.compile(_ID_SEP)
74
+ _TEXT_CITATION_LINE_RE = re.compile(
75
+ r"(?im)^\s*✨\s+\d+\s+openclaw-smart learning(?:s)? applied\s+"
76
+ r"\[oc:(?P<ids>[^\]]+)\]\s*$"
77
+ )
78
+
79
+ CITATION_INSTRUCTION = (
80
+ "_First, fully answer the user — citation does not change what or how "
81
+ "you reply. Then, as a final step, consider whether to cite: if — and "
82
+ "only if — an injected `[oc:…]` item materially changed your reply "
83
+ "(different wording, action, or conclusion than you would have produced "
84
+ "without it), append exactly one final citation line after your answer. "
85
+ "Do not call `oc-cite` or any other tool for citations. Ids come verbatim "
86
+ "from the `[oc:…]` tags — keep the leading `p` (preference) or `s` "
87
+ "(skill) and the `-<fp>` suffix. Use this exact format for one id: "
88
+ "`✨ 1 openclaw-smart learning applied [oc:s1-ab12]`. Use this exact "
89
+ "format for multiple ids: `✨ 2 openclaw-smart learnings applied "
90
+ "[oc:s1-ab12,p2-cd34]`, where the number is the count of ids in the "
91
+ "brackets. Never emit a standalone wrapper like `✨s1-ab12✨` or "
92
+ "`✨abc123✨`; those are not openclaw-smart citations and cannot be "
93
+ "resolved. Default is to skip. If an item is merely on-topic, confirms "
94
+ "what you already planned, or your reply would read the same without it, "
95
+ "do not cite — end the turn normally with your reply. When unsure, skip. "
96
+ "Do not add any other text, tool calls, or role markers after the final "
97
+ "citation line._"
98
+ )
99
+
100
+
101
+ def _fingerprint(real_id: Any) -> str:
102
+ """Return the first ``_FINGERPRINT_LEN`` alphanumeric chars of ``real_id``."""
103
+ if real_id is None:
104
+ return ""
105
+ return "".join(c for c in str(real_id).lower() if c.isalnum())[:_FINGERPRINT_LEN]
106
+
107
+
108
+ def rank_id(kind: str, rank: int, real_id: Any = None) -> str:
109
+ """Return the citation id for a skill or preference item.
110
+
111
+ Format is ``{letter}{rank}-{fingerprint}`` where ``letter`` is ``p`` for
112
+ preferences and ``s`` for skills, ``rank`` is the 1-based position within
113
+ the current retrieval batch, and ``fingerprint`` is up to 4 alphanumeric
114
+ chars derived from ``real_id``. The fingerprint is omitted when no real
115
+ id is available (falling back to the rank form ``s1`` / ``p1``).
116
+
117
+ Raises:
118
+ ValueError: If ``kind`` is not ``"profile"`` or ``"playbook"``.
119
+ """
120
+ if kind == "profile":
121
+ prefix = "p"
122
+ elif kind == "playbook":
123
+ prefix = "s"
124
+ else:
125
+ raise ValueError(f"unknown citation kind: {kind!r}")
126
+ fp = _fingerprint(real_id)
127
+ return f"{prefix}{rank}-{fp}" if fp else f"{prefix}{rank}"
128
+
129
+
130
+ def parse_citation_command(command: str) -> list[str]:
131
+ """Extract citation ids from an ``oc-cite`` Bash command string.
132
+
133
+ Returns an empty list when the command does not match the expected shape
134
+ (chained commands, extra arguments, or anything other than a bare
135
+ ``oc-cite <ids>`` invocation are rejected to avoid false positives from
136
+ accidental mentions).
137
+ """
138
+ match = CITATION_CMD_RE.match(command or "")
139
+ if not match:
140
+ return []
141
+ return _parse_id_tokens(match.group(1))
142
+
143
+
144
+ def parse_text_citations(text: str) -> list[str]:
145
+ """Extract text-only citation ids from a final learning marker line.
146
+
147
+ The parser intentionally only accepts lines containing the visual
148
+ ``openclaw-smart learning(s) applied`` marker, so ordinary references to
149
+ injected ``[oc:...]`` ids inside an answer do not count as citations.
150
+ When multiple matching lines exist, the last one wins because the
151
+ instruction requires the citation marker to be final.
152
+ """
153
+ matches = list(_TEXT_CITATION_LINE_RE.finditer(text or ""))
154
+ if not matches:
155
+ return []
156
+ return _parse_id_tokens(matches[-1].group("ids"))
157
+
158
+
159
+ def _parse_id_tokens(raw_ids: str) -> list[str]:
160
+ return [
161
+ clean.group(1).lower()
162
+ for tok in _SPLIT_RE.split(raw_ids.strip())
163
+ if (clean := _CLEAN_ID_RE.match(tok))
164
+ ]
165
+
166
+
167
+ def ensure_installed() -> Path:
168
+ """Idempotently install ``oc-cite`` into ``~/.openclaw-smart/bin/``.
169
+
170
+ Called from every before_tool_call / before_prompt_build inject, so we
171
+ short-circuit when the target file already exists with the executable
172
+ bit set — the steady-state path is one ``stat`` syscall instead of
173
+ mkdir + copy + stat + chmod. Keying on filesystem state (rather than a
174
+ module-level boolean) keeps test isolation working when tests
175
+ monkeypatch ``INSTALL_PATH`` to a fresh tmpdir.
176
+
177
+ Never raises — filesystem errors are logged at DEBUG and the caller
178
+ proceeds with injection regardless (the citation feature degrades to
179
+ silent if the script is unreachable).
180
+ """
181
+ try:
182
+ if (
183
+ INSTALL_PATH.is_file()
184
+ and INSTALL_PATH.stat().st_mode & stat_.S_IXUSR
185
+ and _SOURCE_SCRIPT.is_file()
186
+ and INSTALL_PATH.read_bytes() == _SOURCE_SCRIPT.read_bytes()
187
+ ):
188
+ return INSTALL_PATH
189
+ _INSTALL_DIR.mkdir(parents=True, exist_ok=True)
190
+ if _SOURCE_SCRIPT.is_file():
191
+ shutil.copy2(_SOURCE_SCRIPT, INSTALL_PATH)
192
+ mode = INSTALL_PATH.stat().st_mode
193
+ INSTALL_PATH.chmod(mode | stat_.S_IXUSR | stat_.S_IXGRP | stat_.S_IXOTH)
194
+ except OSError as exc:
195
+ _LOGGER.debug("oc-cite install failed: %s", exc)
196
+ return INSTALL_PATH
@@ -0,0 +1,272 @@
1
+ """Local-script assistant backend for Reflexio playbook optimization.
2
+
3
+ Reflexio's ``LocalScriptAssistant`` sends one JSON payload on stdin and expects
4
+ one JSON object on stdout. This module bridges that protocol to a guarded
5
+ ``openclaw infer model run`` subprocess so candidate playbooks can be evaluated
6
+ against the local model without re-entering openclaw-smart hooks.
7
+
8
+ Compared to claude-smart's variant, openClaw's ``infer model run`` is a one-shot
9
+ completion (not a full agent with tool access), so we cannot grant read-only
10
+ tool calls during evaluation. The optimizer therefore evaluates rules purely
11
+ based on the prompt context. ``OPENCLAW_SMART_INTERNAL=1`` is set on the
12
+ subprocess env to short-circuit our own hooks (recursion guard).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os
19
+ import shutil
20
+ import subprocess # noqa: S404 — subprocess is the integration point.
21
+ import sys
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ _CLI_TIMEOUT_SECONDS = 300
26
+ ENV_CLI_PATH = "OPENCLAW_BIN"
27
+ ENV_DEFAULT_MODEL = "OPENCLAW_DEFAULT_MODEL"
28
+ ENV_TIMEOUT = "OPENCLAW_OPTIMIZER_TIMEOUT"
29
+
30
+
31
+ class OptimizerAssistantError(Exception):
32
+ """Raised for any local assistant protocol or openClaw CLI failure."""
33
+
34
+
35
+ def main() -> int:
36
+ """Console-script entrypoint for ``openclaw-smart-optimizer-assistant``.
37
+
38
+ Returns:
39
+ int: Exit code (0 on success, 1 on any error).
40
+ """
41
+ try:
42
+ payload = _read_payload()
43
+ messages = _validated_list(payload, "messages")
44
+ playbooks = _validated_list(payload, "playbooks")
45
+ prompt, system_prompt = _build_prompt(messages, playbooks)
46
+ content = _run_openclaw_cli(prompt=prompt, system_prompt=system_prompt)
47
+ except Exception as exc: # noqa: BLE001 — script errors become LocalScript failures.
48
+ sys.stderr.write(f"{type(exc).__name__}: {exc}\n")
49
+ return 1
50
+
51
+ json.dump({"content": content}, sys.stdout, ensure_ascii=False)
52
+ sys.stdout.write("\n")
53
+ return 0
54
+
55
+
56
+ def _read_payload() -> dict[str, Any]:
57
+ """Read and parse a JSON payload from stdin.
58
+
59
+ Returns:
60
+ dict[str, Any]: Parsed payload.
61
+
62
+ Raises:
63
+ OptimizerAssistantError: If stdin is not valid JSON or not an object.
64
+ """
65
+ raw = sys.stdin.read()
66
+ try:
67
+ payload = json.loads(raw)
68
+ except json.JSONDecodeError as exc:
69
+ raise OptimizerAssistantError("stdin must be a JSON object") from exc
70
+ if not isinstance(payload, dict):
71
+ raise OptimizerAssistantError("stdin must be a JSON object")
72
+ return payload
73
+
74
+
75
+ def _validated_list(payload: dict[str, Any], field: str) -> list[Any]:
76
+ """Return ``payload[field]`` as a list, raising if missing or wrong type."""
77
+ value = payload.get(field)
78
+ if not isinstance(value, list):
79
+ raise OptimizerAssistantError(f"payload.{field} must be a list")
80
+ return value
81
+
82
+
83
+ def _build_prompt(messages: list[Any], playbooks: list[Any]) -> tuple[str, str]:
84
+ """Build ``(prompt, system_prompt)`` from the optimizer payload.
85
+
86
+ Args:
87
+ messages (list[Any]): Conversation messages from the optimizer.
88
+ playbooks (list[Any]): Candidate playbook rules to evaluate.
89
+
90
+ Returns:
91
+ tuple[str, str]: ``(prompt, system_prompt)`` for the CLI call.
92
+
93
+ Raises:
94
+ OptimizerAssistantError: If messages contain no content.
95
+ """
96
+ normalized = [_normalize_message(message) for message in messages]
97
+ normalized = [message for message in normalized if message["content"]]
98
+ if not normalized:
99
+ raise OptimizerAssistantError("payload.messages must contain content")
100
+
101
+ final_message = normalized[-1]
102
+ prior_messages = normalized[:-1]
103
+
104
+ system_sections = [_render_playbooks(playbooks)]
105
+ existing_system = [
106
+ message["content"] for message in normalized if message["role"] == "system"
107
+ ]
108
+ if existing_system:
109
+ system_sections.append(
110
+ "## Existing system context\n" + "\n\n".join(existing_system)
111
+ )
112
+
113
+ prior_dialogue = [
114
+ message
115
+ for message in prior_messages
116
+ if message["role"] in {"user", "assistant"}
117
+ ]
118
+ if prior_dialogue:
119
+ system_sections.append(
120
+ "## Conversation so far\n" + _render_transcript(prior_dialogue)
121
+ )
122
+
123
+ prompt = final_message["content"]
124
+ if final_message["role"] != "user":
125
+ prompt = _render_transcript([final_message])
126
+ system_prompt = "\n\n".join(section for section in system_sections if section)
127
+ return prompt, system_prompt
128
+
129
+
130
+ def _normalize_message(message: Any) -> dict[str, str]:
131
+ """Coerce a raw message dict to ``{role, content}`` with safe defaults."""
132
+ if not isinstance(message, dict):
133
+ raise OptimizerAssistantError("each message must be an object")
134
+ role = str(message.get("role") or "user").strip().lower()
135
+ if role not in {"user", "assistant", "system"}:
136
+ role = "user"
137
+ content = message.get("content")
138
+ if not isinstance(content, str):
139
+ raise OptimizerAssistantError("each message.content must be a string")
140
+ return {"role": role, "content": content.strip()}
141
+
142
+
143
+ def _render_playbooks(playbooks: list[Any]) -> str:
144
+ """Render the candidate playbook rules as a markdown section."""
145
+ if not playbooks:
146
+ return ""
147
+ lines = ["## Candidate playbook rules"]
148
+ for index, playbook in enumerate(playbooks, start=1):
149
+ if not isinstance(playbook, dict):
150
+ raise OptimizerAssistantError("each playbook must be an object")
151
+ content = playbook.get("content")
152
+ if not isinstance(content, str) or not content.strip():
153
+ raise OptimizerAssistantError("each playbook.content must be a string")
154
+ trigger = playbook.get("trigger")
155
+ suffix = ""
156
+ if isinstance(trigger, str) and trigger.strip():
157
+ suffix = f" (when: {trigger.strip()})"
158
+ lines.append(f"{index}. {content.strip()}{suffix}")
159
+ return "\n".join(lines)
160
+
161
+
162
+ def _render_transcript(messages: list[dict[str, str]]) -> str:
163
+ """Render a list of normalized messages as a ``Role: content`` transcript."""
164
+ labels = {"user": "User", "assistant": "Assistant", "system": "System"}
165
+ return "\n\n".join(
166
+ f"{labels.get(message['role'], 'User')}: {message['content']}"
167
+ for message in messages
168
+ )
169
+
170
+
171
+ def _resolve_cli_path() -> str | None:
172
+ """Return the openclaw binary path, honoring ``OPENCLAW_BIN`` then PATH."""
173
+ override = os.environ.get(ENV_CLI_PATH)
174
+ if override and Path(override).is_file() and os.access(override, os.X_OK):
175
+ return override
176
+ return shutil.which("openclaw")
177
+
178
+
179
+ def _run_openclaw_cli(*, prompt: str, system_prompt: str) -> str:
180
+ """Invoke ``openclaw infer model run`` and return the completion text.
181
+
182
+ Args:
183
+ prompt (str): User-facing portion of the prompt.
184
+ system_prompt (str): System context (playbook rules + transcript)
185
+ concatenated into the same prompt — openclaw does not have a
186
+ separate ``--system-prompt`` flag.
187
+
188
+ Returns:
189
+ str: Completion text extracted from the CLI's JSON stdout.
190
+
191
+ Raises:
192
+ OptimizerAssistantError: On missing CLI, timeout, non-zero exit, or
193
+ unparseable JSON output.
194
+ """
195
+ cli = _resolve_cli_path()
196
+ if not cli:
197
+ raise OptimizerAssistantError("openclaw CLI not found; set OPENCLAW_BIN")
198
+
199
+ full_prompt = _join_prompt(prompt=prompt, system_prompt=system_prompt)
200
+ argv = [cli, "infer", "model", "run", "--prompt", full_prompt, "--json"]
201
+ model = os.environ.get(ENV_DEFAULT_MODEL)
202
+ if model:
203
+ argv += ["--model", model]
204
+
205
+ env = os.environ.copy()
206
+ env["OPENCLAW_SMART_INTERNAL"] = "1"
207
+ timeout_s = int(os.environ.get(ENV_TIMEOUT, str(_CLI_TIMEOUT_SECONDS)))
208
+
209
+ try:
210
+ proc = subprocess.run( # noqa: S603 — argv is fully constructed.
211
+ argv,
212
+ env=env,
213
+ capture_output=True,
214
+ text=True,
215
+ timeout=timeout_s,
216
+ check=False,
217
+ )
218
+ except subprocess.TimeoutExpired as exc:
219
+ raise OptimizerAssistantError(
220
+ f"openclaw CLI timed out after {timeout_s}s"
221
+ ) from exc
222
+ except FileNotFoundError as exc:
223
+ raise OptimizerAssistantError("openclaw CLI not found on PATH") from exc
224
+
225
+ if proc.returncode != 0:
226
+ stderr = proc.stderr.strip()
227
+ raise OptimizerAssistantError(
228
+ f"openclaw CLI exited {proc.returncode}: {stderr[:500]}"
229
+ )
230
+
231
+ try:
232
+ data = json.loads(proc.stdout)
233
+ except json.JSONDecodeError as exc:
234
+ raise OptimizerAssistantError(
235
+ f"openclaw CLI returned non-JSON: {proc.stdout[:200]}"
236
+ ) from exc
237
+ if not isinstance(data, dict):
238
+ raise OptimizerAssistantError("openclaw CLI JSON output must be an object")
239
+
240
+ content = _extract_content(data)
241
+ if not content:
242
+ raise OptimizerAssistantError(
243
+ f"openclaw CLI JSON had no completion text: {list(data)[:5]}"
244
+ )
245
+ return content
246
+
247
+
248
+ def _join_prompt(*, prompt: str, system_prompt: str) -> str:
249
+ """Concatenate system context above the user prompt."""
250
+ if not system_prompt:
251
+ return prompt
252
+ return f"{system_prompt}\n\n## Task\n{prompt}"
253
+
254
+
255
+ def _extract_content(payload: dict[str, Any]) -> str:
256
+ """Pull the completion text from openclaw's JSON output.
257
+
258
+ The exact field name in ``openclaw infer model run --json`` output is not
259
+ pinned at design time; scan common keys in priority order.
260
+ """
261
+ for key in ("text", "content", "output", "result", "response"):
262
+ value = payload.get(key)
263
+ if isinstance(value, str) and value:
264
+ return value
265
+ msg = payload.get("message")
266
+ if isinstance(msg, dict) and isinstance(msg.get("content"), str):
267
+ return msg["content"]
268
+ return ""
269
+
270
+
271
+ if __name__ == "__main__":
272
+ raise SystemExit(main())
@@ -0,0 +1,96 @@
1
+ """Publish-to-reflexio orchestration used by agent_end, session_end, and the sync CLI.
2
+
3
+ One helper — ``publish_unpublished`` — owns the read-buffer → slice → publish →
4
+ stamp-watermark sequence so the three call sites stay in sync. Returns a
5
+ ``(status, interaction_count)`` tuple so callers can format appropriate
6
+ messaging without peeking at the adapter.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import contextlib
12
+ from collections.abc import Iterator
13
+ from typing import Literal
14
+
15
+ from openclaw_smart import state
16
+ from openclaw_smart.reflexio_adapter import Adapter
17
+
18
+ try:
19
+ import fcntl # POSIX only — Windows falls back to no publish lock.
20
+ except ImportError: # pragma: no cover — non-POSIX platforms
21
+ fcntl = None # type: ignore[assignment]
22
+
23
+ PublishStatus = Literal["nothing", "ok", "failed"]
24
+
25
+
26
+ @contextlib.contextmanager
27
+ def _session_publish_lock(session_id: str) -> Iterator[None]:
28
+ """Serialize read-publish-watermark for one session buffer."""
29
+ lock_path = state.publish_lock_path(session_id)
30
+ if lock_path is None or fcntl is None:
31
+ yield
32
+ return
33
+ lock_path.parent.mkdir(parents=True, exist_ok=True)
34
+ with lock_path.open("a", encoding="utf-8") as fh:
35
+ fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
36
+ try:
37
+ yield
38
+ finally:
39
+ fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
40
+
41
+
42
+ def publish_unpublished(
43
+ *,
44
+ session_id: str,
45
+ project_id: str,
46
+ force_extraction: bool,
47
+ skip_aggregation: bool,
48
+ adapter: Adapter | None = None,
49
+ ) -> tuple[PublishStatus, int]:
50
+ """Drain the session buffer to reflexio and stamp the high-water mark.
51
+
52
+ Args:
53
+ session_id (str): openClaw session id (``sessionKey``), attached to
54
+ each interaction.
55
+ project_id (str): Stable project name; used as reflexio's ``user_id``
56
+ (preferences) so preferences accumulate at the project level
57
+ across sessions. ``agent_version`` is hardcoded to ``"openclaw"``
58
+ in the adapter so skills roll up globally per agent rather than
59
+ per project.
60
+ force_extraction (bool): Whether to ask reflexio to run extraction
61
+ synchronously instead of queuing for the next sweep.
62
+ skip_aggregation (bool): When True, reflexio extracts preferences and
63
+ raw project-specific skill entries but skips the rollup into
64
+ shared skills. openclaw-smart passes False on every publish path
65
+ so ``user_playbooks`` roll up into ``agent_playbooks``;
66
+ aggregation additionally requires ``aggregation_config`` to be
67
+ set on reflexio's ``user_playbook_extractor_configs[0]`` and
68
+ ``optimize_agent_playbooks=true`` at the top level — otherwise
69
+ the rollup silently no-ops.
70
+ adapter (Adapter | None): Injection point for tests; a fresh
71
+ ``Adapter()`` is constructed when omitted.
72
+
73
+ Returns:
74
+ tuple[PublishStatus, int]: ``("nothing", 0)`` if the buffer has no
75
+ unpublished turns, ``("ok", n)`` after a successful publish of
76
+ ``n`` interactions, or ``("failed", n)`` if reflexio rejected or
77
+ was unreachable. On ``"failed"`` the watermark is not advanced,
78
+ so the next hook retries the same batch.
79
+ """
80
+ with _session_publish_lock(session_id):
81
+ records = state.read_all(session_id)
82
+ _, interactions = state.unpublished_slice(records)
83
+ if not interactions:
84
+ return ("nothing", 0)
85
+ client = adapter if adapter is not None else Adapter()
86
+ ok = client.publish(
87
+ session_id=session_id,
88
+ project_id=project_id,
89
+ interactions=interactions,
90
+ force_extraction=force_extraction,
91
+ skip_aggregation=skip_aggregation,
92
+ )
93
+ if ok:
94
+ state.append(session_id, {"published_up_to": len(records)})
95
+ return ("ok", len(interactions))
96
+ return ("failed", len(interactions))
@@ -0,0 +1,66 @@
1
+ """Compose a reflexio search query from a before_tool_call payload.
2
+
3
+ Deterministic — no LLM call — so the before_tool_call hook can stay inside
4
+ its latency budget. The output is fed to ``ReflexioClient.search(query=...)``
5
+ (the unified ``/api/search`` endpoint, which fans out to user playbooks,
6
+ agent playbooks, and preferences server-side), which tokenizes via reflexio's
7
+ FTS5 sanitizer (OR-joined, stemmed) plus a vector-similarity leg. Short,
8
+ meaning-dense strings give the most selective hybrid ranking.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Mapping
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ _MAX_SNIPPET_LEN = 400
18
+
19
+
20
+ def from_tool_call(tool_name: str, tool_input: Mapping[str, Any]) -> str:
21
+ """Compose a search query from an openClaw before_tool_call payload.
22
+
23
+ Args:
24
+ tool_name (str): Tool name as reported by openClaw (after
25
+ camelCase→snake_case translation, e.g. ``"Edit"``, ``"Bash"``).
26
+ tool_input (Mapping[str, Any]): The tool's input dict as delivered
27
+ by the hook payload (after camelCase translation).
28
+
29
+ Returns:
30
+ str: A short query suitable for reflexio hybrid search, or ``""``
31
+ when the tool is not one we compose for (caller should then
32
+ skip the search entirely).
33
+ """
34
+ match tool_name:
35
+ case "Edit" | "Write" | "NotebookEdit":
36
+ return _from_file_edit(tool_input)
37
+ case "Bash":
38
+ return _from_bash(tool_input)
39
+ case _:
40
+ return ""
41
+
42
+
43
+ def _as_str(value: Any) -> str:
44
+ """Coerce a tool-payload field to a string, treating non-strings as empty.
45
+
46
+ Tool inputs come from external openClaw payloads; a malformed event with
47
+ a non-string ``new_string`` or ``command`` would otherwise crash the
48
+ hook with ``AttributeError`` / ``TypeError``. We prefer a clean empty
49
+ query over a partial failure.
50
+ """
51
+ return value if isinstance(value, str) else ""
52
+
53
+
54
+ def _from_file_edit(tool_input: Mapping[str, Any]) -> str:
55
+ path = _as_str(tool_input.get("file_path"))
56
+ snippet = _as_str(tool_input.get("new_string")) or _as_str(
57
+ tool_input.get("content")
58
+ )
59
+ basename = Path(path).name if path else ""
60
+ return f"{basename} {snippet[:_MAX_SNIPPET_LEN]}".strip()
61
+
62
+
63
+ def _from_bash(tool_input: Mapping[str, Any]) -> str:
64
+ command = _as_str(tool_input.get("command"))
65
+ first_line = command.splitlines()[0] if command else ""
66
+ return first_line[:_MAX_SNIPPET_LEN].strip()