claude-smart 0.2.45 → 0.2.47
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 +47 -10
- package/bin/claude-smart.js +535 -23
- 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 +27 -6
- 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/layout.tsx +20 -0
- 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 +232 -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 +251 -0
- package/plugin/opencode/tsconfig.json +13 -0
- package/plugin/pyproject.toml +25 -4
- 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 +17 -9
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +498 -12
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- 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 +5 -5
- package/plugin/vendor/reflexio/.env.example +54 -30
- package/plugin/vendor/reflexio/README.md +17 -11
- package/plugin/vendor/reflexio/pyproject.toml +13 -1
- package/plugin/vendor/reflexio/reflexio/README.md +4 -1
- 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 +212 -52
- 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/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +154 -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 +246 -10
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +17 -5
- package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +224 -21
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +52 -25
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +11 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- 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/domain/governance.py +117 -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 +97 -3
- 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 +104 -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 +58 -33
- 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/api.py +899 -103
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/auth.py +90 -0
- 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/extensions.py +213 -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/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
- 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/rerank/cross_encoder_reranker.py +12 -1
- 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 +278 -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 +28 -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 +47 -16
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +219 -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 +18 -11
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- 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} +334 -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_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} +31 -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/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +99 -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 +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -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 +16 -2
- 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 +644 -197
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +106 -119
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +630 -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 +31 -1369
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +756 -210
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +148 -34
- 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/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +150 -23
- 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 +40 -57
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
- 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 +4 -677
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +134 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +62 -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/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -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 +372 -34
- 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/_auth.py +0 -25
- 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,
|
|
@@ -27,15 +24,21 @@ from reflexio.models.config_schema import (
|
|
|
27
24
|
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
28
25
|
from reflexio.server.llm.litellm_client import LiteLLMClient
|
|
29
26
|
from reflexio.server.services.operation_state_utils import OperationStateManager
|
|
27
|
+
from reflexio.server.services.playbook.aggregation_prompt_processing import (
|
|
28
|
+
AggregationPromptProcessingContext,
|
|
29
|
+
AggregationPromptProcessor,
|
|
30
|
+
)
|
|
30
31
|
from reflexio.server.services.playbook.playbook_service_constants import (
|
|
31
32
|
PlaybookServiceConstants,
|
|
32
33
|
)
|
|
33
34
|
from reflexio.server.services.playbook.playbook_service_utils import (
|
|
34
35
|
PlaybookAggregationOutput,
|
|
35
36
|
PlaybookAggregatorRequest,
|
|
37
|
+
StructuredPlaybookContent,
|
|
36
38
|
ensure_playbook_content,
|
|
37
39
|
)
|
|
38
40
|
from reflexio.server.services.service_utils import log_model_response
|
|
41
|
+
from reflexio.server.tracing import capture_anomaly, sentry_tags
|
|
39
42
|
from reflexio.server.usage_metrics import record_usage_event
|
|
40
43
|
|
|
41
44
|
logger = logging.getLogger(__name__)
|
|
@@ -52,17 +55,25 @@ class PlaybookAggregator:
|
|
|
52
55
|
llm_client: LiteLLMClient,
|
|
53
56
|
request_context: RequestContext,
|
|
54
57
|
agent_version: str,
|
|
58
|
+
aggregation_prompt_processor: AggregationPromptProcessor | None = None,
|
|
55
59
|
) -> None:
|
|
56
60
|
self.client = llm_client
|
|
57
61
|
self.storage = request_context.storage
|
|
58
62
|
self.configurator = request_context.configurator
|
|
59
63
|
self.request_context = request_context
|
|
60
64
|
self.agent_version = agent_version
|
|
65
|
+
self.aggregation_prompt_processor = aggregation_prompt_processor
|
|
61
66
|
|
|
62
67
|
# ===============================
|
|
63
68
|
# private methods - operation state
|
|
64
69
|
# ===============================
|
|
65
70
|
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _format_prompt_extra_instructions(instructions: str | None) -> str:
|
|
73
|
+
if not instructions or not instructions.strip():
|
|
74
|
+
return ""
|
|
75
|
+
return f"{instructions.strip()}\n"
|
|
76
|
+
|
|
66
77
|
def _create_state_manager(self) -> OperationStateManager:
|
|
67
78
|
"""
|
|
68
79
|
Create an OperationStateManager for the playbook aggregator.
|
|
@@ -103,7 +114,7 @@ class PlaybookAggregator:
|
|
|
103
114
|
# Count user playbooks with ID greater than last processed using efficient count query
|
|
104
115
|
# Only count current user playbooks (status=None), not archived or pending ones.
|
|
105
116
|
# Singleton aggregation operates on the user's whole playbook set — no name filter.
|
|
106
|
-
new_count = self.storage.count_user_playbooks( #
|
|
117
|
+
new_count = self.storage.count_user_playbooks( # pyright: ignore[reportOptionalMemberAccess]
|
|
107
118
|
min_user_playbook_id=last_processed_id,
|
|
108
119
|
agent_version=self.agent_version,
|
|
109
120
|
status_filter=[None],
|
|
@@ -194,6 +205,98 @@ class PlaybookAggregator:
|
|
|
194
205
|
blocks.append("\n".join(lines))
|
|
195
206
|
return "\n\n".join(blocks) if blocks else "(No playbook items)"
|
|
196
207
|
|
|
208
|
+
def _preprocess_prompt_field(
|
|
209
|
+
self,
|
|
210
|
+
text: str | None,
|
|
211
|
+
shared_state: dict[str, object],
|
|
212
|
+
processing_context: AggregationPromptProcessingContext | None = None,
|
|
213
|
+
) -> str | None:
|
|
214
|
+
if text is None or self.aggregation_prompt_processor is None:
|
|
215
|
+
return text
|
|
216
|
+
result = self.aggregation_prompt_processor.preprocess_prompt_text(
|
|
217
|
+
text,
|
|
218
|
+
shared_state=shared_state,
|
|
219
|
+
context=processing_context,
|
|
220
|
+
)
|
|
221
|
+
if processing_context is not None and (result.changed or result.text != text):
|
|
222
|
+
processing_context.changed = True
|
|
223
|
+
return result.text
|
|
224
|
+
|
|
225
|
+
def _preprocess_user_playbook_for_prompt(
|
|
226
|
+
self,
|
|
227
|
+
playbook: UserPlaybook,
|
|
228
|
+
shared_state: dict[str, object],
|
|
229
|
+
processing_context: AggregationPromptProcessingContext | None = None,
|
|
230
|
+
) -> UserPlaybook:
|
|
231
|
+
if self.aggregation_prompt_processor is None:
|
|
232
|
+
return playbook
|
|
233
|
+
return playbook.model_copy(
|
|
234
|
+
update={
|
|
235
|
+
"content": self._preprocess_prompt_field(
|
|
236
|
+
playbook.content, shared_state, processing_context
|
|
237
|
+
)
|
|
238
|
+
or "",
|
|
239
|
+
"trigger": self._preprocess_prompt_field(
|
|
240
|
+
playbook.trigger, shared_state, processing_context
|
|
241
|
+
),
|
|
242
|
+
"rationale": self._preprocess_prompt_field(
|
|
243
|
+
playbook.rationale, shared_state, processing_context
|
|
244
|
+
),
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _aggregation_prompt_extra_instructions_for_context(
|
|
249
|
+
self,
|
|
250
|
+
processing_context: AggregationPromptProcessingContext | None,
|
|
251
|
+
) -> str:
|
|
252
|
+
if (
|
|
253
|
+
processing_context is None
|
|
254
|
+
or not processing_context.changed
|
|
255
|
+
or self.aggregation_prompt_processor is None
|
|
256
|
+
):
|
|
257
|
+
return ""
|
|
258
|
+
|
|
259
|
+
context_instructions = self.aggregation_prompt_processor.prompt_instructions(
|
|
260
|
+
processing_context
|
|
261
|
+
)
|
|
262
|
+
if not isinstance(context_instructions, str):
|
|
263
|
+
context_instructions = None
|
|
264
|
+
return self._format_prompt_extra_instructions(context_instructions)
|
|
265
|
+
|
|
266
|
+
def _postprocess_aggregation_output(
|
|
267
|
+
self,
|
|
268
|
+
value: object,
|
|
269
|
+
processing_context: AggregationPromptProcessingContext | None = None,
|
|
270
|
+
) -> tuple[object, int]:
|
|
271
|
+
if self.aggregation_prompt_processor is None:
|
|
272
|
+
return value, 0
|
|
273
|
+
result = self.aggregation_prompt_processor.postprocess_aggregation_output(
|
|
274
|
+
value,
|
|
275
|
+
context=processing_context,
|
|
276
|
+
)
|
|
277
|
+
return result.value, result.artifacts_removed
|
|
278
|
+
|
|
279
|
+
def _postprocess_aggregation_response(
|
|
280
|
+
self,
|
|
281
|
+
response: PlaybookAggregationOutput,
|
|
282
|
+
processing_context: AggregationPromptProcessingContext | None = None,
|
|
283
|
+
) -> tuple[PlaybookAggregationOutput, int]:
|
|
284
|
+
processed, artifact_count = self._postprocess_aggregation_output(
|
|
285
|
+
response,
|
|
286
|
+
processing_context,
|
|
287
|
+
)
|
|
288
|
+
if not isinstance(processed, PlaybookAggregationOutput):
|
|
289
|
+
return response, 0
|
|
290
|
+
return processed, artifact_count
|
|
291
|
+
|
|
292
|
+
def _record_postprocessing_artifacts(self, artifact_count: int) -> None:
|
|
293
|
+
if artifact_count <= 0:
|
|
294
|
+
return
|
|
295
|
+
logger.warning(
|
|
296
|
+
"Post-processed %d residual artifacts in aggregated playbook output",
|
|
297
|
+
artifact_count,
|
|
298
|
+
)
|
|
299
|
+
|
|
197
300
|
@staticmethod
|
|
198
301
|
def _get_direction_key(fb: UserPlaybook) -> str:
|
|
199
302
|
"""
|
|
@@ -465,102 +568,6 @@ class PlaybookAggregator:
|
|
|
465
568
|
|
|
466
569
|
return changed_clusters, playbook_ids_to_archive
|
|
467
570
|
|
|
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
571
|
# ===============================
|
|
565
572
|
# public methods
|
|
566
573
|
# ===============================
|
|
@@ -572,6 +579,8 @@ class PlaybookAggregator:
|
|
|
572
579
|
dict: Aggregation stats with keys: clusters_found, user_playbooks_processed, playbooks_generated, skipped (optional)
|
|
573
580
|
"""
|
|
574
581
|
aggregation_start = time.perf_counter()
|
|
582
|
+
# Stable id for this aggregation run — groups all lineage events produced below.
|
|
583
|
+
_run_id = str(uuid.uuid4())
|
|
575
584
|
_empty_stats = {
|
|
576
585
|
"clusters_found": 0,
|
|
577
586
|
"user_playbooks_processed": 0,
|
|
@@ -667,6 +676,12 @@ class PlaybookAggregator:
|
|
|
667
676
|
playbook_name,
|
|
668
677
|
self.agent_version,
|
|
669
678
|
)
|
|
679
|
+
logger.info(
|
|
680
|
+
"Aggregation prompt processor: %s",
|
|
681
|
+
type(self.aggregation_prompt_processor).__name__
|
|
682
|
+
if self.aggregation_prompt_processor is not None
|
|
683
|
+
else "disabled",
|
|
684
|
+
)
|
|
670
685
|
|
|
671
686
|
# Get existing APPROVED and PENDING playbooks before archiving (to pass to LLM for deduplication).
|
|
672
687
|
# Singleton aggregation pulls the user's whole set — no name filter.
|
|
@@ -694,11 +709,6 @@ class PlaybookAggregator:
|
|
|
694
709
|
)
|
|
695
710
|
clusters = self.get_clusters(user_playbooks, playbook_aggregator_config)
|
|
696
711
|
|
|
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
712
|
# Determine which clusters changed (skip for rerun)
|
|
703
713
|
mgr = self._create_state_manager()
|
|
704
714
|
archived_playbook_ids = []
|
|
@@ -764,10 +774,6 @@ class PlaybookAggregator:
|
|
|
764
774
|
len(archived_playbook_ids),
|
|
765
775
|
)
|
|
766
776
|
|
|
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
777
|
try:
|
|
772
778
|
# Emit the started event inside the protected block so any failure
|
|
773
779
|
# from here on is paired with an aggregation_failed event.
|
|
@@ -789,6 +795,37 @@ class PlaybookAggregator:
|
|
|
789
795
|
)
|
|
790
796
|
new_playbooks = [playbook for playbook, _ in generated_pairs]
|
|
791
797
|
|
|
798
|
+
previous_fingerprints_for_changed_clusters = {}
|
|
799
|
+
changed_fps_by_previous_fp = {}
|
|
800
|
+
changed_fps_with_replacements = set()
|
|
801
|
+
previous_playbook_id_by_fp = {}
|
|
802
|
+
if not playbook_aggregator_request.rerun and prev_fingerprints:
|
|
803
|
+
for cluster_playbooks in changed_clusters.values():
|
|
804
|
+
fp = self._compute_cluster_fingerprint(cluster_playbooks)
|
|
805
|
+
current_user_ids = {
|
|
806
|
+
fb.user_playbook_id
|
|
807
|
+
for fb in cluster_playbooks
|
|
808
|
+
if fb.user_playbook_id is not None
|
|
809
|
+
}
|
|
810
|
+
matched_prev_fingerprints = {
|
|
811
|
+
prev_fp: fp_data
|
|
812
|
+
for prev_fp, fp_data in prev_fingerprints.items()
|
|
813
|
+
if fp_data.get("agent_playbook_id") is not None
|
|
814
|
+
and current_user_ids
|
|
815
|
+
& set(fp_data.get("user_playbook_ids") or [])
|
|
816
|
+
}
|
|
817
|
+
if matched_prev_fingerprints:
|
|
818
|
+
previous_fingerprints_for_changed_clusters[fp] = (
|
|
819
|
+
matched_prev_fingerprints
|
|
820
|
+
)
|
|
821
|
+
for prev_fp, fp_data in matched_prev_fingerprints.items():
|
|
822
|
+
changed_fps_by_previous_fp.setdefault(prev_fp, set()).add(
|
|
823
|
+
fp
|
|
824
|
+
)
|
|
825
|
+
playbook_id = fp_data.get("agent_playbook_id")
|
|
826
|
+
if playbook_id is not None:
|
|
827
|
+
previous_playbook_id_by_fp[prev_fp] = playbook_id
|
|
828
|
+
|
|
792
829
|
# Lazy archive: only full-archive when the LLM produced replacements.
|
|
793
830
|
# Skipping the archive when new_playbooks is empty preserves existing
|
|
794
831
|
# PENDING/APPROVED playbooks that the LLM identified as duplicates.
|
|
@@ -806,17 +843,12 @@ class PlaybookAggregator:
|
|
|
806
843
|
)
|
|
807
844
|
full_archive = False
|
|
808
845
|
|
|
809
|
-
# Save playbooks (returns playbooks with playbook_id populated)
|
|
810
|
-
saved_playbooks = self.storage.save_agent_playbooks(new_playbooks) # type: ignore[reportOptionalMemberAccess]
|
|
811
|
-
|
|
812
846
|
# Build new fingerprint state
|
|
813
847
|
new_fingerprints = {}
|
|
814
848
|
|
|
815
849
|
if not playbook_aggregator_request.rerun:
|
|
816
850
|
# Carry forward unchanged fingerprints from previous state
|
|
817
|
-
prev_fps =
|
|
818
|
-
name=playbook_name, version=self.agent_version
|
|
819
|
-
)
|
|
851
|
+
prev_fps = prev_fingerprints
|
|
820
852
|
current_fp_set = set()
|
|
821
853
|
for cluster_playbooks in clusters.values():
|
|
822
854
|
fp = self._compute_cluster_fingerprint(cluster_playbooks)
|
|
@@ -837,31 +869,47 @@ class PlaybookAggregator:
|
|
|
837
869
|
}
|
|
838
870
|
)
|
|
839
871
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
872
|
+
saved_playbook_list: list[AgentPlaybook] = []
|
|
873
|
+
selective_supersede_playbook_ids = set()
|
|
874
|
+
replaced_previous_fingerprints = set()
|
|
875
|
+
|
|
876
|
+
# Save each playbook + its aggregate event atomically, then assign
|
|
877
|
+
# fingerprints and source-windows for the saved row.
|
|
878
|
+
for playbook, cluster_playbooks in generated_pairs:
|
|
879
|
+
run_mode = "full_archive" if full_archive else "incremental"
|
|
880
|
+
member_ids = [
|
|
881
|
+
str(fb.user_playbook_id)
|
|
882
|
+
for fb in cluster_playbooks
|
|
883
|
+
if fb.user_playbook_id
|
|
884
|
+
]
|
|
885
|
+
saved_fb = self.storage.save_agent_playbook_with_aggregate_event( # type: ignore[reportOptionalMemberAccess]
|
|
886
|
+
playbook,
|
|
887
|
+
source_ids=member_ids,
|
|
888
|
+
request_id=_run_id,
|
|
889
|
+
run_mode=run_mode,
|
|
890
|
+
)
|
|
891
|
+
saved_playbook_list.append(saved_fb)
|
|
858
892
|
if saved_fb and saved_fb.agent_playbook_id:
|
|
859
893
|
fp_key = self._compute_cluster_fingerprint(cluster_playbooks)
|
|
894
|
+
changed_fps_with_replacements.add(fp_key)
|
|
860
895
|
raw_ids = sorted(fb.user_playbook_id for fb in cluster_playbooks)
|
|
861
896
|
new_fingerprints[fp_key] = {
|
|
862
897
|
"agent_playbook_id": saved_fb.agent_playbook_id,
|
|
863
898
|
"user_playbook_ids": raw_ids,
|
|
864
899
|
}
|
|
900
|
+
for prev_fp in previous_fingerprints_for_changed_clusters.get(
|
|
901
|
+
fp_key, {}
|
|
902
|
+
):
|
|
903
|
+
all_overlapping_clusters_replaced = (
|
|
904
|
+
changed_fps_by_previous_fp.get(prev_fp, set()).issubset(
|
|
905
|
+
changed_fps_with_replacements
|
|
906
|
+
)
|
|
907
|
+
)
|
|
908
|
+
playbook_id = previous_playbook_id_by_fp.get(prev_fp)
|
|
909
|
+
if all_overlapping_clusters_replaced:
|
|
910
|
+
replaced_previous_fingerprints.add(prev_fp)
|
|
911
|
+
if playbook_id is not None:
|
|
912
|
+
selective_supersede_playbook_ids.add(playbook_id)
|
|
865
913
|
self.storage.set_source_windows_for_agent_playbook( # type: ignore[reportOptionalMemberAccess]
|
|
866
914
|
saved_fb.agent_playbook_id,
|
|
867
915
|
[
|
|
@@ -876,6 +924,33 @@ class PlaybookAggregator:
|
|
|
876
924
|
],
|
|
877
925
|
)
|
|
878
926
|
|
|
927
|
+
# Changed clusters that did not get a replacement keep their previous
|
|
928
|
+
# fingerprint/playbook mapping so a later successful replacement can
|
|
929
|
+
# supersede the old playbook. Brand-new duplicate clusters still get
|
|
930
|
+
# a None marker to avoid repeated LLM calls for the same fingerprint.
|
|
931
|
+
for cluster_playbooks in changed_clusters.values():
|
|
932
|
+
fp = self._compute_cluster_fingerprint(cluster_playbooks)
|
|
933
|
+
if fp in new_fingerprints:
|
|
934
|
+
continue
|
|
935
|
+
|
|
936
|
+
previous_for_cluster = previous_fingerprints_for_changed_clusters.get(
|
|
937
|
+
fp, {}
|
|
938
|
+
)
|
|
939
|
+
preserved_previous = {
|
|
940
|
+
prev_fp: fp_data
|
|
941
|
+
for prev_fp, fp_data in previous_for_cluster.items()
|
|
942
|
+
if prev_fp not in replaced_previous_fingerprints
|
|
943
|
+
}
|
|
944
|
+
if preserved_previous:
|
|
945
|
+
new_fingerprints.update(preserved_previous)
|
|
946
|
+
continue
|
|
947
|
+
|
|
948
|
+
raw_ids = sorted(fb.user_playbook_id for fb in cluster_playbooks)
|
|
949
|
+
new_fingerprints[fp] = {
|
|
950
|
+
"agent_playbook_id": None,
|
|
951
|
+
"user_playbook_ids": raw_ids,
|
|
952
|
+
}
|
|
953
|
+
|
|
879
954
|
# Store fingerprints in operation state
|
|
880
955
|
mgr.update_cluster_fingerprints(
|
|
881
956
|
name=playbook_name,
|
|
@@ -886,39 +961,70 @@ class PlaybookAggregator:
|
|
|
886
961
|
# Update operation state with the highest user_playbook_id processed
|
|
887
962
|
self._update_operation_state(playbook_name, user_playbooks)
|
|
888
963
|
|
|
889
|
-
#
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
964
|
+
# Remove archived playbooks after successful aggregation. ALWAYS soft-supersede
|
|
965
|
+
# (never hard-delete) so the removal is reconstructable from lineage — mirrors the
|
|
966
|
+
# profile dedup always-soft path (#206).
|
|
967
|
+
archived_ids_without_overlapping_changed_cluster: set[int] = set()
|
|
968
|
+
if new_playbooks and prev_fingerprints:
|
|
969
|
+
archived_id_set = set(archived_playbook_ids)
|
|
970
|
+
for prev_fp, fp_data in prev_fingerprints.items():
|
|
971
|
+
playbook_id = fp_data.get("agent_playbook_id")
|
|
972
|
+
if (
|
|
973
|
+
playbook_id in archived_id_set
|
|
974
|
+
and prev_fp not in changed_fps_by_previous_fp
|
|
975
|
+
):
|
|
976
|
+
archived_ids_without_overlapping_changed_cluster.add(
|
|
977
|
+
playbook_id
|
|
978
|
+
)
|
|
979
|
+
ids_to_supersede = {
|
|
980
|
+
*selective_supersede_playbook_ids,
|
|
981
|
+
*archived_ids_without_overlapping_changed_cluster,
|
|
982
|
+
}
|
|
983
|
+
if not _run_id:
|
|
984
|
+
# Empty request_id makes the removal unreconstructable (lineage events are keyed
|
|
985
|
+
# on it). Fail loud and skip removal — never silently hard-delete.
|
|
986
|
+
capture_anomaly(
|
|
987
|
+
"lineage.aggregation.missing_request_id",
|
|
988
|
+
level="error",
|
|
989
|
+
org_id=self.request_context.org_id,
|
|
912
990
|
)
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
991
|
+
else:
|
|
992
|
+
try:
|
|
993
|
+
if full_archive:
|
|
994
|
+
for name in full_archive_playbook_names:
|
|
995
|
+
self.storage.supersede_agent_playbooks_by_playbook_name( # type: ignore[reportOptionalMemberAccess]
|
|
996
|
+
name,
|
|
997
|
+
agent_version=self.agent_version,
|
|
998
|
+
request_id=_run_id,
|
|
999
|
+
)
|
|
1000
|
+
elif ids_to_supersede:
|
|
1001
|
+
self.storage.supersede_agent_playbooks_by_ids( # type: ignore[reportOptionalMemberAccess]
|
|
1002
|
+
sorted(ids_to_supersede),
|
|
1003
|
+
request_id=_run_id,
|
|
1004
|
+
)
|
|
1005
|
+
elif archived_playbook_ids:
|
|
1006
|
+
logger.info(
|
|
1007
|
+
"Skipping selective supersede of %s (agent_version=%s): LLM produced 0 new playbooks; existing PENDING/APPROVED playbooks preserved",
|
|
1008
|
+
archived_playbook_ids,
|
|
1009
|
+
self.agent_version,
|
|
1010
|
+
)
|
|
1011
|
+
except Exception:
|
|
1012
|
+
with sentry_tags(
|
|
1013
|
+
subsystem="playbook_aggregation",
|
|
1014
|
+
op="supersede_agent_playbooks",
|
|
1015
|
+
org_id=self.request_context.org_id,
|
|
1016
|
+
request_id=_run_id,
|
|
1017
|
+
):
|
|
1018
|
+
logger.exception(
|
|
1019
|
+
"Failed to soft-supersede archived agent playbooks (run %s)",
|
|
1020
|
+
_run_id,
|
|
1021
|
+
)
|
|
1022
|
+
capture_anomaly(
|
|
1023
|
+
"lineage.aggregation.supersede_failed",
|
|
1024
|
+
level="error",
|
|
1025
|
+
org_id=self.request_context.org_id,
|
|
1026
|
+
request_id=_run_id,
|
|
919
1027
|
)
|
|
920
|
-
elif archived_playbook_ids:
|
|
921
|
-
self.storage.delete_agent_playbooks_by_ids(archived_playbook_ids) # type: ignore[reportOptionalMemberAccess]
|
|
922
1028
|
|
|
923
1029
|
self._enqueue_playbook_optimization(saved_playbook_list)
|
|
924
1030
|
|
|
@@ -1197,19 +1303,35 @@ class PlaybookAggregator:
|
|
|
1197
1303
|
direction_overlap_threshold: float = 0.6,
|
|
1198
1304
|
) -> list[tuple[AgentPlaybook, list[UserPlaybook]]]:
|
|
1199
1305
|
"""Generate agent playbooks while preserving their exact source cluster."""
|
|
1200
|
-
|
|
1306
|
+
new_playbooks: list[tuple[AgentPlaybook, list[UserPlaybook]]] = []
|
|
1201
1307
|
approved_playbooks_str = (
|
|
1202
1308
|
"\n".join([f"- {fb.content}" for fb in existing_approved_playbooks])
|
|
1203
1309
|
if existing_approved_playbooks
|
|
1204
1310
|
else "None"
|
|
1205
1311
|
)
|
|
1206
|
-
|
|
1207
|
-
new_playbooks: list[tuple[AgentPlaybook, list[UserPlaybook]]] = []
|
|
1208
1312
|
for cluster_playbooks in clusters.values():
|
|
1313
|
+
shared_state: dict[str, object] = {}
|
|
1314
|
+
processing_context = AggregationPromptProcessingContext(
|
|
1315
|
+
data={
|
|
1316
|
+
"agent_version": self.agent_version,
|
|
1317
|
+
"org_id": self.request_context.org_id,
|
|
1318
|
+
}
|
|
1319
|
+
)
|
|
1320
|
+
if self.aggregation_prompt_processor is None:
|
|
1321
|
+
prompt_cluster_playbooks = cluster_playbooks
|
|
1322
|
+
else:
|
|
1323
|
+
prompt_cluster_playbooks = [
|
|
1324
|
+
self._preprocess_user_playbook_for_prompt(
|
|
1325
|
+
playbook, shared_state, processing_context
|
|
1326
|
+
)
|
|
1327
|
+
for playbook in cluster_playbooks
|
|
1328
|
+
]
|
|
1329
|
+
|
|
1209
1330
|
playbook = self._generate_playbook_from_cluster(
|
|
1210
|
-
|
|
1331
|
+
prompt_cluster_playbooks,
|
|
1211
1332
|
approved_playbooks_str,
|
|
1212
1333
|
direction_overlap_threshold=direction_overlap_threshold,
|
|
1334
|
+
processing_context=processing_context,
|
|
1213
1335
|
)
|
|
1214
1336
|
if playbook is not None:
|
|
1215
1337
|
new_playbooks.append((playbook, cluster_playbooks))
|
|
@@ -1259,6 +1381,7 @@ class PlaybookAggregator:
|
|
|
1259
1381
|
cluster_playbooks: list[UserPlaybook],
|
|
1260
1382
|
existing_approved_playbooks_str: str,
|
|
1261
1383
|
direction_overlap_threshold: float = 0.6,
|
|
1384
|
+
processing_context: AggregationPromptProcessingContext | None = None,
|
|
1262
1385
|
) -> AgentPlaybook | None:
|
|
1263
1386
|
"""
|
|
1264
1387
|
Generate a playbook from a cluster using structured JSON output.
|
|
@@ -1289,14 +1412,21 @@ class PlaybookAggregator:
|
|
|
1289
1412
|
# Build content directly as a freeform summary
|
|
1290
1413
|
content_text = f"When {trigger}, {first_content}."
|
|
1291
1414
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
playbook_status=PlaybookStatus.PENDING,
|
|
1298
|
-
playbook_metadata="mock_generated",
|
|
1415
|
+
response = PlaybookAggregationOutput(
|
|
1416
|
+
playbook=StructuredPlaybookContent(
|
|
1417
|
+
content=content_text,
|
|
1418
|
+
trigger=trigger,
|
|
1419
|
+
)
|
|
1299
1420
|
)
|
|
1421
|
+
response, artifact_count = self._postprocess_aggregation_response(
|
|
1422
|
+
response,
|
|
1423
|
+
processing_context,
|
|
1424
|
+
)
|
|
1425
|
+
self._record_postprocessing_artifacts(artifact_count)
|
|
1426
|
+
playbook = self._process_aggregation_response(response, cluster_playbooks)
|
|
1427
|
+
if playbook is None:
|
|
1428
|
+
return None
|
|
1429
|
+
return playbook.model_copy(update={"playbook_metadata": "mock_generated"})
|
|
1300
1430
|
|
|
1301
1431
|
# Format raw playbooks for prompt using structured format
|
|
1302
1432
|
raw_playbooks_str = self._format_structured_cluster_input(
|
|
@@ -1312,6 +1442,9 @@ class PlaybookAggregator:
|
|
|
1312
1442
|
{
|
|
1313
1443
|
"user_playbooks": raw_playbooks_str,
|
|
1314
1444
|
"existing_approved_playbooks": existing_approved_playbooks_str,
|
|
1445
|
+
"aggregation_prompt_extra_instructions": self._aggregation_prompt_extra_instructions_for_context(
|
|
1446
|
+
processing_context
|
|
1447
|
+
),
|
|
1315
1448
|
},
|
|
1316
1449
|
),
|
|
1317
1450
|
}
|
|
@@ -1324,6 +1457,18 @@ class PlaybookAggregator:
|
|
|
1324
1457
|
response_format=PlaybookAggregationOutput,
|
|
1325
1458
|
parse_structured_output=True,
|
|
1326
1459
|
)
|
|
1460
|
+
if isinstance(response, PlaybookAggregationOutput):
|
|
1461
|
+
response, artifact_count = self._postprocess_aggregation_response(
|
|
1462
|
+
response,
|
|
1463
|
+
processing_context,
|
|
1464
|
+
)
|
|
1465
|
+
self._record_postprocessing_artifacts(artifact_count)
|
|
1466
|
+
else:
|
|
1467
|
+
response, artifact_count = self._postprocess_aggregation_output(
|
|
1468
|
+
response,
|
|
1469
|
+
processing_context,
|
|
1470
|
+
)
|
|
1471
|
+
self._record_postprocessing_artifacts(artifact_count)
|
|
1327
1472
|
log_model_response(logger, "Aggregation structured response", response)
|
|
1328
1473
|
|
|
1329
1474
|
if not isinstance(response, PlaybookAggregationOutput):
|
|
@@ -1335,9 +1480,14 @@ class PlaybookAggregator:
|
|
|
1335
1480
|
|
|
1336
1481
|
return self._process_aggregation_response(response, cluster_playbooks)
|
|
1337
1482
|
except Exception as exc:
|
|
1483
|
+
processed_error, artifact_count = self._postprocess_aggregation_output(
|
|
1484
|
+
str(exc),
|
|
1485
|
+
processing_context,
|
|
1486
|
+
)
|
|
1487
|
+
self._record_postprocessing_artifacts(artifact_count)
|
|
1338
1488
|
logger.error(
|
|
1339
1489
|
"AgentPlaybook aggregation failed due to %s, returning None.",
|
|
1340
|
-
|
|
1490
|
+
processed_error,
|
|
1341
1491
|
)
|
|
1342
1492
|
return None
|
|
1343
1493
|
|
|
@@ -1349,7 +1499,10 @@ class PlaybookAggregator:
|
|
|
1349
1499
|
|
|
1350
1500
|
Args:
|
|
1351
1501
|
response: Parsed PlaybookAggregationOutput from LLM
|
|
1352
|
-
cluster_playbooks:
|
|
1502
|
+
cluster_playbooks: Cluster playbooks used only for non-user metadata
|
|
1503
|
+
such as playbook name and agent version. Callers may pass
|
|
1504
|
+
prompt-preprocessed copies here, so this method must not read
|
|
1505
|
+
user-authored fields from them.
|
|
1353
1506
|
|
|
1354
1507
|
Returns:
|
|
1355
1508
|
AgentPlaybook or None if no playbook should be generated
|
|
@@ -1365,6 +1518,9 @@ class PlaybookAggregator:
|
|
|
1365
1518
|
# content is always the LLM's freeform summary;
|
|
1366
1519
|
# fall back to formatted structured fields for backward compatibility
|
|
1367
1520
|
playbook_content = ensure_playbook_content(structured.content, structured)
|
|
1521
|
+
if not playbook_content.strip():
|
|
1522
|
+
logger.info("Aggregated playbook has no valid content, skipping")
|
|
1523
|
+
return None
|
|
1368
1524
|
logger.info(
|
|
1369
1525
|
"Aggregated playbook content (freeform): %.200s",
|
|
1370
1526
|
playbook_content,
|