foxclaw 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,659 @@
1
+ """SupervisorAgent —— 主管(总协调、任务分发、全局态势)。"""
2
+
3
+ import asyncio
4
+ import json
5
+ import time
6
+ from datetime import datetime
7
+
8
+ from ..base import CognitiveAgent
9
+ from ...planning import planner as P
10
+ from ._helpers import _with_prior, _dispatch_failed, _extract_step_results, PRIOR_MAX_CHARS
11
+
12
+ class SupervisorAgent(CognitiveAgent):
13
+ AGENT_ID = "supervisor"
14
+ AGENT_NAME = "主管 Agent"
15
+ ALLOWED_TOOL_CATEGORIES = {"read", "write", "calendar", "finance", "network"}
16
+ IS_ROUTER = True # 调度层:豁免语义漂移拦截(接收任意输入是其本职)
17
+
18
+ @property
19
+ def _role_prompt(self) -> str:
20
+ return """你是 Foxclaw 的主管 Agent,负责总体协调和任务分发。
21
+
22
+ 你的核心职责:
23
+ 1. 理解用户的整体目标,将复杂任务分解为子任务
24
+ 2. 根据任务性质,决定分配给哪个子 Agent(财务 / 办公学习)
25
+ 3. 整合各 Agent 的返回结果,向用户呈现统一的答案
26
+ 4. 维护全局状态,感知各 Agent 的工作进展
27
+
28
+ 你的工作风格:
29
+ - 清晰、高效、以结果为导向
30
+ - 对用户的长期目标保持记忆,而不是只处理当前请求
31
+ - 当任务超出某个子 Agent 能力范围时,主动说明
32
+
33
+ 注意:你有访问所有类别工具的权限,但应该谨慎使用,优先委派给专门的子 Agent。
34
+
35
+ 关于联网:你具备实时联网搜索能力——需要最新/实时信息(版本、新闻、价格等)时,结果会自动注入上下文,或由你调用工具获取;直接基于检索结果作答,没查到就如实说"暂未查到",别声称"没有联网能力"。(不编造实时信息的通则见内核·真相优先,不再赘述。)"""
36
+
37
+ def register_subagents(self, subagents: dict):
38
+ """注入可调度的子 Agent 登记表。"""
39
+ 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
+ from ...core.scheduler import CognitiveScheduler
46
+ from ...core.steering_queue import SteeringQueue
47
+ if not hasattr(self, "_scheduler"):
48
+ self._scheduler = CognitiveScheduler()
49
+ if not hasattr(self, "_steering_queue"):
50
+ self._steering_queue = SteeringQueue(lease_seconds=120)
51
+
52
+ def attach_orchestrator(self, orchestrator):
53
+ """挂载编排引擎:全局态势 + 跨 Agent 请求路由。"""
54
+ self.orch = orchestrator
55
+
56
+ def global_situation(self) -> dict:
57
+ """主管的驾驶舱视图:汇总三个内核状态。"""
58
+ orch = getattr(self, "orch", None)
59
+ if orch is None:
60
+ return {"error": "编排引擎未挂载"}
61
+ sit = orch.gather_situation()
62
+ self._think(
63
+ content=f"全局态势汇总:{len(sit.alerts)} 条跨域告警 "
64
+ f"(财务/学习/行程)",
65
+ node_type="reflection", conf=0.9,
66
+ )
67
+ return sit.to_dict()
68
+
69
+ def cross_request(self, from_agent: str, to_agent: str,
70
+ request_type: str, payload: dict, reason: str = "") -> dict:
71
+ """发起跨 Agent 请求,目标 Agent 用自己的守门人裁决。"""
72
+ orch = getattr(self, "orch", None)
73
+ if orch is None:
74
+ return {"error": "编排引擎未挂载"}
75
+ from foxclaw.orchestration import CrossAgentRequest
76
+ req = CrossAgentRequest(from_agent=from_agent, to_agent=to_agent,
77
+ request_type=request_type, payload=payload,
78
+ reason=reason)
79
+ # 走协商闭环:被拒时给发起方让步重报的机会。对未实现 concede 的 Agent 而言
80
+ # 只会跑一轮 → 与原先的一次性 route_request 逐字等价(零行为变更)。
81
+ result = orch.negotiate(req)
82
+ rounds = len(result.negotiation) or 1
83
+ self._think(
84
+ content=f"跨 Agent 编排:{from_agent}→{to_agent} "
85
+ f"({request_type}) 裁决={result.status} 协商={rounds}轮",
86
+ node_type="decision", conf=0.88,
87
+ )
88
+ return result.to_dict()
89
+
90
+ # 路由关键词表:无 LLM 时也能基于关键词分发。
91
+ # 拓扑 5 → 3 后:原行政(日程/行程)关键词并入办公;"助手"那组(整理/汇总/沟通)
92
+ # 不再有归属 —— 它们正是主管自己该干的兜底活,未命中 → 主管自答。
93
+ ROUTING_KEYWORDS = {
94
+ "finance": ["收支", "支出", "预算", "理财", "投资", "储蓄", "财务", "消费", "报销", "账"],
95
+ "office": ["学习", "论文", "知识", "总结", "研究", "文档", "写作", "资料", "搜索", "查",
96
+ "日程", "行程", "会议", "出差", "预约", "日历", "计划表", "提醒",
97
+ # 原助手的"整理/汇总/邮件"归办公:那是内容活,主管只兜底通用与协调
98
+ "整理", "汇总", "邮件", "纪要"],
99
+ }
100
+
101
+ # 子 Agent 职责描述(P0-1 LLM 规划用):喂给 submit_plan 工具做 agent 枚举约束
102
+ SUBAGENT_DESC = {
103
+ "finance": "财务 Agent:理财、收支、预算、风控、对账",
104
+ "office": "办公学习 Agent:学习、知识管理、研究、写作、日程与行程安排",
105
+ }
106
+
107
+ # 元问题关键词:这类"系统级/自我介绍"问题由主管自己回答,不分派
108
+ META_KEYWORDS = ["你会做什么", "你能做什么", "你是谁", "你能干", "你会干",
109
+ "介绍一下", "介绍下", "自我介绍", "有什么功能", "能帮我做什么",
110
+ "你都能", "怎么用", "如何使用", "你好", "在吗", "干什么",
111
+ "你能联网", "你可以联网", "能联网吗", "支持联网", "会联网"]
112
+
113
+ def _is_meta(self, task: str) -> bool:
114
+ """判断是否为系统级/介绍类元问题(主管自答,不分派)。"""
115
+ t = task.strip()
116
+ has_meta = any(k in t for k in self.META_KEYWORDS)
117
+ has_biz = any(P.keyword_hits(t, kws) for kws in self.ROUTING_KEYWORDS.values())
118
+ return has_meta and not has_biz
119
+
120
+ @staticmethod
121
+ def _is_expense_report(task: str) -> bool:
122
+ """是不是一句「零星花钱报备」(今天买衣服 105 块 / 后天洗车 100)。
123
+
124
+ 这类句子**不含任何财务关键词**,但它是财务应用最前线的输入。不在这里拦一道,
125
+ 就会被当成闲聊由主管自答,账直接丢了 —— 所以它必须算作"命中财务领域"。
126
+ """
127
+ try:
128
+ from ...finance.nlparse import has_expense_signal
129
+ return has_expense_signal(task)
130
+ except Exception:
131
+ return False
132
+
133
+ def _route(self, task: str, sched=None) -> str:
134
+ """决定任务派给哪个子 Agent。
135
+
136
+ 综合决策:花费报备(最高优先)+ 关键词命中(主)+ 调度意图(辅)。
137
+ - 花费报备 → 财务(哪怕一个财务关键词都没出现)
138
+ - 关键词明确命中 → 直接采用(领域词最可靠)
139
+ - 关键词不明 + 调度意图为 memory/reasoning → 倾向 office(知识/研究)
140
+ - 都不明 → "supervisor"(主管自答;助手已下放为技能,不再有兜底 Agent)
141
+ """
142
+ if self._is_expense_report(task):
143
+ return "finance"
144
+ # 关键词命中计数走统一原语(negation=False,与原计数逐字一致)
145
+ scores = {aid: P.keyword_hits(task, kws)
146
+ for aid, kws in self.ROUTING_KEYWORDS.items()}
147
+ best = max(scores, key=scores.get)
148
+ if scores[best] > 0:
149
+ return best
150
+ # 关键词不明:用调度意图做辅助判断
151
+ if sched is not None:
152
+ intent = getattr(getattr(sched, "intent", None), "category", None)
153
+ if intent in ("reasoning", "memory"):
154
+ return "office" # 推理/记忆类 → 知识研究 Agent
155
+ # 无明确归属 → 主管自答(dispatch 见 target 不在 subagents 即走 _dispatch_fallback)
156
+ return "supervisor"
157
+
158
+ def _coerce_agent(self, ag: str, subtask: str, subagents: dict) -> str:
159
+ """把 planner 给出的未知 agent 名收敛到一个真实存在的子 Agent。
160
+
161
+ 拓扑收缩后旧名字(assistant/admin)可能仍出现在 LLM 的计划里 —— 直接按子任务重新
162
+ 分诊一次;仍不命中就交给"办公学习"(通用性最强),绝不留一个 `subagents[ag]` 会
163
+ KeyError 的名字。"""
164
+ re_routed = self._route(subtask)
165
+ if re_routed in subagents:
166
+ return re_routed
167
+ return "office" if "office" in subagents else next(iter(subagents), "")
168
+
169
+ def _has_keyword_hit(self, task: str) -> bool:
170
+ """任务是否命中任一领域关键词(命中 = 关键词路由可信,不必再问 LLM)。
171
+
172
+ 花费报备也算命中 —— 否则 route stage 会把「今天买衣服 105 块」判成
173
+ "无关键词 → 主管自答",连派发都进不去。
174
+ """
175
+ return (any(P.keyword_hits(task, kws) for kws in self.ROUTING_KEYWORDS.values())
176
+ or self._is_expense_report(task))
177
+
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
+
186
+ def _plan_task(self, task: str, sched=None):
187
+ """用 planner 判断任务是否需要拆解(多步)。
188
+
189
+ 返回 (plan, step_routes) 或 (None, None)。
190
+ step_routes: [(子任务文本, 该去的子Agent)],让 planner 的拆解真正映射到 Agent。
191
+
192
+ 【P0-1】FOXCLAW_LLM_PLAN=1 时优先 LLM 规划(function-calling 拆解+路由);
193
+ 不可用/失败/单步 → 回退下方规则拆解(行为不变)。
194
+ """
195
+ llm = self._llm_plan(task)
196
+ if llm is not None:
197
+ return llm
198
+ try:
199
+ from ...skills.pipeline import plan as ahs_plan
200
+ reg = getattr(self, "skill_registry", None)
201
+ if reg is None:
202
+ return None, None
203
+ p = ahs_plan(task, reg)
204
+ if len(p.tasks) <= 1:
205
+ return None, None
206
+ # 为每个子任务单独算路由(关键词为主)→ planner 拆解真正落到不同 Agent
207
+ step_routes = [(t.name, self._route(t.name)) for t in p.tasks]
208
+ return p, step_routes
209
+ except Exception:
210
+ return None, None
211
+
212
+ def _llm_plan(self, task: str):
213
+ """【P0-1】LLM 驱动规划:function-calling 拆解任务并路由到子 Agent。
214
+
215
+ 返回 (plan, step_routes) 或 None(未开启/不支持 tools/失败/单步)。
216
+ 单步返回 None 交回 _route 关键词路由,不改变单步行为。
217
+ """
218
+ from ...planning.llm_planner import llm_plan, llm_plan_enabled
219
+ if not llm_plan_enabled():
220
+ return None
221
+ if self.router.is_stub or not self.router.supports_tools():
222
+ return None
223
+ subagents = getattr(self, "_subagents", {}) or {}
224
+ if not subagents:
225
+ return None
226
+ catalog = {aid: self.SUBAGENT_DESC.get(aid, getattr(a, "AGENT_NAME", aid))
227
+ for aid, a in subagents.items()}
228
+ try:
229
+ result = llm_plan(self.router, task, catalog)
230
+ except Exception:
231
+ return None
232
+ if not result or not result.get("steps"):
233
+ return None
234
+ steps = result["steps"]
235
+ if len(steps) <= 1:
236
+ return None # 单步 → 交回 _route 关键词路由,不改变单步行为
237
+ from ...skills.pipeline import Plan, PlanTask
238
+ n = len(steps)
239
+ parallel = result.get("flow") == "parallel"
240
+ tasks = [
241
+ PlanTask(id=f"T{i+1}", name=s["name"], suggested_skill=None,
242
+ dependencies=[] if parallel else ([f"T{i}"] if i > 0 else []))
243
+ for i, s in enumerate(steps)
244
+ ]
245
+ plan = Plan(intent=result.get("intent") or task, tasks=tasks,
246
+ flow_type=result.get("flow") or "sequential",
247
+ steps=[f"T{i+1}" for i in range(n)])
248
+ step_routes = [(s["name"], s["agent"]) for s in steps]
249
+ return plan, step_routes
250
+
251
+ def _replan_enabled(self) -> bool:
252
+ """【P0-1】反思重规划是否可用:opt-in + 模型支持 tools + 有子 Agent。"""
253
+ from ...planning.llm_planner import replan_enabled
254
+ return (replan_enabled()
255
+ and not self.router.is_stub
256
+ and self.router.supports_tools()
257
+ and bool(getattr(self, "_subagents", {})))
258
+
259
+ async def _replan_and_redo(self, task, sched, session_id, subagents,
260
+ context, memories, dispatch_result, max_rounds: int = 2):
261
+ """【P0-1】反思重规划:多步执行含失败时,用 LLM 反思并产出修正计划,重新执行。
262
+
263
+ 返回最终 dispatch_result(成功则为最后一次执行结果;失败/不可用则保留最后结果)。
264
+ 循环最多 max_rounds 轮防死循环;每轮先反思再执行,成功即停。
265
+ """
266
+ from ...planning.llm_planner import llm_replan
267
+ catalog = {aid: self.SUBAGENT_DESC.get(aid, getattr(a, "AGENT_NAME", aid))
268
+ for aid, a in subagents.items()}
269
+ current = dispatch_result
270
+ for rnd in range(1, max_rounds + 1):
271
+ prior = _extract_step_results(current)
272
+ self._think(
273
+ content=f"反思重规划 第{rnd}轮:检测到失败步骤,LLM 反思并产出修正计划",
274
+ node_type="reflection", conf=0.85,
275
+ )
276
+ try:
277
+ replan = await asyncio.to_thread(
278
+ llm_replan, self.router, task, prior, catalog)
279
+ except Exception:
280
+ replan = None
281
+ if not replan or not replan.get("steps"):
282
+ self._think(
283
+ content="反思判定:无法完成/无需重试,终止重规划",
284
+ node_type="reflection", conf=0.8,
285
+ )
286
+ break
287
+ new_routes = [(s["name"], s["agent"]) for s in replan["steps"]]
288
+ self._think(
289
+ content=f"修正计划({len(new_routes)}步):"
290
+ + " → ".join(n for n, _ in new_routes),
291
+ node_type="decision", conf=0.8,
292
+ )
293
+ if replan.get("flow") == "parallel":
294
+ current = await self._dispatch_parallel(
295
+ task, new_routes, sched, session_id, subagents, context, memories)
296
+ else:
297
+ current = await self._dispatch_multi(
298
+ task, new_routes, sched, session_id, subagents, context, memories)
299
+ if not _dispatch_failed(current):
300
+ self._think(
301
+ content=f"重规划第{rnd}轮执行成功",
302
+ node_type="reflection", conf=0.9,
303
+ )
304
+ break
305
+ return current
306
+
307
+ def _schedule(self, task: str, subagents: dict):
308
+ """认知调度:用 OS 式调度器算优先级/负载/抢占。返回 ScheduleDecision。"""
309
+ from ...core.scheduler import CognitiveScheduler, SystemContext
310
+ if not hasattr(self, "_scheduler"):
311
+ self._scheduler = CognitiveScheduler()
312
+ # 系统上下文:活跃 Agent 数 = 当前持有活跃 session 的子 Agent 数
313
+ # (用真实可得的 _session_id 判断忙碌,而非不存在的 is_busy 属性)
314
+ active = sum(1 for a in subagents.values()
315
+ if getattr(a, "_session_id", None))
316
+ ctx = SystemContext(active_agents=active, queue_depth=0, system_load=0.1)
317
+ return self._scheduler.schedule(task, ctx)
318
+
319
+ async def _dispatch_multi(self, task, step_routes, sched, session_id,
320
+ subagents, context, memories) -> dict:
321
+ """多 Agent **链式**顺序执行:每个子任务派给各自的 Agent,后一步能看到前序步骤的结果。
322
+
323
+ (原先每步都只拿到同一份原始 context,等于 N 个互不相干的任务排队跑——
324
+ 「查本月餐饮花了多少,再据此定预算」的第 2 步根本拿不到第 1 步的数字。)"""
325
+ pm = getattr(self, "_process_manager", None)
326
+ bus = getattr(self, "_system_bus", None)
327
+ steps_out = []
328
+ prior = [] # 已完成步骤的结果,供后续步骤参考
329
+ any_blocked = False
330
+ for idx, (subtask, ag) in enumerate(step_routes, 1):
331
+ if ag not in subagents:
332
+ ag = self._coerce_agent(ag, subtask, subagents)
333
+ proc = pm.create(subtask, agent_id=ag, schedule=sched.to_dict()) if pm else None
334
+ self._think(
335
+ content=f"多步执行 第{idx}步:「{subtask}」→ {ag}",
336
+ node_type="tool_intent", tool_name=f"dispatch_{ag}", conf=0.9,
337
+ )
338
+ if proc:
339
+ 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
+ _timeout = float(context.get("parallel_timeout", 120))
345
+ try:
346
+ sub_result = await asyncio.wait_for(
347
+ subagents[ag].run_task(
348
+ _with_prior(subtask, prior),
349
+ {**context, "_no_emit": True, "prior_steps": list(prior)}),
350
+ timeout=_timeout)
351
+ except asyncio.TimeoutError:
352
+ _note = f"第{idx}步子 Agent {ag} 执行超时({_timeout:g}s),该步未完成"
353
+ if proc:
354
+ pm.mark_failed(proc.process_id, reason="subagent_timeout")
355
+ sub_result = {"result": _note, "error": "subagent_timeout", "agent": ag}
356
+ sub_output = sub_result.get("result", "")
357
+ if isinstance(sub_output, dict):
358
+ sub_output = json.dumps(sub_output, ensure_ascii=False)
359
+ # 未完成(含超时)一律视为 blocked —— 不得让超时步骤被当成
360
+ # 成功结果进入 prior 链,否则后续步骤会基于"不存在的产出"继续推理。
361
+ blocked = sub_result.get("error") in ("harness_blocked", "subagent_timeout")
362
+ any_blocked = any_blocked or blocked
363
+ if proc:
364
+ (pm.mark_failed(proc.process_id, reason=sub_result.get("error") or "")
365
+ if blocked else pm.mark_completed(proc.process_id))
366
+ # 展示用的 subtask 保留**原始**文本(不含前序上下文前言,免污染 UI)
367
+ steps_out.append({"step": idx, "subtask": subtask, "agent": ag,
368
+ "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
+ # 长链 + 大输出会滚成可观的内存与拷贝开销。
373
+ prior.append({"step": idx, "agent": ag,
374
+ "output": str(sub_output)[:PRIOR_MAX_CHARS]})
375
+ # 【P1-1】execution.completed 不再逐子任务手动 publish——改由 base 模板
376
+ # `_emit_execution` 统一发一条**任务级**事件(子 Agent 均带 _no_emit=True)。
377
+ # 无总线(部分测试/独立调用)→ 主管手动兜底沉淀记忆,与单步路径一致
378
+ if bus is None and not blocked:
379
+ self._store_memory(
380
+ content=f"任务「{subtask}」由 {ag} 处理。要点:{str(sub_output)[:200]}",
381
+ mem_type="decision",
382
+ title=f"多步记录_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{idx}",
383
+ tags=["dispatch", "multi_step", f"to:{ag}"],
384
+ shared=True,
385
+ )
386
+
387
+ summary = f"多步任务完成,共 {len(steps_out)} 步,跨 {len({s['agent'] for s in steps_out})} 个 Agent"
388
+ return {
389
+ "result": {
390
+ "dispatched_to": "multi",
391
+ "multi_steps": steps_out,
392
+ "blocked": any_blocked,
393
+ "summary": summary,
394
+ },
395
+ "schedule": sched.to_dict(),
396
+ "plan": {"multi_step": True, "multi_agent": True,
397
+ "steps": [s["subtask"] for s in steps_out]},
398
+ "session_id": session_id,
399
+ "agent": self.AGENT_ID,
400
+ "thought_summary": self.thought_chain.get_summary(self.AGENT_ID),
401
+ }
402
+
403
+ async def _dispatch_parallel(self, task, step_routes, sched, session_id,
404
+ subagents, context, memories) -> dict:
405
+ """并行 fork-join:无依赖子任务并发派给不同 Agent,租约队列汇聚结果。
406
+
407
+ 并发安全设计(与全系统"线程安全铁律"一致):
408
+ · 跨 Agent 并行,但**同一 Agent 的多个子任务在组内串行** —— 因 Agent 实例持有
409
+ _session_id/_parent_node_id 这类会话态,同实例并发会串味;
410
+ · 主管**自身**的 _think 只在 fork 前 / join 后串行调用,绝不在并发分支里碰,
411
+ 避免主管会话态竞态;
412
+ · 子任务结果经 SteeringQueue(RLock)publish→lease→ack 回收:不丢(超时租约重入队)、
413
+ 不重(锁内挑选);ProcessManager/SystemBus/HMR 均已加锁,并发写审计不丢。
414
+ · 每个子任务带超时(计算上的"租约"):某分支卡死不拖垮整批。
415
+ """
416
+ from ...core.steering_queue import SteeringQueue
417
+ if not hasattr(self, "_steering_queue"):
418
+ self._steering_queue = SteeringQueue(lease_seconds=120)
419
+ q = self._steering_queue
420
+ batch = f"batch_{session_id}"
421
+ pm = getattr(self, "_process_manager", None)
422
+ bus = getattr(self, "_system_bus", None)
423
+ timeout = float(context.get("parallel_timeout", 120))
424
+
425
+ agents_involved = sorted({ag for _, ag in step_routes})
426
+ # fork 记录(串行,主管自身 think 不并发)
427
+ self._think(
428
+ content=f"并行 fork {len(step_routes)} 个子任务 → {agents_involved}",
429
+ node_type="tool_intent", tool_name="fork_parallel", conf=0.9,
430
+ )
431
+
432
+ # 按 Agent 分组:不同组并行,同组内串行(护 per-agent 会话态)
433
+ groups: dict[str, list] = {}
434
+ for idx, (subtask, ag) in enumerate(step_routes):
435
+ if ag not in subagents:
436
+ ag = self._coerce_agent(ag, subtask, subagents)
437
+ groups.setdefault(ag, []).append((idx, subtask))
438
+
439
+ async def run_group(ag, items):
440
+ for idx, subtask in items:
441
+ proc = pm.create(subtask, agent_id=ag,
442
+ schedule=sched.to_dict()) if pm else None
443
+ if proc:
444
+ pm.mark_running(proc.process_id)
445
+ try:
446
+ sub = await asyncio.wait_for(
447
+ subagents[ag].run_task(subtask, {**context, "_no_emit": True}),
448
+ timeout=timeout)
449
+ out = sub.get("result", "")
450
+ if isinstance(out, dict):
451
+ out = json.dumps(out, ensure_ascii=False)
452
+ blocked = sub.get("error") == "harness_blocked"
453
+ if proc:
454
+ (pm.mark_failed(proc.process_id, reason="harness_blocked")
455
+ if blocked else pm.mark_completed(proc.process_id))
456
+ q.publish(batch, ag, {"idx": idx, "subtask": subtask, "agent": ag,
457
+ "output": str(out)[:200], "blocked": blocked,
458
+ "failed": False})
459
+ # 【P1-1】execution.completed 不再逐子任务手动 publish——
460
+ # 改由 base 模板 `_emit_execution` 统一发任务级事件。
461
+ except asyncio.TimeoutError:
462
+ if proc:
463
+ pm.mark_failed(proc.process_id, reason="timeout")
464
+ q.publish(batch, ag, {"idx": idx, "subtask": subtask, "agent": ag,
465
+ "output": f"(子任务超时 >{timeout}s)",
466
+ "blocked": False, "failed": True})
467
+ except Exception as e:
468
+ if proc:
469
+ pm.mark_failed(proc.process_id, reason=f"error:{e}")
470
+ q.publish(batch, ag, {"idx": idx, "subtask": subtask, "agent": ag,
471
+ "output": f"(子任务失败: {e})",
472
+ "blocked": False, "failed": True})
473
+
474
+ t0 = time.perf_counter()
475
+ await asyncio.gather(*[run_group(ag, items) for ag, items in groups.items()])
476
+ wall_ms = round((time.perf_counter() - t0) * 1000, 1)
477
+
478
+ # join:租约收集(不丢不重)→ 按 idx 还原原始顺序 → ack
479
+ leased = q.lease(batch, max_items=len(step_routes) * 2)
480
+ steps_out = []
481
+ for it in leased:
482
+ steps_out.append(it.payload)
483
+ q.ack(it.item_id, it.lease_id)
484
+ steps_out.sort(key=lambda s: s["idx"])
485
+
486
+ any_blocked = any(s.get("blocked") for s in steps_out)
487
+ any_failed = any(s.get("failed") for s in steps_out)
488
+ # join 记录(串行)
489
+ self._think(
490
+ content=f"并行 join:{len(steps_out)}/{len(step_routes)} 步汇聚,"
491
+ f"墙钟 {wall_ms}ms,blocked={any_blocked},failed={any_failed}",
492
+ node_type="reflection", conf=0.88,
493
+ )
494
+ # 无总线时兜底沉淀记忆(与顺序路径一致)
495
+ if bus is None:
496
+ for s in steps_out:
497
+ if not s.get("blocked") and not s.get("failed"):
498
+ self._store_memory(
499
+ content=f"任务「{s['subtask']}」由 {s['agent']} 处理。"
500
+ f"要点:{str(s['output'])[:200]}",
501
+ mem_type="decision",
502
+ title=f"并行记录_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{s['idx']}",
503
+ tags=["dispatch", "parallel", f"to:{s['agent']}"], shared=True)
504
+
505
+ return {
506
+ "result": {
507
+ "dispatched_to": "parallel",
508
+ "parallel_steps": steps_out,
509
+ "blocked": any_blocked,
510
+ "summary": f"并行完成 {len(steps_out)} 步,跨 {len(groups)} 个 Agent,"
511
+ f"墙钟 {wall_ms}ms",
512
+ },
513
+ "schedule": sched.to_dict(),
514
+ "plan": {"multi_step": True, "multi_agent": True, "parallel": True,
515
+ "wall_ms": wall_ms, "steps": [s["subtask"] for s in steps_out]},
516
+ "session_id": session_id,
517
+ "agent": self.AGENT_ID,
518
+ "thought_summary": self.thought_chain.get_summary(self.AGENT_ID),
519
+ }
520
+
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
+
527
+ def _cognitive_pipeline(self):
528
+ """主管的认知管线:SUPERVISOR_STAGES(路由分发),而非领域 Agent 的 DEFAULT_STAGES。"""
529
+ pipe = getattr(self, "_pipeline_cache", None)
530
+ if pipe is None:
531
+ from ...pipeline import CognitivePipeline
532
+ from ...pipeline.supervisor_stages import SUPERVISOR_STAGES
533
+ pipe = self._pipeline_cache = CognitivePipeline(SUPERVISOR_STAGES)
534
+ return pipe
535
+
536
+ def _task_entities(self, task: str) -> list:
537
+ """主管的目标基线实体(旧 run_task 用 _extract_entities 开会话,逐字保留)。"""
538
+ return self._extract_entities(task)
539
+
540
+ # 崩溃/重启恢复记账已上移到 base 生命周期钩子(领域 Agent 此前完全没接线,跑一半崩掉
541
+ # 在恢复扫描里不可见);主管只是"恰好也装着 _recovery",无需再覆盖。
542
+
543
+ async def _dispatch_single(self, task, target, sched, session_id,
544
+ subagents, context, memories, plan_obj) -> dict:
545
+ """单 Agent 分发(从原 _run_task_body 抽出,行为逐字一致,除两点 P1-1 变更):
546
+ ① execution.completed 不再在此手动 publish——改由 base 模板 `_emit_execution`
547
+ 统一发一条**任务级**事件(子 Agent 调用带 _no_emit=True 仍不双发);
548
+ ② 返回体不再带 session_id/agent/thought_summary(由 base 模板统一组装)。"""
549
+ # 创建进程(任务即进程),携带调度决策 → 状态 scheduled
550
+ pm = getattr(self, "_process_manager", None)
551
+ proc = pm.create(task, agent_id=target, schedule=sched.to_dict()) if pm else None
552
+
553
+ self._think(
554
+ content=f"调度 {target} Agent 执行子任务",
555
+ node_type="tool_intent",
556
+ tool_name=f"dispatch_{target}",
557
+ conf=0.92,
558
+ )
559
+ # 进程进入 running
560
+ if proc:
561
+ 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
+ _timeout = float(context.get("parallel_timeout", 120))
569
+ try:
570
+ sub_result = await asyncio.wait_for(
571
+ subagents[target].run_task(task, {**context, "_no_emit": True}),
572
+ timeout=_timeout)
573
+ except asyncio.TimeoutError:
574
+ # 诚实交代:不伪造子 Agent 的产出,明确回报"超时未完成"
575
+ _note = f"子 Agent {target} 执行超时({_timeout:g}s),已中止并如实回报未完成结果"
576
+ if proc:
577
+ pm.mark_failed(proc.process_id, reason="subagent_timeout")
578
+ self._think(content=_note, node_type="decision", conf=0.95)
579
+ sub_result = {"result": _note, "error": "subagent_timeout", "agent": target}
580
+
581
+ # 汇聚子 Agent 结果
582
+ sub_output = sub_result.get("result", "")
583
+ if isinstance(sub_output, dict):
584
+ sub_output = json.dumps(sub_output, ensure_ascii=False)
585
+ blocked = sub_result.get("error") == "harness_blocked"
586
+
587
+ # 进程终态:被拦截/出错→failed,否则→completed
588
+ if proc:
589
+ if blocked:
590
+ pm.mark_failed(proc.process_id, reason="harness_blocked")
591
+ else:
592
+ pm.mark_completed(proc.process_id)
593
+
594
+ self._think(
595
+ content=f"{target} 返回结果(截断={blocked}):{str(sub_output)[:120]}",
596
+ node_type="reflection",
597
+ conf=0.88,
598
+ )
599
+
600
+ # 记忆沉淀:正常由真值管线(事件驱动,经 base._emit_execution)负责;
601
+ # 无总线时(如部分测试场景)主管手动兜底,避免丢记忆。
602
+ bus = getattr(self, "_system_bus", None)
603
+ if bus is None:
604
+ self._store_memory(
605
+ content=f"任务「{task}」由 {target} 处理。要点:{str(sub_output)[:200]}",
606
+ mem_type="decision",
607
+ title=f"调度记录_{datetime.now().strftime('%Y%m%d_%H%M')}",
608
+ tags=["dispatch", f"to:{target}"],
609
+ shared=True,
610
+ )
611
+
612
+ return {
613
+ "result": {
614
+ "dispatched_to": target,
615
+ "sub_result": sub_output,
616
+ "blocked": blocked,
617
+ "summary": f"主管已将任务派给 {subagents[target].AGENT_NAME} 处理并汇聚结果",
618
+ },
619
+ "schedule": sched.to_dict(),
620
+ "plan": ({"steps": [t.name for t in plan_obj.tasks],
621
+ "multi_step": True} if plan_obj else {"multi_step": False}),
622
+ "process_id": proc.process_id if proc else None,
623
+ }
624
+
625
+ async def _dispatch_fallback(self, task, context) -> dict:
626
+ """子 Agent 不可用(未注册):主管自行处理。"""
627
+ plan_response = await self._call_llm_web(task, [{
628
+ "role": "user",
629
+ "content": f"任务:{task}\n请直接给出处理方案。"
630
+ }])
631
+ self._think(content=f"主管自处理:{plan_response[:150]}", node_type="decision", conf=0.85)
632
+ self._store_memory(
633
+ content=f"任务:{task}\n方案:{plan_response[:300]}",
634
+ mem_type="decision",
635
+ title=f"主管处理_{datetime.now().strftime('%Y%m%d_%H%M')}",
636
+ shared=True,
637
+ )
638
+ return {
639
+ "result": {"dispatched_to": "supervisor", "sub_result": plan_response},
640
+ }
641
+
642
+ def _extract_entities(self, text: str) -> list[str]:
643
+ """简单实体提取(规则版,后续可接 NER)。
644
+
645
+ 中英文都支持:英文取首字母大写的词;中文取出现的领域关键词
646
+ (因中文无大小写,原首字母逻辑对中文无效)。
647
+ """
648
+ keywords = []
649
+ # 英文:首字母大写的词
650
+ for word in text.split():
651
+ if len(word) > 3 and word[0].isupper():
652
+ keywords.append(word)
653
+ # 中文:命中路由关键词的领域词(作为实体线索)
654
+ for kws in self.ROUTING_KEYWORDS.values():
655
+ for kw in kws:
656
+ if kw in text and kw not in keywords:
657
+ keywords.append(kw)
658
+ return keywords[:10]
659
+