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,429 @@
1
+ """
2
+ CognitivePipeline —— 显式分阶段认知管线(v2 ② Step1:抽壳)。
3
+
4
+ 目标形态:
5
+ perceive → recall → plan → route → act → verify → reflect → persist
6
+ 每个 stage 接收并就地更新共享的 PipelineContext(同步或异步均可)。
7
+ 管线本身只负责"按序跑 stage",不含任何领域/编排逻辑——那些在各 stage 内。
8
+
9
+ Step 1 只实现 recall / act 两个 stage(等价于 P1-1 模板的召回+领域执行)。
10
+ 异常(如 HarnessBlockedError)向上抛出,由 base.run_task 统一兜底/emit——保持 P1-1 行为。
11
+ """
12
+ from __future__ import annotations
13
+ import asyncio
14
+ import inspect
15
+ import os
16
+ from dataclasses import dataclass, field
17
+ from typing import Any, Callable
18
+ from .stage_meta import named_stage
19
+
20
+
21
+ @dataclass
22
+ class PipelineContext:
23
+ """一次任务在管线里流动的共享上下文。stage 读它、也写它。
24
+
25
+ 【P4 审核·可变但并发安全,2026-07-17 V3】ctx 是 run_task 内**每任务新建的局部对象**
26
+ (base.run_task:`ctx = PipelineContext(...)`),且 CognitivePipeline.run 里 stage
27
+ **顺序 await、无 stage 并行**——故 ctx **不跨任务共享**。范式原则五"不可变状态流转"
28
+ 反对的是"跨任务/并发共享可变状态"(改一半被读到不一致);ctx 不属于该反模式,可变安全。
29
+ 并发隔离由 test_pipeline.py::test_concurrent_ctx_isolation 锁定(24 并发任务零串)。
30
+ (会话指针 _session_id/_parent_node_id 另经 contextvar 隔离,见 base.py。)"""
31
+ agent: Any
32
+ task: str
33
+ context: dict = field(default_factory=dict)
34
+ session_id: str = ""
35
+ perceived: dict = field(default_factory=dict)
36
+ memories: list = field(default_factory=list)
37
+ plan: dict = field(default_factory=dict)
38
+ route: dict = field(default_factory=dict)
39
+ result: Any = None
40
+ extra: dict = field(default_factory=dict)
41
+ verified: dict = field(default_factory=dict)
42
+ reflect: dict = field(default_factory=dict)
43
+ trace: list = field(default_factory=list)
44
+ stop_event: Any = None
45
+ interrupted: bool = False
46
+ sched: Any = None
47
+ target: str = ""
48
+ plan_obj: Any = None
49
+ step_routes: list = field(default_factory=list)
50
+ multi_agent: bool = False
51
+ dispatch_result: dict = field(default_factory=dict)
52
+
53
+
54
+ def _emit_stage(ctx: "PipelineContext", stage_name: str) -> None:
55
+ """逐 stage emit `agent.cognition.<stage>` 到事件脊柱(Step 5:可观测/可重放)。
56
+ 经 ctx.agent._caps 解析脊柱;无中枢(standalone/假 Agent)则优雅跳过。
57
+ payload 刻意极小(只 agent/session/stage)——这类高频事件给重放/审计用,
58
+ WS 投影会把 agent.cognition.* 过滤掉不广播(避免泛洪,见 api._register_spine_projection)。"""
59
+ agent = ctx.agent
60
+ caps = getattr(agent, "_caps", None)
61
+ spine = caps.get("event_spine") if caps is not None else None
62
+ if spine is None:
63
+ return
64
+ try:
65
+ spine.publish(f"agent.cognition.{stage_name}", {
66
+ "agent_id": getattr(agent, "AGENT_ID", "?"),
67
+ "session_id": ctx.session_id,
68
+ "stage": stage_name,
69
+ }, source=getattr(agent, "AGENT_ID", "pipeline"))
70
+ except Exception:
71
+ pass
72
+
73
+
74
+ def _stop_requested(ctx: "PipelineContext") -> bool:
75
+ """协作式中断检查点(P3)。stop_event 置位 → 请求在下一个 stage 边界 / 重试迭代前停止。
76
+ duck-type 于 `.is_set()`(threading.Event 与 asyncio.Event 皆可,与 stream_reply→model_router
77
+ 的 stop_event **同一约定**)。默认 `ctx.stop_event=None` → 恒 False → 行为逐字不变(opt-in)。"""
78
+ ev = ctx.stop_event
79
+ return ev is not None and ev.is_set()
80
+
81
+
82
+ class CognitivePipeline:
83
+ """有序 stage 列表。run(ctx) 按序执行,支持同步/异步 stage,逐 stage 记轨迹 + emit 事件。"""
84
+
85
+ def __init__(self, stages: list[Callable]):
86
+ self.stages = list(stages)
87
+
88
+ async def run(self, ctx: PipelineContext, on_stage=None) -> PipelineContext:
89
+ """on_stage(name, ctx)(D1,opt-in):每个 stage 完成后回调一次,供流式路径
90
+ (`run_task_stream`)逐 stage 吐进度。默认 None → 不调用,行为逐字不变。
91
+ 回调异常被吞(fail-safe):流式反馈绝不拖垮任务执行。"""
92
+ from foxclaw.core.tracing import span as _span
93
+ for stage in self.stages:
94
+ if _stop_requested(ctx):
95
+ ctx.interrupted = True
96
+ break
97
+ name = getattr(stage, "stage_name", getattr(stage, "__name__", "stage"))
98
+ with _span(f"stage.{name}", agent=getattr(ctx.agent, "AGENT_ID", "")):
99
+ r = stage(ctx)
100
+ if inspect.isawaitable(r):
101
+ await r
102
+ ctx.trace.append(name)
103
+ _emit_stage(ctx, name)
104
+ if on_stage is not None:
105
+ try:
106
+ on_stage(name, ctx)
107
+ except Exception:
108
+ pass
109
+ return ctx
110
+
111
+
112
+ @named_stage("perceive")
113
+ def stage_perceive(ctx: PipelineContext) -> None:
114
+ """感知:把原始输入框定为结构化感知(Step 5)。当前记录轻量输入特征;
115
+ 这里是未来"意图识别/输入解析"的落点。也是逐 stage 事件链的起点。"""
116
+ ctx.perceived = {
117
+ "task_len": len(ctx.task or ""),
118
+ "has_context": bool(ctx.context),
119
+ }
120
+
121
+
122
+ @named_stage("recall")
123
+ async def stage_recall(ctx: PipelineContext) -> None:
124
+ """召回相关记忆(等价 P1-1 模板的 _recall_memory(_recall_query, top_k))。"""
125
+ a = ctx.agent
126
+ ctx.memories = a._recall_memory(a._recall_query(ctx.task), top_k=a._recall_topk)
127
+
128
+
129
+ def _plan_text(plan: dict) -> str:
130
+ steps = plan.get("steps") or []
131
+ body = " → ".join(f"{i + 1}){s}" for i, s in enumerate(steps))
132
+ return f"计划[{plan.get('flow', 'single')}/{len(steps)}步]:{body}"[:400]
133
+
134
+
135
+ def _record_plan_node(ctx: PipelineContext) -> None:
136
+ """把计划落成 thought-chain 的 `plan` 节点(steps/flow 进 metadata),供回显与审计。
137
+
138
+ 纯观测、fail-safe:任何失败(含 Harness 对计划节点的 BLOCK)都**不影响执行**——
139
+ 计划节点不参与准入裁决,也不被 act 读取(见下方「informational」边界说明)。
140
+ """
141
+ agent = getattr(ctx, "agent", None)
142
+ think = getattr(agent, "_think", None)
143
+ if think is None:
144
+ return
145
+ try:
146
+ think(content=_plan_text(ctx.plan), node_type="plan", conf=0.8,
147
+ metadata=dict(ctx.plan))
148
+ except Exception:
149
+ pass
150
+
151
+
152
+ @named_stage("plan")
153
+ def stage_plan(ctx: PipelineContext) -> None:
154
+ """结构规划:用 **P1-2 统一规划引擎** 分析任务结构(是否多步/并行),记录到 ctx.plan。
155
+
156
+ **诚实边界(2026-07-08 审计校正)**:本 stage 纯 **informational**——写入 `ctx.plan` 供
157
+ 观测/审计,但**没有任何 `_execute` 读取 ctx.plan**,故当前不改变执行(act 整段交 LLM)。
158
+ (2026-11-07 起额外落一条 `plan` 推理节点并经 SSE `{"plan":…}` 事件送前端:计划**可见**,
159
+ 但仍不驱动执行——「按 plan 逐步执行」是行为改变,仍需 opt-in + confirm 挂起,不在此做。)
160
+ 它与主管的实际分派**共用同一 P1-2 引擎**(engine 层已统一),但主管的分派在 SupervisorAgent
161
+ 自己的流程里、不经本管线。
162
+ **它不做 skill 规划/dispatch**——skills 目前走独立治理面(`use_skill` / `ahs_pipeline`,
163
+ 经 `/api/ahs/*`),完全绕过本管线。"把 skill 接入管线的 act 阶段"是独立路线项
164
+ (见 docs/internal/V2_ROADMAP_DESIGN.md「后续路线项 · Skill 接入认知管线」),属行为改变的特性,
165
+ 须 opt-in + 建模 confirm 挂起 + fail-safe,不在本 stage 顺手做。"""
166
+ from ..planning import planner as P
167
+ task = (ctx.task or "").strip()
168
+ if any(k in task for k in P.PARALLEL_TRIGGERS):
169
+ steps = P.split_on(task, P.PARALLEL_TRIGGERS + P.LIST_SEPS)
170
+ flow = "parallel" if len(steps) > 1 else "single"
171
+ else:
172
+ steps = P.split_on(task, P.SPLIT_MARKERS)
173
+ flow = "sequential" if len(steps) > 1 else "single"
174
+ if len(steps) <= 1:
175
+ steps, flow = [task], "single"
176
+ ctx.plan = {"steps": steps, "flow": flow, "multi_step": len(steps) > 1}
177
+ _record_plan_node(ctx)
178
+
179
+
180
+ AUTO_ROUTE_THRESHOLD = 0.6
181
+ AUTO_ROUTE_READ_ONLY = True
182
+
183
+
184
+ def _is_read_only(skill) -> bool:
185
+ """只读判定:显式 read_only 优先;否则按 side_effect 推断,且 destructive 一票否决。"""
186
+ ro = getattr(skill, "read_only", None)
187
+ if ro is not None:
188
+ return bool(ro)
189
+ return (getattr(skill, "side_effect", "write") == "read"
190
+ and not getattr(skill, "destructive", False))
191
+
192
+
193
+ def _route_policy(agent, context: dict) -> dict:
194
+ """决定 route 阶段是否选 skill、阈值、是否限只读。
195
+
196
+ **默认开**(仅只读 + 高阈值),让已装好的技能/连接器在正常对话里真的可达。
197
+ 关闭途径:context['_route_skill']=False / Agent 级 ROUTE_SKILL_POLICY=False /
198
+ 环境变量 FOXCLAW_ROUTE_SKILL=0。显式给 dict 可覆盖阈值与只读限制(如
199
+ {'threshold':0.5,'read_only':False})——那是调用方自己承担风险的显式选择。
200
+ """
201
+ p = context.get("_route_skill")
202
+ if p is None:
203
+ p = getattr(agent, "ROUTE_SKILL_POLICY", None)
204
+ if p is None and os.environ.get("FOXCLAW_ROUTE_SKILL", "") == "0":
205
+ p = False
206
+ if p is False:
207
+ return {"enabled": False, "threshold": AUTO_ROUTE_THRESHOLD, "read_only": True}
208
+ if p is None or p is True:
209
+ p = {}
210
+ return {"enabled": True,
211
+ "threshold": float(p.get("threshold", AUTO_ROUTE_THRESHOLD)),
212
+ "read_only": bool(p.get("read_only", AUTO_ROUTE_READ_ONLY))}
213
+
214
+
215
+ @named_stage("route")
216
+ def stage_route(ctx: PipelineContext) -> None:
217
+ """路由决策:记录本任务由谁处理到 ctx.route(target=self 或 skill)。
218
+
219
+ **A·Step1(2026-07-08,铺线,默认关、行为不变)**:开启后(见 `_route_policy`)用 P1-2 router
220
+ 在 Agent 的 `skill_registry` 上选主 skill,命中置信≥阈值 →
221
+ `ctx.route={target:"skill", skill_id, confidence}`;否则/未开启/无 registry/选路异常 →
222
+ `{target:"self"}`(与现状逐字一致,fail-safe 回落)。
223
+ **本步仍不改变执行**:`stage_act` 尚未消费 `ctx.route`(真正按路由 dispatch skill 是 Step2),
224
+ 故即便选中 skill,act 仍走 `_execute`(LLM)。这里只把"路由到 skill 的决策"显式落到 ctx.route,
225
+ 为 Step2 的**受 PolicyGate 治理的 dispatch + confirm 挂起**铺线
226
+ (见 docs/internal/V2_ROADMAP_DESIGN.md「Skill 接入认知管线」)。主管的子 Agent 分派仍在
227
+ SupervisorAgent 流程里、不经本管线;`use_skill`/`ahs_pipeline` 独立面不受影响。"""
228
+ ctx.route = {"target": "self", "reason": "领域 Agent 自处理"}
229
+ policy = _route_policy(ctx.agent, ctx.context)
230
+ if not policy["enabled"]:
231
+ return
232
+ reg = getattr(ctx.agent, "skill_registry", None)
233
+ if reg is None:
234
+ return
235
+ try:
236
+ from ..skills.pipeline import route as skill_route
237
+ r = skill_route(ctx.task, reg)
238
+ except Exception:
239
+ return
240
+ if r.primary and r.confidence >= policy["threshold"]:
241
+ sk = reg.get(r.primary)
242
+ if policy["read_only"] and sk is not None and not _is_read_only(sk):
243
+ return
244
+ if sk is not None and getattr(sk, "external", False):
245
+ try:
246
+ from ..core.web_search import is_personal_query
247
+ if is_personal_query(ctx.task):
248
+ return
249
+ except Exception:
250
+ pass
251
+ ctx.route = {"target": "skill", "skill_id": r.primary,
252
+ "confidence": round(float(r.confidence), 3),
253
+ "reason": f"router 选中 skill「{r.primary}」"
254
+ f"(置信 {r.confidence:.2f}≥{policy['threshold']})"}
255
+
256
+
257
+ def _skill_dispatch_enabled(agent, context: dict) -> bool:
258
+ """act 阶段是否按 ctx.route 真正 dispatch skill。**默认开** —— 与 _route_policy 配套:
259
+ route 只会选中「只读 + 高置信」的技能,选中了却不执行等于白选(工具依旧够不着)。
260
+ 真正的安全边界在 route 的只读闸 + SkillAdapter 的 PolicyGate,而非这个开关。
261
+
262
+ 关闭途径:context['_skill_dispatch']=False / Agent 级 PIPELINE_SKILL_DISPATCH=False /
263
+ 环境变量 FOXCLAW_PIPELINE_SKILL_DISPATCH=0。"""
264
+ v = context.get("_skill_dispatch")
265
+ if v is not None:
266
+ return bool(v)
267
+ v = getattr(agent, "PIPELINE_SKILL_DISPATCH", None)
268
+ if v is not None:
269
+ return bool(v)
270
+ return os.environ.get("FOXCLAW_PIPELINE_SKILL_DISPATCH", "") != "0"
271
+
272
+
273
+ def _confirmed_for(context: dict, sid: str) -> bool:
274
+ """本次 skill 是否已获确认。支持三种形态,**绑定优先**(防止确认漂移):
275
+ · `_confirmed=True` → 概括确认(向后兼容);
276
+ · `_confirmed="<skill_id>"` / `{'skill_id': ...}` → **仅当匹配本次 sid 才确认**。
277
+ 深审加固:避免"用户确认了 A、但重跑时 route 选中 B 却被 _confirmed=True 概括放行",
278
+ 也避免会话级 _confirmed=True 概括自动执行非预期的有副作用 skill——确认应绑定到
279
+ pending_action.skill_id(推荐调用方回传该 id 作 _confirmed)。"""
280
+ c = context.get("_confirmed")
281
+ if not c:
282
+ return False
283
+ if c is True:
284
+ return True
285
+ if isinstance(c, str):
286
+ return c == sid
287
+ if isinstance(c, dict):
288
+ return c.get("skill_id") == sid
289
+ return False
290
+
291
+
292
+ async def _act_via_skill(ctx: "PipelineContext"):
293
+ """按 ctx.route 选中的 skill,经 **SkillAdapter(→PolicyGate 四阶段权限)** dispatch。
294
+ 返回 (result, extra),或 None 表示"无法走 skill,回落 _execute"。
295
+
296
+ **安全铁律(A·Step2 的核心)**:
297
+ · 经 adapter.invoke,权限门在 dispatch **之前**裁决——destructive/send/pay 类未确认 →
298
+ needs_confirmation,**根本不执行、零副作用**(与直呼 skill 面同一治理)。
299
+ · 非成功结果(needs_confirmation/blocked/circuit_open/...)**绝不静默当完成**,而是结构化
300
+ 标记 pending/status 回传;**绝不 fail-open**(呼应 P3)。把它提升为 run_task 顶层
301
+ needs_confirmation 是 Step3。
302
+ · 同步 invoke 放 to_thread,避免阻塞事件循环(与 _call_llm 一致)。"""
303
+ agent = ctx.agent
304
+ reg = getattr(agent, "skill_registry", None)
305
+ adp = getattr(agent, "skill_adapter", None)
306
+ if reg is None or adp is None:
307
+ return None
308
+ sid = (ctx.route or {}).get("skill_id")
309
+ if not isinstance(sid, str) or not sid:
310
+ return None
311
+ skill = reg.get(sid)
312
+ if skill is None:
313
+ return None
314
+ from ..skills.pipeline import _build_args
315
+ from ..skills import set_loop_session, reset_loop_session
316
+ args = _build_args(skill, ctx.task, {})
317
+ confirmed = _confirmed_for(ctx.context, sid)
318
+ token = set_loop_session(getattr(ctx, "session_id", None))
319
+ try:
320
+ res = await asyncio.to_thread(adp.invoke, skill, args, confirmed)
321
+ except Exception as e:
322
+ return ({"skill": sid, "status": "error", "message": f"skill dispatch 异常: {e}",
323
+ "pending": False}, {"via": "skill", "skill_id": sid, "skill_status": "error"})
324
+ finally:
325
+ reset_loop_session(token)
326
+ extra = {"via": "skill", "skill_id": sid, "skill_status": res.status}
327
+ if res.success:
328
+ return ({"skill": sid, "output": res.output, "status": res.status}, extra)
329
+ return ({"skill": sid, "status": res.status, "message": res.message,
330
+ "pending": res.status == "needs_confirmation"}, extra)
331
+
332
+
333
+ @named_stage("act")
334
+ async def stage_act(ctx: PipelineContext) -> None:
335
+ """领域执行步骤。默认:领域 `_execute`(→ LLM),等价 P1-1 模板。
336
+
337
+ **A·Step2(opt-in,首个真改执行的步骤)**:开启 skill dispatch(见 `_skill_dispatch_enabled`)
338
+ 且 `ctx.route.target=="skill"` 时,经 SkillAdapter(PolicyGate 治理)真正 dispatch 选中的 skill;
339
+ 无工具箱/无匹配 spec/未开启 → 回落 `_execute`(行为不变)。安全见 `_act_via_skill`。"""
340
+ if (ctx.route or {}).get("target") == "skill" \
341
+ and _skill_dispatch_enabled(ctx.agent, ctx.context):
342
+ r = await _act_via_skill(ctx)
343
+ if r is not None:
344
+ ctx.result, ctx.extra = r
345
+ return
346
+ ctx.result, ctx.extra = await ctx.agent._execute(
347
+ ctx.task, ctx.context, ctx.session_id, ctx.memories)
348
+
349
+
350
+ @named_stage("verify")
351
+ def stage_verify(ctx: PipelineContext) -> None:
352
+ """把本次执行的 Harness 校验**结果**显式化到 ctx.verified(Step 2)。
353
+
354
+ 关键:Harness 校验本就**内联在 _think**(act 期间每个推理节点都过 harness.check),
355
+ BLOCK 会抛 HarnessBlockedError 中断管线。故本 stage **不新增任何校验/拦截点**——
356
+ 只**读取**本 session 已产生的推理节点,汇总健康度(是否 WARN/最大漂移),使"校验结果"
357
+ 成为一等的管线值,供 Step 3 的 reflect 决策(低置信重试)。行为逐字不变。
358
+
359
+ 走到本 stage 即代表 act 未被 BLOCK(否则已在 act 抛异常中断)。"""
360
+ a = ctx.agent
361
+ try:
362
+ nodes = a.thought_chain.get_chain(ctx.session_id, a.AGENT_ID)
363
+ except Exception:
364
+ nodes = []
365
+ warns = sum(1 for n in nodes if getattr(n, "harness_status", "") == "WARN")
366
+ deltas = [n.harness_delta for n in nodes
367
+ if getattr(n, "harness_delta", None) is not None]
368
+ ctx.verified = {
369
+ "passed": True,
370
+ "nodes": len(nodes),
371
+ "warns": warns,
372
+ "max_delta": round(max(deltas), 3) if deltas else 0.0,
373
+ "last_delta": round(deltas[-1], 3) if deltas else 0.0,
374
+ }
375
+
376
+
377
+ def _reflect_policy(agent, context: dict) -> dict:
378
+ """决定 reflect 是否启用重试及其上限。默认**关**(behavior-preserving):
379
+ 不显式开启时 reflect 只记录"accept"决策、绝不重跑。
380
+ 开启途径:context['_reflect']=True/{...} 或 Agent 级 REFLECT_POLICY={...}。"""
381
+ p = context.get("_reflect")
382
+ if p is None:
383
+ p = getattr(agent, "REFLECT_POLICY", None)
384
+ if not p:
385
+ return {"enabled": False, "max_retries": 0, "drift_threshold": 1.1}
386
+ if p is True:
387
+ p = {}
388
+ return {"enabled": True,
389
+ "max_retries": int(p.get("max_retries", 1)),
390
+ "drift_threshold": float(p.get("drift_threshold", 0.5))}
391
+
392
+
393
+ @named_stage("reflect")
394
+ async def stage_reflect(ctx: PipelineContext) -> None:
395
+ """反思:读 verify 信号(max_delta),决定接受还是**有上限地**重试(Step 3)。
396
+
397
+ 默认接受 → 无行为变化(现有全部任务不受影响)。opt-in 开启后,当漂移超阈值且
398
+ 仍有重试额度时,重跑 act(领域执行)+ verify,直到达标或用尽额度。
399
+ **fail-safe**:重试自身若出错(如重跑又漂移到 BLOCK),回滚到重试前的结果并停止——
400
+ 重试只会改善、绝不让结果变差(呼应 P3 的守门人 fail-safe 教训)。"""
401
+ policy = _reflect_policy(ctx.agent, ctx.context)
402
+ decision, retries = "accept", 0
403
+ if policy["enabled"]:
404
+ while (ctx.verified.get("last_delta", 0.0) > policy["drift_threshold"]
405
+ and retries < policy["max_retries"]):
406
+ if _stop_requested(ctx):
407
+ ctx.interrupted = True
408
+ decision = "interrupted"
409
+ break
410
+ prev_result, prev_extra = ctx.result, ctx.extra
411
+ try:
412
+ retries += 1
413
+ decision = "retry"
414
+ ctx.result, ctx.extra = await ctx.agent._execute(
415
+ ctx.task, ctx.context, ctx.session_id, ctx.memories)
416
+ stage_verify(ctx)
417
+ except Exception:
418
+ ctx.result, ctx.extra = prev_result, prev_extra
419
+ decision = "retry_reverted"
420
+ break
421
+ if decision == "retry" and ctx.verified.get("last_delta", 0.0) <= policy["drift_threshold"]:
422
+ decision = "retry_accepted"
423
+ ctx.reflect = {"decision": decision, "retries": retries,
424
+ "final_last_delta": ctx.verified.get("last_delta", 0.0),
425
+ "final_max_delta": ctx.verified.get("max_delta", 0.0)}
426
+
427
+
428
+ DEFAULT_STAGES = [stage_perceive, stage_recall, stage_plan, stage_route,
429
+ stage_act, stage_verify, stage_reflect]
@@ -0,0 +1,21 @@
1
+ """stage 的命名元数据。
2
+
3
+ pipeline 的两个模块都按「函数 + stage_name 属性」标注阶段,读侧用 getattr 兜底
4
+ (见 cognitive_pipeline 里取 name 的那处)。属性赋值 `fn.stage_name = ...` 在类型
5
+ 检查里不成立 —— 函数对象的类型没有这个属性;setattr 是运行期真做的同一件事,
6
+ 而且让「名字」与「定义」写在同一处,不会漏标。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Callable, TypeVar
11
+
12
+ F = TypeVar("F", bound=Callable[..., object])
13
+
14
+
15
+ def named_stage(name: str) -> Callable[[F], F]:
16
+ """给 stage 可调用挂 stage_name(stage 事件与轨迹标注用这个键)。"""
17
+ def deco(fn: F) -> F:
18
+ vars(fn)["stage_name"] = name
19
+ return fn
20
+
21
+ return deco
@@ -0,0 +1,201 @@
1
+ """
2
+ 主管专用 pipeline stages(P1-1)—— 把 SupervisorAgent 原 `_run_task_body` 的路由分发逻辑
3
+ 拆成可组合、可观测、可中断的 stage,使主管改走 base 统一模板 `_run_task_core`,从而获得
4
+ hooks / 流式(run_task_stream)/ stop_event 中断 / 统一 `_emit_execution` / blocked 顶层提升。
5
+
6
+ 设计约束(与文稿 P1-1 规格 + 现行为逐点对齐):
7
+ · stage 是操作 ctx 的可调用(async 函数 + stage_name 属性),与 cognitive_pipeline 既有约定一致;
8
+ · 只操作 ctx 与 agent 的既有方法(_route/_schedule/_plan_task/_dispatch_*),不新增语义;
9
+ · 分诊 prompt / 自答 prompt / 调度 think 文案全部**逐字搬移**自 agents.py 原实现,不改行为;
10
+ · 主管自答(无关键词命中+模型可用)是 route 的**决策**、dispatch 的**执行**——决策与执行分 stage,
11
+ 让 stop_event 在"决定自答"与"花钱调 LLM"之间有检查点;
12
+ · execution.completed 不再由 dispatch 手动 publish(改由 base 模板 `_emit_execution` 统一发,
13
+ 文稿 P1-1 Step 4);无总线时的手动兜底沉淀(_store_memory)保留(standalone/测试场景);
14
+ · 内部短路标记放 ctx.extra 的 `_meta_answered`/`_self_answer`,aggregate 负责清除,不外泄到返回体。
15
+ """
16
+ import asyncio
17
+ from datetime import datetime
18
+
19
+ from ..core.dispatch import dispatch_failed
20
+ from .cognitive_pipeline import PipelineContext, stage_recall # noqa: F401 (recall 复用默认 stage)
21
+ from .stage_meta import named_stage
22
+
23
+
24
+ @named_stage("meta_check")
25
+ async def sv_meta_check(ctx: PipelineContext) -> None:
26
+ agent = ctx.agent
27
+ if not agent._is_meta(ctx.task):
28
+ return
29
+ agent._think(
30
+ content=f"元问题「{ctx.task}」由主管直接回答(系统介绍,不分派)",
31
+ node_type="decision", conf=0.92,
32
+ )
33
+ subagents = getattr(agent, "_subagents", {})
34
+ sub_names = {
35
+ "finance": "财务 Agent(理财、收支、预算、风控)",
36
+ "office": "办公学习 Agent(学习、知识管理、研究、写作、日程与行程)",
37
+ }
38
+ available = [v for k, v in sub_names.items() if k in subagents]
39
+ team = "\n".join(f"· {x}" for x in available) if available else "(子 Agent 尚未注册)"
40
+ intro_prompt = (
41
+ f"你是 Foxclaw 的主管 Agent(Supervisor),负责统筹协调整个智能体团队。"
42
+ f"用户问:「{ctx.task}」。请以主管身份友好、简洁地直接回答用户这个问题。\n"
43
+ f"背景信息(供你回答时参考):\n"
44
+ f"- 你统领的团队:\n{team}\n"
45
+ f"- 关于联网:Foxclaw 已接入百度 AI 搜索,具备联网查询实时信息的能力"
46
+ f"(当用户问最新版本、新闻、价格等时会自动联网)。所以如果用户问你能不能联网,"
47
+ f"应回答「可以联网」,而不是说没有联网能力。\n"
48
+ f"请针对用户的实际问题回答,控制在 150 字内,口吻像专业总管,不要自称助手,"
49
+ f"不要答非所问地只做自我介绍。"
50
+ )
51
+ reply = await agent._call_llm([{"role": "user", "content": intro_prompt}])
52
+ agent._store_memory(
53
+ content=f"主管自答元问题「{ctx.task}」",
54
+ mem_type="decision",
55
+ title=f"主管介绍_{datetime.now().strftime('%Y%m%d_%H%M')}",
56
+ shared=True,
57
+ )
58
+ ctx.result = {"dispatched_to": "supervisor", "sub_result": reply,
59
+ "self_answered": True}
60
+ ctx.extra["_meta_answered"] = True
61
+
62
+
63
+ @named_stage("schedule")
64
+ async def sv_schedule(ctx: PipelineContext) -> None:
65
+ if ctx.extra.get("_meta_answered"):
66
+ return
67
+ agent = ctx.agent
68
+ subagents = getattr(agent, "_subagents", {})
69
+ ctx.sched = agent._schedule(ctx.task, subagents)
70
+ agent._think(
71
+ content=(f"认知调度:{ctx.sched.reason}"
72
+ + (f" · 抢占({ctx.sched.preemption_reason})" if ctx.sched.preemptible else "")),
73
+ node_type="decision", conf=0.9,
74
+ )
75
+
76
+
77
+ @named_stage("route")
78
+ async def sv_route(ctx: PipelineContext) -> None:
79
+ if ctx.extra.get("_meta_answered"):
80
+ return
81
+ agent = ctx.agent
82
+ target = agent._route(ctx.task, ctx.sched)
83
+ ctx.target = target
84
+ ctx.route = {"target": target, "reason": "主管关键词+调度意图分诊"}
85
+ if not agent._has_keyword_hit(ctx.task) and not agent.router.is_stub:
86
+ agent._think(
87
+ content="无关键词命中 → 主管直接自答(省掉二次分诊派发,正面对话提速)",
88
+ node_type="decision", conf=0.88)
89
+ ctx.extra["_self_answer"] = True
90
+ return
91
+ agent._think(
92
+ content=f"任务分诊:「{ctx.task}」→ 派给 {target} Agent。已有相关记忆 {len(ctx.memories)} 条",
93
+ node_type="decision",
94
+ conf=0.9,
95
+ )
96
+
97
+
98
+ @named_stage("plan")
99
+ async def sv_plan(ctx: PipelineContext) -> None:
100
+ if ctx.extra.get("_meta_answered") or ctx.extra.get("_self_answer"):
101
+ return
102
+ agent = ctx.agent
103
+ plan_obj, step_routes = await asyncio.to_thread(agent._plan_task, ctx.task, ctx.sched)
104
+ if plan_obj is not None:
105
+ routes_desc = " → ".join(f"{name}[{ag}]" for name, ag in step_routes)
106
+ agent._think(
107
+ content=f"planner 判定为多步任务({len(plan_obj.tasks)}步):{routes_desc}",
108
+ node_type="decision", conf=0.85,
109
+ )
110
+ distinct_agents = {ag for _, ag in step_routes}
111
+ ctx.multi_agent = len(distinct_agents) > 1
112
+ else:
113
+ ctx.multi_agent = False
114
+ ctx.plan_obj = plan_obj
115
+ ctx.step_routes = step_routes or []
116
+
117
+
118
+ @named_stage("dispatch")
119
+ async def sv_dispatch(ctx: PipelineContext) -> None:
120
+ agent = ctx.agent
121
+ if ctx.extra.get("_meta_answered"):
122
+ return
123
+
124
+ if ctx.extra.get("_self_answer"):
125
+ _msgs = [{"role": "user", "content": (
126
+ f"你是 Foxclaw 的主管 Agent(Supervisor),统筹整个智能体团队。"
127
+ f"用户说:「{ctx.task}」。请以主管身份**直接、友好、专业**地回答,针对用户的实际"
128
+ f"问题作答,不要只做自我介绍、不要答非所问。若确属需要专门领域深办的任务,可"
129
+ f"简要引导用户去对应能力,但仍先给出有用的直接回应。"
130
+ )}]
131
+ _msgs = await asyncio.to_thread(agent._web_augment, ctx.task, _msgs)
132
+ reply = await agent._call_llm(_msgs)
133
+ agent._store_memory(
134
+ content=f"主管自答正面对话「{ctx.task}」", mem_type="decision",
135
+ title=f"主管自答_{datetime.now().strftime('%Y%m%d_%H%M')}", shared=True)
136
+ ctx.dispatch_result = {
137
+ "result": {"dispatched_to": "supervisor", "sub_result": reply,
138
+ "self_answered": True},
139
+ }
140
+ return
141
+
142
+ subagents = getattr(agent, "_subagents", {})
143
+
144
+ if ctx.multi_agent and ctx.step_routes:
145
+ if getattr(ctx.plan_obj, "flow_type", "sequential") == "parallel":
146
+ agent._think(
147
+ content=f"计划为并行({len(ctx.step_routes)}个无依赖子任务)→ fork-join 并发调度",
148
+ node_type="decision", conf=0.88,
149
+ )
150
+ ctx.dispatch_result = await agent._dispatch_parallel(
151
+ ctx.task, ctx.step_routes, ctx.sched, ctx.session_id,
152
+ subagents, ctx.context, ctx.memories)
153
+ else:
154
+ ctx.dispatch_result = await agent._dispatch_multi(
155
+ ctx.task, ctx.step_routes, ctx.sched, ctx.session_id,
156
+ subagents, ctx.context, ctx.memories)
157
+ return
158
+
159
+ if ctx.target in subagents:
160
+ ctx.dispatch_result = await agent._dispatch_single(
161
+ ctx.task, ctx.target, ctx.sched, ctx.session_id,
162
+ subagents, ctx.context, ctx.memories, ctx.plan_obj)
163
+ return
164
+
165
+ ctx.dispatch_result = await agent._dispatch_fallback(ctx.task, ctx.context)
166
+
167
+
168
+ @named_stage("replan")
169
+ async def sv_replan(ctx: PipelineContext) -> None:
170
+ if ctx.extra.get("_meta_answered") or ctx.extra.get("_self_answer"):
171
+ return
172
+ agent = ctx.agent
173
+ dr = ctx.dispatch_result or {}
174
+ if not dispatch_failed(dr):
175
+ return
176
+ if not agent._replan_enabled():
177
+ return
178
+ subagents = getattr(agent, "_subagents", {})
179
+ max_rounds = int((ctx.context or {}).get("replan_max_rounds", 2))
180
+ ctx.dispatch_result = await agent._replan_and_redo(
181
+ ctx.task, ctx.sched, ctx.session_id, subagents, ctx.context, ctx.memories,
182
+ dr, max_rounds=max_rounds)
183
+
184
+
185
+ @named_stage("aggregate")
186
+ async def sv_aggregate(ctx: PipelineContext) -> None:
187
+ ctx.extra.pop("_meta_answered", None)
188
+ ctx.extra.pop("_self_answer", None)
189
+ dr = ctx.dispatch_result or {}
190
+ if not dr:
191
+ return
192
+ ctx.result = dr.get("result", ctx.result)
193
+ for k in ("schedule", "plan", "process_id"):
194
+ if k in dr:
195
+ ctx.extra[k] = dr[k]
196
+ if isinstance(ctx.result, dict) and ctx.result.get("blocked"):
197
+ ctx.extra["skill_status"] = "blocked"
198
+
199
+
200
+ SUPERVISOR_STAGES = [stage_recall, sv_meta_check, sv_schedule, sv_route,
201
+ sv_plan, sv_dispatch, sv_replan, sv_aggregate]
@@ -0,0 +1,17 @@
1
+ """
2
+ foxclaw.planning —— 统一规划原语(P1-2)。
3
+
4
+ 三处路由(主管→子Agent / AHS→skill / search→数据源)共用的机制单一真源:
5
+ 否定词检测、标点拆分、marker 常量、触发词打分。
6
+ """
7
+ from .planner import (
8
+ NEGATIONS, SPLIT_MARKERS, PARALLEL_TRIGGERS, LIST_SEPS,
9
+ is_negated, split_on, keyword_hits,
10
+ ScoredCandidate, TriggerRoute, trigger_route,
11
+ )
12
+
13
+ __all__ = [
14
+ "NEGATIONS", "SPLIT_MARKERS", "PARALLEL_TRIGGERS", "LIST_SEPS",
15
+ "is_negated", "split_on", "keyword_hits",
16
+ "ScoredCandidate", "TriggerRoute", "trigger_route",
17
+ ]