foxclaw 0.1.0 → 0.1.3
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/README.md +54 -29
- package/bin/foxclaw.cjs +51 -0
- package/index.js +4 -10
- package/package.json +46 -38
- package/python/.dockerignore +35 -0
- package/python/.env.example +173 -0
- package/python/.gitattributes +46 -0
- package/python/.github/workflows/ci.yml +167 -0
- package/python/.github/workflows/release.yml +183 -0
- package/python/CHANGELOG.md +57 -0
- package/python/CHANGELOG_CN.md +57 -0
- package/python/Dockerfile +64 -0
- package/python/LICENSE +21 -0
- package/python/MANIFEST.in +14 -0
- package/python/README.md +201 -0
- package/python/README_CN.md +336 -0
- package/python/bench/bm25_audit.py +186 -0
- package/python/bench/bm25_regression.py +177 -0
- package/python/bench/bm25_stress.py +155 -0
- package/python/bench/cd_audit.py +215 -0
- package/python/bench/chaos.py +140 -0
- package/python/bench/concurrency.py +103 -0
- package/python/bench/e2e_real_models.py +94 -0
- package/python/bench/llm_retry_test.py +241 -0
- package/python/bench/load_test.py +193 -0
- package/python/bench/vector_ivf_upgrade.py +128 -0
- package/python/docs/CHANNELS.md +195 -0
- package/python/docs/GOVERNANCE.md +164 -0
- package/python/docs/RELEASE.md +110 -0
- package/python/docs/RUN.md +75 -0
- package/python/docs/RUN_CN.md +121 -0
- package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
- package/python/evals/__init__.py +0 -0
- package/python/evals/governance/README.md +61 -0
- package/python/evals/governance/__init__.py +0 -0
- package/python/evals/governance/cases.py +115 -0
- package/python/evals/governance/compare_embeddings.py +94 -0
- package/python/evals/governance/convergence.svg +11 -0
- package/python/evals/governance/report.json +371 -0
- package/python/evals/governance/run_evals.py +368 -0
- package/python/examples/governance_middleware_demo.py +72 -0
- package/python/examples/langgraph_governance_demo.py +104 -0
- package/python/examples/sample_itinerary.csv +6 -0
- package/python/examples/sample_knowledge.csv +9 -0
- package/python/examples/sample_transactions.csv +14 -0
- package/python/foxclaw/__init__.py +408 -0
- package/python/foxclaw/agents/__init__.py +12 -0
- package/python/foxclaw/agents/agents.py +35 -0
- package/python/foxclaw/agents/base.py +794 -0
- package/python/foxclaw/agents/impl/__init__.py +1 -0
- package/python/foxclaw/agents/impl/_helpers.py +70 -0
- package/python/foxclaw/agents/impl/finance.py +231 -0
- package/python/foxclaw/agents/impl/office.py +350 -0
- package/python/foxclaw/agents/impl/supervisor.py +592 -0
- package/python/foxclaw/agents/mixins/_context.py +128 -0
- package/python/foxclaw/agents/mixins/augment.py +143 -0
- package/python/foxclaw/agents/mixins/llm.py +67 -0
- package/python/foxclaw/agents/mixins/memory.py +41 -0
- package/python/foxclaw/agents/mixins/preference.py +131 -0
- package/python/foxclaw/agents/mixins/prompt.py +71 -0
- package/python/foxclaw/agents/mixins/skillbox.py +58 -0
- package/python/foxclaw/agents/mixins/tool_exec.py +417 -0
- package/python/foxclaw/api/__init__.py +224 -0
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/bootstrap.py +96 -0
- package/python/foxclaw/api/middleware.py +294 -0
- package/python/foxclaw/api/routes/ahs.py +404 -0
- package/python/foxclaw/api/routes/attachments.py +116 -0
- package/python/foxclaw/api/routes/browser.py +35 -0
- package/python/foxclaw/api/routes/domain.py +306 -0
- package/python/foxclaw/api/routes/knowledge.py +145 -0
- package/python/foxclaw/api/routes/llm_admin.py +449 -0
- package/python/foxclaw/api/routes/memory.py +105 -0
- package/python/foxclaw/api/routes/orchestration.py +39 -0
- package/python/foxclaw/api/routes/recovery.py +44 -0
- package/python/foxclaw/api/routes/review.py +266 -0
- package/python/foxclaw/api/routes/search.py +33 -0
- package/python/foxclaw/api/routes/system.py +311 -0
- package/python/foxclaw/api/routes/task.py +263 -0
- package/python/foxclaw/api/routes/ws.py +64 -0
- package/python/foxclaw/api/spine.py +52 -0
- package/python/foxclaw/browser/__init__.py +13 -0
- package/python/foxclaw/browser/action/__init__.py +1 -0
- package/python/foxclaw/browser/action/action_executor.py +50 -0
- package/python/foxclaw/browser/action/action_types.py +44 -0
- package/python/foxclaw/browser/action/action_verifier.py +68 -0
- package/python/foxclaw/browser/action/url_policy.py +143 -0
- package/python/foxclaw/browser/adapter/__init__.py +1 -0
- package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
- package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
- package/python/foxclaw/browser/driver/__init__.py +1 -0
- package/python/foxclaw/browser/driver/base_driver.py +61 -0
- package/python/foxclaw/browser/driver/playwright_driver.py +170 -0
- package/python/foxclaw/browser/perception/__init__.py +1 -0
- package/python/foxclaw/browser/perception/dom_extractor.py +42 -0
- package/python/foxclaw/browser/perception/element_indexer.py +56 -0
- package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
- package/python/foxclaw/browser/perception/snapshot.py +29 -0
- package/python/foxclaw/browser/service.py +144 -0
- package/python/foxclaw/browser/session/__init__.py +1 -0
- package/python/foxclaw/browser/session/session_manager.py +57 -0
- package/python/foxclaw/capability/__init__.py +12 -0
- package/python/foxclaw/capability/embedding_service.py +65 -0
- package/python/foxclaw/channels/__init__.py +138 -0
- package/python/foxclaw/channels/discord.py +175 -0
- package/python/foxclaw/channels/dispatcher.py +179 -0
- package/python/foxclaw/channels/feishu.py +215 -0
- package/python/foxclaw/channels/openclaw_bridge.py +151 -0
- package/python/foxclaw/channels/signing.py +187 -0
- package/python/foxclaw/channels/telegram.py +120 -0
- package/python/foxclaw/channels/weixin.py +215 -0
- package/python/foxclaw/cli.py +183 -0
- package/python/foxclaw/cognitive/__init__.py +4 -0
- package/python/foxclaw/cognitive/reasoner.py +81 -0
- package/python/foxclaw/cognitive/state.py +141 -0
- package/python/foxclaw/core/__init__.py +9 -0
- package/python/foxclaw/core/audit_chain.py +131 -0
- package/python/foxclaw/core/audit_sink.py +100 -0
- package/python/foxclaw/core/compaction.py +265 -0
- package/python/foxclaw/core/config.py +65 -0
- package/python/foxclaw/core/confirm_nonce.py +151 -0
- package/python/foxclaw/core/dispatch.py +27 -0
- package/python/foxclaw/core/envfile.py +48 -0
- package/python/foxclaw/core/failure_policy.py +142 -0
- package/python/foxclaw/core/gateway.py +81 -0
- package/python/foxclaw/core/governance_audit.py +171 -0
- package/python/foxclaw/core/idempotency.py +282 -0
- package/python/foxclaw/core/injection_guard.py +202 -0
- package/python/foxclaw/core/memory_adapter.py +489 -0
- package/python/foxclaw/core/metrics.py +88 -0
- package/python/foxclaw/core/model_router.py +985 -0
- package/python/foxclaw/core/paths.py +103 -0
- package/python/foxclaw/core/recovery.py +349 -0
- package/python/foxclaw/core/scheduler.py +141 -0
- package/python/foxclaw/core/secrets.py +131 -0
- package/python/foxclaw/core/sessions.py +155 -0
- package/python/foxclaw/core/settings.py +414 -0
- package/python/foxclaw/core/shared_write.py +186 -0
- package/python/foxclaw/core/steering_queue.py +154 -0
- package/python/foxclaw/core/system_bus.py +84 -0
- package/python/foxclaw/core/thought_chain.py +316 -0
- package/python/foxclaw/core/token_meter.py +231 -0
- package/python/foxclaw/core/tools.py +164 -0
- package/python/foxclaw/core/tracing.py +375 -0
- package/python/foxclaw/core/truth_pipeline.py +110 -0
- package/python/foxclaw/core/update_check.py +149 -0
- package/python/foxclaw/core/vec_recall_adapter.py +477 -0
- package/python/foxclaw/core/verify.py +173 -0
- package/python/foxclaw/core/web_search.py +218 -0
- package/python/foxclaw/finance/__init__.py +15 -0
- package/python/foxclaw/finance/evidence.py +149 -0
- package/python/foxclaw/finance/fx.py +98 -0
- package/python/foxclaw/finance/graph.py +179 -0
- package/python/foxclaw/finance/models.py +146 -0
- package/python/foxclaw/finance/nlparse.py +515 -0
- package/python/foxclaw/finance/policy.py +301 -0
- package/python/foxclaw/finance/reasoner.py +121 -0
- package/python/foxclaw/finance/reasoning.py +319 -0
- package/python/foxclaw/finance/reconcile.py +175 -0
- package/python/foxclaw/finance/runtime.py +500 -0
- package/python/foxclaw/governance/__init__.py +24 -0
- package/python/foxclaw/governance/middleware.py +176 -0
- package/python/foxclaw/harness/__init__.py +17 -0
- package/python/foxclaw/harness/_base.py +66 -0
- package/python/foxclaw/harness/_policy.py +340 -0
- package/python/foxclaw/harness/drift.py +77 -0
- package/python/foxclaw/harness/event_bus.py +28 -0
- package/python/foxclaw/harness/harness.py +358 -0
- package/python/foxclaw/harness/loop_detection.py +145 -0
- package/python/foxclaw/itinerary/__init__.py +10 -0
- package/python/foxclaw/itinerary/amap_client.py +114 -0
- package/python/foxclaw/itinerary/models.py +80 -0
- package/python/foxclaw/itinerary/reasoner.py +96 -0
- package/python/foxclaw/itinerary/reasoning.py +168 -0
- package/python/foxclaw/itinerary/runtime.py +144 -0
- package/python/foxclaw/kernel/__init__.py +13 -0
- package/python/foxclaw/kernel/capabilities.py +72 -0
- package/python/foxclaw/kernel/policy_gate.py +108 -0
- package/python/foxclaw/kernel/process_manager.py +186 -0
- package/python/foxclaw/kernel/scheduler.py +125 -0
- package/python/foxclaw/kernel/sentinel.py +443 -0
- package/python/foxclaw/kernel/sentinel_client.py +243 -0
- package/python/foxclaw/kernel/spine.py +97 -0
- package/python/foxclaw/knowledge/__init__.py +360 -0
- package/python/foxclaw/knowledge/truth_crystallizer.py +89 -0
- package/python/foxclaw/learning/__init__.py +12 -0
- package/python/foxclaw/learning/knowledge_graph.py +144 -0
- package/python/foxclaw/learning/models.py +93 -0
- package/python/foxclaw/learning/reasoner.py +67 -0
- package/python/foxclaw/learning/runtime.py +351 -0
- package/python/foxclaw/learning/spaced_repetition.py +91 -0
- package/python/foxclaw/mcp_server.py +188 -0
- package/python/foxclaw/orchestration/__init__.py +167 -0
- package/python/foxclaw/pipeline/__init__.py +23 -0
- package/python/foxclaw/pipeline/cognitive_pipeline.py +429 -0
- package/python/foxclaw/pipeline/stage_meta.py +21 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +201 -0
- package/python/foxclaw/planning/__init__.py +17 -0
- package/python/foxclaw/planning/llm_planner.py +232 -0
- package/python/foxclaw/planning/planner.py +94 -0
- package/python/foxclaw/search/__init__.py +24 -0
- package/python/foxclaw/search/agent_port.py +190 -0
- package/python/foxclaw/search/evidence.py +109 -0
- package/python/foxclaw/search/external_search.py +50 -0
- package/python/foxclaw/search/hkc_read_port.py +60 -0
- package/python/foxclaw/search/internal_search.py +71 -0
- package/python/foxclaw/search/models.py +127 -0
- package/python/foxclaw/search/planner.py +44 -0
- package/python/foxclaw/search/retriever.py +39 -0
- package/python/foxclaw/search/runtime.py +127 -0
- package/python/foxclaw/search/strategy.py +44 -0
- package/python/foxclaw/skills/__init__.py +559 -0
- package/python/foxclaw/skills/archive.py +111 -0
- package/python/foxclaw/skills/fetch.py +226 -0
- package/python/foxclaw/skills/hooks_spec.py +69 -0
- package/python/foxclaw/skills/install_cmd.py +216 -0
- package/python/foxclaw/skills/lifecycle.py +252 -0
- package/python/foxclaw/skills/markdown_skill.py +175 -0
- package/python/foxclaw/skills/markdown_store.py +39 -0
- package/python/foxclaw/skills/mcp_client.py +184 -0
- package/python/foxclaw/skills/mcp_store.py +40 -0
- package/python/foxclaw/skills/permission.py +145 -0
- package/python/foxclaw/skills/pipeline.py +354 -0
- package/python/foxclaw/skills/policy.py +74 -0
- package/python/foxclaw/skills/review.py +72 -0
- package/python/foxclaw/skills/store.py +57 -0
- package/python/frontend/assets/core_bg.jpg +0 -0
- package/python/frontend/deck/auth.js +25 -0
- package/python/frontend/deck/chat_model_picker.js +483 -0
- package/python/frontend/deck/domain.js +276 -0
- package/python/frontend/deck/main.js +1806 -0
- package/python/frontend/deck/markdown.js +44 -0
- package/python/frontend/deck/settings.js +307 -0
- package/python/frontend/deck/state.js +5 -0
- package/python/frontend/deck/util.js +4 -0
- package/python/frontend/deck/visuals.js +190 -0
- package/python/frontend/deck.css +855 -0
- package/python/frontend/deck.html +686 -0
- package/python/frontend/deck_v2.html +341 -0
- package/python/frontend/planet/index.html +266 -0
- package/python/frontend/planet/planet.css +579 -0
- package/python/frontend/planet/planet.js +1436 -0
- package/python/frontend/planet/vendor/three.core.min.js +6 -0
- package/python/frontend/planet/vendor/three.module.min.js +6 -0
- package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
- package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
- package/python/frontend/v3/index.html +15 -0
- package/python/frontend/widget_runtime.js +252 -0
- package/python/frontend/widgets.js +639 -0
- package/python/frontend_src/index.html +14 -0
- package/python/frontend_src/package-lock.json +1376 -0
- package/python/frontend_src/package.json +19 -0
- package/python/frontend_src/src/App.vue +58 -0
- package/python/frontend_src/src/api.js +82 -0
- package/python/frontend_src/src/components/ChatDock.vue +365 -0
- package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
- package/python/frontend_src/src/components/DocViewer.vue +162 -0
- package/python/frontend_src/src/components/Inspector.vue +235 -0
- package/python/frontend_src/src/components/LeftRail.vue +415 -0
- package/python/frontend_src/src/components/MenuPopover.vue +128 -0
- package/python/frontend_src/src/components/MessageStream.vue +389 -0
- package/python/frontend_src/src/components/ModelPicker.vue +350 -0
- package/python/frontend_src/src/components/QuickIngest.vue +112 -0
- package/python/frontend_src/src/components/Stage.vue +272 -0
- package/python/frontend_src/src/components/ToolMenu.vue +141 -0
- package/python/frontend_src/src/components/TopBar.vue +118 -0
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +82 -0
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +83 -0
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +425 -0
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +117 -0
- package/python/frontend_src/src/components/panels/PanelBox.vue +50 -0
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +292 -0
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +358 -0
- package/python/frontend_src/src/components/panels/TasksPanel.vue +73 -0
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +746 -0
- package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
- package/python/frontend_src/src/lib/fmt.js +44 -0
- package/python/frontend_src/src/lib/i18n.js +53 -0
- package/python/frontend_src/src/lib/locales/en.js +173 -0
- package/python/frontend_src/src/lib/locales/zh.js +174 -0
- package/python/frontend_src/src/lib/md.js +190 -0
- package/python/frontend_src/src/main.js +9 -0
- package/python/frontend_src/src/stores/app.js +91 -0
- package/python/frontend_src/src/stores/chat.js +450 -0
- package/python/frontend_src/src/stores/model.js +96 -0
- package/python/frontend_src/src/stores/panels.js +289 -0
- package/python/frontend_src/src/stores/profile.js +31 -0
- package/python/frontend_src/src/stores/system.js +25 -0
- package/python/frontend_src/src/styles/base.css +138 -0
- package/python/frontend_src/src/styles/panel.css +117 -0
- package/python/frontend_src/src/styles/tokens.css +78 -0
- package/python/frontend_src/vite.config.js +39 -0
- package/python/pyproject.toml +268 -0
- package/python/requirements.txt +311 -0
- package/python/skills/README.md +123 -0
- package/python/skills/execution-contract/SKILL.md +30 -0
- package/python/skills/general-assistant/SKILL.md +34 -0
- package/python/skills/planner/SKILL.md +33 -0
- package/python/skills/schedule-itinerary/SKILL.md +35 -0
- package/python/skills/skill-composition/SKILL.md +31 -0
- package/python/skills/skill-guard/SKILL.md +67 -0
- package/python/skills/skill-memory/SKILL.md +50 -0
- package/python/skills/skill-router/SKILL.md +35 -0
- package/python/tests/conftest.py +54 -0
- package/python/tests/run_all.py +200 -0
- package/python/tests/test_agent_lifecycle.py +326 -0
- package/python/tests/test_ahs_p2.py +393 -0
- package/python/tests/test_anthropic_tools.py +192 -0
- package/python/tests/test_api.py +97 -0
- package/python/tests/test_api_auth.py +79 -0
- package/python/tests/test_api_host_guard.py +69 -0
- package/python/tests/test_attachments.py +151 -0
- package/python/tests/test_audit_chain.py +116 -0
- package/python/tests/test_audit_sink.py +130 -0
- package/python/tests/test_browser.py +149 -0
- package/python/tests/test_browser_request_guard.py +180 -0
- package/python/tests/test_capabilities.py +152 -0
- package/python/tests/test_channel_signing.py +185 -0
- package/python/tests/test_channel_trust.py +109 -0
- package/python/tests/test_channels.py +174 -0
- package/python/tests/test_cli.py +46 -0
- package/python/tests/test_compaction_toolpair.py +119 -0
- package/python/tests/test_config_layering.py +61 -0
- package/python/tests/test_confirm_nonce.py +186 -0
- package/python/tests/test_context_compaction_wiring.py +124 -0
- package/python/tests/test_dispatch.py +89 -0
- package/python/tests/test_domain_evals.py +334 -0
- package/python/tests/test_domain_import.py +72 -0
- package/python/tests/test_domain_reasoners.py +134 -0
- package/python/tests/test_e2e.py +161 -0
- package/python/tests/test_emb_cache.py +97 -0
- package/python/tests/test_embedding_singleton.py +87 -0
- package/python/tests/test_envfile.py +83 -0
- package/python/tests/test_evals_smoke.py +90 -0
- package/python/tests/test_event_spine.py +174 -0
- package/python/tests/test_evidence_semantic.py +88 -0
- package/python/tests/test_evidence_sse.py +94 -0
- package/python/tests/test_file_ingest.py +135 -0
- package/python/tests/test_finance.py +144 -0
- package/python/tests/test_finance_ledger.py +245 -0
- package/python/tests/test_finance_persist.py +174 -0
- package/python/tests/test_gateway.py +125 -0
- package/python/tests/test_governance.py +421 -0
- package/python/tests/test_governance_audit.py +324 -0
- package/python/tests/test_governance_middleware.py +137 -0
- package/python/tests/test_hkc.py +268 -0
- package/python/tests/test_hkc_index_build_manager.py +306 -0
- package/python/tests/test_hkc_manifest_reuse.py +294 -0
- package/python/tests/test_hkc_search_event_sync.py +482 -0
- package/python/tests/test_import_path_guard.py +155 -0
- package/python/tests/test_injection_guard.py +193 -0
- package/python/tests/test_integration.py +117 -0
- package/python/tests/test_itinerary.py +100 -0
- package/python/tests/test_kee_semantic.py +84 -0
- package/python/tests/test_kernel.py +212 -0
- package/python/tests/test_learning.py +101 -0
- package/python/tests/test_llm_admin.py +145 -0
- package/python/tests/test_llm_planner.py +344 -0
- package/python/tests/test_markdown_skills.py +62 -0
- package/python/tests/test_markdown_skills_openclaw.py +66 -0
- package/python/tests/test_mcp_connectors.py +97 -0
- package/python/tests/test_mcp_guardian_discovery.py +87 -0
- package/python/tests/test_mcp_no_memory.py +43 -0
- package/python/tests/test_mcp_policy_gate.py +76 -0
- package/python/tests/test_mcp_server.py +92 -0
- package/python/tests/test_memory_recall.py +92 -0
- package/python/tests/test_metrics.py +58 -0
- package/python/tests/test_multi_chain.py +90 -0
- package/python/tests/test_multimodal.py +122 -0
- package/python/tests/test_negotiation.py +136 -0
- package/python/tests/test_orchestration.py +109 -0
- package/python/tests/test_orchestration_decoupled.py +103 -0
- package/python/tests/test_p3_p5_hardening.py +324 -0
- package/python/tests/test_parallel_dispatch.py +207 -0
- package/python/tests/test_pipeline.py +492 -0
- package/python/tests/test_plan_visible.py +86 -0
- package/python/tests/test_planner.py +88 -0
- package/python/tests/test_policy_gate.py +108 -0
- package/python/tests/test_profile.py +67 -0
- package/python/tests/test_readme_claims.py +163 -0
- package/python/tests/test_reasoner.py +170 -0
- package/python/tests/test_release_check.py +252 -0
- package/python/tests/test_resume_checkpoint.py +148 -0
- package/python/tests/test_routing.py +85 -0
- package/python/tests/test_scheduler.py +138 -0
- package/python/tests/test_search.py +194 -0
- package/python/tests/test_secrets.py +153 -0
- package/python/tests/test_semantic.py +69 -0
- package/python/tests/test_sessions.py +90 -0
- package/python/tests/test_settings.py +265 -0
- package/python/tests/test_shadow_learning.py +290 -0
- package/python/tests/test_shared_write.py +200 -0
- package/python/tests/test_skill_taint_gate.py +145 -0
- package/python/tests/test_skills.py +117 -0
- package/python/tests/test_skills_fetch.py +282 -0
- package/python/tests/test_stats_and_events.py +162 -0
- package/python/tests/test_streaming.py +55 -0
- package/python/tests/test_supervisor_pipeline.py +176 -0
- package/python/tests/test_supervisor_triage.py +102 -0
- package/python/tests/test_taint.py +112 -0
- package/python/tests/test_task_stream.py +74 -0
- package/python/tests/test_thinking_sse.py +66 -0
- package/python/tests/test_thinking_stream.py +76 -0
- package/python/tests/test_tool_center.py +113 -0
- package/python/tests/test_tool_parallel.py +113 -0
- package/python/tests/test_tool_params.py +99 -0
- package/python/tests/test_tool_use.py +229 -0
- package/python/tests/test_toplevel_concurrency.py +116 -0
- package/python/tests/test_tracing.py +256 -0
- package/python/tests/test_truth_crystallizer.py +125 -0
- package/python/tests/test_truth_flow.py +88 -0
- package/python/tests/test_update_check.py +95 -0
- package/python/tests/test_url_policy.py +53 -0
- package/python/tests/test_verify_evidence.py +212 -0
- package/python/tests/test_version_sync.py +100 -0
- package/python/tests/test_web_trigger.py +82 -0
- package/python/tools/check_faiss_type.py +30 -0
- package/python/tools/check_log.ps1 +38 -0
- package/python/tools/ivf_status.py +72 -0
- package/python/tools/mypy_baseline.txt +13 -0
- package/python/tools/mypy_ratchet.py +143 -0
- package/python/tools/pack.py +173 -0
- package/python/tools/release_check.py +349 -0
- package/python/tools/restart_foxclaw.ps1 +71 -0
- package/python/tools/shared_write_audit.py +190 -0
- package/python/tools/size_baseline.txt +28 -0
- package/python/tools/size_check.py +102 -0
- package/python/tools/strip_comments.py +228 -0
- package/python/tools/verify_env.bat +2 -0
- package/python/tools/verify_env.py +25 -0
- package/python/vendor/HMR_LICENSE +37 -0
- package/python/vendor/HMR_README.md +65 -0
- package/python/vendor/hkc/LICENSE +37 -0
- package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
- package/python/vendor/hkc/hkc_ace/ace.py +457 -0
- package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
- package/python/vendor/hkc/hkc_api/__init__.py +0 -0
- package/python/vendor/hkc/hkc_api/container.py +416 -0
- package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
- package/python/vendor/hkc/hkc_api/main.py +157 -0
- package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
- package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
- package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
- package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
- package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
- package/python/vendor/hkc/hkc_api/schemas.py +153 -0
- package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
- package/python/vendor/hkc/hkc_core/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
- package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
- package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
- package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
- package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
- package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
- package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
- package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
- package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
- package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
- package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
- package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
- package/python/vendor/hkc/hkc_kde/kde.py +298 -0
- package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
- package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
- package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
- package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
- package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
- package/python/vendor/hkc/hkc_kde/models.py +74 -0
- package/python/vendor/hkc/hkc_kde/packager.py +329 -0
- package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
- package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
- package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
- package/python/vendor/hkc/hkc_kee/kee.py +541 -0
- package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
- package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
- package/python/vendor/hkc/hkc_sdk/base.py +151 -0
- package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
- package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
- package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
- package/python/vendor/hkc/hkc_sdk/models.py +171 -0
- package/python/vendor/hkc/hkc_search/__init__.py +0 -0
- package/python/vendor/hkc/hkc_search/bm25.py +325 -0
- package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
- package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
- package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
- package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
- package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
- package/python/vendor/hmr/__init__.py +0 -0
- package/python/vendor/hmr/core/__init__.py +0 -0
- package/python/vendor/hmr/core/hmr.py +684 -0
- package/python/vendor/hmr/core/models.py +310 -0
- package/python/vendor/hmr/engines/__init__.py +0 -0
- package/python/vendor/hmr/engines/evolution.py +710 -0
- package/python/vendor/hmr/engines/jit_compiler.py +472 -0
- package/python/vendor/hmr/engines/lifecycle.py +264 -0
- package/python/vendor/hmr/engines/policy.py +625 -0
- package/python/vendor/hmr/engines/recall.py +260 -0
- package/python/vendor/hmr/engines/runtime_state.py +158 -0
- package/python/vendor/hmr/engines/scheduler.py +323 -0
- package/python/vendor/hmr/engines/semantic.py +150 -0
- package/python/vendor/hmr/engines/temporal.py +261 -0
- package/python/vendor/hmr/engines/thought_chain.py +635 -0
- package/python/vendor/hmr/graph/__init__.py +0 -0
- package/python/vendor/hmr/graph/cwg.py +338 -0
- package/python/vendor/hmr/graph/memory_graph.py +530 -0
- package/python/vendor/hmr/storage/__init__.py +0 -0
- package/python/vendor/hmr/storage/memory_fs.py +300 -0
- package/python/vendor/hmr/storage/vector_store.py +520 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
认知搜索内核(HSR 融入)测试 —— 全离线,不联网(external_ports=[])。
|
|
3
|
+
|
|
4
|
+
覆盖:
|
|
5
|
+
1. 空源检索:无 HMR / 无知识 / 无外部端口 → 返回 SearchResult,迭代有上限,不死循环
|
|
6
|
+
2. 内部记忆证据:假 HMR recall → 证据图含 MEMORY 来源节点
|
|
7
|
+
3. 知识端口边界:HKCReadPort(client=None) 永远返回 [](降级不报错)
|
|
8
|
+
4. 经 Foxclaw 惰性 search_runtime + aclose teardown
|
|
9
|
+
|
|
10
|
+
联网(百度)与真实 HKC 检索不在离线回归内;这里验证 HSR 的编排/融合/迭代/降级契约。
|
|
11
|
+
"""
|
|
12
|
+
import sys, tempfile, shutil, asyncio
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
15
|
+
|
|
16
|
+
from foxclaw.search import SearchRuntime, HKCReadPort, SourceType
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FakeHMR:
|
|
20
|
+
"""假 HMR:recall 返回两条记忆,验证内部记忆证据能进证据图。"""
|
|
21
|
+
def recall(self, query, agent_id=None, top_k=5):
|
|
22
|
+
return [
|
|
23
|
+
{"id": "m1", "content": f"关于「{query}」的历史记忆一", "confidence": 0.7},
|
|
24
|
+
{"id": "m2", "content": f"关于「{query}」的历史记忆二", "confidence": 0.6},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_empty_sources_terminates():
|
|
29
|
+
print("\n【1】空源检索:不死循环 + 迭代有上限")
|
|
30
|
+
rt = SearchRuntime(hmr=None, knowledge_port=None, external_ports=[])
|
|
31
|
+
r = rt.search("任意查询", agent_id="finance")
|
|
32
|
+
assert r.iterations <= 5, f"迭代应有上限,实际 {r.iterations}"
|
|
33
|
+
assert isinstance(r.trace, list) and r.trace
|
|
34
|
+
assert 0.0 <= r.confidence <= 1.0
|
|
35
|
+
print(f" ✓ 返回 SearchResult: iterations={r.iterations} confidence={r.confidence:.2f} "
|
|
36
|
+
f"trace={len(r.trace)}条")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_memory_evidence():
|
|
40
|
+
print("\n【2】内部记忆证据进证据图(MEMORY 来源)")
|
|
41
|
+
rt = SearchRuntime(hmr=FakeHMR(), knowledge_port=None, external_ports=[])
|
|
42
|
+
r = rt.search("分析活字印刷的历史", agent_id="office")
|
|
43
|
+
nodes = list(r.graph.nodes.values())
|
|
44
|
+
mem = [n for n in nodes if n.source == SourceType.MEMORY]
|
|
45
|
+
assert len(mem) >= 1, "假 HMR 的记忆应作为证据进图"
|
|
46
|
+
print(f" ✓ 证据图 {len(nodes)} 节点,其中 MEMORY 来源 {len(mem)} 个")
|
|
47
|
+
print(f" 样例: {mem[0].fact[:30]!r} conf={mem[0].confidence:.2f}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_hkc_port_degrades():
|
|
51
|
+
print("\n【3】知识端口降级:HKCReadPort(client=None) 返回 []")
|
|
52
|
+
port = HKCReadPort(client=None)
|
|
53
|
+
assert port.available is False
|
|
54
|
+
assert port.query("任意", top_k=5) == []
|
|
55
|
+
print(" ✓ client=None 时 available=False 且 query 返回 [](不报错)")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_via_foxclawos_lazy():
|
|
59
|
+
print("\n【4】经 Foxclaw 惰性 search_runtime + aclose")
|
|
60
|
+
d = tempfile.mkdtemp(prefix="hsr_os_")
|
|
61
|
+
try:
|
|
62
|
+
from foxclaw import Foxclaw
|
|
63
|
+
os_ = Foxclaw(data_dir=d, shadow_rounds=50)
|
|
64
|
+
assert "search_runtime" not in os_.capabilities._caps, "未访问前不该构造"
|
|
65
|
+
rt = os_.search_runtime
|
|
66
|
+
assert rt is not None and os_.search_runtime is rt, "应缓存同一实例"
|
|
67
|
+
rt.external_ports = []
|
|
68
|
+
r = rt.search("测试查询", agent_id="supervisor")
|
|
69
|
+
assert r.confidence is not None
|
|
70
|
+
print(f" ✓ 惰性构造成功,search 返回 confidence={r.confidence:.2f}")
|
|
71
|
+
asyncio.run(os_.aclose())
|
|
72
|
+
print(" ✓ aclose() 释放(含惰性装配过的 HKC)")
|
|
73
|
+
finally:
|
|
74
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class _NS:
|
|
78
|
+
"""简易命名空间对象(带属性)。"""
|
|
79
|
+
def __init__(self, **kw): self.__dict__.update(kw)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class FakeThoughtChain:
|
|
83
|
+
def search_nodes(self, keyword, agent_id=None, limit=20):
|
|
84
|
+
if "预算" in keyword or "风险" in keyword:
|
|
85
|
+
return [_NS(id="t1", agent_id="finance", content="评估本月预算风险偏高")]
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class FakeRegistry:
|
|
90
|
+
def catalog(self):
|
|
91
|
+
return [{"name": "gmail_send", "triggers": ["发邮件", "email"],
|
|
92
|
+
"kind": "mcp", "side_effect": "external"}]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_agent_port_all_subsources():
|
|
96
|
+
print("\n【5】AgentSourcePort:四类子源全连通")
|
|
97
|
+
from foxclaw.search import AgentSourcePort
|
|
98
|
+
finance = _NS(
|
|
99
|
+
transactions=[_NS(entity="星巴克咖啡", amount=35, category="餐饮", tags=["coffee"])],
|
|
100
|
+
budgets=[_NS(category="餐饮", monthly_limit=800)], entities={})
|
|
101
|
+
learning = _NS(items={"k1": _NS(content="活字印刷术由毕昇发明", topic="历史", tags=[])})
|
|
102
|
+
itinerary = _NS(waypoints=[_NS(name="首都机场", address="北京朝阳", city="北京", start_time="09:00")])
|
|
103
|
+
agents = {"finance": _NS(get_status=lambda: {"role": "理财风控", "status": "idle"})}
|
|
104
|
+
|
|
105
|
+
class HMRStub:
|
|
106
|
+
def recall(self, query, agent_id=None, top_k=5):
|
|
107
|
+
return [{"id": "m1", "content": f"{agent_id}记得关于{query}的事"}]
|
|
108
|
+
|
|
109
|
+
port = AgentSourcePort(agents=agents, finance_runtime=finance, learning_runtime=learning,
|
|
110
|
+
itinerary_runtime=itinerary, thought_chain=FakeThoughtChain(),
|
|
111
|
+
skill_registry=FakeRegistry(), hmr=HMRStub())
|
|
112
|
+
assert port.available
|
|
113
|
+
from foxclaw.search import SourceType
|
|
114
|
+
|
|
115
|
+
def origins(q):
|
|
116
|
+
return {e.origin for e in port.query(q, top_k=8)}
|
|
117
|
+
|
|
118
|
+
assert any("finance:transaction" in o for o in origins("星巴克")), "财务交易应连通"
|
|
119
|
+
assert any("learning" in o for o in origins("活字印刷")), "学习知识应连通"
|
|
120
|
+
assert any("itinerary" in o for o in origins("首都机场")), "行程点应连通"
|
|
121
|
+
assert any("thoughtchain" in o for o in origins("预算风险")), "推理链应连通"
|
|
122
|
+
assert any("capability:skill" in o for o in origins("发邮件")), "能力目录应连通"
|
|
123
|
+
assert any(":hmr:" in o for o in origins("任意")), "各Agent记忆应连通"
|
|
124
|
+
evs = port.query("星巴克", top_k=8)
|
|
125
|
+
assert all(e.source == SourceType.AGENT for e in evs)
|
|
126
|
+
print(" ✓ 财务/学习/行程/推理链/能力/各Agent记忆 六路均连通,来源标记 AGENT")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_bidirectional_and_internal_only():
|
|
130
|
+
print("\n【6】双向:Agent 调用内部搜索(internal_only,不联网)")
|
|
131
|
+
d = tempfile.mkdtemp(prefix="hsr_bi_")
|
|
132
|
+
try:
|
|
133
|
+
from foxclaw import Foxclaw
|
|
134
|
+
from foxclaw.search import SourceType
|
|
135
|
+
os_ = Foxclaw(data_dir=d, shadow_rounds=50)
|
|
136
|
+
from foxclaw.finance.models import Transaction
|
|
137
|
+
os_.finance_runtime.add_transaction(
|
|
138
|
+
Transaction(entity="蔚来汽车", amount=-260000, category="购车",
|
|
139
|
+
account="alipay", timestamp="2026-05-01T10:00:00"),
|
|
140
|
+
run_gatekeeper=False)
|
|
141
|
+
_ = os_.search_runtime
|
|
142
|
+
res = os_.finance.search_internal("蔚来汽车")
|
|
143
|
+
assert res is not None, "search_internal 应返回 SearchResult"
|
|
144
|
+
agent_ev = [n for n in res.graph.nodes.values() if n.source == SourceType.AGENT]
|
|
145
|
+
assert any("蔚来" in n.fact for n in agent_ev), "应从财务内核检索到该交易"
|
|
146
|
+
assert all(n.source != SourceType.WEB for n in res.graph.nodes.values())
|
|
147
|
+
print(f" ✓ os_.finance.search_internal('蔚来汽车') → AGENT证据 {len(agent_ev)} 条,无联网")
|
|
148
|
+
asyncio.run(os_.aclose())
|
|
149
|
+
finally:
|
|
150
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_internal_source_isolation():
|
|
154
|
+
print("\n【7】深审修复:内部单源抛异常被隔离(不掀翻整个 search)")
|
|
155
|
+
class _BoomPort:
|
|
156
|
+
def query(self, text, top_k=5): raise RuntimeError("内部源炸了")
|
|
157
|
+
class _OKKnow:
|
|
158
|
+
def query(self, text, top_k=5):
|
|
159
|
+
from foxclaw.search import SourceType
|
|
160
|
+
from foxclaw.search.models import RawEvidence
|
|
161
|
+
return [RawEvidence(id="k1", fact=f"知识:{text}", source=SourceType.KNOWLEDGE,
|
|
162
|
+
origin="hkc:k1", confidence=0.7)]
|
|
163
|
+
rt = SearchRuntime(hmr=FakeHMR(), knowledge_port=_OKKnow(),
|
|
164
|
+
agent_port=_BoomPort(), external_ports=[])
|
|
165
|
+
r = rt.search("测试查询", agent_id="finance", internal_only=True)
|
|
166
|
+
facts = [n.fact for n in r.graph.nodes.values()]
|
|
167
|
+
assert any("知识" in f for f in facts), "知识源应仍返回(不受 agent 源崩溃影响)"
|
|
168
|
+
assert any("记忆" in f for f in facts), "记忆源应仍返回"
|
|
169
|
+
print(f" ✓ agent 源抛异常被隔离,其它源正常:{len(r.graph.nodes)} 节点")
|
|
170
|
+
rt2 = SearchRuntime(hmr=None, knowledge_port=_BoomPort(),
|
|
171
|
+
agent_port=_BoomPort(), external_ports=[])
|
|
172
|
+
r2 = rt2.search("测试", internal_only=True)
|
|
173
|
+
assert len(r2.graph.nodes) == 0 and 0.0 <= r2.confidence <= 1.0
|
|
174
|
+
print(" ✓ 全源崩溃 → search 不崩、返回空图(与 external 隔离对称)")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def main():
|
|
178
|
+
print("=" * 60)
|
|
179
|
+
print("认知搜索内核 · HSR 融入(全离线,不联网)")
|
|
180
|
+
print("=" * 60)
|
|
181
|
+
test_empty_sources_terminates()
|
|
182
|
+
test_memory_evidence()
|
|
183
|
+
test_hkc_port_degrades()
|
|
184
|
+
test_via_foxclawos_lazy()
|
|
185
|
+
test_agent_port_all_subsources()
|
|
186
|
+
test_bidirectional_and_internal_only()
|
|
187
|
+
test_internal_source_isolation()
|
|
188
|
+
print("\n" + "=" * 60)
|
|
189
|
+
print("✓ HSR 融入验证完成:编排/证据融合/迭代上限/端口降级/惰性teardown")
|
|
190
|
+
print("=" * 60)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
main()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""本地密钥存储:keyring 优先、base64 回退、改名/删除不留孤儿。
|
|
2
|
+
|
|
3
|
+
不碰真 OS 凭据库(测试机器上可能没有后端,也可能有用户真实数据),
|
|
4
|
+
用 fake 替换 `secrets._keyring`。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from foxclaw.core import secrets
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _FakeKeyring:
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.store = {}
|
|
15
|
+
|
|
16
|
+
def set_password(self, service, user, value):
|
|
17
|
+
self.store[(service, user)] = value
|
|
18
|
+
|
|
19
|
+
def get_password(self, service, user):
|
|
20
|
+
return self.store.get((service, user))
|
|
21
|
+
|
|
22
|
+
def delete_password(self, service, user):
|
|
23
|
+
if (service, user) not in self.store:
|
|
24
|
+
raise KeyError(user)
|
|
25
|
+
del self.store[(service, user)]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def fake_kr(monkeypatch):
|
|
30
|
+
kr = _FakeKeyring()
|
|
31
|
+
monkeypatch.setattr(secrets, "_keyring", lambda: kr)
|
|
32
|
+
return kr
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def no_kr(monkeypatch):
|
|
37
|
+
monkeypatch.setattr(secrets, "_keyring", lambda: None)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_store_takes_over_when_keyring_available(fake_kr):
|
|
41
|
+
assert secrets.store("m1", "sk-abc") is True
|
|
42
|
+
assert fake_kr.store[(secrets.SERVICE, "m1")] == "sk-abc"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_store_reports_failure_without_keyring(no_kr):
|
|
46
|
+
assert secrets.store("m1", "sk-abc") is False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_load_prefers_keyring_over_legacy_ciphertext(fake_kr):
|
|
50
|
+
fake_kr.store[(secrets.SERVICE, "m1")] = "sk-new"
|
|
51
|
+
assert secrets.load("m1", secrets.obf("sk-old")) == "sk-new"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_load_falls_back_to_legacy_ciphertext(no_kr):
|
|
55
|
+
assert secrets.load("m1", secrets.obf("sk-old")) == "sk-old"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_load_returns_empty_when_nothing_stored(no_kr):
|
|
59
|
+
assert secrets.load("m1") == ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_rename_moves_credential(fake_kr):
|
|
63
|
+
"""改名不搬迁 → load(新名) 取不到,key 静默丢失。"""
|
|
64
|
+
secrets.store("old", "sk-abc")
|
|
65
|
+
assert secrets.rename("old", "new") is True
|
|
66
|
+
assert secrets.load("new") == "sk-abc"
|
|
67
|
+
assert fake_kr.get_password(secrets.SERVICE, "old") is None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_rename_without_source_is_noop(fake_kr):
|
|
71
|
+
assert secrets.rename("missing", "new") is False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_delete_is_idempotent(fake_kr):
|
|
75
|
+
secrets.store("m1", "sk-abc")
|
|
76
|
+
secrets.delete("m1")
|
|
77
|
+
secrets.delete("m1")
|
|
78
|
+
assert secrets.load("m1") == ""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_delete_without_keyring_does_not_raise(no_kr):
|
|
82
|
+
secrets.delete("m1")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_save_key_keeps_only_marker_when_keyring_available(fake_kr):
|
|
86
|
+
"""有 OS 凭据库时,JSON 里不能再有任何可还原的密文。"""
|
|
87
|
+
from foxclaw.api.routes.llm_admin import _save_key
|
|
88
|
+
|
|
89
|
+
fields = _save_key("m1", " sk-abc ")
|
|
90
|
+
assert fields == {"api_key_kr": True, "api_key_obf": ""}
|
|
91
|
+
assert fake_kr.store[(secrets.SERVICE, "m1")] == "sk-abc"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_save_key_falls_back_to_ciphertext(no_kr):
|
|
95
|
+
from foxclaw.api.routes.llm_admin import _save_key
|
|
96
|
+
|
|
97
|
+
fields = _save_key("m1", "sk-abc")
|
|
98
|
+
assert fields["api_key_kr"] is False
|
|
99
|
+
assert secrets.deobf(fields["api_key_obf"]) == "sk-abc"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_save_key_empty_input_clears_both(no_kr):
|
|
103
|
+
from foxclaw.api.routes.llm_admin import _save_key
|
|
104
|
+
|
|
105
|
+
assert _save_key("m1", "") == {"api_key_kr": False, "api_key_obf": ""}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.parametrize("record,expected", [
|
|
109
|
+
({"api_key_kr": True, "api_key_obf": ""}, True),
|
|
110
|
+
({"api_key_kr": False, "api_key_obf": "c2stYWJj"}, True),
|
|
111
|
+
({"api_key": "sk-legacy"}, True),
|
|
112
|
+
({"api_key_kr": False, "api_key_obf": ""}, False),
|
|
113
|
+
({}, False),
|
|
114
|
+
])
|
|
115
|
+
def test_has_key(record, expected):
|
|
116
|
+
from foxclaw.api.routes.llm_admin import _has_key
|
|
117
|
+
|
|
118
|
+
assert _has_key(record) is expected
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_obf_is_reversible(no_kr):
|
|
122
|
+
"""base64 只是混淆,不要在任何地方当加密用 —— 这条测试的作用是钉住这一点。"""
|
|
123
|
+
import base64
|
|
124
|
+
|
|
125
|
+
assert secrets.deobf(secrets.obf("sk-secret")) == "sk-secret"
|
|
126
|
+
assert base64.b64decode(secrets.obf("sk-secret")).decode() == "sk-secret"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_backend_probe_is_cached(monkeypatch):
|
|
130
|
+
import sys
|
|
131
|
+
import types
|
|
132
|
+
|
|
133
|
+
calls = []
|
|
134
|
+
|
|
135
|
+
class _Fail:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
fake = types.ModuleType("keyring")
|
|
139
|
+
fake.get_keyring = lambda: calls.append(1) or _Fail()
|
|
140
|
+
backends = types.ModuleType("keyring.backends")
|
|
141
|
+
fail_mod = types.ModuleType("keyring.backends.fail")
|
|
142
|
+
fail_mod.Keyring = _Fail
|
|
143
|
+
monkeypatch.setitem(sys.modules, "keyring", fake)
|
|
144
|
+
monkeypatch.setitem(sys.modules, "keyring.backends", backends)
|
|
145
|
+
monkeypatch.setitem(sys.modules, "keyring.backends.fail", fail_mod)
|
|
146
|
+
|
|
147
|
+
secrets.reset_probe()
|
|
148
|
+
try:
|
|
149
|
+
assert secrets.available() is False
|
|
150
|
+
secrets.available()
|
|
151
|
+
assert len(calls) == 1
|
|
152
|
+
finally:
|
|
153
|
+
secrets.reset_probe()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""真语义检索 + KEE 语义化验证。用 HMR 已缓存的 all-MiniLM-L6-v2 证明机制(离线可用);
|
|
2
|
+
模型未缓存则优雅跳过(退出0,不 FAIL)——真机上装了多语言模型即走真语义,否则 auto/keyword 降级。"""
|
|
3
|
+
import os, sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
6
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "vendor" / "hkc"))
|
|
7
|
+
|
|
8
|
+
def test_semantic():
|
|
9
|
+
os.environ.setdefault("HF_HUB_OFFLINE", "1")
|
|
10
|
+
os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
|
|
11
|
+
|
|
12
|
+
MODEL = "all-MiniLM-L6-v2"
|
|
13
|
+
from hkc_search.embedding_backends import get_st_model
|
|
14
|
+
|
|
15
|
+
print("=" * 60); print("真语义检索 + KEE 语义化验证"); print("=" * 60)
|
|
16
|
+
|
|
17
|
+
from hkc_kee.kee import _SimpleEmbedder as _SE
|
|
18
|
+
assert _SE._has_neg("this is a badge") is False, "badge 误命中否定"
|
|
19
|
+
assert _SE._has_neg("I know this") is False, "know 误命中否定"
|
|
20
|
+
assert _SE._has_neg("this is not good") is True
|
|
21
|
+
assert _SE._has_neg("cannot proceed") is True
|
|
22
|
+
assert _SE._has_neg("这个方案不好") is True
|
|
23
|
+
print("[0] KEE 否定词词边界(badge/know 不误命中) OK")
|
|
24
|
+
|
|
25
|
+
from hkc_kee.kee import KnowledgeEvolutionEngine
|
|
26
|
+
class _Bus:
|
|
27
|
+
def subscribe(self, *a, **k): pass
|
|
28
|
+
class _Store: pass
|
|
29
|
+
class _Gen: pass
|
|
30
|
+
eng = KnowledgeEvolutionEngine(_Store(), _Bus(), _Gen())
|
|
31
|
+
assert abs(eng.similarity_threshold - 0.30) < 1e-9, "默认阈值应 0.30(与既有硬编码一致)"
|
|
32
|
+
eng2 = KnowledgeEvolutionEngine(_Store(), _Bus(), _Gen(), similarity_threshold=0.90)
|
|
33
|
+
assert abs(eng2.similarity_threshold - 0.90) < 1e-9, "阈值应可经构造参数配置"
|
|
34
|
+
print("[0b] KEE 语义方向阈值接线(默认 0.30 / 可配置) OK")
|
|
35
|
+
|
|
36
|
+
m = get_st_model(MODEL)
|
|
37
|
+
if m is None:
|
|
38
|
+
print("[skip] all-MiniLM 未缓存,跳过真语义验证(生产靠 auto/keyword 降级兜底)")
|
|
39
|
+
if __name__ != "__main__":
|
|
40
|
+
import pytest
|
|
41
|
+
pytest.skip("all-MiniLM 未缓存 → 真语义验证跳过(auto/keyword 降级兜底)",
|
|
42
|
+
allow_module_level=True)
|
|
43
|
+
sys.exit(0)
|
|
44
|
+
|
|
45
|
+
assert get_st_model(MODEL) is m, "同名应共享同一实例(单例)"
|
|
46
|
+
print("[1] get_st_model 同名单例 OK")
|
|
47
|
+
|
|
48
|
+
import numpy as np
|
|
49
|
+
from hkc_search.embedding_backends import LocalSTBackend
|
|
50
|
+
b = LocalSTBackend(model_name=MODEL)
|
|
51
|
+
va, vb, vc = b.encode(["I am very happy today", "Feeling great today", "The stock market crashed"])
|
|
52
|
+
sim_close = float(np.dot(va, vb)); sim_far = float(np.dot(va, vc))
|
|
53
|
+
assert sim_close > sim_far, f"相近应更高: {sim_close} vs {sim_far}"
|
|
54
|
+
print(f"[2] LocalST 真余弦:相近({sim_close:.2f}) > 无关({sim_far:.2f}) OK")
|
|
55
|
+
|
|
56
|
+
from hkc_kee.kee import _SimpleEmbedder
|
|
57
|
+
_SimpleEmbedder._MODEL_NAME = MODEL
|
|
58
|
+
emb = _SimpleEmbedder()
|
|
59
|
+
s_same = emb.score("this plan is good", "this plan is nice")
|
|
60
|
+
s_unrel = emb.score("this plan is good", "it will rain tomorrow")
|
|
61
|
+
s_contra = emb.score("this plan is good", "this plan is not good")
|
|
62
|
+
assert s_same > s_unrel, f"相近应 > 无关: {s_same} vs {s_unrel}"
|
|
63
|
+
assert s_contra < s_same, f"矛盾应 < 同向: {s_contra} vs {s_same}"
|
|
64
|
+
print(f"[3] KEE score:同向({s_same:.2f}) > 无关({s_unrel:.2f}),矛盾({s_contra:.2f})<同向 OK")
|
|
65
|
+
|
|
66
|
+
print("\n真语义检索 + KEE 语义化验证通过")
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
test_semantic()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
会话存储回归测试(全离线)。
|
|
3
|
+
|
|
4
|
+
覆盖:建/列/读/追加/改名/删、标题自动提炼、按更新时间倒序、
|
|
5
|
+
列表不含 messages(负载)、路径穿越防护、并发追加(对话跑在后台线程)。
|
|
6
|
+
"""
|
|
7
|
+
import sys, tempfile, shutil, threading
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
10
|
+
|
|
11
|
+
from foxclaw.core.sessions import SessionStore, derive_title
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture()
|
|
16
|
+
def store():
|
|
17
|
+
d = tempfile.mkdtemp(prefix="sess_")
|
|
18
|
+
try:
|
|
19
|
+
yield SessionStore(Path(d) / "sessions")
|
|
20
|
+
finally:
|
|
21
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_create_list_get(store):
|
|
25
|
+
s = store.create()
|
|
26
|
+
assert s["id"] and s["title"] == "新会话" and s["message_count"] == 0
|
|
27
|
+
assert [x["id"] for x in store.list()] == [s["id"]]
|
|
28
|
+
assert store.get(s["id"])["messages"] == []
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_append_and_title_derivation(store):
|
|
32
|
+
s = store.create()
|
|
33
|
+
assert store.append(s["id"], "user", "帮我分析本月支出情况。还有别的")
|
|
34
|
+
assert store.append(s["id"], "assistant", "好的,本月支出如下…")
|
|
35
|
+
d = store.get(s["id"])
|
|
36
|
+
assert len(d["messages"]) == 2
|
|
37
|
+
assert d["messages"][0]["role"] == "user"
|
|
38
|
+
assert d["title"] == "帮我分析本月支出情况"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_append_rejects_bad_role_and_missing_session(store):
|
|
42
|
+
s = store.create()
|
|
43
|
+
assert store.append(s["id"], "system", "x") is False
|
|
44
|
+
assert store.append(s["id"], "user", "") is False
|
|
45
|
+
assert store.append("deadbeef1234", "user", "x") is False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_list_is_light_and_sorted(store):
|
|
49
|
+
a = store.create(); store.append(a["id"], "user", "第一个会话")
|
|
50
|
+
b = store.create(); store.append(b["id"], "user", "第二个会话")
|
|
51
|
+
lst = store.list()
|
|
52
|
+
assert lst[0]["id"] == b["id"], "应按 updated_at 倒序"
|
|
53
|
+
assert "messages" not in lst[0], "列表不得带 messages(负载)"
|
|
54
|
+
assert lst[0]["message_count"] == 1 and lst[0]["last"] == "第二个会话"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_rename_and_delete(store):
|
|
58
|
+
s = store.create()
|
|
59
|
+
assert store.rename(s["id"], "季度复盘")
|
|
60
|
+
assert store.get(s["id"])["title"] == "季度复盘"
|
|
61
|
+
assert store.delete(s["id"])
|
|
62
|
+
assert store.get(s["id"]) is None
|
|
63
|
+
assert store.delete(s["id"]) is False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_path_traversal_blocked(store):
|
|
67
|
+
"""id 只允许安全字符,杜绝 ../ 路径穿越。"""
|
|
68
|
+
assert store.get("../../etc/passwd") is None
|
|
69
|
+
assert store.delete("../../x") is False
|
|
70
|
+
assert store.append("../../x", "user", "hi") is False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_concurrent_append_no_loss(store):
|
|
74
|
+
"""对话跑在后台线程,追加必须线程安全、不丢消息。"""
|
|
75
|
+
s = store.create()
|
|
76
|
+
def w(i):
|
|
77
|
+
store.append(s["id"], "user", f"消息{i}")
|
|
78
|
+
ts = [threading.Thread(target=w, args=(i,)) for i in range(20)]
|
|
79
|
+
[t.start() for t in ts]; [t.join() for t in ts]
|
|
80
|
+
assert len(store.get(s["id"])["messages"]) == 20
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_derive_title():
|
|
84
|
+
assert derive_title("你好呀!今天天气") == "你好呀"
|
|
85
|
+
assert derive_title("") == "新会话"
|
|
86
|
+
assert len(derive_title("长" * 100)) <= 24
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
pytest.main([__file__, "-v"])
|