claude-smart 0.2.44 → 0.2.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +34 -5
- package/bin/claude-smart.js +295 -5
- package/package.json +22 -3
- package/plugin/.claude-plugin/plugin.json +4 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +22 -2
- package/plugin/commands/clear-all.md +1 -0
- package/plugin/commands/dashboard.md +1 -0
- package/plugin/commands/learn.md +1 -0
- package/plugin/commands/restart.md +1 -0
- package/plugin/commands/show.md +1 -0
- package/plugin/dashboard/app/configure/env/page.tsx +40 -14
- package/plugin/dashboard/app/configure/server/page.tsx +51 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +8 -8
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +7 -3
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +10 -3
- package/plugin/dashboard/lib/claude-settings-file.ts +20 -10
- package/plugin/dashboard/lib/reflexio-client.ts +16 -0
- package/plugin/dashboard/lib/status.ts +10 -3
- package/plugin/dashboard/lib/types.ts +18 -6
- package/plugin/hooks/codex-hooks.json +0 -1
- package/plugin/opencode/assistant-buffer.ts +108 -0
- package/plugin/opencode/dist/assistant-buffer.js +96 -0
- package/plugin/opencode/dist/internal.js +12 -0
- package/plugin/opencode/dist/payload.js +85 -0
- package/plugin/opencode/dist/server.mjs +158 -0
- package/plugin/opencode/internal.ts +18 -0
- package/plugin/opencode/package.json +3 -0
- package/plugin/opencode/payload.ts +90 -0
- package/plugin/opencode/server.mts +174 -0
- package/plugin/opencode/tsconfig.json +13 -0
- package/plugin/pyproject.toml +20 -3
- package/plugin/scripts/_lib.sh +77 -29
- package/plugin/scripts/backend-service.sh +27 -5
- package/plugin/scripts/dashboard-service.sh +7 -5
- package/plugin/scripts/ensure-plugin-root.sh +4 -4
- package/plugin/scripts/hook_entry.sh +3 -3
- package/plugin/scripts/opencode-claude-compat +4 -0
- package/plugin/scripts/opencode-claude-compat.cmd +3 -0
- package/plugin/scripts/opencode-claude-compat.js +225 -0
- package/plugin/scripts/smart-install.sh +10 -8
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +304 -6
- package/plugin/src/claude_smart/env_config.py +12 -4
- package/plugin/src/claude_smart/events/session_start.py +26 -7
- package/plugin/src/claude_smart/events/stop.py +1 -1
- package/plugin/src/claude_smart/ids.py +1 -1
- package/plugin/src/claude_smart/reflexio_adapter.py +5 -5
- package/plugin/src/claude_smart/runtime.py +7 -1
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +54 -30
- package/plugin/vendor/reflexio/README.md +14 -8
- package/plugin/vendor/reflexio/pyproject.toml +13 -1
- package/plugin/vendor/reflexio/reflexio/README.md +1 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +9 -7
- package/plugin/vendor/reflexio/reflexio/cli/__main__.py +10 -1
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +22 -5
- package/plugin/vendor/reflexio/reflexio/cli/commands/interactions.py +34 -4
- package/plugin/vendor/reflexio/reflexio/cli/commands/profiles.py +12 -7
- package/plugin/vendor/reflexio/reflexio/cli/commands/services.py +13 -6
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +210 -50
- package/plugin/vendor/reflexio/reflexio/cli/commands/shortcuts.py +15 -2
- package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +292 -22
- package/plugin/vendor/reflexio/reflexio/cli/log_format.py +29 -4
- package/plugin/vendor/reflexio/reflexio/cli/run_services.py +17 -8
- package/plugin/vendor/reflexio/reflexio/cli/stop_services.py +13 -10
- package/plugin/vendor/reflexio/reflexio/client/client.py +57 -13
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/README.md +135 -257
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js +13 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/npm/openclaw-smart/package.json +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/README.md +38 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/index.ts +151 -110
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/openclaw.plugin.json +20 -10
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/package.json +28 -6
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/pyproject.toml +42 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/_lib.sh +371 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh +33 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/backend-service.sh +271 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/cli.sh +68 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh +15 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh +84 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh +106 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/npm-cli.js +219 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/scripts/smart-install.sh +269 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md +10 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md +15 -44
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/skills/show/SKILL.md +8 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py +634 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py +224 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py +79 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py +160 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py +187 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py +69 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py +30 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py +36 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py +130 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py +131 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py +94 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py +75 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py +196 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py +272 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py +96 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py +66 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py +336 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py +61 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +323 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py +190 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py +112 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py +86 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py +144 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_cli.py +184 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py +142 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py +233 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py +116 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py +35 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py +47 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py +109 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py +117 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_ids.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_internal_call.py +50 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py +88 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_publish.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_query_compose.py +48 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py +188 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_runtime.py +41 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +235 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts +6 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts +56 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts +171 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tsconfig.build.json +18 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{tsconfig.json → plugin/tsconfig.json} +2 -2
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/uv.lock +3835 -0
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/vitest.config.ts +18 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +238 -10
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +11 -4
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +16 -5
- package/plugin/vendor/reflexio/reflexio/lib/_lineage_parity_readers.py +187 -0
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +198 -6
- package/plugin/vendor/reflexio/reflexio/lib/_reflection.py +1 -1
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +29 -20
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +127 -38
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +4 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +64 -41
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +19 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/pending_tool_call_schema.py +12 -5
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +52 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +5 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +88 -7
- package/plugin/vendor/reflexio/reflexio/models/structured_output.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/OVERVIEW.md +3 -3
- package/plugin/vendor/reflexio/reflexio/server/README.md +45 -32
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +29 -5
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +2 -2
- package/plugin/vendor/reflexio/reflexio/server/_auth.py +65 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +734 -85
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/precondition_checks.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +173 -0
- package/plugin/vendor/reflexio/reflexio/server/billing_signals.py +64 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +46 -16
- package/plugin/vendor/reflexio/reflexio/server/env_utils.py +65 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +264 -77
- package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +69 -7
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +75 -16
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/openclaw_provider.py +280 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/token_accounting.py +48 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +107 -17
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +360 -4
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.6.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/memory_reflection/v1.7.0.prompt.md +85 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_aggregation/v2.3.0.prompt.md +253 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +74 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.3.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +266 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.4.0.prompt.md +137 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.2.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_main/v1.3.0.prompt.md +34 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.0.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.1.0.prompt.md +5 -12
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/profile_update_instruction_start/v1.2.0.prompt.md +155 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/tagging/v1.0.0.prompt.md +23 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_manager.py +89 -32
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +26 -11
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +22 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_constants.py +4 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/agent_success_evaluation_utils.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluator.py → components/evaluator.py} +3 -16
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +28 -72
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{group_evaluation_runner.py → runner.py} +12 -10
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{delayed_group_evaluator.py → scheduler.py} +10 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/{agent_success_evaluation_service.py → service.py} +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +160 -0
- package/plugin/vendor/reflexio/reflexio/server/services/configurator/base_configurator.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{rule_attribution.py → components/rule_attribution.py} +10 -9
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/eval_sampler.py +15 -24
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +238 -164
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/README.md +31 -0
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +13 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +32 -118
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +39 -16
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +105 -23
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/__init__.py +0 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +193 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/resolve.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +26 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +11 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/__init__.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_aggregator.py → components/aggregator.py} +316 -178
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_consolidator.py → components/consolidator.py} +168 -62
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_extractor.py → components/extractor.py} +5 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_service_utils.py +33 -3
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/{playbook_generation_service.py → service.py} +121 -19
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +84 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/README.md +10 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/models.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +178 -30
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/README.md +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_deduplicator.py → components/consolidator.py} +7 -6
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_extractor.py → components/extractor.py} +30 -22
- package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service_utils.py +6 -13
- package/plugin/vendor/reflexio/reflexio/server/services/profile/{profile_generation_service.py → service.py} +32 -50
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/__init__.py +4 -8
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/components/__init__.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service_utils.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_service.py → service.py} +36 -16
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +83 -6
- package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/outcome.py +1 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +4 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +642 -197
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +61 -104
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +628 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_operations.py +38 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +978 -184
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +519 -128
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +77 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +23 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_stall_state.py +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +131 -20
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +2 -8
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_base.py +5 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +36 -53
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_lineage.py +215 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +243 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +121 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +60 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +51 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/README.md +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/service.py +200 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +149 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +229 -32
- package/plugin/vendor/reflexio/reflexio/server/site_var/README.md +2 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/feature_flags.py +120 -1
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/feature_flags.json +4 -0
- package/plugin/vendor/reflexio/reflexio/server/site_var/site_var_sources/search_settings.json +5 -0
- package/plugin/vendor/reflexio/reflexio/server/tracing.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +18 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -26
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +41 -4
- package/scripts/setup-claude-smart.sh +8 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/TESTING.md +0 -517
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/hook/handler.js +0 -473
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package-lock.json +0 -2156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/package.json +0 -18
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/handler.ts +0 -241
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/hook/setup.ts +0 -140
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/publish.ts +0 -113
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/search.ts +0 -52
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/server.ts +0 -103
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts +0 -156
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/lib/user-id.ts +0 -134
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/rules/reflexio.md +0 -24
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/publish_clawhub.sh +0 -278
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/references/HOOK.md +0 -164
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/install.sh +0 -36
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/scripts/uninstall.sh +0 -35
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/publish.test.ts +0 -27
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/search.test.ts +0 -31
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/server.test.ts +0 -42
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/setup.test.ts +0 -49
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts +0 -91
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/tests/user-id.test.ts +0 -50
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/vitest.config.ts +0 -13
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/group_aggregation.py +0 -192
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/invariants.py +0 -303
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/plan.py +0 -138
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/tools.py +0 -1125
- /package/plugin/vendor/reflexio/reflexio/integrations/{__init__.py → openclaw/plugin/src/openclaw_smart/__init__.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/integrations/openclaw/{types → plugin/types}/openclaw.d.ts +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{distribution.py → components/distribution.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{hero_state.py → components/hero_state.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/{shadow_aggregation.py → components/shadow_aggregation.py} +0 -0
- /package/plugin/vendor/reflexio/reflexio/server/services/reflection/{reflection_extractor.py → components/extractor.py} +0 -0
package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Per-session JSONL buffer for interactions awaiting publish to reflexio.
|
|
2
|
+
|
|
3
|
+
Each openClaw session gets one file at
|
|
4
|
+
``~/.openclaw-smart/sessions/{session_id}.jsonl``. Lines are one of:
|
|
5
|
+
|
|
6
|
+
- ``{"role": "User", ...}`` — a user turn (see InteractionData fields)
|
|
7
|
+
- ``{"role": "Assistant", ...}`` — a finalized assistant turn
|
|
8
|
+
- ``{"role": "Assistant_tool", ...}`` — a single tool invocation, attached
|
|
9
|
+
to the next assistant turn at ``agent_end`` time
|
|
10
|
+
- ``{"published_up_to": N}`` — high-water mark so ``agent_end`` /
|
|
11
|
+
``session_end`` don't re-publish rows already sent to reflexio
|
|
12
|
+
|
|
13
|
+
The buffer exists for offline resilience: when reflexio is unreachable,
|
|
14
|
+
``agent_end`` appends without publishing and the next successful hook drains.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
from collections.abc import Iterable
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import fcntl # POSIX only — Windows hooks fall back to append-without-lock.
|
|
29
|
+
except ImportError: # pragma: no cover — non-POSIX platforms
|
|
30
|
+
fcntl = None # type: ignore[assignment]
|
|
31
|
+
|
|
32
|
+
_LOGGER = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
_ENV_STATE_DIR = "OPENCLAW_SMART_STATE_DIR"
|
|
35
|
+
_DEFAULT_STATE_DIR = Path.home() / ".openclaw-smart" / "sessions"
|
|
36
|
+
|
|
37
|
+
_TOOL_DATA_FIELD_MAX_LEN = 256
|
|
38
|
+
|
|
39
|
+
_VALID_CITATION_KINDS = frozenset({"playbook", "profile"})
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _truncate_tool_data_field(value: Any) -> Any:
|
|
43
|
+
"""Truncate a single tool_data field value to ``_TOOL_DATA_FIELD_MAX_LEN``.
|
|
44
|
+
|
|
45
|
+
Only *top-level string* values are shortened. Nested containers
|
|
46
|
+
(dicts, lists) and non-string scalars pass through unchanged, even if
|
|
47
|
+
the container holds overlong strings — extractor prompts built from
|
|
48
|
+
this payload are bounded upstream by reflexio, and truncating a mid-
|
|
49
|
+
structure string risks producing invalid JSON when the caller later
|
|
50
|
+
serializes. The cap keeps long fields (``Edit.old_string`` /
|
|
51
|
+
``new_string`` diffs, multi-line ``Bash`` scripts) from inflating the
|
|
52
|
+
extractor's input; short fields like file paths, URLs, and typical
|
|
53
|
+
commands stay intact.
|
|
54
|
+
"""
|
|
55
|
+
if isinstance(value, str) and len(value) > _TOOL_DATA_FIELD_MAX_LEN:
|
|
56
|
+
return value[:_TOOL_DATA_FIELD_MAX_LEN]
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def state_dir() -> Path:
|
|
61
|
+
"""Root directory for session JSONL files. Honours ``OPENCLAW_SMART_STATE_DIR``."""
|
|
62
|
+
override = os.environ.get(_ENV_STATE_DIR)
|
|
63
|
+
return Path(override) if override else _DEFAULT_STATE_DIR
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# openClaw session ids are opaque tokens. The real shape we see in
|
|
67
|
+
# production is ``agent:<id>:<run>`` (e.g. ``agent:main:main``) — colons
|
|
68
|
+
# are part of the protocol, not a path separator on POSIX, so they're
|
|
69
|
+
# safe in filesystem paths. Restrict to a conservative
|
|
70
|
+
# alphanumeric + dot/underscore/hyphen/colon set so a crafted value
|
|
71
|
+
# with `/` or `\` path separators or shell metacharacters can't write or
|
|
72
|
+
# read outside ``state_dir()``. Any session id outside this charset is
|
|
73
|
+
# treated as malformed and the resulting filesystem op becomes a no-op
|
|
74
|
+
# upstream (see ``append`` / ``read_all``).
|
|
75
|
+
#
|
|
76
|
+
# We additionally reject ids that are pure-dot tokens (``.``, ``..``) or
|
|
77
|
+
# contain a ``..`` substring even though the underlying regex would
|
|
78
|
+
# accept them — those are not valid filenames and signal an escape
|
|
79
|
+
# attempt.
|
|
80
|
+
_SESSION_ID_RE = re.compile(r"^[A-Za-z0-9._:-]{1,128}$")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _safe_session_id(session_id: str) -> str | None:
|
|
84
|
+
"""Return ``session_id`` iff it matches the safe-id charset, else ``None``.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
session_id (str): Caller-supplied session key.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
str | None: The session id unchanged when safe, ``None`` otherwise.
|
|
91
|
+
"""
|
|
92
|
+
if not isinstance(session_id, str):
|
|
93
|
+
return None
|
|
94
|
+
if not _SESSION_ID_RE.fullmatch(session_id):
|
|
95
|
+
return None
|
|
96
|
+
if ".." in session_id:
|
|
97
|
+
return None
|
|
98
|
+
if set(session_id) == {"."}:
|
|
99
|
+
return None
|
|
100
|
+
return session_id
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def session_path(session_id: str) -> Path | None:
|
|
104
|
+
"""Return the JSONL path for a given session id, or ``None`` if unsafe."""
|
|
105
|
+
sid = _safe_session_id(session_id)
|
|
106
|
+
if sid is None:
|
|
107
|
+
return None
|
|
108
|
+
return state_dir() / f"{sid}.jsonl"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def injected_path(session_id: str) -> Path | None:
|
|
112
|
+
"""Return the per-session oc-cite registry path, or ``None`` if unsafe."""
|
|
113
|
+
sid = _safe_session_id(session_id)
|
|
114
|
+
if sid is None:
|
|
115
|
+
return None
|
|
116
|
+
return state_dir() / f"{sid}.injected.jsonl"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def publish_lock_path(session_id: str) -> Path | None:
|
|
120
|
+
"""Return the per-session publish lock path, or ``None`` if unsafe."""
|
|
121
|
+
sid = _safe_session_id(session_id)
|
|
122
|
+
if sid is None:
|
|
123
|
+
return None
|
|
124
|
+
return state_dir() / f"{sid}.publish.lock"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def append_injected(session_id: str, entries: Iterable[dict[str, Any]]) -> None:
|
|
128
|
+
"""Append citation-registry entries to the per-session injected-items file.
|
|
129
|
+
|
|
130
|
+
Each entry maps a short ``id`` (4-hex-char) back to the skill or
|
|
131
|
+
preference it came from so the ``agent_end`` hook can resolve ids cited
|
|
132
|
+
by openClaw via ``oc-cite`` into human-readable titles for the
|
|
133
|
+
dashboard. Silently no-ops when ``entries`` is empty.
|
|
134
|
+
"""
|
|
135
|
+
records = list(entries)
|
|
136
|
+
if not records:
|
|
137
|
+
return
|
|
138
|
+
path = injected_path(session_id)
|
|
139
|
+
if path is None:
|
|
140
|
+
_LOGGER.warning(
|
|
141
|
+
"rejected unsafe session_id for append_injected: %r", session_id
|
|
142
|
+
)
|
|
143
|
+
return
|
|
144
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
with path.open("a", encoding="utf-8") as fh:
|
|
146
|
+
if fcntl is not None:
|
|
147
|
+
try:
|
|
148
|
+
fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
|
|
149
|
+
except OSError as exc:
|
|
150
|
+
_LOGGER.debug("flock failed on %s: %s", path, exc)
|
|
151
|
+
for rec in records:
|
|
152
|
+
fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def read_injected(session_id: str) -> dict[str, dict[str, Any]]:
|
|
156
|
+
"""Return the per-session citation registry keyed by id.
|
|
157
|
+
|
|
158
|
+
Later entries win when the same id was injected multiple times
|
|
159
|
+
(identical content produces the same hash-derived id, so the extra
|
|
160
|
+
record only refreshes metadata).
|
|
161
|
+
"""
|
|
162
|
+
path = injected_path(session_id)
|
|
163
|
+
if path is None or not path.exists():
|
|
164
|
+
return {}
|
|
165
|
+
registry: dict[str, dict[str, Any]] = {}
|
|
166
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
167
|
+
for line in fh:
|
|
168
|
+
line = line.strip()
|
|
169
|
+
if not line:
|
|
170
|
+
continue
|
|
171
|
+
try:
|
|
172
|
+
entry = json.loads(line)
|
|
173
|
+
except json.JSONDecodeError as exc:
|
|
174
|
+
_LOGGER.warning("Skipping malformed injected line in %s: %s", path, exc)
|
|
175
|
+
continue
|
|
176
|
+
item_id = entry.get("id")
|
|
177
|
+
if isinstance(item_id, str) and item_id:
|
|
178
|
+
registry[item_id] = entry
|
|
179
|
+
return registry
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def append(session_id: str, record: dict[str, Any]) -> None:
|
|
183
|
+
"""Append one JSON record to the session buffer. Creates the dir if needed.
|
|
184
|
+
|
|
185
|
+
Holds an exclusive ``flock`` on the buffer file across the write so
|
|
186
|
+
concurrent hooks (e.g. parallel ``after_tool_call`` fires) cannot
|
|
187
|
+
interleave JSON lines when a payload exceeds the buffered-writer's
|
|
188
|
+
flush size.
|
|
189
|
+
"""
|
|
190
|
+
path = session_path(session_id)
|
|
191
|
+
if path is None:
|
|
192
|
+
_LOGGER.warning("rejected unsafe session_id for append: %r", session_id)
|
|
193
|
+
return
|
|
194
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
195
|
+
line = json.dumps(record, ensure_ascii=False) + "\n"
|
|
196
|
+
with path.open("a", encoding="utf-8") as fh:
|
|
197
|
+
if fcntl is not None:
|
|
198
|
+
try:
|
|
199
|
+
fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
|
|
200
|
+
except OSError as exc:
|
|
201
|
+
_LOGGER.debug("flock failed on %s: %s", path, exc)
|
|
202
|
+
fh.write(line)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def read_all(session_id: str) -> list[dict[str, Any]]:
|
|
206
|
+
"""Return every record in the buffer as a list of dicts. Missing file → []."""
|
|
207
|
+
path = session_path(session_id)
|
|
208
|
+
if path is None or not path.exists():
|
|
209
|
+
return []
|
|
210
|
+
records: list[dict[str, Any]] = []
|
|
211
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
212
|
+
for line in fh:
|
|
213
|
+
line = line.strip()
|
|
214
|
+
if not line:
|
|
215
|
+
continue
|
|
216
|
+
try:
|
|
217
|
+
records.append(json.loads(line))
|
|
218
|
+
except json.JSONDecodeError as exc:
|
|
219
|
+
_LOGGER.warning("Skipping malformed buffer line in %s: %s", path, exc)
|
|
220
|
+
return records
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _to_wire_citations(cited_items: Any) -> list[dict[str, str]]:
|
|
224
|
+
"""Map local ``cited_items`` to the wire ``Citation`` shape.
|
|
225
|
+
|
|
226
|
+
Local entries (from ``events.agent_end._resolve_cited_items``) carry
|
|
227
|
+
``{id, kind, title, real_id}``; reflexio's ``InteractionData.citations``
|
|
228
|
+
wants ``{kind, real_id, tag, title}`` where ``tag`` is the rank id
|
|
229
|
+
(``s1-301``-style) we already keep under ``id``. Entries without a
|
|
230
|
+
``real_id`` (unresolved injections) are dropped — the server can't
|
|
231
|
+
join them back to a stored row.
|
|
232
|
+
"""
|
|
233
|
+
if not isinstance(cited_items, list):
|
|
234
|
+
return []
|
|
235
|
+
out: list[dict[str, str]] = []
|
|
236
|
+
for item in cited_items:
|
|
237
|
+
if not isinstance(item, dict):
|
|
238
|
+
continue
|
|
239
|
+
real_id = item.get("real_id")
|
|
240
|
+
kind = item.get("kind")
|
|
241
|
+
if not isinstance(real_id, str) or not real_id:
|
|
242
|
+
continue
|
|
243
|
+
if kind not in _VALID_CITATION_KINDS:
|
|
244
|
+
continue
|
|
245
|
+
tag = item.get("id")
|
|
246
|
+
title = item.get("title")
|
|
247
|
+
out.append(
|
|
248
|
+
{
|
|
249
|
+
"kind": kind,
|
|
250
|
+
"real_id": real_id,
|
|
251
|
+
"tag": tag if isinstance(tag, str) else "",
|
|
252
|
+
"title": title if isinstance(title, str) else "",
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
return out
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def unpublished_slice(
|
|
259
|
+
records: Iterable[dict[str, Any]],
|
|
260
|
+
) -> tuple[int, list[dict[str, Any]]]:
|
|
261
|
+
"""Split records into (last-published index, unpublished turn records).
|
|
262
|
+
|
|
263
|
+
Walks the records in order, tracking the most recent ``published_up_to``
|
|
264
|
+
marker and collecting turn records (anything with a ``role``) that come
|
|
265
|
+
after it. Tool records are folded into the closest following Assistant
|
|
266
|
+
turn's ``tools_used``.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
tuple[int, list[dict]]: ``(published_up_to, interactions)``. The
|
|
270
|
+
integer is the watermark after which all turns are unpublished;
|
|
271
|
+
the list is formatted for ``InteractionData`` construction.
|
|
272
|
+
"""
|
|
273
|
+
published = 0
|
|
274
|
+
pending_tools: list[dict[str, Any]] = []
|
|
275
|
+
turns: list[dict[str, Any]] = []
|
|
276
|
+
for idx, rec in enumerate(records):
|
|
277
|
+
if "published_up_to" in rec:
|
|
278
|
+
marker = rec.get("published_up_to")
|
|
279
|
+
# Clamp to the number of records seen before this marker (``idx``).
|
|
280
|
+
# An over-range marker (e.g. from a tampered/corrupt buffer) would
|
|
281
|
+
# otherwise skip every subsequent turn via the ``idx < published``
|
|
282
|
+
# gate below, silently dropping valid unpublished records.
|
|
283
|
+
if isinstance(marker, int) and 0 <= marker <= idx:
|
|
284
|
+
published = marker
|
|
285
|
+
pending_tools = []
|
|
286
|
+
turns = []
|
|
287
|
+
continue
|
|
288
|
+
if idx < published:
|
|
289
|
+
continue
|
|
290
|
+
role = rec.get("role")
|
|
291
|
+
if role == "Assistant_tool":
|
|
292
|
+
raw_tool_input = rec.get("tool_input")
|
|
293
|
+
tool_input = raw_tool_input if isinstance(raw_tool_input, dict) else {}
|
|
294
|
+
tool_output = rec.get("tool_output") or ""
|
|
295
|
+
tool_entry: dict[str, Any] = {
|
|
296
|
+
"tool_name": rec.get("tool_name", ""),
|
|
297
|
+
"status": rec.get("status", "success"),
|
|
298
|
+
}
|
|
299
|
+
tool_data: dict[str, Any] = {}
|
|
300
|
+
if tool_input:
|
|
301
|
+
tool_data["input"] = {
|
|
302
|
+
k: _truncate_tool_data_field(v) for k, v in tool_input.items()
|
|
303
|
+
}
|
|
304
|
+
if tool_output:
|
|
305
|
+
tool_data["output"] = _truncate_tool_data_field(tool_output)
|
|
306
|
+
if tool_data:
|
|
307
|
+
tool_entry["tool_data"] = tool_data
|
|
308
|
+
pending_tools.append(tool_entry)
|
|
309
|
+
continue
|
|
310
|
+
if role in {"User", "Assistant"}:
|
|
311
|
+
turn = {
|
|
312
|
+
k: v for k, v in rec.items() if k not in {"role", "ts", "cited_items"}
|
|
313
|
+
}
|
|
314
|
+
turn["role"] = role
|
|
315
|
+
if role == "Assistant":
|
|
316
|
+
citations = _to_wire_citations(rec.get("cited_items"))
|
|
317
|
+
if citations:
|
|
318
|
+
turn["citations"] = citations
|
|
319
|
+
if pending_tools:
|
|
320
|
+
turn["tools_used"] = pending_tools
|
|
321
|
+
pending_tools = []
|
|
322
|
+
turns.append(turn)
|
|
323
|
+
return published, turns
|
|
File without changes
|
package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/integration/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""End-to-end: drive all 6 hooks with synthetic payloads, assert buffer state.
|
|
2
|
+
|
|
3
|
+
Exercises the hook dispatcher → event-handler → state buffer pipeline in
|
|
4
|
+
order, the same sequence openClaw would trigger during a real session. The
|
|
5
|
+
reflexio adapter is patched to a no-op so the test never depends on a live
|
|
6
|
+
backend; failures inside ``publish`` would otherwise be tolerated silently
|
|
7
|
+
(per ``reflexio_adapter.Adapter.publish``) but explicit patching keeps the
|
|
8
|
+
test deterministic across CI runs.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import io
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from unittest.mock import MagicMock
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
pytestmark = pytest.mark.e2e
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def isolated_state(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
|
|
25
|
+
"""Force state.py to use a tmp directory so the test never touches ~/.openclaw-smart/."""
|
|
26
|
+
state_dir = tmp_path / "sessions"
|
|
27
|
+
monkeypatch.setenv("OPENCLAW_SMART_STATE_DIR", str(state_dir))
|
|
28
|
+
return state_dir
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def stub_adapter(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
|
|
33
|
+
"""Replace every imported ``Adapter`` symbol with a MagicMock that returns success.
|
|
34
|
+
|
|
35
|
+
Each consumer module does ``from openclaw_smart.reflexio_adapter import Adapter``
|
|
36
|
+
at import time, so patching the source module alone leaves stale bindings in
|
|
37
|
+
consumers. We patch the consumer-side aliases as well so the test is
|
|
38
|
+
isolated regardless of import order.
|
|
39
|
+
"""
|
|
40
|
+
from openclaw_smart import ( # noqa: F401 — force-import the consumers
|
|
41
|
+
context_inject,
|
|
42
|
+
publish,
|
|
43
|
+
reflexio_adapter,
|
|
44
|
+
)
|
|
45
|
+
from openclaw_smart.events import session_start
|
|
46
|
+
|
|
47
|
+
fake = MagicMock()
|
|
48
|
+
fake.publish.return_value = True
|
|
49
|
+
fake.apply_extraction_defaults.return_value = True
|
|
50
|
+
fake.apply_optimizer_defaults.return_value = True
|
|
51
|
+
fake.fetch_stall_state.return_value = None
|
|
52
|
+
fake.search_all.return_value = ([], [], [])
|
|
53
|
+
|
|
54
|
+
factory = lambda *a, **kw: fake # noqa: E731 — single-line lambda is clearer here
|
|
55
|
+
monkeypatch.setattr(reflexio_adapter, "Adapter", factory)
|
|
56
|
+
monkeypatch.setattr(publish, "Adapter", factory)
|
|
57
|
+
monkeypatch.setattr(context_inject, "Adapter", factory)
|
|
58
|
+
monkeypatch.setattr(session_start, "Adapter", factory)
|
|
59
|
+
return fake
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _drive(monkeypatch: pytest.MonkeyPatch, event: str, payload: dict) -> int:
|
|
63
|
+
from openclaw_smart import hook
|
|
64
|
+
|
|
65
|
+
monkeypatch.setattr("sys.stdin", io.StringIO(json.dumps(payload)))
|
|
66
|
+
return hook.main(["openclaw", event])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_full_session_loop_records_all_roles(
|
|
70
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
71
|
+
isolated_state: Path,
|
|
72
|
+
stub_adapter: MagicMock,
|
|
73
|
+
capsys: pytest.CaptureFixture[str],
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Walk the 6 hooks; assert User, Assistant_tool, Assistant records land."""
|
|
76
|
+
from openclaw_smart import state
|
|
77
|
+
|
|
78
|
+
session = "e2e-1"
|
|
79
|
+
project = "test-project"
|
|
80
|
+
|
|
81
|
+
# session-start: pushes defaults + emits a stall banner (or empty stdout).
|
|
82
|
+
assert (
|
|
83
|
+
_drive(
|
|
84
|
+
monkeypatch, "session-start", {"sessionKey": session, "agentId": project}
|
|
85
|
+
)
|
|
86
|
+
== 0
|
|
87
|
+
)
|
|
88
|
+
capsys.readouterr() # drain
|
|
89
|
+
|
|
90
|
+
# before-prompt-build: appends a "User" record.
|
|
91
|
+
assert (
|
|
92
|
+
_drive(
|
|
93
|
+
monkeypatch,
|
|
94
|
+
"before-prompt-build",
|
|
95
|
+
{
|
|
96
|
+
"sessionKey": session,
|
|
97
|
+
"prompt": "implement OAuth flow",
|
|
98
|
+
"agentId": project,
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
== 0
|
|
102
|
+
)
|
|
103
|
+
capsys.readouterr()
|
|
104
|
+
|
|
105
|
+
# before-tool-call: observe-only stub; no record, no stdout.
|
|
106
|
+
assert (
|
|
107
|
+
_drive(
|
|
108
|
+
monkeypatch,
|
|
109
|
+
"before-tool-call",
|
|
110
|
+
{
|
|
111
|
+
"sessionKey": session,
|
|
112
|
+
"toolName": "Edit",
|
|
113
|
+
"params": {"file_path": "x.py"},
|
|
114
|
+
"agentId": project,
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
== 0
|
|
118
|
+
)
|
|
119
|
+
assert capsys.readouterr().out.strip() == ""
|
|
120
|
+
|
|
121
|
+
# after-tool-call: appends an "Assistant_tool" record (camelCase translated).
|
|
122
|
+
assert (
|
|
123
|
+
_drive(
|
|
124
|
+
monkeypatch,
|
|
125
|
+
"after-tool-call",
|
|
126
|
+
{
|
|
127
|
+
"sessionKey": session,
|
|
128
|
+
"toolName": "Edit",
|
|
129
|
+
"params": {"file_path": "x.py", "new_string": "..."},
|
|
130
|
+
"result": "wrote 10 lines",
|
|
131
|
+
"agentId": project,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
== 0
|
|
135
|
+
)
|
|
136
|
+
capsys.readouterr()
|
|
137
|
+
|
|
138
|
+
# agent-end: extracts the assistant text and appends an "Assistant" record.
|
|
139
|
+
assert (
|
|
140
|
+
_drive(
|
|
141
|
+
monkeypatch,
|
|
142
|
+
"agent-end",
|
|
143
|
+
{
|
|
144
|
+
"sessionKey": session,
|
|
145
|
+
"agentId": project,
|
|
146
|
+
"messages": [
|
|
147
|
+
{"role": "user", "content": "implement OAuth flow"},
|
|
148
|
+
{"role": "assistant", "content": "Done."},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
== 0
|
|
153
|
+
)
|
|
154
|
+
capsys.readouterr()
|
|
155
|
+
|
|
156
|
+
# session-end: force-publish; should not crash.
|
|
157
|
+
assert (
|
|
158
|
+
_drive(monkeypatch, "session-end", {"sessionKey": session, "agentId": project})
|
|
159
|
+
== 0
|
|
160
|
+
)
|
|
161
|
+
capsys.readouterr()
|
|
162
|
+
|
|
163
|
+
records = state.read_all(session)
|
|
164
|
+
roles = [r.get("role") for r in records if "role" in r]
|
|
165
|
+
assert "User" in roles
|
|
166
|
+
assert "Assistant_tool" in roles
|
|
167
|
+
assert "Assistant" in roles
|
|
168
|
+
|
|
169
|
+
# session_end publishes; we asserted publish was called via the stub.
|
|
170
|
+
assert stub_adapter.publish.called
|
|
171
|
+
|
|
172
|
+
# The agent_end / session_end path writes a published_up_to watermark
|
|
173
|
+
# once a publish succeeds. The stub returns True, so we should see it.
|
|
174
|
+
assert any("published_up_to" in r for r in records), (
|
|
175
|
+
"expected published_up_to watermark after session_end"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_unknown_event_is_silent(
|
|
180
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
181
|
+
isolated_state: Path,
|
|
182
|
+
capsys: pytest.CaptureFixture[str],
|
|
183
|
+
) -> None:
|
|
184
|
+
"""An unrecognized hook name short-circuits with empty stdout, never crashes."""
|
|
185
|
+
monkeypatch.setattr("sys.stdin", io.StringIO("{}"))
|
|
186
|
+
from openclaw_smart import hook
|
|
187
|
+
|
|
188
|
+
rc = hook.main(["openclaw", "this-does-not-exist"])
|
|
189
|
+
assert rc == 0
|
|
190
|
+
assert capsys.readouterr().out.strip() == ""
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Integration: publish buffered turns to a local SQLite-backed reflexio.
|
|
2
|
+
|
|
3
|
+
Requires a reflexio backend reachable at ``REFLEXIO_URL`` (default
|
|
4
|
+
``http://localhost:8061/``). Skipped automatically when the backend is
|
|
5
|
+
unreachable so the suite is portable across machines.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
pytestmark = pytest.mark.integration
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _reflexio_url() -> str:
|
|
19
|
+
return os.environ.get("REFLEXIO_URL", "http://localhost:8061/")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _backend_alive(url: str) -> bool:
|
|
23
|
+
"""True iff ``GET <url>health`` returns 200 within a short window."""
|
|
24
|
+
import urllib.error
|
|
25
|
+
import urllib.request
|
|
26
|
+
|
|
27
|
+
health = url.rstrip("/") + "/health"
|
|
28
|
+
try:
|
|
29
|
+
with urllib.request.urlopen(health, timeout=1.5) as resp: # nosec — local only
|
|
30
|
+
return resp.status == 200
|
|
31
|
+
except (urllib.error.URLError, TimeoutError, OSError):
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def require_backend() -> None:
|
|
37
|
+
if not _backend_alive(_reflexio_url()):
|
|
38
|
+
pytest.skip(f"reflexio backend not reachable at {_reflexio_url()}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def isolated_state(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
|
|
43
|
+
"""Force state.py to write under tmp_path so the test doesn't leak fixtures."""
|
|
44
|
+
state_dir = tmp_path / "sessions"
|
|
45
|
+
monkeypatch.setenv("OPENCLAW_SMART_STATE_DIR", str(state_dir))
|
|
46
|
+
return state_dir
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_publish_lands_in_reflexio_storage(
|
|
50
|
+
require_backend: None,
|
|
51
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
52
|
+
isolated_state: Path,
|
|
53
|
+
worker_id: str = "test",
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Append two turns to the JSONL buffer → publish → assert success status + count."""
|
|
56
|
+
from openclaw_smart import state
|
|
57
|
+
from openclaw_smart.publish import publish_unpublished
|
|
58
|
+
|
|
59
|
+
session_id = f"oc-int-{worker_id}"
|
|
60
|
+
project_id = f"oc-test-proj-{worker_id}"
|
|
61
|
+
|
|
62
|
+
state.append(session_id, {"role": "User", "content": "implement auth"})
|
|
63
|
+
state.append(
|
|
64
|
+
session_id,
|
|
65
|
+
{"role": "Assistant", "content": "Wrote login/logout endpoints."},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
status, count = publish_unpublished(
|
|
69
|
+
session_id=session_id,
|
|
70
|
+
project_id=project_id,
|
|
71
|
+
force_extraction=False,
|
|
72
|
+
skip_aggregation=False,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
assert status == "ok", f"publish returned {status!r} — check backend logs"
|
|
76
|
+
assert count == 2
|
|
77
|
+
|
|
78
|
+
# The watermark is now stamped — a second publish with no new turns is a no-op.
|
|
79
|
+
status2, count2 = publish_unpublished(
|
|
80
|
+
session_id=session_id,
|
|
81
|
+
project_id=project_id,
|
|
82
|
+
force_extraction=False,
|
|
83
|
+
skip_aggregation=False,
|
|
84
|
+
)
|
|
85
|
+
assert (status2, count2) == ("nothing", 0)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_publish_failed_when_backend_url_invalid(
|
|
89
|
+
monkeypatch: pytest.MonkeyPatch, isolated_state: Path
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Pointing the adapter at a dead port surfaces 'failed' without stamping."""
|
|
92
|
+
from openclaw_smart import state
|
|
93
|
+
from openclaw_smart.publish import publish_unpublished
|
|
94
|
+
from openclaw_smart.reflexio_adapter import Adapter
|
|
95
|
+
|
|
96
|
+
# Pick a port that's almost certainly closed.
|
|
97
|
+
bogus = Adapter(url="http://127.0.0.1:9/")
|
|
98
|
+
session_id = "oc-int-bogus"
|
|
99
|
+
state.append(session_id, {"role": "User", "content": "hi"})
|
|
100
|
+
|
|
101
|
+
status, count = publish_unpublished(
|
|
102
|
+
session_id=session_id,
|
|
103
|
+
project_id="proj-bogus",
|
|
104
|
+
force_extraction=False,
|
|
105
|
+
skip_aggregation=False,
|
|
106
|
+
adapter=bogus,
|
|
107
|
+
)
|
|
108
|
+
assert status == "failed"
|
|
109
|
+
assert count == 1
|
|
110
|
+
# No watermark stamped — a retry would re-publish the same record.
|
|
111
|
+
records = state.read_all(session_id)
|
|
112
|
+
assert not any("published_up_to" in r for r in records)
|