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.
Files changed (344) hide show
  1. package/index.js +1 -8
  2. package/package.json +1 -1
  3. package/python/.env.example +8 -0
  4. package/python/.github/workflows/ci.yml +47 -2
  5. package/python/CHANGELOG.md +57 -0
  6. package/python/CHANGELOG_CN.md +57 -0
  7. package/python/README.md +11 -2
  8. package/python/README_CN.md +32 -20
  9. package/python/bench/bm25_audit.py +1 -14
  10. package/python/bench/bm25_regression.py +0 -1
  11. package/python/bench/bm25_stress.py +0 -4
  12. package/python/bench/cd_audit.py +0 -23
  13. package/python/bench/chaos.py +6 -8
  14. package/python/bench/e2e_real_models.py +0 -1
  15. package/python/bench/llm_retry_test.py +2 -9
  16. package/python/bench/load_test.py +5 -8
  17. package/python/bench/vector_ivf_upgrade.py +1 -7
  18. package/python/docs/RELEASE.md +4 -3
  19. package/python/docs/RUN_CN.md +2 -2
  20. package/python/evals/governance/cases.py +0 -37
  21. package/python/evals/governance/run_evals.py +5 -37
  22. package/python/examples/governance_middleware_demo.py +1 -6
  23. package/python/examples/langgraph_governance_demo.py +7 -11
  24. package/python/foxclaw/__init__.py +34 -152
  25. package/python/foxclaw/agents/base.py +65 -136
  26. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  27. package/python/foxclaw/agents/impl/finance.py +4 -21
  28. package/python/foxclaw/agents/impl/office.py +7 -24
  29. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  30. package/python/foxclaw/agents/mixins/_context.py +0 -23
  31. package/python/foxclaw/agents/mixins/augment.py +10 -16
  32. package/python/foxclaw/agents/mixins/llm.py +7 -6
  33. package/python/foxclaw/agents/mixins/memory.py +7 -1
  34. package/python/foxclaw/agents/mixins/preference.py +58 -8
  35. package/python/foxclaw/agents/mixins/prompt.py +0 -6
  36. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  37. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  38. package/python/foxclaw/api/__init__.py +18 -77
  39. package/python/foxclaw/api/_state.py +43 -0
  40. package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
  41. package/python/foxclaw/api/middleware.py +5 -28
  42. package/python/foxclaw/api/routes/ahs.py +60 -73
  43. package/python/foxclaw/api/routes/attachments.py +5 -6
  44. package/python/foxclaw/api/routes/browser.py +7 -11
  45. package/python/foxclaw/api/routes/domain.py +18 -26
  46. package/python/foxclaw/api/routes/knowledge.py +3 -21
  47. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  48. package/python/foxclaw/api/routes/memory.py +28 -12
  49. package/python/foxclaw/api/routes/orchestration.py +3 -5
  50. package/python/foxclaw/api/routes/recovery.py +4 -3
  51. package/python/foxclaw/api/routes/review.py +2 -11
  52. package/python/foxclaw/api/routes/search.py +3 -7
  53. package/python/foxclaw/api/routes/system.py +33 -49
  54. package/python/foxclaw/api/routes/task.py +31 -49
  55. package/python/foxclaw/api/routes/ws.py +2 -6
  56. package/python/foxclaw/api/spine.py +1 -4
  57. package/python/foxclaw/browser/action/action_executor.py +1 -5
  58. package/python/foxclaw/browser/action/action_types.py +0 -1
  59. package/python/foxclaw/browser/action/url_policy.py +1 -5
  60. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  61. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  62. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  63. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  64. package/python/foxclaw/browser/service.py +7 -11
  65. package/python/foxclaw/capability/embedding_service.py +4 -6
  66. package/python/foxclaw/channels/__init__.py +9 -11
  67. package/python/foxclaw/channels/discord.py +4 -13
  68. package/python/foxclaw/channels/dispatcher.py +2 -27
  69. package/python/foxclaw/channels/feishu.py +10 -20
  70. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  71. package/python/foxclaw/channels/signing.py +9 -9
  72. package/python/foxclaw/channels/telegram.py +3 -4
  73. package/python/foxclaw/channels/weixin.py +7 -17
  74. package/python/foxclaw/cli.py +42 -13
  75. package/python/foxclaw/cognitive/reasoner.py +9 -10
  76. package/python/foxclaw/cognitive/state.py +18 -24
  77. package/python/foxclaw/core/audit_chain.py +1 -3
  78. package/python/foxclaw/core/audit_sink.py +3 -3
  79. package/python/foxclaw/core/compaction.py +8 -26
  80. package/python/foxclaw/core/config.py +2 -2
  81. package/python/foxclaw/core/confirm_nonce.py +6 -12
  82. package/python/foxclaw/core/envfile.py +2 -2
  83. package/python/foxclaw/core/failure_policy.py +17 -26
  84. package/python/foxclaw/core/gateway.py +6 -10
  85. package/python/foxclaw/core/governance_audit.py +3 -7
  86. package/python/foxclaw/core/idempotency.py +15 -22
  87. package/python/foxclaw/core/injection_guard.py +10 -29
  88. package/python/foxclaw/core/memory_adapter.py +30 -57
  89. package/python/foxclaw/core/metrics.py +4 -5
  90. package/python/foxclaw/core/model_router.py +36 -148
  91. package/python/foxclaw/core/paths.py +1 -1
  92. package/python/foxclaw/core/recovery.py +21 -34
  93. package/python/foxclaw/core/scheduler.py +23 -26
  94. package/python/foxclaw/core/secrets.py +0 -3
  95. package/python/foxclaw/core/sessions.py +4 -6
  96. package/python/foxclaw/core/settings.py +16 -32
  97. package/python/foxclaw/core/shared_write.py +11 -23
  98. package/python/foxclaw/core/steering_queue.py +12 -11
  99. package/python/foxclaw/core/system_bus.py +1 -1
  100. package/python/foxclaw/core/thought_chain.py +30 -32
  101. package/python/foxclaw/core/token_meter.py +9 -13
  102. package/python/foxclaw/core/tools.py +4 -12
  103. package/python/foxclaw/core/tracing.py +25 -40
  104. package/python/foxclaw/core/truth_pipeline.py +5 -9
  105. package/python/foxclaw/core/update_check.py +149 -0
  106. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  107. package/python/foxclaw/core/verify.py +6 -14
  108. package/python/foxclaw/core/web_search.py +4 -28
  109. package/python/foxclaw/finance/evidence.py +14 -18
  110. package/python/foxclaw/finance/fx.py +1 -3
  111. package/python/foxclaw/finance/graph.py +9 -18
  112. package/python/foxclaw/finance/models.py +20 -24
  113. package/python/foxclaw/finance/nlparse.py +12 -72
  114. package/python/foxclaw/finance/policy.py +8 -36
  115. package/python/foxclaw/finance/reasoner.py +3 -11
  116. package/python/foxclaw/finance/reasoning.py +13 -34
  117. package/python/foxclaw/finance/reconcile.py +5 -10
  118. package/python/foxclaw/finance/runtime.py +19 -46
  119. package/python/foxclaw/governance/middleware.py +13 -21
  120. package/python/foxclaw/harness/_base.py +2 -8
  121. package/python/foxclaw/harness/_policy.py +19 -38
  122. package/python/foxclaw/harness/drift.py +1 -13
  123. package/python/foxclaw/harness/event_bus.py +2 -5
  124. package/python/foxclaw/harness/harness.py +9 -97
  125. package/python/foxclaw/harness/loop_detection.py +11 -18
  126. package/python/foxclaw/itinerary/amap_client.py +2 -5
  127. package/python/foxclaw/itinerary/models.py +7 -7
  128. package/python/foxclaw/itinerary/reasoner.py +1 -6
  129. package/python/foxclaw/itinerary/reasoning.py +15 -21
  130. package/python/foxclaw/itinerary/runtime.py +8 -12
  131. package/python/foxclaw/kernel/capabilities.py +5 -6
  132. package/python/foxclaw/kernel/policy_gate.py +4 -10
  133. package/python/foxclaw/kernel/process_manager.py +17 -36
  134. package/python/foxclaw/kernel/scheduler.py +3 -9
  135. package/python/foxclaw/kernel/sentinel.py +13 -23
  136. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  137. package/python/foxclaw/kernel/spine.py +9 -9
  138. package/python/foxclaw/knowledge/__init__.py +4 -20
  139. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  140. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  141. package/python/foxclaw/learning/models.py +13 -13
  142. package/python/foxclaw/learning/reasoner.py +1 -4
  143. package/python/foxclaw/learning/runtime.py +19 -31
  144. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  145. package/python/foxclaw/mcp_server.py +2 -3
  146. package/python/foxclaw/orchestration/__init__.py +16 -33
  147. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  148. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  149. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  150. package/python/foxclaw/planning/llm_planner.py +3 -3
  151. package/python/foxclaw/planning/planner.py +6 -11
  152. package/python/foxclaw/search/agent_port.py +7 -11
  153. package/python/foxclaw/search/evidence.py +0 -4
  154. package/python/foxclaw/search/internal_search.py +3 -5
  155. package/python/foxclaw/search/models.py +11 -13
  156. package/python/foxclaw/search/planner.py +3 -3
  157. package/python/foxclaw/search/retriever.py +1 -1
  158. package/python/foxclaw/search/runtime.py +1 -8
  159. package/python/foxclaw/skills/__init__.py +46 -100
  160. package/python/foxclaw/skills/archive.py +111 -0
  161. package/python/foxclaw/skills/fetch.py +226 -0
  162. package/python/foxclaw/skills/hooks_spec.py +21 -25
  163. package/python/foxclaw/skills/install_cmd.py +216 -0
  164. package/python/foxclaw/skills/lifecycle.py +16 -42
  165. package/python/foxclaw/skills/markdown_skill.py +10 -9
  166. package/python/foxclaw/skills/mcp_client.py +5 -11
  167. package/python/foxclaw/skills/mcp_store.py +0 -3
  168. package/python/foxclaw/skills/permission.py +13 -48
  169. package/python/foxclaw/skills/pipeline.py +21 -53
  170. package/python/foxclaw/skills/policy.py +4 -10
  171. package/python/foxclaw/skills/review.py +3 -3
  172. package/python/frontend/deck/auth.js +0 -7
  173. package/python/frontend/deck/chat_model_picker.js +0 -54
  174. package/python/frontend/deck/domain.js +0 -20
  175. package/python/frontend/deck/main.js +0 -144
  176. package/python/frontend/deck/markdown.js +0 -4
  177. package/python/frontend/deck/settings.js +1 -18
  178. package/python/frontend/deck/state.js +0 -1
  179. package/python/frontend/deck/util.js +0 -2
  180. package/python/frontend/deck/visuals.js +0 -22
  181. package/python/frontend/planet/planet.js +0 -201
  182. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  183. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  184. package/python/frontend/v3/index.html +3 -3
  185. package/python/frontend/widget_runtime.js +0 -43
  186. package/python/frontend/widgets.js +0 -57
  187. package/python/frontend_src/index.html +1 -1
  188. package/python/frontend_src/package-lock.json +2 -2
  189. package/python/frontend_src/package.json +1 -1
  190. package/python/frontend_src/src/App.vue +3 -7
  191. package/python/frontend_src/src/api.js +0 -7
  192. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  193. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  194. package/python/frontend_src/src/components/Inspector.vue +0 -6
  195. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  196. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  197. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  198. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  199. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  200. package/python/frontend_src/src/components/Stage.vue +71 -25
  201. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  202. package/python/frontend_src/src/components/TopBar.vue +38 -10
  203. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  204. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  205. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  206. package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
  207. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  208. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  209. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  210. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  211. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  212. package/python/frontend_src/src/lib/fmt.js +0 -4
  213. package/python/frontend_src/src/lib/i18n.js +53 -0
  214. package/python/frontend_src/src/lib/locales/en.js +173 -0
  215. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  216. package/python/frontend_src/src/lib/md.js +0 -14
  217. package/python/frontend_src/src/main.js +2 -0
  218. package/python/frontend_src/src/stores/app.js +17 -32
  219. package/python/frontend_src/src/stores/chat.js +0 -34
  220. package/python/frontend_src/src/stores/model.js +0 -8
  221. package/python/frontend_src/src/stores/panels.js +0 -41
  222. package/python/frontend_src/src/stores/profile.js +31 -0
  223. package/python/frontend_src/src/stores/system.js +25 -0
  224. package/python/frontend_src/vite.config.js +0 -7
  225. package/python/pyproject.toml +6 -3
  226. package/python/requirements.txt +311 -0
  227. package/python/tests/conftest.py +0 -22
  228. package/python/tests/run_all.py +3 -66
  229. package/python/tests/test_agent_lifecycle.py +4 -38
  230. package/python/tests/test_ahs_p2.py +4 -25
  231. package/python/tests/test_anthropic_tools.py +1 -15
  232. package/python/tests/test_api.py +19 -9
  233. package/python/tests/test_api_auth.py +1 -5
  234. package/python/tests/test_attachments.py +9 -25
  235. package/python/tests/test_audit_chain.py +2 -7
  236. package/python/tests/test_audit_sink.py +0 -6
  237. package/python/tests/test_browser.py +2 -6
  238. package/python/tests/test_browser_request_guard.py +0 -4
  239. package/python/tests/test_capabilities.py +0 -6
  240. package/python/tests/test_channel_signing.py +2 -9
  241. package/python/tests/test_channels.py +1 -11
  242. package/python/tests/test_config_layering.py +0 -4
  243. package/python/tests/test_confirm_nonce.py +1 -12
  244. package/python/tests/test_context_compaction_wiring.py +1 -7
  245. package/python/tests/test_dispatch.py +0 -6
  246. package/python/tests/test_domain_evals.py +11 -80
  247. package/python/tests/test_domain_import.py +0 -6
  248. package/python/tests/test_domain_reasoners.py +0 -14
  249. package/python/tests/test_e2e.py +0 -17
  250. package/python/tests/test_emb_cache.py +7 -17
  251. package/python/tests/test_embedding_singleton.py +1 -3
  252. package/python/tests/test_evals_smoke.py +0 -24
  253. package/python/tests/test_event_spine.py +10 -18
  254. package/python/tests/test_evidence_semantic.py +1 -14
  255. package/python/tests/test_evidence_sse.py +3 -6
  256. package/python/tests/test_file_ingest.py +0 -11
  257. package/python/tests/test_finance.py +0 -9
  258. package/python/tests/test_finance_ledger.py +5 -12
  259. package/python/tests/test_finance_persist.py +8 -12
  260. package/python/tests/test_gateway.py +3 -22
  261. package/python/tests/test_governance.py +5 -52
  262. package/python/tests/test_governance_audit.py +4 -39
  263. package/python/tests/test_governance_middleware.py +1 -7
  264. package/python/tests/test_hkc.py +6 -24
  265. package/python/tests/test_hkc_index_build_manager.py +0 -33
  266. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  267. package/python/tests/test_hkc_search_event_sync.py +3 -54
  268. package/python/tests/test_import_path_guard.py +0 -6
  269. package/python/tests/test_injection_guard.py +18 -36
  270. package/python/tests/test_integration.py +5 -29
  271. package/python/tests/test_itinerary.py +0 -3
  272. package/python/tests/test_kee_semantic.py +0 -5
  273. package/python/tests/test_kernel.py +1 -11
  274. package/python/tests/test_learning.py +3 -12
  275. package/python/tests/test_llm_admin.py +4 -19
  276. package/python/tests/test_llm_planner.py +1 -23
  277. package/python/tests/test_markdown_skills.py +3 -14
  278. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  279. package/python/tests/test_mcp_connectors.py +3 -15
  280. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  281. package/python/tests/test_mcp_no_memory.py +3 -3
  282. package/python/tests/test_mcp_policy_gate.py +0 -1
  283. package/python/tests/test_mcp_server.py +6 -9
  284. package/python/tests/test_memory_recall.py +1 -4
  285. package/python/tests/test_metrics.py +0 -8
  286. package/python/tests/test_multi_chain.py +1 -13
  287. package/python/tests/test_multimodal.py +5 -18
  288. package/python/tests/test_negotiation.py +3 -13
  289. package/python/tests/test_orchestration.py +1 -11
  290. package/python/tests/test_orchestration_decoupled.py +0 -6
  291. package/python/tests/test_p3_p5_hardening.py +3 -17
  292. package/python/tests/test_parallel_dispatch.py +4 -12
  293. package/python/tests/test_pipeline.py +17 -58
  294. package/python/tests/test_plan_visible.py +4 -4
  295. package/python/tests/test_planner.py +0 -7
  296. package/python/tests/test_policy_gate.py +2 -3
  297. package/python/tests/test_profile.py +67 -0
  298. package/python/tests/test_readme_claims.py +163 -0
  299. package/python/tests/test_reasoner.py +2 -17
  300. package/python/tests/test_release_check.py +2 -13
  301. package/python/tests/test_resume_checkpoint.py +7 -15
  302. package/python/tests/test_routing.py +2 -12
  303. package/python/tests/test_scheduler.py +7 -10
  304. package/python/tests/test_search.py +1 -11
  305. package/python/tests/test_secrets.py +4 -10
  306. package/python/tests/test_semantic.py +1 -10
  307. package/python/tests/test_sessions.py +3 -4
  308. package/python/tests/test_settings.py +2 -18
  309. package/python/tests/test_shadow_learning.py +3 -43
  310. package/python/tests/test_shared_write.py +0 -7
  311. package/python/tests/test_skill_taint_gate.py +1 -10
  312. package/python/tests/test_skills.py +2 -18
  313. package/python/tests/test_skills_fetch.py +282 -0
  314. package/python/tests/test_stats_and_events.py +2 -5
  315. package/python/tests/test_streaming.py +2 -7
  316. package/python/tests/test_supervisor_pipeline.py +4 -5
  317. package/python/tests/test_supervisor_triage.py +4 -10
  318. package/python/tests/test_taint.py +2 -7
  319. package/python/tests/test_task_stream.py +1 -12
  320. package/python/tests/test_thinking_sse.py +0 -1
  321. package/python/tests/test_tool_center.py +1 -17
  322. package/python/tests/test_tool_parallel.py +2 -2
  323. package/python/tests/test_tool_params.py +1 -5
  324. package/python/tests/test_tool_use.py +0 -13
  325. package/python/tests/test_toplevel_concurrency.py +3 -4
  326. package/python/tests/test_tracing.py +7 -41
  327. package/python/tests/test_truth_crystallizer.py +4 -6
  328. package/python/tests/test_truth_flow.py +2 -7
  329. package/python/tests/test_update_check.py +95 -0
  330. package/python/tests/test_verify_evidence.py +8 -21
  331. package/python/tests/test_version_sync.py +35 -6
  332. package/python/tests/test_web_trigger.py +0 -17
  333. package/python/tools/check_faiss_type.py +0 -1
  334. package/python/tools/check_log.ps1 +0 -8
  335. package/python/tools/mypy_baseline.txt +12 -4
  336. package/python/tools/mypy_ratchet.py +52 -6
  337. package/python/tools/pack.py +0 -5
  338. package/python/tools/release_check.py +7 -7
  339. package/python/tools/restart_foxclaw.ps1 +5 -8
  340. package/python/tools/shared_write_audit.py +1 -15
  341. package/python/tools/size_check.py +1 -1
  342. package/python/tools/strip_comments.py +228 -0
  343. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  344. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -16,11 +16,12 @@ hooks / 流式(run_task_stream)/ stop_event 中断 / 统一 `_emit_execution` /
16
16
  import asyncio
17
17
  from datetime import datetime
18
18
 
19
+ from ..core.dispatch import dispatch_failed
19
20
  from .cognitive_pipeline import PipelineContext, stage_recall # noqa: F401 (recall 复用默认 stage)
20
- from ..core.dispatch import dispatch_failed # 谓词在 core:避免 pipeline 反向 import agents
21
+ from .stage_meta import named_stage
21
22
 
22
23
 
23
- # ── Stage 2: meta_check —— 元问题判断(你是谁/能做什么 → 主管以总管身份自答,不分派)──
24
+ @named_stage("meta_check")
24
25
  async def sv_meta_check(ctx: PipelineContext) -> None:
25
26
  agent = ctx.agent
26
27
  if not agent._is_meta(ctx.task):
@@ -30,7 +31,6 @@ async def sv_meta_check(ctx: PipelineContext) -> None:
30
31
  node_type="decision", conf=0.92,
31
32
  )
32
33
  subagents = getattr(agent, "_subagents", {})
33
- # 拓扑 5 → 3:助手/行政已下放为指令技能,不再出现在团队介绍里。
34
34
  sub_names = {
35
35
  "finance": "财务 Agent(理财、收支、预算、风控)",
36
36
  "office": "办公学习 Agent(学习、知识管理、研究、写作、日程与行程)",
@@ -57,11 +57,10 @@ async def sv_meta_check(ctx: PipelineContext) -> None:
57
57
  )
58
58
  ctx.result = {"dispatched_to": "supervisor", "sub_result": reply,
59
59
  "self_answered": True}
60
- ctx.extra["_meta_answered"] = True # 短路标记:后续 stage 跳过(aggregate 清除)
61
- sv_meta_check.stage_name = "meta_check"
60
+ ctx.extra["_meta_answered"] = True
62
61
 
63
62
 
64
- # ── Stage 3: schedule —— 认知调度(优先级/负载/抢占)────────────────
63
+ @named_stage("schedule")
65
64
  async def sv_schedule(ctx: PipelineContext) -> None:
66
65
  if ctx.extra.get("_meta_answered"):
67
66
  return
@@ -73,10 +72,9 @@ async def sv_schedule(ctx: PipelineContext) -> None:
73
72
  + (f" · 抢占({ctx.sched.preemption_reason})" if ctx.sched.preemptible else "")),
74
73
  node_type="decision", conf=0.9,
75
74
  )
76
- sv_schedule.stage_name = "schedule"
77
75
 
78
76
 
79
- # ── Stage 4: route —— 路由决策(关键词+调度意图;未命中且模型可用 → 决策主管自答)──
77
+ @named_stage("route")
80
78
  async def sv_route(ctx: PipelineContext) -> None:
81
79
  if ctx.extra.get("_meta_answered"):
82
80
  return
@@ -84,11 +82,6 @@ async def sv_route(ctx: PipelineContext) -> None:
84
82
  target = agent._route(ctx.task, ctx.sched)
85
83
  ctx.target = target
86
84
  ctx.route = {"target": target, "reason": "主管关键词+调度意图分诊"}
87
- # 无关键词命中 = 正面对话/泛化问题 → 主管**直接自答一次**,省掉"LLM 分诊 + 派发"的
88
- # 第二次串行 LLM 调用(每次约 5s 网络往返,正面对话不值当)。只有命中领域关键词
89
- # (财务/行政/办公/…)才派发给对应子 Agent。stub 下保持原路径(零额外调用、离线测试确定)。
90
- # 权衡:少数本该派给领域 Agent 的"无关键词"任务会由主管自答——用户明确选速度优先。
91
- # (本 stage 只做**决策**置标记;自答的 LLM 调用在 dispatch stage 执行。)
92
85
  if not agent._has_keyword_hit(ctx.task) and not agent.router.is_stub:
93
86
  agent._think(
94
87
  content="无关键词命中 → 主管直接自答(省掉二次分诊派发,正面对话提速)",
@@ -100,16 +93,13 @@ async def sv_route(ctx: PipelineContext) -> None:
100
93
  node_type="decision",
101
94
  conf=0.9,
102
95
  )
103
- sv_route.stage_name = "route"
104
96
 
105
97
 
106
- # ── Stage 5: plan —— planner 拆解(多步?跨 Agent?)────────────────
98
+ @named_stage("plan")
107
99
  async def sv_plan(ctx: PipelineContext) -> None:
108
100
  if ctx.extra.get("_meta_answered") or ctx.extra.get("_self_answer"):
109
101
  return
110
102
  agent = ctx.agent
111
- # P0-1:LLM 规划含同步网络调用(call_tools),放线程池避免阻塞事件循环;
112
- # 规则拆解路径是纯 CPU,放线程池开销可忽略、行为不变。
113
103
  plan_obj, step_routes = await asyncio.to_thread(agent._plan_task, ctx.task, ctx.sched)
114
104
  if plan_obj is not None:
115
105
  routes_desc = " → ".join(f"{name}[{ag}]" for name, ag in step_routes)
@@ -117,23 +107,20 @@ async def sv_plan(ctx: PipelineContext) -> None:
117
107
  content=f"planner 判定为多步任务({len(plan_obj.tasks)}步):{routes_desc}",
118
108
  node_type="decision", conf=0.85,
119
109
  )
120
- # 若子任务跨不同 Agent,标记为多 Agent 顺序执行
121
110
  distinct_agents = {ag for _, ag in step_routes}
122
111
  ctx.multi_agent = len(distinct_agents) > 1
123
112
  else:
124
113
  ctx.multi_agent = False
125
114
  ctx.plan_obj = plan_obj
126
115
  ctx.step_routes = step_routes or []
127
- sv_plan.stage_name = "plan"
128
116
 
129
117
 
130
- # ── Stage 6: dispatch —— 分发执行(自答 / 多 Agent / 单 Agent / 兜底)──
118
+ @named_stage("dispatch")
131
119
  async def sv_dispatch(ctx: PipelineContext) -> None:
132
120
  agent = ctx.agent
133
121
  if ctx.extra.get("_meta_answered"):
134
122
  return
135
123
 
136
- # 主管自答(route stage 的决策在此执行)
137
124
  if ctx.extra.get("_self_answer"):
138
125
  _msgs = [{"role": "user", "content": (
139
126
  f"你是 Foxclaw 的主管 Agent(Supervisor),统筹整个智能体团队。"
@@ -141,9 +128,6 @@ async def sv_dispatch(ctx: PipelineContext) -> None:
141
128
  f"问题作答,不要只做自我介绍、不要答非所问。若确属需要专门领域深办的任务,可"
142
129
  f"简要引导用户去对应能力,但仍先给出有用的直接回应。"
143
130
  )}]
144
- # 联网增强:新闻/实时类(_web_augment 内 needs_search 命中)走百度搜索注入真实结果
145
- # 再作答,避免主管凭空编造(系统 prompt 明令严禁模拟搜索)。纯闲聊 needs_search=False,
146
- # 不搜、不减速。阻塞 I/O 放线程池(与 base._execute 同法),不堵 async 事件循环。
147
131
  _msgs = await asyncio.to_thread(agent._web_augment, ctx.task, _msgs)
148
132
  reply = await agent._call_llm(_msgs)
149
133
  agent._store_memory(
@@ -157,7 +141,6 @@ async def sv_dispatch(ctx: PipelineContext) -> None:
157
141
 
158
142
  subagents = getattr(agent, "_subagents", {})
159
143
 
160
- # 多 Agent 执行:并行计划(无依赖)→ fork-join 并发;否则顺序执行后汇聚
161
144
  if ctx.multi_agent and ctx.step_routes:
162
145
  if getattr(ctx.plan_obj, "flow_type", "sequential") == "parallel":
163
146
  agent._think(
@@ -173,58 +156,46 @@ async def sv_dispatch(ctx: PipelineContext) -> None:
173
156
  subagents, ctx.context, ctx.memories)
174
157
  return
175
158
 
176
- # 单 Agent 分发
177
159
  if ctx.target in subagents:
178
160
  ctx.dispatch_result = await agent._dispatch_single(
179
161
  ctx.task, ctx.target, ctx.sched, ctx.session_id,
180
162
  subagents, ctx.context, ctx.memories, ctx.plan_obj)
181
163
  return
182
164
 
183
- # 子 Agent 不可用(未注册)→ 主管自处理
184
165
  ctx.dispatch_result = await agent._dispatch_fallback(ctx.task, ctx.context)
185
- sv_dispatch.stage_name = "dispatch"
186
166
 
187
167
 
188
- # ── Stage 6.5: replan —— 反思重规划(P0-1:失败步骤 → LLM 反思 → 修正计划 → 重执行)──
168
+ @named_stage("replan")
189
169
  async def sv_replan(ctx: PipelineContext) -> None:
190
170
  if ctx.extra.get("_meta_answered") or ctx.extra.get("_self_answer"):
191
171
  return
192
172
  agent = ctx.agent
193
173
  dr = ctx.dispatch_result or {}
194
174
  if not dispatch_failed(dr):
195
- return # 无失败/未完成步骤 → 无需重规划
175
+ return
196
176
  if not agent._replan_enabled():
197
- return # opt-in 关 / 不支持 tools → 保留原结果(fail-safe)
177
+ return
198
178
  subagents = getattr(agent, "_subagents", {})
199
179
  max_rounds = int((ctx.context or {}).get("replan_max_rounds", 2))
200
180
  ctx.dispatch_result = await agent._replan_and_redo(
201
181
  ctx.task, ctx.sched, ctx.session_id, subagents, ctx.context, ctx.memories,
202
182
  dr, max_rounds=max_rounds)
203
- sv_replan.stage_name = "replan"
204
183
 
205
184
 
206
- # ── Stage 7: aggregate —— 结果组装(dispatch_result → ctx.result/extra)──
185
+ @named_stage("aggregate")
207
186
  async def sv_aggregate(ctx: PipelineContext) -> None:
208
- # 清除内部短路标记,不外泄到返回体(它们会经 **ctx.extra 透传进 resp)
209
187
  ctx.extra.pop("_meta_answered", None)
210
188
  ctx.extra.pop("_self_answer", None)
211
189
  dr = ctx.dispatch_result or {}
212
190
  if not dr:
213
- return # meta 路径:result 已在 meta_check 设好
191
+ return
214
192
  ctx.result = dr.get("result", ctx.result)
215
- # 白名单透传(与原主管返回体逐字一致的顶层键);session_id/agent/thought_summary
216
- # 由 base 模板统一组装,不从 dispatch_result 取。
217
193
  for k in ("schedule", "plan", "process_id"):
218
194
  if k in dr:
219
195
  ctx.extra[k] = dr[k]
220
- # 「未完成 ⇒ 不作为已完成真值沉淀」:任一子任务被 Harness 拦截 → 提升为顶层
221
- # status=blocked(与 base 模板的 skill_status 提升同一机制),执行事件 blocked=True,
222
- # 真值链不沉淀(呼应 P3-5)。成功/自答路径不带此键,行为逐字不变。
223
196
  if isinstance(ctx.result, dict) and ctx.result.get("blocked"):
224
197
  ctx.extra["skill_status"] = "blocked"
225
- sv_aggregate.stage_name = "aggregate"
226
198
 
227
199
 
228
- # 主管 pipeline 的 stage 序列(recall 复用默认实现:与旧 `self._recall_memory(task)` 逐字等价)
229
200
  SUPERVISOR_STAGES = [stage_recall, sv_meta_check, sv_schedule, sv_route,
230
201
  sv_plan, sv_dispatch, sv_replan, sv_aggregate]
@@ -152,9 +152,9 @@ def _invoke_plan_tool(router, system: str, user_content: str, agent_catalog: dic
152
152
  return None
153
153
  calls = res.get("tool_calls") or []
154
154
  if not calls:
155
- return None # 模型未调用工具 → 不可用,回退
155
+ return None
156
156
  if calls[0].get("args_parse_failed"):
157
- return None # 参数 JSON 解析失败 → 不可信,回退
157
+ return None
158
158
  args = calls[0].get("args") or {}
159
159
  steps = args.get("steps")
160
160
  if not isinstance(steps, list):
@@ -168,7 +168,7 @@ def _invoke_plan_tool(router, system: str, user_content: str, agent_catalog: dic
168
168
  continue
169
169
  agent = str(s.get("agent") or "office").strip()
170
170
  if agent not in agent_catalog:
171
- agent = "office" # 幻觉出不存在的 Agent(含已下放的 assistant/admin)→ 兜底办公学习
171
+ agent = "office"
172
172
  dep = s.get("depends_on") or []
173
173
  if not isinstance(dep, list):
174
174
  dep = []
@@ -11,14 +11,11 @@
11
11
  from __future__ import annotations
12
12
  import re
13
13
  from dataclasses import dataclass, field
14
+ from typing import Optional
14
15
 
15
- # ── 单一真源:否定词 + 顺序/并行/列表 标记 ──────────────────────
16
16
  NEGATIONS = ["不", "别", "无需", "不要", "取消", "甭", "don't", "no need", "without"]
17
- # 顺序拆分信号(明确的顺序连接词;不用"和/并"等高风险常用字,避免误拆名词短语)
18
17
  SPLIT_MARKERS = ["然后", "接着", "之后", "再", "最后", ";", ";"]
19
- # 并行信号:出现这些"同时性"连接词 → 子任务彼此无依赖,可并行 fork
20
18
  PARALLEL_TRIGGERS = ["同时", "分别", "并行", "一并"]
21
- # 列表分隔符(仅在检出并行触发词时才当拆分点,避免误拆普通名词短语)
22
19
  LIST_SEPS = ["、", ",", ",", ";", ";"]
23
20
 
24
21
 
@@ -55,15 +52,15 @@ class ScoredCandidate:
55
52
 
56
53
  @dataclass
57
54
  class TriggerRoute:
58
- primary: str = None
55
+ primary: Optional[str] = None
59
56
  confidence: float = 0.0
60
- candidates: list = field(default_factory=list) # [(id, score)]
61
- fallback: str = None
57
+ candidates: list = field(default_factory=list)
58
+ fallback: Optional[str] = None
62
59
  needs_composition: bool = False
63
60
  reason: str = ""
64
61
 
65
62
 
66
- def trigger_route(task: str, candidates: list, fallback_id: str = None,
63
+ def trigger_route(task: str, candidates: list, fallback_id: str | None = None,
67
64
  min_conf: float = 0.15, near_gap: float = 0.1) -> TriggerRoute:
68
65
  """触发词路由三步打分:否定过滤后的触发命中(0.8)+ 描述领域匹配(0.2),确定性排序。
69
66
  这是 AHS 原 route 的算法,现作为三处共用的通用引擎。"""
@@ -75,17 +72,15 @@ def trigger_route(task: str, candidates: list, fallback_id: str = None,
75
72
  triggers = c.triggers or []
76
73
  if not triggers:
77
74
  scored.append((c.id, 0.0)); continue
78
- # 第1步:否定过滤后的触发命中数 → 命中即给强基础分,不被触发词总数稀释
79
75
  hits = keyword_hits(task, triggers, negation=True)
80
76
  hit_score = 0.0 if hits == 0 else min(0.6 + 0.15 * (hits - 1), 1.0)
81
- # 第2步:领域匹配(描述里的词出现在任务中,粗略语义近似)
82
77
  desc_words = [w for w in re.split(r"[\s,,、]", c.description or "") if len(w) >= 2]
83
78
  domain_hit = sum(1 for w in desc_words if w in task)
84
79
  domain_score = min(domain_hit / max(len(desc_words), 1), 1.0)
85
80
  score = 0.8 * hit_score + 0.2 * domain_score
86
81
  scored.append((c.id, round(score, 3)))
87
82
 
88
- scored.sort(key=lambda x: (-x[1], x[0])) # 分降序,平局按 id 字典序(确定性)
83
+ scored.sort(key=lambda x: (-x[1], x[0]))
89
84
  top_id, top_score = scored[0]
90
85
  cands = [(i, s) for i, s in scored if s > 0]
91
86
 
@@ -29,7 +29,7 @@ def _score(query: str, text: str) -> int:
29
29
  for term in re.split(r"[\s,,、;;::/]+", q):
30
30
  if len(term) >= 2 and term in t:
31
31
  score += 2
32
- for i in range(len(q) - 1): # 中文/无分隔场景的 2-gram 重叠
32
+ for i in range(len(q) - 1):
33
33
  if q[i:i + 2] in t:
34
34
  score += 1
35
35
  return score
@@ -65,13 +65,12 @@ class AgentSourcePort:
65
65
  try:
66
66
  scored += gather(text, top_k)
67
67
  except Exception:
68
- continue # 单一子源失败不影响其它子源
68
+ continue
69
69
  scored.sort(key=lambda x: x[0], reverse=True)
70
70
  return [ev for _, ev in scored[:max(top_k * 2, top_k)]]
71
71
 
72
- # ── 1. 领域内核 ──────────────────────────────────────────
73
72
  def _from_finance(self, text, top_k):
74
- out = []
73
+ out: list[tuple[float, RawEvidence]] = []
75
74
  rt = self.finance
76
75
  if rt is None:
77
76
  return out
@@ -101,7 +100,7 @@ class AgentSourcePort:
101
100
  return out
102
101
 
103
102
  def _from_learning(self, text, top_k):
104
- out = []
103
+ out: list[tuple[float, RawEvidence]] = []
105
104
  rt = self.learning
106
105
  if rt is None:
107
106
  return out
@@ -116,7 +115,7 @@ class AgentSourcePort:
116
115
  return out
117
116
 
118
117
  def _from_itinerary(self, text, top_k):
119
- out = []
118
+ out: list[tuple[float, RawEvidence]] = []
120
119
  rt = self.itinerary
121
120
  if rt is None:
122
121
  return out
@@ -130,9 +129,8 @@ class AgentSourcePort:
130
129
  confidence=_conf(s))))
131
130
  return out
132
131
 
133
- # ── 2. ThoughtChain 推理链 ───────────────────────────────
134
132
  def _from_thoughtchain(self, text, top_k):
135
- out = []
133
+ out: list[tuple[float, RawEvidence]] = []
136
134
  tc = self.thought_chain
137
135
  if tc is None or not hasattr(tc, "search_nodes"):
138
136
  return out
@@ -145,9 +143,8 @@ class AgentSourcePort:
145
143
  confidence=_conf(max(s, 1)))))
146
144
  return out
147
145
 
148
- # ── 3. 各 Agent 的 HMR 记忆 ─────────────────────────────
149
146
  def _from_memories(self, text, top_k):
150
- out = []
147
+ out: list[tuple[float, RawEvidence]] = []
151
148
  if self.hmr is None:
152
149
  return out
153
150
  for aid in (self.agents.keys() if self.agents else []):
@@ -167,7 +164,6 @@ class AgentSourcePort:
167
164
  confidence=_conf(s))))
168
165
  return out
169
166
 
170
- # ── 4. 能力/状态目录 ─────────────────────────────────────
171
167
  def _from_capabilities(self, text, top_k):
172
168
  out = []
173
169
  reg = self.skill_registry
@@ -24,7 +24,6 @@ from .models import RawEvidence, EvidenceGraph, EvidenceNode, EvidenceEdge
24
24
  _SIMILARITY_SUPPORT = 0.6
25
25
  _SIMILARITY_DUP = 0.92
26
26
 
27
- # 否定词:ASCII 按词边界(防 not⊂another/notebook、no⊂know);中文按子串(中文无词边界)
28
27
  _NEG_ASCII = ("not", "no longer", "never", "cannot", "isn't", "aren't", "won't")
29
28
  _NEG_CJK = ("不是", "并非", "并不", "没有", "无法", "不会", "未能")
30
29
  _NEG_ASCII_RE = re.compile(
@@ -37,9 +36,6 @@ def _has_neg(text: str) -> bool:
37
36
 
38
37
 
39
38
  def _sem_model_name() -> str:
40
- # 【P2·单 embedding 入口】解析链:与 harness.py 一致 ——
41
- # 显式传参 > EMBEDDING_MODEL(单入口) > HKC_EMBEDDING_MODEL > FOXCLAW_EVIDENCE_MODEL > HKC_KEE_SEMANTIC_MODEL > MiniLM 兜底。
42
- # 与 KEE 默认同名 → get_st_model 共享同一实例;可经环境变量换任意 ST 模型(模型无关)
43
39
  return (os.environ.get("FOXCLAW_EVIDENCE_MODEL")
44
40
  or os.environ.get("EMBEDDING_MODEL")
45
41
  or os.environ.get("HKC_EMBEDDING_MODEL")
@@ -23,9 +23,9 @@ _UNGROUNDED_PENALTY = 0.15
23
23
 
24
24
  class InternalSearchEngine:
25
25
  def __init__(self, memory_port=None, knowledge_port=None, agent_port=None):
26
- self.memory_port = memory_port # 只读查询 HMR:query(text, top_k) -> list[RawEvidence]
27
- self.knowledge_port = knowledge_port # HKC 只读查询接口(同进程 DirectClient)
28
- self.agent_port = agent_port # Agent 源:领域内核 + 推理链 + 各Agent记忆 + 能力/状态
26
+ self.memory_port = memory_port
27
+ self.knowledge_port = knowledge_port
28
+ self.agent_port = agent_port
29
29
 
30
30
  @staticmethod
31
31
  def _safe_query(port, text: str, top_k: int) -> list[RawEvidence]:
@@ -46,8 +46,6 @@ class InternalSearchEngine:
46
46
  memory_hits = self._safe_query(self.memory_port, text, top_k)
47
47
  agent_hits = self._safe_query(self.agent_port, text, top_k)
48
48
 
49
- # 知识候选用「内部一切可溯源证据」(记忆 + Agent 态势)交叉验证:找得到就 grounded,
50
- # 找不到就做孤证惩罚。这样 HKC 权威知识与 Agent 实际持有的数据互为印证。
51
49
  if knowledge_hits:
52
50
  knowledge_hits = self._cross_link(knowledge_hits, memory_hits + agent_hits)
53
51
 
@@ -12,17 +12,16 @@ from typing import Any, Optional
12
12
 
13
13
 
14
14
  class RouteType(str, Enum):
15
- INTERNAL = "HSR-I" # 查内部(HMR 记忆 / 未来的 HKC 知识)
16
- EXTERNAL = "HSR-E" # 查外部世界(联网搜索)
17
- HYBRID = "HYBRID" # 内外混合 + 交叉验证
15
+ INTERNAL = "HSR-I"
16
+ EXTERNAL = "HSR-E"
17
+ HYBRID = "HYBRID"
18
18
 
19
19
 
20
20
  class SourceType(str, Enum):
21
- MEMORY = "memory" # 来自 HMR(只读查询)
22
- KNOWLEDGE = "knowledge" # 来自 HKC(通过 foxclaw-hkc 的 hkc_sdk 只读查询)
23
- AGENT = "agent" # 来自五个 Agent 持有的内部数据:领域内核(财务/学习/行程)
24
- # + ThoughtChain 推理链 + 各 Agent HMR 记忆 + 能力/状态目录
25
- WEB = "web" # 来自联网搜索(当前接的是百度 AI 搜索)
21
+ MEMORY = "memory"
22
+ KNOWLEDGE = "knowledge"
23
+ AGENT = "agent"
24
+ WEB = "web"
26
25
 
27
26
 
28
27
  @dataclass
@@ -45,11 +44,10 @@ class RawEvidence:
45
44
  id: str
46
45
  fact: str
47
46
  source: SourceType
48
- origin: str # 具体来源:hmr 记忆 id / 网页 url
47
+ origin: str
49
48
  confidence: float = 0.5
50
49
  raw: Any = None
51
- grounded_by: Optional[str] = None # 指向另一条证据的 id:
52
- # "这条知识由这条 HMR 记忆溯源支撑"
50
+ grounded_by: Optional[str] = None
53
51
 
54
52
 
55
53
  @dataclass
@@ -66,7 +64,7 @@ class EvidenceNode:
66
64
  class EvidenceEdge:
67
65
  src: str
68
66
  dst: str
69
- relation: str # "supports" | "contradicts" | "grounded_by"
67
+ relation: str
70
68
 
71
69
 
72
70
  @dataclass
@@ -103,7 +101,7 @@ class EvidenceGraph:
103
101
 
104
102
  @dataclass
105
103
  class StrategyDecision:
106
- action: str # "continue" | "stop" | "switch_query" | "switch_source"
104
+ action: str
107
105
  reason: str
108
106
  next_steps: list[SubQuery] = field(default_factory=list)
109
107
 
@@ -7,7 +7,7 @@ Query Planner —— 决定路由(HSR-I / HSR-E / HYBRID),并把任务拆成
7
7
  """
8
8
  from __future__ import annotations
9
9
  from .models import SearchPlan, SubQuery, RouteType
10
- from ..planning import planner as P # 统一拆分原语(P1-2)
10
+ from ..planning import planner as P
11
11
 
12
12
  _MEMORY_HINTS = ("我之前", "我们讨论过", "上次", "记得", "history", "之前说")
13
13
  _HYBRID_HINTS = ("为什么", "对比", "分析", "综合", "评估")
@@ -28,7 +28,7 @@ class QueryPlanner:
28
28
  steps = [SubQuery(id="s1", text=query)]
29
29
  elif route == RouteType.EXTERNAL:
30
30
  steps = self._decompose(query)
31
- else: # HYBRID:先查内部,再拆外部子问题
31
+ else:
32
32
  steps = [SubQuery(id="s1", text=query), *self._decompose(query, offset=2)]
33
33
 
34
34
  return SearchPlan(
@@ -38,7 +38,7 @@ class QueryPlanner:
38
38
  )
39
39
 
40
40
  def _decompose(self, query: str, offset: int = 1) -> list[SubQuery]:
41
- parts = P.split_on(query, P.LIST_SEPS) # 统一拆分原语(等价于原 [,,、;;])
41
+ parts = P.split_on(query, P.LIST_SEPS)
42
42
  if len(parts) <= 1:
43
43
  parts = [query]
44
44
  return [SubQuery(id=f"s{offset + i}", text=p) for i, p in enumerate(parts)]
@@ -35,5 +35,5 @@ class Retriever:
35
35
  try:
36
36
  out += port.search(step.text)
37
37
  except Exception:
38
- continue # 单个外部数据源失败不影响其它数据源/主流程
38
+ continue
39
39
  return out
@@ -69,22 +69,16 @@ class SearchRuntime:
69
69
  def __init__(self, hmr=None, knowledge_port=None, agent_port=None,
70
70
  external_ports: list | None = None):
71
71
  self.hmr = hmr
72
- # knowledge_port 由 foxclaw/__init__.py 在启动时构造好传进来
73
- # (进程内直连的 HKCReadPort)。这里不做任何自动连接/默认值——
74
- # 连不连 HKC、怎么连,是 Foxclaw 启动流程的事,不是 SearchRuntime 的事。
75
72
  self.knowledge_port = knowledge_port
76
- # agent_port:内部搜索的「Agent」源(领域内核 + 推理链 + 各Agent记忆 + 能力/状态),
77
- # 同样由 Foxclaw 启动时用各持有方引用构造好传进来。
78
73
  self.agent_port = agent_port
79
74
  self.planner = QueryPlanner()
80
75
  self.fusion = EvidenceFusion()
81
76
  self.strategy = SearchStrategy()
82
- # 外部端口(联网)面向 AI/Agent:是证据/工具管线的一环,不是给人的搜索框。
83
77
  self.external_ports = (
84
78
  external_ports if external_ports is not None else [BaiduWebSearchPort()]
85
79
  )
86
80
 
87
- def search(self, query: str, agent_id: str = None, context: dict = None,
81
+ def search(self, query: str, agent_id: str | None = None, context: dict | None = None,
88
82
  internal_only: bool = False) -> SearchResult:
89
83
  """唯一公开入口。给定 query,返回 SearchResult,过程中不写任何持久化状态。
90
84
 
@@ -101,7 +95,6 @@ class SearchRuntime:
101
95
  trace: list[str] = []
102
96
  plan = self.planner.plan(query, context)
103
97
  if internal_only and plan.route == RouteType.EXTERNAL:
104
- # 只查内部时,把纯外部路由拉回内部,保证内部三源(记忆/知识/Agent)被检索
105
98
  plan.route = RouteType.INTERNAL
106
99
  trace.append("[internal_only] 路由改写为 HSR-I(仅内部)")
107
100
  trace.append(f"[plan] {plan.reasoning}")