foxclaw 0.1.2 → 0.1.4
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/index.js +1 -8
- package/package.json +1 -1
- package/python/.env.example +8 -0
- package/python/.github/workflows/ci.yml +47 -2
- package/python/.github/workflows/release.yml +5 -0
- package/python/CHANGELOG.md +100 -0
- package/python/CHANGELOG_CN.md +100 -0
- package/python/README.md +17 -2
- package/python/README_CN.md +38 -20
- package/python/bench/bm25_audit.py +1 -14
- package/python/bench/bm25_regression.py +0 -1
- package/python/bench/bm25_stress.py +0 -4
- package/python/bench/cd_audit.py +0 -23
- package/python/bench/chaos.py +6 -8
- package/python/bench/e2e_real_models.py +0 -1
- package/python/bench/llm_retry_test.py +2 -9
- package/python/bench/load_test.py +5 -8
- package/python/bench/vector_ivf_upgrade.py +1 -7
- package/python/docs/CONTENT_GUARD.md +189 -0
- package/python/docs/CONTENT_GUARD_CN.md +189 -0
- package/python/docs/RELEASE.md +78 -37
- package/python/docs/RUN_CN.md +2 -2
- package/python/evals/governance/cases.py +0 -37
- package/python/evals/governance/run_evals.py +5 -37
- package/python/examples/governance_middleware_demo.py +1 -6
- package/python/examples/langgraph_governance_demo.py +7 -11
- package/python/foxclaw/__init__.py +74 -152
- package/python/foxclaw/agents/base.py +82 -136
- package/python/foxclaw/agents/impl/_helpers.py +2 -10
- package/python/foxclaw/agents/impl/finance.py +4 -21
- package/python/foxclaw/agents/impl/office.py +7 -24
- package/python/foxclaw/agents/impl/supervisor.py +12 -79
- package/python/foxclaw/agents/mixins/_context.py +0 -23
- package/python/foxclaw/agents/mixins/augment.py +10 -16
- package/python/foxclaw/agents/mixins/llm.py +7 -6
- package/python/foxclaw/agents/mixins/memory.py +23 -3
- package/python/foxclaw/agents/mixins/preference.py +58 -8
- package/python/foxclaw/agents/mixins/prompt.py +59 -12
- package/python/foxclaw/agents/mixins/skillbox.py +6 -6
- package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
- package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
- package/python/foxclaw/api/__init__.py +18 -77
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/bootstrap.py +114 -0
- package/python/foxclaw/api/middleware.py +5 -28
- package/python/foxclaw/api/routes/ahs.py +60 -73
- package/python/foxclaw/api/routes/attachments.py +5 -6
- package/python/foxclaw/api/routes/browser.py +7 -11
- package/python/foxclaw/api/routes/domain.py +18 -26
- package/python/foxclaw/api/routes/knowledge.py +3 -21
- package/python/foxclaw/api/routes/llm_admin.py +21 -33
- package/python/foxclaw/api/routes/memory.py +50 -12
- package/python/foxclaw/api/routes/orchestration.py +3 -5
- package/python/foxclaw/api/routes/recovery.py +4 -3
- package/python/foxclaw/api/routes/review.py +2 -11
- package/python/foxclaw/api/routes/search.py +3 -7
- package/python/foxclaw/api/routes/system.py +33 -49
- package/python/foxclaw/api/routes/task.py +31 -49
- package/python/foxclaw/api/routes/ws.py +2 -6
- package/python/foxclaw/api/spine.py +1 -4
- package/python/foxclaw/behavior/__init__.py +20 -0
- package/python/foxclaw/behavior/behavior.yaml +62 -0
- package/python/foxclaw/behavior/spec.py +287 -0
- package/python/foxclaw/browser/action/action_executor.py +1 -5
- package/python/foxclaw/browser/action/action_types.py +0 -1
- package/python/foxclaw/browser/action/url_policy.py +1 -5
- package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
- package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
- package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
- package/python/foxclaw/browser/perception/element_indexer.py +1 -2
- package/python/foxclaw/browser/service.py +7 -11
- package/python/foxclaw/capability/embedding_service.py +4 -6
- package/python/foxclaw/channels/__init__.py +9 -11
- package/python/foxclaw/channels/discord.py +4 -13
- package/python/foxclaw/channels/dispatcher.py +2 -27
- package/python/foxclaw/channels/feishu.py +10 -20
- package/python/foxclaw/channels/openclaw_bridge.py +4 -9
- package/python/foxclaw/channels/signing.py +9 -9
- package/python/foxclaw/channels/telegram.py +3 -4
- package/python/foxclaw/channels/weixin.py +7 -17
- package/python/foxclaw/cli.py +42 -13
- package/python/foxclaw/cognitive/reasoner.py +9 -10
- package/python/foxclaw/cognitive/state.py +18 -24
- package/python/foxclaw/core/audit_chain.py +1 -3
- package/python/foxclaw/core/audit_sink.py +3 -3
- package/python/foxclaw/core/compaction.py +8 -26
- package/python/foxclaw/core/config.py +2 -2
- package/python/foxclaw/core/confirm_nonce.py +6 -12
- package/python/foxclaw/core/content_guard.py +341 -0
- package/python/foxclaw/core/envfile.py +2 -2
- package/python/foxclaw/core/failure_policy.py +17 -26
- package/python/foxclaw/core/gateway.py +6 -10
- package/python/foxclaw/core/governance_audit.py +3 -7
- package/python/foxclaw/core/idempotency.py +15 -22
- package/python/foxclaw/core/injection_guard.py +10 -29
- package/python/foxclaw/core/memory_adapter.py +53 -60
- package/python/foxclaw/core/metrics.py +4 -5
- package/python/foxclaw/core/model_router.py +74 -150
- package/python/foxclaw/core/paths.py +1 -1
- package/python/foxclaw/core/recovery.py +21 -34
- package/python/foxclaw/core/scheduler.py +23 -26
- package/python/foxclaw/core/secrets.py +0 -3
- package/python/foxclaw/core/sensitive_write.py +283 -0
- package/python/foxclaw/core/sessions.py +4 -6
- package/python/foxclaw/core/settings.py +16 -32
- package/python/foxclaw/core/shared_write.py +11 -23
- package/python/foxclaw/core/steering_queue.py +12 -11
- package/python/foxclaw/core/system_bus.py +1 -1
- package/python/foxclaw/core/thought_chain.py +30 -32
- package/python/foxclaw/core/token_meter.py +9 -13
- package/python/foxclaw/core/tools.py +4 -12
- package/python/foxclaw/core/tracing.py +25 -40
- package/python/foxclaw/core/truth_pipeline.py +5 -9
- package/python/foxclaw/core/update_check.py +149 -0
- package/python/foxclaw/core/vec_recall_adapter.py +11 -37
- package/python/foxclaw/core/verify.py +6 -14
- package/python/foxclaw/core/web_search.py +4 -28
- package/python/foxclaw/finance/evidence.py +14 -18
- package/python/foxclaw/finance/fx.py +1 -3
- package/python/foxclaw/finance/graph.py +9 -18
- package/python/foxclaw/finance/models.py +20 -24
- package/python/foxclaw/finance/nlparse.py +12 -72
- package/python/foxclaw/finance/policy.py +8 -36
- package/python/foxclaw/finance/reasoner.py +3 -11
- package/python/foxclaw/finance/reasoning.py +13 -34
- package/python/foxclaw/finance/reconcile.py +5 -10
- package/python/foxclaw/finance/runtime.py +19 -46
- package/python/foxclaw/governance/middleware.py +13 -21
- package/python/foxclaw/harness/_base.py +2 -8
- package/python/foxclaw/harness/_policy.py +19 -38
- package/python/foxclaw/harness/drift.py +1 -13
- package/python/foxclaw/harness/event_bus.py +2 -5
- package/python/foxclaw/harness/harness.py +9 -97
- package/python/foxclaw/harness/loop_detection.py +11 -18
- package/python/foxclaw/itinerary/amap_client.py +2 -5
- package/python/foxclaw/itinerary/models.py +7 -7
- package/python/foxclaw/itinerary/reasoner.py +1 -6
- package/python/foxclaw/itinerary/reasoning.py +15 -21
- package/python/foxclaw/itinerary/runtime.py +8 -12
- package/python/foxclaw/kernel/capabilities.py +5 -6
- package/python/foxclaw/kernel/policy_gate.py +4 -10
- package/python/foxclaw/kernel/process_manager.py +17 -36
- package/python/foxclaw/kernel/scheduler.py +3 -9
- package/python/foxclaw/kernel/sentinel.py +13 -23
- package/python/foxclaw/kernel/sentinel_client.py +12 -15
- package/python/foxclaw/kernel/spine.py +9 -9
- package/python/foxclaw/knowledge/__init__.py +4 -20
- package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
- package/python/foxclaw/learning/knowledge_graph.py +4 -9
- package/python/foxclaw/learning/models.py +13 -13
- package/python/foxclaw/learning/reasoner.py +1 -4
- package/python/foxclaw/learning/runtime.py +19 -31
- package/python/foxclaw/learning/spaced_repetition.py +2 -7
- package/python/foxclaw/mcp_server.py +2 -3
- package/python/foxclaw/orchestration/__init__.py +16 -33
- package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
- package/python/foxclaw/pipeline/stage_meta.py +21 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
- package/python/foxclaw/planning/llm_planner.py +3 -3
- package/python/foxclaw/planning/planner.py +6 -11
- package/python/foxclaw/search/agent_port.py +7 -11
- package/python/foxclaw/search/evidence.py +0 -4
- package/python/foxclaw/search/internal_search.py +3 -5
- package/python/foxclaw/search/models.py +11 -13
- package/python/foxclaw/search/planner.py +3 -3
- package/python/foxclaw/search/retriever.py +1 -1
- package/python/foxclaw/search/runtime.py +1 -8
- package/python/foxclaw/skills/__init__.py +46 -100
- package/python/foxclaw/skills/archive.py +111 -0
- package/python/foxclaw/skills/fetch.py +226 -0
- package/python/foxclaw/skills/hooks_spec.py +21 -25
- package/python/foxclaw/skills/install_cmd.py +216 -0
- package/python/foxclaw/skills/lifecycle.py +16 -42
- package/python/foxclaw/skills/markdown_skill.py +10 -9
- package/python/foxclaw/skills/mcp_client.py +5 -11
- package/python/foxclaw/skills/mcp_store.py +0 -3
- package/python/foxclaw/skills/permission.py +13 -48
- package/python/foxclaw/skills/pipeline.py +21 -53
- package/python/foxclaw/skills/policy.py +4 -10
- package/python/foxclaw/skills/review.py +3 -3
- package/python/frontend/deck/auth.js +0 -7
- package/python/frontend/deck/chat_model_picker.js +0 -54
- package/python/frontend/deck/domain.js +0 -20
- package/python/frontend/deck/main.js +0 -144
- package/python/frontend/deck/markdown.js +0 -4
- package/python/frontend/deck/settings.js +1 -18
- package/python/frontend/deck/state.js +0 -1
- package/python/frontend/deck/util.js +0 -2
- package/python/frontend/deck/visuals.js +0 -22
- package/python/frontend/planet/planet.js +18 -201
- package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
- package/python/frontend/v3/assets/index-tN90dpUH.js +34 -0
- package/python/frontend/v3/index.html +3 -3
- package/python/frontend/widget_runtime.js +0 -43
- package/python/frontend/widgets.js +0 -57
- package/python/frontend_src/index.html +1 -1
- package/python/frontend_src/package-lock.json +2 -2
- package/python/frontend_src/package.json +1 -1
- package/python/frontend_src/src/App.vue +3 -7
- package/python/frontend_src/src/api.js +0 -7
- package/python/frontend_src/src/components/ChatDock.vue +27 -39
- package/python/frontend_src/src/components/DocViewer.vue +0 -9
- package/python/frontend_src/src/components/Inspector.vue +0 -6
- package/python/frontend_src/src/components/LeftRail.vue +60 -39
- package/python/frontend_src/src/components/MenuPopover.vue +9 -11
- package/python/frontend_src/src/components/MessageStream.vue +18 -30
- package/python/frontend_src/src/components/ModelPicker.vue +0 -5
- package/python/frontend_src/src/components/QuickIngest.vue +0 -6
- package/python/frontend_src/src/components/Stage.vue +71 -25
- package/python/frontend_src/src/components/ToolMenu.vue +0 -3
- package/python/frontend_src/src/components/TopBar.vue +38 -10
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +42 -3
- package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
- package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
- package/python/frontend_src/src/lib/fmt.js +0 -4
- package/python/frontend_src/src/lib/i18n.js +53 -0
- package/python/frontend_src/src/lib/locales/en.js +173 -0
- package/python/frontend_src/src/lib/locales/zh.js +174 -0
- package/python/frontend_src/src/lib/md.js +0 -14
- package/python/frontend_src/src/main.js +2 -0
- package/python/frontend_src/src/stores/app.js +17 -32
- package/python/frontend_src/src/stores/chat.js +0 -34
- package/python/frontend_src/src/stores/model.js +0 -8
- package/python/frontend_src/src/stores/panels.js +16 -44
- package/python/frontend_src/src/stores/profile.js +31 -0
- package/python/frontend_src/src/stores/system.js +25 -0
- package/python/frontend_src/vite.config.js +0 -7
- package/python/pyproject.toml +17 -3
- package/python/requirements.txt +311 -0
- package/python/tests/conftest.py +0 -22
- package/python/tests/run_all.py +4 -66
- package/python/tests/test_agent_lifecycle.py +4 -38
- package/python/tests/test_agent_rules.py +81 -0
- package/python/tests/test_ahs_p2.py +4 -25
- package/python/tests/test_anthropic_tools.py +1 -15
- package/python/tests/test_api.py +19 -9
- package/python/tests/test_api_auth.py +1 -5
- package/python/tests/test_attachments.py +9 -25
- package/python/tests/test_audit_chain.py +2 -7
- package/python/tests/test_audit_sink.py +0 -6
- package/python/tests/test_behavior_spec.py +198 -0
- package/python/tests/test_browser.py +2 -6
- package/python/tests/test_browser_request_guard.py +0 -4
- package/python/tests/test_capabilities.py +0 -6
- package/python/tests/test_channel_signing.py +2 -9
- package/python/tests/test_channels.py +1 -11
- package/python/tests/test_config_layering.py +0 -4
- package/python/tests/test_confirm_nonce.py +1 -12
- package/python/tests/test_conflict_dismiss.py +143 -0
- package/python/tests/test_content_guard.py +177 -0
- package/python/tests/test_context_compaction_wiring.py +1 -7
- package/python/tests/test_dispatch.py +0 -6
- package/python/tests/test_domain_evals.py +11 -80
- package/python/tests/test_domain_import.py +0 -6
- package/python/tests/test_domain_reasoners.py +0 -14
- package/python/tests/test_e2e.py +0 -17
- package/python/tests/test_emb_cache.py +7 -17
- package/python/tests/test_embedding_singleton.py +1 -3
- package/python/tests/test_evals_smoke.py +0 -24
- package/python/tests/test_event_spine.py +10 -18
- package/python/tests/test_evidence_semantic.py +1 -14
- package/python/tests/test_evidence_sse.py +3 -6
- package/python/tests/test_file_ingest.py +0 -11
- package/python/tests/test_finance.py +0 -9
- package/python/tests/test_finance_ledger.py +5 -12
- package/python/tests/test_finance_persist.py +8 -12
- package/python/tests/test_gateway.py +3 -22
- package/python/tests/test_governance.py +5 -52
- package/python/tests/test_governance_audit.py +4 -39
- package/python/tests/test_governance_middleware.py +1 -7
- package/python/tests/test_hkc.py +6 -24
- package/python/tests/test_hkc_index_build_manager.py +0 -33
- package/python/tests/test_hkc_manifest_reuse.py +4 -27
- package/python/tests/test_hkc_search_event_sync.py +3 -54
- package/python/tests/test_import_path_guard.py +0 -6
- package/python/tests/test_injection_guard.py +18 -36
- package/python/tests/test_integration.py +5 -29
- package/python/tests/test_itinerary.py +0 -3
- package/python/tests/test_kee_semantic.py +0 -5
- package/python/tests/test_kernel.py +1 -11
- package/python/tests/test_learning.py +3 -12
- package/python/tests/test_llm_admin.py +4 -19
- package/python/tests/test_llm_planner.py +1 -23
- package/python/tests/test_markdown_skills.py +3 -14
- package/python/tests/test_markdown_skills_openclaw.py +4 -9
- package/python/tests/test_mcp_connectors.py +3 -15
- package/python/tests/test_mcp_guardian_discovery.py +2 -6
- package/python/tests/test_mcp_no_memory.py +3 -3
- package/python/tests/test_mcp_policy_gate.py +0 -1
- package/python/tests/test_mcp_server.py +6 -9
- package/python/tests/test_memory_recall.py +1 -4
- package/python/tests/test_metrics.py +0 -8
- package/python/tests/test_multi_chain.py +1 -13
- package/python/tests/test_multimodal.py +5 -18
- package/python/tests/test_negotiation.py +3 -13
- package/python/tests/test_orchestration.py +1 -11
- package/python/tests/test_orchestration_decoupled.py +0 -6
- package/python/tests/test_p3_p5_hardening.py +3 -17
- package/python/tests/test_parallel_dispatch.py +4 -12
- package/python/tests/test_pipeline.py +17 -58
- package/python/tests/test_plan_visible.py +4 -4
- package/python/tests/test_planner.py +0 -7
- package/python/tests/test_policy_gate.py +2 -3
- package/python/tests/test_profile.py +67 -0
- package/python/tests/test_readme_claims.py +163 -0
- package/python/tests/test_reasoner.py +2 -17
- package/python/tests/test_release_check.py +2 -13
- package/python/tests/test_resume_checkpoint.py +7 -15
- package/python/tests/test_routing.py +2 -12
- package/python/tests/test_scheduler.py +7 -10
- package/python/tests/test_search.py +1 -11
- package/python/tests/test_secrets.py +4 -10
- package/python/tests/test_semantic.py +1 -10
- package/python/tests/test_sensitive_write.py +181 -0
- package/python/tests/test_sessions.py +3 -4
- package/python/tests/test_settings.py +2 -18
- package/python/tests/test_shadow_learning.py +3 -43
- package/python/tests/test_shared_write.py +0 -7
- package/python/tests/test_skill_taint_gate.py +1 -10
- package/python/tests/test_skills.py +2 -18
- package/python/tests/test_skills_fetch.py +282 -0
- package/python/tests/test_stats_and_events.py +2 -5
- package/python/tests/test_streaming.py +2 -7
- package/python/tests/test_supervisor_pipeline.py +4 -5
- package/python/tests/test_supervisor_triage.py +4 -10
- package/python/tests/test_taint.py +2 -7
- package/python/tests/test_task_stream.py +1 -12
- package/python/tests/test_thinking_sse.py +0 -1
- package/python/tests/test_tool_center.py +1 -17
- package/python/tests/test_tool_parallel.py +2 -2
- package/python/tests/test_tool_params.py +1 -5
- package/python/tests/test_tool_use.py +0 -13
- package/python/tests/test_toplevel_concurrency.py +3 -4
- package/python/tests/test_tracing.py +7 -41
- package/python/tests/test_truth_crystallizer.py +4 -6
- package/python/tests/test_truth_flow.py +2 -7
- package/python/tests/test_update_check.py +95 -0
- package/python/tests/test_verify_evidence.py +8 -21
- package/python/tests/test_version_sync.py +35 -6
- package/python/tests/test_web_trigger.py +0 -17
- package/python/tools/build_content_lexicon.py +218 -0
- package/python/tools/bundle_web.py +98 -0
- package/python/tools/check_faiss_type.py +0 -1
- package/python/tools/check_log.ps1 +0 -8
- package/python/tools/mypy_baseline.txt +12 -4
- package/python/tools/mypy_ratchet.py +52 -6
- package/python/tools/pack.py +0 -5
- package/python/tools/recheck_conflicts.py +156 -0
- package/python/tools/release_check.py +24 -15
- package/python/tools/restart_foxclaw.ps1 +5 -8
- package/python/tools/shared_write_audit.py +1 -15
- package/python/tools/size_check.py +1 -1
- package/python/tools/strip_comments.py +228 -0
- package/python/vendor/hkc/hkc_api/routes/ability.py +31 -0
- package/python/vendor/hkc/hkc_api/schemas.py +11 -0
- package/python/vendor/hkc/hkc_kee/kee.py +124 -3
- package/python/vendor/hkc/hkc_sdk/base.py +22 -0
- package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
- package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
- package/python/foxclaw/api/app.py +0 -123
- package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
- package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""系统状态 / 模型路由 / 认知调度 / 事件总线 / 真值 / 进程 / 思考链 / Harness / 探活 / 监控与追踪。
|
|
2
2
|
|
|
3
|
-
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.
|
|
4
|
-
晚绑定读取(
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
|
|
4
|
+
晚绑定读取(状态在 `_state`,测试可重绑 `_state._os_instance`);路由用 `@app` 装饰器在 import 时注册。
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
@@ -16,21 +16,19 @@ from foxclaw import __version__
|
|
|
16
16
|
import foxclaw.api as _api
|
|
17
17
|
from .. import app
|
|
18
18
|
from ..middleware import _gateway_on, _GATEWAY
|
|
19
|
-
from ...core import settings
|
|
19
|
+
from ...core import settings, update_check
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
# ── 系统状态 ───────────────────────────────────────────────
|
|
23
|
-
|
|
24
22
|
@app.get("/api/status")
|
|
25
23
|
async def get_system_status():
|
|
26
24
|
"""获取所有 Agent 的状态"""
|
|
27
|
-
return _api.
|
|
25
|
+
return _api.os_instance().get_all_status()
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
@app.get("/api/tokens")
|
|
31
29
|
async def get_token_summary():
|
|
32
30
|
"""Token 消耗摘要(成本可观测性):总量、按 Agent、按模型。"""
|
|
33
|
-
meter = getattr(_api.
|
|
31
|
+
meter = getattr(_api.current_os(), "token_meter", None)
|
|
34
32
|
if meter is None:
|
|
35
33
|
return {"error": "token meter 未启用"}
|
|
36
34
|
return {
|
|
@@ -43,18 +41,16 @@ async def get_token_summary():
|
|
|
43
41
|
@app.get("/api/tokens/report")
|
|
44
42
|
async def get_token_report():
|
|
45
43
|
"""Token 消耗的人类可读文本报告。"""
|
|
46
|
-
meter = getattr(_api.
|
|
44
|
+
meter = getattr(_api.current_os(), "token_meter", None)
|
|
47
45
|
if meter is None:
|
|
48
46
|
return {"error": "token meter 未启用"}
|
|
49
47
|
return {"report": meter.report()}
|
|
50
48
|
|
|
51
49
|
|
|
52
|
-
# ── 模型路由与系统信息 ─────────────────────────────────────
|
|
53
|
-
|
|
54
50
|
@app.get("/api/model")
|
|
55
51
|
async def get_model():
|
|
56
52
|
"""当前模型路由状态 + 可用 provider 列表。"""
|
|
57
|
-
return _api.
|
|
53
|
+
return _api.os_instance().router.info()
|
|
58
54
|
|
|
59
55
|
|
|
60
56
|
@app.get("/api/config/amap")
|
|
@@ -69,24 +65,24 @@ async def get_amap_config():
|
|
|
69
65
|
@app.get("/api/system/info")
|
|
70
66
|
async def get_system_info():
|
|
71
67
|
"""系统级只读信息:版本、运行参数、各 Agent、模块状态(供设置面板展示)。"""
|
|
68
|
+
update_check.maybe_refresh()
|
|
72
69
|
agents = {}
|
|
73
|
-
for aid, ag in _api.
|
|
70
|
+
for aid, ag in _api.os_instance()._agents.items():
|
|
74
71
|
h = ag.harness
|
|
75
72
|
agents[aid] = {
|
|
76
73
|
"name": getattr(ag, "AGENT_NAME", aid),
|
|
77
74
|
"shadow_mode": h.is_shadow_mode,
|
|
78
75
|
"shadow_rounds": h.shadow_rounds,
|
|
79
76
|
"run_count": getattr(h, "_run_count", 0),
|
|
80
|
-
# 降级可见性(B 类修复):语义维度停用时,漂移检测只剩 tool/scope 两维,
|
|
81
|
-
# 跑题不会被拦截 —— 前端需据此提示,不能只在服务端日志里静默。
|
|
82
77
|
"semantic_degraded": getattr(h, "semantic_degraded", False),
|
|
83
78
|
"embedding_mode": getattr(getattr(h, "sem_drift", None), "mode", "unknown"),
|
|
84
79
|
}
|
|
85
80
|
return {
|
|
86
81
|
"version": __version__,
|
|
82
|
+
"update": update_check.status(),
|
|
87
83
|
"data_dir": os.environ.get("FOXCLAW_DATA_DIR", "./foxclaw_data"),
|
|
88
84
|
"shadow_rounds": settings.get_int("FOXCLAW_SHADOW_ROUNDS"),
|
|
89
|
-
"model": _api.
|
|
85
|
+
"model": _api.os_instance().router.info(),
|
|
90
86
|
"amap": {
|
|
91
87
|
"web_key": bool(os.environ.get("AMAP_MAPS_API_KEY")),
|
|
92
88
|
"js_key": bool(os.environ.get("AMAP_JS_KEY")),
|
|
@@ -105,21 +101,19 @@ class ModelSwitchPayload(BaseModel):
|
|
|
105
101
|
async def switch_model(p: ModelSwitchPayload):
|
|
106
102
|
"""运行时切换模型 provider(多模型路由核心能力)。"""
|
|
107
103
|
try:
|
|
108
|
-
return _api.
|
|
104
|
+
return _api.os_instance().router.switch(p.provider, p.model)
|
|
109
105
|
except Exception as e:
|
|
110
106
|
raise HTTPException(400, f"切换失败: {e}")
|
|
111
107
|
|
|
112
108
|
|
|
113
109
|
@app.get("/api/agents/{agent_id}/status")
|
|
114
110
|
async def get_agent_status(agent_id: str):
|
|
115
|
-
agent = _api.
|
|
111
|
+
agent = _api.os_instance().get_agent(agent_id)
|
|
116
112
|
if not agent:
|
|
117
113
|
raise HTTPException(404, f"Agent {agent_id} 不存在")
|
|
118
114
|
return agent.get_status()
|
|
119
115
|
|
|
120
116
|
|
|
121
|
-
# ── 认知调度 / 事件总线 / 真值 / 进程 / 认知状态 ─────────────
|
|
122
|
-
|
|
123
117
|
class SchedulePreviewPayload(BaseModel):
|
|
124
118
|
task: str
|
|
125
119
|
|
|
@@ -136,14 +130,14 @@ async def scheduler_preview(payload: SchedulePreviewPayload):
|
|
|
136
130
|
@app.get("/api/scheduler/stats")
|
|
137
131
|
async def scheduler_stats():
|
|
138
132
|
"""调度器记账:并发配额 + 提交/运行/完成/失败/峰值(可观测性)。"""
|
|
139
|
-
return _api.
|
|
133
|
+
return _api.os_instance().scheduler.stats()
|
|
140
134
|
|
|
141
135
|
|
|
142
136
|
@app.get("/api/bus/history")
|
|
143
|
-
async def bus_history(channel: str = None, limit: int = 30):
|
|
137
|
+
async def bus_history(channel: str | None = None, limit: int = 30):
|
|
144
138
|
"""系统事件总线历史:观测执行→审计→记忆的真值流转。"""
|
|
145
139
|
try:
|
|
146
|
-
return {"events": _api.
|
|
140
|
+
return {"events": _api.os_instance().system_bus.history(channel, limit)}
|
|
147
141
|
except Exception:
|
|
148
142
|
return {"events": []}
|
|
149
143
|
|
|
@@ -151,16 +145,16 @@ async def bus_history(channel: str = None, limit: int = 30):
|
|
|
151
145
|
@app.get("/api/truth/last")
|
|
152
146
|
async def truth_last():
|
|
153
147
|
"""真值管线最近一次的 trace 与记忆更新。"""
|
|
154
|
-
tp = _api.
|
|
148
|
+
tp = _api.os_instance().truth_pipeline
|
|
155
149
|
return {"last_trace": tp.last_trace, "last_memory_update": tp.last_memory_update}
|
|
156
150
|
|
|
157
151
|
|
|
158
152
|
@app.get("/api/processes")
|
|
159
|
-
async def list_processes(state: str = None, limit: int = 30):
|
|
153
|
+
async def list_processes(state: str | None = None, limit: int = 30):
|
|
160
154
|
"""进程列表:观测任务作为"进程"的生命周期状态。"""
|
|
161
155
|
try:
|
|
162
|
-
return {"processes": _api.
|
|
163
|
-
"stats": _api.
|
|
156
|
+
return {"processes": _api.os_instance().process_manager.list(state, limit),
|
|
157
|
+
"stats": _api.os_instance().process_manager.stats()}
|
|
164
158
|
except Exception:
|
|
165
159
|
return {"processes": [], "stats": {}}
|
|
166
160
|
|
|
@@ -168,7 +162,7 @@ async def list_processes(state: str = None, limit: int = 30):
|
|
|
168
162
|
@app.get("/api/processes/{process_id}")
|
|
169
163
|
async def get_process(process_id: str):
|
|
170
164
|
"""单个进程详情:含完整状态历史(state_history)。"""
|
|
171
|
-
rec = _api.
|
|
165
|
+
rec = _api.os_instance().process_manager.get(process_id)
|
|
172
166
|
return rec.to_dict() if rec else {"error": "not_found"}
|
|
173
167
|
|
|
174
168
|
|
|
@@ -176,21 +170,18 @@ async def get_process(process_id: str):
|
|
|
176
170
|
async def cognitive_state():
|
|
177
171
|
"""认知状态快照:系统此刻在想什么(目标/焦点/注意力/激活记忆/活跃进程)。"""
|
|
178
172
|
try:
|
|
179
|
-
return _api.
|
|
173
|
+
return _api.os_instance().cognitive_state.snapshot()
|
|
180
174
|
except Exception as e:
|
|
181
175
|
return {"error": str(e)}
|
|
182
176
|
|
|
183
177
|
|
|
184
|
-
# ── ThoughtChain 查询 ──────────────────────────────────────
|
|
185
|
-
|
|
186
178
|
@app.get("/api/thoughtchain/{agent_id}/chain")
|
|
187
|
-
async def get_thought_chain(agent_id: str, session_id: str = None):
|
|
179
|
+
async def get_thought_chain(agent_id: str, session_id: str | None = None):
|
|
188
180
|
"""获取 Agent 的推理链"""
|
|
189
|
-
tc = _api.
|
|
181
|
+
tc = _api.os_instance().thought_chain
|
|
190
182
|
if session_id:
|
|
191
183
|
nodes = tc.get_chain(session_id, agent_id)
|
|
192
184
|
else:
|
|
193
|
-
# 返回最近 20 个节点
|
|
194
185
|
nodes = tc.get_blocked_nodes(agent_id, limit=20)
|
|
195
186
|
return {"nodes": [n.to_dict() for n in nodes]}
|
|
196
187
|
|
|
@@ -198,7 +189,7 @@ async def get_thought_chain(agent_id: str, session_id: str = None):
|
|
|
198
189
|
@app.get("/api/thoughtchain/{agent_id}/blocked")
|
|
199
190
|
async def get_blocked_nodes(agent_id: str, limit: int = 20):
|
|
200
191
|
"""获取被 Harness 截断的节点(审计用)"""
|
|
201
|
-
tc = _api.
|
|
192
|
+
tc = _api.os_instance().thought_chain
|
|
202
193
|
nodes = tc.get_blocked_nodes(agent_id, limit=limit)
|
|
203
194
|
return {"blocked": [n.to_dict() for n in nodes], "count": len(nodes)}
|
|
204
195
|
|
|
@@ -206,7 +197,7 @@ async def get_blocked_nodes(agent_id: str, limit: int = 20):
|
|
|
206
197
|
@app.get("/api/thoughtchain/{agent_id}/trend")
|
|
207
198
|
async def get_drift_trend(agent_id: str, limit: int = 100):
|
|
208
199
|
"""获取漂移趋势数据(前端可视化用)"""
|
|
209
|
-
tc = _api.
|
|
200
|
+
tc = _api.os_instance().thought_chain
|
|
210
201
|
trend = tc.get_delta_trend(agent_id, limit=limit)
|
|
211
202
|
return {"trend": trend, "agent_id": agent_id}
|
|
212
203
|
|
|
@@ -214,15 +205,13 @@ async def get_drift_trend(agent_id: str, limit: int = 100):
|
|
|
214
205
|
@app.get("/api/thoughtchain/{agent_id}/summary")
|
|
215
206
|
async def get_thought_summary(agent_id: str):
|
|
216
207
|
"""获取 ThoughtChain 健康摘要"""
|
|
217
|
-
return _api.
|
|
218
|
-
|
|
208
|
+
return _api.os_instance().thought_chain.get_summary(agent_id)
|
|
219
209
|
|
|
220
|
-
# ── Harness 状态 ───────────────────────────────────────────
|
|
221
210
|
|
|
222
211
|
@app.get("/api/harness/{agent_id}/status")
|
|
223
212
|
async def get_harness_status(agent_id: str):
|
|
224
213
|
"""获取 Harness 状态:阈值、shadow mode、policy 摘要"""
|
|
225
|
-
agent = _api.
|
|
214
|
+
agent = _api.os_instance().get_agent(agent_id)
|
|
226
215
|
if not agent:
|
|
227
216
|
raise HTTPException(404, f"Agent {agent_id} 不存在")
|
|
228
217
|
return {
|
|
@@ -236,15 +225,12 @@ async def get_harness_status(agent_id: str):
|
|
|
236
225
|
@app.get("/api/harness/{agent_id}/policy")
|
|
237
226
|
async def get_policy(agent_id: str):
|
|
238
227
|
"""获取 Agent 的 Policy YAML(完整内容)"""
|
|
239
|
-
agent = _api.
|
|
228
|
+
agent = _api.os_instance().get_agent(agent_id)
|
|
240
229
|
if not agent:
|
|
241
230
|
raise HTTPException(404, f"Agent {agent_id} 不存在")
|
|
242
231
|
return agent.harness._policy
|
|
243
232
|
|
|
244
233
|
|
|
245
|
-
# ── 健康探活 ───────────────────────────────────────────────
|
|
246
|
-
# 标准探活端点(开放,不在鉴权前缀内、不触发 HKC 装配)。星球 UI 的 connect() 用它探活,
|
|
247
|
-
# 也供部署/监控用。放在静态挂载("/")之前,否则会被 StaticFiles 的 catch-all 吞成 404。
|
|
248
234
|
@app.get("/health")
|
|
249
235
|
async def health():
|
|
250
236
|
"""存活探针(liveness):浅、快,不触发子系统。"""
|
|
@@ -255,7 +241,7 @@ async def health():
|
|
|
255
241
|
async def health_ready():
|
|
256
242
|
"""就绪探针(readiness):逐子系统深度检查。**开放**(探针无需鉴权),
|
|
257
243
|
组件只回 ok/stub/down,不泄露 provider 名等细节。不触发 HKC 惰性初始化。"""
|
|
258
|
-
o = _api.
|
|
244
|
+
o = _api.current_os()
|
|
259
245
|
comps = {}
|
|
260
246
|
comps["hmr"] = "ok" if getattr(o, "hmr", None) is not None else "down"
|
|
261
247
|
_router = getattr(o, "router", None)
|
|
@@ -264,9 +250,9 @@ async def health_ready():
|
|
|
264
250
|
elif getattr(_router, "is_stub", True):
|
|
265
251
|
comps["router"] = "stub"
|
|
266
252
|
else:
|
|
267
|
-
comps["router"] = "ok"
|
|
253
|
+
comps["router"] = "ok"
|
|
268
254
|
comps["scheduler"] = "ok" if getattr(o, "scheduler", None) is not None else "down"
|
|
269
|
-
hkc_svc = getattr(o, "_hkc_service", None)
|
|
255
|
+
hkc_svc = getattr(o, "_hkc_service", None)
|
|
270
256
|
comps["hkc"] = ("ok" if (hkc_svc is not None and getattr(hkc_svc, "available", False))
|
|
271
257
|
else ("uninitialized" if hkc_svc is None else "degraded"))
|
|
272
258
|
comps["mcp_server"] = "on" if getattr(o, "mcp_manager", None) is not None else "off"
|
|
@@ -274,8 +260,6 @@ async def health_ready():
|
|
|
274
260
|
return {"status": "degraded" if down else "ok", "components": comps, "version": __version__}
|
|
275
261
|
|
|
276
262
|
|
|
277
|
-
# ── 监控 / 追踪 / 网关 ──────────────────────────────────────
|
|
278
|
-
|
|
279
263
|
@app.get("/api/metrics")
|
|
280
264
|
async def metrics_json():
|
|
281
265
|
"""可观测:各端点/LLM 的计数、错误率、延迟分位(p50/p95/avg)+ uptime。"""
|
|
@@ -285,7 +269,7 @@ async def metrics_json():
|
|
|
285
269
|
|
|
286
270
|
@app.get("/api/traces")
|
|
287
271
|
async def list_traces(limit: int = 30, persisted: bool = False,
|
|
288
|
-
slow_ms: float = None, errors_only: bool = False):
|
|
272
|
+
slow_ms: float | None = None, errors_only: bool = False):
|
|
289
273
|
"""请求链路。默认看内存里最近若干条;persisted=1 查**已落库的历史**
|
|
290
274
|
(可加 slow_ms / errors_only 过滤)——「找出昨天那次慢请求」走这里。"""
|
|
291
275
|
from ...core.tracing import TRACES
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""任务执行 / 流式直答 / 流式任务 路由与多模态护栏。
|
|
2
2
|
|
|
3
|
-
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
|
|
4
4
|
晚绑定读取;路由用 `@app` 装饰器在 import 时注册。流式限流器 `_STREAM_LIMITER` 为
|
|
5
5
|
模块级单例,`__init__.py` 会 re-export(测试复位其桶容量)。
|
|
6
6
|
"""
|
|
@@ -19,18 +19,14 @@ from ...mcp_server import _RateLimiter as _RL
|
|
|
19
19
|
from ...core import settings
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
# ── 请求模型 ───────────────────────────────────────────────
|
|
23
|
-
|
|
24
22
|
class TaskRequest(BaseModel):
|
|
25
23
|
task: str
|
|
26
24
|
agent_id: str = "supervisor"
|
|
27
25
|
context: dict = {}
|
|
28
|
-
images: list = []
|
|
29
|
-
history: list = []
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
attachments: list = [] # 聊天附件:[{name,size,mime,url,text}];text 为提取正文(可为空),
|
|
33
|
-
# 仅 /chat/stream 消费;随问题注入 LLM + 落盘会话供回看
|
|
26
|
+
images: list = []
|
|
27
|
+
history: list = []
|
|
28
|
+
session_id: str = ""
|
|
29
|
+
attachments: list = []
|
|
34
30
|
|
|
35
31
|
|
|
36
32
|
class GoalRequest(BaseModel):
|
|
@@ -40,8 +36,6 @@ class GoalRequest(BaseModel):
|
|
|
40
36
|
entities: list[str] = []
|
|
41
37
|
|
|
42
38
|
|
|
43
|
-
# ── 任务执行 ───────────────────────────────────────────────
|
|
44
|
-
|
|
45
39
|
@app.post("/api/task")
|
|
46
40
|
async def run_task(req: TaskRequest):
|
|
47
41
|
"""执行任务,返回结果 + ThoughtChain 摘要。
|
|
@@ -53,33 +47,31 @@ async def run_task(req: TaskRequest):
|
|
|
53
47
|
同一线程池,故无嵌套线程池死锁;
|
|
54
48
|
· harness 累积态已在 harness.py 用 RLock 保护(嵌入在锁外,故并行不受锁影响)。"""
|
|
55
49
|
def _work():
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
lambda: asyncio.run(_api._os_instance.run(req.task, req.agent_id,
|
|
50
|
+
return _api.os_instance().scheduler.run(
|
|
51
|
+
lambda: asyncio.run(_api.os_instance().run(req.task, req.agent_id,
|
|
59
52
|
context=req.context)),
|
|
60
53
|
agent_id=req.agent_id, task=req.task)
|
|
61
54
|
return await asyncio.to_thread(_work)
|
|
62
55
|
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
_STREAM_LIMITER = _RL(settings.get_int("FOXCLAW_STREAM_RATE")) # 次/分
|
|
57
|
+
_STREAM_LIMITER = _RL(settings.get_int("FOXCLAW_STREAM_RATE"))
|
|
66
58
|
_STREAM_SEM = _threading.Semaphore(settings.get_int("FOXCLAW_STREAM_CONCURRENCY"))
|
|
67
59
|
|
|
68
60
|
|
|
69
61
|
_MAX_IMAGES = 6
|
|
70
|
-
_MAX_IMG_B64 = 10 * 1024 * 1024
|
|
71
|
-
_MAX_IMG_TOTAL = 20 * 1024 * 1024
|
|
62
|
+
_MAX_IMG_B64 = 10 * 1024 * 1024
|
|
63
|
+
_MAX_IMG_TOTAL = 20 * 1024 * 1024
|
|
72
64
|
_ALLOWED_IMG_MT = {"image/png", "image/jpeg", "image/webp", "image/gif"}
|
|
73
65
|
|
|
74
|
-
_MAX_ATTACHMENTS = 6
|
|
75
|
-
_ATTACH_TEXT_MAX = 20000
|
|
76
|
-
_ATTACH_TEXT_TOTAL = 60000
|
|
66
|
+
_MAX_ATTACHMENTS = 6
|
|
67
|
+
_ATTACH_TEXT_MAX = 20000
|
|
68
|
+
_ATTACH_TEXT_TOTAL = 60000
|
|
77
69
|
|
|
78
70
|
|
|
79
71
|
def _sanitize_images(images):
|
|
80
72
|
"""校验/裁剪前端传来的多模态图片:限数量、限单图大小、限累计总量、白名单类型。
|
|
81
73
|
防超大 payload/OOM(前端已缩放,这是对直连/恶意客户端的后端自保)。"""
|
|
82
|
-
out = []
|
|
74
|
+
out: list[dict] = []
|
|
83
75
|
total = 0
|
|
84
76
|
if not isinstance(images, list):
|
|
85
77
|
return out
|
|
@@ -92,7 +84,7 @@ def _sanitize_images(images):
|
|
|
92
84
|
continue
|
|
93
85
|
if not isinstance(d, str) or not d or len(d) > _MAX_IMG_B64:
|
|
94
86
|
continue
|
|
95
|
-
if total + len(d) > _MAX_IMG_TOTAL:
|
|
87
|
+
if total + len(d) > _MAX_IMG_TOTAL:
|
|
96
88
|
break
|
|
97
89
|
total += len(d)
|
|
98
90
|
out.append({"media_type": mt, "data": d})
|
|
@@ -103,7 +95,7 @@ def _sanitize_attachments(attachments):
|
|
|
103
95
|
"""校验/裁剪前端传来的聊天附件:限数量、字段清洗、正文长度上限 + 累计上限。
|
|
104
96
|
附件是前端经 /api/upload 上传后带回的引用({name,size,mime,url,text});这里只做
|
|
105
97
|
直连/恶意客户端的自保,不信任 text 之外任何字段用于文件系统操作(下载按 url 里的 id 白名单)。"""
|
|
106
|
-
out = []
|
|
98
|
+
out: list[dict] = []
|
|
107
99
|
total = 0
|
|
108
100
|
if not isinstance(attachments, list):
|
|
109
101
|
return out
|
|
@@ -114,11 +106,12 @@ def _sanitize_attachments(attachments):
|
|
|
114
106
|
text = str(a.get("text") or "")[:_ATTACH_TEXT_MAX]
|
|
115
107
|
if not name and not text:
|
|
116
108
|
continue
|
|
117
|
-
if total + len(text) > _ATTACH_TEXT_TOTAL:
|
|
109
|
+
if total + len(text) > _ATTACH_TEXT_TOTAL:
|
|
118
110
|
text = text[:_ATTACH_TEXT_TOTAL - total]
|
|
119
111
|
total += len(text)
|
|
120
|
-
try:
|
|
121
|
-
|
|
112
|
+
try:
|
|
113
|
+
raw_size = a.get("size")
|
|
114
|
+
size = int(raw_size) if raw_size else 0
|
|
122
115
|
except (TypeError, ValueError):
|
|
123
116
|
size = 0
|
|
124
117
|
out.append({
|
|
@@ -142,12 +135,12 @@ async def chat_stream(req: TaskRequest, request: Request):
|
|
|
142
135
|
yield "data: " + json.dumps({"error": "rate limited"}) + "\n\n"
|
|
143
136
|
return StreamingResponse(_rl(), media_type="text/event-stream")
|
|
144
137
|
|
|
145
|
-
agent = _api.
|
|
138
|
+
agent = _api.os_instance()._agents.get(req.agent_id) or _api.os_instance().supervisor
|
|
146
139
|
imgs = _sanitize_images(req.images)
|
|
147
140
|
atts = _sanitize_attachments(req.attachments)
|
|
148
141
|
|
|
149
142
|
async def async_sse():
|
|
150
|
-
if not _STREAM_SEM.acquire(blocking=False):
|
|
143
|
+
if not _STREAM_SEM.acquire(blocking=False):
|
|
151
144
|
yield "data: " + json.dumps({"error": "busy, try again later"}) + "\n\n"
|
|
152
145
|
return
|
|
153
146
|
q: asyncio.Queue = asyncio.Queue()
|
|
@@ -158,12 +151,9 @@ async def chat_stream(req: TaskRequest, request: Request):
|
|
|
158
151
|
_parts = []
|
|
159
152
|
|
|
160
153
|
def _on_thinking(delta: str) -> None:
|
|
161
|
-
# 推理走独立事件,不混进正文:前端折叠展示,正文仍只收 {"delta"}。
|
|
162
154
|
loop.call_soon_threadsafe(q.put_nowait, {"thinking": delta})
|
|
163
155
|
|
|
164
156
|
def _on_evidence(evs: list) -> None:
|
|
165
|
-
# 执行证据(核验结论 + 来源链接/文件)整块送:前端挂在答案下方供核对,
|
|
166
|
-
# 不进正文(正文是给用户读的答案,证据是给人查的凭据)。
|
|
167
157
|
loop.call_soon_threadsafe(q.put_nowait, {"evidence": evs})
|
|
168
158
|
|
|
169
159
|
try:
|
|
@@ -177,22 +167,21 @@ async def chat_stream(req: TaskRequest, request: Request):
|
|
|
177
167
|
except Exception as e:
|
|
178
168
|
loop.call_soon_threadsafe(q.put_nowait, {"error": str(e)})
|
|
179
169
|
finally:
|
|
180
|
-
# 落盘会话历史(问 + 答),供"会话/所有会话"回看。断连中止也保留已生成部分。
|
|
181
170
|
if req.session_id:
|
|
182
171
|
try:
|
|
183
|
-
_api.
|
|
172
|
+
_api.os_instance().sessions.append(req.session_id, "user", req.task, attachments=atts)
|
|
184
173
|
_reply = "".join(_parts)
|
|
185
174
|
if _reply:
|
|
186
|
-
_api.
|
|
175
|
+
_api.os_instance().sessions.append(req.session_id, "assistant", _reply)
|
|
187
176
|
except Exception:
|
|
188
177
|
pass
|
|
189
|
-
loop.call_soon_threadsafe(q.put_nowait, None)
|
|
178
|
+
loop.call_soon_threadsafe(q.put_nowait, None)
|
|
190
179
|
|
|
191
180
|
_threading.Thread(target=_producer, daemon=True, name="chat-stream").start()
|
|
192
181
|
try:
|
|
193
182
|
while True:
|
|
194
183
|
if await request.is_disconnected():
|
|
195
|
-
stop.set(); break
|
|
184
|
+
stop.set(); break
|
|
196
185
|
try:
|
|
197
186
|
item = await asyncio.wait_for(q.get(), timeout=0.5)
|
|
198
187
|
except asyncio.TimeoutError:
|
|
@@ -200,15 +189,11 @@ async def chat_stream(req: TaskRequest, request: Request):
|
|
|
200
189
|
if item is None:
|
|
201
190
|
break
|
|
202
191
|
if "thinking" in item:
|
|
203
|
-
# 命名事件(SSE 规范):event: thinking + data。老客户端只认 data: 行也不会被
|
|
204
|
-
# 误导成正文(delta 字段不存在);新前端按事件名折叠展示。
|
|
205
192
|
yield ("event: thinking\ndata: "
|
|
206
193
|
+ json.dumps({"thinking": item["thinking"]}, ensure_ascii=False)
|
|
207
194
|
+ "\n\n")
|
|
208
195
|
continue
|
|
209
196
|
if "evidence" in item:
|
|
210
|
-
# 与 thinking 同理走命名事件:老客户端拿不到这个事件名也不会把凭据
|
|
211
|
-
# 当成正文拼进回答。
|
|
212
197
|
yield ("event: evidence\ndata: "
|
|
213
198
|
+ json.dumps({"evidence": item["evidence"]}, ensure_ascii=False)
|
|
214
199
|
+ "\n\n")
|
|
@@ -235,16 +220,13 @@ async def task_stream(req: TaskRequest, request: Request):
|
|
|
235
220
|
yield "data: " + json.dumps({"error": "rate limited"}) + "\n\n"
|
|
236
221
|
return StreamingResponse(_rl(), media_type="text/event-stream")
|
|
237
222
|
|
|
238
|
-
agent = _api.
|
|
239
|
-
# 【P3-3】P1-1 落地后主管走统一 pipeline(supervisor_stages),支持 run_task_stream,
|
|
240
|
-
# 不再回落 assistant(助手已下放为技能)。未知 agent_id 仍回落主管兼容旧客户端
|
|
241
|
-
# (旧前端可能还传 "assistant"/"admin"),但打 warning 不静默。
|
|
223
|
+
agent = _api.os_instance()._agents.get(req.agent_id)
|
|
242
224
|
if agent is None:
|
|
243
225
|
print(f"[Foxclaw API] ⚠ /api/task/stream 收到未知 agent_id={req.agent_id},回落 supervisor")
|
|
244
|
-
agent = _api.
|
|
226
|
+
agent = _api.os_instance().supervisor
|
|
245
227
|
|
|
246
228
|
async def async_sse():
|
|
247
|
-
if not _STREAM_SEM.acquire(blocking=False):
|
|
229
|
+
if not _STREAM_SEM.acquire(blocking=False):
|
|
248
230
|
yield "data: " + json.dumps({"error": "busy, try again later"}) + "\n\n"
|
|
249
231
|
return
|
|
250
232
|
q: asyncio.Queue = asyncio.Queue()
|
|
@@ -259,13 +241,13 @@ async def task_stream(req: TaskRequest, request: Request):
|
|
|
259
241
|
except Exception as e:
|
|
260
242
|
loop.call_soon_threadsafe(q.put_nowait, {"error": str(e)})
|
|
261
243
|
finally:
|
|
262
|
-
loop.call_soon_threadsafe(q.put_nowait, None)
|
|
244
|
+
loop.call_soon_threadsafe(q.put_nowait, None)
|
|
263
245
|
|
|
264
246
|
_threading.Thread(target=_producer, daemon=True, name="task-stream-sse").start()
|
|
265
247
|
try:
|
|
266
248
|
while True:
|
|
267
249
|
if await request.is_disconnected():
|
|
268
|
-
stop.set(); break
|
|
250
|
+
stop.set(); break
|
|
269
251
|
try:
|
|
270
252
|
item = await asyncio.wait_for(q.get(), timeout=0.5)
|
|
271
253
|
except asyncio.TimeoutError:
|
|
@@ -45,21 +45,17 @@ async def harness_websocket(websocket: WebSocket):
|
|
|
45
45
|
"""
|
|
46
46
|
api_key = os.environ.get("FOXCLAW_API_KEY", "").strip()
|
|
47
47
|
if api_key:
|
|
48
|
-
# 【P3-loopback】与 HTTP 中间件同口径:本机(127.0.0.1 / ::1)WS 客户端免 ?key=
|
|
49
|
-
# 设 FOXCLAW_LOOPBACK_BYPASS=0 可关。详见 foxclaw.api.middleware._loopback_bypass_on
|
|
50
48
|
ws_host = websocket.client.host if websocket.client else ""
|
|
51
49
|
if not (_loopback_bypass_on() and _is_loopback_client(ws_host)):
|
|
52
50
|
provided = (websocket.query_params.get("key")
|
|
53
51
|
or websocket.headers.get("x-api-key"))
|
|
54
|
-
# 【P3-1】常量时间比较(与 HTTP 中间件同一辅助函数);None 由 or "" 兜底——
|
|
55
|
-
# api_key 非空时 not eq("", api_key) 必为 True,与原 None != api_key 行为一致
|
|
56
52
|
if not _constant_time_eq(provided or "", api_key):
|
|
57
|
-
await websocket.close(code=1008)
|
|
53
|
+
await websocket.close(code=1008)
|
|
58
54
|
return
|
|
59
55
|
await manager.connect(websocket)
|
|
60
56
|
try:
|
|
61
57
|
while True:
|
|
62
|
-
await websocket.receive_text()
|
|
58
|
+
await websocket.receive_text()
|
|
63
59
|
except WebSocketDisconnect:
|
|
64
60
|
manager.disconnect(websocket)
|
|
65
61
|
|
|
@@ -35,11 +35,9 @@ def _prewarm_hkc(osi):
|
|
|
35
35
|
def _run():
|
|
36
36
|
t0 = _t.time()
|
|
37
37
|
try:
|
|
38
|
-
svc = getattr(osi, "hkc", None)
|
|
38
|
+
svc = getattr(osi, "hkc", None)
|
|
39
39
|
if not svc or not getattr(svc, "available", False):
|
|
40
40
|
return
|
|
41
|
-
# 搜索索引已在 assemble() 内构建(向量索引持久化加载优先,避免每次启动全量
|
|
42
|
-
# embedding 把 CPU 打满、启动卡几分钟),这里不再重复 build_index。
|
|
43
41
|
try:
|
|
44
42
|
lr = getattr(osi, "learning_runtime", None)
|
|
45
43
|
if lr is not None:
|
|
@@ -50,6 +48,5 @@ def _prewarm_hkc(osi):
|
|
|
50
48
|
except Exception as e:
|
|
51
49
|
print(f"[Foxclaw API] · HKC 预热未启用(可选): {e}")
|
|
52
50
|
|
|
53
|
-
# 晚绑定:把守护线程挂到主模块 `__init__.py` 的 `_prewarm_thread`(lifespan 关闭前需 join)。
|
|
54
51
|
_api._prewarm_thread = threading.Thread(target=_run, name="hkc-prewarm", daemon=True)
|
|
55
52
|
_api._prewarm_thread.start()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Behavior Spec:行为规范(硬边界)与 Persona(可调人格)分层。"""
|
|
2
|
+
from .spec import (
|
|
3
|
+
BehaviorLoadError,
|
|
4
|
+
BehaviorSpec,
|
|
5
|
+
Rule,
|
|
6
|
+
load,
|
|
7
|
+
local_path,
|
|
8
|
+
prompt_block,
|
|
9
|
+
resolve,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"BehaviorLoadError",
|
|
14
|
+
"BehaviorSpec",
|
|
15
|
+
"Rule",
|
|
16
|
+
"load",
|
|
17
|
+
"local_path",
|
|
18
|
+
"prompt_block",
|
|
19
|
+
"resolve",
|
|
20
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Foxclaw 行为规范 v1 —— 可执行的检查点清单
|
|
2
|
+
#
|
|
3
|
+
# 这不是规范全文,是「规矩 ↔ 真实检查器」的登记表:
|
|
4
|
+
# · code_check 必须指向仓库里**已经存在**的可调用对象,加载时 import 解析,解析不到即启动失败。
|
|
5
|
+
# 刻意不提供「注册桩」——全放行的桩会让 prompt 断言变绿而拦截为零,正是 core/config.py 反对的
|
|
6
|
+
# 「建了没接线」。
|
|
7
|
+
# · KERNEL_PROMPT【一/三/五/六/七】已覆盖的风格类条款不在此重复登记(两边措辞会漂):
|
|
8
|
+
# read_only_allowed / tool_priority / ask_when_unsure / concise_by_default。
|
|
9
|
+
# · 暂缓登记(同样理由,缺落点不假装覆盖):explain_used_context / confirm_cross_agent /
|
|
10
|
+
# proactive 四条(静默时段、全局静默、只建议不执行、解释为什么现在说)。
|
|
11
|
+
#
|
|
12
|
+
# 字段:
|
|
13
|
+
# id 唯一、稳定、被测试与审计引用;不复用旧 id 表达新语义
|
|
14
|
+
# rule 一句话规矩,注入 system prompt 最前段
|
|
15
|
+
# enforce prompt(只注入) / code(只强制) / both(双保险)
|
|
16
|
+
# code_check 真实检查点,"module:Class.method" 或 "module:func",可多个
|
|
17
|
+
# note 兑现情况:缺什么、卡在哪(不填即已完整兑现)
|
|
18
|
+
|
|
19
|
+
version: 1
|
|
20
|
+
spec_id: foxclaw-behavior
|
|
21
|
+
|
|
22
|
+
prohibitions:
|
|
23
|
+
- id: no_privacy_upload
|
|
24
|
+
rule: 人格、偏好、记忆、账本、行程、屏幕内容不得外发到云端或第三方检索;用户当次显式授权时也只发完成该任务所需的最小上下文
|
|
25
|
+
enforce: prompt
|
|
26
|
+
note: 代码层只有数据目录白名单(core/paths.py 的 FOXCLAW_DATA_DIR + 导入根),没有上传前的出站过滤;privacy_filter 未实现
|
|
27
|
+
|
|
28
|
+
- id: no_impersonation
|
|
29
|
+
rule: 不得以用户身份向第三方发起联系(邮件、消息、表单提交);只产出草稿,发送由用户执行
|
|
30
|
+
enforce: prompt
|
|
31
|
+
note: outbound_guard 未实现;channels 只有签名与信任校验(openclaw_bridge / signing),没有身份代理拦截
|
|
32
|
+
|
|
33
|
+
- id: no_harmful_content
|
|
34
|
+
rule: 不得生成、复述或戏仿违法违规与伤害性内容(教唆犯罪、色情与性暗示、暴力与自残方法、歧视仇恨、侵犯隐私);触及边界只回固定话术,不解释、不延伸、不给替代方案
|
|
35
|
+
enforce: both
|
|
36
|
+
code_check: "foxclaw.core.content_guard:ContentGuard.check"
|
|
37
|
+
note: 检查点已接(core/content_guard.py:内置请求型规则 + 可挂外部平台词表,输入/输出两端确定性拦截);默认 enforce,置 FOXCLAW_CONTENT_GUARD_MODE=audit 可降级观察、off 关闭
|
|
38
|
+
|
|
39
|
+
confirmations:
|
|
40
|
+
- id: confirm_side_effect
|
|
41
|
+
rule: 有副作用的技能调用必须先在当轮取得用户确认;确认是一次性凭据,绑定会话、动作与参数,不可跨轮复用
|
|
42
|
+
enforce: both
|
|
43
|
+
code_check:
|
|
44
|
+
- "foxclaw.kernel.policy_gate:PolicyGate.check_action"
|
|
45
|
+
- "foxclaw.core.confirm_nonce:ConfirmationBroker.consume"
|
|
46
|
+
|
|
47
|
+
- id: confirm_high_sensitivity
|
|
48
|
+
rule: 身份、健康、财务明细、凭据等高敏感内容写入长期记忆前必须显式确认并留痕
|
|
49
|
+
enforce: both
|
|
50
|
+
code_check: "foxclaw.core.sensitive_write:SensitiveWritePolicy.evaluate"
|
|
51
|
+
note: 检查点已接(core/sensitive_write.py,确定性正则 + Luhn,不调模型);默认 audit 灰度不拦截,置 FOXCLAW_SENSITIVE_WRITE_MODE=enforce 才真正拦,被拦内容转待确认队列
|
|
52
|
+
|
|
53
|
+
uncertainty:
|
|
54
|
+
- id: no_silent_failure
|
|
55
|
+
rule: 执行失败必须回读核对并如实告知,不得宣称"已完成";读不到证据就说读不到
|
|
56
|
+
enforce: both
|
|
57
|
+
code_check: "foxclaw.core.verify:verify"
|
|
58
|
+
|
|
59
|
+
interaction:
|
|
60
|
+
- id: no_paternalism
|
|
61
|
+
rule: 不说教、不替用户做不可逆决定;给建议、摆清取舍,选择权留给用户
|
|
62
|
+
enforce: prompt
|