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
|
@@ -4,6 +4,7 @@ import hashlib
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
import time
|
|
7
|
+
import uuid
|
|
7
8
|
from collections.abc import Sequence
|
|
8
9
|
from typing import TYPE_CHECKING
|
|
9
10
|
|
|
@@ -12,13 +13,9 @@ if TYPE_CHECKING:
|
|
|
12
13
|
|
|
13
14
|
from reflexio.models.api_schema.service_schemas import (
|
|
14
15
|
AgentPlaybook,
|
|
15
|
-
AgentPlaybookSnapshot,
|
|
16
16
|
AgentPlaybookSourceWindow,
|
|
17
|
-
AgentPlaybookUpdateEntry,
|
|
18
|
-
PlaybookAggregationChangeLog,
|
|
19
17
|
PlaybookStatus,
|
|
20
18
|
UserPlaybook,
|
|
21
|
-
agent_playbook_to_snapshot,
|
|
22
19
|
)
|
|
23
20
|
from reflexio.models.config_schema import (
|
|
24
21
|
SINGLETON_USER_PLAYBOOK_NAME,
|
|
@@ -33,9 +30,12 @@ from reflexio.server.services.playbook.playbook_service_constants import (
|
|
|
33
30
|
from reflexio.server.services.playbook.playbook_service_utils import (
|
|
34
31
|
PlaybookAggregationOutput,
|
|
35
32
|
PlaybookAggregatorRequest,
|
|
33
|
+
StructuredPlaybookContent,
|
|
36
34
|
ensure_playbook_content,
|
|
37
35
|
)
|
|
36
|
+
from reflexio.server.services.playbook.user_detail_stripping import UserDetailStripper
|
|
38
37
|
from reflexio.server.services.service_utils import log_model_response
|
|
38
|
+
from reflexio.server.tracing import capture_anomaly, sentry_tags
|
|
39
39
|
from reflexio.server.usage_metrics import record_usage_event
|
|
40
40
|
|
|
41
41
|
logger = logging.getLogger(__name__)
|
|
@@ -52,17 +52,33 @@ class PlaybookAggregator:
|
|
|
52
52
|
llm_client: LiteLLMClient,
|
|
53
53
|
request_context: RequestContext,
|
|
54
54
|
agent_version: str,
|
|
55
|
+
user_detail_stripper: UserDetailStripper | None = None,
|
|
55
56
|
) -> None:
|
|
56
57
|
self.client = llm_client
|
|
57
58
|
self.storage = request_context.storage
|
|
58
59
|
self.configurator = request_context.configurator
|
|
59
60
|
self.request_context = request_context
|
|
60
61
|
self.agent_version = agent_version
|
|
62
|
+
self.user_detail_stripper = user_detail_stripper
|
|
63
|
+
prompt_extra_instructions = getattr(
|
|
64
|
+
user_detail_stripper, "prompt_extra_instructions", None
|
|
65
|
+
)
|
|
66
|
+
if not isinstance(prompt_extra_instructions, str):
|
|
67
|
+
prompt_extra_instructions = None
|
|
68
|
+
self.aggregation_prompt_extra_instructions = (
|
|
69
|
+
self._format_prompt_extra_instructions(prompt_extra_instructions)
|
|
70
|
+
)
|
|
61
71
|
|
|
62
72
|
# ===============================
|
|
63
73
|
# private methods - operation state
|
|
64
74
|
# ===============================
|
|
65
75
|
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _format_prompt_extra_instructions(instructions: str | None) -> str:
|
|
78
|
+
if not instructions or not instructions.strip():
|
|
79
|
+
return ""
|
|
80
|
+
return f"{instructions.strip()}\n"
|
|
81
|
+
|
|
66
82
|
def _create_state_manager(self) -> OperationStateManager:
|
|
67
83
|
"""
|
|
68
84
|
Create an OperationStateManager for the playbook aggregator.
|
|
@@ -103,7 +119,7 @@ class PlaybookAggregator:
|
|
|
103
119
|
# Count user playbooks with ID greater than last processed using efficient count query
|
|
104
120
|
# Only count current user playbooks (status=None), not archived or pending ones.
|
|
105
121
|
# Singleton aggregation operates on the user's whole playbook set — no name filter.
|
|
106
|
-
new_count = self.storage.count_user_playbooks( #
|
|
122
|
+
new_count = self.storage.count_user_playbooks( # pyright: ignore[reportOptionalMemberAccess]
|
|
107
123
|
min_user_playbook_id=last_processed_id,
|
|
108
124
|
agent_version=self.agent_version,
|
|
109
125
|
status_filter=[None],
|
|
@@ -194,6 +210,108 @@ class PlaybookAggregator:
|
|
|
194
210
|
blocks.append("\n".join(lines))
|
|
195
211
|
return "\n\n".join(blocks) if blocks else "(No playbook items)"
|
|
196
212
|
|
|
213
|
+
def _strip_prompt_field(
|
|
214
|
+
self,
|
|
215
|
+
text: str | None,
|
|
216
|
+
shared_mapping: dict[str, int],
|
|
217
|
+
) -> str | None:
|
|
218
|
+
if text is None or self.user_detail_stripper is None:
|
|
219
|
+
return text
|
|
220
|
+
return self.user_detail_stripper.strip_user_details(
|
|
221
|
+
text, shared_mapping=shared_mapping
|
|
222
|
+
).text
|
|
223
|
+
|
|
224
|
+
def _strip_user_playbook_for_prompt(
|
|
225
|
+
self,
|
|
226
|
+
playbook: UserPlaybook,
|
|
227
|
+
shared_mapping: dict[str, int],
|
|
228
|
+
) -> UserPlaybook:
|
|
229
|
+
if self.user_detail_stripper is None:
|
|
230
|
+
return playbook
|
|
231
|
+
return playbook.model_copy(
|
|
232
|
+
update={
|
|
233
|
+
"content": self._strip_prompt_field(playbook.content, shared_mapping)
|
|
234
|
+
or "",
|
|
235
|
+
"trigger": self._strip_prompt_field(playbook.trigger, shared_mapping),
|
|
236
|
+
"rationale": self._strip_prompt_field(
|
|
237
|
+
playbook.rationale, shared_mapping
|
|
238
|
+
),
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def _sanitize_aggregation_output_text(
|
|
243
|
+
self,
|
|
244
|
+
text: str | None,
|
|
245
|
+
) -> tuple[str | None, int]:
|
|
246
|
+
if self.user_detail_stripper is None:
|
|
247
|
+
return text, 0
|
|
248
|
+
return self.user_detail_stripper.sanitize_aggregation_output_text(text)
|
|
249
|
+
|
|
250
|
+
def _sanitize_aggregation_response(
|
|
251
|
+
self, response: PlaybookAggregationOutput
|
|
252
|
+
) -> tuple[PlaybookAggregationOutput, int]:
|
|
253
|
+
structured = response.playbook
|
|
254
|
+
if structured is None:
|
|
255
|
+
return response, 0
|
|
256
|
+
|
|
257
|
+
updates: dict[str, str | None] = {}
|
|
258
|
+
placeholder_count = 0
|
|
259
|
+
for field_name, value in structured.model_dump().items():
|
|
260
|
+
if not isinstance(value, str):
|
|
261
|
+
continue
|
|
262
|
+
sanitized, field_count = self._sanitize_aggregation_output_text(value)
|
|
263
|
+
placeholder_count += field_count
|
|
264
|
+
if sanitized != value:
|
|
265
|
+
updates[field_name] = sanitized
|
|
266
|
+
|
|
267
|
+
if not updates:
|
|
268
|
+
return response, 0
|
|
269
|
+
return response.model_copy(
|
|
270
|
+
update={"playbook": structured.model_copy(update=updates)}
|
|
271
|
+
), placeholder_count
|
|
272
|
+
|
|
273
|
+
def _sanitize_aggregation_log_value(self, value: object) -> tuple[object, int]:
|
|
274
|
+
if isinstance(value, str):
|
|
275
|
+
return self._sanitize_aggregation_output_text(value)
|
|
276
|
+
|
|
277
|
+
if isinstance(value, dict):
|
|
278
|
+
sanitized: dict[object, object] = {}
|
|
279
|
+
placeholder_count = 0
|
|
280
|
+
for key, item in value.items():
|
|
281
|
+
sanitized_key, key_count = self._sanitize_aggregation_log_value(key)
|
|
282
|
+
sanitized_item, item_count = self._sanitize_aggregation_log_value(item)
|
|
283
|
+
sanitized[sanitized_key] = sanitized_item
|
|
284
|
+
placeholder_count += key_count + item_count
|
|
285
|
+
return sanitized, placeholder_count
|
|
286
|
+
|
|
287
|
+
if isinstance(value, list):
|
|
288
|
+
sanitized_items: list[object] = []
|
|
289
|
+
placeholder_count = 0
|
|
290
|
+
for item in value:
|
|
291
|
+
sanitized_item, item_count = self._sanitize_aggregation_log_value(item)
|
|
292
|
+
sanitized_items.append(sanitized_item)
|
|
293
|
+
placeholder_count += item_count
|
|
294
|
+
return sanitized_items, placeholder_count
|
|
295
|
+
|
|
296
|
+
if isinstance(value, tuple):
|
|
297
|
+
sanitized_items: list[object] = []
|
|
298
|
+
placeholder_count = 0
|
|
299
|
+
for item in value:
|
|
300
|
+
sanitized_item, item_count = self._sanitize_aggregation_log_value(item)
|
|
301
|
+
sanitized_items.append(sanitized_item)
|
|
302
|
+
placeholder_count += item_count
|
|
303
|
+
return tuple(sanitized_items), placeholder_count
|
|
304
|
+
|
|
305
|
+
return value, 0
|
|
306
|
+
|
|
307
|
+
def _record_placeholder_leakage(self, placeholder_count: int) -> None:
|
|
308
|
+
if placeholder_count <= 0:
|
|
309
|
+
return
|
|
310
|
+
logger.warning(
|
|
311
|
+
"Replaced %d residual user-detail placeholders in aggregated playbook output",
|
|
312
|
+
placeholder_count,
|
|
313
|
+
)
|
|
314
|
+
|
|
197
315
|
@staticmethod
|
|
198
316
|
def _get_direction_key(fb: UserPlaybook) -> str:
|
|
199
317
|
"""
|
|
@@ -465,102 +583,6 @@ class PlaybookAggregator:
|
|
|
465
583
|
|
|
466
584
|
return changed_clusters, playbook_ids_to_archive
|
|
467
585
|
|
|
468
|
-
def _build_change_log(
|
|
469
|
-
self,
|
|
470
|
-
playbook_name: str,
|
|
471
|
-
full_archive: bool,
|
|
472
|
-
before_playbooks_by_id: dict[int, AgentPlaybook],
|
|
473
|
-
saved_playbooks: list[AgentPlaybook],
|
|
474
|
-
archived_playbook_ids: list[int],
|
|
475
|
-
prev_fingerprints: dict,
|
|
476
|
-
) -> PlaybookAggregationChangeLog:
|
|
477
|
-
"""Build a PlaybookAggregationChangeLog from the aggregation run results.
|
|
478
|
-
|
|
479
|
-
Args:
|
|
480
|
-
playbook_name: The playbook name being aggregated
|
|
481
|
-
full_archive: Whether this was a full archive (rerun/first run)
|
|
482
|
-
before_playbooks_by_id: Snapshot of playbooks before archiving, keyed by playbook_id
|
|
483
|
-
saved_playbooks: Newly saved playbooks from this run
|
|
484
|
-
archived_playbook_ids: AgentPlaybook IDs that were selectively archived (incremental mode)
|
|
485
|
-
prev_fingerprints: Previous cluster fingerprints (empty for full archive)
|
|
486
|
-
|
|
487
|
-
Returns:
|
|
488
|
-
PlaybookAggregationChangeLog with added/removed/updated lists populated
|
|
489
|
-
"""
|
|
490
|
-
added: list[AgentPlaybookSnapshot] = []
|
|
491
|
-
removed: list[AgentPlaybookSnapshot] = []
|
|
492
|
-
updated: list[AgentPlaybookUpdateEntry] = []
|
|
493
|
-
|
|
494
|
-
if full_archive:
|
|
495
|
-
# No 1:1 mapping — all old playbooks are removed, all new are added
|
|
496
|
-
removed = [
|
|
497
|
-
agent_playbook_to_snapshot(fb) for fb in before_playbooks_by_id.values()
|
|
498
|
-
]
|
|
499
|
-
added = [agent_playbook_to_snapshot(fb) for fb in saved_playbooks if fb]
|
|
500
|
-
else:
|
|
501
|
-
# Incremental mode: map old playbook_ids to new playbooks via fingerprints
|
|
502
|
-
# Build a set of old playbook_ids that were archived
|
|
503
|
-
archived_id_set = set(archived_playbook_ids)
|
|
504
|
-
|
|
505
|
-
# Build mapping: old_playbook_id -> new_playbook_id via fingerprint changes
|
|
506
|
-
# prev_fingerprints maps fp_hash -> {playbook_id, user_playbook_ids}
|
|
507
|
-
# new_fingerprints maps fp_hash -> {playbook_id, user_playbook_ids}
|
|
508
|
-
# If an old fingerprint disappeared and a new one appeared, and
|
|
509
|
-
# the old fp had a playbook_id in archived_id_set, we can try to pair them.
|
|
510
|
-
# However, without a direct cluster-level old->new mapping, we use a simpler approach:
|
|
511
|
-
# archived playbooks that have a corresponding new playbook (by position in saved list) are updates.
|
|
512
|
-
|
|
513
|
-
# Collect old playbook_ids from disappeared fingerprints
|
|
514
|
-
old_fp_playbook_ids = {}
|
|
515
|
-
for fp, fp_data in prev_fingerprints.items():
|
|
516
|
-
fid = fp_data.get("agent_playbook_id")
|
|
517
|
-
if fid is not None and fid in archived_id_set:
|
|
518
|
-
old_fp_playbook_ids[fid] = fp
|
|
519
|
-
|
|
520
|
-
# For each saved playbook, try to match with an archived old playbook
|
|
521
|
-
matched_old_ids: set[int] = set()
|
|
522
|
-
for saved_fb in saved_playbooks:
|
|
523
|
-
if not saved_fb:
|
|
524
|
-
continue
|
|
525
|
-
# Try to find an old playbook from the archived set to pair with
|
|
526
|
-
paired_old_id = None
|
|
527
|
-
for old_id in list(old_fp_playbook_ids.keys()):
|
|
528
|
-
if old_id not in matched_old_ids:
|
|
529
|
-
paired_old_id = old_id
|
|
530
|
-
matched_old_ids.add(old_id)
|
|
531
|
-
break
|
|
532
|
-
|
|
533
|
-
if (
|
|
534
|
-
paired_old_id is not None
|
|
535
|
-
and paired_old_id in before_playbooks_by_id
|
|
536
|
-
):
|
|
537
|
-
updated.append(
|
|
538
|
-
AgentPlaybookUpdateEntry(
|
|
539
|
-
before=agent_playbook_to_snapshot(
|
|
540
|
-
before_playbooks_by_id[paired_old_id]
|
|
541
|
-
),
|
|
542
|
-
after=agent_playbook_to_snapshot(saved_fb),
|
|
543
|
-
)
|
|
544
|
-
)
|
|
545
|
-
else:
|
|
546
|
-
added.append(agent_playbook_to_snapshot(saved_fb))
|
|
547
|
-
|
|
548
|
-
# Remaining archived playbooks that weren't paired are removals
|
|
549
|
-
for old_id in archived_id_set:
|
|
550
|
-
if old_id not in matched_old_ids and old_id in before_playbooks_by_id:
|
|
551
|
-
removed.append(
|
|
552
|
-
agent_playbook_to_snapshot(before_playbooks_by_id[old_id])
|
|
553
|
-
)
|
|
554
|
-
|
|
555
|
-
return PlaybookAggregationChangeLog(
|
|
556
|
-
playbook_name=playbook_name,
|
|
557
|
-
agent_version=self.agent_version,
|
|
558
|
-
run_mode="full_archive" if full_archive else "incremental",
|
|
559
|
-
added_agent_playbooks=added,
|
|
560
|
-
removed_agent_playbooks=removed,
|
|
561
|
-
updated_agent_playbooks=updated,
|
|
562
|
-
)
|
|
563
|
-
|
|
564
586
|
# ===============================
|
|
565
587
|
# public methods
|
|
566
588
|
# ===============================
|
|
@@ -572,6 +594,8 @@ class PlaybookAggregator:
|
|
|
572
594
|
dict: Aggregation stats with keys: clusters_found, user_playbooks_processed, playbooks_generated, skipped (optional)
|
|
573
595
|
"""
|
|
574
596
|
aggregation_start = time.perf_counter()
|
|
597
|
+
# Stable id for this aggregation run — groups all lineage events produced below.
|
|
598
|
+
_run_id = str(uuid.uuid4())
|
|
575
599
|
_empty_stats = {
|
|
576
600
|
"clusters_found": 0,
|
|
577
601
|
"user_playbooks_processed": 0,
|
|
@@ -667,6 +691,12 @@ class PlaybookAggregator:
|
|
|
667
691
|
playbook_name,
|
|
668
692
|
self.agent_version,
|
|
669
693
|
)
|
|
694
|
+
logger.info(
|
|
695
|
+
"User detail stripping for aggregation: %s",
|
|
696
|
+
type(self.user_detail_stripper).__name__
|
|
697
|
+
if self.user_detail_stripper is not None
|
|
698
|
+
else "disabled",
|
|
699
|
+
)
|
|
670
700
|
|
|
671
701
|
# Get existing APPROVED and PENDING playbooks before archiving (to pass to LLM for deduplication).
|
|
672
702
|
# Singleton aggregation pulls the user's whole set — no name filter.
|
|
@@ -694,11 +724,6 @@ class PlaybookAggregator:
|
|
|
694
724
|
)
|
|
695
725
|
clusters = self.get_clusters(user_playbooks, playbook_aggregator_config)
|
|
696
726
|
|
|
697
|
-
# Capture all current playbooks before archiving (for change log)
|
|
698
|
-
before_playbooks_by_id: dict[int, AgentPlaybook] = {
|
|
699
|
-
fb.agent_playbook_id: fb for fb in existing_playbooks
|
|
700
|
-
}
|
|
701
|
-
|
|
702
727
|
# Determine which clusters changed (skip for rerun)
|
|
703
728
|
mgr = self._create_state_manager()
|
|
704
729
|
archived_playbook_ids = []
|
|
@@ -764,10 +789,6 @@ class PlaybookAggregator:
|
|
|
764
789
|
len(archived_playbook_ids),
|
|
765
790
|
)
|
|
766
791
|
|
|
767
|
-
# Selectively archive only playbooks from changed/disappeared clusters
|
|
768
|
-
if archived_playbook_ids:
|
|
769
|
-
self.storage.archive_agent_playbooks_by_ids(archived_playbook_ids) # type: ignore[reportOptionalMemberAccess]
|
|
770
|
-
|
|
771
792
|
try:
|
|
772
793
|
# Emit the started event inside the protected block so any failure
|
|
773
794
|
# from here on is paired with an aggregation_failed event.
|
|
@@ -789,6 +810,37 @@ class PlaybookAggregator:
|
|
|
789
810
|
)
|
|
790
811
|
new_playbooks = [playbook for playbook, _ in generated_pairs]
|
|
791
812
|
|
|
813
|
+
previous_fingerprints_for_changed_clusters = {}
|
|
814
|
+
changed_fps_by_previous_fp = {}
|
|
815
|
+
changed_fps_with_replacements = set()
|
|
816
|
+
previous_playbook_id_by_fp = {}
|
|
817
|
+
if not playbook_aggregator_request.rerun and prev_fingerprints:
|
|
818
|
+
for cluster_playbooks in changed_clusters.values():
|
|
819
|
+
fp = self._compute_cluster_fingerprint(cluster_playbooks)
|
|
820
|
+
current_user_ids = {
|
|
821
|
+
fb.user_playbook_id
|
|
822
|
+
for fb in cluster_playbooks
|
|
823
|
+
if fb.user_playbook_id is not None
|
|
824
|
+
}
|
|
825
|
+
matched_prev_fingerprints = {
|
|
826
|
+
prev_fp: fp_data
|
|
827
|
+
for prev_fp, fp_data in prev_fingerprints.items()
|
|
828
|
+
if fp_data.get("agent_playbook_id") is not None
|
|
829
|
+
and current_user_ids
|
|
830
|
+
& set(fp_data.get("user_playbook_ids") or [])
|
|
831
|
+
}
|
|
832
|
+
if matched_prev_fingerprints:
|
|
833
|
+
previous_fingerprints_for_changed_clusters[fp] = (
|
|
834
|
+
matched_prev_fingerprints
|
|
835
|
+
)
|
|
836
|
+
for prev_fp, fp_data in matched_prev_fingerprints.items():
|
|
837
|
+
changed_fps_by_previous_fp.setdefault(prev_fp, set()).add(
|
|
838
|
+
fp
|
|
839
|
+
)
|
|
840
|
+
playbook_id = fp_data.get("agent_playbook_id")
|
|
841
|
+
if playbook_id is not None:
|
|
842
|
+
previous_playbook_id_by_fp[prev_fp] = playbook_id
|
|
843
|
+
|
|
792
844
|
# Lazy archive: only full-archive when the LLM produced replacements.
|
|
793
845
|
# Skipping the archive when new_playbooks is empty preserves existing
|
|
794
846
|
# PENDING/APPROVED playbooks that the LLM identified as duplicates.
|
|
@@ -806,17 +858,12 @@ class PlaybookAggregator:
|
|
|
806
858
|
)
|
|
807
859
|
full_archive = False
|
|
808
860
|
|
|
809
|
-
# Save playbooks (returns playbooks with playbook_id populated)
|
|
810
|
-
saved_playbooks = self.storage.save_agent_playbooks(new_playbooks) # type: ignore[reportOptionalMemberAccess]
|
|
811
|
-
|
|
812
861
|
# Build new fingerprint state
|
|
813
862
|
new_fingerprints = {}
|
|
814
863
|
|
|
815
864
|
if not playbook_aggregator_request.rerun:
|
|
816
865
|
# Carry forward unchanged fingerprints from previous state
|
|
817
|
-
prev_fps =
|
|
818
|
-
name=playbook_name, version=self.agent_version
|
|
819
|
-
)
|
|
866
|
+
prev_fps = prev_fingerprints
|
|
820
867
|
current_fp_set = set()
|
|
821
868
|
for cluster_playbooks in clusters.values():
|
|
822
869
|
fp = self._compute_cluster_fingerprint(cluster_playbooks)
|
|
@@ -837,31 +884,47 @@ class PlaybookAggregator:
|
|
|
837
884
|
}
|
|
838
885
|
)
|
|
839
886
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
887
|
+
saved_playbook_list: list[AgentPlaybook] = []
|
|
888
|
+
selective_supersede_playbook_ids = set()
|
|
889
|
+
replaced_previous_fingerprints = set()
|
|
890
|
+
|
|
891
|
+
# Save each playbook + its aggregate event atomically, then assign
|
|
892
|
+
# fingerprints and source-windows for the saved row.
|
|
893
|
+
for playbook, cluster_playbooks in generated_pairs:
|
|
894
|
+
run_mode = "full_archive" if full_archive else "incremental"
|
|
895
|
+
member_ids = [
|
|
896
|
+
str(fb.user_playbook_id)
|
|
897
|
+
for fb in cluster_playbooks
|
|
898
|
+
if fb.user_playbook_id
|
|
899
|
+
]
|
|
900
|
+
saved_fb = self.storage.save_agent_playbook_with_aggregate_event( # type: ignore[reportOptionalMemberAccess]
|
|
901
|
+
playbook,
|
|
902
|
+
source_ids=member_ids,
|
|
903
|
+
request_id=_run_id,
|
|
904
|
+
run_mode=run_mode,
|
|
905
|
+
)
|
|
906
|
+
saved_playbook_list.append(saved_fb)
|
|
858
907
|
if saved_fb and saved_fb.agent_playbook_id:
|
|
859
908
|
fp_key = self._compute_cluster_fingerprint(cluster_playbooks)
|
|
909
|
+
changed_fps_with_replacements.add(fp_key)
|
|
860
910
|
raw_ids = sorted(fb.user_playbook_id for fb in cluster_playbooks)
|
|
861
911
|
new_fingerprints[fp_key] = {
|
|
862
912
|
"agent_playbook_id": saved_fb.agent_playbook_id,
|
|
863
913
|
"user_playbook_ids": raw_ids,
|
|
864
914
|
}
|
|
915
|
+
for prev_fp in previous_fingerprints_for_changed_clusters.get(
|
|
916
|
+
fp_key, {}
|
|
917
|
+
):
|
|
918
|
+
all_overlapping_clusters_replaced = (
|
|
919
|
+
changed_fps_by_previous_fp.get(prev_fp, set()).issubset(
|
|
920
|
+
changed_fps_with_replacements
|
|
921
|
+
)
|
|
922
|
+
)
|
|
923
|
+
playbook_id = previous_playbook_id_by_fp.get(prev_fp)
|
|
924
|
+
if all_overlapping_clusters_replaced:
|
|
925
|
+
replaced_previous_fingerprints.add(prev_fp)
|
|
926
|
+
if playbook_id is not None:
|
|
927
|
+
selective_supersede_playbook_ids.add(playbook_id)
|
|
865
928
|
self.storage.set_source_windows_for_agent_playbook( # type: ignore[reportOptionalMemberAccess]
|
|
866
929
|
saved_fb.agent_playbook_id,
|
|
867
930
|
[
|
|
@@ -876,6 +939,33 @@ class PlaybookAggregator:
|
|
|
876
939
|
],
|
|
877
940
|
)
|
|
878
941
|
|
|
942
|
+
# Changed clusters that did not get a replacement keep their previous
|
|
943
|
+
# fingerprint/playbook mapping so a later successful replacement can
|
|
944
|
+
# supersede the old playbook. Brand-new duplicate clusters still get
|
|
945
|
+
# a None marker to avoid repeated LLM calls for the same fingerprint.
|
|
946
|
+
for cluster_playbooks in changed_clusters.values():
|
|
947
|
+
fp = self._compute_cluster_fingerprint(cluster_playbooks)
|
|
948
|
+
if fp in new_fingerprints:
|
|
949
|
+
continue
|
|
950
|
+
|
|
951
|
+
previous_for_cluster = previous_fingerprints_for_changed_clusters.get(
|
|
952
|
+
fp, {}
|
|
953
|
+
)
|
|
954
|
+
preserved_previous = {
|
|
955
|
+
prev_fp: fp_data
|
|
956
|
+
for prev_fp, fp_data in previous_for_cluster.items()
|
|
957
|
+
if prev_fp not in replaced_previous_fingerprints
|
|
958
|
+
}
|
|
959
|
+
if preserved_previous:
|
|
960
|
+
new_fingerprints.update(preserved_previous)
|
|
961
|
+
continue
|
|
962
|
+
|
|
963
|
+
raw_ids = sorted(fb.user_playbook_id for fb in cluster_playbooks)
|
|
964
|
+
new_fingerprints[fp] = {
|
|
965
|
+
"agent_playbook_id": None,
|
|
966
|
+
"user_playbook_ids": raw_ids,
|
|
967
|
+
}
|
|
968
|
+
|
|
879
969
|
# Store fingerprints in operation state
|
|
880
970
|
mgr.update_cluster_fingerprints(
|
|
881
971
|
name=playbook_name,
|
|
@@ -886,39 +976,54 @@ class PlaybookAggregator:
|
|
|
886
976
|
# Update operation state with the highest user_playbook_id processed
|
|
887
977
|
self._update_operation_state(playbook_name, user_playbooks)
|
|
888
978
|
|
|
889
|
-
#
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
979
|
+
# Remove archived playbooks after successful aggregation. ALWAYS soft-supersede
|
|
980
|
+
# (never hard-delete) so the removal is reconstructable from lineage — mirrors the
|
|
981
|
+
# profile dedup always-soft path (#206).
|
|
982
|
+
if not _run_id:
|
|
983
|
+
# Empty request_id makes the removal unreconstructable (lineage events are keyed
|
|
984
|
+
# on it). Fail loud and skip removal — never silently hard-delete.
|
|
985
|
+
capture_anomaly(
|
|
986
|
+
"lineage.aggregation.missing_request_id",
|
|
987
|
+
level="error",
|
|
988
|
+
org_id=self.request_context.org_id,
|
|
898
989
|
)
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
990
|
+
else:
|
|
991
|
+
try:
|
|
992
|
+
if full_archive:
|
|
993
|
+
for name in full_archive_playbook_names:
|
|
994
|
+
self.storage.supersede_agent_playbooks_by_playbook_name( # type: ignore[reportOptionalMemberAccess]
|
|
995
|
+
name,
|
|
996
|
+
agent_version=self.agent_version,
|
|
997
|
+
request_id=_run_id,
|
|
998
|
+
)
|
|
999
|
+
elif selective_supersede_playbook_ids:
|
|
1000
|
+
self.storage.supersede_agent_playbooks_by_ids( # type: ignore[reportOptionalMemberAccess]
|
|
1001
|
+
sorted(selective_supersede_playbook_ids),
|
|
1002
|
+
request_id=_run_id,
|
|
1003
|
+
)
|
|
1004
|
+
elif archived_playbook_ids:
|
|
1005
|
+
logger.info(
|
|
1006
|
+
"Skipping selective supersede of %s (agent_version=%s): LLM produced 0 new playbooks; existing PENDING/APPROVED playbooks preserved",
|
|
1007
|
+
archived_playbook_ids,
|
|
1008
|
+
self.agent_version,
|
|
1009
|
+
)
|
|
1010
|
+
except Exception:
|
|
1011
|
+
with sentry_tags(
|
|
1012
|
+
subsystem="playbook_aggregation",
|
|
1013
|
+
op="supersede_agent_playbooks",
|
|
1014
|
+
org_id=self.request_context.org_id,
|
|
1015
|
+
request_id=_run_id,
|
|
1016
|
+
):
|
|
1017
|
+
logger.exception(
|
|
1018
|
+
"Failed to soft-supersede archived agent playbooks (run %s)",
|
|
1019
|
+
_run_id,
|
|
1020
|
+
)
|
|
1021
|
+
capture_anomaly(
|
|
1022
|
+
"lineage.aggregation.supersede_failed",
|
|
1023
|
+
level="error",
|
|
1024
|
+
org_id=self.request_context.org_id,
|
|
1025
|
+
request_id=_run_id,
|
|
919
1026
|
)
|
|
920
|
-
elif archived_playbook_ids:
|
|
921
|
-
self.storage.delete_agent_playbooks_by_ids(archived_playbook_ids) # type: ignore[reportOptionalMemberAccess]
|
|
922
1027
|
|
|
923
1028
|
self._enqueue_playbook_optimization(saved_playbook_list)
|
|
924
1029
|
|
|
@@ -1197,17 +1302,24 @@ class PlaybookAggregator:
|
|
|
1197
1302
|
direction_overlap_threshold: float = 0.6,
|
|
1198
1303
|
) -> list[tuple[AgentPlaybook, list[UserPlaybook]]]:
|
|
1199
1304
|
"""Generate agent playbooks while preserving their exact source cluster."""
|
|
1200
|
-
|
|
1305
|
+
new_playbooks: list[tuple[AgentPlaybook, list[UserPlaybook]]] = []
|
|
1201
1306
|
approved_playbooks_str = (
|
|
1202
1307
|
"\n".join([f"- {fb.content}" for fb in existing_approved_playbooks])
|
|
1203
1308
|
if existing_approved_playbooks
|
|
1204
1309
|
else "None"
|
|
1205
1310
|
)
|
|
1206
|
-
|
|
1207
|
-
new_playbooks: list[tuple[AgentPlaybook, list[UserPlaybook]]] = []
|
|
1208
1311
|
for cluster_playbooks in clusters.values():
|
|
1312
|
+
shared_mapping: dict[str, int] = {}
|
|
1313
|
+
if self.user_detail_stripper is None:
|
|
1314
|
+
prompt_cluster_playbooks = cluster_playbooks
|
|
1315
|
+
else:
|
|
1316
|
+
prompt_cluster_playbooks = [
|
|
1317
|
+
self._strip_user_playbook_for_prompt(playbook, shared_mapping)
|
|
1318
|
+
for playbook in cluster_playbooks
|
|
1319
|
+
]
|
|
1320
|
+
|
|
1209
1321
|
playbook = self._generate_playbook_from_cluster(
|
|
1210
|
-
|
|
1322
|
+
prompt_cluster_playbooks,
|
|
1211
1323
|
approved_playbooks_str,
|
|
1212
1324
|
direction_overlap_threshold=direction_overlap_threshold,
|
|
1213
1325
|
)
|
|
@@ -1289,14 +1401,18 @@ class PlaybookAggregator:
|
|
|
1289
1401
|
# Build content directly as a freeform summary
|
|
1290
1402
|
content_text = f"When {trigger}, {first_content}."
|
|
1291
1403
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
playbook_status=PlaybookStatus.PENDING,
|
|
1298
|
-
playbook_metadata="mock_generated",
|
|
1404
|
+
response = PlaybookAggregationOutput(
|
|
1405
|
+
playbook=StructuredPlaybookContent(
|
|
1406
|
+
content=content_text,
|
|
1407
|
+
trigger=trigger,
|
|
1408
|
+
)
|
|
1299
1409
|
)
|
|
1410
|
+
response, placeholder_count = self._sanitize_aggregation_response(response)
|
|
1411
|
+
self._record_placeholder_leakage(placeholder_count)
|
|
1412
|
+
playbook = self._process_aggregation_response(response, cluster_playbooks)
|
|
1413
|
+
if playbook is None:
|
|
1414
|
+
return None
|
|
1415
|
+
return playbook.model_copy(update={"playbook_metadata": "mock_generated"})
|
|
1300
1416
|
|
|
1301
1417
|
# Format raw playbooks for prompt using structured format
|
|
1302
1418
|
raw_playbooks_str = self._format_structured_cluster_input(
|
|
@@ -1312,6 +1428,7 @@ class PlaybookAggregator:
|
|
|
1312
1428
|
{
|
|
1313
1429
|
"user_playbooks": raw_playbooks_str,
|
|
1314
1430
|
"existing_approved_playbooks": existing_approved_playbooks_str,
|
|
1431
|
+
"aggregation_prompt_extra_instructions": self.aggregation_prompt_extra_instructions,
|
|
1315
1432
|
},
|
|
1316
1433
|
),
|
|
1317
1434
|
}
|
|
@@ -1324,6 +1441,16 @@ class PlaybookAggregator:
|
|
|
1324
1441
|
response_format=PlaybookAggregationOutput,
|
|
1325
1442
|
parse_structured_output=True,
|
|
1326
1443
|
)
|
|
1444
|
+
if isinstance(response, PlaybookAggregationOutput):
|
|
1445
|
+
response, placeholder_count = self._sanitize_aggregation_response(
|
|
1446
|
+
response
|
|
1447
|
+
)
|
|
1448
|
+
self._record_placeholder_leakage(placeholder_count)
|
|
1449
|
+
else:
|
|
1450
|
+
response, placeholder_count = self._sanitize_aggregation_log_value(
|
|
1451
|
+
response
|
|
1452
|
+
)
|
|
1453
|
+
self._record_placeholder_leakage(placeholder_count)
|
|
1327
1454
|
log_model_response(logger, "Aggregation structured response", response)
|
|
1328
1455
|
|
|
1329
1456
|
if not isinstance(response, PlaybookAggregationOutput):
|
|
@@ -1335,9 +1462,13 @@ class PlaybookAggregator:
|
|
|
1335
1462
|
|
|
1336
1463
|
return self._process_aggregation_response(response, cluster_playbooks)
|
|
1337
1464
|
except Exception as exc:
|
|
1465
|
+
sanitized_error, placeholder_count = self._sanitize_aggregation_log_value(
|
|
1466
|
+
str(exc)
|
|
1467
|
+
)
|
|
1468
|
+
self._record_placeholder_leakage(placeholder_count)
|
|
1338
1469
|
logger.error(
|
|
1339
1470
|
"AgentPlaybook aggregation failed due to %s, returning None.",
|
|
1340
|
-
|
|
1471
|
+
sanitized_error,
|
|
1341
1472
|
)
|
|
1342
1473
|
return None
|
|
1343
1474
|
|
|
@@ -1349,13 +1480,17 @@ class PlaybookAggregator:
|
|
|
1349
1480
|
|
|
1350
1481
|
Args:
|
|
1351
1482
|
response: Parsed PlaybookAggregationOutput from LLM
|
|
1352
|
-
cluster_playbooks:
|
|
1483
|
+
cluster_playbooks: Cluster playbooks used only for non-user metadata
|
|
1484
|
+
such as playbook name and agent version. Callers may pass
|
|
1485
|
+
prompt-sanitized copies here, so this method must not read
|
|
1486
|
+
user-authored fields from them.
|
|
1353
1487
|
|
|
1354
1488
|
Returns:
|
|
1355
1489
|
AgentPlaybook or None if no playbook should be generated
|
|
1356
1490
|
"""
|
|
1357
1491
|
if not response:
|
|
1358
1492
|
return None
|
|
1493
|
+
response, _placeholder_count = self._sanitize_aggregation_response(response)
|
|
1359
1494
|
|
|
1360
1495
|
structured = response.playbook
|
|
1361
1496
|
if structured is None:
|
|
@@ -1365,6 +1500,9 @@ class PlaybookAggregator:
|
|
|
1365
1500
|
# content is always the LLM's freeform summary;
|
|
1366
1501
|
# fall back to formatted structured fields for backward compatibility
|
|
1367
1502
|
playbook_content = ensure_playbook_content(structured.content, structured)
|
|
1503
|
+
if not playbook_content.strip():
|
|
1504
|
+
logger.info("Aggregated playbook has no valid content, skipping")
|
|
1505
|
+
return None
|
|
1368
1506
|
logger.info(
|
|
1369
1507
|
"Aggregated playbook content (freeform): %.200s",
|
|
1370
1508
|
playbook_content,
|