foxclaw 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/CHANGELOG.md +57 -0
- package/python/CHANGELOG_CN.md +57 -0
- package/python/README.md +11 -2
- package/python/README_CN.md +32 -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/RELEASE.md +4 -3
- 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 +34 -152
- package/python/foxclaw/agents/base.py +65 -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 +7 -1
- package/python/foxclaw/agents/mixins/preference.py +58 -8
- package/python/foxclaw/agents/mixins/prompt.py +0 -6
- package/python/foxclaw/agents/mixins/skillbox.py +6 -6
- package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
- package/python/foxclaw/api/__init__.py +18 -77
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
- 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 +28 -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/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/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 +30 -57
- package/python/foxclaw/core/metrics.py +4 -5
- package/python/foxclaw/core/model_router.py +36 -148
- 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/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 +0 -201
- package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
- package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
- package/python/frontend/v3/index.html +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 +0 -1
- 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 +0 -41
- 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 +6 -3
- package/python/requirements.txt +311 -0
- package/python/tests/conftest.py +0 -22
- package/python/tests/run_all.py +3 -66
- package/python/tests/test_agent_lifecycle.py +4 -38
- 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_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_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_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/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/release_check.py +7 -7
- 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/frontend/v3/assets/index-BD7VPbMV.js +0 -33
- package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
|
@@ -5,7 +5,6 @@ from datetime import datetime
|
|
|
5
5
|
from ..base import CognitiveAgent
|
|
6
6
|
from ._helpers import _fmt_mem
|
|
7
7
|
|
|
8
|
-
# 分析/建议类意图:即使这句话里带了金额,也要走 LLM 给结论,而不是只回一张记账回执
|
|
9
8
|
_ADVICE_WORDS = ("分析", "建议", "怎么", "如何", "多少", "预算", "超支", "优化", "规划",
|
|
10
9
|
"趋势", "为什么", "要不要", "合算", "划算", "省", "控制", "总结",
|
|
11
10
|
"报告", "够不够", "对比", "排名", "异常", "提醒", "合理", "占比",
|
|
@@ -39,7 +38,6 @@ def _should_autorecord(task: str) -> bool:
|
|
|
39
38
|
return False
|
|
40
39
|
if settled:
|
|
41
40
|
return True
|
|
42
|
-
# 没有完成态动词时,只有「金额 + 明确消费类目」这种报备才记(洗车 100)
|
|
43
41
|
return not _wants_advice(t)
|
|
44
42
|
|
|
45
43
|
|
|
@@ -52,7 +50,6 @@ class FinanceAgent(CognitiveAgent):
|
|
|
52
50
|
"""挂载财务内核(FinanceRuntime):记忆+推理+Policy 守门人。"""
|
|
53
51
|
self.fin = runtime
|
|
54
52
|
|
|
55
|
-
# ── 编排协作(P3:财务领域态势 + 预算守门人,从编排层下沉至此)──────
|
|
56
53
|
def situation(self) -> dict:
|
|
57
54
|
rt = getattr(self, "fin", None)
|
|
58
55
|
if rt is None:
|
|
@@ -85,8 +82,6 @@ class FinanceAgent(CognitiveAgent):
|
|
|
85
82
|
entity=req.payload.get("entity", "差旅"),
|
|
86
83
|
account=req.payload.get("account", "default"))
|
|
87
84
|
bal = rt.account_balances.get(txn.account)
|
|
88
|
-
# Step3:模糊情形的 LLM 裁决(默认关;FOXCLAW_FINANCE_LLM_ARBITER=1 才启用)。
|
|
89
|
-
# 安全不变量:LLM 只在规则已批准的模糊带被咨询、只能 approve→confirm、出错→confirm、永不 reject。
|
|
90
85
|
import os as _os
|
|
91
86
|
_llm_on = _os.environ.get("FOXCLAW_FINANCE_LLM_ARBITER", "") == "1"
|
|
92
87
|
rv = FinanceGatekeeperReasoner(
|
|
@@ -95,7 +90,6 @@ class FinanceAgent(CognitiveAgent):
|
|
|
95
90
|
req.status = {"approve": "approved", "confirm": "needs_confirmation",
|
|
96
91
|
"reject": "rejected"}[rv.decision]
|
|
97
92
|
req.verdict = rv.rationale
|
|
98
|
-
# 附证据链(可审计;新增字段,鸭子类型透传,不改既有行为)
|
|
99
93
|
try:
|
|
100
94
|
req.evidence = [e.to_dict() for e in rv.evidence]
|
|
101
95
|
req.reason_source = rv.source
|
|
@@ -114,7 +108,7 @@ class FinanceAgent(CognitiveAgent):
|
|
|
114
108
|
try:
|
|
115
109
|
rec = fin.record_text(task)
|
|
116
110
|
except Exception:
|
|
117
|
-
return ""
|
|
111
|
+
return ""
|
|
118
112
|
if not rec.get("recorded"):
|
|
119
113
|
return ""
|
|
120
114
|
ack = rec.get("ack") or ""
|
|
@@ -128,16 +122,15 @@ class FinanceAgent(CognitiveAgent):
|
|
|
128
122
|
tags=["finance", "ledger"],
|
|
129
123
|
)
|
|
130
124
|
except Exception:
|
|
131
|
-
pass
|
|
125
|
+
pass
|
|
132
126
|
return ack
|
|
133
127
|
|
|
134
|
-
def analyze_finances(self, period: str = None, current_balance: float = None) -> dict:
|
|
128
|
+
def analyze_finances(self, period: str | None = None, current_balance: float | None = None) -> dict:
|
|
135
129
|
"""调用财务内核做综合分析,并把关键洞察写入 ThoughtChain。"""
|
|
136
130
|
fin = getattr(self, "fin", None)
|
|
137
131
|
if fin is None:
|
|
138
132
|
return {"error": "财务内核未挂载"}
|
|
139
133
|
report = fin.analyze(period=period, current_balance=current_balance)
|
|
140
|
-
# 把守门人裁决写进推理链(受 Harness 监督)
|
|
141
134
|
verdicts = report.get("gatekeeper_verdicts", [])
|
|
142
135
|
self._think(
|
|
143
136
|
content=f"财务分析完成:{report['transaction_count']} 笔交易,"
|
|
@@ -168,9 +161,6 @@ class FinanceAgent(CognitiveAgent):
|
|
|
168
161
|
_recall_query = lambda self, task: f"财务 {task}"
|
|
169
162
|
|
|
170
163
|
async def _execute(self, task, context, session_id, memories):
|
|
171
|
-
# 第一线:零星花钱报备(「今天买了一件衣服花了 105 块」)。
|
|
172
|
-
# 解析→记账→回执全走确定性规则,不让 LLM 碰金额;回执本身就是答案,
|
|
173
|
-
# 没有分析意图时不必再等一次 LLM 往返。
|
|
174
164
|
ledger_ack = self._auto_record(task)
|
|
175
165
|
if ledger_ack and not _wants_advice(task):
|
|
176
166
|
return ledger_ack, {}
|
|
@@ -181,14 +171,12 @@ class FinanceAgent(CognitiveAgent):
|
|
|
181
171
|
conf=0.93,
|
|
182
172
|
)
|
|
183
173
|
|
|
184
|
-
# 构建财务上下文
|
|
185
174
|
finance_context = ""
|
|
186
175
|
if memories:
|
|
187
176
|
finance_context = "历史财务记录:\n" + "\n".join([
|
|
188
177
|
_fmt_mem(m, 100) for m in memories[:5]
|
|
189
178
|
])
|
|
190
179
|
|
|
191
|
-
# 接入财务内核:若已挂载且有交易数据,做真实结构化分析,作为 LLM 的依据
|
|
192
180
|
kernel_insight = ""
|
|
193
181
|
fin = getattr(self, "fin", None)
|
|
194
182
|
if fin is not None and getattr(fin, "transactions", None):
|
|
@@ -205,20 +193,16 @@ class FinanceAgent(CognitiveAgent):
|
|
|
205
193
|
if anomalies:
|
|
206
194
|
parts.append(f"· 检测到 {len(anomalies)} 笔异常支出")
|
|
207
195
|
if parts:
|
|
208
|
-
# 注:标签用产品级措辞(不带"内核"等内部实现词),避免 LLM 回复把内部
|
|
209
|
-
# 架构词汇透给终端用户(深层实现对用户保密;多 Agent 作卖点可讲)。
|
|
210
196
|
kernel_insight = ("\n\n【财务分析结果(基于真实交易,请据此回答)】\n"
|
|
211
197
|
+ "\n".join(parts))
|
|
212
|
-
# 把证据链摘要沉淀到 ThoughtChain(可审计)
|
|
213
198
|
self._think(
|
|
214
199
|
content=f"财务内核证据链:{len(ec)} 条决策,{len(anomalies)} 异常",
|
|
215
200
|
node_type="reflection", conf=0.9,
|
|
216
201
|
)
|
|
217
202
|
except Exception:
|
|
218
|
-
pass
|
|
203
|
+
pass
|
|
219
204
|
|
|
220
205
|
if ledger_ack:
|
|
221
|
-
# 已落账的明细必须交给 LLM:否则它会重复"确认记账",甚至自己再报一遍数字
|
|
222
206
|
kernel_insight = ("\n\n【刚刚已记账(已落账,请据此回答,不要重复报账)】\n"
|
|
223
207
|
+ ledger_ack + kernel_insight)
|
|
224
208
|
|
|
@@ -237,7 +221,6 @@ class FinanceAgent(CognitiveAgent):
|
|
|
237
221
|
conf=0.90,
|
|
238
222
|
)
|
|
239
223
|
|
|
240
|
-
# 记忆财务决策
|
|
241
224
|
self._store_memory(
|
|
242
225
|
content=f"财务分析:{task[:80]}\n结论:{response[:200]}",
|
|
243
226
|
mem_type="decision",
|
|
@@ -23,8 +23,6 @@ class OfficeAgent(CognitiveAgent):
|
|
|
23
23
|
AGENT_NAME = "办公学习 Agent"
|
|
24
24
|
ALLOWED_TOOL_CATEGORIES = {"read", "write", "network", "calendar"}
|
|
25
25
|
|
|
26
|
-
# 日程类任务的触发词:命中则按"行政"话术走(不索要 knowledge JSON)。
|
|
27
|
-
# 与 skills/schedule-itinerary/SKILL.md 的 triggers 保持一致。
|
|
28
26
|
SCHEDULE_HINTS = ("日程", "行程", "会议", "出差", "预约", "日历", "路线", "几点", "提醒")
|
|
29
27
|
|
|
30
28
|
def attach_runtime(self, runtime):
|
|
@@ -38,7 +36,6 @@ class OfficeAgent(CognitiveAgent):
|
|
|
38
36
|
"""
|
|
39
37
|
self.itin = runtime
|
|
40
38
|
|
|
41
|
-
# ── 编排协作(P3:领域态势从编排层下沉至此)──────
|
|
42
39
|
def situation(self) -> dict:
|
|
43
40
|
"""一次自报两个领域:学习 + 行程。编排层按 `domains` 聚合(缺一个不影响另一个)。"""
|
|
44
41
|
return {"domains": {"learning": self._learning_status(),
|
|
@@ -83,11 +80,8 @@ class OfficeAgent(CognitiveAgent):
|
|
|
83
80
|
return [{"domain": "itinerary", "severity": "critical", "msg": c["reason"]}
|
|
84
81
|
for c in conflicts]
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# 硬压下去只会规划出一个没法执行的行程 —— 宁可认输转由人决定)。
|
|
89
|
-
CONCEDE_STEP = 0.75 # 每轮降到上一轮的 75%(改高铁/换酒店/减一天的量化近似)
|
|
90
|
-
CONCEDE_FLOOR = 0.60 # 相对**原始**申请额的下限
|
|
83
|
+
CONCEDE_STEP = 0.75
|
|
84
|
+
CONCEDE_FLOOR = 0.60
|
|
91
85
|
|
|
92
86
|
def concede(self, req, verdict: str):
|
|
93
87
|
"""【协商闭环·发起方】差旅预算被财务守门人拒 → 降标准重报。
|
|
@@ -104,11 +98,9 @@ class OfficeAgent(CognitiveAgent):
|
|
|
104
98
|
if amount <= 0:
|
|
105
99
|
return None
|
|
106
100
|
original = float(req.payload.get("_original_amount") or amount)
|
|
107
|
-
# 降一档,但**夹到下限**而不是跌破就放弃 —— 否则步长过冲时会在下限之上提前认输
|
|
108
|
-
# (8000→6000→(4500 破线)放弃),白白浪费 4800 这个本来能过的方案。
|
|
109
101
|
new_amount = max(round(amount * self.CONCEDE_STEP), round(original * self.CONCEDE_FLOOR))
|
|
110
102
|
if new_amount <= 0 or new_amount >= amount:
|
|
111
|
-
return None
|
|
103
|
+
return None
|
|
112
104
|
p = dict(req.payload)
|
|
113
105
|
p["amount"] = new_amount
|
|
114
106
|
p["_original_amount"] = original
|
|
@@ -154,14 +146,12 @@ class OfficeAgent(CognitiveAgent):
|
|
|
154
146
|
)
|
|
155
147
|
return report
|
|
156
148
|
|
|
157
|
-
# ── 学习/知识(原 OfficeAgent)────────────────────────────────
|
|
158
149
|
@staticmethod
|
|
159
150
|
def _strip_knowledge_json(text: str) -> str:
|
|
160
151
|
"""从回复中剥离 {"new_knowledge": [...]} 结构化块,只保留自然语言。"""
|
|
161
152
|
if not text or "new_knowledge" not in text:
|
|
162
153
|
return text
|
|
163
154
|
import re
|
|
164
|
-
# 去掉 {"new_knowledge": [ ... ]} 整块(含可能的 markdown 代码围栏)
|
|
165
155
|
cleaned = re.sub(r'`{0,3}\s*\{\s*"new_knowledge".*?\}\s*\]\s*\}\s*`{0,3}',
|
|
166
156
|
'', text, flags=re.DOTALL)
|
|
167
157
|
return cleaned.strip()
|
|
@@ -172,8 +162,6 @@ class OfficeAgent(CognitiveAgent):
|
|
|
172
162
|
if rt is None:
|
|
173
163
|
return {"error": "学习内核未挂载"}
|
|
174
164
|
report = rt.analyze()
|
|
175
|
-
# 给前端「知识库」轮播提供知识条目明细:analyze 本身只有统计、无明细,
|
|
176
|
-
# 否则前端读 analysis.items 拿不到 → 一直显示"知识库为空"(即便已录入)。
|
|
177
165
|
try:
|
|
178
166
|
with rt._lock:
|
|
179
167
|
_vals = list(rt.items.values())
|
|
@@ -220,7 +208,7 @@ Hermes 自学原则(像赫尔墨斯那样):
|
|
|
220
208
|
你的知识是活的,会生长的。"""
|
|
221
209
|
|
|
222
210
|
_task_entities = lambda self, task: ["学习", "知识", "文档", "日程", "行程", "会议"]
|
|
223
|
-
_recall_topk = 8
|
|
211
|
+
_recall_topk = 8
|
|
224
212
|
|
|
225
213
|
def _recall_query(self, task: str) -> str:
|
|
226
214
|
"""学习 + 日程两类记忆都要召回(合并后单一 Agent 覆盖两个域)。"""
|
|
@@ -244,7 +232,6 @@ Hermes 自学原则(像赫尔墨斯那样):
|
|
|
244
232
|
f"- {_fmt_mem(m, 80)}" for m in memories[:6]
|
|
245
233
|
])
|
|
246
234
|
|
|
247
|
-
# 接入学习内核:若已挂载且有知识点,做真实分析(待复习/掌握度)作为依据
|
|
248
235
|
kernel_insight = ""
|
|
249
236
|
learn_rt = getattr(self, "learn_rt", None)
|
|
250
237
|
if learn_rt is not None and getattr(learn_rt, "items", None):
|
|
@@ -262,9 +249,8 @@ Hermes 自学原则(像赫尔墨斯那样):
|
|
|
262
249
|
node_type="reflection", conf=0.9,
|
|
263
250
|
)
|
|
264
251
|
except Exception:
|
|
265
|
-
pass
|
|
252
|
+
pass
|
|
266
253
|
|
|
267
|
-
# 接入行程内核:若已挂载且有路点,做真实规划(路线/冲突)作为 LLM 依据
|
|
268
254
|
itin = getattr(self, "itin", None)
|
|
269
255
|
if itin is not None and getattr(itin, "waypoints", None):
|
|
270
256
|
try:
|
|
@@ -285,7 +271,7 @@ Hermes 自学原则(像赫尔墨斯那样):
|
|
|
285
271
|
node_type="reflection", conf=0.9,
|
|
286
272
|
)
|
|
287
273
|
except Exception:
|
|
288
|
-
pass
|
|
274
|
+
pass
|
|
289
275
|
|
|
290
276
|
if schedule:
|
|
291
277
|
prompt = f"""行程/日程任务:{task}
|
|
@@ -310,13 +296,11 @@ Hermes 自学原则(像赫尔墨斯那样):
|
|
|
310
296
|
|
|
311
297
|
new_knowledge_count = 0
|
|
312
298
|
if not schedule:
|
|
313
|
-
# 提取并存储新知识(括号平衡解析:正确处理内容含 []{} 的情况)
|
|
314
299
|
try:
|
|
315
300
|
start = response.find('{"new_knowledge"')
|
|
316
301
|
if start < 0:
|
|
317
302
|
start = response.find('{ "new_knowledge"')
|
|
318
303
|
if start >= 0:
|
|
319
|
-
# 从 start 起做花括号配对,找到完整 JSON 对象的结束位置
|
|
320
304
|
depth, end, in_str, esc = 0, -1, False, False
|
|
321
305
|
for i in range(start, len(response)):
|
|
322
306
|
c = response[i]
|
|
@@ -346,9 +330,8 @@ Hermes 自学原则(像赫尔墨斯那样):
|
|
|
346
330
|
)
|
|
347
331
|
new_knowledge_count += 1
|
|
348
332
|
except Exception:
|
|
349
|
-
pass
|
|
333
|
+
pass
|
|
350
334
|
|
|
351
|
-
# 从返回文本中剥离 new_knowledge JSON 块(不展示给用户)
|
|
352
335
|
response = self._strip_knowledge_json(response)
|
|
353
336
|
|
|
354
337
|
self._think(
|
|
@@ -8,12 +8,13 @@ from datetime import datetime
|
|
|
8
8
|
from ..base import CognitiveAgent
|
|
9
9
|
from ...planning import planner as P
|
|
10
10
|
from ._helpers import _with_prior, _dispatch_failed, _extract_step_results, PRIOR_MAX_CHARS
|
|
11
|
+
from typing import Any, cast
|
|
11
12
|
|
|
12
13
|
class SupervisorAgent(CognitiveAgent):
|
|
13
14
|
AGENT_ID = "supervisor"
|
|
14
15
|
AGENT_NAME = "主管 Agent"
|
|
15
16
|
ALLOWED_TOOL_CATEGORIES = {"read", "write", "calendar", "finance", "network"}
|
|
16
|
-
IS_ROUTER = True
|
|
17
|
+
IS_ROUTER = True
|
|
17
18
|
|
|
18
19
|
@property
|
|
19
20
|
def _role_prompt(self) -> str:
|
|
@@ -37,11 +38,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
37
38
|
def register_subagents(self, subagents: dict):
|
|
38
39
|
"""注入可调度的子 Agent 登记表。"""
|
|
39
40
|
self._subagents = subagents
|
|
40
|
-
# 【深审硬化·消除懒建 race】主管单例会被并发任务共享,_scheduler/_steering_queue 原先在
|
|
41
|
-
# _schedule/_dispatch_parallel 里 `if not hasattr` 懒建——并发下是 check-then-act race
|
|
42
|
-
# (现因 steering 队列 batch 隔离 + scheduler 无状态而良性,但依赖此实现细节脆弱)。改在
|
|
43
|
-
# 本方法(Foxclaw 启动时单线程调一次)**预建**,并发任务到达时属性已就绪、不再 race。
|
|
44
|
-
# 保留下游懒建作 standalone(未 register)兜底。
|
|
45
41
|
from ...core.scheduler import CognitiveScheduler
|
|
46
42
|
from ...core.steering_queue import SteeringQueue
|
|
47
43
|
if not hasattr(self, "_scheduler"):
|
|
@@ -72,12 +68,10 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
72
68
|
orch = getattr(self, "orch", None)
|
|
73
69
|
if orch is None:
|
|
74
70
|
return {"error": "编排引擎未挂载"}
|
|
75
|
-
from foxclaw.orchestration import CrossAgentRequest
|
|
71
|
+
from foxclaw.orchestration import CrossAgentRequest, RequestType
|
|
76
72
|
req = CrossAgentRequest(from_agent=from_agent, to_agent=to_agent,
|
|
77
|
-
request_type=request_type, payload=payload,
|
|
73
|
+
request_type=cast(RequestType, request_type), payload=payload,
|
|
78
74
|
reason=reason)
|
|
79
|
-
# 走协商闭环:被拒时给发起方让步重报的机会。对未实现 concede 的 Agent 而言
|
|
80
|
-
# 只会跑一轮 → 与原先的一次性 route_request 逐字等价(零行为变更)。
|
|
81
75
|
result = orch.negotiate(req)
|
|
82
76
|
rounds = len(result.negotiation) or 1
|
|
83
77
|
self._think(
|
|
@@ -87,24 +81,18 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
87
81
|
)
|
|
88
82
|
return result.to_dict()
|
|
89
83
|
|
|
90
|
-
# 路由关键词表:无 LLM 时也能基于关键词分发。
|
|
91
|
-
# 拓扑 5 → 3 后:原行政(日程/行程)关键词并入办公;"助手"那组(整理/汇总/沟通)
|
|
92
|
-
# 不再有归属 —— 它们正是主管自己该干的兜底活,未命中 → 主管自答。
|
|
93
84
|
ROUTING_KEYWORDS = {
|
|
94
85
|
"finance": ["收支", "支出", "预算", "理财", "投资", "储蓄", "财务", "消费", "报销", "账"],
|
|
95
86
|
"office": ["学习", "论文", "知识", "总结", "研究", "文档", "写作", "资料", "搜索", "查",
|
|
96
87
|
"日程", "行程", "会议", "出差", "预约", "日历", "计划表", "提醒",
|
|
97
|
-
# 原助手的"整理/汇总/邮件"归办公:那是内容活,主管只兜底通用与协调
|
|
98
88
|
"整理", "汇总", "邮件", "纪要"],
|
|
99
89
|
}
|
|
100
90
|
|
|
101
|
-
# 子 Agent 职责描述(P0-1 LLM 规划用):喂给 submit_plan 工具做 agent 枚举约束
|
|
102
91
|
SUBAGENT_DESC = {
|
|
103
92
|
"finance": "财务 Agent:理财、收支、预算、风控、对账",
|
|
104
93
|
"office": "办公学习 Agent:学习、知识管理、研究、写作、日程与行程安排",
|
|
105
94
|
}
|
|
106
95
|
|
|
107
|
-
# 元问题关键词:这类"系统级/自我介绍"问题由主管自己回答,不分派
|
|
108
96
|
META_KEYWORDS = ["你会做什么", "你能做什么", "你是谁", "你能干", "你会干",
|
|
109
97
|
"介绍一下", "介绍下", "自我介绍", "有什么功能", "能帮我做什么",
|
|
110
98
|
"你都能", "怎么用", "如何使用", "你好", "在吗", "干什么",
|
|
@@ -141,18 +129,15 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
141
129
|
"""
|
|
142
130
|
if self._is_expense_report(task):
|
|
143
131
|
return "finance"
|
|
144
|
-
# 关键词命中计数走统一原语(negation=False,与原计数逐字一致)
|
|
145
132
|
scores = {aid: P.keyword_hits(task, kws)
|
|
146
133
|
for aid, kws in self.ROUTING_KEYWORDS.items()}
|
|
147
|
-
best = max(scores, key=scores
|
|
134
|
+
best = max(scores, key=lambda k: scores[k])
|
|
148
135
|
if scores[best] > 0:
|
|
149
136
|
return best
|
|
150
|
-
# 关键词不明:用调度意图做辅助判断
|
|
151
137
|
if sched is not None:
|
|
152
138
|
intent = getattr(getattr(sched, "intent", None), "category", None)
|
|
153
139
|
if intent in ("reasoning", "memory"):
|
|
154
|
-
return "office"
|
|
155
|
-
# 无明确归属 → 主管自答(dispatch 见 target 不在 subagents 即走 _dispatch_fallback)
|
|
140
|
+
return "office"
|
|
156
141
|
return "supervisor"
|
|
157
142
|
|
|
158
143
|
def _coerce_agent(self, ag: str, subtask: str, subagents: dict) -> str:
|
|
@@ -175,13 +160,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
175
160
|
return (any(P.keyword_hits(task, kws) for kws in self.ROUTING_KEYWORDS.values())
|
|
176
161
|
or self._is_expense_report(task))
|
|
177
162
|
|
|
178
|
-
# 【已删除 · 请勿凭记忆加回】这里原有一个 `_llm_classify(task) -> Optional[str]`:
|
|
179
|
-
# 关键词未命中时用一次 LLM 把任务分诊到领域 Agent。它已被「无关键词命中 → 主管直接
|
|
180
|
-
# 自答」分支取代并成为死代码(全仓库零调用点),故删除。当时的取舍:每次分诊要多一次
|
|
181
|
-
# 约 5s 的串行 LLM 往返,正面对话不值当,于是改为主管自答。P1-1 后该分支在
|
|
182
|
-
# pipeline/supervisor_stages.py 的 route(决策)/dispatch(执行)两个 stage 里。
|
|
183
|
-
# 如需恢复精修,应从 git 历史取回本方法,并同时改回那两个 stage、补回对应测试
|
|
184
|
-
# —— 只加回方法不接线等于再制造一处死代码。
|
|
185
163
|
|
|
186
164
|
def _plan_task(self, task: str, sched=None):
|
|
187
165
|
"""用 planner 判断任务是否需要拆解(多步)。
|
|
@@ -203,7 +181,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
203
181
|
p = ahs_plan(task, reg)
|
|
204
182
|
if len(p.tasks) <= 1:
|
|
205
183
|
return None, None
|
|
206
|
-
# 为每个子任务单独算路由(关键词为主)→ planner 拆解真正落到不同 Agent
|
|
207
184
|
step_routes = [(t.name, self._route(t.name)) for t in p.tasks]
|
|
208
185
|
return p, step_routes
|
|
209
186
|
except Exception:
|
|
@@ -233,7 +210,7 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
233
210
|
return None
|
|
234
211
|
steps = result["steps"]
|
|
235
212
|
if len(steps) <= 1:
|
|
236
|
-
return None
|
|
213
|
+
return None
|
|
237
214
|
from ...skills.pipeline import Plan, PlanTask
|
|
238
215
|
n = len(steps)
|
|
239
216
|
parallel = result.get("flow") == "parallel"
|
|
@@ -309,8 +286,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
309
286
|
from ...core.scheduler import CognitiveScheduler, SystemContext
|
|
310
287
|
if not hasattr(self, "_scheduler"):
|
|
311
288
|
self._scheduler = CognitiveScheduler()
|
|
312
|
-
# 系统上下文:活跃 Agent 数 = 当前持有活跃 session 的子 Agent 数
|
|
313
|
-
# (用真实可得的 _session_id 判断忙碌,而非不存在的 is_busy 属性)
|
|
314
289
|
active = sum(1 for a in subagents.values()
|
|
315
290
|
if getattr(a, "_session_id", None))
|
|
316
291
|
ctx = SystemContext(active_agents=active, queue_depth=0, system_load=0.1)
|
|
@@ -322,10 +297,10 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
322
297
|
|
|
323
298
|
(原先每步都只拿到同一份原始 context,等于 N 个互不相干的任务排队跑——
|
|
324
299
|
「查本月餐饮花了多少,再据此定预算」的第 2 步根本拿不到第 1 步的数字。)"""
|
|
325
|
-
pm = getattr(self, "_process_manager", None)
|
|
300
|
+
pm: Any = getattr(self, "_process_manager", None)
|
|
326
301
|
bus = getattr(self, "_system_bus", None)
|
|
327
302
|
steps_out = []
|
|
328
|
-
prior = []
|
|
303
|
+
prior: list[Any] = []
|
|
329
304
|
any_blocked = False
|
|
330
305
|
for idx, (subtask, ag) in enumerate(step_routes, 1):
|
|
331
306
|
if ag not in subagents:
|
|
@@ -337,10 +312,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
337
312
|
)
|
|
338
313
|
if proc:
|
|
339
314
|
pm.mark_running(proc.process_id)
|
|
340
|
-
# _no_emit:子 Agent 不自发 execution.completed,由下方主管统一发(带 schedule),避免双发
|
|
341
|
-
# prior_steps 同时也放进 context,便于管线/Agent 按结构化方式取用
|
|
342
|
-
# 补超时(D 类修复):与 _dispatch_single / _dispatch_parallel 三处标准一致,
|
|
343
|
-
# 子 Agent 卡死不再让整条多步链永久挂起。
|
|
344
315
|
_timeout = float(context.get("parallel_timeout", 120))
|
|
345
316
|
try:
|
|
346
317
|
sub_result = await asyncio.wait_for(
|
|
@@ -356,25 +327,16 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
356
327
|
sub_output = sub_result.get("result", "")
|
|
357
328
|
if isinstance(sub_output, dict):
|
|
358
329
|
sub_output = json.dumps(sub_output, ensure_ascii=False)
|
|
359
|
-
# 未完成(含超时)一律视为 blocked —— 不得让超时步骤被当成
|
|
360
|
-
# 成功结果进入 prior 链,否则后续步骤会基于"不存在的产出"继续推理。
|
|
361
330
|
blocked = sub_result.get("error") in ("harness_blocked", "subagent_timeout")
|
|
362
331
|
any_blocked = any_blocked or blocked
|
|
363
332
|
if proc:
|
|
364
333
|
(pm.mark_failed(proc.process_id, reason=sub_result.get("error") or "")
|
|
365
334
|
if blocked else pm.mark_completed(proc.process_id))
|
|
366
|
-
# 展示用的 subtask 保留**原始**文本(不含前序上下文前言,免污染 UI)
|
|
367
335
|
steps_out.append({"step": idx, "subtask": subtask, "agent": ag,
|
|
368
336
|
"output": str(sub_output)[:200], "blocked": blocked})
|
|
369
|
-
if not blocked and sub_output:
|
|
370
|
-
# 入链即截断:渲染时本就只用最近 PRIOR_MAX_STEPS 步 × PRIOR_MAX_CHARS 字符,
|
|
371
|
-
# 存全量纯属浪费——且 prior 会被 list(prior) 复制进**每个**后续步骤的 context,
|
|
372
|
-
# 长链 + 大输出会滚成可观的内存与拷贝开销。
|
|
337
|
+
if not blocked and sub_output:
|
|
373
338
|
prior.append({"step": idx, "agent": ag,
|
|
374
339
|
"output": str(sub_output)[:PRIOR_MAX_CHARS]})
|
|
375
|
-
# 【P1-1】execution.completed 不再逐子任务手动 publish——改由 base 模板
|
|
376
|
-
# `_emit_execution` 统一发一条**任务级**事件(子 Agent 均带 _no_emit=True)。
|
|
377
|
-
# 无总线(部分测试/独立调用)→ 主管手动兜底沉淀记忆,与单步路径一致
|
|
378
340
|
if bus is None and not blocked:
|
|
379
341
|
self._store_memory(
|
|
380
342
|
content=f"任务「{subtask}」由 {ag} 处理。要点:{str(sub_output)[:200]}",
|
|
@@ -418,18 +380,16 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
418
380
|
self._steering_queue = SteeringQueue(lease_seconds=120)
|
|
419
381
|
q = self._steering_queue
|
|
420
382
|
batch = f"batch_{session_id}"
|
|
421
|
-
pm = getattr(self, "_process_manager", None)
|
|
383
|
+
pm: Any = getattr(self, "_process_manager", None)
|
|
422
384
|
bus = getattr(self, "_system_bus", None)
|
|
423
385
|
timeout = float(context.get("parallel_timeout", 120))
|
|
424
386
|
|
|
425
387
|
agents_involved = sorted({ag for _, ag in step_routes})
|
|
426
|
-
# fork 记录(串行,主管自身 think 不并发)
|
|
427
388
|
self._think(
|
|
428
389
|
content=f"并行 fork {len(step_routes)} 个子任务 → {agents_involved}",
|
|
429
390
|
node_type="tool_intent", tool_name="fork_parallel", conf=0.9,
|
|
430
391
|
)
|
|
431
392
|
|
|
432
|
-
# 按 Agent 分组:不同组并行,同组内串行(护 per-agent 会话态)
|
|
433
393
|
groups: dict[str, list] = {}
|
|
434
394
|
for idx, (subtask, ag) in enumerate(step_routes):
|
|
435
395
|
if ag not in subagents:
|
|
@@ -456,8 +416,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
456
416
|
q.publish(batch, ag, {"idx": idx, "subtask": subtask, "agent": ag,
|
|
457
417
|
"output": str(out)[:200], "blocked": blocked,
|
|
458
418
|
"failed": False})
|
|
459
|
-
# 【P1-1】execution.completed 不再逐子任务手动 publish——
|
|
460
|
-
# 改由 base 模板 `_emit_execution` 统一发任务级事件。
|
|
461
419
|
except asyncio.TimeoutError:
|
|
462
420
|
if proc:
|
|
463
421
|
pm.mark_failed(proc.process_id, reason="timeout")
|
|
@@ -475,7 +433,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
475
433
|
await asyncio.gather(*[run_group(ag, items) for ag, items in groups.items()])
|
|
476
434
|
wall_ms = round((time.perf_counter() - t0) * 1000, 1)
|
|
477
435
|
|
|
478
|
-
# join:租约收集(不丢不重)→ 按 idx 还原原始顺序 → ack
|
|
479
436
|
leased = q.lease(batch, max_items=len(step_routes) * 2)
|
|
480
437
|
steps_out = []
|
|
481
438
|
for it in leased:
|
|
@@ -485,13 +442,11 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
485
442
|
|
|
486
443
|
any_blocked = any(s.get("blocked") for s in steps_out)
|
|
487
444
|
any_failed = any(s.get("failed") for s in steps_out)
|
|
488
|
-
# join 记录(串行)
|
|
489
445
|
self._think(
|
|
490
446
|
content=f"并行 join:{len(steps_out)}/{len(step_routes)} 步汇聚,"
|
|
491
447
|
f"墙钟 {wall_ms}ms,blocked={any_blocked},failed={any_failed}",
|
|
492
448
|
node_type="reflection", conf=0.88,
|
|
493
449
|
)
|
|
494
|
-
# 无总线时兜底沉淀记忆(与顺序路径一致)
|
|
495
450
|
if bus is None:
|
|
496
451
|
for s in steps_out:
|
|
497
452
|
if not s.get("blocked") and not s.get("failed"):
|
|
@@ -518,11 +473,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
518
473
|
"thought_summary": self.thought_chain.get_summary(self.AGENT_ID),
|
|
519
474
|
}
|
|
520
475
|
|
|
521
|
-
# ── 【P1-1】主管 pipeline 化:不再重写 run_task ────────────────────
|
|
522
|
-
# 生命周期(会话/召回/返回体/Harness 兜底/hooks/流式/中断/统一 _emit_execution)
|
|
523
|
-
# 全部走 base 的 _run_task_core;路由分发逻辑拆成 7 个 pipeline stage
|
|
524
|
-
# (foxclaw/pipeline/supervisor_stages.py:recall→meta_check→schedule→route→plan→
|
|
525
|
-
# dispatch→aggregate)。本类只保留:pipeline 覆盖、领域钩子、恢复记账、分发执行体。
|
|
526
476
|
|
|
527
477
|
def _cognitive_pipeline(self):
|
|
528
478
|
"""主管的认知管线:SUPERVISOR_STAGES(路由分发),而非领域 Agent 的 DEFAULT_STAGES。"""
|
|
@@ -537,8 +487,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
537
487
|
"""主管的目标基线实体(旧 run_task 用 _extract_entities 开会话,逐字保留)。"""
|
|
538
488
|
return self._extract_entities(task)
|
|
539
489
|
|
|
540
|
-
# 崩溃/重启恢复记账已上移到 base 生命周期钩子(领域 Agent 此前完全没接线,跑一半崩掉
|
|
541
|
-
# 在恢复扫描里不可见);主管只是"恰好也装着 _recovery",无需再覆盖。
|
|
542
490
|
|
|
543
491
|
async def _dispatch_single(self, task, target, sched, session_id,
|
|
544
492
|
subagents, context, memories, plan_obj) -> dict:
|
|
@@ -546,8 +494,7 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
546
494
|
① execution.completed 不再在此手动 publish——改由 base 模板 `_emit_execution`
|
|
547
495
|
统一发一条**任务级**事件(子 Agent 调用带 _no_emit=True 仍不双发);
|
|
548
496
|
② 返回体不再带 session_id/agent/thought_summary(由 base 模板统一组装)。"""
|
|
549
|
-
|
|
550
|
-
pm = getattr(self, "_process_manager", None)
|
|
497
|
+
pm: Any = getattr(self, "_process_manager", None)
|
|
551
498
|
proc = pm.create(task, agent_id=target, schedule=sched.to_dict()) if pm else None
|
|
552
499
|
|
|
553
500
|
self._think(
|
|
@@ -556,35 +503,25 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
556
503
|
tool_name=f"dispatch_{target}",
|
|
557
504
|
conf=0.92,
|
|
558
505
|
)
|
|
559
|
-
# 进程进入 running
|
|
560
506
|
if proc:
|
|
561
507
|
pm.mark_running(proc.process_id)
|
|
562
|
-
# _no_emit:子 Agent 不自发,主管任务级统一 emit(见 base._emit_execution),避免双发
|
|
563
|
-
#
|
|
564
|
-
# 补超时(D 类修复:同文件内标准一致性)。此前只有并行路径
|
|
565
|
-
# (_dispatch_parallel)有 asyncio.wait_for,单步/链式路径直接 await ——
|
|
566
|
-
# 子 Agent 卡死(LLM 挂起/工具阻塞)会让主管任务**永久挂起**,且无日志
|
|
567
|
-
# 无告警无释放。同一语义必须两套标准一致,超时值同样取 parallel_timeout。
|
|
568
508
|
_timeout = float(context.get("parallel_timeout", 120))
|
|
569
509
|
try:
|
|
570
510
|
sub_result = await asyncio.wait_for(
|
|
571
511
|
subagents[target].run_task(task, {**context, "_no_emit": True}),
|
|
572
512
|
timeout=_timeout)
|
|
573
513
|
except asyncio.TimeoutError:
|
|
574
|
-
# 诚实交代:不伪造子 Agent 的产出,明确回报"超时未完成"
|
|
575
514
|
_note = f"子 Agent {target} 执行超时({_timeout:g}s),已中止并如实回报未完成结果"
|
|
576
515
|
if proc:
|
|
577
516
|
pm.mark_failed(proc.process_id, reason="subagent_timeout")
|
|
578
517
|
self._think(content=_note, node_type="decision", conf=0.95)
|
|
579
518
|
sub_result = {"result": _note, "error": "subagent_timeout", "agent": target}
|
|
580
519
|
|
|
581
|
-
# 汇聚子 Agent 结果
|
|
582
520
|
sub_output = sub_result.get("result", "")
|
|
583
521
|
if isinstance(sub_output, dict):
|
|
584
522
|
sub_output = json.dumps(sub_output, ensure_ascii=False)
|
|
585
523
|
blocked = sub_result.get("error") == "harness_blocked"
|
|
586
524
|
|
|
587
|
-
# 进程终态:被拦截/出错→failed,否则→completed
|
|
588
525
|
if proc:
|
|
589
526
|
if blocked:
|
|
590
527
|
pm.mark_failed(proc.process_id, reason="harness_blocked")
|
|
@@ -597,8 +534,6 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
597
534
|
conf=0.88,
|
|
598
535
|
)
|
|
599
536
|
|
|
600
|
-
# 记忆沉淀:正常由真值管线(事件驱动,经 base._emit_execution)负责;
|
|
601
|
-
# 无总线时(如部分测试场景)主管手动兜底,避免丢记忆。
|
|
602
537
|
bus = getattr(self, "_system_bus", None)
|
|
603
538
|
if bus is None:
|
|
604
539
|
self._store_memory(
|
|
@@ -646,11 +581,9 @@ class SupervisorAgent(CognitiveAgent):
|
|
|
646
581
|
(因中文无大小写,原首字母逻辑对中文无效)。
|
|
647
582
|
"""
|
|
648
583
|
keywords = []
|
|
649
|
-
# 英文:首字母大写的词
|
|
650
584
|
for word in text.split():
|
|
651
585
|
if len(word) > 3 and word[0].isupper():
|
|
652
586
|
keywords.append(word)
|
|
653
|
-
# 中文:命中路由关键词的领域词(作为实体线索)
|
|
654
587
|
for kws in self.ROUTING_KEYWORDS.values():
|
|
655
588
|
for kw in kws:
|
|
656
589
|
if kw in text and kw not in keywords:
|
|
@@ -8,45 +8,22 @@ from ...core.thought_chain import ThoughtNode
|
|
|
8
8
|
from ...harness.harness import HarnessResult
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
# 每任务(per asyncio-Task / per-thread)会话状态。放在 contextvar 而非实例属性,使同一
|
|
12
|
-
# Agent 实例并发处理多个顶层任务时,各任务的会话指针互不覆盖(asyncio 每个 Task 自动持
|
|
13
|
-
# 独立 context 副本;旧实现下 await 后的推理节点会被最后启动的会话吞掉——已实测复现)。
|
|
14
|
-
# 写法:单个 module 级 contextvar 承载一个 dict、按"每实例 token"分槽:
|
|
15
|
-
# ① GC 友好(只有一个 contextvar,不随实例增长);
|
|
16
|
-
# ② 支持 supervisor→subagent 的同任务嵌套(不同实例 token 不冲突,互不覆盖);
|
|
17
|
-
# ③ copy-on-write —— 一个任务的 set 生成新 dict,不影响其它任务。
|
|
18
11
|
_agent_ctx: contextvars.ContextVar = contextvars.ContextVar(
|
|
19
12
|
"foxclaw_agent_ctx", default=None)
|
|
20
13
|
|
|
21
|
-
# 抑制记忆写入(随 to_thread/asyncio.run 上下文复制传播):MCP ask_foxclaw 驱动的外部任务
|
|
22
|
-
# 不写入 HMR,防外部客户端灌垃圾污染你的个人记忆。默认 False(正常任务照写)。
|
|
23
14
|
_suppress_memory: contextvars.ContextVar = contextvars.ContextVar(
|
|
24
15
|
"foxclaw_suppress_memory", default=False)
|
|
25
16
|
|
|
26
|
-
# act 阶段 LLM 流式回调(D1,随 to_thread/asyncio.run 上下文复制传播):run_task_stream 设它,
|
|
27
|
-
# 承载 {"on_token": cb, "stop_event": ev};_call_llm 读到非 None 则走 router.stream 逐 token 回调
|
|
28
|
-
# + 累积返回完整串(返回值语义不变)。默认 None = 非流式(走 router.complete,行为逐字不变)。
|
|
29
17
|
_act_stream: contextvars.ContextVar = contextvars.ContextVar(
|
|
30
18
|
"foxclaw_act_stream", default=None)
|
|
31
19
|
|
|
32
|
-
# 断点续跑(P1):{步骤指纹: {"name": 工具名, "result": 上次结果}}。run_task 带
|
|
33
|
-
# `context={"_resume_task_id": ...}` 时由 _run_task_core 从 recovery 日志装载,工具轮命中
|
|
34
|
-
# 同指纹步骤则复用结果、不再重复执行(避免重跑联网/副作用)。默认 None = 正常执行,行为不变。
|
|
35
20
|
_resume_steps: contextvars.ContextVar = contextvars.ContextVar(
|
|
36
21
|
"foxclaw_resume_steps", default=None)
|
|
37
22
|
|
|
38
|
-
# E2 推理上下文注入(随 to_thread/asyncio.run 上下文复制传播):pre_reasoning(agent_start)钩子
|
|
39
|
-
# 返回的 additional_context 存这里,_call_llm 前插为一条 user 消息注入推理。默认 None = 不注入
|
|
40
|
-
# (无钩子/钩子未返回上下文时行为逐字不变)。
|
|
41
23
|
_reasoning_inject: contextvars.ContextVar = contextvars.ContextVar(
|
|
42
24
|
"foxclaw_reasoning_inject", default=None)
|
|
43
25
|
|
|
44
26
|
|
|
45
|
-
# 执行证据(存在会话槽里,与污点同寿命):每次工具轮把「这一步有没有凭据」记下来,
|
|
46
|
-
# 最终由 stream_reply 一次性交给端点做 SSE `event: evidence`,前端展示给人核对。
|
|
47
|
-
# 放会话槽而非实例属性:同一 Agent 并发两个任务时证据不得串台。
|
|
48
|
-
|
|
49
|
-
|
|
50
27
|
def _evidence_reset(agent) -> None:
|
|
51
28
|
tok = getattr(agent, "_ctx_token", None)
|
|
52
29
|
if tok is not None:
|