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.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +34 -5
- package/bin/claude-smart.js +295 -5
- package/package.json +22 -3
- package/plugin/.claude-plugin/plugin.json +4 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +22 -2
- package/plugin/commands/clear-all.md +1 -0
- package/plugin/commands/dashboard.md +1 -0
- package/plugin/commands/learn.md +1 -0
- package/plugin/commands/restart.md +1 -0
- package/plugin/commands/show.md +1 -0
- package/plugin/dashboard/app/configure/env/page.tsx +40 -14
- package/plugin/dashboard/app/configure/server/page.tsx +51 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
- package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
- package/plugin/dashboard/lib/reflexio-client.ts +16 -0
- package/plugin/dashboard/lib/status.ts +10 -3
- package/plugin/dashboard/lib/types.ts +18 -6
- package/plugin/hooks/codex-hooks.json +0 -1
- package/plugin/opencode/assistant-buffer.ts +108 -0
- package/plugin/opencode/dist/assistant-buffer.js +96 -0
- package/plugin/opencode/dist/internal.js +12 -0
- package/plugin/opencode/dist/payload.js +85 -0
- package/plugin/opencode/dist/server.mjs +158 -0
- package/plugin/opencode/internal.ts +18 -0
- package/plugin/opencode/package.json +3 -0
- package/plugin/opencode/payload.ts +90 -0
- package/plugin/opencode/server.mts +174 -0
- package/plugin/opencode/tsconfig.json +13 -0
- package/plugin/pyproject.toml +20 -3
- package/plugin/scripts/_lib.sh +5 -1
- package/plugin/scripts/backend-service.sh +22 -2
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +1 -1
- package/plugin/scripts/opencode-claude-compat +4 -0
- package/plugin/scripts/opencode-claude-compat.cmd +3 -0
- package/plugin/scripts/opencode-claude-compat.js +225 -0
- package/plugin/scripts/smart-install.sh +10 -8
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +304 -6
- package/plugin/src/claude_smart/env_config.py +12 -4
- package/plugin/src/claude_smart/events/session_start.py +26 -7
- package/plugin/src/claude_smart/events/stop.py +1 -1
- package/plugin/src/claude_smart/ids.py +1 -1
- package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
- package/plugin/src/claude_smart/runtime.py +7 -1
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +54 -30
- package/plugin/vendor/reflexio/README.md +14 -8
- package/plugin/vendor/reflexio/pyproject.toml +13 -1
- package/plugin/vendor/reflexio/reflexio/README.md +1 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
- package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
- package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
- package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
- package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
- package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
- package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
- package/scripts/setup-claude-smart.sh +8 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
- /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py
ADDED
|
@@ -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())
|
package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py
ADDED
|
@@ -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()
|