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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import sqlite3
|
|
5
|
+
import uuid
|
|
5
6
|
from concurrent.futures import ThreadPoolExecutor
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
@@ -24,6 +25,7 @@ from reflexio.server.llm.providers.embedding_service_provider import (
|
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
from ._base import (
|
|
28
|
+
_TOMBSTONE_STATUS_VALUES,
|
|
27
29
|
SQLiteStorageBase,
|
|
28
30
|
_build_status_sql,
|
|
29
31
|
_effective_search_mode,
|
|
@@ -37,6 +39,40 @@ from ._base import (
|
|
|
37
39
|
_true_rrf_merge,
|
|
38
40
|
_vector_rank_rows,
|
|
39
41
|
)
|
|
42
|
+
from ._lineage import _GC_ELIGIBLE_STATUSES, _append_event_stmt
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _emit_hard_delete_profile(
|
|
46
|
+
conn: sqlite3.Connection,
|
|
47
|
+
*,
|
|
48
|
+
org_id: str,
|
|
49
|
+
entity_id: str,
|
|
50
|
+
request_id: str,
|
|
51
|
+
actor: str = "api",
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Emit a single hard_delete lineage event for a profile entity."""
|
|
54
|
+
_append_event_stmt(
|
|
55
|
+
conn,
|
|
56
|
+
org_id=org_id,
|
|
57
|
+
entity_type="profile",
|
|
58
|
+
entity_id=entity_id,
|
|
59
|
+
op="hard_delete",
|
|
60
|
+
prov="wasInvalidatedBy",
|
|
61
|
+
source_ids=[],
|
|
62
|
+
actor=actor,
|
|
63
|
+
request_id=request_id,
|
|
64
|
+
reason="erasure",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _build_tags_sql(alias: str, tags: list[str] | None) -> tuple[str, list[Any]]:
|
|
69
|
+
if not tags:
|
|
70
|
+
return "", []
|
|
71
|
+
placeholders = ",".join("?" for _ in tags)
|
|
72
|
+
return (
|
|
73
|
+
f"EXISTS (SELECT 1 FROM json_each({alias}.tags) WHERE value IN ({placeholders}))",
|
|
74
|
+
list(tags),
|
|
75
|
+
)
|
|
40
76
|
|
|
41
77
|
|
|
42
78
|
class ProfileMixin:
|
|
@@ -45,6 +81,7 @@ class ProfileMixin:
|
|
|
45
81
|
# Type hints for instance attributes/methods provided by SQLiteStorageBase via MRO
|
|
46
82
|
_lock: Any
|
|
47
83
|
conn: sqlite3.Connection
|
|
84
|
+
org_id: str
|
|
48
85
|
_execute: Any
|
|
49
86
|
_fetchone: Any
|
|
50
87
|
_fetchall: Any
|
|
@@ -57,6 +94,8 @@ class ProfileMixin:
|
|
|
57
94
|
_fts_delete_profile: Any
|
|
58
95
|
_vec_upsert: Any
|
|
59
96
|
_vec_delete: Any
|
|
97
|
+
_delete_profile_search_rows: Any
|
|
98
|
+
_delete_in_chunks: Any
|
|
60
99
|
_has_sqlite_vec: bool
|
|
61
100
|
llm_client: Any
|
|
62
101
|
embedding_model_name: str
|
|
@@ -84,13 +123,19 @@ class ProfileMixin:
|
|
|
84
123
|
self,
|
|
85
124
|
user_id: str,
|
|
86
125
|
status_filter: list[Status | None] | None = None,
|
|
126
|
+
tags: list[str] | None = None,
|
|
87
127
|
) -> list[UserProfile]:
|
|
88
128
|
if status_filter is None:
|
|
89
129
|
status_filter = [None]
|
|
90
130
|
current_ts = _epoch_now()
|
|
91
131
|
frag, params = _build_status_sql(status_filter)
|
|
92
|
-
|
|
132
|
+
conditions = ["user_id = ?", "expiration_timestamp >= ?", frag]
|
|
93
133
|
all_params: list[Any] = [user_id, current_ts, *params]
|
|
134
|
+
tag_frag, tag_params = _build_tags_sql("profiles", tags)
|
|
135
|
+
if tag_frag:
|
|
136
|
+
conditions.append(tag_frag)
|
|
137
|
+
all_params.extend(tag_params)
|
|
138
|
+
sql = f"SELECT * FROM profiles WHERE {' AND '.join(conditions)}"
|
|
94
139
|
return [_row_to_profile(r) for r in self._fetchall(sql, all_params)]
|
|
95
140
|
|
|
96
141
|
@SQLiteStorageBase.handle_exceptions
|
|
@@ -111,9 +156,10 @@ class ProfileMixin:
|
|
|
111
156
|
(profile_id, user_id, content, last_modified_timestamp,
|
|
112
157
|
generated_from_request_id, profile_time_to_live,
|
|
113
158
|
expiration_timestamp, custom_features, embedding, source,
|
|
114
|
-
|
|
115
|
-
source_span, notes, reader_angle, created_at
|
|
116
|
-
|
|
159
|
+
status, extractor_names, expanded_terms,
|
|
160
|
+
source_span, notes, reader_angle, tags, source_interaction_ids, created_at,
|
|
161
|
+
merged_into, superseded_by)
|
|
162
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
117
163
|
(
|
|
118
164
|
profile.profile_id,
|
|
119
165
|
profile.user_id,
|
|
@@ -131,7 +177,11 @@ class ProfileMixin:
|
|
|
131
177
|
profile.source_span,
|
|
132
178
|
profile.notes,
|
|
133
179
|
profile.reader_angle,
|
|
180
|
+
_json_dumps(profile.tags),
|
|
181
|
+
_json_dumps(profile.source_interaction_ids),
|
|
134
182
|
_iso_now(),
|
|
183
|
+
profile.merged_into,
|
|
184
|
+
profile.superseded_by,
|
|
135
185
|
),
|
|
136
186
|
)
|
|
137
187
|
fts_parts = [profile.content or ""]
|
|
@@ -152,88 +202,171 @@ class ProfileMixin:
|
|
|
152
202
|
def update_user_profile_by_id(
|
|
153
203
|
self, user_id: str, profile_id: str, new_profile: UserProfile
|
|
154
204
|
) -> None:
|
|
205
|
+
"""Replace a profile's content in-place and emit a revise lineage event.
|
|
206
|
+
|
|
207
|
+
Each call generates a fresh request_id so every edit is a distinct audit
|
|
208
|
+
event (not collapsed by the idempotency key). The UPDATE, lineage event,
|
|
209
|
+
FTS sync, and vec sync are all executed inside a single lock acquisition;
|
|
210
|
+
self._lock is an RLock so the inner _fts_upsert_profile/_vec_upsert calls
|
|
211
|
+
that re-acquire it are safe.
|
|
212
|
+
"""
|
|
155
213
|
current_ts = _epoch_now()
|
|
156
214
|
row = self._fetchone(
|
|
157
215
|
"SELECT profile_id FROM profiles WHERE user_id = ? AND profile_id = ? AND expiration_timestamp >= ?",
|
|
158
216
|
(user_id, profile_id, current_ts),
|
|
159
217
|
)
|
|
160
218
|
if not row:
|
|
161
|
-
import logging
|
|
162
|
-
|
|
163
|
-
logger = logging.getLogger(__name__)
|
|
164
219
|
logger.warning("User profile not found for user id: %s", user_id)
|
|
165
220
|
return
|
|
166
221
|
embedding = self._get_embedding(
|
|
167
222
|
"\n".join([new_profile.content, str(new_profile.custom_features)])
|
|
168
223
|
)
|
|
169
224
|
new_profile.embedding = embedding
|
|
225
|
+
with self._lock:
|
|
226
|
+
cur = self.conn.execute(
|
|
227
|
+
"""UPDATE profiles SET content=?, last_modified_timestamp=?,
|
|
228
|
+
generated_from_request_id=?, profile_time_to_live=?,
|
|
229
|
+
expiration_timestamp=?, custom_features=?, embedding=?,
|
|
230
|
+
source=?, status=?, extractor_names=?, expanded_terms=?,
|
|
231
|
+
source_span=?, notes=?, reader_angle=?, tags=?, source_interaction_ids=?
|
|
232
|
+
WHERE profile_id=?""",
|
|
233
|
+
(
|
|
234
|
+
new_profile.content,
|
|
235
|
+
new_profile.last_modified_timestamp,
|
|
236
|
+
new_profile.generated_from_request_id,
|
|
237
|
+
new_profile.profile_time_to_live.value,
|
|
238
|
+
new_profile.expiration_timestamp,
|
|
239
|
+
_json_dumps(new_profile.custom_features),
|
|
240
|
+
_json_dumps(new_profile.embedding),
|
|
241
|
+
new_profile.source,
|
|
242
|
+
new_profile.status.value if new_profile.status else None,
|
|
243
|
+
_json_dumps(new_profile.extractor_names),
|
|
244
|
+
new_profile.expanded_terms,
|
|
245
|
+
new_profile.source_span,
|
|
246
|
+
new_profile.notes,
|
|
247
|
+
new_profile.reader_angle,
|
|
248
|
+
_json_dumps(new_profile.tags),
|
|
249
|
+
_json_dumps(new_profile.source_interaction_ids),
|
|
250
|
+
profile_id,
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
if cur.rowcount > 0:
|
|
254
|
+
_append_event_stmt(
|
|
255
|
+
self.conn,
|
|
256
|
+
org_id=self.org_id,
|
|
257
|
+
entity_type="profile",
|
|
258
|
+
entity_id=str(profile_id),
|
|
259
|
+
op="revise",
|
|
260
|
+
prov="wasRevisionOf",
|
|
261
|
+
source_ids=[],
|
|
262
|
+
actor="api",
|
|
263
|
+
request_id=uuid.uuid4().hex,
|
|
264
|
+
reason="in-place update",
|
|
265
|
+
)
|
|
266
|
+
self.conn.commit()
|
|
267
|
+
fts_parts = [new_profile.content or ""]
|
|
268
|
+
if new_profile.custom_features:
|
|
269
|
+
fts_parts.extend(
|
|
270
|
+
str(v) for v in new_profile.custom_features.values() if v
|
|
271
|
+
)
|
|
272
|
+
if new_profile.expanded_terms:
|
|
273
|
+
fts_parts.append(new_profile.expanded_terms)
|
|
274
|
+
self._fts_upsert_profile(profile_id, " ".join(fts_parts))
|
|
275
|
+
rowid_row = self._fetchone(
|
|
276
|
+
"SELECT rowid FROM profiles WHERE profile_id = ?", (profile_id,)
|
|
277
|
+
)
|
|
278
|
+
if rowid_row and embedding:
|
|
279
|
+
self._vec_upsert("profiles_vec", rowid_row["rowid"], embedding)
|
|
280
|
+
|
|
281
|
+
@SQLiteStorageBase.handle_exceptions
|
|
282
|
+
def update_user_profile_tags(
|
|
283
|
+
self, user_id: str, profile_id: str, tags: list[str]
|
|
284
|
+
) -> None:
|
|
170
285
|
self._execute(
|
|
171
|
-
"
|
|
172
|
-
|
|
173
|
-
expiration_timestamp=?, custom_features=?, embedding=?,
|
|
174
|
-
source=?, status=?, extractor_names=?, expanded_terms=?,
|
|
175
|
-
source_span=?, notes=?, reader_angle=?
|
|
176
|
-
WHERE profile_id=?""",
|
|
177
|
-
(
|
|
178
|
-
new_profile.content,
|
|
179
|
-
new_profile.last_modified_timestamp,
|
|
180
|
-
new_profile.generated_from_request_id,
|
|
181
|
-
new_profile.profile_time_to_live.value,
|
|
182
|
-
new_profile.expiration_timestamp,
|
|
183
|
-
_json_dumps(new_profile.custom_features),
|
|
184
|
-
_json_dumps(new_profile.embedding),
|
|
185
|
-
new_profile.source,
|
|
186
|
-
new_profile.status.value if new_profile.status else None,
|
|
187
|
-
_json_dumps(new_profile.extractor_names),
|
|
188
|
-
new_profile.expanded_terms,
|
|
189
|
-
new_profile.source_span,
|
|
190
|
-
new_profile.notes,
|
|
191
|
-
new_profile.reader_angle,
|
|
192
|
-
profile_id,
|
|
193
|
-
),
|
|
286
|
+
"UPDATE profiles SET tags=? WHERE user_id=? AND profile_id=?",
|
|
287
|
+
(_json_dumps(tags), user_id, profile_id),
|
|
194
288
|
)
|
|
195
|
-
fts_parts = [new_profile.content or ""]
|
|
196
|
-
if new_profile.custom_features:
|
|
197
|
-
fts_parts.extend(str(v) for v in new_profile.custom_features.values() if v)
|
|
198
|
-
if new_profile.expanded_terms:
|
|
199
|
-
fts_parts.append(new_profile.expanded_terms)
|
|
200
|
-
self._fts_upsert_profile(profile_id, " ".join(fts_parts))
|
|
201
|
-
rowid_row = self._fetchone(
|
|
202
|
-
"SELECT rowid FROM profiles WHERE profile_id = ?", (profile_id,)
|
|
203
|
-
)
|
|
204
|
-
if rowid_row and embedding:
|
|
205
|
-
self._vec_upsert("profiles_vec", rowid_row["rowid"], embedding)
|
|
206
289
|
|
|
207
290
|
@SQLiteStorageBase.handle_exceptions
|
|
208
291
|
def delete_user_profile(self, request: DeleteUserProfileRequest) -> None:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
)
|
|
213
|
-
self.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
292
|
+
# Atomic: fts + vec + row + lineage in ONE lock/commit to prevent rowid reuse
|
|
293
|
+
# race. profiles uses implicit (reusable) rowid keyed by TEXT PK — a cleanup
|
|
294
|
+
# running after commit could race with a concurrent INSERT reusing the freed
|
|
295
|
+
# rowid and delete the NEW profile's vec row. (#196)
|
|
296
|
+
with self._lock:
|
|
297
|
+
rowid_row = self.conn.execute(
|
|
298
|
+
"SELECT rowid FROM profiles WHERE user_id = ? AND profile_id = ?",
|
|
299
|
+
(request.user_id, request.profile_id),
|
|
300
|
+
).fetchone()
|
|
301
|
+
if rowid_row is None:
|
|
302
|
+
return
|
|
303
|
+
self.conn.execute(
|
|
304
|
+
"DELETE FROM profiles_fts WHERE profile_id = ?",
|
|
305
|
+
(request.profile_id,),
|
|
306
|
+
)
|
|
307
|
+
if self._has_sqlite_vec and rowid_row:
|
|
308
|
+
self.conn.execute(
|
|
309
|
+
"DELETE FROM profiles_vec WHERE rowid = ?",
|
|
310
|
+
(rowid_row["rowid"],),
|
|
311
|
+
)
|
|
312
|
+
cur = self.conn.execute(
|
|
313
|
+
"DELETE FROM profiles WHERE user_id = ? AND profile_id = ?",
|
|
314
|
+
(request.user_id, request.profile_id),
|
|
315
|
+
)
|
|
316
|
+
if cur.rowcount > 0:
|
|
317
|
+
_emit_hard_delete_profile(
|
|
318
|
+
self.conn,
|
|
319
|
+
org_id=self.org_id,
|
|
320
|
+
entity_id=str(request.profile_id),
|
|
321
|
+
request_id=uuid.uuid4().hex,
|
|
322
|
+
)
|
|
323
|
+
self.conn.commit()
|
|
220
324
|
|
|
221
325
|
@SQLiteStorageBase.handle_exceptions
|
|
222
326
|
def delete_all_profiles_for_user(self, user_id: str) -> None:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
327
|
+
# Atomic: fts + vec + row + lineage in ONE lock/commit — rowid reuse race
|
|
328
|
+
# prevention (see delete_user_profile comment, #196).
|
|
329
|
+
batch_request_id = uuid.uuid4().hex
|
|
330
|
+
with self._lock:
|
|
331
|
+
rows = self.conn.execute(
|
|
332
|
+
"SELECT rowid, profile_id FROM profiles WHERE user_id = ?", (user_id,)
|
|
333
|
+
).fetchall()
|
|
334
|
+
if not rows:
|
|
335
|
+
return
|
|
336
|
+
pids = [r["profile_id"] for r in rows]
|
|
337
|
+
rowids = [r["rowid"] for r in rows]
|
|
338
|
+
self._delete_in_chunks("profiles_fts", "profile_id", pids)
|
|
339
|
+
if self._has_sqlite_vec and rowids:
|
|
340
|
+
self._delete_in_chunks("profiles_vec", "rowid", rowids)
|
|
341
|
+
self.conn.execute("DELETE FROM profiles WHERE user_id = ?", (user_id,))
|
|
342
|
+
for pid in pids:
|
|
343
|
+
_emit_hard_delete_profile(
|
|
344
|
+
self.conn,
|
|
345
|
+
org_id=self.org_id,
|
|
346
|
+
entity_id=str(pid),
|
|
347
|
+
request_id=batch_request_id,
|
|
348
|
+
)
|
|
349
|
+
self.conn.commit()
|
|
232
350
|
|
|
233
351
|
@SQLiteStorageBase.handle_exceptions
|
|
234
352
|
def delete_all_profiles(self) -> None:
|
|
353
|
+
# Also wipe profiles_vec (full-wipe variant of the rowid-race fix, #196).
|
|
354
|
+
batch_request_id = uuid.uuid4().hex
|
|
235
355
|
with self._lock:
|
|
356
|
+
pids = [
|
|
357
|
+
r["profile_id"]
|
|
358
|
+
for r in self.conn.execute("SELECT profile_id FROM profiles").fetchall()
|
|
359
|
+
]
|
|
360
|
+
for pid in pids:
|
|
361
|
+
_emit_hard_delete_profile(
|
|
362
|
+
self.conn,
|
|
363
|
+
org_id=self.org_id,
|
|
364
|
+
entity_id=str(pid),
|
|
365
|
+
request_id=batch_request_id,
|
|
366
|
+
)
|
|
236
367
|
self.conn.execute("DELETE FROM profiles_fts")
|
|
368
|
+
if self._has_sqlite_vec:
|
|
369
|
+
self.conn.execute("DELETE FROM profiles_vec")
|
|
237
370
|
self.conn.execute("DELETE FROM profiles")
|
|
238
371
|
self.conn.commit()
|
|
239
372
|
|
|
@@ -251,25 +384,60 @@ class ProfileMixin:
|
|
|
251
384
|
) -> int:
|
|
252
385
|
new_val = new_status.value if new_status else None
|
|
253
386
|
now_ts = _epoch_now()
|
|
254
|
-
|
|
387
|
+
old_val_str = old_status.value if old_status else "None"
|
|
388
|
+
new_val_str = new_status.value if new_status else "None"
|
|
389
|
+
reason = f"{old_val_str}->{new_val_str}"
|
|
255
390
|
|
|
256
391
|
if old_status is None or (
|
|
257
392
|
hasattr(old_status, "value") and old_status.value is None
|
|
258
393
|
):
|
|
259
394
|
where = "status IS NULL"
|
|
395
|
+
select_params: list[Any] = []
|
|
260
396
|
else:
|
|
261
397
|
where = "status = ?"
|
|
262
|
-
|
|
398
|
+
select_params = [old_status.value]
|
|
263
399
|
|
|
400
|
+
extra_params: list[Any] = []
|
|
264
401
|
if user_ids is not None:
|
|
265
402
|
placeholders = ",".join("?" for _ in user_ids)
|
|
266
403
|
where += f" AND user_id IN ({placeholders})"
|
|
267
|
-
|
|
404
|
+
extra_params.extend(user_ids)
|
|
268
405
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
406
|
+
# Set retired_at = now when transitioning to a GC-eligible status; clear to NULL otherwise.
|
|
407
|
+
retired_at_val = now_ts if new_val in _GC_ELIGIBLE_STATUSES else None
|
|
408
|
+
|
|
409
|
+
batch_request_id = uuid.uuid4().hex
|
|
410
|
+
with self._lock:
|
|
411
|
+
affected = [
|
|
412
|
+
r["profile_id"]
|
|
413
|
+
for r in self.conn.execute(
|
|
414
|
+
f"SELECT profile_id FROM profiles WHERE {where}",
|
|
415
|
+
select_params + extra_params,
|
|
416
|
+
).fetchall()
|
|
417
|
+
]
|
|
418
|
+
cur = self.conn.execute(
|
|
419
|
+
f"UPDATE profiles SET status = ?, last_modified_timestamp = ?, retired_at = ? WHERE {where}",
|
|
420
|
+
[new_val, now_ts, retired_at_val] + select_params + extra_params,
|
|
421
|
+
)
|
|
422
|
+
from_val = old_status.value if old_status else None
|
|
423
|
+
to_val = new_status.value if new_status else None
|
|
424
|
+
for pid in affected:
|
|
425
|
+
_append_event_stmt(
|
|
426
|
+
self.conn,
|
|
427
|
+
org_id=self.org_id,
|
|
428
|
+
entity_type="profile",
|
|
429
|
+
entity_id=str(pid),
|
|
430
|
+
op="status_change",
|
|
431
|
+
prov="wasInvalidatedBy",
|
|
432
|
+
source_ids=[],
|
|
433
|
+
actor="api",
|
|
434
|
+
request_id=batch_request_id,
|
|
435
|
+
reason=reason,
|
|
436
|
+
from_status=from_val,
|
|
437
|
+
to_status=to_val,
|
|
438
|
+
status_namespace="lifecycle_status",
|
|
439
|
+
)
|
|
440
|
+
self.conn.commit()
|
|
273
441
|
return cur.rowcount
|
|
274
442
|
|
|
275
443
|
@SQLiteStorageBase.handle_exceptions
|
|
@@ -295,26 +463,206 @@ class ProfileMixin:
|
|
|
295
463
|
return [_row_to_profile(r) for r in self._fetchall(sql, params)]
|
|
296
464
|
|
|
297
465
|
@SQLiteStorageBase.handle_exceptions
|
|
298
|
-
def
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
466
|
+
def get_profile_by_id(
|
|
467
|
+
self, profile_id: str, *, include_tombstones: bool = False
|
|
468
|
+
) -> UserProfile | None:
|
|
469
|
+
"""Fetch a single profile by primary key.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
profile_id: The profile's primary key.
|
|
473
|
+
include_tombstones: When False (default), MERGED/SUPERSEDED profiles
|
|
474
|
+
return None. Set to True for lineage resolution (resolve_current).
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
The UserProfile if found and not filtered, otherwise None.
|
|
478
|
+
"""
|
|
479
|
+
sql = "SELECT * FROM profiles WHERE profile_id = ?"
|
|
480
|
+
if not include_tombstones:
|
|
481
|
+
sql += " AND (status IS NULL OR status NOT IN (?, ?))"
|
|
482
|
+
row = self._fetchone(sql, (profile_id, *_TOMBSTONE_STATUS_VALUES))
|
|
483
|
+
else:
|
|
484
|
+
row = self._fetchone(sql, (profile_id,))
|
|
485
|
+
return _row_to_profile(row) if row else None
|
|
486
|
+
|
|
487
|
+
@SQLiteStorageBase.handle_exceptions
|
|
488
|
+
def get_distinct_generated_from_request_ids(self) -> list[str]:
|
|
489
|
+
"""Return DISTINCT non-empty generated_from_request_id values, including tombstones.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
list[str]: Distinct non-empty ``generated_from_request_id`` values.
|
|
493
|
+
"""
|
|
494
|
+
rows = self._fetchall(
|
|
495
|
+
"SELECT DISTINCT generated_from_request_id FROM profiles"
|
|
496
|
+
" WHERE generated_from_request_id IS NOT NULL"
|
|
497
|
+
" AND generated_from_request_id != ''",
|
|
498
|
+
(),
|
|
499
|
+
)
|
|
500
|
+
return [row[0] for row in rows]
|
|
501
|
+
|
|
502
|
+
@SQLiteStorageBase.handle_exceptions
|
|
503
|
+
def get_profiles_by_generated_from_request_id(
|
|
504
|
+
self,
|
|
505
|
+
request_id: str,
|
|
506
|
+
) -> list[UserProfile]:
|
|
507
|
+
"""Return all profiles for a generated_from_request_id, including tombstones.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
request_id (str): The generated_from_request_id to filter on.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
list[UserProfile]: All matching profiles (any status).
|
|
514
|
+
"""
|
|
515
|
+
rows = self._fetchall(
|
|
516
|
+
"SELECT * FROM profiles WHERE generated_from_request_id = ?",
|
|
517
|
+
(request_id,),
|
|
518
|
+
)
|
|
519
|
+
return [_row_to_profile(r) for r in rows]
|
|
520
|
+
|
|
521
|
+
def get_all_generated_profiles(self) -> list[UserProfile]:
|
|
522
|
+
"""All profiles (any status) with a non-empty generated_from_request_id."""
|
|
523
|
+
rows = self._fetchall(
|
|
524
|
+
"SELECT * FROM profiles "
|
|
525
|
+
"WHERE generated_from_request_id IS NOT NULL "
|
|
526
|
+
"AND generated_from_request_id <> ''",
|
|
303
527
|
)
|
|
528
|
+
return [_row_to_profile(r) for r in rows]
|
|
529
|
+
|
|
530
|
+
@SQLiteStorageBase.handle_exceptions
|
|
531
|
+
def archive_profile_by_id(self, user_id: str, profile_id: str) -> bool:
|
|
532
|
+
with self._lock:
|
|
533
|
+
now_ts = _epoch_now()
|
|
534
|
+
cur = self.conn.execute(
|
|
535
|
+
"UPDATE profiles SET status = ?, last_modified_timestamp = ?, retired_at = ? "
|
|
536
|
+
"WHERE profile_id = ? AND user_id = ? AND status IS NULL",
|
|
537
|
+
(Status.ARCHIVED.value, now_ts, now_ts, profile_id, user_id),
|
|
538
|
+
)
|
|
539
|
+
if cur.rowcount > 0:
|
|
540
|
+
_append_event_stmt(
|
|
541
|
+
self.conn,
|
|
542
|
+
org_id=self.org_id,
|
|
543
|
+
entity_type="profile",
|
|
544
|
+
entity_id=str(profile_id),
|
|
545
|
+
op="status_change",
|
|
546
|
+
prov="wasInvalidatedBy",
|
|
547
|
+
source_ids=[],
|
|
548
|
+
actor="api",
|
|
549
|
+
request_id=uuid.uuid4().hex,
|
|
550
|
+
reason="None->archived",
|
|
551
|
+
from_status=None,
|
|
552
|
+
to_status="archived",
|
|
553
|
+
status_namespace="lifecycle_status",
|
|
554
|
+
)
|
|
555
|
+
self.conn.commit()
|
|
304
556
|
return cur.rowcount > 0
|
|
305
557
|
|
|
558
|
+
@SQLiteStorageBase.handle_exceptions
|
|
559
|
+
def supersede_profiles_by_ids(
|
|
560
|
+
self,
|
|
561
|
+
user_id: str,
|
|
562
|
+
profile_ids: list[str],
|
|
563
|
+
request_id: str,
|
|
564
|
+
) -> list[str]:
|
|
565
|
+
"""Soft-delete profiles by setting status to SUPERSEDED, emitting set-based lineage.
|
|
566
|
+
|
|
567
|
+
For each matching id (user_id scoped, currently CURRENT), updates status to
|
|
568
|
+
SUPERSEDED and emits one ``status_change`` event under the shared ``request_id``.
|
|
569
|
+
Atomic: one ``conn.commit()`` at the end, guarded on rowcount per id.
|
|
570
|
+
FTS/vec rows are NOT removed — reads exclude tombstones by status filter.
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
user_id (str): Owning user id.
|
|
574
|
+
profile_ids (list[str]): Profile ids to supersede.
|
|
575
|
+
request_id (str): Shared request id for all emitted lineage events.
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
list[str]: The profile ids actually superseded by this call, in input order
|
|
579
|
+
(already-superseded or absent ids are omitted).
|
|
580
|
+
"""
|
|
581
|
+
if not profile_ids:
|
|
582
|
+
return []
|
|
583
|
+
if not request_id:
|
|
584
|
+
raise ValueError("request_id must be non-empty for supersede")
|
|
585
|
+
now_ts = _epoch_now()
|
|
586
|
+
# Eligibility: CURRENT (NULL) or PENDING — the two live statuses dedup can target.
|
|
587
|
+
eligible = (None, Status.PENDING.value)
|
|
588
|
+
committed_ids: list[str] = []
|
|
589
|
+
with self._lock:
|
|
590
|
+
for pid in profile_ids:
|
|
591
|
+
# Read current status for from_status derivation (user_id scoped)
|
|
592
|
+
row = self.conn.execute(
|
|
593
|
+
"SELECT status FROM profiles WHERE profile_id = ? AND user_id = ?",
|
|
594
|
+
(pid, user_id),
|
|
595
|
+
).fetchone()
|
|
596
|
+
if row is None:
|
|
597
|
+
continue
|
|
598
|
+
old_status_val = (
|
|
599
|
+
row[0] if isinstance(row, (tuple, list)) else row["status"]
|
|
600
|
+
)
|
|
601
|
+
if old_status_val not in eligible:
|
|
602
|
+
continue
|
|
603
|
+
cur = self.conn.execute(
|
|
604
|
+
"UPDATE profiles SET status = ?, last_modified_timestamp = ?, retired_at = ? "
|
|
605
|
+
"WHERE profile_id = ? AND user_id = ? "
|
|
606
|
+
"AND (status IS NULL OR status = ?)",
|
|
607
|
+
(
|
|
608
|
+
Status.SUPERSEDED.value,
|
|
609
|
+
now_ts,
|
|
610
|
+
now_ts,
|
|
611
|
+
pid,
|
|
612
|
+
user_id,
|
|
613
|
+
Status.PENDING.value,
|
|
614
|
+
),
|
|
615
|
+
)
|
|
616
|
+
if cur.rowcount > 0:
|
|
617
|
+
_append_event_stmt(
|
|
618
|
+
self.conn,
|
|
619
|
+
org_id=self.org_id,
|
|
620
|
+
entity_type="profile",
|
|
621
|
+
entity_id=str(pid),
|
|
622
|
+
op="status_change",
|
|
623
|
+
prov="wasInvalidatedBy",
|
|
624
|
+
source_ids=[],
|
|
625
|
+
actor="dedup",
|
|
626
|
+
request_id=request_id,
|
|
627
|
+
reason=f"{old_status_val}->superseded",
|
|
628
|
+
from_status=old_status_val,
|
|
629
|
+
to_status=Status.SUPERSEDED.value,
|
|
630
|
+
status_namespace="lifecycle_status",
|
|
631
|
+
)
|
|
632
|
+
committed_ids.append(pid)
|
|
633
|
+
self.conn.commit()
|
|
634
|
+
return committed_ids
|
|
635
|
+
|
|
306
636
|
@SQLiteStorageBase.handle_exceptions
|
|
307
637
|
def delete_all_profiles_by_status(self, status: Status) -> int:
|
|
308
|
-
#
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
638
|
+
# Atomic: fts + vec + row + lineage in ONE lock/commit — rowid reuse race
|
|
639
|
+
# prevention (see delete_user_profile comment, #196).
|
|
640
|
+
batch_request_id = uuid.uuid4().hex
|
|
641
|
+
with self._lock:
|
|
642
|
+
rows = self.conn.execute(
|
|
643
|
+
"SELECT rowid, profile_id FROM profiles WHERE status = ?",
|
|
644
|
+
(status.value,),
|
|
645
|
+
).fetchall()
|
|
646
|
+
if not rows:
|
|
647
|
+
return 0
|
|
648
|
+
pids = [r["profile_id"] for r in rows]
|
|
649
|
+
rowids = [r["rowid"] for r in rows]
|
|
650
|
+
self._delete_in_chunks("profiles_fts", "profile_id", pids)
|
|
651
|
+
if self._has_sqlite_vec and rowids:
|
|
652
|
+
self._delete_in_chunks("profiles_vec", "rowid", rowids)
|
|
653
|
+
ph = ",".join("?" for _ in pids)
|
|
654
|
+
cur = self.conn.execute(
|
|
655
|
+
f"DELETE FROM profiles WHERE profile_id IN ({ph})",
|
|
656
|
+
pids, # noqa: S608
|
|
313
657
|
)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
658
|
+
for pid in pids:
|
|
659
|
+
_emit_hard_delete_profile(
|
|
660
|
+
self.conn,
|
|
661
|
+
org_id=self.org_id,
|
|
662
|
+
entity_id=str(pid),
|
|
663
|
+
request_id=batch_request_id,
|
|
664
|
+
)
|
|
665
|
+
self.conn.commit()
|
|
318
666
|
return cur.rowcount
|
|
319
667
|
|
|
320
668
|
@SQLiteStorageBase.handle_exceptions
|
|
@@ -331,15 +679,41 @@ class ProfileMixin:
|
|
|
331
679
|
return [r["user_id"] for r in rows]
|
|
332
680
|
|
|
333
681
|
@SQLiteStorageBase.handle_exceptions
|
|
334
|
-
def delete_profiles_by_ids(
|
|
682
|
+
def delete_profiles_by_ids(
|
|
683
|
+
self, profile_ids: list[str], *, emit_hard_delete: bool = True
|
|
684
|
+
) -> int:
|
|
335
685
|
if not profile_ids:
|
|
336
686
|
return 0
|
|
337
|
-
|
|
338
|
-
|
|
687
|
+
# Atomic: fts + vec + row + lineage in ONE lock/commit — rowid reuse race
|
|
688
|
+
# prevention (see delete_user_profile comment, #196).
|
|
339
689
|
ph = ",".join("?" for _ in profile_ids)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
690
|
+
batch_request_id = uuid.uuid4().hex
|
|
691
|
+
with self._lock:
|
|
692
|
+
pre_rows = self.conn.execute(
|
|
693
|
+
f"SELECT rowid, profile_id FROM profiles WHERE profile_id IN ({ph})",
|
|
694
|
+
profile_ids,
|
|
695
|
+
).fetchall()
|
|
696
|
+
if not pre_rows:
|
|
697
|
+
return 0
|
|
698
|
+
existing = [r["profile_id"] for r in pre_rows]
|
|
699
|
+
rowids = [r["rowid"] for r in pre_rows]
|
|
700
|
+
self._delete_in_chunks("profiles_fts", "profile_id", existing)
|
|
701
|
+
if self._has_sqlite_vec and rowids:
|
|
702
|
+
self._delete_in_chunks("profiles_vec", "rowid", rowids)
|
|
703
|
+
cur = self.conn.execute(
|
|
704
|
+
f"DELETE FROM profiles WHERE profile_id IN ({ph})",
|
|
705
|
+
profile_ids, # noqa: S608
|
|
706
|
+
)
|
|
707
|
+
if emit_hard_delete:
|
|
708
|
+
for pid in existing:
|
|
709
|
+
_emit_hard_delete_profile(
|
|
710
|
+
self.conn,
|
|
711
|
+
org_id=self.org_id,
|
|
712
|
+
entity_id=str(pid),
|
|
713
|
+
request_id=batch_request_id,
|
|
714
|
+
actor="system",
|
|
715
|
+
)
|
|
716
|
+
self.conn.commit()
|
|
343
717
|
return cur.rowcount
|
|
344
718
|
|
|
345
719
|
# ------------------------------------------------------------------
|
|
@@ -376,9 +750,9 @@ class ProfileMixin:
|
|
|
376
750
|
"""INSERT OR REPLACE INTO interactions
|
|
377
751
|
(interaction_id, user_id, content, request_id, created_at,
|
|
378
752
|
role, user_action, user_action_description,
|
|
379
|
-
interacted_image_url,
|
|
380
|
-
tools_used, citations, embedding)
|
|
381
|
-
VALUES (
|
|
753
|
+
interacted_image_url, image_encoding, shadow_content,
|
|
754
|
+
expert_content, tools_used, citations, embedding)
|
|
755
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
382
756
|
(
|
|
383
757
|
interaction.interaction_id,
|
|
384
758
|
interaction.user_id,
|
|
@@ -389,6 +763,7 @@ class ProfileMixin:
|
|
|
389
763
|
interaction.user_action.value,
|
|
390
764
|
interaction.user_action_description,
|
|
391
765
|
interaction.interacted_image_url,
|
|
766
|
+
interaction.image_encoding,
|
|
392
767
|
interaction.shadow_content,
|
|
393
768
|
interaction.expert_content,
|
|
394
769
|
_json_dumps([t.model_dump() for t in interaction.tools_used]),
|
|
@@ -402,9 +777,9 @@ class ProfileMixin:
|
|
|
402
777
|
"""INSERT INTO interactions
|
|
403
778
|
(user_id, content, request_id, created_at,
|
|
404
779
|
role, user_action, user_action_description,
|
|
405
|
-
interacted_image_url,
|
|
406
|
-
tools_used, citations, embedding)
|
|
407
|
-
VALUES (
|
|
780
|
+
interacted_image_url, image_encoding, shadow_content,
|
|
781
|
+
expert_content, tools_used, citations, embedding)
|
|
782
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
408
783
|
(
|
|
409
784
|
interaction.user_id,
|
|
410
785
|
interaction.content,
|
|
@@ -414,6 +789,7 @@ class ProfileMixin:
|
|
|
414
789
|
interaction.user_action.value,
|
|
415
790
|
interaction.user_action_description,
|
|
416
791
|
interaction.interacted_image_url,
|
|
792
|
+
interaction.image_encoding,
|
|
417
793
|
interaction.shadow_content,
|
|
418
794
|
interaction.expert_content,
|
|
419
795
|
_json_dumps([t.model_dump() for t in interaction.tools_used]),
|
|
@@ -464,34 +840,49 @@ class ProfileMixin:
|
|
|
464
840
|
|
|
465
841
|
@SQLiteStorageBase.handle_exceptions
|
|
466
842
|
def delete_user_interaction(self, request: DeleteUserInteractionRequest) -> None:
|
|
467
|
-
self.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
843
|
+
with self._lock:
|
|
844
|
+
row = self.conn.execute(
|
|
845
|
+
"SELECT interaction_id FROM interactions WHERE user_id = ? AND interaction_id = ?",
|
|
846
|
+
(request.user_id, request.interaction_id),
|
|
847
|
+
).fetchone()
|
|
848
|
+
if row is None:
|
|
849
|
+
return
|
|
850
|
+
self.conn.execute(
|
|
851
|
+
"DELETE FROM interactions_fts WHERE rowid = ?",
|
|
852
|
+
(request.interaction_id,),
|
|
853
|
+
)
|
|
854
|
+
if self._has_sqlite_vec:
|
|
855
|
+
self.conn.execute(
|
|
856
|
+
"DELETE FROM interactions_vec WHERE rowid = ?",
|
|
857
|
+
(request.interaction_id,),
|
|
858
|
+
)
|
|
859
|
+
self.conn.execute(
|
|
860
|
+
"DELETE FROM interactions WHERE user_id = ? AND interaction_id = ?",
|
|
861
|
+
(request.user_id, request.interaction_id),
|
|
862
|
+
)
|
|
863
|
+
self.conn.commit()
|
|
472
864
|
|
|
473
865
|
@SQLiteStorageBase.handle_exceptions
|
|
474
866
|
def delete_all_interactions_for_user(self, user_id: str) -> None:
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
r["interaction_id"]
|
|
478
|
-
for r in self._fetchall(
|
|
867
|
+
with self._lock:
|
|
868
|
+
rows = self.conn.execute(
|
|
479
869
|
"SELECT interaction_id FROM interactions WHERE user_id = ?", (user_id,)
|
|
480
|
-
)
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
self._execute("DELETE FROM interactions WHERE user_id = ?", (user_id,))
|
|
870
|
+
).fetchall()
|
|
871
|
+
if not rows:
|
|
872
|
+
return
|
|
873
|
+
ids = [r["interaction_id"] for r in rows]
|
|
874
|
+
self._delete_in_chunks("interactions_fts", "rowid", ids)
|
|
875
|
+
if self._has_sqlite_vec:
|
|
876
|
+
self._delete_in_chunks("interactions_vec", "rowid", ids)
|
|
877
|
+
self.conn.execute("DELETE FROM interactions WHERE user_id = ?", (user_id,))
|
|
878
|
+
self.conn.commit()
|
|
490
879
|
|
|
491
880
|
@SQLiteStorageBase.handle_exceptions
|
|
492
881
|
def delete_all_interactions(self) -> None:
|
|
493
882
|
with self._lock:
|
|
494
883
|
self.conn.execute("DELETE FROM interactions_fts")
|
|
884
|
+
if self._has_sqlite_vec:
|
|
885
|
+
self.conn.execute("DELETE FROM interactions_vec")
|
|
495
886
|
self.conn.execute("DELETE FROM interactions")
|
|
496
887
|
self.conn.commit()
|
|
497
888
|
|
|
@@ -504,22 +895,18 @@ class ProfileMixin:
|
|
|
504
895
|
def delete_oldest_interactions(self, count: int) -> int:
|
|
505
896
|
if count <= 0:
|
|
506
897
|
return 0
|
|
507
|
-
rows = self._fetchall(
|
|
508
|
-
"SELECT interaction_id FROM interactions ORDER BY created_at ASC LIMIT ?",
|
|
509
|
-
(count,),
|
|
510
|
-
)
|
|
511
|
-
if not rows:
|
|
512
|
-
return 0
|
|
513
|
-
ids = [r["interaction_id"] for r in rows]
|
|
514
|
-
placeholders = ",".join("?" for _ in ids)
|
|
515
898
|
with self._lock:
|
|
516
|
-
self.conn.execute(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
899
|
+
rows = self.conn.execute(
|
|
900
|
+
"SELECT interaction_id FROM interactions ORDER BY created_at ASC LIMIT ?",
|
|
901
|
+
(count,),
|
|
902
|
+
).fetchall()
|
|
903
|
+
if not rows:
|
|
904
|
+
return 0
|
|
905
|
+
ids = [r["interaction_id"] for r in rows]
|
|
906
|
+
self._delete_in_chunks("interactions_fts", "rowid", ids)
|
|
907
|
+
if self._has_sqlite_vec:
|
|
908
|
+
self._delete_in_chunks("interactions_vec", "rowid", ids)
|
|
909
|
+
self._delete_in_chunks("interactions", "interaction_id", ids)
|
|
523
910
|
self.conn.commit()
|
|
524
911
|
return len(ids)
|
|
525
912
|
|
|
@@ -657,6 +1044,10 @@ class ProfileMixin:
|
|
|
657
1044
|
frag, sparams = _build_status_sql(status_filter)
|
|
658
1045
|
conditions.append(frag)
|
|
659
1046
|
params.extend(sparams)
|
|
1047
|
+
tag_frag, tag_params = _build_tags_sql("p", req.tags)
|
|
1048
|
+
if tag_frag:
|
|
1049
|
+
conditions.append(tag_frag)
|
|
1050
|
+
params.extend(tag_params)
|
|
660
1051
|
|
|
661
1052
|
where_clause = " AND ".join(conditions)
|
|
662
1053
|
overfetch = match_count * 5 if mode != SearchMode.FTS else match_count
|