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