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,417 @@
|
|
|
1
|
+
"""AHS 执行链:技能安装/管理、MCP 接入、Markdown 指令技能、策略封杀、运行、断路器、钩子审计。
|
|
2
|
+
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
|
|
4
|
+
晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import uuid
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from fastapi import HTTPException
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
import foxclaw.api as _api
|
|
15
|
+
from .. import app
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ============ AHS 执行链(Agent Harness Skills 标准)============
|
|
19
|
+
|
|
20
|
+
class AHSRunPayload(BaseModel):
|
|
21
|
+
task: str
|
|
22
|
+
confirmed: bool = False
|
|
23
|
+
# 【P2-2】断点续跑:传上次响应里的 pending_confirmation(确认后连 confirmed=True 一起传)。
|
|
24
|
+
# Optional:pydantic v2 下 dict=None 字段收到显式 null 会 422,必须 Optional。
|
|
25
|
+
resume_state: Optional[dict] = None
|
|
26
|
+
# 【P1】关键操作(send/pay/destructive)二次确认码:上次响应 pending_confirmation.confirm_nonce。
|
|
27
|
+
# 单次使用、绑定 skill+参数、默认 5 分钟过期。也可直接放在 resume_state 里,二者等价。
|
|
28
|
+
confirm_nonce: str = ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.get("/api/ahs/skills")
|
|
32
|
+
async def ahs_list_skills():
|
|
33
|
+
"""列出注册的 skill(含信任分级 trust + 封杀标记 blocked)。"""
|
|
34
|
+
pol = _api._os_instance.skill_policy
|
|
35
|
+
out = _api._os_instance.skill_registry.catalog()
|
|
36
|
+
for s in out:
|
|
37
|
+
s["blocked"] = pol.is_blocked(s.get("id", ""))
|
|
38
|
+
return {"skills": out}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PolicyDenyPayload(BaseModel):
|
|
42
|
+
id: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.get("/api/ahs/policy")
|
|
46
|
+
async def ahs_policy_get():
|
|
47
|
+
"""全局封杀策略快照(deny/allow)。"""
|
|
48
|
+
return _api._os_instance.skill_policy.snapshot()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.post("/api/ahs/policy/deny")
|
|
52
|
+
async def ahs_policy_deny(p: PolicyDenyPayload):
|
|
53
|
+
"""封杀一个技能 id(deny 压倒 enable,立即对所有执行/注入生效)。"""
|
|
54
|
+
_api._os_instance.skill_policy.deny(p.id)
|
|
55
|
+
return {"ok": True, "policy": _api._os_instance.skill_policy.snapshot()}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@app.delete("/api/ahs/policy/deny/{skill_id}")
|
|
59
|
+
async def ahs_policy_undeny(skill_id: str):
|
|
60
|
+
"""解封。E1:被 policy 层(管理员 FOXCLAW_POLICY_DENY / policy.json)锁定的技能运行时无法解封,
|
|
61
|
+
诚实返回 ok=False + 原因(不再假报成功);其余情况与改前一致(ok=True)。"""
|
|
62
|
+
_api._os_instance.skill_policy.undeny(skill_id)
|
|
63
|
+
snap = _api._os_instance.skill_policy.snapshot()
|
|
64
|
+
if skill_id in snap.get("policy_deny", []):
|
|
65
|
+
return {"ok": False,
|
|
66
|
+
"reason": f"「{skill_id}」被管理员策略(policy 层)锁定,运行时无法解封", "policy": snap}
|
|
67
|
+
return {"ok": True, "policy": snap}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SkillInstallPayload(BaseModel):
|
|
71
|
+
id: str
|
|
72
|
+
name: str
|
|
73
|
+
description: str = ""
|
|
74
|
+
kind: str # 只允许 "web_api" | "mcp"
|
|
75
|
+
triggers: list = []
|
|
76
|
+
side_effect: str = "write" # read/write/send/pay;不传按有副作用(会要求确认)
|
|
77
|
+
destructive: bool = False
|
|
78
|
+
external: bool = True
|
|
79
|
+
params: dict = {}
|
|
80
|
+
# web_api
|
|
81
|
+
endpoint: str = ""
|
|
82
|
+
http_method: str = "GET"
|
|
83
|
+
http_headers: dict = {}
|
|
84
|
+
param_location: str = "auto"
|
|
85
|
+
allow_local: bool = False
|
|
86
|
+
timeout: float = 10.0
|
|
87
|
+
# mcp
|
|
88
|
+
mcp_server: str = ""
|
|
89
|
+
mcp_tool: str = ""
|
|
90
|
+
acknowledged: bool = False # warn 级风险,用户确认后置 true
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class SkillPatchPayload(BaseModel):
|
|
94
|
+
enabled: bool
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class SkillTestPayload(BaseModel):
|
|
98
|
+
args: dict = {}
|
|
99
|
+
confirmed: bool = False
|
|
100
|
+
mode: str = "strict"
|
|
101
|
+
# 关键操作(send/pay/destructive)的二次确认码:上次响应里的 confirm_nonce。
|
|
102
|
+
# 宿主注入了 confirms(见 foxclaw/__init__.py)时,confirmed=True 只有配有效 nonce
|
|
103
|
+
# 才放行 —— 原先这个端点收不到 nonce,界面上点「确认」永远退回 needs_confirmation。
|
|
104
|
+
confirm_nonce: str = ""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_SKILL_KINDS = {"web_api", "mcp"}
|
|
108
|
+
_SIDE_EFFECTS = {"read", "write", "send", "pay"}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _validate_install(p: "SkillInstallPayload"):
|
|
112
|
+
if p.kind not in _SKILL_KINDS:
|
|
113
|
+
raise HTTPException(400, "kind 只能是 web_api 或 mcp(不支持从界面安装本地代码技能)")
|
|
114
|
+
if p.side_effect not in _SIDE_EFFECTS:
|
|
115
|
+
raise HTTPException(400, "side_effect 非法")
|
|
116
|
+
if p.kind == "web_api":
|
|
117
|
+
if not (p.endpoint.startswith("http://") or p.endpoint.startswith("https://")):
|
|
118
|
+
raise HTTPException(400, "web_api 需合法 http(s) endpoint")
|
|
119
|
+
if p.kind == "mcp" and not (p.mcp_server and p.mcp_tool):
|
|
120
|
+
raise HTTPException(400, "mcp 需同时提供 mcp_server 与 mcp_tool")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.post("/api/ahs/skills")
|
|
124
|
+
async def ahs_install_skill(p: SkillInstallPayload):
|
|
125
|
+
"""安装/注册一个 web_api|mcp 技能(过安装审核门 + 持久化)。同 id 覆盖更新。"""
|
|
126
|
+
_validate_install(p)
|
|
127
|
+
from foxclaw.skills import SkillSpec
|
|
128
|
+
from foxclaw.skills.review import review_install
|
|
129
|
+
reg = _api._os_instance.skill_registry
|
|
130
|
+
_existing = reg.get(p.id)
|
|
131
|
+
if _existing is not None and _existing.source == "builtin":
|
|
132
|
+
raise HTTPException(409, "该 id 属于内置技能,不能覆盖")
|
|
133
|
+
existed = _existing is not None
|
|
134
|
+
spec = SkillSpec(
|
|
135
|
+
id=p.id, name=p.name, description=p.description, kind=p.kind,
|
|
136
|
+
triggers=list(p.triggers), side_effect=p.side_effect,
|
|
137
|
+
destructive=p.destructive, external=p.external, params=dict(p.params),
|
|
138
|
+
endpoint=p.endpoint, http_method=p.http_method, http_headers=dict(p.http_headers),
|
|
139
|
+
param_location=p.param_location, allow_local=p.allow_local, timeout=p.timeout,
|
|
140
|
+
mcp_server=p.mcp_server, mcp_tool=p.mcp_tool, source="user",
|
|
141
|
+
)
|
|
142
|
+
verdict = review_install(spec) # 安装审核门
|
|
143
|
+
if verdict["verdict"] == "deny":
|
|
144
|
+
raise HTTPException(403, detail={"error": "install_denied", "reasons": verdict["reasons"]})
|
|
145
|
+
if verdict["verdict"] == "warn" and not p.acknowledged:
|
|
146
|
+
raise HTTPException(409, detail={"needs_ack": True, "warnings": verdict["warnings"]})
|
|
147
|
+
reg.register(spec)
|
|
148
|
+
_api._os_instance.skill_store.save(reg)
|
|
149
|
+
return {"ok": True, "skill": spec.to_dict(), "created": not existed, "warnings": verdict["warnings"]}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@app.patch("/api/ahs/skills/{skill_id}")
|
|
153
|
+
async def ahs_patch_skill(skill_id: str, p: SkillPatchPayload):
|
|
154
|
+
"""启停一个技能(内置/用户均可,持久化)。"""
|
|
155
|
+
reg = _api._os_instance.skill_registry
|
|
156
|
+
sp = reg.get(skill_id)
|
|
157
|
+
if sp is None:
|
|
158
|
+
raise HTTPException(404, "技能不存在")
|
|
159
|
+
sp.enabled = bool(p.enabled)
|
|
160
|
+
_api._os_instance.skill_store.save(reg)
|
|
161
|
+
return {"ok": True, "skill": sp.to_dict()}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@app.delete("/api/ahs/skills/{skill_id}")
|
|
165
|
+
async def ahs_delete_skill(skill_id: str):
|
|
166
|
+
"""删除用户安装的技能;内置技能不可删(只能停用)。"""
|
|
167
|
+
reg = _api._os_instance.skill_registry
|
|
168
|
+
sp = reg.get(skill_id)
|
|
169
|
+
if sp is None:
|
|
170
|
+
raise HTTPException(404, "技能不存在")
|
|
171
|
+
if sp.source == "builtin":
|
|
172
|
+
raise HTTPException(409, "内置技能不可删除,只能停用")
|
|
173
|
+
reg.unregister(skill_id)
|
|
174
|
+
_api._os_instance.skill_store.save(reg)
|
|
175
|
+
return {"ok": True}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@app.post("/api/ahs/skills/{skill_id}/test")
|
|
179
|
+
async def ahs_test_skill(skill_id: str, p: SkillTestPayload):
|
|
180
|
+
"""单技能测试调用:走真实权限管线/断路器/SSRF。副作用未确认 → needs_confirmation。"""
|
|
181
|
+
reg = _api._os_instance.skill_registry
|
|
182
|
+
sp = reg.get(skill_id)
|
|
183
|
+
if sp is None:
|
|
184
|
+
raise HTTPException(404, "技能不存在")
|
|
185
|
+
# 阻塞 I/O(web_api httpx / mcp 桥,最长数十秒)放线程池,勿堵死事件循环(否则慢工具冻全站)
|
|
186
|
+
res = await asyncio.to_thread(_api._os_instance.skill_adapter.invoke,
|
|
187
|
+
sp, p.args, p.confirmed, p.mode,
|
|
188
|
+
confirm_nonce=p.confirm_nonce)
|
|
189
|
+
return res.to_dict()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class McpServerPayload(BaseModel):
|
|
193
|
+
id: str
|
|
194
|
+
name: str = ""
|
|
195
|
+
url: str
|
|
196
|
+
headers: dict = {}
|
|
197
|
+
transport: str = "streamable_http" # streamable_http | sse
|
|
198
|
+
acknowledged: bool = False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@app.post("/api/ahs/mcp/servers")
|
|
202
|
+
async def mcp_add_server(p: McpServerPayload):
|
|
203
|
+
"""接入一个远程 MCP server:审核→连接拉工具→批量注册 mcp 技能→持久化。"""
|
|
204
|
+
from foxclaw.skills.review import review_mcp_server
|
|
205
|
+
from foxclaw.skills.mcp_client import tool_to_spec
|
|
206
|
+
if p.transport not in ("streamable_http", "sse"):
|
|
207
|
+
raise HTTPException(400, "transport 只能是 streamable_http 或 sse")
|
|
208
|
+
if not (p.url.startswith("http://") or p.url.startswith("https://")):
|
|
209
|
+
raise HTTPException(400, "url 需为 http(s)")
|
|
210
|
+
verdict = review_mcp_server(p.url)
|
|
211
|
+
if verdict["verdict"] == "deny":
|
|
212
|
+
raise HTTPException(403, detail={"error": "install_denied", "reasons": verdict["reasons"]})
|
|
213
|
+
if verdict["verdict"] == "warn" and not p.acknowledged:
|
|
214
|
+
raise HTTPException(409, detail={"needs_ack": True, "warnings": verdict["warnings"]})
|
|
215
|
+
cfg = {"id": p.id, "name": p.name or p.id, "url": p.url,
|
|
216
|
+
"headers": dict(p.headers), "transport": p.transport}
|
|
217
|
+
sentinel = getattr(_api._os_instance, "sentinel", None)
|
|
218
|
+
try:
|
|
219
|
+
if sentinel is not None:
|
|
220
|
+
# 守门人模式:headers 只在这次调用经手,发现由守门人代执行,主进程只存元数据。
|
|
221
|
+
tools = await asyncio.to_thread(_api._os_instance.mcp_add_server_guardian, cfg)
|
|
222
|
+
else:
|
|
223
|
+
tools = await asyncio.to_thread(_api._os_instance.mcp_manager.add_server, cfg) # 桥阻塞→线程池
|
|
224
|
+
_api._os_instance.mcp_store.save(_api._os_instance.mcp_manager.servers())
|
|
225
|
+
except Exception as e:
|
|
226
|
+
raise HTTPException(502, f"连接 MCP server 失败: {e}")
|
|
227
|
+
reg = _api._os_instance.skill_registry
|
|
228
|
+
n = 0
|
|
229
|
+
for tool in tools:
|
|
230
|
+
reg.register(tool_to_spec(p.id, tool)); n += 1
|
|
231
|
+
_api._os_instance.skill_store.save(reg)
|
|
232
|
+
_api._os_instance.sync_mcp_servers_to_sentinel() # 元数据同步;守门人已持有 headers
|
|
233
|
+
return {"ok": True, "server": {k: cfg[k] for k in ("id", "name", "url", "transport")},
|
|
234
|
+
"tools_registered": n, "warnings": verdict["warnings"]}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@app.get("/api/ahs/mcp/servers")
|
|
238
|
+
async def mcp_list_servers():
|
|
239
|
+
"""列出已接入 MCP server(工具数由注册表派生,无需连接)。"""
|
|
240
|
+
reg = _api._os_instance.skill_registry
|
|
241
|
+
all_sk = reg.all_including_disabled()
|
|
242
|
+
out = []
|
|
243
|
+
for c in _api._os_instance.mcp_manager.servers():
|
|
244
|
+
tc = sum(1 for s in all_sk if s.kind == "mcp" and s.mcp_server == c["id"])
|
|
245
|
+
out.append({"id": c["id"], "name": c["name"], "url": c["url"],
|
|
246
|
+
"transport": c["transport"], "tool_count": tc})
|
|
247
|
+
return {"servers": out}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@app.delete("/api/ahs/mcp/servers/{server_id}")
|
|
251
|
+
async def mcp_remove_server(server_id: str):
|
|
252
|
+
"""移除 server + 其注册的所有 mcp 技能。"""
|
|
253
|
+
mgr = _api._os_instance.mcp_manager
|
|
254
|
+
if not mgr.remove_server(server_id):
|
|
255
|
+
raise HTTPException(404, "server 不存在")
|
|
256
|
+
reg = _api._os_instance.skill_registry
|
|
257
|
+
removed = [s.id for s in reg.all_including_disabled()
|
|
258
|
+
if s.kind == "mcp" and s.mcp_server == server_id]
|
|
259
|
+
for sid in removed:
|
|
260
|
+
reg.unregister(sid)
|
|
261
|
+
_api._os_instance.skill_store.save(reg)
|
|
262
|
+
_api._os_instance.mcp_store.save(mgr.servers())
|
|
263
|
+
_api._os_instance.sync_mcp_servers_to_sentinel() # 配置(含凭证)同步给守门人
|
|
264
|
+
return {"ok": True, "removed_skills": len(removed)}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@app.post("/api/ahs/mcp/servers/{server_id}/refresh")
|
|
268
|
+
async def mcp_refresh_server(server_id: str):
|
|
269
|
+
"""重连拉工具:新增/更新技能,删除已消失的工具技能。"""
|
|
270
|
+
from foxclaw.skills.mcp_client import tool_to_spec
|
|
271
|
+
mgr = _api._os_instance.mcp_manager
|
|
272
|
+
if not any(s["id"] == server_id for s in mgr.servers()):
|
|
273
|
+
raise HTTPException(404, "server 不存在")
|
|
274
|
+
sentinel = getattr(_api._os_instance, "sentinel", None)
|
|
275
|
+
try:
|
|
276
|
+
if sentinel is not None:
|
|
277
|
+
tools = await asyncio.to_thread(sentinel.mcp_list_tools, server_id) # 守门人代发现(失败抛异常)
|
|
278
|
+
else:
|
|
279
|
+
tools = await asyncio.to_thread(mgr.list_tools, server_id) # 桥阻塞→线程池
|
|
280
|
+
except Exception as e:
|
|
281
|
+
raise HTTPException(502, f"刷新失败: {e}")
|
|
282
|
+
reg = _api._os_instance.skill_registry
|
|
283
|
+
fresh = set()
|
|
284
|
+
for tool in tools:
|
|
285
|
+
spec = tool_to_spec(server_id, tool); reg.register(spec); fresh.add(spec.id)
|
|
286
|
+
for s in [s for s in reg.all_including_disabled()
|
|
287
|
+
if s.kind == "mcp" and s.mcp_server == server_id and s.id not in fresh]:
|
|
288
|
+
reg.unregister(s.id)
|
|
289
|
+
_api._os_instance.skill_store.save(reg)
|
|
290
|
+
return {"ok": True, "tools": len(fresh)}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class MdSkillPayload(BaseModel):
|
|
294
|
+
content: str = ""
|
|
295
|
+
package_b64: str = "" # OpenClaw 包(zip)的 base64;有则优先,解包取 SKILL.md
|
|
296
|
+
id: str = ""
|
|
297
|
+
acknowledged: bool = False
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class MdSkillPatchPayload(BaseModel):
|
|
301
|
+
enabled: bool | None = None
|
|
302
|
+
triggers: list | None = None # 改触发词(免解压重打包);None=不动
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@app.post("/api/ahs/md-skills")
|
|
306
|
+
async def md_install(p: MdSkillPayload):
|
|
307
|
+
"""上传/注册一个 SKILL.md 指令技能(单 .md 文本 或 OpenClaw zip 包)。解析→审核→持久化。"""
|
|
308
|
+
from foxclaw.skills.markdown_skill import parse_skill_md, extract_skill_package
|
|
309
|
+
content, want_id, want_version = p.content, (p.id or None), ""
|
|
310
|
+
if p.package_b64:
|
|
311
|
+
import base64 as _b64
|
|
312
|
+
try:
|
|
313
|
+
content, meta = extract_skill_package(_b64.b64decode(p.package_b64))
|
|
314
|
+
except ValueError as e:
|
|
315
|
+
raise HTTPException(400, f"OpenClaw 包解析失败: {e}")
|
|
316
|
+
except Exception as e:
|
|
317
|
+
raise HTTPException(400, f"包解码失败: {e}")
|
|
318
|
+
want_id = want_id or (meta.get("slug") or None)
|
|
319
|
+
want_version = str(meta.get("version") or "")
|
|
320
|
+
try:
|
|
321
|
+
skill = parse_skill_md(content, id=want_id)
|
|
322
|
+
except ValueError as e:
|
|
323
|
+
raise HTTPException(400, f"SKILL.md 解析失败: {e}")
|
|
324
|
+
if want_version and not skill.version:
|
|
325
|
+
skill.version = want_version
|
|
326
|
+
if not p.acknowledged:
|
|
327
|
+
raise HTTPException(409, detail={"needs_ack": True, "warnings": [
|
|
328
|
+
"此技能会把内容注入你 AI 的提示词、影响其行为,仅从可信来源安装"]})
|
|
329
|
+
reg = _api._os_instance.md_skills
|
|
330
|
+
existed = reg.get(skill.id) is not None
|
|
331
|
+
reg.register(skill)
|
|
332
|
+
_api._os_instance.md_store.save(reg)
|
|
333
|
+
return {"ok": True, "skill": skill.to_meta(), "created": not existed}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@app.get("/api/ahs/md-skills")
|
|
337
|
+
async def md_list():
|
|
338
|
+
pol = _api._os_instance.skill_policy
|
|
339
|
+
out = _api._os_instance.md_skills.catalog()
|
|
340
|
+
for s in out:
|
|
341
|
+
s["blocked"] = pol.is_blocked(s.get("id", ""))
|
|
342
|
+
return {"skills": out}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@app.get("/api/ahs/md-skills/{skill_id}")
|
|
346
|
+
async def md_get(skill_id: str):
|
|
347
|
+
s = _api._os_instance.md_skills.get(skill_id)
|
|
348
|
+
if s is None:
|
|
349
|
+
raise HTTPException(404, "指令技能不存在")
|
|
350
|
+
return s.to_dict()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@app.patch("/api/ahs/md-skills/{skill_id}")
|
|
354
|
+
async def md_patch(skill_id: str, p: MdSkillPatchPayload):
|
|
355
|
+
reg = _api._os_instance.md_skills
|
|
356
|
+
s = reg.get(skill_id)
|
|
357
|
+
if s is None:
|
|
358
|
+
raise HTTPException(404, "指令技能不存在")
|
|
359
|
+
if p.enabled is not None:
|
|
360
|
+
s.enabled = bool(p.enabled)
|
|
361
|
+
if p.triggers is not None:
|
|
362
|
+
s.triggers = [str(t).strip() for t in p.triggers if str(t).strip()]
|
|
363
|
+
_api._os_instance.md_store.save(reg)
|
|
364
|
+
return {"ok": True, "skill": s.to_meta()}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@app.delete("/api/ahs/md-skills/{skill_id}")
|
|
368
|
+
async def md_delete(skill_id: str):
|
|
369
|
+
reg = _api._os_instance.md_skills
|
|
370
|
+
if reg.get(skill_id) is None:
|
|
371
|
+
raise HTTPException(404, "指令技能不存在")
|
|
372
|
+
reg.unregister(skill_id)
|
|
373
|
+
_api._os_instance.md_store.save(reg)
|
|
374
|
+
return {"ok": True}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@app.post("/api/ahs/run")
|
|
378
|
+
async def ahs_run(payload: AHSRunPayload):
|
|
379
|
+
"""跑一遍 AHS 执行链:router→planner→composition,全程过 guard+breaker+hooks。
|
|
380
|
+
【P3-2】用 run_async 把同步 compose 放到线程池,避免阻塞事件循环。
|
|
381
|
+
【P2-2】暴露 pending_confirmation/is_pending,供前端识别"待确认"并断点续跑
|
|
382
|
+
(确认后带 confirmed=True + resume_state 重发本接口)。
|
|
383
|
+
【P1】关键操作还需带 confirm_nonce(响应里的 pending_confirmation.confirm_nonce);
|
|
384
|
+
缺/错/过期 → 仍返回 pending_confirmation 并换发新码,不会执行。"""
|
|
385
|
+
from foxclaw.skills import set_loop_session, reset_loop_session
|
|
386
|
+
# 循环检测按执行链分槽:断点续跑复用同一 task_id,否则每次请求独立(消除跨请求污染)
|
|
387
|
+
sid = (payload.resume_state or {}).get("task_id") or f"ahs_{uuid.uuid4().hex[:8]}"
|
|
388
|
+
token = set_loop_session(sid)
|
|
389
|
+
try:
|
|
390
|
+
res = await _api._os_instance.ahs_pipeline.run_async(
|
|
391
|
+
payload.task, confirmed=payload.confirmed,
|
|
392
|
+
resume_state=payload.resume_state,
|
|
393
|
+
confirm_nonce=payload.confirm_nonce)
|
|
394
|
+
return {
|
|
395
|
+
"task": res.task, "route": res.route, "plan": res.plan,
|
|
396
|
+
"composition": res.composition, "success": res.success,
|
|
397
|
+
"summary": res.summary,
|
|
398
|
+
"pending_confirmation": res.pending_confirmation,
|
|
399
|
+
"is_pending": res.is_pending,
|
|
400
|
+
}
|
|
401
|
+
except Exception as e:
|
|
402
|
+
return {"success": False, "summary": f"执行链异常: {e}"}
|
|
403
|
+
finally:
|
|
404
|
+
reset_loop_session(token)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@app.get("/api/ahs/breaker")
|
|
408
|
+
async def ahs_breaker_status():
|
|
409
|
+
"""断路器状态:各 skill 的失败计数与开闸情况。"""
|
|
410
|
+
br = _api._os_instance.skill_adapter.breaker
|
|
411
|
+
return {"breakers": {sid: br.status(sid) for sid in br._entries}}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@app.get("/api/ahs/hooks/audit")
|
|
415
|
+
async def ahs_hooks_audit():
|
|
416
|
+
"""钩子审计日志(最近 20 条)。"""
|
|
417
|
+
return {"audit": _api._os_instance.skill_adapter.hooks.recent_audit(20)}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""聊天附件:通用文件上传/下载(多模态渲染·步骤7)。
|
|
2
|
+
|
|
3
|
+
与「文件摄入知识库」(domain.py 的 import + HKC ingest)不同:这里是**聊天附件**——文件随问题
|
|
4
|
+
发给 LLM(文本类提取正文、其它降级为元数据提示),并在对话里以卡片渲染。图片多模态(images
|
|
5
|
+
base64)走既有 /chat/stream 的 images 通道,不经过这里。
|
|
6
|
+
|
|
7
|
+
安全铁律:
|
|
8
|
+
· 文件名只留 basename + 清洗非法字符,杜绝路径穿越;
|
|
9
|
+
· id 只认我们生成的 [A-Za-z0-9_-] 形态;
|
|
10
|
+
· 大小 / 文本提取长度均设上限(防超大 payload / 超长上下文);
|
|
11
|
+
· 附件落 `<data_dir>/attachments/`,下载按 id 定位,不信任客户端传路径。
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
import uuid
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from fastapi import File, HTTPException, UploadFile
|
|
20
|
+
from fastapi.responses import FileResponse
|
|
21
|
+
|
|
22
|
+
import foxclaw.api as _api
|
|
23
|
+
from .. import app
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_MAX_ATTACH_BYTES = 20 * 1024 * 1024 # 单附件上限 20MB
|
|
27
|
+
_MAX_TEXT_CHARS = 20000 # 单文件提取正文上限(防超长文件撑爆上下文)
|
|
28
|
+
_ID_RE = re.compile(r"^[A-Za-z0-9_-]{1,64}$")
|
|
29
|
+
|
|
30
|
+
# 文本类扩展名 → 直接读正文;.pdf 走 fitz(可选依赖);其余二进制 → 降级为元数据提示
|
|
31
|
+
_TEXT_EXTS = {
|
|
32
|
+
".txt", ".md", ".markdown", ".csv", ".json", ".yaml", ".yml", ".log",
|
|
33
|
+
".py", ".js", ".ts", ".html", ".css", ".xml", ".toml", ".ini", ".sh",
|
|
34
|
+
".go", ".rs", ".java", ".c", ".cpp", ".h", ".sql", ".rb", ".php",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _att_dir() -> Path:
|
|
39
|
+
d = Path(_api._os_instance.data_dir) / "attachments"
|
|
40
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
return d
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _decode_text(data: bytes) -> str:
|
|
45
|
+
"""按常见编码尝试解码,失败返回空(降级)。"""
|
|
46
|
+
for enc in ("utf-8", "gbk", "utf-16"):
|
|
47
|
+
try:
|
|
48
|
+
return data.decode(enc)
|
|
49
|
+
except (UnicodeDecodeError, LookupError):
|
|
50
|
+
continue
|
|
51
|
+
return ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _extract_text(path: Path, name: str) -> str:
|
|
55
|
+
"""从上传的附件提取正文:文本类直接读、PDF 用 fitz、其它返回空(前端/LLM 降级为元数据提示)。"""
|
|
56
|
+
ext = Path(name).suffix.lower()
|
|
57
|
+
if ext in _TEXT_EXTS:
|
|
58
|
+
try:
|
|
59
|
+
return _decode_text(path.read_bytes())[:_MAX_TEXT_CHARS]
|
|
60
|
+
except Exception:
|
|
61
|
+
return ""
|
|
62
|
+
if ext == ".pdf":
|
|
63
|
+
try:
|
|
64
|
+
import fitz # PyMuPDF(可选依赖 knowledge-files)
|
|
65
|
+
except ImportError:
|
|
66
|
+
return ""
|
|
67
|
+
try:
|
|
68
|
+
doc = fitz.open(str(path))
|
|
69
|
+
try:
|
|
70
|
+
text = ""
|
|
71
|
+
for page in doc:
|
|
72
|
+
text += page.get_text()
|
|
73
|
+
if len(text) >= _MAX_TEXT_CHARS:
|
|
74
|
+
break
|
|
75
|
+
return text[:_MAX_TEXT_CHARS]
|
|
76
|
+
finally:
|
|
77
|
+
doc.close()
|
|
78
|
+
except Exception:
|
|
79
|
+
return ""
|
|
80
|
+
return ""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.post("/api/upload")
|
|
84
|
+
async def upload_attachment(file: UploadFile = File(...)):
|
|
85
|
+
"""通用聊天附件上传:落盘 + 尽力提取正文,返回 {id,name,size,mime,url,text}。
|
|
86
|
+
text 为空表示不可提取(二进制/图片等),前端/LLM 降级为「文件名 + 元数据」提示。"""
|
|
87
|
+
raw_name = (file.filename or "attachment").strip()
|
|
88
|
+
name = Path(raw_name).name or "attachment" # 只留 basename,去路径分隔符
|
|
89
|
+
data = await file.read()
|
|
90
|
+
if not data:
|
|
91
|
+
raise HTTPException(400, "空文件")
|
|
92
|
+
if len(data) > _MAX_ATTACH_BYTES:
|
|
93
|
+
raise HTTPException(413, f"文件过大({len(data) / 1048576:.1f}MB>{_MAX_ATTACH_BYTES // 1048576}MB)")
|
|
94
|
+
aid = uuid.uuid4().hex[:12]
|
|
95
|
+
safe_name = re.sub(r"[^A-Za-z0-9._\-]", "_", name)[:120] or "attachment"
|
|
96
|
+
dest = _att_dir() / f"{aid}_{safe_name}"
|
|
97
|
+
dest.write_bytes(data)
|
|
98
|
+
text = _extract_text(dest, name)
|
|
99
|
+
return {
|
|
100
|
+
"id": aid,
|
|
101
|
+
"name": name,
|
|
102
|
+
"size": len(data),
|
|
103
|
+
"mime": (file.content_type or "application/octet-stream").lower(),
|
|
104
|
+
"url": f"/api/attachments/{aid}",
|
|
105
|
+
"text": text,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.get("/api/attachments/{aid}")
|
|
110
|
+
async def get_attachment(aid: str):
|
|
111
|
+
"""下载附件。id 只认自生成形态,路径穿越到此为止。"""
|
|
112
|
+
if not _ID_RE.fullmatch(aid or ""):
|
|
113
|
+
raise HTTPException(400, "非法附件 id")
|
|
114
|
+
d = _att_dir()
|
|
115
|
+
for p in d.glob(f"{aid}_*"):
|
|
116
|
+
return FileResponse(str(p), filename=p.name.split("_", 1)[1])
|
|
117
|
+
raise HTTPException(404, "附件不存在")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""浏览器子系统(HIR-Browser)端点。
|
|
2
|
+
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
|
|
4
|
+
晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
import foxclaw.api as _api
|
|
10
|
+
from .. import app
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ── 浏览器子系统(HIR-Browser):AI 用的浏览器 ─────────────────
|
|
14
|
+
# 感知网页→压缩语义结构→执行动作→校验结果。async 安全(专用工作线程),
|
|
15
|
+
# playwright 缺失时结构化降级(available=False),不崩溃。受 /api 鉴权保护。
|
|
16
|
+
|
|
17
|
+
class BrowserActPayload(BaseModel):
|
|
18
|
+
type: str # navigate|click|type|scroll|extract|wait
|
|
19
|
+
target: str | None = None # 元素编号;navigate 时为 URL
|
|
20
|
+
value: object | None = None # type 的文本;scroll 的方向
|
|
21
|
+
meta: dict = {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.get("/api/browser/status")
|
|
25
|
+
async def browser_status():
|
|
26
|
+
"""浏览器子系统能力/会话状态(available/session_active/launch_error)。"""
|
|
27
|
+
return _api._os_instance.browser_service.status()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.get("/api/browser/perceive")
|
|
31
|
+
async def browser_perceive():
|
|
32
|
+
"""只感知当前页面语义结构(带编号的可交互元素 + 摘要),不改变页面。"""
|
|
33
|
+
return await _api._os_instance.browser_service.perceive()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.post("/api/browser/act")
|
|
37
|
+
async def browser_act(p: BrowserActPayload):
|
|
38
|
+
"""执行一个 Web 动作,返回校验后最新语义结构。"""
|
|
39
|
+
return await _api._os_instance.browser_service.act(p.model_dump())
|