foxclaw 0.1.0 → 0.1.2
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 +11 -10
- package/package.json +46 -38
- package/python/.dockerignore +35 -0
- package/python/.env.example +165 -0
- package/python/.gitattributes +46 -0
- package/python/.github/workflows/ci.yml +122 -0
- package/python/.github/workflows/release.yml +183 -0
- package/python/Dockerfile +64 -0
- package/python/LICENSE +21 -0
- package/python/MANIFEST.in +14 -0
- package/python/README.md +192 -0
- package/python/README_CN.md +324 -0
- package/python/bench/bm25_audit.py +199 -0
- package/python/bench/bm25_regression.py +178 -0
- package/python/bench/bm25_stress.py +159 -0
- package/python/bench/cd_audit.py +238 -0
- package/python/bench/chaos.py +142 -0
- package/python/bench/concurrency.py +103 -0
- package/python/bench/e2e_real_models.py +95 -0
- package/python/bench/llm_retry_test.py +248 -0
- package/python/bench/load_test.py +196 -0
- package/python/bench/vector_ivf_upgrade.py +134 -0
- package/python/docs/CHANNELS.md +195 -0
- package/python/docs/GOVERNANCE.md +164 -0
- package/python/docs/RELEASE.md +109 -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 +152 -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 +400 -0
- package/python/examples/governance_middleware_demo.py +77 -0
- package/python/examples/langgraph_governance_demo.py +108 -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 +526 -0
- package/python/foxclaw/agents/__init__.py +12 -0
- package/python/foxclaw/agents/agents.py +35 -0
- package/python/foxclaw/agents/base.py +865 -0
- package/python/foxclaw/agents/impl/__init__.py +1 -0
- package/python/foxclaw/agents/impl/_helpers.py +78 -0
- package/python/foxclaw/agents/impl/finance.py +248 -0
- package/python/foxclaw/agents/impl/office.py +367 -0
- package/python/foxclaw/agents/impl/supervisor.py +659 -0
- package/python/foxclaw/agents/mixins/_context.py +151 -0
- package/python/foxclaw/agents/mixins/augment.py +149 -0
- package/python/foxclaw/agents/mixins/llm.py +66 -0
- package/python/foxclaw/agents/mixins/memory.py +35 -0
- package/python/foxclaw/agents/mixins/preference.py +81 -0
- package/python/foxclaw/agents/mixins/prompt.py +77 -0
- package/python/foxclaw/agents/mixins/skillbox.py +58 -0
- package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
- package/python/foxclaw/api/__init__.py +283 -0
- package/python/foxclaw/api/app.py +123 -0
- package/python/foxclaw/api/middleware.py +317 -0
- package/python/foxclaw/api/routes/ahs.py +417 -0
- package/python/foxclaw/api/routes/attachments.py +117 -0
- package/python/foxclaw/api/routes/browser.py +39 -0
- package/python/foxclaw/api/routes/domain.py +314 -0
- package/python/foxclaw/api/routes/knowledge.py +163 -0
- package/python/foxclaw/api/routes/llm_admin.py +461 -0
- package/python/foxclaw/api/routes/memory.py +89 -0
- package/python/foxclaw/api/routes/orchestration.py +41 -0
- package/python/foxclaw/api/routes/recovery.py +43 -0
- package/python/foxclaw/api/routes/review.py +275 -0
- package/python/foxclaw/api/routes/search.py +37 -0
- package/python/foxclaw/api/routes/system.py +327 -0
- package/python/foxclaw/api/routes/task.py +281 -0
- package/python/foxclaw/api/routes/ws.py +68 -0
- package/python/foxclaw/api/spine.py +55 -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 +54 -0
- package/python/foxclaw/browser/action/action_types.py +45 -0
- package/python/foxclaw/browser/action/action_verifier.py +68 -0
- package/python/foxclaw/browser/action/url_policy.py +147 -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 +166 -0
- package/python/foxclaw/browser/perception/__init__.py +1 -0
- package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
- package/python/foxclaw/browser/perception/element_indexer.py +57 -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 +148 -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 +67 -0
- package/python/foxclaw/channels/__init__.py +140 -0
- package/python/foxclaw/channels/discord.py +184 -0
- package/python/foxclaw/channels/dispatcher.py +204 -0
- package/python/foxclaw/channels/feishu.py +225 -0
- package/python/foxclaw/channels/openclaw_bridge.py +156 -0
- package/python/foxclaw/channels/signing.py +187 -0
- package/python/foxclaw/channels/telegram.py +121 -0
- package/python/foxclaw/channels/weixin.py +225 -0
- package/python/foxclaw/cli.py +154 -0
- package/python/foxclaw/cognitive/__init__.py +4 -0
- package/python/foxclaw/cognitive/reasoner.py +82 -0
- package/python/foxclaw/cognitive/state.py +147 -0
- package/python/foxclaw/core/__init__.py +9 -0
- package/python/foxclaw/core/audit_chain.py +133 -0
- package/python/foxclaw/core/audit_sink.py +100 -0
- package/python/foxclaw/core/compaction.py +283 -0
- package/python/foxclaw/core/config.py +65 -0
- package/python/foxclaw/core/confirm_nonce.py +157 -0
- package/python/foxclaw/core/dispatch.py +27 -0
- package/python/foxclaw/core/envfile.py +48 -0
- package/python/foxclaw/core/failure_policy.py +151 -0
- package/python/foxclaw/core/gateway.py +85 -0
- package/python/foxclaw/core/governance_audit.py +175 -0
- package/python/foxclaw/core/idempotency.py +289 -0
- package/python/foxclaw/core/injection_guard.py +221 -0
- package/python/foxclaw/core/memory_adapter.py +516 -0
- package/python/foxclaw/core/metrics.py +89 -0
- package/python/foxclaw/core/model_router.py +1097 -0
- package/python/foxclaw/core/paths.py +103 -0
- package/python/foxclaw/core/recovery.py +362 -0
- package/python/foxclaw/core/scheduler.py +144 -0
- package/python/foxclaw/core/secrets.py +134 -0
- package/python/foxclaw/core/sessions.py +157 -0
- package/python/foxclaw/core/settings.py +430 -0
- package/python/foxclaw/core/shared_write.py +198 -0
- package/python/foxclaw/core/steering_queue.py +153 -0
- package/python/foxclaw/core/system_bus.py +84 -0
- package/python/foxclaw/core/thought_chain.py +318 -0
- package/python/foxclaw/core/token_meter.py +235 -0
- package/python/foxclaw/core/tools.py +172 -0
- package/python/foxclaw/core/tracing.py +390 -0
- package/python/foxclaw/core/truth_pipeline.py +114 -0
- package/python/foxclaw/core/vec_recall_adapter.py +503 -0
- package/python/foxclaw/core/verify.py +181 -0
- package/python/foxclaw/core/web_search.py +242 -0
- package/python/foxclaw/finance/__init__.py +15 -0
- package/python/foxclaw/finance/evidence.py +153 -0
- package/python/foxclaw/finance/fx.py +100 -0
- package/python/foxclaw/finance/graph.py +188 -0
- package/python/foxclaw/finance/models.py +150 -0
- package/python/foxclaw/finance/nlparse.py +575 -0
- package/python/foxclaw/finance/policy.py +329 -0
- package/python/foxclaw/finance/reasoner.py +129 -0
- package/python/foxclaw/finance/reasoning.py +340 -0
- package/python/foxclaw/finance/reconcile.py +180 -0
- package/python/foxclaw/finance/runtime.py +527 -0
- package/python/foxclaw/governance/__init__.py +24 -0
- package/python/foxclaw/governance/middleware.py +184 -0
- package/python/foxclaw/harness/__init__.py +17 -0
- package/python/foxclaw/harness/_base.py +72 -0
- package/python/foxclaw/harness/_policy.py +359 -0
- package/python/foxclaw/harness/drift.py +89 -0
- package/python/foxclaw/harness/event_bus.py +31 -0
- package/python/foxclaw/harness/harness.py +446 -0
- package/python/foxclaw/harness/loop_detection.py +152 -0
- package/python/foxclaw/itinerary/__init__.py +10 -0
- package/python/foxclaw/itinerary/amap_client.py +117 -0
- package/python/foxclaw/itinerary/models.py +80 -0
- package/python/foxclaw/itinerary/reasoner.py +101 -0
- package/python/foxclaw/itinerary/reasoning.py +174 -0
- package/python/foxclaw/itinerary/runtime.py +148 -0
- package/python/foxclaw/kernel/__init__.py +13 -0
- package/python/foxclaw/kernel/capabilities.py +73 -0
- package/python/foxclaw/kernel/policy_gate.py +114 -0
- package/python/foxclaw/kernel/process_manager.py +205 -0
- package/python/foxclaw/kernel/scheduler.py +131 -0
- package/python/foxclaw/kernel/sentinel.py +453 -0
- package/python/foxclaw/kernel/sentinel_client.py +246 -0
- package/python/foxclaw/kernel/spine.py +97 -0
- package/python/foxclaw/knowledge/__init__.py +376 -0
- package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
- package/python/foxclaw/learning/__init__.py +12 -0
- package/python/foxclaw/learning/knowledge_graph.py +149 -0
- package/python/foxclaw/learning/models.py +93 -0
- package/python/foxclaw/learning/reasoner.py +70 -0
- package/python/foxclaw/learning/runtime.py +363 -0
- package/python/foxclaw/learning/spaced_repetition.py +96 -0
- package/python/foxclaw/mcp_server.py +189 -0
- package/python/foxclaw/orchestration/__init__.py +184 -0
- package/python/foxclaw/pipeline/__init__.py +23 -0
- package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
- package/python/foxclaw/planning/__init__.py +17 -0
- package/python/foxclaw/planning/llm_planner.py +232 -0
- package/python/foxclaw/planning/planner.py +99 -0
- package/python/foxclaw/search/__init__.py +24 -0
- package/python/foxclaw/search/agent_port.py +194 -0
- package/python/foxclaw/search/evidence.py +113 -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 +73 -0
- package/python/foxclaw/search/models.py +129 -0
- package/python/foxclaw/search/planner.py +44 -0
- package/python/foxclaw/search/retriever.py +39 -0
- package/python/foxclaw/search/runtime.py +134 -0
- package/python/foxclaw/search/strategy.py +44 -0
- package/python/foxclaw/skills/__init__.py +613 -0
- package/python/foxclaw/skills/hooks_spec.py +73 -0
- package/python/foxclaw/skills/lifecycle.py +278 -0
- package/python/foxclaw/skills/markdown_skill.py +174 -0
- package/python/foxclaw/skills/markdown_store.py +39 -0
- package/python/foxclaw/skills/mcp_client.py +190 -0
- package/python/foxclaw/skills/mcp_store.py +43 -0
- package/python/foxclaw/skills/permission.py +180 -0
- package/python/foxclaw/skills/pipeline.py +386 -0
- package/python/foxclaw/skills/policy.py +80 -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 +32 -0
- package/python/frontend/deck/chat_model_picker.js +537 -0
- package/python/frontend/deck/domain.js +296 -0
- package/python/frontend/deck/main.js +1950 -0
- package/python/frontend/deck/markdown.js +48 -0
- package/python/frontend/deck/settings.js +324 -0
- package/python/frontend/deck/state.js +6 -0
- package/python/frontend/deck/util.js +6 -0
- package/python/frontend/deck/visuals.js +212 -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 +1637 -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-BD7VPbMV.js +33 -0
- package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
- package/python/frontend/v3/index.html +15 -0
- package/python/frontend/widget_runtime.js +295 -0
- package/python/frontend/widgets.js +696 -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 +62 -0
- package/python/frontend_src/src/api.js +89 -0
- package/python/frontend_src/src/components/ChatDock.vue +377 -0
- package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
- package/python/frontend_src/src/components/DocViewer.vue +171 -0
- package/python/frontend_src/src/components/Inspector.vue +241 -0
- package/python/frontend_src/src/components/LeftRail.vue +394 -0
- package/python/frontend_src/src/components/MenuPopover.vue +130 -0
- package/python/frontend_src/src/components/MessageStream.vue +401 -0
- package/python/frontend_src/src/components/ModelPicker.vue +355 -0
- package/python/frontend_src/src/components/QuickIngest.vue +118 -0
- package/python/frontend_src/src/components/Stage.vue +226 -0
- package/python/frontend_src/src/components/ToolMenu.vue +144 -0
- package/python/frontend_src/src/components/TopBar.vue +90 -0
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
- package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
- package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
- package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
- package/python/frontend_src/src/lib/fmt.js +48 -0
- package/python/frontend_src/src/lib/md.js +204 -0
- package/python/frontend_src/src/main.js +7 -0
- package/python/frontend_src/src/stores/app.js +106 -0
- package/python/frontend_src/src/stores/chat.js +484 -0
- package/python/frontend_src/src/stores/model.js +104 -0
- package/python/frontend_src/src/stores/panels.js +330 -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 +46 -0
- package/python/pyproject.toml +265 -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 +76 -0
- package/python/tests/run_all.py +263 -0
- package/python/tests/test_agent_lifecycle.py +360 -0
- package/python/tests/test_ahs_p2.py +414 -0
- package/python/tests/test_anthropic_tools.py +206 -0
- package/python/tests/test_api.py +87 -0
- package/python/tests/test_api_auth.py +83 -0
- package/python/tests/test_api_host_guard.py +69 -0
- package/python/tests/test_attachments.py +167 -0
- package/python/tests/test_audit_chain.py +121 -0
- package/python/tests/test_audit_sink.py +136 -0
- package/python/tests/test_browser.py +153 -0
- package/python/tests/test_browser_request_guard.py +184 -0
- package/python/tests/test_capabilities.py +158 -0
- package/python/tests/test_channel_signing.py +192 -0
- package/python/tests/test_channel_trust.py +109 -0
- package/python/tests/test_channels.py +184 -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 +65 -0
- package/python/tests/test_confirm_nonce.py +197 -0
- package/python/tests/test_context_compaction_wiring.py +130 -0
- package/python/tests/test_dispatch.py +95 -0
- package/python/tests/test_domain_evals.py +403 -0
- package/python/tests/test_domain_import.py +78 -0
- package/python/tests/test_domain_reasoners.py +148 -0
- package/python/tests/test_e2e.py +178 -0
- package/python/tests/test_emb_cache.py +107 -0
- package/python/tests/test_embedding_singleton.py +89 -0
- package/python/tests/test_envfile.py +83 -0
- package/python/tests/test_evals_smoke.py +114 -0
- package/python/tests/test_event_spine.py +182 -0
- package/python/tests/test_evidence_semantic.py +101 -0
- package/python/tests/test_evidence_sse.py +97 -0
- package/python/tests/test_file_ingest.py +146 -0
- package/python/tests/test_finance.py +153 -0
- package/python/tests/test_finance_ledger.py +252 -0
- package/python/tests/test_finance_persist.py +178 -0
- package/python/tests/test_gateway.py +144 -0
- package/python/tests/test_governance.py +468 -0
- package/python/tests/test_governance_audit.py +359 -0
- package/python/tests/test_governance_middleware.py +143 -0
- package/python/tests/test_hkc.py +286 -0
- package/python/tests/test_hkc_index_build_manager.py +339 -0
- package/python/tests/test_hkc_manifest_reuse.py +317 -0
- package/python/tests/test_hkc_search_event_sync.py +533 -0
- package/python/tests/test_import_path_guard.py +161 -0
- package/python/tests/test_injection_guard.py +211 -0
- package/python/tests/test_integration.py +141 -0
- package/python/tests/test_itinerary.py +103 -0
- package/python/tests/test_kee_semantic.py +89 -0
- package/python/tests/test_kernel.py +222 -0
- package/python/tests/test_learning.py +110 -0
- package/python/tests/test_llm_admin.py +160 -0
- package/python/tests/test_llm_planner.py +366 -0
- package/python/tests/test_markdown_skills.py +73 -0
- package/python/tests/test_markdown_skills_openclaw.py +71 -0
- package/python/tests/test_mcp_connectors.py +109 -0
- package/python/tests/test_mcp_guardian_discovery.py +91 -0
- package/python/tests/test_mcp_no_memory.py +43 -0
- package/python/tests/test_mcp_policy_gate.py +77 -0
- package/python/tests/test_mcp_server.py +95 -0
- package/python/tests/test_memory_recall.py +95 -0
- package/python/tests/test_metrics.py +66 -0
- package/python/tests/test_multi_chain.py +102 -0
- package/python/tests/test_multimodal.py +135 -0
- package/python/tests/test_negotiation.py +146 -0
- package/python/tests/test_orchestration.py +119 -0
- package/python/tests/test_orchestration_decoupled.py +109 -0
- package/python/tests/test_p3_p5_hardening.py +338 -0
- package/python/tests/test_parallel_dispatch.py +215 -0
- package/python/tests/test_pipeline.py +533 -0
- package/python/tests/test_plan_visible.py +86 -0
- package/python/tests/test_planner.py +95 -0
- package/python/tests/test_policy_gate.py +109 -0
- package/python/tests/test_reasoner.py +185 -0
- package/python/tests/test_release_check.py +263 -0
- package/python/tests/test_resume_checkpoint.py +156 -0
- package/python/tests/test_routing.py +95 -0
- package/python/tests/test_scheduler.py +141 -0
- package/python/tests/test_search.py +204 -0
- package/python/tests/test_secrets.py +159 -0
- package/python/tests/test_semantic.py +78 -0
- package/python/tests/test_sessions.py +91 -0
- package/python/tests/test_settings.py +281 -0
- package/python/tests/test_shadow_learning.py +330 -0
- package/python/tests/test_shared_write.py +207 -0
- package/python/tests/test_skill_taint_gate.py +154 -0
- package/python/tests/test_skills.py +133 -0
- package/python/tests/test_stats_and_events.py +165 -0
- package/python/tests/test_streaming.py +60 -0
- package/python/tests/test_supervisor_pipeline.py +177 -0
- package/python/tests/test_supervisor_triage.py +108 -0
- package/python/tests/test_taint.py +117 -0
- package/python/tests/test_task_stream.py +85 -0
- package/python/tests/test_thinking_sse.py +67 -0
- package/python/tests/test_thinking_stream.py +76 -0
- package/python/tests/test_tool_center.py +129 -0
- package/python/tests/test_tool_parallel.py +113 -0
- package/python/tests/test_tool_params.py +103 -0
- package/python/tests/test_tool_use.py +242 -0
- package/python/tests/test_toplevel_concurrency.py +117 -0
- package/python/tests/test_tracing.py +290 -0
- package/python/tests/test_truth_crystallizer.py +127 -0
- package/python/tests/test_truth_flow.py +93 -0
- package/python/tests/test_url_policy.py +53 -0
- package/python/tests/test_verify_evidence.py +225 -0
- package/python/tests/test_version_sync.py +71 -0
- package/python/tests/test_web_trigger.py +99 -0
- package/python/tools/check_faiss_type.py +31 -0
- package/python/tools/check_log.ps1 +46 -0
- package/python/tools/ivf_status.py +72 -0
- package/python/tools/mypy_baseline.txt +5 -0
- package/python/tools/mypy_ratchet.py +97 -0
- package/python/tools/pack.py +178 -0
- package/python/tools/release_check.py +349 -0
- package/python/tools/restart_foxclaw.ps1 +74 -0
- package/python/tools/shared_write_audit.py +204 -0
- package/python/tools/size_baseline.txt +28 -0
- package/python/tools/size_check.py +102 -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,190 @@
|
|
|
1
|
+
"""MCP 连接器(HTTP Streamable/SSE 远程)。mcp SDK 是 async,本模块用后台事件循环线程
|
|
2
|
+
把它桥成同步,供 SkillAdapter(sync,跨请求/后台线程)调用。按需连接:每次开→用→关。
|
|
3
|
+
仅远程传输,无本地代码执行。"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
import asyncio
|
|
6
|
+
import threading
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
|
|
9
|
+
from mcp import ClientSession
|
|
10
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
11
|
+
from mcp.client.sse import sse_client
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ── async→sync 桥:单例后台 loop 线程 ──────────────────────────
|
|
15
|
+
class _Bridge:
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._loop = asyncio.new_event_loop()
|
|
18
|
+
t = threading.Thread(target=self._run, daemon=True, name="mcp-loop")
|
|
19
|
+
t.start()
|
|
20
|
+
|
|
21
|
+
def _run(self):
|
|
22
|
+
asyncio.set_event_loop(self._loop)
|
|
23
|
+
self._loop.run_forever()
|
|
24
|
+
|
|
25
|
+
def run(self, coro, timeout: float = 35):
|
|
26
|
+
fut = asyncio.run_coroutine_threadsafe(coro, self._loop)
|
|
27
|
+
try:
|
|
28
|
+
return fut.result(timeout=timeout)
|
|
29
|
+
except BaseException:
|
|
30
|
+
fut.cancel() # 超时/异常必须取消协程,否则挂起的连接会一直留在 loop 里泄漏
|
|
31
|
+
raise
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_bridge = None
|
|
35
|
+
_bridge_lock = threading.Lock()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _get_bridge() -> _Bridge:
|
|
39
|
+
global _bridge
|
|
40
|
+
with _bridge_lock:
|
|
41
|
+
if _bridge is None:
|
|
42
|
+
_bridge = _Bridge()
|
|
43
|
+
return _bridge
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ── SSRF 防线:连接(含重定向的每一跳)都校验目标 host 不是内网/本地/元数据 ──────
|
|
47
|
+
async def _ssrf_request_hook(request):
|
|
48
|
+
"""httpx 请求钩子:对每个即将发出的请求(初始 + 重定向 follow-up)校验 host。
|
|
49
|
+
堵 redirect-SSRF + DNS 重绑定 + 配置漂移(恶意 server 把你导去内网/云元数据)。"""
|
|
50
|
+
from .review import _is_internal_host
|
|
51
|
+
host = (request.url.host or "").lower()
|
|
52
|
+
if await asyncio.to_thread(_is_internal_host, host):
|
|
53
|
+
raise RuntimeError(f"MCP 连接被拒:目标解析到内网/本地/元数据地址({host})")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _safe_httpx_factory(headers=None, timeout=None, auth=None):
|
|
57
|
+
import httpx
|
|
58
|
+
return httpx.AsyncClient(
|
|
59
|
+
headers=headers,
|
|
60
|
+
timeout=timeout if timeout is not None else httpx.Timeout(30.0),
|
|
61
|
+
auth=auth,
|
|
62
|
+
follow_redirects=True, # 允许(合法同源尾斜杠跳转),但每跳过钩子
|
|
63
|
+
event_hooks={"request": [_ssrf_request_hook]},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ── 底层 async 助手(测试可 monkeypatch 这两个) ─────────────────
|
|
68
|
+
@asynccontextmanager
|
|
69
|
+
async def _connect(url, headers, transport):
|
|
70
|
+
if transport == "sse":
|
|
71
|
+
async with sse_client(url, headers=headers, httpx_client_factory=_safe_httpx_factory) as (read, write):
|
|
72
|
+
async with ClientSession(read, write) as s:
|
|
73
|
+
await s.initialize()
|
|
74
|
+
yield s
|
|
75
|
+
else:
|
|
76
|
+
async with streamablehttp_client(url, headers=headers, httpx_client_factory=_safe_httpx_factory) as (read, write, _):
|
|
77
|
+
async with ClientSession(read, write) as s:
|
|
78
|
+
await s.initialize()
|
|
79
|
+
yield s
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def _amcp_list_tools(url, headers, transport):
|
|
83
|
+
async with _connect(url, headers, transport) as s:
|
|
84
|
+
return list((await s.list_tools()).tools)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def _amcp_call_tool(url, headers, transport, name, args):
|
|
88
|
+
async with _connect(url, headers, transport) as s:
|
|
89
|
+
r = await s.call_tool(name, args or {})
|
|
90
|
+
text = "".join(getattr(c, "text", "") for c in (r.content or []))
|
|
91
|
+
return {"isError": bool(getattr(r, "isError", False)), "content": text,
|
|
92
|
+
"structured": getattr(r, "structuredContent", None)}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _norm(cfg: dict) -> dict:
|
|
96
|
+
return {"id": cfg["id"], "name": cfg.get("name") or cfg["id"], "url": cfg["url"],
|
|
97
|
+
"headers": dict(cfg.get("headers") or {}),
|
|
98
|
+
"transport": cfg.get("transport") or "streamable_http"}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ── 工具 → SkillSpec(fail-closed) ─────────────────────────────
|
|
102
|
+
def _get(tool, key, default=None):
|
|
103
|
+
"""兼容 dict(守门人序列化后) 与 mcp.types.Tool 对象(直连回退路径)。"""
|
|
104
|
+
return tool.get(key, default) if isinstance(tool, dict) else getattr(tool, key, default)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def tool_to_spec(server_id: str, tool):
|
|
108
|
+
from . import SkillSpec
|
|
109
|
+
ann = _get(tool, "annotations", None)
|
|
110
|
+
if isinstance(ann, dict):
|
|
111
|
+
ro = ann.get("readOnlyHint")
|
|
112
|
+
de = ann.get("destructiveHint")
|
|
113
|
+
else:
|
|
114
|
+
ro = getattr(ann, "readOnlyHint", None) if ann else None
|
|
115
|
+
de = getattr(ann, "destructiveHint", None) if ann else None
|
|
116
|
+
if ro is True:
|
|
117
|
+
se, destr = "read", False
|
|
118
|
+
elif de is True:
|
|
119
|
+
se, destr = "write", True
|
|
120
|
+
else:
|
|
121
|
+
se, destr = "write", False # fail-closed:无注解按有副作用
|
|
122
|
+
tname = _get(tool, "name", "") or ""
|
|
123
|
+
return SkillSpec(
|
|
124
|
+
id=f"mcp__{server_id}__{tname}", name=tname,
|
|
125
|
+
description=_get(tool, "description", "") or "", kind="mcp",
|
|
126
|
+
mcp_server=server_id, mcp_tool=tname, side_effect=se, destructive=destr,
|
|
127
|
+
external=True, params=_get(tool, "inputSchema", {}) or {},
|
|
128
|
+
triggers=[tname], source="user",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ── 供 SkillAdapter 的客户端代理 ──────────────────────────────
|
|
133
|
+
class _ClientProxy:
|
|
134
|
+
def __init__(self, mgr, sid):
|
|
135
|
+
self._mgr, self._sid = mgr, sid
|
|
136
|
+
|
|
137
|
+
def call_tool(self, tool, args):
|
|
138
|
+
cfg = self._mgr._servers.get(self._sid)
|
|
139
|
+
if not cfg:
|
|
140
|
+
raise RuntimeError(f"MCP server {self._sid} 未登记")
|
|
141
|
+
from foxclaw.core.tracing import span as _span, current_traceparent
|
|
142
|
+
with _span("mcp.call_tool", server=self._sid, tool=str(tool)):
|
|
143
|
+
# 跨进程传播:带上 W3C traceparent,对端(外部 MCP server)若支持即可接上同一条链。
|
|
144
|
+
# 这是 Foxclaw 唯一真实的跨进程边界,也是"分布式追踪"在本系统里唯一有意义之处。
|
|
145
|
+
headers = dict(cfg["headers"] or {})
|
|
146
|
+
tp = current_traceparent()
|
|
147
|
+
if tp:
|
|
148
|
+
headers["traceparent"] = tp
|
|
149
|
+
out = _get_bridge().run(_amcp_call_tool(cfg["url"], headers, cfg["transport"], tool, args or {}))
|
|
150
|
+
if out.get("isError"):
|
|
151
|
+
raise RuntimeError(out.get("content") or "MCP 工具返回错误")
|
|
152
|
+
return out
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class McpManager:
|
|
156
|
+
"""服务器配置 + 连接。实现 .get(id) 供 SkillAdapter.mcp_clients 用。"""
|
|
157
|
+
def __init__(self):
|
|
158
|
+
self._servers: dict = {}
|
|
159
|
+
self._lock = threading.RLock()
|
|
160
|
+
|
|
161
|
+
def restore(self, cfg: dict):
|
|
162
|
+
with self._lock:
|
|
163
|
+
self._servers[cfg["id"]] = _norm(cfg)
|
|
164
|
+
|
|
165
|
+
def add_server(self, cfg: dict):
|
|
166
|
+
c = _norm(cfg)
|
|
167
|
+
tools = _get_bridge().run(_amcp_list_tools(c["url"], c["headers"], c["transport"]))
|
|
168
|
+
with self._lock:
|
|
169
|
+
self._servers[c["id"]] = c
|
|
170
|
+
return tools
|
|
171
|
+
|
|
172
|
+
def list_tools(self, sid: str):
|
|
173
|
+
with self._lock:
|
|
174
|
+
c = self._servers.get(sid)
|
|
175
|
+
if not c:
|
|
176
|
+
raise KeyError(sid)
|
|
177
|
+
return _get_bridge().run(_amcp_list_tools(c["url"], c["headers"], c["transport"]))
|
|
178
|
+
|
|
179
|
+
def remove_server(self, sid: str) -> bool:
|
|
180
|
+
with self._lock:
|
|
181
|
+
return self._servers.pop(sid, None) is not None
|
|
182
|
+
|
|
183
|
+
def servers(self) -> list:
|
|
184
|
+
with self._lock:
|
|
185
|
+
return [dict(c) for c in self._servers.values()]
|
|
186
|
+
|
|
187
|
+
def get(self, sid: str):
|
|
188
|
+
with self._lock:
|
|
189
|
+
has = sid in self._servers
|
|
190
|
+
return _ClientProxy(self, sid) if has else None
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""MCP server 配置持久化。文件 <data_dir>/mcp_servers.json。
|
|
2
|
+
|
|
3
|
+
【P-Muse·第4步·安全边界】守门人模式(FOXCLAW_GUARDIAN=1)下,主进程只把
|
|
4
|
+
非敏感元数据(id/name/url/transport)写进这里,headers 凭证只在守门人进程。
|
|
5
|
+
回退路径(FOXCLAW_GUARDIAN=0,进程内直连)没有守门人保护,headers 会连同配置
|
|
6
|
+
一起落盘——save 检测到 headers 非空时会打印告警,避免"静默落盘凭证"。
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
import threading
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class McpStore:
|
|
16
|
+
def __init__(self, data_dir):
|
|
17
|
+
self._path = Path(data_dir) / "mcp_servers.json"
|
|
18
|
+
self._lock = threading.Lock()
|
|
19
|
+
|
|
20
|
+
def load(self) -> list:
|
|
21
|
+
if not self._path.exists():
|
|
22
|
+
return []
|
|
23
|
+
try:
|
|
24
|
+
return json.loads(self._path.read_text(encoding="utf-8")).get("servers", [])
|
|
25
|
+
except Exception:
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
def save(self, servers: list):
|
|
29
|
+
with self._lock:
|
|
30
|
+
try:
|
|
31
|
+
# 【P-Muse·第4步】守门人模式下主进程只存元数据(headers 为空),正常不触发。
|
|
32
|
+
# 一旦有 headers 非空,说明走的是回退路径(或误传凭证)——明文凭证即将落盘,
|
|
33
|
+
# 主动告警,消除"凭证静默落盘、重启后才暴露"的隐患。
|
|
34
|
+
if any(dict(s.get("headers") or {}) for s in servers):
|
|
35
|
+
print("[McpStore] ⚠ 检测到 headers 凭证,将明文落盘到 "
|
|
36
|
+
f"{self._path.name}(当前为回退路径/未启用守门人)。",
|
|
37
|
+
file=sys.stderr, flush=True)
|
|
38
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
tmp = self._path.with_suffix(".tmp")
|
|
40
|
+
tmp.write_text(json.dumps({"servers": servers}, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
41
|
+
tmp.replace(self._path)
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
工具权限管线 (Permission Pipeline) —— HHS 标准 skill-guard 的代码落地。
|
|
3
|
+
|
|
4
|
+
把抽象的「四阶段权限管线」实现为可运行代码:
|
|
5
|
+
Stage 1 validate —— 输入合法性(参数 schema)
|
|
6
|
+
Stage 2 rule-match —— deny > ask > allow 优先级铁律
|
|
7
|
+
Stage 3 capability —— read_only / destructive / external,Fail-closed
|
|
8
|
+
Stage 4 confirm —— 有副作用/危险操作需确认
|
|
9
|
+
|
|
10
|
+
对应 docs/internal/HARNESS_STANDARD.md 的 A 类工具权限护栏。
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Literal, Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Decision = Literal["allow", "ask", "deny"]
|
|
18
|
+
|
|
19
|
+
# 【P5-1】ask 的结构化原因码。此前 ask + requires_confirmation=True 只有一个语义:
|
|
20
|
+
# 「用户确认后就能执行」。但 Stage 1 的「缺参数」不是「需确认」——用户确认一百次
|
|
21
|
+
# 也补不上参数,必须先补数据。reason_code 让调用方(前端/编排层/审计)能区分:
|
|
22
|
+
MISSING_PARAMS = "MISSING_PARAMS" # 缺必填参数,不可通过确认绕过(先补数据)
|
|
23
|
+
DESTRUCTIVE_CONFIRM = "DESTRUCTIVE_CONFIRM" # 不可逆操作,需确认
|
|
24
|
+
SIDE_EFFECT_CONFIRM = "SIDE_EFFECT_CONFIRM" # write/send/pay 副作用,需确认
|
|
25
|
+
ASK_RULE = "ASK_RULE" # 命中宿主 ask 规则
|
|
26
|
+
EXTERNAL_CONFIRM = "EXTERNAL_CONFIRM" # 外部调用,需确认
|
|
27
|
+
TAINT_CONFIRM = "TAINT_CONFIRM" # 会话已取回敏感数据,外发/副作用须二次确认
|
|
28
|
+
# deny / allow 侧文稿未定义 code,保守留 "":deny 的铁律语义(deny 不可覆盖)已由
|
|
29
|
+
# decision + stage 充分表达,不擅自扩大枚举面。
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class PermissionRules:
|
|
34
|
+
"""宿主提供的规则集。deny 优先级最高且不可覆盖。"""
|
|
35
|
+
deny: list = field(default_factory=list) # skill id / glob,命中即拒绝
|
|
36
|
+
ask: list = field(default_factory=list) # 命中需确认
|
|
37
|
+
allow: list = field(default_factory=list) # 命中放行
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _match(patterns: list, skill_id: str) -> bool:
|
|
41
|
+
import fnmatch
|
|
42
|
+
return any(fnmatch.fnmatch(skill_id, p) for p in patterns)
|
|
43
|
+
|
|
44
|
+
def hits_deny(self, sid: str) -> bool:
|
|
45
|
+
return self._match(self.deny, sid)
|
|
46
|
+
|
|
47
|
+
def hits_ask(self, sid: str) -> bool:
|
|
48
|
+
return self._match(self.ask, sid)
|
|
49
|
+
|
|
50
|
+
def hits_allow(self, sid: str) -> bool:
|
|
51
|
+
return self._match(self.allow, sid)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class PermissionDecision:
|
|
56
|
+
decision: Decision
|
|
57
|
+
stage: int # 哪个阶段做出的决定(1-4)
|
|
58
|
+
reason: str = ""
|
|
59
|
+
trust_downgrade: bool = False # 结果是否须按「不可信」处理(MCP/远程)
|
|
60
|
+
requires_confirmation: bool = False
|
|
61
|
+
# 【P5-1】ask 的结构化原因码(见模块顶部常量);非 ask 或文稿未定义的情形为 ""
|
|
62
|
+
reason_code: str = ""
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict:
|
|
65
|
+
return {
|
|
66
|
+
"decision": self.decision, "stage": self.stage, "reason": self.reason,
|
|
67
|
+
"trust_downgrade": self.trust_downgrade,
|
|
68
|
+
"requires_confirmation": self.requires_confirmation,
|
|
69
|
+
"reason_code": self.reason_code,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def evaluate(skill, args: dict, rules: Optional[PermissionRules] = None,
|
|
74
|
+
confirmed: bool = False, mode: str = "strict",
|
|
75
|
+
taint_level: int = 0, taint_threshold: int = 0) -> PermissionDecision:
|
|
76
|
+
"""四阶段权限管线。返回 allow / ask / deny。
|
|
77
|
+
|
|
78
|
+
skill: SkillSpec(需有 id / side_effect / kind,及可选 destructive/external/read_only)
|
|
79
|
+
|
|
80
|
+
【P-Muse·污点门槛】taint_level = 本会话已取回数据的最高敏感度(见
|
|
81
|
+
core.injection_guard.taint_level);taint_threshold = 触发二次确认的阈值。
|
|
82
|
+
两者默认 0 = 关闭,既有调用方行为逐字不变。仅当「污点达标 且 动作为外发/副作用」时,
|
|
83
|
+
在 Stage 3 强制升级为 ask(TAINT_CONFIRM)——对齐 Muse「碰过敏感数据 → 动态提门槛」。
|
|
84
|
+
"""
|
|
85
|
+
rules = rules or PermissionRules()
|
|
86
|
+
sid = skill.id
|
|
87
|
+
|
|
88
|
+
# ---------- Stage 1: validate(输入合法性)----------
|
|
89
|
+
# 参数 schema 基本校验:声明的必填参数是否齐全
|
|
90
|
+
# 兼容两种 params 格式:{"k": "str"}(简单)或 {"k": {"required": True}}(详细)
|
|
91
|
+
params = getattr(skill, "params", {}) or {}
|
|
92
|
+
missing = []
|
|
93
|
+
for k, spec in params.items():
|
|
94
|
+
required = isinstance(spec, dict) and spec.get("required")
|
|
95
|
+
if required and k not in (args or {}):
|
|
96
|
+
missing.append(k)
|
|
97
|
+
if missing:
|
|
98
|
+
# 校验失败 → 优雅降级为 ask(不是安全拒绝,是数据不全)
|
|
99
|
+
# 【P5-1】MISSING_PARAMS:确认无法绕过缺参数,调用方须先补数据
|
|
100
|
+
return PermissionDecision("ask", 1,
|
|
101
|
+
reason=f"缺少必填参数: {', '.join(missing)}",
|
|
102
|
+
requires_confirmation=True, reason_code=MISSING_PARAMS)
|
|
103
|
+
|
|
104
|
+
# ---------- Stage 2: rule-match(deny > ask > allow 铁律)----------
|
|
105
|
+
if rules.hits_deny(sid):
|
|
106
|
+
return PermissionDecision("deny", 2, reason=f"命中 deny 规则(不可覆盖): {sid}")
|
|
107
|
+
ask_by_rule = rules.hits_ask(sid)
|
|
108
|
+
allow_by_rule = rules.hits_allow(sid)
|
|
109
|
+
# deny 不命中时,ask 优先于 allow
|
|
110
|
+
|
|
111
|
+
# ---------- Stage 3: capability(能力检查,Fail-closed)----------
|
|
112
|
+
side_effect = getattr(skill, "side_effect", "write") # 未声明 → 按有副作用(fail-closed)
|
|
113
|
+
destructive = getattr(skill, "destructive", False)
|
|
114
|
+
external = getattr(skill, "external", False)
|
|
115
|
+
# read_only 字段默认 None → 按 side_effect 推断(read 即只读);显式 True/False 则尊重
|
|
116
|
+
_ro = getattr(skill, "read_only", None)
|
|
117
|
+
read_only = (side_effect == "read") if _ro is None else _ro
|
|
118
|
+
# 单一真源(P6 修复):信任分级由 SkillSpec.trust_level() 唯一裁定。此前 permission 另存一份
|
|
119
|
+
# TRUST_LEVEL dict,与 trust_level() 重复且含死 key(bundled/remote —— kind 永不取此二值);对 3 个
|
|
120
|
+
# 合法 SkillKind(local/web_api/mcp)二者 downgrade 行为完全一致,故收敛为单一真源、行为不变。
|
|
121
|
+
# duck-typed skill 无 trust_level 方法时 fail-closed 当低信任。
|
|
122
|
+
# 【P5-0】trust_level() 抛异常 → 当低信任(fail-closed),不让权限管线崩。
|
|
123
|
+
# 第三方 skill 的 trust_level() 若有 bug(如 AttributeError),原实现会让异常直接冒泡、
|
|
124
|
+
# evaluate 崩溃,用户看到「调用失败」而非「权限拒绝」——这是 fail-open。改为兜住异常、
|
|
125
|
+
# 按最低信任(low)处理:无法判定信任等级时,宁可保守。
|
|
126
|
+
_tl = getattr(skill, "trust_level", None)
|
|
127
|
+
try:
|
|
128
|
+
trust = _tl() if callable(_tl) else "low"
|
|
129
|
+
except Exception:
|
|
130
|
+
trust = "low"
|
|
131
|
+
downgrade = trust == "low" # 低信任(mcp/远程)结果须按不可信处理
|
|
132
|
+
|
|
133
|
+
# readonly 模式:非只读一律拒绝
|
|
134
|
+
if mode == "readonly" and not read_only:
|
|
135
|
+
return PermissionDecision("deny", 3,
|
|
136
|
+
reason="readonly 模式下禁止非只读操作", trust_downgrade=downgrade)
|
|
137
|
+
|
|
138
|
+
# destructive 永不自动放行(即使有 allow 规则也要确认)
|
|
139
|
+
if destructive and not confirmed:
|
|
140
|
+
return PermissionDecision("ask", 3,
|
|
141
|
+
reason="不可逆(destructive)操作,必须用户确认",
|
|
142
|
+
trust_downgrade=downgrade, requires_confirmation=True,
|
|
143
|
+
reason_code=DESTRUCTIVE_CONFIRM)
|
|
144
|
+
|
|
145
|
+
# Stage 3.5 污点门槛(Muse 式「碰过敏感数据 → 外发动作追加确认」):
|
|
146
|
+
# 会话已取回 ≥threshold 的用户数据,再外发/副作用 → 即使工具本身可信也强制确认,
|
|
147
|
+
# 防「模型把用户隐私塞进 send/write/external」外泄。置于 trusted 之前:
|
|
148
|
+
# 污点是结构性约束,trusted 只信任工具本身,不豁免「携带敏感数据外发」。
|
|
149
|
+
# [P-Muse·确认放行] `not confirmed`:此闸防的是**静默**外发,用户显式确认后必须放行,
|
|
150
|
+
# 否则「问一次 → 确认 → 再问一次」永远出不去(与上方 destructive 的判定口径一致)。
|
|
151
|
+
if taint_threshold and taint_level >= taint_threshold and not confirmed:
|
|
152
|
+
if side_effect in ("write", "send", "pay") or external:
|
|
153
|
+
return PermissionDecision("ask", 3,
|
|
154
|
+
reason="会话已取回用户敏感数据,外发/副作用动作需二次确认(污点防护)",
|
|
155
|
+
trust_downgrade=downgrade, requires_confirmation=True,
|
|
156
|
+
reason_code=TAINT_CONFIRM)
|
|
157
|
+
|
|
158
|
+
# trusted 模式:跳过 ask,只保留 deny(上面已查)+ destructive(上面已查)
|
|
159
|
+
if mode == "trusted":
|
|
160
|
+
return PermissionDecision("allow", 3,
|
|
161
|
+
reason="trusted 模式放行", trust_downgrade=downgrade)
|
|
162
|
+
|
|
163
|
+
# ---------- Stage 4: confirm(确认)----------
|
|
164
|
+
needs_confirm = (
|
|
165
|
+
ask_by_rule or external or side_effect in ("write", "send", "pay")
|
|
166
|
+
)
|
|
167
|
+
if needs_confirm and not confirmed:
|
|
168
|
+
why = ("命中 ask 规则" if ask_by_rule else
|
|
169
|
+
"外部调用" if external else f"{side_effect} 副作用")
|
|
170
|
+
# 【P5-1】三个分支各自的结构化原因码
|
|
171
|
+
code = (ASK_RULE if ask_by_rule else
|
|
172
|
+
EXTERNAL_CONFIRM if external else SIDE_EFFECT_CONFIRM)
|
|
173
|
+
return PermissionDecision("ask", 4,
|
|
174
|
+
reason=f"需确认: {why}", trust_downgrade=downgrade,
|
|
175
|
+
requires_confirmation=True, reason_code=code)
|
|
176
|
+
|
|
177
|
+
# 放行:显式 allow 规则,或只读无副作用,或已确认
|
|
178
|
+
reason = ("命中 allow 规则" if allow_by_rule else
|
|
179
|
+
"只读无副作用" if read_only else "已确认")
|
|
180
|
+
return PermissionDecision("allow", 4, reason=reason, trust_downgrade=downgrade)
|