claude-smart 0.2.44 → 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 +77 -29
- package/plugin/scripts/backend-service.sh +27 -5
- package/plugin/scripts/dashboard-service.sh +7 -5
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +3 -3
- 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
|
@@ -17,6 +17,7 @@ from reflexio.models.config_schema import (
|
|
|
17
17
|
DeduplicationConfig,
|
|
18
18
|
SearchOptions,
|
|
19
19
|
)
|
|
20
|
+
from reflexio.models.structured_output import StrictStructuredOutput
|
|
20
21
|
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
21
22
|
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
22
23
|
from reflexio.server.services.deduplication_utils import (
|
|
@@ -36,18 +37,19 @@ logger = logging.getLogger(__name__)
|
|
|
36
37
|
def _coerce_existing_position(value: object) -> int:
|
|
37
38
|
"""Accept either a bare int position or an ``"EXISTING-N"`` label.
|
|
38
39
|
|
|
39
|
-
Wired
|
|
40
|
-
|
|
41
|
-
``existing_by_position`` as ``f"EXISTING-{idx}"`` in ``_apply_unify``).
|
|
40
|
+
Wired to all three EXISTING-id integer fields —
|
|
41
|
+
``UnifyDecision.archive_existing_ids``,
|
|
42
42
|
``RejectNewDecision.superseded_by_existing_id`` and
|
|
43
|
-
``DifferentiateDecision.existing_id
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
``DifferentiateDecision.existing_id``. The consolidation prompt instructs
|
|
44
|
+
the model to emit a list **position** for every one of them, and the apply
|
|
45
|
+
path resolves each position-first via ``existing_by_position``
|
|
46
|
+
(``f"EXISTING-{idx}"``), falling back to ``existing_by_id`` only for older
|
|
47
|
+
prompt outputs. Coercing the label here keeps all three fields consistent
|
|
48
|
+
so a weak model that returns ``"EXISTING-0"`` for any of them does not kill
|
|
49
|
+
the whole batch.
|
|
48
50
|
|
|
49
51
|
The consolidation prompt labels rows as ``[EXISTING-0]``, ``[EXISTING-1]``
|
|
50
|
-
etc. (see ``_format_playbooks_with_prefix``) and
|
|
52
|
+
etc. (see ``_format_playbooks_with_prefix``) and the apply path
|
|
51
53
|
reconstructs ``f"EXISTING-{position}"`` from the integer the LLM returns.
|
|
52
54
|
Strong structured-output models (GPT-4o, Claude) honor the ``list[int]``
|
|
53
55
|
schema and return the bare integer ``0``; weaker models (e.g. MiniMax-M3)
|
|
@@ -132,12 +134,11 @@ class UnifyDecision(BaseModel):
|
|
|
132
134
|
class RejectNewDecision(BaseModel):
|
|
133
135
|
"""The new candidate is redundant; an existing row supersedes it (storage no-op).
|
|
134
136
|
|
|
135
|
-
``superseded_by_existing_id`` is a
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
``_coerce_existing_position`` docstring).
|
|
137
|
+
``superseded_by_existing_id`` is a bare integer that normally refers to the
|
|
138
|
+
rendered ``EXISTING-N`` list position. The apply path also accepts a DB
|
|
139
|
+
``user_playbook_id`` fallback for older prompts/tests, but list position is
|
|
140
|
+
resolved first because it is the only identifier visible in the rendered
|
|
141
|
+
consolidation prompt.
|
|
141
142
|
"""
|
|
142
143
|
|
|
143
144
|
kind: Literal["reject_new"] = "reject_new"
|
|
@@ -145,16 +146,22 @@ class RejectNewDecision(BaseModel):
|
|
|
145
146
|
superseded_by_existing_id: int
|
|
146
147
|
reason: str = ""
|
|
147
148
|
|
|
149
|
+
@field_validator("superseded_by_existing_id", mode="before")
|
|
150
|
+
@classmethod
|
|
151
|
+
def _coerce_superseded_id(cls, value: object) -> int:
|
|
152
|
+
return _coerce_existing_position(value)
|
|
153
|
+
|
|
148
154
|
model_config = ConfigDict(json_schema_extra={"additionalProperties": False})
|
|
149
155
|
|
|
150
156
|
|
|
151
157
|
class DifferentiateDecision(BaseModel):
|
|
152
158
|
"""Both rules valid in distinct contexts: refine both triggers.
|
|
153
159
|
|
|
154
|
-
``existing_id`` is a
|
|
155
|
-
``
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
``existing_id`` is a bare integer that normally refers to the rendered
|
|
161
|
+
``EXISTING-N`` list position. The apply path also accepts a DB
|
|
162
|
+
``user_playbook_id`` fallback for older prompts/tests, but list position is
|
|
163
|
+
resolved first because it is the only identifier visible in the rendered
|
|
164
|
+
consolidation prompt.
|
|
158
165
|
"""
|
|
159
166
|
|
|
160
167
|
kind: Literal["differentiate"] = "differentiate"
|
|
@@ -164,6 +171,11 @@ class DifferentiateDecision(BaseModel):
|
|
|
164
171
|
refined_existing_trigger: str
|
|
165
172
|
reason: str = ""
|
|
166
173
|
|
|
174
|
+
@field_validator("existing_id", mode="before")
|
|
175
|
+
@classmethod
|
|
176
|
+
def _coerce_existing_id(cls, value: object) -> int:
|
|
177
|
+
return _coerce_existing_position(value)
|
|
178
|
+
|
|
167
179
|
@field_validator("refined_new_trigger", "refined_existing_trigger")
|
|
168
180
|
@classmethod
|
|
169
181
|
def _non_empty(cls, v: str) -> str:
|
|
@@ -190,11 +202,17 @@ ConsolidationDecision = Annotated[
|
|
|
190
202
|
]
|
|
191
203
|
|
|
192
204
|
|
|
193
|
-
|
|
194
|
-
|
|
205
|
+
# ``PlaybookConsolidationOutput`` inherits ``StrictStructuredOutput`` so the
|
|
206
|
+
# emitted JSON schema is folded into the provider-accepted union shape while the
|
|
207
|
+
# discriminator is kept for keyed validation at parse time (Sentry
|
|
208
|
+
# PYTHON-FASTAPI-9J). This note is a comment, NOT part of the docstring, because
|
|
209
|
+
# the docstring is serialized into the wire schema's ``description`` sent to the
|
|
210
|
+
# model — keep implementation tokens out of it.
|
|
211
|
+
class PlaybookConsolidationOutput(StrictStructuredOutput):
|
|
212
|
+
"""Output schema for playbook consolidation as a 4-kind tagged union.
|
|
195
213
|
|
|
196
214
|
Each decision is one of ``UnifyDecision``, ``RejectNewDecision``,
|
|
197
|
-
``DifferentiateDecision``, or ``IndependentDecision``; the ``kind``
|
|
215
|
+
``DifferentiateDecision``, or ``IndependentDecision``; the ``kind`` field
|
|
198
216
|
selects the concrete shape.
|
|
199
217
|
"""
|
|
200
218
|
|
|
@@ -503,7 +521,7 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
503
521
|
request_id: str,
|
|
504
522
|
agent_version: str,
|
|
505
523
|
user_id: str | None = None,
|
|
506
|
-
) -> tuple[list[UserPlaybook], list[int]]:
|
|
524
|
+
) -> tuple[list[UserPlaybook], list[int], list[tuple[int, list[int]]]]:
|
|
507
525
|
"""
|
|
508
526
|
Consolidate user playbook entries across extractors and against existing entries in DB.
|
|
509
527
|
|
|
@@ -514,7 +532,18 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
514
532
|
user_id: Optional user ID to scope the existing entry search
|
|
515
533
|
|
|
516
534
|
Returns:
|
|
517
|
-
Tuple of (consolidated entries,
|
|
535
|
+
Tuple of ``(consolidated entries, existing ids to delete after save,
|
|
536
|
+
merge_groups)``. ``merge_groups`` is a list of
|
|
537
|
+
``(survivor_index, source_existing_ids)`` where ``survivor_index``
|
|
538
|
+
indexes into the returned entries list and identifies the row that
|
|
539
|
+
supersedes the given existing source ids (one entry per ``unify``
|
|
540
|
+
decision that archives at least one existing row). Callers persist
|
|
541
|
+
the entries first (assigning survivor ids), then route each merge
|
|
542
|
+
group through ``storage.merge_records`` so each source becomes a
|
|
543
|
+
MERGED tombstone pointing at its survivor. The "existing ids to
|
|
544
|
+
delete" set still includes ALL archived ids (merge sources +
|
|
545
|
+
non-merge archives such as ``differentiate``'s split source); the
|
|
546
|
+
caller subtracts the merge-covered ids to find pure-delete leftovers.
|
|
518
547
|
"""
|
|
519
548
|
# Check if mock mode is enabled
|
|
520
549
|
if os.getenv("MOCK_LLM_RESPONSE", "").lower() == "true":
|
|
@@ -523,7 +552,7 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
523
552
|
for result in results:
|
|
524
553
|
if isinstance(result, list):
|
|
525
554
|
all_playbooks.extend(result)
|
|
526
|
-
return all_playbooks, []
|
|
555
|
+
return all_playbooks, [], []
|
|
527
556
|
|
|
528
557
|
# Flatten all new entries
|
|
529
558
|
new_playbooks: list[UserPlaybook] = []
|
|
@@ -532,7 +561,7 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
532
561
|
new_playbooks.extend(result)
|
|
533
562
|
|
|
534
563
|
if not new_playbooks:
|
|
535
|
-
return [], []
|
|
564
|
+
return [], [], []
|
|
536
565
|
|
|
537
566
|
# Retrieve existing entries via hybrid search
|
|
538
567
|
existing_playbooks = self._retrieve_existing_playbooks(
|
|
@@ -555,13 +584,13 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
555
584
|
error_type=type(e).__name__,
|
|
556
585
|
):
|
|
557
586
|
logger.exception("Failed to identify duplicates")
|
|
558
|
-
return new_playbooks, []
|
|
587
|
+
return new_playbooks, [], []
|
|
559
588
|
|
|
560
589
|
if not dedup_output.decisions:
|
|
561
590
|
logger.info(
|
|
562
591
|
"No consolidation decisions returned for request %s", request_id
|
|
563
592
|
)
|
|
564
|
-
return new_playbooks, []
|
|
593
|
+
return new_playbooks, [], []
|
|
565
594
|
|
|
566
595
|
logger.info(
|
|
567
596
|
"Received %d consolidation decisions for request %s",
|
|
@@ -589,7 +618,7 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
589
618
|
dedup_output: PlaybookConsolidationOutput,
|
|
590
619
|
request_id: str,
|
|
591
620
|
agent_version: str, # noqa: ARG002
|
|
592
|
-
) -> tuple[list[UserPlaybook], list[int]]:
|
|
621
|
+
) -> tuple[list[UserPlaybook], list[int], list[tuple[int, list[int]]]]:
|
|
593
622
|
"""
|
|
594
623
|
Build the deduplicated entry list from LLM decisions.
|
|
595
624
|
|
|
@@ -606,7 +635,16 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
606
635
|
agent_version: Agent version (currently unused, kept for symmetry).
|
|
607
636
|
|
|
608
637
|
Returns:
|
|
609
|
-
Tuple of (entries ready to save, existing entry IDs to delete
|
|
638
|
+
Tuple of ``(entries ready to save, existing entry IDs to delete,
|
|
639
|
+
merge_groups)``. ``merge_groups`` carries one
|
|
640
|
+
``(survivor_index, source_existing_ids)`` per ``unify`` decision
|
|
641
|
+
that archives >= 1 existing row, where ``survivor_index`` indexes
|
|
642
|
+
into the returned entries list (the unified survivor row) and the
|
|
643
|
+
second element is the existing ids that decision supersedes. Only
|
|
644
|
+
``unify`` produces merge groups — it collapses N existing rows into
|
|
645
|
+
one survivor. ``differentiate`` archives its split source but emits
|
|
646
|
+
two rows (no single survivor), so its archived id appears in the
|
|
647
|
+
delete set but NOT in any merge group.
|
|
610
648
|
"""
|
|
611
649
|
candidates_by_id = {
|
|
612
650
|
f"NEW-{idx}": playbook for idx, playbook in enumerate(new_playbooks)
|
|
@@ -626,10 +664,11 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
626
664
|
seen_archive: set[int] = set()
|
|
627
665
|
new_rows: list[UserPlaybook] = []
|
|
628
666
|
handled_new_ids: set[str] = set()
|
|
667
|
+
merge_groups: list[tuple[int, list[int]]] = []
|
|
629
668
|
|
|
630
669
|
for decision in dedup_output.decisions:
|
|
631
670
|
try:
|
|
632
|
-
rows, marked_new_ids = self._apply_one(
|
|
671
|
+
rows, marked_new_ids, merge_source_ids = self._apply_one(
|
|
633
672
|
decision=decision,
|
|
634
673
|
candidates_by_id=candidates_by_id,
|
|
635
674
|
existing_by_id=existing_by_id,
|
|
@@ -659,10 +698,17 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
659
698
|
existing_id_str,
|
|
660
699
|
)
|
|
661
700
|
continue
|
|
701
|
+
# Record the merge group BEFORE extending: the unified survivor is
|
|
702
|
+
# the first (and only) row a ``unify`` decision emits, so its index
|
|
703
|
+
# in the final list is the current length of ``new_rows``.
|
|
704
|
+
if merge_source_ids:
|
|
705
|
+
merge_groups.append((len(new_rows), merge_source_ids))
|
|
662
706
|
new_rows.extend(rows)
|
|
663
707
|
handled_new_ids.update(marked_new_ids)
|
|
664
708
|
self._bump_counter(result_counters, decision.kind)
|
|
665
|
-
self._log_decision(
|
|
709
|
+
self._log_decision(
|
|
710
|
+
decision, candidates_by_id, existing_by_id, existing_by_position
|
|
711
|
+
)
|
|
666
712
|
|
|
667
713
|
# Safety fallback: add any NEW entries the LLM did not reference, so a
|
|
668
714
|
# misbehaving model cannot silently drop extracted playbooks.
|
|
@@ -684,7 +730,7 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
684
730
|
result_counters.failed_count,
|
|
685
731
|
)
|
|
686
732
|
|
|
687
|
-
return new_rows, archive_ids
|
|
733
|
+
return new_rows, archive_ids, merge_groups
|
|
688
734
|
|
|
689
735
|
def _apply_one(
|
|
690
736
|
self,
|
|
@@ -696,7 +742,7 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
696
742
|
archive_ids: list[int],
|
|
697
743
|
seen_archive: set[int],
|
|
698
744
|
request_id: str,
|
|
699
|
-
) -> tuple[list[UserPlaybook], list[str]]:
|
|
745
|
+
) -> tuple[list[UserPlaybook], list[str], list[int]]:
|
|
700
746
|
"""Dispatch a single decision to its kind-specific apply method.
|
|
701
747
|
|
|
702
748
|
Args:
|
|
@@ -712,9 +758,14 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
712
758
|
request_id: Request ID stamped onto newly-built rows.
|
|
713
759
|
|
|
714
760
|
Returns:
|
|
715
|
-
Tuple of ``(rows_to_insert, handled_new_ids)
|
|
716
|
-
|
|
717
|
-
decision (used to suppress the safety fallback).
|
|
761
|
+
Tuple of ``(rows_to_insert, handled_new_ids, merge_source_ids)``.
|
|
762
|
+
``handled_new_ids`` is the set of ``"NEW-N"`` candidate ids consumed
|
|
763
|
+
by this decision (used to suppress the safety fallback).
|
|
764
|
+
``merge_source_ids`` is non-empty ONLY for a ``unify`` decision that
|
|
765
|
+
collapses >= 1 existing row into its single survivor (the first row
|
|
766
|
+
in ``rows_to_insert``); for all other kinds it is ``[]`` because they
|
|
767
|
+
either archive nothing or split into multiple rows with no single
|
|
768
|
+
survivor.
|
|
718
769
|
"""
|
|
719
770
|
if isinstance(decision, UnifyDecision):
|
|
720
771
|
return self._apply_unify(
|
|
@@ -729,12 +780,14 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
729
780
|
return self._apply_reject_new(
|
|
730
781
|
decision,
|
|
731
782
|
existing_by_id=existing_by_id,
|
|
783
|
+
existing_by_position=existing_by_position,
|
|
732
784
|
)
|
|
733
785
|
if isinstance(decision, DifferentiateDecision):
|
|
734
786
|
return self._apply_differentiate(
|
|
735
787
|
decision,
|
|
736
788
|
candidates_by_id=candidates_by_id,
|
|
737
789
|
existing_by_id=existing_by_id,
|
|
790
|
+
existing_by_position=existing_by_position,
|
|
738
791
|
archive_ids=archive_ids,
|
|
739
792
|
seen_archive=seen_archive,
|
|
740
793
|
request_id=request_id,
|
|
@@ -752,7 +805,7 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
752
805
|
archive_ids: list[int],
|
|
753
806
|
seen_archive: set[int],
|
|
754
807
|
request_id: str,
|
|
755
|
-
) -> tuple[list[UserPlaybook], list[str]]:
|
|
808
|
+
) -> tuple[list[UserPlaybook], list[str], list[int]]:
|
|
756
809
|
"""Collapse / compose NEW (+ 0..N EXISTING) into one row.
|
|
757
810
|
|
|
758
811
|
Looks up each ``archive_existing_ids`` entry by position
|
|
@@ -774,7 +827,11 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
774
827
|
request_id: Request ID stamped on the unified row.
|
|
775
828
|
|
|
776
829
|
Returns:
|
|
777
|
-
Tuple of ([unified_row], [consumed NEW-N ids])
|
|
830
|
+
Tuple of ``([unified_row], [consumed NEW-N ids], merge_source_ids)``
|
|
831
|
+
where ``merge_source_ids`` are the existing ids collapsed into the
|
|
832
|
+
unified survivor (the returned row). The survivor identity is not
|
|
833
|
+
known until the caller persists the row and reads its assigned id,
|
|
834
|
+
so the merge is materialized by the caller, not here.
|
|
778
835
|
|
|
779
836
|
Raises:
|
|
780
837
|
KeyError: If ``decision.new_id`` does not resolve to a known
|
|
@@ -795,11 +852,14 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
795
852
|
)
|
|
796
853
|
existing_members.append(existing)
|
|
797
854
|
|
|
855
|
+
merge_source_ids: list[int] = []
|
|
798
856
|
for existing in existing_members:
|
|
799
857
|
pid = existing.user_playbook_id
|
|
800
858
|
if pid and pid not in seen_archive:
|
|
801
859
|
seen_archive.add(pid)
|
|
802
860
|
archive_ids.append(pid)
|
|
861
|
+
if pid:
|
|
862
|
+
merge_source_ids.append(pid)
|
|
803
863
|
|
|
804
864
|
budget = self._dedup_config.max_unified_content_chars
|
|
805
865
|
content_len = len(decision.content)
|
|
@@ -830,43 +890,54 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
830
890
|
source=candidate.source,
|
|
831
891
|
source_interaction_ids=combined_source_ids,
|
|
832
892
|
)
|
|
833
|
-
return [unified_row], [decision.new_id]
|
|
893
|
+
return [unified_row], [decision.new_id], merge_source_ids
|
|
834
894
|
|
|
835
895
|
def _apply_reject_new(
|
|
836
896
|
self,
|
|
837
897
|
decision: RejectNewDecision,
|
|
838
898
|
*,
|
|
839
899
|
existing_by_id: dict[int, UserPlaybook],
|
|
840
|
-
|
|
900
|
+
existing_by_position: dict[str, UserPlaybook],
|
|
901
|
+
) -> tuple[list[UserPlaybook], list[str], list[int]]:
|
|
841
902
|
"""No-op apply: the existing row wins and the new candidate is dropped.
|
|
842
903
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
904
|
+
Resolve the integer against the rendered ``EXISTING-N`` position first,
|
|
905
|
+
then as a DB ``user_playbook_id`` for backwards compatibility. If it
|
|
906
|
+
does not resolve to a known existing row, the decision is treated as
|
|
907
|
+
malformed: we log a warning and return ``([], [])`` so the safety
|
|
908
|
+
fallback re-inserts the candidate rather than silently dropping
|
|
909
|
+
extracted data.
|
|
847
910
|
|
|
848
911
|
Args:
|
|
849
912
|
decision: The ``RejectNewDecision`` to apply.
|
|
850
913
|
existing_by_id: Mapping ``user_playbook_id`` -> existing playbook,
|
|
851
|
-
used
|
|
914
|
+
used as a fallback for ``decision.superseded_by_existing_id``.
|
|
915
|
+
existing_by_position: Mapping ``"EXISTING-M"`` -> existing playbook.
|
|
852
916
|
|
|
853
917
|
Returns:
|
|
854
|
-
Tuple of ([], [consumed NEW-N id]) when the existing id
|
|
855
|
-
or ``([], [])`` when the existing id is unknown.
|
|
918
|
+
Tuple of ``([], [consumed NEW-N id], [])`` when the existing id
|
|
919
|
+
resolves, or ``([], [], [])`` when the existing id is unknown.
|
|
920
|
+
Never produces a merge group — the existing row is kept as-is (no
|
|
921
|
+
archive, no survivor).
|
|
856
922
|
"""
|
|
857
|
-
|
|
923
|
+
existing = self._resolve_existing_reference(
|
|
924
|
+
decision.superseded_by_existing_id,
|
|
925
|
+
existing_by_position=existing_by_position,
|
|
926
|
+
existing_by_id=existing_by_id,
|
|
927
|
+
)
|
|
928
|
+
if existing is None:
|
|
858
929
|
logger.warning(
|
|
859
930
|
"event=consolidation_reject_new_invalid new_id=%s existing_id=%d",
|
|
860
931
|
decision.new_id,
|
|
861
932
|
decision.superseded_by_existing_id,
|
|
862
933
|
)
|
|
863
|
-
return [], []
|
|
934
|
+
return [], [], []
|
|
864
935
|
logger.info(
|
|
865
936
|
"event=consolidation_reject_new new_id=%s existing_id=%d",
|
|
866
937
|
decision.new_id,
|
|
867
938
|
decision.superseded_by_existing_id,
|
|
868
939
|
)
|
|
869
|
-
return [], [decision.new_id]
|
|
940
|
+
return [], [decision.new_id], []
|
|
870
941
|
|
|
871
942
|
def _apply_differentiate(
|
|
872
943
|
self,
|
|
@@ -874,10 +945,11 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
874
945
|
*,
|
|
875
946
|
candidates_by_id: dict[str, UserPlaybook],
|
|
876
947
|
existing_by_id: dict[int, UserPlaybook],
|
|
948
|
+
existing_by_position: dict[str, UserPlaybook],
|
|
877
949
|
archive_ids: list[int],
|
|
878
950
|
seen_archive: set[int],
|
|
879
951
|
request_id: str,
|
|
880
|
-
) -> tuple[list[UserPlaybook], list[str]]:
|
|
952
|
+
) -> tuple[list[UserPlaybook], list[str], list[int]]:
|
|
881
953
|
"""Archive the existing row and emit two refined rows in its place.
|
|
882
954
|
|
|
883
955
|
Builds one ``UserPlaybook`` from the candidate's content/polarity with
|
|
@@ -889,27 +961,36 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
889
961
|
decision: The ``DifferentiateDecision`` to apply.
|
|
890
962
|
candidates_by_id: Mapping ``"NEW-N"`` -> candidate playbook.
|
|
891
963
|
existing_by_id: Mapping ``user_playbook_id`` -> existing playbook.
|
|
964
|
+
existing_by_position: Mapping ``"EXISTING-M"`` -> existing playbook.
|
|
892
965
|
archive_ids: Accumulator mutated with the existing id to archive.
|
|
893
966
|
seen_archive: Dedup set for ``archive_ids``.
|
|
894
967
|
request_id: Request ID stamped on both new rows.
|
|
895
968
|
|
|
896
969
|
Returns:
|
|
897
|
-
Tuple of ([refined_new_row, refined_existing_row], [NEW-N id]
|
|
970
|
+
Tuple of ``([refined_new_row, refined_existing_row], [NEW-N id],
|
|
971
|
+
[])``. ``differentiate`` is a SPLIT, not a merge: the existing row
|
|
972
|
+
is archived but maps to no single survivor, so it produces NO merge
|
|
973
|
+
group (its archived id is a pure-delete leftover for the caller).
|
|
898
974
|
"""
|
|
899
975
|
candidate = candidates_by_id.get(decision.new_id)
|
|
900
976
|
if candidate is None:
|
|
901
977
|
raise KeyError(
|
|
902
978
|
f"differentiate references unknown NEW id: {decision.new_id}"
|
|
903
979
|
)
|
|
904
|
-
existing =
|
|
980
|
+
existing = self._resolve_existing_reference(
|
|
981
|
+
decision.existing_id,
|
|
982
|
+
existing_by_position=existing_by_position,
|
|
983
|
+
existing_by_id=existing_by_id,
|
|
984
|
+
)
|
|
905
985
|
if existing is None:
|
|
906
986
|
raise KeyError(
|
|
907
987
|
f"differentiate references unknown EXISTING id: {decision.existing_id}"
|
|
908
988
|
)
|
|
909
989
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
990
|
+
existing_db_id = existing.user_playbook_id
|
|
991
|
+
if existing_db_id and existing_db_id not in seen_archive:
|
|
992
|
+
seen_archive.add(existing_db_id)
|
|
993
|
+
archive_ids.append(existing_db_id)
|
|
913
994
|
|
|
914
995
|
now_ts = int(datetime.now(UTC).timestamp())
|
|
915
996
|
refined_candidate = candidate.model_copy(
|
|
@@ -929,14 +1010,14 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
929
1010
|
"source_interaction_ids": list(existing.source_interaction_ids),
|
|
930
1011
|
}
|
|
931
1012
|
)
|
|
932
|
-
return [refined_candidate, refined_existing], [decision.new_id]
|
|
1013
|
+
return [refined_candidate, refined_existing], [decision.new_id], []
|
|
933
1014
|
|
|
934
1015
|
def _apply_independent(
|
|
935
1016
|
self,
|
|
936
1017
|
decision: IndependentDecision,
|
|
937
1018
|
*,
|
|
938
1019
|
candidates_by_id: dict[str, UserPlaybook],
|
|
939
|
-
) -> tuple[list[UserPlaybook], list[str]]:
|
|
1020
|
+
) -> tuple[list[UserPlaybook], list[str], list[int]]:
|
|
940
1021
|
"""Insert the new candidate unchanged; no archive.
|
|
941
1022
|
|
|
942
1023
|
Args:
|
|
@@ -944,12 +1025,13 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
944
1025
|
candidates_by_id: Mapping ``"NEW-N"`` -> candidate playbook.
|
|
945
1026
|
|
|
946
1027
|
Returns:
|
|
947
|
-
Tuple of ([candidate row], [consumed NEW-N id])
|
|
1028
|
+
Tuple of ``([candidate row], [consumed NEW-N id], [])`` — no archive,
|
|
1029
|
+
so never a merge group.
|
|
948
1030
|
"""
|
|
949
1031
|
candidate = candidates_by_id.get(decision.new_id)
|
|
950
1032
|
if candidate is None:
|
|
951
1033
|
raise KeyError(f"independent references unknown NEW id: {decision.new_id}")
|
|
952
|
-
return [candidate], [decision.new_id]
|
|
1034
|
+
return [candidate], [decision.new_id], []
|
|
953
1035
|
|
|
954
1036
|
@staticmethod
|
|
955
1037
|
def _merge_source_ids(playbooks: list[UserPlaybook]) -> list[int]:
|
|
@@ -970,6 +1052,25 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
970
1052
|
combined.append(sid)
|
|
971
1053
|
return combined
|
|
972
1054
|
|
|
1055
|
+
@staticmethod
|
|
1056
|
+
def _resolve_existing_reference(
|
|
1057
|
+
raw_id: int,
|
|
1058
|
+
*,
|
|
1059
|
+
existing_by_position: dict[str, UserPlaybook],
|
|
1060
|
+
existing_by_id: dict[int, UserPlaybook],
|
|
1061
|
+
) -> UserPlaybook | None:
|
|
1062
|
+
"""Resolve an LLM existing-row integer.
|
|
1063
|
+
|
|
1064
|
+
The rendered prompt labels rows as ``EXISTING-N`` and asks the model to
|
|
1065
|
+
emit bare integers, so position is the primary interpretation. DB id is
|
|
1066
|
+
retained as a compatibility fallback for older prompt outputs.
|
|
1067
|
+
"""
|
|
1068
|
+
if 0 <= raw_id < len(existing_by_position):
|
|
1069
|
+
existing = existing_by_position.get(f"EXISTING-{raw_id}")
|
|
1070
|
+
if existing is not None:
|
|
1071
|
+
return existing
|
|
1072
|
+
return existing_by_id.get(raw_id)
|
|
1073
|
+
|
|
973
1074
|
@staticmethod
|
|
974
1075
|
def _bump_counter(result: PlaybookConsolidationResult, kind: str) -> None:
|
|
975
1076
|
"""Increment the per-kind counter on ``result`` for a successful apply.
|
|
@@ -987,6 +1088,7 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
987
1088
|
decision: ConsolidationDecision,
|
|
988
1089
|
candidates_by_id: dict[str, UserPlaybook],
|
|
989
1090
|
existing_by_id: dict[int, UserPlaybook],
|
|
1091
|
+
existing_by_position: dict[str, UserPlaybook],
|
|
990
1092
|
) -> None:
|
|
991
1093
|
"""Emit a structured per-decision log line for probe ingest.
|
|
992
1094
|
|
|
@@ -1029,7 +1131,11 @@ class PlaybookConsolidator(BaseDeduplicator):
|
|
|
1029
1131
|
"existing_id",
|
|
1030
1132
|
getattr(decision, "superseded_by_existing_id", 0),
|
|
1031
1133
|
)
|
|
1032
|
-
existing_pb =
|
|
1134
|
+
existing_pb = PlaybookConsolidator._resolve_existing_reference(
|
|
1135
|
+
existing_id_raw,
|
|
1136
|
+
existing_by_position=existing_by_position,
|
|
1137
|
+
existing_by_id=existing_by_id,
|
|
1138
|
+
)
|
|
1033
1139
|
trigger_match = (
|
|
1034
1140
|
new_pb is not None
|
|
1035
1141
|
and existing_pb is not None
|
|
@@ -10,6 +10,7 @@ from reflexio.models.config_schema import PlaybookConfig
|
|
|
10
10
|
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
11
11
|
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
12
12
|
from reflexio.server.llm.model_defaults import ModelRole, resolve_model_name
|
|
13
|
+
from reflexio.server.llm.token_accounting import RunTokenTotals, sum_trace_tokens
|
|
13
14
|
from reflexio.server.services.extraction.outcome import ExtractionOutcome
|
|
14
15
|
from reflexio.server.services.extraction.resumable_agent import (
|
|
15
16
|
run_resumable_extraction_agent,
|
|
@@ -35,7 +36,7 @@ from reflexio.server.services.service_utils import (
|
|
|
35
36
|
from reflexio.server.site_var.site_var_manager import SiteVarManager
|
|
36
37
|
|
|
37
38
|
if TYPE_CHECKING:
|
|
38
|
-
from reflexio.server.services.playbook.
|
|
39
|
+
from reflexio.server.services.playbook.service import (
|
|
39
40
|
PlaybookGenerationServiceConfig,
|
|
40
41
|
)
|
|
41
42
|
|
|
@@ -79,6 +80,7 @@ class PlaybookExtractor:
|
|
|
79
80
|
self.service_config: PlaybookGenerationServiceConfig = service_config
|
|
80
81
|
self.agent_context: str = agent_context
|
|
81
82
|
self._last_resumable_run_id: str | None = None
|
|
83
|
+
self._last_resumable_token_totals: RunTokenTotals | None = None
|
|
82
84
|
|
|
83
85
|
# Get LLM config overrides from configuration
|
|
84
86
|
config = self.request_context.configurator.get_config()
|
|
@@ -223,6 +225,7 @@ class PlaybookExtractor:
|
|
|
223
225
|
return ExtractionOutcome.completed(
|
|
224
226
|
user_playbooks,
|
|
225
227
|
run_id=self._last_resumable_run_id,
|
|
228
|
+
token_totals=self._last_resumable_token_totals,
|
|
226
229
|
)
|
|
227
230
|
return user_playbooks
|
|
228
231
|
|
|
@@ -318,6 +321,7 @@ class PlaybookExtractor:
|
|
|
318
321
|
log_label="Playbook extraction",
|
|
319
322
|
)
|
|
320
323
|
self._last_resumable_run_id = result.run_id
|
|
324
|
+
self._last_resumable_token_totals = sum_trace_tokens(result.trace)
|
|
321
325
|
if not isinstance(result.output, StructuredPlaybookList):
|
|
322
326
|
logger.warning(
|
|
323
327
|
"Playbook extraction did not finish: %s",
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Shared atomic supersede primitive for applying a playbook edit.
|
|
2
|
+
|
|
3
|
+
Extracted from ReflectionService._replace_playbook so online and background
|
|
4
|
+
playbook repair paths share one lifecycle (insert-then-supersede, no orphan).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from reflexio.models.api_schema.domain.entities import LineageContext, UserPlaybook
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from reflexio.server.services.storage.storage_base import BaseStorage
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def apply_playbook_edit(
|
|
16
|
+
storage: "BaseStorage",
|
|
17
|
+
*,
|
|
18
|
+
incumbent_id: int,
|
|
19
|
+
new_playbook: UserPlaybook,
|
|
20
|
+
source: str,
|
|
21
|
+
request_id: str,
|
|
22
|
+
) -> int:
|
|
23
|
+
"""Insert a replacement playbook then atomically supersede the incumbent.
|
|
24
|
+
|
|
25
|
+
Uses ``storage.supersede_record`` (atomic conditional CAS) so a lost race
|
|
26
|
+
never leaves an orphan CURRENT row:
|
|
27
|
+
|
|
28
|
+
- Insert the new playbook as CURRENT.
|
|
29
|
+
- Call ``supersede_record(incumbent_id → new_id)``, which only succeeds when
|
|
30
|
+
the incumbent is still CURRENT (``status IS NULL``).
|
|
31
|
+
- If ``supersede_record`` returns ``False`` (incumbent already gone), delete
|
|
32
|
+
the just-inserted successor and return ``-1``.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
storage: A BaseStorage instance providing ``save_user_playbooks``,
|
|
36
|
+
``supersede_record``, and ``delete_user_playbooks_by_ids``.
|
|
37
|
+
incumbent_id: ``user_playbook_id`` of the playbook being replaced.
|
|
38
|
+
new_playbook: The replacement playbook (inserted as CURRENT, i.e.
|
|
39
|
+
``status=None``).
|
|
40
|
+
source: Provenance label stored on the new playbook row and in the
|
|
41
|
+
lineage event actor field.
|
|
42
|
+
request_id: Operation-run correlation id for the lineage event. Must be
|
|
43
|
+
non-empty; use the reflection run id (``ReflectionServiceRequest.request_id``)
|
|
44
|
+
or another operation-scoped id. Raises ``ValueError`` immediately
|
|
45
|
+
(before any storage write) when empty, preventing orphaned successor rows.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The ``user_playbook_id`` of the newly inserted playbook, or ``-1`` if
|
|
49
|
+
the incumbent was not CURRENT (no mutation; no orphan left behind).
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If ``request_id`` is empty or None.
|
|
53
|
+
"""
|
|
54
|
+
if not request_id:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"apply_playbook_edit: request_id must be non-empty (operation-run correlation id)"
|
|
57
|
+
)
|
|
58
|
+
new_playbook.source = source
|
|
59
|
+
storage.save_user_playbooks([new_playbook])
|
|
60
|
+
new_id: int = new_playbook.user_playbook_id
|
|
61
|
+
|
|
62
|
+
ctx = LineageContext(op_kind="revise", actor=source, request_id=request_id)
|
|
63
|
+
superseded = storage.supersede_record(
|
|
64
|
+
entity_type="user_playbook",
|
|
65
|
+
incumbent_id=str(incumbent_id),
|
|
66
|
+
successor_id=str(new_id),
|
|
67
|
+
context=ctx,
|
|
68
|
+
)
|
|
69
|
+
if not superseded:
|
|
70
|
+
# lost the race: delete the just-inserted successor so no orphan CURRENT row
|
|
71
|
+
# remains. It was never live, so this is a rollback — not an audited erasure.
|
|
72
|
+
storage.delete_user_playbooks_by_ids([new_id], emit_hard_delete=False)
|
|
73
|
+
return -1
|
|
74
|
+
return new_id
|