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
|
@@ -20,21 +20,20 @@ class LearningRuntime:
|
|
|
20
20
|
"""office Agent 的学习内核。"""
|
|
21
21
|
|
|
22
22
|
def __init__(self, hmr=None, hkc_provider=None, persist_path=None):
|
|
23
|
-
self.items: dict = {}
|
|
24
|
-
self.review_states: dict = {}
|
|
23
|
+
self.items: dict = {}
|
|
24
|
+
self.review_states: dict = {}
|
|
25
25
|
self.sr = SpacedRepetition()
|
|
26
26
|
self.hmr = hmr
|
|
27
|
-
self._hkc_provider = hkc_provider
|
|
28
|
-
self._lock = threading.RLock()
|
|
27
|
+
self._hkc_provider = hkc_provider
|
|
28
|
+
self._lock = threading.RLock()
|
|
29
29
|
embedder = None
|
|
30
30
|
if hmr is not None and hasattr(hmr, "_hmr"):
|
|
31
31
|
embedder = self._make_embedder(hmr)
|
|
32
32
|
self.graph = KnowledgeGraph(embedder=embedder)
|
|
33
|
-
# persist_path 现在只作「离线兜底队列 + 旧 learning.json 迁移源」;知识真源是 HKC。
|
|
34
33
|
self._persist_path = Path(persist_path) if persist_path else None
|
|
35
|
-
self._hkc_loaded = False
|
|
36
|
-
self._load_file()
|
|
37
|
-
self._ensure_hkc_loaded()
|
|
34
|
+
self._hkc_loaded = False
|
|
35
|
+
self._load_file()
|
|
36
|
+
self._ensure_hkc_loaded()
|
|
38
37
|
|
|
39
38
|
def _make_embedder(self, hmr):
|
|
40
39
|
"""尝试复用 HMR 的 embedding;失败则返回 None(图谱自动降级哈希)。"""
|
|
@@ -46,7 +45,6 @@ class LearningRuntime:
|
|
|
46
45
|
pass
|
|
47
46
|
return None
|
|
48
47
|
|
|
49
|
-
# ── 存储:知识内容+笔记+复习状态 全落 HKC(真源);HKC 拿不到时落离线队列(兜底 A)──
|
|
50
48
|
def _graph_store(self):
|
|
51
49
|
"""惰性拿 HKC 的 graph_store。拿不到(HKC 未装配/未配/异常)返回 None。"""
|
|
52
50
|
if not self._hkc_provider:
|
|
@@ -126,9 +124,9 @@ class LearningRuntime:
|
|
|
126
124
|
if self._hkc_loaded:
|
|
127
125
|
return
|
|
128
126
|
gs = self._graph_store()
|
|
129
|
-
if gs is None:
|
|
127
|
+
if gs is None:
|
|
130
128
|
return
|
|
131
|
-
try:
|
|
129
|
+
try:
|
|
132
130
|
for ku in gs.query_all(limit=5000):
|
|
133
131
|
ex = getattr(ku, "extra", {}) or {}
|
|
134
132
|
if ex.get("kind") != "learning_note":
|
|
@@ -146,9 +144,9 @@ class LearningRuntime:
|
|
|
146
144
|
except Exception:
|
|
147
145
|
pass
|
|
148
146
|
except Exception:
|
|
149
|
-
return
|
|
147
|
+
return
|
|
150
148
|
self._hkc_loaded = True
|
|
151
|
-
if self._persist_path and self._persist_path.exists():
|
|
149
|
+
if self._persist_path and self._persist_path.exists():
|
|
152
150
|
self._save()
|
|
153
151
|
|
|
154
152
|
def _save(self):
|
|
@@ -166,11 +164,11 @@ class LearningRuntime:
|
|
|
166
164
|
if ok:
|
|
167
165
|
if self._persist_path and self._persist_path.exists():
|
|
168
166
|
try:
|
|
169
|
-
self._persist_path.unlink()
|
|
167
|
+
self._persist_path.unlink()
|
|
170
168
|
except Exception:
|
|
171
169
|
pass
|
|
172
170
|
return
|
|
173
|
-
if self._persist_path:
|
|
171
|
+
if self._persist_path:
|
|
174
172
|
try:
|
|
175
173
|
data = {
|
|
176
174
|
"items": [asdict(it) for it in items],
|
|
@@ -183,9 +181,8 @@ class LearningRuntime:
|
|
|
183
181
|
except Exception:
|
|
184
182
|
pass
|
|
185
183
|
|
|
186
|
-
# ── 采集 + 沉淀 ───────────────────────────────────────
|
|
187
184
|
def learn(self, content: str, topic: str = "general",
|
|
188
|
-
source: str = "", tags: list = None) -> dict:
|
|
185
|
+
source: str = "", tags: list | None = None) -> dict:
|
|
189
186
|
"""采集一个知识点 → 存进 HKC(知识真源)+ 间隔重复队列 + 图谱。
|
|
190
187
|
铁律:知识内容+笔记归 HKC,不进 HMR(HMR 只装 token 上下文/工作运行记忆)。"""
|
|
191
188
|
self._ensure_hkc_loaded()
|
|
@@ -195,7 +192,7 @@ class LearningRuntime:
|
|
|
195
192
|
self.items[item.id] = item
|
|
196
193
|
self.review_states[item.id] = ReviewState(item_id=item.id)
|
|
197
194
|
self.graph.add_item(item)
|
|
198
|
-
self._save()
|
|
195
|
+
self._save()
|
|
199
196
|
return {"item": item.to_dict(),
|
|
200
197
|
"review": self.review_states[item.id].to_dict()}
|
|
201
198
|
|
|
@@ -212,7 +209,6 @@ class LearningRuntime:
|
|
|
212
209
|
count += 1
|
|
213
210
|
return count
|
|
214
211
|
|
|
215
|
-
# ── 固化:复习 ────────────────────────────────────────
|
|
216
212
|
def review(self, item_id: str, grade: int) -> dict:
|
|
217
213
|
"""复习一个知识点,提交回忆质量评分(0~5),更新掌握度。"""
|
|
218
214
|
self._ensure_hkc_loaded()
|
|
@@ -237,7 +233,7 @@ class LearningRuntime:
|
|
|
237
233
|
gs = self._graph_store()
|
|
238
234
|
if gs is not None:
|
|
239
235
|
try:
|
|
240
|
-
gs.delete(item_id)
|
|
236
|
+
gs.delete(item_id)
|
|
241
237
|
except Exception:
|
|
242
238
|
pass
|
|
243
239
|
if found:
|
|
@@ -256,7 +252,7 @@ class LearningRuntime:
|
|
|
256
252
|
return {"error": "未知知识点"}
|
|
257
253
|
item.notes.append({"text": text, "ts": datetime.now().isoformat()})
|
|
258
254
|
notes = list(item.notes)
|
|
259
|
-
self._save()
|
|
255
|
+
self._save()
|
|
260
256
|
return {"notes": notes}
|
|
261
257
|
|
|
262
258
|
def due_today(self) -> list:
|
|
@@ -265,7 +261,6 @@ class LearningRuntime:
|
|
|
265
261
|
return [{"item": self.items[s.item_id].to_dict(),
|
|
266
262
|
"review": s.to_dict()} for s in due]
|
|
267
263
|
|
|
268
|
-
# ── 连接:构建图谱 ────────────────────────────────────
|
|
269
264
|
def build_connections(self, semantic_threshold: float = 0.55) -> dict:
|
|
270
265
|
"""自动构建知识连接(同主题 + 语义)。"""
|
|
271
266
|
topic_edges = self.graph.auto_link_by_topic()
|
|
@@ -283,13 +278,11 @@ class LearningRuntime:
|
|
|
283
278
|
"edge_type": etype, "weight": weight, "reason": reason})
|
|
284
279
|
return out
|
|
285
280
|
|
|
286
|
-
# ── 学习策略(对应财务守门人)─────────────────────────
|
|
287
281
|
def strategy_check(self, daily_cap: int = 20) -> list:
|
|
288
282
|
"""学习策略裁决:复习提醒、薄弱主题、负荷预警、连接建议。"""
|
|
289
283
|
verdicts = []
|
|
290
284
|
states = list(self.review_states.values())
|
|
291
285
|
|
|
292
|
-
# 到期复习
|
|
293
286
|
due = self.sr.due_items(states)
|
|
294
287
|
if due:
|
|
295
288
|
verdicts.append(LearningVerdict(
|
|
@@ -297,7 +290,6 @@ class LearningRuntime:
|
|
|
297
290
|
message=f"今天有 {len(due)} 个知识点待复习",
|
|
298
291
|
payload={"count": len(due)}))
|
|
299
292
|
|
|
300
|
-
# 遗忘风险(薄弱项)
|
|
301
293
|
risky = self.sr.forgetting_risk(states)
|
|
302
294
|
if risky:
|
|
303
295
|
verdicts.append(LearningVerdict(
|
|
@@ -305,7 +297,6 @@ class LearningRuntime:
|
|
|
305
297
|
message=f"{len(risky)} 个知识点处于高遗忘风险,建议优先复习",
|
|
306
298
|
payload={"risky": risky[:5]}))
|
|
307
299
|
|
|
308
|
-
# 负荷预警
|
|
309
300
|
load = self.sr.learning_load(states)
|
|
310
301
|
if load["total_due"] > daily_cap:
|
|
311
302
|
verdicts.append(LearningVerdict(
|
|
@@ -313,7 +304,6 @@ class LearningRuntime:
|
|
|
313
304
|
message=f"未来复习量 {load['total_due']} 超过每日上限 {daily_cap},建议分批",
|
|
314
305
|
payload=load))
|
|
315
306
|
|
|
316
|
-
# 孤立知识点(无连接)→ 建议建立关联
|
|
317
307
|
connected = set()
|
|
318
308
|
for e in self.graph.edges:
|
|
319
309
|
connected.add(e.source_id)
|
|
@@ -326,14 +316,12 @@ class LearningRuntime:
|
|
|
326
316
|
payload={"isolated_count": len(isolated)}))
|
|
327
317
|
return verdicts
|
|
328
318
|
|
|
329
|
-
# ── 综合分析(office Agent 核心输出)──────────────────
|
|
330
319
|
def analyze(self) -> dict:
|
|
331
|
-
self._ensure_hkc_loaded()
|
|
320
|
+
self._ensure_hkc_loaded()
|
|
332
321
|
states = list(self.review_states.values())
|
|
333
322
|
masteries = [s.mastery for s in states]
|
|
334
323
|
avg_mastery = round(sum(masteries) / len(masteries), 3) if masteries else 0.0
|
|
335
324
|
|
|
336
|
-
# 按主题统计掌握度
|
|
337
325
|
from collections import defaultdict
|
|
338
326
|
topic_mastery = defaultdict(list)
|
|
339
327
|
for iid, item in self.items.items():
|
|
@@ -346,7 +334,7 @@ class LearningRuntime:
|
|
|
346
334
|
"total_items": len(self.items),
|
|
347
335
|
"avg_mastery": avg_mastery,
|
|
348
336
|
"topic_mastery": topics,
|
|
349
|
-
"weakest_topic": (min(topics, key=topics
|
|
337
|
+
"weakest_topic": (min(topics, key=lambda t: topics[t]) if topics else None),
|
|
350
338
|
"due_today": len(self.sr.due_items(states)),
|
|
351
339
|
"forgetting_risk": self.sr.forgetting_risk(states),
|
|
352
340
|
"learning_load": self.sr.learning_load(states),
|
|
@@ -22,19 +22,16 @@ class SpacedRepetition:
|
|
|
22
22
|
@staticmethod
|
|
23
23
|
def review(state: ReviewState, grade: int) -> ReviewState:
|
|
24
24
|
"""根据本次回忆质量,更新间隔重复状态。返回更新后的 state。"""
|
|
25
|
-
# grade 钳制到 SM-2 定义域 [0,5],防止越界输入让 EF 公式算出无意义值
|
|
26
25
|
grade = max(0, min(5, int(grade)))
|
|
27
26
|
now = datetime.now()
|
|
28
27
|
state.total_reviews += 1
|
|
29
28
|
state.last_review = now.isoformat()
|
|
30
29
|
|
|
31
30
|
if grade < 3:
|
|
32
|
-
# 没记住:重置,短期内再复习
|
|
33
31
|
state.repetitions = 0
|
|
34
32
|
state.interval = 1
|
|
35
33
|
state.lapses += 1
|
|
36
34
|
else:
|
|
37
|
-
# 记住了:按 SM-2 推进间隔
|
|
38
35
|
if state.repetitions == 0:
|
|
39
36
|
state.interval = 1
|
|
40
37
|
elif state.repetitions == 1:
|
|
@@ -43,11 +40,9 @@ class SpacedRepetition:
|
|
|
43
40
|
state.interval = round(state.interval * state.ease_factor)
|
|
44
41
|
state.repetitions += 1
|
|
45
42
|
|
|
46
|
-
# 更新难度因子(SM-2 公式)
|
|
47
43
|
ef = state.ease_factor + (0.1 - (5 - grade) * (0.08 + (5 - grade) * 0.02))
|
|
48
44
|
state.ease_factor = max(1.3, round(ef, 3))
|
|
49
45
|
|
|
50
|
-
# 下次复习时间
|
|
51
46
|
state.due_date = (now + timedelta(days=state.interval)).isoformat()
|
|
52
47
|
return state
|
|
53
48
|
|
|
@@ -79,13 +74,13 @@ class SpacedRepetition:
|
|
|
79
74
|
def learning_load(states: list, days: int = 7) -> dict:
|
|
80
75
|
"""学习负荷(对应财务烧钱速度):未来 N 天的复习量。"""
|
|
81
76
|
from collections import defaultdict
|
|
82
|
-
by_day = defaultdict(int)
|
|
77
|
+
by_day: defaultdict[int, int] = defaultdict(int)
|
|
83
78
|
for s in states:
|
|
84
79
|
d = s.days_until_due
|
|
85
80
|
if 0 <= d <= days:
|
|
86
81
|
by_day[d] += 1
|
|
87
82
|
elif d < 0:
|
|
88
|
-
by_day[0] += 1
|
|
83
|
+
by_day[0] += 1
|
|
89
84
|
total_due = sum(by_day.values())
|
|
90
85
|
return {
|
|
91
86
|
"window_days": days,
|
|
@@ -90,7 +90,6 @@ def build_foxclaw_mcp(os_getter, expose_agents: bool = False) -> FastMCP:
|
|
|
90
90
|
"""os_getter:无参回调,返回当前 Foxclaw 实例(惰性,启动后才有)。
|
|
91
91
|
expose_agents:另开一道开关才暴露 Agent 调度(有副作用面,默认关)。"""
|
|
92
92
|
mcp = FastMCP("Foxclaw Knowledge", stateless_http=True, streamable_http_path="/")
|
|
93
|
-
# 限流(env 可配):读工具较宽松、ask_foxclaw(驱动 LLM,贵)较紧
|
|
94
93
|
read_limiter = _RateLimiter(_env_int("FOXCLAW_MCP_RATE_READ", 120))
|
|
95
94
|
agent_limiter = _RateLimiter(_env_int("FOXCLAW_MCP_RATE_AGENT", 20))
|
|
96
95
|
|
|
@@ -111,7 +110,7 @@ def build_foxclaw_mcp(os_getter, expose_agents: bool = False) -> FastMCP:
|
|
|
111
110
|
"type": str(getattr(h, "ku_type", ""))}
|
|
112
111
|
for h in (hits or [])]
|
|
113
112
|
except Exception:
|
|
114
|
-
return [{"error": "internal error"}]
|
|
113
|
+
return [{"error": "internal error"}]
|
|
115
114
|
|
|
116
115
|
@mcp.tool()
|
|
117
116
|
def knowledge_get(ku_id: str) -> dict:
|
|
@@ -172,7 +171,7 @@ def build_foxclaw_mcp(os_getter, expose_agents: bool = False) -> FastMCP:
|
|
|
172
171
|
|
|
173
172
|
def _work():
|
|
174
173
|
from foxclaw.agents.base import _suppress_memory
|
|
175
|
-
_suppress_memory.set(True)
|
|
174
|
+
_suppress_memory.set(True)
|
|
176
175
|
return os_.scheduler.run(
|
|
177
176
|
lambda: _a.run(os_.run(task, agent)),
|
|
178
177
|
agent_id=agent, task=task)
|
|
@@ -22,14 +22,14 @@ RequestStatus = Literal["pending", "approved", "rejected", "needs_confirmation"]
|
|
|
22
22
|
class CrossAgentRequest:
|
|
23
23
|
"""跨 Agent 请求 — 编排的基本单元。
|
|
24
24
|
一个 Agent 向另一个发起请求,目标 Agent 用自己的守门人裁决。"""
|
|
25
|
-
from_agent: str
|
|
26
|
-
to_agent: str
|
|
25
|
+
from_agent: str
|
|
26
|
+
to_agent: str
|
|
27
27
|
request_type: RequestType
|
|
28
|
-
payload: dict
|
|
28
|
+
payload: dict
|
|
29
29
|
reason: str = ""
|
|
30
30
|
status: RequestStatus = "pending"
|
|
31
|
-
verdict: str = ""
|
|
32
|
-
negotiation: list = field(default_factory=list)
|
|
31
|
+
verdict: str = ""
|
|
32
|
+
negotiation: list = field(default_factory=list)
|
|
33
33
|
id: str = field(default_factory=lambda: f"req_{uuid.uuid4().hex[:10]}")
|
|
34
34
|
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
35
35
|
|
|
@@ -40,10 +40,10 @@ class CrossAgentRequest:
|
|
|
40
40
|
@dataclass
|
|
41
41
|
class GlobalSituation:
|
|
42
42
|
"""全局态势 — 主管的驾驶舱视图,汇总各内核关键指标。"""
|
|
43
|
-
finance: dict = field(default_factory=dict)
|
|
44
|
-
learning: dict = field(default_factory=dict)
|
|
45
|
-
itinerary: dict = field(default_factory=dict)
|
|
46
|
-
alerts: list = field(default_factory=list)
|
|
43
|
+
finance: dict = field(default_factory=dict)
|
|
44
|
+
learning: dict = field(default_factory=dict)
|
|
45
|
+
itinerary: dict = field(default_factory=dict)
|
|
46
|
+
alerts: list = field(default_factory=list)
|
|
47
47
|
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
48
48
|
|
|
49
49
|
def to_dict(self) -> dict:
|
|
@@ -58,13 +58,8 @@ class Orchestrator:
|
|
|
58
58
|
不再反向直接持有含主管的 agents 字典)。"""
|
|
59
59
|
|
|
60
60
|
def __init__(self, caps, auditor=None):
|
|
61
|
-
self._caps = caps
|
|
62
|
-
# 【P1-3】统一治理审计(可空:standalone 无审计)。裁决事件原本**完全没有**——
|
|
63
|
-
# 跨 Agent 委派的批准/拒绝/转人工,事前无任何可观测记录。
|
|
61
|
+
self._caps = caps
|
|
64
62
|
self._auditor = auditor
|
|
65
|
-
# 【深审修复·长期运行泄漏】原为普通 list、只增不减且**全库从不读**——每次跨 Agent 请求
|
|
66
|
-
# append 一个 req → 无界增长。改有界 deque(保留最近 N 条供未来观测,符合本库 token_meter/
|
|
67
|
-
# HookManager 的有界日志惯例);deque.append 原子 ⇒ 并发路由也安全。
|
|
68
63
|
self.request_log: deque = deque(maxlen=1000)
|
|
69
64
|
|
|
70
65
|
@property
|
|
@@ -72,12 +67,11 @@ class Orchestrator:
|
|
|
72
67
|
"""按名字从注册中枢解析 agents(id -> agent 实例)。"""
|
|
73
68
|
return self._caps.get("agents") or {}
|
|
74
69
|
|
|
75
|
-
# ── 全局态势汇总(P3:通用聚合,不懂任何领域内部 API)────────────
|
|
76
70
|
def gather_situation(self) -> GlobalSituation:
|
|
77
71
|
"""通用聚合各 Agent 自报的领域态势(agent.situation()),形成驾驶舱视图。
|
|
78
72
|
编排层不再直接读 finance/learning/itinerary 内核——领域知识已归属各领域 Agent。"""
|
|
79
73
|
sit = GlobalSituation()
|
|
80
|
-
alerts = []
|
|
74
|
+
alerts: list[dict] = []
|
|
81
75
|
for agent in self.agents.values():
|
|
82
76
|
try:
|
|
83
77
|
s = agent.situation()
|
|
@@ -85,12 +79,10 @@ class Orchestrator:
|
|
|
85
79
|
continue
|
|
86
80
|
if not s:
|
|
87
81
|
continue
|
|
88
|
-
# 一个 Agent 可以自报**多个**领域(拓扑 5→3 后,办公学习 Agent 同时覆盖
|
|
89
|
-
# learning 与 itinerary),故 `domains: {name: status}` 与旧的单 `domain` 都收。
|
|
90
82
|
doms = s.get("domains")
|
|
91
83
|
if isinstance(doms, dict) and doms:
|
|
92
84
|
for d, st in doms.items():
|
|
93
|
-
if d and hasattr(sit, d):
|
|
85
|
+
if d and hasattr(sit, d):
|
|
94
86
|
setattr(sit, d, st or {})
|
|
95
87
|
else:
|
|
96
88
|
domain = s.get("domain")
|
|
@@ -102,24 +94,16 @@ class Orchestrator:
|
|
|
102
94
|
.get(a.get("severity"), 3))
|
|
103
95
|
return sit
|
|
104
96
|
|
|
105
|
-
# ── 跨 Agent 请求路由(P3:通用委托,由目标 Agent 用自己的守门人裁决)──────
|
|
106
97
|
def route_request(self, req: CrossAgentRequest) -> CrossAgentRequest:
|
|
107
98
|
"""把请求路由给目标 Agent,由**目标 Agent**用自己的守门人裁决(编排层不懂领域)。"""
|
|
108
99
|
target = self.agents.get(req.to_agent)
|
|
109
100
|
if target is None:
|
|
110
|
-
# 【P1-0】原为 status="approved"(默认放行)—— 那是 fail-open:发起方会拿到
|
|
111
|
-
# "已批准",但**没有任何 Agent 真的处理过这个请求**。后果是实打实的,例如
|
|
112
|
-
# 行程 Agent 向未注册的 finance 申请差旅预算,会直接拿到批准并据此下单。
|
|
113
|
-
# 与下方「裁决异常 → 转人工确认」同属一条原则:裁决权不在编排层,凡未真正
|
|
114
|
-
# 裁决过的,都不能算通过。改为 needs_confirmation 同样"不崩",只是不再说谎。
|
|
115
101
|
req.status = "needs_confirmation"
|
|
116
102
|
req.verdict = f"目标 Agent '{req.to_agent}' 不存在,无法裁决,转人工确认"
|
|
117
103
|
else:
|
|
118
104
|
try:
|
|
119
105
|
target.handle_cross_request(req)
|
|
120
106
|
except Exception as e:
|
|
121
|
-
# 守门人裁决崩溃(如畸形 payload 让解析抛错)必须 **fail-safe**:转人工确认,
|
|
122
|
-
# 绝不自动放行——对守门人而言,裁决失败时放行=fail-open,是安全隐患。
|
|
123
107
|
req.status = "needs_confirmation"
|
|
124
108
|
req.verdict = f"裁决异常,转人工确认: {e}"
|
|
125
109
|
self.request_log.append(req)
|
|
@@ -143,7 +127,6 @@ class Orchestrator:
|
|
|
143
127
|
"negotiation_round": len(req.negotiation)},
|
|
144
128
|
correlation_id=req.id)
|
|
145
129
|
|
|
146
|
-
# ── 协商闭环:被拒 → 发起方让步 → 再裁决 ──────────────────────────
|
|
147
130
|
def negotiate(self, req: CrossAgentRequest, max_rounds: int = 3) -> CrossAgentRequest:
|
|
148
131
|
"""跑协商协议。原先裁决是**终局**:财务说预算不足→rejected→到此为止,用户只拿到
|
|
149
132
|
一句"被拒绝"。现在:发起方可拿着拒绝理由降标准重来(改高铁/换酒店/减一天)。
|
|
@@ -165,17 +148,17 @@ class Orchestrator:
|
|
|
165
148
|
"verdict": req.verdict,
|
|
166
149
|
"payload": dict(req.payload)})
|
|
167
150
|
if req.status != "rejected":
|
|
168
|
-
break
|
|
151
|
+
break
|
|
169
152
|
requester = self.agents.get(req.from_agent)
|
|
170
153
|
if requester is None:
|
|
171
154
|
break
|
|
172
155
|
try:
|
|
173
|
-
revised = requester.concede(req, req.verdict)
|
|
156
|
+
revised = requester.concede(req, req.verdict)
|
|
174
157
|
except Exception as e:
|
|
175
158
|
req.negotiation.append({"round": rnd, "concede_error": str(e)[:120]})
|
|
176
|
-
break
|
|
159
|
+
break
|
|
177
160
|
if not revised or revised == req.payload:
|
|
178
|
-
break
|
|
161
|
+
break
|
|
179
162
|
req.negotiation[-1]["conceded_to"] = dict(revised)
|
|
180
163
|
req.payload = revised
|
|
181
164
|
return req
|