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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Render reflexio preferences + skills as markdown for display or injection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from openclaw_smart import oc_cite
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _first_nonempty(*values: Any) -> str:
|
|
12
|
+
"""Return the first truthy string value, or an empty string."""
|
|
13
|
+
for v in values:
|
|
14
|
+
if isinstance(v, str) and v.strip():
|
|
15
|
+
return v.strip()
|
|
16
|
+
return ""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def render(
|
|
20
|
+
*,
|
|
21
|
+
project_id: str,
|
|
22
|
+
user_playbooks: Iterable[Any],
|
|
23
|
+
agent_playbooks: Iterable[Any],
|
|
24
|
+
profiles: Iterable[Any],
|
|
25
|
+
) -> str:
|
|
26
|
+
"""Render skills + preferences as full audit markdown.
|
|
27
|
+
|
|
28
|
+
Empty sections are omitted. When all sections are empty, returns "".
|
|
29
|
+
"""
|
|
30
|
+
markdown, _ = render_with_registry(
|
|
31
|
+
project_id=project_id,
|
|
32
|
+
user_playbooks=user_playbooks,
|
|
33
|
+
agent_playbooks=agent_playbooks,
|
|
34
|
+
profiles=profiles,
|
|
35
|
+
)
|
|
36
|
+
return markdown
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def render_with_registry(
|
|
40
|
+
*,
|
|
41
|
+
project_id: str,
|
|
42
|
+
user_playbooks: Iterable[Any],
|
|
43
|
+
agent_playbooks: Iterable[Any],
|
|
44
|
+
profiles: Iterable[Any],
|
|
45
|
+
) -> tuple[str, list[dict[str, Any]]]:
|
|
46
|
+
"""Variant of ``render`` that also returns the citation registry.
|
|
47
|
+
|
|
48
|
+
Every skill and preference bullet is tagged with a short ``[oc:ID]``
|
|
49
|
+
prefix. The registry maps those ids back to ``{id, kind, title,
|
|
50
|
+
content}`` entries so ``events.agent_end`` can resolve citations into
|
|
51
|
+
human-readable titles for the dashboard.
|
|
52
|
+
|
|
53
|
+
Agent playbooks (cross-project, distilled) are listed before user
|
|
54
|
+
playbooks (this project's lessons) under one ``### Project-specific
|
|
55
|
+
skills`` heading. The model doesn't need to reason about the split.
|
|
56
|
+
"""
|
|
57
|
+
playbook_lines, playbook_entries = _format_combined_playbooks(
|
|
58
|
+
agent_playbooks=agent_playbooks, user_playbooks=user_playbooks
|
|
59
|
+
)
|
|
60
|
+
profile_lines, profile_entries = _format_profiles(profiles)
|
|
61
|
+
if not playbook_lines and not profile_lines:
|
|
62
|
+
return "", []
|
|
63
|
+
|
|
64
|
+
sections: list[str] = [f"## openclaw-smart — project `{project_id}`"]
|
|
65
|
+
if playbook_lines:
|
|
66
|
+
sections.append("### Project-specific skills")
|
|
67
|
+
sections.extend(playbook_lines)
|
|
68
|
+
if profile_lines:
|
|
69
|
+
sections.append("### Project preferences")
|
|
70
|
+
sections.extend(profile_lines)
|
|
71
|
+
sections.append(oc_cite.CITATION_INSTRUCTION)
|
|
72
|
+
return "\n".join(sections) + "\n", playbook_entries + profile_entries
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def render_inline(
|
|
76
|
+
*,
|
|
77
|
+
project_id: str,
|
|
78
|
+
user_playbooks: Iterable[Any],
|
|
79
|
+
agent_playbooks: Iterable[Any],
|
|
80
|
+
profiles: Iterable[Any],
|
|
81
|
+
) -> str:
|
|
82
|
+
"""Render skills + preferences for mid-session injection.
|
|
83
|
+
|
|
84
|
+
Same bullet format as ``render`` but with no top-level project header.
|
|
85
|
+
This block is injected just-in-time alongside an in-flight user prompt or
|
|
86
|
+
tool call, so the caller already has project context.
|
|
87
|
+
"""
|
|
88
|
+
markdown, _ = render_inline_with_registry(
|
|
89
|
+
project_id=project_id,
|
|
90
|
+
user_playbooks=user_playbooks,
|
|
91
|
+
agent_playbooks=agent_playbooks,
|
|
92
|
+
profiles=profiles,
|
|
93
|
+
)
|
|
94
|
+
return markdown
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def render_inline_with_registry(
|
|
98
|
+
*,
|
|
99
|
+
project_id: str,
|
|
100
|
+
user_playbooks: Iterable[Any],
|
|
101
|
+
agent_playbooks: Iterable[Any],
|
|
102
|
+
profiles: Iterable[Any],
|
|
103
|
+
) -> tuple[str, list[dict[str, Any]]]:
|
|
104
|
+
"""Variant of ``render_inline`` that also returns the citation registry."""
|
|
105
|
+
del project_id # kept for symmetry with ``render_with_registry``.
|
|
106
|
+
playbook_lines, playbook_entries = _format_combined_playbooks(
|
|
107
|
+
agent_playbooks=agent_playbooks, user_playbooks=user_playbooks
|
|
108
|
+
)
|
|
109
|
+
profile_lines, profile_entries = _format_profiles(profiles)
|
|
110
|
+
if not playbook_lines and not profile_lines:
|
|
111
|
+
return "", []
|
|
112
|
+
sections: list[str] = []
|
|
113
|
+
if playbook_lines:
|
|
114
|
+
sections.append("### Relevant project-specific skills")
|
|
115
|
+
sections.extend(playbook_lines)
|
|
116
|
+
if profile_lines:
|
|
117
|
+
sections.append("### Relevant project preferences")
|
|
118
|
+
sections.extend(profile_lines)
|
|
119
|
+
sections.append(oc_cite.CITATION_INSTRUCTION)
|
|
120
|
+
return "\n".join(sections) + "\n", playbook_entries + profile_entries
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _format_combined_playbooks(
|
|
124
|
+
*,
|
|
125
|
+
agent_playbooks: Iterable[Any],
|
|
126
|
+
user_playbooks: Iterable[Any],
|
|
127
|
+
) -> tuple[list[str], list[dict[str, Any]]]:
|
|
128
|
+
"""Render agent playbooks first, then user playbooks, with one shared rank counter."""
|
|
129
|
+
lines: list[str] = []
|
|
130
|
+
entries: list[dict[str, Any]] = []
|
|
131
|
+
rank = 0
|
|
132
|
+
for pb in agent_playbooks:
|
|
133
|
+
rank = _append_playbook_bullet(
|
|
134
|
+
pb, "agent_playbook_id", "agent_playbook", rank, lines, entries
|
|
135
|
+
)
|
|
136
|
+
for pb in user_playbooks:
|
|
137
|
+
rank = _append_playbook_bullet(
|
|
138
|
+
pb, "user_playbook_id", "user_playbook", rank, lines, entries
|
|
139
|
+
)
|
|
140
|
+
return lines, entries
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _append_playbook_bullet(
|
|
144
|
+
pb: Any,
|
|
145
|
+
id_field: str,
|
|
146
|
+
source_kind: str,
|
|
147
|
+
rank: int,
|
|
148
|
+
lines: list[str],
|
|
149
|
+
entries: list[dict[str, Any]],
|
|
150
|
+
) -> int:
|
|
151
|
+
content = _first_nonempty(_field(pb, "content"))
|
|
152
|
+
if not content:
|
|
153
|
+
return rank
|
|
154
|
+
rank += 1
|
|
155
|
+
trigger = _first_nonempty(_field(pb, "trigger"))
|
|
156
|
+
rationale = _first_nonempty(_field(pb, "rationale"))
|
|
157
|
+
real_id = _field(pb, id_field)
|
|
158
|
+
item_id = oc_cite.rank_id("playbook", rank, real_id)
|
|
159
|
+
title = _title_from_content(content)
|
|
160
|
+
bullet = f"- [oc:{item_id}] {content}"
|
|
161
|
+
if trigger:
|
|
162
|
+
bullet += f" _(when: {trigger})_"
|
|
163
|
+
if rationale:
|
|
164
|
+
bullet += f" — *why:* {rationale}"
|
|
165
|
+
lines.append(bullet)
|
|
166
|
+
entries.append(
|
|
167
|
+
{
|
|
168
|
+
"id": item_id,
|
|
169
|
+
"kind": "playbook",
|
|
170
|
+
"title": title,
|
|
171
|
+
"content": content,
|
|
172
|
+
"real_id": str(real_id) if real_id is not None else None,
|
|
173
|
+
"source_kind": source_kind,
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
return rank
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _format_profiles(
|
|
180
|
+
profiles: Iterable[Any],
|
|
181
|
+
) -> tuple[list[str], list[dict[str, Any]]]:
|
|
182
|
+
lines: list[str] = []
|
|
183
|
+
entries: list[dict[str, Any]] = []
|
|
184
|
+
rank = 0
|
|
185
|
+
for p in profiles:
|
|
186
|
+
content = _first_nonempty(_field(p, "content"))
|
|
187
|
+
if not content:
|
|
188
|
+
continue
|
|
189
|
+
rank += 1
|
|
190
|
+
real_id = _field(p, "profile_id")
|
|
191
|
+
item_id = oc_cite.rank_id("profile", rank, real_id)
|
|
192
|
+
title = _title_from_content(content)
|
|
193
|
+
lines.append(f"- [oc:{item_id}] {content}")
|
|
194
|
+
entries.append(
|
|
195
|
+
{
|
|
196
|
+
"id": item_id,
|
|
197
|
+
"kind": "profile",
|
|
198
|
+
"title": title,
|
|
199
|
+
"content": content,
|
|
200
|
+
"real_id": str(real_id) if real_id is not None else None,
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
return lines, entries
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _title_from_content(content: str, limit: int = 80) -> str:
|
|
207
|
+
"""Derive a compact human-readable title from a bullet's content."""
|
|
208
|
+
text = content.strip()
|
|
209
|
+
if not text:
|
|
210
|
+
return ""
|
|
211
|
+
for terminator in (". ", "\n"):
|
|
212
|
+
idx = text.find(terminator)
|
|
213
|
+
if 0 < idx <= limit:
|
|
214
|
+
return text[:idx].rstrip()
|
|
215
|
+
if len(text) <= limit:
|
|
216
|
+
return text
|
|
217
|
+
return text[: limit - 1].rstrip() + "…"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _field(obj: Any, name: str) -> Any:
|
|
221
|
+
"""Read ``name`` from either an attribute or a dict key."""
|
|
222
|
+
if isinstance(obj, dict):
|
|
223
|
+
return obj.get(name)
|
|
224
|
+
return getattr(obj, name, None)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Shared "search reflexio, render markdown, emit envelope" pipeline.
|
|
2
|
+
|
|
3
|
+
before_tool_call and before_prompt_build both (a) run a query-aware reflexio
|
|
4
|
+
search, (b) render the hits with ``context_format.render_inline_with_registry``,
|
|
5
|
+
(c) persist the citation registry for the agent_end hook to resolve, and
|
|
6
|
+
(d) emit a ``prependContext`` envelope on stdout. The TS shim wraps stdout
|
|
7
|
+
and translates it to the openClaw plugin-SDK return shape.
|
|
8
|
+
|
|
9
|
+
Per Phase 0 finding B2 we use ``prependContext`` (per-turn) rather than
|
|
10
|
+
``prependSystemContext`` (cached) so injected context refreshes every turn.
|
|
11
|
+
``before_tool_call`` is observe-only per finding Q1, so handlers there
|
|
12
|
+
should NOT call ``emit_context`` — they only append state.
|
|
13
|
+
|
|
14
|
+
The caller remains responsible for handler-specific framing — see the two
|
|
15
|
+
call sites for the small policy differences.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
|
|
24
|
+
from openclaw_smart import context_format, oc_cite, state
|
|
25
|
+
from openclaw_smart.reflexio_adapter import Adapter
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def emit_context(
|
|
29
|
+
*,
|
|
30
|
+
session_id: str,
|
|
31
|
+
project_id: str,
|
|
32
|
+
query: str,
|
|
33
|
+
top_k: int,
|
|
34
|
+
adapter: Adapter | None = None,
|
|
35
|
+
) -> bool:
|
|
36
|
+
"""Search reflexio, render hits, emit ``prependContext`` JSON on stdout.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
session_id (str): openClaw session id (``sessionKey``); used to scope
|
|
40
|
+
the per-session citation registry.
|
|
41
|
+
project_id (str): reflexio ``user_id`` for this repo.
|
|
42
|
+
query (str): Free-text query routed to reflexio's unified
|
|
43
|
+
``/api/search`` endpoint, which fans out to user playbooks
|
|
44
|
+
(project-scoped), agent playbooks (global), and preferences
|
|
45
|
+
(project-scoped) server-side.
|
|
46
|
+
top_k (int): Cap on hits per collection.
|
|
47
|
+
adapter (Adapter | None): Injection seam for tests. A fresh
|
|
48
|
+
``Adapter()`` is used when ``None``.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
bool: ``True`` when markdown was emitted to stdout; ``False`` when
|
|
52
|
+
the search returned nothing to inject.
|
|
53
|
+
"""
|
|
54
|
+
user_playbooks, agent_playbooks, profiles = (adapter or Adapter()).search_all(
|
|
55
|
+
project_id=project_id,
|
|
56
|
+
query=query,
|
|
57
|
+
top_k=top_k,
|
|
58
|
+
)
|
|
59
|
+
markdown, registry = context_format.render_inline_with_registry(
|
|
60
|
+
project_id=project_id,
|
|
61
|
+
user_playbooks=user_playbooks,
|
|
62
|
+
agent_playbooks=agent_playbooks,
|
|
63
|
+
profiles=profiles,
|
|
64
|
+
)
|
|
65
|
+
if not markdown:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
oc_cite.ensure_installed()
|
|
69
|
+
state.append_injected(
|
|
70
|
+
session_id,
|
|
71
|
+
(dict(entry, ts=int(time.time())) for entry in registry),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
sys.stdout.write(json.dumps({"prependContext": markdown}))
|
|
75
|
+
sys.stdout.write("\n")
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
__all__ = ["emit_context"]
|
|
File without changes
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""after_tool_call hook — record the tool invocation and its outcome.
|
|
2
|
+
|
|
3
|
+
openClaw delivers ``after_tool_call`` payloads with camelCase fields
|
|
4
|
+
(``toolName``, ``params``, ``result``). Per Phase 0 finding B1 the handler
|
|
5
|
+
translates these to snake_case before persisting so downstream consumers
|
|
6
|
+
(state buffer, reflexio API) see a stable shape.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import time
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from openclaw_smart import state
|
|
16
|
+
|
|
17
|
+
# Tool inputs are persisted locally and later published to reflexio, so we
|
|
18
|
+
# apply a conservative redaction pass at ingestion time. Chosen to avoid
|
|
19
|
+
# false positives over maximal coverage — the dashboard shows these
|
|
20
|
+
# verbatim, and users noticing a masked command is far less surprising
|
|
21
|
+
# than a masked ``LOG_LEVEL=INFO``.
|
|
22
|
+
_MAX_STR_LEN = 4096
|
|
23
|
+
_SECRET_ASSIGNMENT = re.compile(
|
|
24
|
+
r"(?P<key>[A-Z][A-Z0-9_]{2,})=(?P<quote>['\"]?)"
|
|
25
|
+
r"(?P<value>[A-Za-z0-9+/=_\-]{20,})(?P=quote)"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _looks_like_secret(value: str) -> bool:
|
|
30
|
+
"""Heuristic: mixed-case letters plus digits suggest a high-entropy token."""
|
|
31
|
+
has_lower = any(c.islower() for c in value)
|
|
32
|
+
has_upper = any(c.isupper() for c in value)
|
|
33
|
+
has_digit = any(c.isdigit() for c in value)
|
|
34
|
+
return has_lower and has_upper and has_digit
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _mask_secrets(text: str) -> str:
|
|
38
|
+
"""Mask values that look like high-entropy secrets in ``KEY=value`` form."""
|
|
39
|
+
|
|
40
|
+
def sub(match: re.Match[str]) -> str:
|
|
41
|
+
value = match.group("value")
|
|
42
|
+
if not _looks_like_secret(value):
|
|
43
|
+
return match.group(0)
|
|
44
|
+
key = match.group("key")
|
|
45
|
+
quote = match.group("quote")
|
|
46
|
+
return f"{key}={quote}<redacted:{len(value)}>{quote}"
|
|
47
|
+
|
|
48
|
+
return _SECRET_ASSIGNMENT.sub(sub, text)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _redact_string(value: str) -> str:
|
|
52
|
+
"""Mask secrets in ``value`` then truncate to ``_MAX_STR_LEN`` chars."""
|
|
53
|
+
masked = _mask_secrets(value)
|
|
54
|
+
if len(masked) > _MAX_STR_LEN:
|
|
55
|
+
return masked[:_MAX_STR_LEN] + "…(truncated)"
|
|
56
|
+
return masked
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _redact(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
60
|
+
"""Redact obvious secrets and truncate oversized string values."""
|
|
61
|
+
return {
|
|
62
|
+
k: _redact_string(v) if isinstance(v, str) else v for k, v in tool_input.items()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _derive_status(tool_response: Any) -> str:
|
|
67
|
+
"""Classify the tool outcome as 'success' or 'error'.
|
|
68
|
+
|
|
69
|
+
openClaw's ``after_tool_call`` payload places the tool result under
|
|
70
|
+
``result``; a structured failure may use ``is_error``/``error`` keys,
|
|
71
|
+
while plain string results default to success.
|
|
72
|
+
"""
|
|
73
|
+
if isinstance(tool_response, dict) and (
|
|
74
|
+
tool_response.get("is_error") or tool_response.get("error")
|
|
75
|
+
):
|
|
76
|
+
return "error"
|
|
77
|
+
return "success"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
_OUTPUT_TEXT_KEYS = ("stdout", "stderr", "output", "content", "text", "error")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _flatten_tool_response_text(tool_response: Any) -> str:
|
|
84
|
+
"""Flatten ``tool_response`` into a single string for buffering.
|
|
85
|
+
|
|
86
|
+
Tool responses arrive in heterogeneous shapes — Bash sends a dict with
|
|
87
|
+
``stdout``/``stderr``, Edit/Read send a string or a dict with
|
|
88
|
+
``content``/``output``, and failures populate ``error``. Joining the
|
|
89
|
+
well-known string-valued keys preserves the parts most useful for
|
|
90
|
+
downstream learning (failure messages, command output) without
|
|
91
|
+
serializing entire structured payloads.
|
|
92
|
+
"""
|
|
93
|
+
if tool_response is None:
|
|
94
|
+
return ""
|
|
95
|
+
if isinstance(tool_response, str):
|
|
96
|
+
return tool_response
|
|
97
|
+
if isinstance(tool_response, dict):
|
|
98
|
+
parts = [
|
|
99
|
+
tool_response[key]
|
|
100
|
+
for key in _OUTPUT_TEXT_KEYS
|
|
101
|
+
if isinstance(tool_response.get(key), str) and tool_response[key]
|
|
102
|
+
]
|
|
103
|
+
if parts:
|
|
104
|
+
return "\n".join(parts)
|
|
105
|
+
for key in ("text", "content"):
|
|
106
|
+
value = tool_response.get(key)
|
|
107
|
+
if isinstance(value, str):
|
|
108
|
+
return value
|
|
109
|
+
return ""
|
|
110
|
+
for attr in ("text", "content"):
|
|
111
|
+
value = getattr(tool_response, attr, None)
|
|
112
|
+
if isinstance(value, str):
|
|
113
|
+
return value
|
|
114
|
+
return ""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _extract_fields(payload: dict[str, Any]) -> dict[str, Any]:
|
|
118
|
+
"""Map openClaw's camelCase keys onto our snake_case schema.
|
|
119
|
+
|
|
120
|
+
Accepts either style so the handler is resilient to dispatcher changes —
|
|
121
|
+
if a future TS shim emits already-translated keys, the snake_case
|
|
122
|
+
branch wins.
|
|
123
|
+
"""
|
|
124
|
+
return {
|
|
125
|
+
"session_id": payload.get("sessionKey") or payload.get("sessionId"),
|
|
126
|
+
"tool_name": payload.get("tool_name") or payload.get("toolName") or "",
|
|
127
|
+
"tool_input": payload.get("tool_input") or payload.get("params") or {},
|
|
128
|
+
"tool_response": payload.get("tool_response")
|
|
129
|
+
if "tool_response" in payload
|
|
130
|
+
else payload.get("result"),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def handle(payload: dict[str, Any]) -> None:
|
|
135
|
+
"""Persist one tool invocation to the session JSONL buffer."""
|
|
136
|
+
fields = _extract_fields(payload)
|
|
137
|
+
session_id = fields["session_id"]
|
|
138
|
+
tool_name = fields["tool_name"]
|
|
139
|
+
if not session_id or not tool_name:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
tool_response = fields["tool_response"]
|
|
143
|
+
output_text = _flatten_tool_response_text(tool_response)
|
|
144
|
+
# ``_redact`` expects a dict; non-dict payloads (raw string, list, or None)
|
|
145
|
+
# are wrapped under ``_raw`` so the redaction path stays uniform without
|
|
146
|
+
# crashing on the unusual shape.
|
|
147
|
+
raw_tool_input = fields["tool_input"]
|
|
148
|
+
if isinstance(raw_tool_input, dict):
|
|
149
|
+
safe_tool_input: dict[str, Any] = _redact(raw_tool_input)
|
|
150
|
+
else:
|
|
151
|
+
safe_tool_input = {"_raw": _redact_string(str(raw_tool_input))}
|
|
152
|
+
record = {
|
|
153
|
+
"ts": int(time.time()),
|
|
154
|
+
"role": "Assistant_tool",
|
|
155
|
+
"tool_name": tool_name,
|
|
156
|
+
"tool_input": safe_tool_input,
|
|
157
|
+
"tool_output": _redact_string(output_text) if output_text else "",
|
|
158
|
+
"status": _derive_status(tool_response),
|
|
159
|
+
}
|
|
160
|
+
state.append(session_id, record)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""agent_end hook — finalize the assistant turn and publish to reflexio.
|
|
2
|
+
|
|
3
|
+
This handler is the openClaw analogue of claude-smart's ``stop.py``, but
|
|
4
|
+
substantially simpler. Per Phase 0 finding Q2, openClaw delivers the final
|
|
5
|
+
messages inline as ``event.messages: unknown[]``, so we read assistant text
|
|
6
|
+
directly from the payload instead of polling a transcript JSONL on disk.
|
|
7
|
+
|
|
8
|
+
Pipeline:
|
|
9
|
+
1. Extract final assistant text from ``payload.messages``.
|
|
10
|
+
2. Parse ``[oc:…]`` citation markers from that text and resolve them
|
|
11
|
+
against the per-session injected registry.
|
|
12
|
+
3. Append an ``Assistant`` record (always, even when the text is empty —
|
|
13
|
+
``state.unpublished_slice`` folds any buffered ``Assistant_tool``
|
|
14
|
+
records into the next ``Assistant`` turn's ``tools_used``).
|
|
15
|
+
4. Drain the buffer to reflexio via ``publish.publish_unpublished``.
|
|
16
|
+
|
|
17
|
+
claude-smart's plan-mode decision scan and ``cs-cite`` Bash tool scan are
|
|
18
|
+
intentionally NOT ported — both rely on Claude-Code-specific transcript
|
|
19
|
+
shapes that openClaw does not emit.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import time
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from openclaw_smart import ids, oc_cite, publish, state
|
|
29
|
+
|
|
30
|
+
_LOGGER = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _extract_assistant_text(messages: Any) -> str:
|
|
34
|
+
"""Return the final assistant turn's text from a messages list.
|
|
35
|
+
|
|
36
|
+
Walks ``messages`` from the end, collecting every contiguous assistant
|
|
37
|
+
entry up to the most recent non-assistant boundary, then joins their
|
|
38
|
+
text content. Mirrors claude-smart's transcript walker but works on
|
|
39
|
+
inline message dicts.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
messages (Any): openClaw's ``event.messages`` blob, expected to be
|
|
43
|
+
a list of dicts with ``role`` and ``content`` fields.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
str: Concatenated assistant text, or ``""`` when none is found.
|
|
47
|
+
"""
|
|
48
|
+
if not isinstance(messages, list):
|
|
49
|
+
return ""
|
|
50
|
+
collected: list[str] = []
|
|
51
|
+
for entry in reversed(messages):
|
|
52
|
+
if not isinstance(entry, dict):
|
|
53
|
+
continue
|
|
54
|
+
role = _entry_role(entry)
|
|
55
|
+
if role != "assistant":
|
|
56
|
+
# Boundary — stop collecting; everything before this belongs to
|
|
57
|
+
# earlier turns and has already been (or will be) published.
|
|
58
|
+
break
|
|
59
|
+
content = _entry_content(entry)
|
|
60
|
+
text_parts = _extract_text_blocks(content)
|
|
61
|
+
if text_parts:
|
|
62
|
+
# Prepend because we walked in reverse order.
|
|
63
|
+
collected = text_parts + collected
|
|
64
|
+
return "\n\n".join(part for part in collected if part)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _entry_role(entry: dict[str, Any]) -> str:
|
|
68
|
+
"""Read the role from an openClaw or Claude-Code-shaped message dict."""
|
|
69
|
+
role = entry.get("role")
|
|
70
|
+
if isinstance(role, str):
|
|
71
|
+
return role.lower()
|
|
72
|
+
message = entry.get("message")
|
|
73
|
+
if isinstance(message, dict):
|
|
74
|
+
nested = message.get("role")
|
|
75
|
+
if isinstance(nested, str):
|
|
76
|
+
return nested.lower()
|
|
77
|
+
return ""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _entry_content(entry: dict[str, Any]) -> Any:
|
|
81
|
+
"""Return the content payload from a message entry, accepting both shapes."""
|
|
82
|
+
if "content" in entry:
|
|
83
|
+
return entry["content"]
|
|
84
|
+
message = entry.get("message")
|
|
85
|
+
if isinstance(message, dict):
|
|
86
|
+
return message.get("content")
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _extract_text_blocks(content: Any) -> list[str]:
|
|
91
|
+
"""Return assistant-visible text from a message content payload.
|
|
92
|
+
|
|
93
|
+
Accepts:
|
|
94
|
+
- bare strings (``content: "hi"``)
|
|
95
|
+
- block lists (``content: [{type: "text", text: "hi"}, ...]``)
|
|
96
|
+
"""
|
|
97
|
+
if isinstance(content, str):
|
|
98
|
+
return [content] if content else []
|
|
99
|
+
if not isinstance(content, list):
|
|
100
|
+
return []
|
|
101
|
+
out: list[str] = []
|
|
102
|
+
for block in content:
|
|
103
|
+
if not isinstance(block, dict):
|
|
104
|
+
continue
|
|
105
|
+
text = block.get("text")
|
|
106
|
+
if isinstance(text, str) and text:
|
|
107
|
+
out.append(text)
|
|
108
|
+
continue
|
|
109
|
+
inner = block.get("content")
|
|
110
|
+
if isinstance(inner, str) and inner:
|
|
111
|
+
out.append(inner)
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _resolve_cited_items(session_id: str, cited_ids: list[str]) -> list[dict[str, Any]]:
|
|
116
|
+
"""Map citation ids to ``{id, kind, title}`` entries via the session registry.
|
|
117
|
+
|
|
118
|
+
Unknown ids (model hallucinations, or items injected in a newer session
|
|
119
|
+
than this hook can see) are dropped. Duplicate ids within one turn
|
|
120
|
+
collapse to a single entry — the user-facing badge row doesn't need
|
|
121
|
+
the multiplicity.
|
|
122
|
+
"""
|
|
123
|
+
if not cited_ids:
|
|
124
|
+
return []
|
|
125
|
+
registry = state.read_injected(session_id)
|
|
126
|
+
seen: set[str] = set()
|
|
127
|
+
resolved: list[dict[str, Any]] = []
|
|
128
|
+
for cid in cited_ids:
|
|
129
|
+
if cid in seen:
|
|
130
|
+
continue
|
|
131
|
+
entry = registry.get(cid)
|
|
132
|
+
if not entry:
|
|
133
|
+
continue
|
|
134
|
+
seen.add(cid)
|
|
135
|
+
item: dict[str, Any] = {
|
|
136
|
+
"id": entry.get("id", cid),
|
|
137
|
+
"kind": entry.get("kind", ""),
|
|
138
|
+
"title": entry.get("title", ""),
|
|
139
|
+
}
|
|
140
|
+
real_id = entry.get("real_id")
|
|
141
|
+
if real_id:
|
|
142
|
+
item["real_id"] = real_id
|
|
143
|
+
source_kind = entry.get("source_kind")
|
|
144
|
+
if isinstance(source_kind, str) and source_kind:
|
|
145
|
+
item["source_kind"] = source_kind
|
|
146
|
+
resolved.append(item)
|
|
147
|
+
return resolved
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def handle(payload: dict[str, Any]) -> None:
|
|
151
|
+
"""Finalize the current assistant turn and publish to reflexio.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
payload (dict[str, Any]): openClaw event/ctx blob, expected to contain
|
|
155
|
+
``sessionKey`` (or ``sessionId``), ``messages``, and optionally
|
|
156
|
+
``agentId`` / ``workspaceDir``.
|
|
157
|
+
"""
|
|
158
|
+
session_id = payload.get("sessionKey") or payload.get("sessionId")
|
|
159
|
+
if not session_id:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
project_id = ids.resolve_project_id_with_fallback(
|
|
163
|
+
cwd=payload.get("workspaceDir"),
|
|
164
|
+
agent_id=payload.get("agentId"),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
assistant_text = _extract_assistant_text(payload.get("messages"))
|
|
168
|
+
cited_ids = oc_cite.parse_text_citations(assistant_text)
|
|
169
|
+
cited_items = _resolve_cited_items(session_id, cited_ids)
|
|
170
|
+
|
|
171
|
+
now = int(time.time())
|
|
172
|
+
record: dict[str, Any] = {
|
|
173
|
+
"ts": now,
|
|
174
|
+
"role": "Assistant",
|
|
175
|
+
"content": assistant_text,
|
|
176
|
+
"user_id": project_id,
|
|
177
|
+
}
|
|
178
|
+
if cited_items:
|
|
179
|
+
record["cited_items"] = cited_items
|
|
180
|
+
state.append(session_id, record)
|
|
181
|
+
|
|
182
|
+
publish.publish_unpublished(
|
|
183
|
+
session_id=session_id,
|
|
184
|
+
project_id=project_id,
|
|
185
|
+
force_extraction=False,
|
|
186
|
+
skip_aggregation=False,
|
|
187
|
+
)
|