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
|
@@ -34,6 +34,7 @@ class ProfileMixin:
|
|
|
34
34
|
self,
|
|
35
35
|
user_id: str,
|
|
36
36
|
status_filter: list[Status | None] | None = None,
|
|
37
|
+
tags: list[str] | None = None,
|
|
37
38
|
) -> list[UserProfile]:
|
|
38
39
|
raise NotImplementedError
|
|
39
40
|
|
|
@@ -78,6 +79,13 @@ class ProfileMixin:
|
|
|
78
79
|
) -> None:
|
|
79
80
|
raise NotImplementedError
|
|
80
81
|
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def update_user_profile_tags(
|
|
84
|
+
self, user_id: str, profile_id: str, tags: list[str]
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Replace only the tags of a profile, leaving content and embedding untouched."""
|
|
87
|
+
raise NotImplementedError
|
|
88
|
+
|
|
81
89
|
@abstractmethod
|
|
82
90
|
def delete_all_interactions_for_user(self, user_id: str) -> None:
|
|
83
91
|
raise NotImplementedError
|
|
@@ -178,6 +186,22 @@ class ProfileMixin:
|
|
|
178
186
|
"""
|
|
179
187
|
raise NotImplementedError
|
|
180
188
|
|
|
189
|
+
@abstractmethod
|
|
190
|
+
def get_profile_by_id(
|
|
191
|
+
self, profile_id: str, *, include_tombstones: bool = False
|
|
192
|
+
) -> UserProfile | None:
|
|
193
|
+
"""Fetch a single profile by primary key.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
profile_id: The profile's primary key.
|
|
197
|
+
include_tombstones: When False (default), MERGED/SUPERSEDED profiles
|
|
198
|
+
return None. Set to True for lineage resolution (resolve_current).
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
The UserProfile if found and not filtered, otherwise None.
|
|
202
|
+
"""
|
|
203
|
+
raise NotImplementedError
|
|
204
|
+
|
|
181
205
|
@abstractmethod
|
|
182
206
|
def archive_profile_by_id(self, user_id: str, profile_id: str) -> bool:
|
|
183
207
|
"""Atomically archive a single profile by id, only if currently CURRENT.
|
|
@@ -220,17 +244,113 @@ class ProfileMixin:
|
|
|
220
244
|
raise NotImplementedError
|
|
221
245
|
|
|
222
246
|
@abstractmethod
|
|
223
|
-
def delete_profiles_by_ids(
|
|
247
|
+
def delete_profiles_by_ids(
|
|
248
|
+
self, profile_ids: list[str], *, emit_hard_delete: bool = True
|
|
249
|
+
) -> int:
|
|
224
250
|
"""Delete profiles by their IDs.
|
|
225
251
|
|
|
226
252
|
Args:
|
|
227
253
|
profile_ids (list[str]): List of profile IDs to delete
|
|
254
|
+
emit_hard_delete (bool): When True (default), emit a ``hard_delete``
|
|
255
|
+
lineage event for each id before removing the row. Pass ``False``
|
|
256
|
+
from rollback callers that cleaned up a never-CURRENT successor to
|
|
257
|
+
avoid poisoning the audit log with phantom erasures.
|
|
228
258
|
|
|
229
259
|
Returns:
|
|
230
260
|
int: Number of profiles deleted
|
|
231
261
|
"""
|
|
232
262
|
raise NotImplementedError
|
|
233
263
|
|
|
264
|
+
@abstractmethod
|
|
265
|
+
def get_distinct_generated_from_request_ids(self) -> list[str]:
|
|
266
|
+
"""Return the DISTINCT non-empty generated_from_request_id values present on profiles.
|
|
267
|
+
|
|
268
|
+
Scoped to the org. Includes profiles of any status (tombstones included) so
|
|
269
|
+
that an add-only run whose profiles were later tombstoned is still discoverable.
|
|
270
|
+
Empty-string values are excluded by the query (they must never form a group).
|
|
271
|
+
|
|
272
|
+
Used by ``reconstruct_profile_change_log`` to discover add-only dedup runs
|
|
273
|
+
(runs that added new profiles but superseded nothing, which emit no lineage
|
|
274
|
+
event and would otherwise be invisible).
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
list[str]: Distinct non-empty ``generated_from_request_id`` values.
|
|
278
|
+
"""
|
|
279
|
+
raise NotImplementedError
|
|
280
|
+
|
|
281
|
+
@abstractmethod
|
|
282
|
+
def get_profiles_by_generated_from_request_id(
|
|
283
|
+
self,
|
|
284
|
+
request_id: str,
|
|
285
|
+
) -> list[UserProfile]:
|
|
286
|
+
"""Return all profiles (any status, including tombstones) for a given generated_from_request_id.
|
|
287
|
+
|
|
288
|
+
Scoped to the org. Used by reconstruct_profile_change_log to find the
|
|
289
|
+
"added" side of a dedup run without depending on mutable status columns.
|
|
290
|
+
The column is set at profile creation and never changes — it is the
|
|
291
|
+
time-travel-stable signal for "added in run R".
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
request_id (str): The generated_from_request_id value to filter on.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
list[UserProfile]: All profiles (live or tombstone) whose
|
|
298
|
+
``generated_from_request_id`` matches, scoped by org.
|
|
299
|
+
"""
|
|
300
|
+
raise NotImplementedError
|
|
301
|
+
|
|
302
|
+
def get_all_generated_profiles(self) -> list[UserProfile]:
|
|
303
|
+
"""Return every profile (any status, incl. tombstones) with a non-empty
|
|
304
|
+
``generated_from_request_id``, scoped to the org.
|
|
305
|
+
|
|
306
|
+
Bulk equivalent of :meth:`get_profiles_by_generated_from_request_id` — it
|
|
307
|
+
lets ``reconstruct_profile_change_log`` resolve the "added" side of every
|
|
308
|
+
run in a single query instead of one read per candidate request_id (an
|
|
309
|
+
org-history-sized fan-out on network-backed storage).
|
|
310
|
+
|
|
311
|
+
The default implementation loops the per-id reads, so every backend is
|
|
312
|
+
correct without an override; backends SHOULD override it with a single
|
|
313
|
+
``WHERE generated_from_request_id <> ''`` query for the performance win.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
list[UserProfile]: All profiles with a non-empty
|
|
317
|
+
``generated_from_request_id``.
|
|
318
|
+
"""
|
|
319
|
+
out: list[UserProfile] = []
|
|
320
|
+
for request_id in self.get_distinct_generated_from_request_ids():
|
|
321
|
+
out.extend(self.get_profiles_by_generated_from_request_id(request_id))
|
|
322
|
+
return out
|
|
323
|
+
|
|
324
|
+
@abstractmethod
|
|
325
|
+
def supersede_profiles_by_ids(
|
|
326
|
+
self,
|
|
327
|
+
user_id: str,
|
|
328
|
+
profile_ids: list[str],
|
|
329
|
+
request_id: str,
|
|
330
|
+
) -> list[str]:
|
|
331
|
+
"""Soft-delete profiles by setting status to SUPERSEDED, emitting set-based lineage.
|
|
332
|
+
|
|
333
|
+
For each profile id that matches (user_id, current status in {NULL/CURRENT,
|
|
334
|
+
PENDING}), updates status to SUPERSEDED and emits one ``status_change``
|
|
335
|
+
lineage event under the shared ``request_id``. Rows are NOT physically
|
|
336
|
+
deleted — reads that exclude tombstones will simply filter them out by status.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
user_id (str): Owning user id. Predicate scoped to this user.
|
|
340
|
+
profile_ids (list[str]): Profile ids to supersede. Already-superseded or
|
|
341
|
+
non-existent ids are silently skipped.
|
|
342
|
+
request_id (str): Shared request id to stamp on all emitted events so the
|
|
343
|
+
entire dedup run is reconstructible from a single id.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
list[str]: The profile ids that were actually superseded by this call, in
|
|
347
|
+
input order. Already-superseded or non-existent ids are omitted (so an
|
|
348
|
+
empty list means nothing committed). Callers needing a count use
|
|
349
|
+
``len(...)``. This is the commit-accurate set used to build a
|
|
350
|
+
commit-atomic legacy change-log "removed" entry.
|
|
351
|
+
"""
|
|
352
|
+
raise NotImplementedError
|
|
353
|
+
|
|
234
354
|
# Search methods
|
|
235
355
|
@abstractmethod
|
|
236
356
|
def search_interaction(
|
|
@@ -4,6 +4,7 @@ from reflexio.models.api_schema.domain import Request
|
|
|
4
4
|
from reflexio.models.api_schema.internal_schema import (
|
|
5
5
|
RequestInteractionDataModel,
|
|
6
6
|
SessionDescriptor,
|
|
7
|
+
SessionFirstRequest,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
|
|
@@ -152,3 +153,62 @@ class RequestMixin:
|
|
|
152
153
|
list[SessionDescriptor]: Deduped descriptors ordered by session_id.
|
|
153
154
|
"""
|
|
154
155
|
raise NotImplementedError
|
|
156
|
+
|
|
157
|
+
def get_first_requests_by_session_ids(
|
|
158
|
+
self, session_ids: list[str]
|
|
159
|
+
) -> dict[str, SessionFirstRequest]:
|
|
160
|
+
"""Return earliest-request metadata for each requested session.
|
|
161
|
+
|
|
162
|
+
Default implementation preserves the historical storage contract by
|
|
163
|
+
calling ``get_sessions`` per session. SQL backends should override with
|
|
164
|
+
a set-based query.
|
|
165
|
+
"""
|
|
166
|
+
out: dict[str, SessionFirstRequest] = {}
|
|
167
|
+
for session_id in set(session_ids):
|
|
168
|
+
requests = []
|
|
169
|
+
page_size = 1000
|
|
170
|
+
offset = 0
|
|
171
|
+
while True:
|
|
172
|
+
grouped = self.get_sessions(
|
|
173
|
+
session_id=session_id,
|
|
174
|
+
top_k=page_size,
|
|
175
|
+
offset=offset,
|
|
176
|
+
)
|
|
177
|
+
rows = grouped.get(session_id) or []
|
|
178
|
+
requests.extend(r.request for r in rows if r.request is not None)
|
|
179
|
+
if len(rows) < page_size:
|
|
180
|
+
break
|
|
181
|
+
offset += page_size
|
|
182
|
+
if not requests:
|
|
183
|
+
continue
|
|
184
|
+
first = min(requests, key=lambda r: r.created_at)
|
|
185
|
+
out[session_id] = SessionFirstRequest(
|
|
186
|
+
session_id=session_id,
|
|
187
|
+
user_id=first.user_id,
|
|
188
|
+
source=first.source or "",
|
|
189
|
+
created_at=first.created_at,
|
|
190
|
+
)
|
|
191
|
+
return out
|
|
192
|
+
|
|
193
|
+
def get_first_requests_by_user_session_pairs(
|
|
194
|
+
self, pairs: list[tuple[str, str]]
|
|
195
|
+
) -> dict[tuple[str, str], SessionFirstRequest]:
|
|
196
|
+
"""Return earliest-request metadata for each requested (user_id, session_id).
|
|
197
|
+
|
|
198
|
+
Default implementation preserves backend compatibility by calling the
|
|
199
|
+
existing user-scoped request lookup per distinct pair. SQL backends
|
|
200
|
+
override with set-based queries.
|
|
201
|
+
"""
|
|
202
|
+
out: dict[tuple[str, str], SessionFirstRequest] = {}
|
|
203
|
+
for user_id, session_id in set(pairs):
|
|
204
|
+
requests = self.get_requests_by_session(user_id, session_id)
|
|
205
|
+
if not requests:
|
|
206
|
+
continue
|
|
207
|
+
first = min(requests, key=lambda r: r.created_at)
|
|
208
|
+
out[(user_id, session_id)] = SessionFirstRequest(
|
|
209
|
+
session_id=session_id,
|
|
210
|
+
user_id=user_id,
|
|
211
|
+
source=first.source or "",
|
|
212
|
+
created_at=first.created_at,
|
|
213
|
+
)
|
|
214
|
+
return out
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from reflexio.models.api_schema.domain.entities import PlaybookRetrievalLog
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RetrievalLogMixin:
|
|
5
|
+
"""Mixin for playbook retrieval log storage methods.
|
|
6
|
+
|
|
7
|
+
These methods are optional retrieval-capture storage hooks. They are
|
|
8
|
+
intentionally concrete (not ``@abstractmethod``) so concrete storage classes
|
|
9
|
+
remain instantiable; each raises ``NotImplementedError`` until a backend
|
|
10
|
+
provides a real implementation.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def save_playbook_retrieval_log(self, log: PlaybookRetrievalLog) -> int:
|
|
14
|
+
"""Persist a retrieval log entry and return its assigned id.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
log (PlaybookRetrievalLog): The log entry to save. ``retrieval_log_id``
|
|
18
|
+
may be 0; the storage layer assigns a real id on insert.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
int: The assigned ``retrieval_log_id``.
|
|
22
|
+
"""
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
def get_playbook_retrieval_logs(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
session_id: str | None = None,
|
|
29
|
+
request_id: str | None = None,
|
|
30
|
+
interaction_id: int | None = None,
|
|
31
|
+
user_id: str | None = None,
|
|
32
|
+
start_time: int | None = None,
|
|
33
|
+
end_time: int | None = None,
|
|
34
|
+
) -> list[PlaybookRetrievalLog]:
|
|
35
|
+
"""Retrieve playbook retrieval log entries, optionally filtered.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
session_id (str | None): Filter to logs for this session. If None,
|
|
39
|
+
no session filter is applied.
|
|
40
|
+
request_id (str | None): Filter to logs for this request. If None,
|
|
41
|
+
no request filter is applied.
|
|
42
|
+
interaction_id (int | None): Filter to logs for this interaction.
|
|
43
|
+
user_id (str | None): Filter to logs for this user.
|
|
44
|
+
start_time (int | None): Inclusive lower bound on ``created_at``.
|
|
45
|
+
end_time (int | None): Inclusive upper bound on ``created_at``.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
list[PlaybookRetrievalLog]: Matching log entries, ordered by
|
|
49
|
+
``created_at`` ascending.
|
|
50
|
+
"""
|
|
51
|
+
raise NotImplementedError
|
package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py
CHANGED
|
@@ -108,6 +108,23 @@ class ShadowVerdictsMixin:
|
|
|
108
108
|
f"{type(self).__name__} does not support shadow_comparison_verdicts"
|
|
109
109
|
)
|
|
110
110
|
|
|
111
|
+
def get_recent_shadow_comparison_verdicts(
|
|
112
|
+
self,
|
|
113
|
+
from_ts: int,
|
|
114
|
+
to_ts: int,
|
|
115
|
+
judge_prompt_version: str,
|
|
116
|
+
limit: int,
|
|
117
|
+
) -> list[ShadowComparisonVerdict]:
|
|
118
|
+
"""Fetch newest verdicts in descending ``created_at`` order."""
|
|
119
|
+
if limit <= 0:
|
|
120
|
+
return []
|
|
121
|
+
verdicts = self.get_shadow_comparison_verdicts(
|
|
122
|
+
from_ts=from_ts,
|
|
123
|
+
to_ts=to_ts,
|
|
124
|
+
judge_prompt_version=judge_prompt_version,
|
|
125
|
+
)
|
|
126
|
+
return list(reversed(verdicts))[:limit]
|
|
127
|
+
|
|
111
128
|
def delete_shadow_comparison_verdicts_by_session(self, session_id: str) -> int:
|
|
112
129
|
"""Remove all verdicts for one session.
|
|
113
130
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# tagging
|
|
2
|
+
|
|
3
|
+
Compact post-generation entity tagging capability.
|
|
4
|
+
|
|
5
|
+
- `service.py` tags profiles and playbooks with the configured tagging prompts.
|
|
6
|
+
- `tagging_scheduler.py` debounces post-publish tagging and rebuilds request context for background execution.
|
|
7
|
+
|
|
8
|
+
This package intentionally does not use `components/`: the service and scheduler are the only module responsibilities.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Post-generation entity tagging services."""
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from pydantic import ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
from reflexio.models.api_schema.service_schemas import (
|
|
9
|
+
AgentPlaybook,
|
|
10
|
+
UserPlaybook,
|
|
11
|
+
UserProfile,
|
|
12
|
+
)
|
|
13
|
+
from reflexio.models.structured_output import StrictStructuredOutput
|
|
14
|
+
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
15
|
+
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
16
|
+
from reflexio.server.llm.model_defaults import ModelRole, resolve_model_name
|
|
17
|
+
from reflexio.server.services.service_utils import log_llm_messages, log_model_response
|
|
18
|
+
from reflexio.server.site_var.site_var_manager import SiteVarManager
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
TAGGING_PROMPT_ID = "tagging"
|
|
23
|
+
|
|
24
|
+
# Upper bound on how many current playbooks a single tagging pass will scan for
|
|
25
|
+
# untagged entries. Tagging is idempotent (already-tagged entities are skipped),
|
|
26
|
+
# so steady-state passes only tag newly generated entities; this bound just keeps
|
|
27
|
+
# a one-time backfill from being silently truncated at the storage default of 100.
|
|
28
|
+
_TAGGING_FETCH_LIMIT = 1000
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TagsOutput(StrictStructuredOutput):
|
|
32
|
+
tags: list[str] = Field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(
|
|
35
|
+
extra="allow",
|
|
36
|
+
json_schema_extra={"additionalProperties": False},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TaggingService:
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
llm_client: LiteLLMClient,
|
|
44
|
+
request_context: RequestContext,
|
|
45
|
+
) -> None:
|
|
46
|
+
self.client = llm_client
|
|
47
|
+
self.request_context = request_context
|
|
48
|
+
self.configurator = request_context.configurator
|
|
49
|
+
self.storage = request_context.storage
|
|
50
|
+
|
|
51
|
+
model_setting = SiteVarManager().get_site_var("llm_model_setting")
|
|
52
|
+
site_var = model_setting if isinstance(model_setting, dict) else {}
|
|
53
|
+
config = self.configurator.get_config()
|
|
54
|
+
llm_config = config.llm_config if config else None
|
|
55
|
+
api_key_config = config.api_key_config if config else None
|
|
56
|
+
self.model_name = resolve_model_name(
|
|
57
|
+
ModelRole.GENERATION,
|
|
58
|
+
site_var_value=site_var.get("default_generation_model_name"),
|
|
59
|
+
config_override=llm_config.generation_model_name if llm_config else None,
|
|
60
|
+
api_key_config=api_key_config,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def run(
|
|
64
|
+
self,
|
|
65
|
+
*,
|
|
66
|
+
user_id: str,
|
|
67
|
+
agent_version: str,
|
|
68
|
+
tag_profiles: bool = True,
|
|
69
|
+
tag_playbooks: bool = True,
|
|
70
|
+
) -> None:
|
|
71
|
+
if self.storage is None:
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
config = self.configurator.get_config()
|
|
75
|
+
if config is None:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
profile_prompt = getattr(
|
|
79
|
+
config.profile_extractor_config, "tagging_definition_prompt", None
|
|
80
|
+
)
|
|
81
|
+
user_playbook_prompt = getattr(
|
|
82
|
+
config.user_playbook_extractor_config, "tagging_definition_prompt", None
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if tag_profiles and profile_prompt:
|
|
86
|
+
self._tag_profiles(
|
|
87
|
+
user_id=user_id, tagging_definition_prompt=profile_prompt
|
|
88
|
+
)
|
|
89
|
+
if tag_playbooks and user_playbook_prompt:
|
|
90
|
+
self._tag_user_playbooks(
|
|
91
|
+
user_id=user_id,
|
|
92
|
+
agent_version=agent_version,
|
|
93
|
+
tagging_definition_prompt=user_playbook_prompt,
|
|
94
|
+
)
|
|
95
|
+
self._tag_agent_playbooks(
|
|
96
|
+
agent_version=agent_version,
|
|
97
|
+
tagging_definition_prompt=user_playbook_prompt,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def _tag_profiles(self, *, user_id: str, tagging_definition_prompt: str) -> None:
|
|
101
|
+
profiles = self.storage.get_user_profile(user_id) # type: ignore[union-attr]
|
|
102
|
+
for profile in profiles:
|
|
103
|
+
if profile.tags is not None:
|
|
104
|
+
continue # already tagged (incl. empty result) — tag each entity once
|
|
105
|
+
tags = self._generate_tags(
|
|
106
|
+
tagging_definition_prompt=tagging_definition_prompt,
|
|
107
|
+
content=self._profile_content(profile),
|
|
108
|
+
)
|
|
109
|
+
self.storage.update_user_profile_tags( # type: ignore[union-attr]
|
|
110
|
+
user_id, profile.profile_id, tags
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def _tag_user_playbooks(
|
|
114
|
+
self,
|
|
115
|
+
*,
|
|
116
|
+
user_id: str,
|
|
117
|
+
agent_version: str,
|
|
118
|
+
tagging_definition_prompt: str,
|
|
119
|
+
) -> None:
|
|
120
|
+
playbooks = self.storage.get_user_playbooks( # type: ignore[union-attr]
|
|
121
|
+
limit=_TAGGING_FETCH_LIMIT,
|
|
122
|
+
user_id=user_id,
|
|
123
|
+
agent_version=agent_version,
|
|
124
|
+
status_filter=[None],
|
|
125
|
+
)
|
|
126
|
+
for playbook in playbooks:
|
|
127
|
+
if playbook.tags is not None:
|
|
128
|
+
continue # already tagged (incl. empty result) — tag each entity once
|
|
129
|
+
tags = self._generate_tags(
|
|
130
|
+
tagging_definition_prompt=tagging_definition_prompt,
|
|
131
|
+
content=self._playbook_content(playbook),
|
|
132
|
+
)
|
|
133
|
+
self.storage.update_user_playbook( # type: ignore[union-attr]
|
|
134
|
+
playbook.user_playbook_id,
|
|
135
|
+
tags=tags,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _tag_agent_playbooks(
|
|
139
|
+
self,
|
|
140
|
+
*,
|
|
141
|
+
agent_version: str,
|
|
142
|
+
tagging_definition_prompt: str,
|
|
143
|
+
) -> None:
|
|
144
|
+
playbooks = self.storage.get_agent_playbooks( # type: ignore[union-attr]
|
|
145
|
+
limit=_TAGGING_FETCH_LIMIT,
|
|
146
|
+
agent_version=agent_version,
|
|
147
|
+
status_filter=[None],
|
|
148
|
+
)
|
|
149
|
+
for playbook in playbooks:
|
|
150
|
+
if playbook.tags is not None:
|
|
151
|
+
continue # already tagged (incl. empty result) — tag each entity once
|
|
152
|
+
tags = self._generate_tags(
|
|
153
|
+
tagging_definition_prompt=tagging_definition_prompt,
|
|
154
|
+
content=self._playbook_content(playbook),
|
|
155
|
+
)
|
|
156
|
+
self.storage.update_agent_playbook( # type: ignore[union-attr]
|
|
157
|
+
playbook.agent_playbook_id,
|
|
158
|
+
tags=tags,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def _generate_tags(
|
|
162
|
+
self, *, tagging_definition_prompt: str, content: str
|
|
163
|
+
) -> list[str]:
|
|
164
|
+
if os.getenv("MOCK_LLM_RESPONSE", "").lower() == "true":
|
|
165
|
+
return ["example_tag"]
|
|
166
|
+
|
|
167
|
+
prompt = self.request_context.prompt_manager.render_prompt(
|
|
168
|
+
TAGGING_PROMPT_ID,
|
|
169
|
+
{
|
|
170
|
+
"tagging_definition_prompt": tagging_definition_prompt.strip(),
|
|
171
|
+
"content": content,
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
messages = [{"role": "user", "content": prompt}]
|
|
175
|
+
log_llm_messages(logger, "Tagging", messages)
|
|
176
|
+
response = self.client.generate_chat_response(
|
|
177
|
+
messages=messages,
|
|
178
|
+
model=self.model_name,
|
|
179
|
+
response_format=TagsOutput,
|
|
180
|
+
)
|
|
181
|
+
log_model_response(logger, "Tagging response", response)
|
|
182
|
+
if not isinstance(response, TagsOutput):
|
|
183
|
+
logger.warning(
|
|
184
|
+
"Unexpected response type from tagging LLM: %s", type(response)
|
|
185
|
+
)
|
|
186
|
+
return []
|
|
187
|
+
return [tag.strip() for tag in response.tags if tag.strip()]
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def _profile_content(profile: UserProfile) -> str:
|
|
191
|
+
return profile.content
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _playbook_content(playbook: UserPlaybook | AgentPlaybook) -> str:
|
|
195
|
+
parts = [playbook.content]
|
|
196
|
+
if playbook.trigger:
|
|
197
|
+
parts.append(f"Trigger: {playbook.trigger}")
|
|
198
|
+
if playbook.rationale:
|
|
199
|
+
parts.append(f"Rationale: {playbook.rationale}")
|
|
200
|
+
return "\n".join(part for part in parts if part)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Singleton scheduler for deferred, off-publish-path entity tagging.
|
|
2
|
+
|
|
3
|
+
Tagging runs an LLM call per newly generated profile/playbook, so it must not
|
|
4
|
+
block the publish request. This scheduler mirrors
|
|
5
|
+
:class:`GroupEvaluationScheduler`: a single daemon thread with a min-heap, where
|
|
6
|
+
each publish upserts the fire time for its ``(org_id, user_id, agent_version)``
|
|
7
|
+
key. Rapid successive publishes for the same key debounce into a single tagging
|
|
8
|
+
pass; when the timer fires, the tagging callback runs on its own daemon thread.
|
|
9
|
+
|
|
10
|
+
Tagging is idempotent (already-tagged entities are skipped), so a deferred pass
|
|
11
|
+
naturally handles both newly generated entities and the one-time backfill that
|
|
12
|
+
happens when a tagging definition is first configured.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import heapq
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import threading
|
|
21
|
+
import time
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
|
|
24
|
+
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
25
|
+
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
26
|
+
from reflexio.server.services.tagging.service import TaggingService
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Delay before a tagging pass fires after the last publish for a key. Long enough
|
|
31
|
+
# to debounce a burst of publishes into one pass, short enough that tags appear
|
|
32
|
+
# promptly. Kept as a patch point for tests.
|
|
33
|
+
TAGGING_DELAY_SECONDS = 15
|
|
34
|
+
IS_TEST_ENV = os.environ.get("IS_TEST_ENV", "false").strip().lower() == "true"
|
|
35
|
+
_EFFECTIVE_DELAY_SECONDS = 1 if IS_TEST_ENV else TAGGING_DELAY_SECONDS
|
|
36
|
+
|
|
37
|
+
# (org_id, user_id, agent_version)
|
|
38
|
+
TaggingKey = tuple[str, str, str]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TaggingScheduler:
|
|
42
|
+
"""Singleton scheduler that fires entity tagging after a short debounce."""
|
|
43
|
+
|
|
44
|
+
_instance = None
|
|
45
|
+
_lock = threading.Lock()
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def get_instance(cls) -> TaggingScheduler:
|
|
49
|
+
if cls._instance is None:
|
|
50
|
+
with cls._lock:
|
|
51
|
+
if cls._instance is None:
|
|
52
|
+
cls._instance = cls()
|
|
53
|
+
return cls._instance
|
|
54
|
+
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
self._scheduled: dict[TaggingKey, tuple[float, Callable]] = {}
|
|
57
|
+
self._heap: list[tuple[float, TaggingKey]] = []
|
|
58
|
+
self._mutex = threading.Lock()
|
|
59
|
+
self._wake_event = threading.Event()
|
|
60
|
+
self._thread = threading.Thread(
|
|
61
|
+
target=self._scheduler_loop, daemon=True, name="tagging-scheduler"
|
|
62
|
+
)
|
|
63
|
+
self._thread.start()
|
|
64
|
+
logger.info("TaggingScheduler started")
|
|
65
|
+
|
|
66
|
+
def schedule(self, key: TaggingKey, callback: Callable) -> None:
|
|
67
|
+
"""Schedule or reschedule a tagging pass for ``key`` (slides the fire time forward)."""
|
|
68
|
+
fire_time = time.monotonic() + _EFFECTIVE_DELAY_SECONDS
|
|
69
|
+
with self._mutex:
|
|
70
|
+
self._scheduled[key] = (fire_time, callback)
|
|
71
|
+
heapq.heappush(self._heap, (fire_time, key))
|
|
72
|
+
self._wake_event.set()
|
|
73
|
+
|
|
74
|
+
def _scheduler_loop(self) -> None:
|
|
75
|
+
while True:
|
|
76
|
+
try:
|
|
77
|
+
with self._mutex:
|
|
78
|
+
next_fire_time = self._heap[0][0] if self._heap else None
|
|
79
|
+
|
|
80
|
+
if next_fire_time is None:
|
|
81
|
+
self._wake_event.wait()
|
|
82
|
+
self._wake_event.clear()
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
wait_seconds = next_fire_time - time.monotonic()
|
|
86
|
+
if wait_seconds > 0:
|
|
87
|
+
self._wake_event.wait(timeout=wait_seconds)
|
|
88
|
+
self._wake_event.clear()
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
with self._mutex:
|
|
92
|
+
while self._heap and self._heap[0][0] <= time.monotonic():
|
|
93
|
+
fire_time, key = heapq.heappop(self._heap)
|
|
94
|
+
|
|
95
|
+
current = self._scheduled.get(key)
|
|
96
|
+
if current is None:
|
|
97
|
+
continue
|
|
98
|
+
current_fire_time, callback = current
|
|
99
|
+
if abs(current_fire_time - fire_time) > 0.001:
|
|
100
|
+
# Superseded by a newer schedule for the same key.
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
del self._scheduled[key]
|
|
104
|
+
t = threading.Thread(
|
|
105
|
+
target=self._run_callback,
|
|
106
|
+
args=(key, callback),
|
|
107
|
+
daemon=True,
|
|
108
|
+
name=f"tagging-{key[1][:20]}",
|
|
109
|
+
)
|
|
110
|
+
t.start()
|
|
111
|
+
except Exception:
|
|
112
|
+
logger.exception("Error in tagging scheduler loop")
|
|
113
|
+
time.sleep(1)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _run_callback(key: TaggingKey, callback: Callable) -> None:
|
|
117
|
+
try:
|
|
118
|
+
logger.info("Firing tagging for key=%s", key)
|
|
119
|
+
callback()
|
|
120
|
+
logger.info("Completed tagging for key=%s", key)
|
|
121
|
+
except Exception:
|
|
122
|
+
logger.exception("Tagging callback failed for key=%s", key)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def schedule_tagging(
|
|
126
|
+
*,
|
|
127
|
+
org_id: str,
|
|
128
|
+
user_id: str,
|
|
129
|
+
agent_version: str,
|
|
130
|
+
request_context: RequestContext,
|
|
131
|
+
llm_client: LiteLLMClient,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Enqueue a deferred tagging pass for the given user/agent off the publish path."""
|
|
134
|
+
if not user_id:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
key: TaggingKey = (org_id, user_id, agent_version)
|
|
138
|
+
storage_base_dir = getattr(request_context, "storage_base_dir", None)
|
|
139
|
+
|
|
140
|
+
def callback() -> None:
|
|
141
|
+
fresh_context = RequestContext(
|
|
142
|
+
org_id=org_id,
|
|
143
|
+
storage_base_dir=storage_base_dir,
|
|
144
|
+
)
|
|
145
|
+
TaggingService(llm_client=llm_client, request_context=fresh_context).run(
|
|
146
|
+
user_id=user_id, agent_version=agent_version
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
TaggingScheduler.get_instance().schedule(key, callback)
|