foxclaw 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,481 @@
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
+
19
+
20
+ @dataclass
21
+ class PipelineContext:
22
+ """一次任务在管线里流动的共享上下文。stage 读它、也写它。
23
+
24
+ 【P4 审核·可变但并发安全,2026-07-17 V3】ctx 是 run_task 内**每任务新建的局部对象**
25
+ (base.run_task:`ctx = PipelineContext(...)`),且 CognitivePipeline.run 里 stage
26
+ **顺序 await、无 stage 并行**——故 ctx **不跨任务共享**。范式原则五"不可变状态流转"
27
+ 反对的是"跨任务/并发共享可变状态"(改一半被读到不一致);ctx 不属于该反模式,可变安全。
28
+ 并发隔离由 test_pipeline.py::test_concurrent_ctx_isolation 锁定(24 并发任务零串)。
29
+ (会话指针 _session_id/_parent_node_id 另经 contextvar 隔离,见 base.py。)"""
30
+ agent: Any # 执行本任务的 Agent(CognitiveAgent)
31
+ task: str
32
+ context: dict = field(default_factory=dict)
33
+ session_id: str = ""
34
+ # 各 stage 逐步填充的产物
35
+ perceived: dict = field(default_factory=dict) # perceive stage 的输入理解
36
+ memories: list = field(default_factory=list)
37
+ plan: dict = field(default_factory=dict) # plan stage 的任务结构(多步?并行?)
38
+ route: dict = field(default_factory=dict) # route stage 的路由决策(target)
39
+ result: Any = None
40
+ extra: dict = field(default_factory=dict)
41
+ verified: dict = field(default_factory=dict) # verify stage 汇总的 Harness 校验结果
42
+ reflect: dict = field(default_factory=dict) # reflect stage 的决策(accept/retry + 次数)
43
+ trace: list = field(default_factory=list) # 逐 stage 轨迹(后续步骤 emit 用)
44
+ # ── 协作式中断(P3,opt-in)──────────────────────────────
45
+ stop_event: Any = None # 置位则在下一 stage 边界 / reflect 重试迭代前优雅停止;默认 None=永不停
46
+ interrupted: bool = False # 被 stop_event 提前中断(供调用方识别"部分完成、未跑完全部 stage")
47
+ # ── 【P1-1】主管路由专用字段(领域 Agent 不用,默认空)──────────
48
+ # 主管 pipeline(supervisor_stages.SUPERVISOR_STAGES)在 schedule/route/plan/dispatch
49
+ # 各 stage 间经这些字段传递路由决策;领域 Agent 的 DEFAULT_STAGES 从不读它们。
50
+ sched: Any = None # schedule stage 的 ScheduleDecision
51
+ target: str = "" # route stage 的分诊目标(子 Agent id)
52
+ plan_obj: Any = None # plan stage 的 planner 拆解结果(多步计划)
53
+ step_routes: list = field(default_factory=list) # [(子任务文本, 目标 Agent)]
54
+ multi_agent: bool = False # 是否跨多 Agent(顺序链式 / 并行 fork-join)
55
+ dispatch_result: dict = field(default_factory=dict) # dispatch stage 的原始分发返回
56
+
57
+
58
+ def _emit_stage(ctx: "PipelineContext", stage_name: str) -> None:
59
+ """逐 stage emit `agent.cognition.<stage>` 到事件脊柱(Step 5:可观测/可重放)。
60
+ 经 ctx.agent._caps 解析脊柱;无中枢(standalone/假 Agent)则优雅跳过。
61
+ payload 刻意极小(只 agent/session/stage)——这类高频事件给重放/审计用,
62
+ WS 投影会把 agent.cognition.* 过滤掉不广播(避免泛洪,见 api._register_spine_projection)。"""
63
+ agent = ctx.agent
64
+ caps = getattr(agent, "_caps", None)
65
+ spine = caps.get("event_spine") if caps is not None else None
66
+ if spine is None:
67
+ return
68
+ try:
69
+ spine.publish(f"agent.cognition.{stage_name}", {
70
+ "agent_id": getattr(agent, "AGENT_ID", "?"),
71
+ "session_id": ctx.session_id,
72
+ "stage": stage_name,
73
+ }, source=getattr(agent, "AGENT_ID", "pipeline"))
74
+ except Exception:
75
+ pass
76
+
77
+
78
+ def _stop_requested(ctx: "PipelineContext") -> bool:
79
+ """协作式中断检查点(P3)。stop_event 置位 → 请求在下一个 stage 边界 / 重试迭代前停止。
80
+ duck-type 于 `.is_set()`(threading.Event 与 asyncio.Event 皆可,与 stream_reply→model_router
81
+ 的 stop_event **同一约定**)。默认 `ctx.stop_event=None` → 恒 False → 行为逐字不变(opt-in)。"""
82
+ ev = ctx.stop_event
83
+ return ev is not None and ev.is_set()
84
+
85
+
86
+ class CognitivePipeline:
87
+ """有序 stage 列表。run(ctx) 按序执行,支持同步/异步 stage,逐 stage 记轨迹 + emit 事件。"""
88
+
89
+ def __init__(self, stages: list[Callable]):
90
+ self.stages = list(stages)
91
+
92
+ async def run(self, ctx: PipelineContext, on_stage=None) -> PipelineContext:
93
+ """on_stage(name, ctx)(D1,opt-in):每个 stage 完成后回调一次,供流式路径
94
+ (`run_task_stream`)逐 stage 吐进度。默认 None → 不调用,行为逐字不变。
95
+ 回调异常被吞(fail-safe):流式反馈绝不拖垮任务执行。"""
96
+ from foxclaw.core.tracing import span as _span
97
+ for stage in self.stages:
98
+ # 协作式中断点(P3):stage **边界**检查 stop_event。opt-in——默认 stop_event=None 恒不触发,
99
+ # 与改前逐字一致。置位时优雅停:标记 interrupted、保留已完成 stage 的 ctx 产物、不跑后续 stage。
100
+ # 粒度=stage 边界:一般无法中止已在执行的单个 stage;但**流式路径(run_task_stream,D1)**下
101
+ # act 的 LLM 生成经 `_act_stream` 的**同一** stop_event 串到 `router.stream`,可 **token 级**
102
+ # 即时停 → 断连时二者协同:停后续 stage(此处)+ 停 act 生成(base._call_llm),省 token。
103
+ if _stop_requested(ctx):
104
+ ctx.interrupted = True
105
+ break
106
+ name = getattr(stage, "stage_name", getattr(stage, "__name__", "stage"))
107
+ # 单一 stage 循环 → 一处埋点覆盖全部 stage(链路里能看到 perceive→…→reflect 各自耗时)
108
+ with _span(f"stage.{name}", agent=getattr(ctx.agent, "AGENT_ID", "")):
109
+ r = stage(ctx)
110
+ if inspect.isawaitable(r):
111
+ await r
112
+ ctx.trace.append(name)
113
+ _emit_stage(ctx, name)
114
+ if on_stage is not None:
115
+ try:
116
+ on_stage(name, ctx) # D1:逐 stage 吐进度给流式路径
117
+ except Exception:
118
+ pass # 流式回调故障不拖垮任务执行(fail-safe)
119
+ return ctx
120
+
121
+
122
+ # ── Stages(Step 1:recall / act)──────────────────────────────
123
+ # stage 是操作 ctx 的可调用;命名 stage_* 便于事件与轨迹标注。
124
+
125
+ def stage_perceive(ctx: PipelineContext) -> None:
126
+ """感知:把原始输入框定为结构化感知(Step 5)。当前记录轻量输入特征;
127
+ 这里是未来"意图识别/输入解析"的落点。也是逐 stage 事件链的起点。"""
128
+ ctx.perceived = {
129
+ "task_len": len(ctx.task or ""),
130
+ "has_context": bool(ctx.context),
131
+ }
132
+ stage_perceive.stage_name = "perceive"
133
+
134
+
135
+ async def stage_recall(ctx: PipelineContext) -> None:
136
+ """召回相关记忆(等价 P1-1 模板的 _recall_memory(_recall_query, top_k))。"""
137
+ a = ctx.agent
138
+ ctx.memories = a._recall_memory(a._recall_query(ctx.task), top_k=a._recall_topk)
139
+ stage_recall.stage_name = "recall"
140
+
141
+
142
+ def _plan_text(plan: dict) -> str:
143
+ steps = plan.get("steps") or []
144
+ body = " → ".join(f"{i + 1}){s}" for i, s in enumerate(steps))
145
+ return f"计划[{plan.get('flow', 'single')}/{len(steps)}步]:{body}"[:400]
146
+
147
+
148
+ def _record_plan_node(ctx: PipelineContext) -> None:
149
+ """把计划落成 thought-chain 的 `plan` 节点(steps/flow 进 metadata),供回显与审计。
150
+
151
+ 纯观测、fail-safe:任何失败(含 Harness 对计划节点的 BLOCK)都**不影响执行**——
152
+ 计划节点不参与准入裁决,也不被 act 读取(见下方「informational」边界说明)。
153
+ """
154
+ agent = getattr(ctx, "agent", None)
155
+ think = getattr(agent, "_think", None) # 轻量替身/测试 Agent 可能没有 _think
156
+ if think is None:
157
+ return
158
+ try:
159
+ think(content=_plan_text(ctx.plan), node_type="plan", conf=0.8,
160
+ metadata=dict(ctx.plan))
161
+ except Exception:
162
+ pass
163
+
164
+
165
+ def stage_plan(ctx: PipelineContext) -> None:
166
+ """结构规划:用 **P1-2 统一规划引擎** 分析任务结构(是否多步/并行),记录到 ctx.plan。
167
+
168
+ **诚实边界(2026-07-08 审计校正)**:本 stage 纯 **informational**——写入 `ctx.plan` 供
169
+ 观测/审计,但**没有任何 `_execute` 读取 ctx.plan**,故当前不改变执行(act 整段交 LLM)。
170
+ (2026-11-07 起额外落一条 `plan` 推理节点并经 SSE `{"plan":…}` 事件送前端:计划**可见**,
171
+ 但仍不驱动执行——「按 plan 逐步执行」是行为改变,仍需 opt-in + confirm 挂起,不在此做。)
172
+ 它与主管的实际分派**共用同一 P1-2 引擎**(engine 层已统一),但主管的分派在 SupervisorAgent
173
+ 自己的流程里、不经本管线。
174
+ **它不做 skill 规划/dispatch**——skills 目前走独立治理面(`use_skill` / `ahs_pipeline`,
175
+ 经 `/api/ahs/*`),完全绕过本管线。"把 skill 接入管线的 act 阶段"是独立路线项
176
+ (见 docs/internal/V2_ROADMAP_DESIGN.md「后续路线项 · Skill 接入认知管线」),属行为改变的特性,
177
+ 须 opt-in + 建模 confirm 挂起 + fail-safe,不在本 stage 顺手做。"""
178
+ from ..planning import planner as P
179
+ task = (ctx.task or "").strip()
180
+ if any(k in task for k in P.PARALLEL_TRIGGERS):
181
+ steps = P.split_on(task, P.PARALLEL_TRIGGERS + P.LIST_SEPS)
182
+ flow = "parallel" if len(steps) > 1 else "single"
183
+ else:
184
+ steps = P.split_on(task, P.SPLIT_MARKERS)
185
+ flow = "sequential" if len(steps) > 1 else "single"
186
+ if len(steps) <= 1:
187
+ steps, flow = [task], "single"
188
+ ctx.plan = {"steps": steps, "flow": flow, "multi_step": len(steps) > 1}
189
+ _record_plan_node(ctx)
190
+ stage_plan.stage_name = "plan"
191
+ # ⚠【预留·informational,不驱动执行】(2026-07-17 V3·B1 审核标注)
192
+ # stage_plan 写 ctx.plan 供观测/审计,但**没有任何 _execute 读取它**,故不改变执行
193
+ # (act 整段交 LLM/skill)。让 act 真按 plan 多步执行是**行为改变**,须 opt-in + 每步
194
+ # confirm 挂起 + fail-safe(见 V2_ROADMAP「Skill 接入认知管线」),不在此顺手做。
195
+ # (B1 自审:曾在此加 `stage_plan.informational=True` 属性,但无任何消费者=又一个死标记,
196
+ # 已删——注释足以说明,不造没人读的假结构化标记。)
197
+
198
+
199
+ # 默认自动派发的门槛(A·Step5,2026-07-16:从"默认关"改为"默认开但严格受限")。
200
+ # 背景:此前 route/dispatch 双双默认关 → 工具中心/MCP 连接器/技能台**在正常对话里根本够不着**
201
+ # (「帮我算 3+5」助手只会用 LLM 硬答,计算器技能不会被调用)。整套工具生态形同虚设。
202
+ # 放量策略:只自动派发**只读**技能、且要求**高置信**;有副作用的(write/send/pay/destructive)
203
+ # 一律不自动选中,仍须显式 use_skill —— 自动执行绝不越过"会改变世界"的那条线。
204
+ # 阈值取 0.6 —— 这是**量出来的**,不是拍的。router 实测分值只有三档:
205
+ # 0.600 强匹配(触发词直接命中,如「帮我计算…」→calculator)
206
+ # 0.480 弱匹配(如「查下去公司的路线」→amap_route、「发邮件给张三」→gmail_send)
207
+ # 0.000 无匹配(闲聊/写代码)
208
+ # 故 (0.48, 0.60] 区间内的任何值都等价于"只要强匹配";再高(如 0.75)则**一个都选不中**、
209
+ # 功能形同虚设。0.6 已是最保守的可用值 —— "比 0.6 更保守"是伪命题。
210
+ AUTO_ROUTE_THRESHOLD = 0.6
211
+ AUTO_ROUTE_READ_ONLY = True # 只自动派发只读技能(有副作用的绝不自动动手)
212
+
213
+
214
+ def _is_read_only(skill) -> bool:
215
+ """只读判定:显式 read_only 优先;否则按 side_effect 推断,且 destructive 一票否决。"""
216
+ ro = getattr(skill, "read_only", None)
217
+ if ro is not None:
218
+ return bool(ro)
219
+ return (getattr(skill, "side_effect", "write") == "read"
220
+ and not getattr(skill, "destructive", False))
221
+
222
+
223
+ def _route_policy(agent, context: dict) -> dict:
224
+ """决定 route 阶段是否选 skill、阈值、是否限只读。
225
+
226
+ **默认开**(仅只读 + 高阈值),让已装好的技能/连接器在正常对话里真的可达。
227
+ 关闭途径:context['_route_skill']=False / Agent 级 ROUTE_SKILL_POLICY=False /
228
+ 环境变量 FOXCLAW_ROUTE_SKILL=0。显式给 dict 可覆盖阈值与只读限制(如
229
+ {'threshold':0.5,'read_only':False})——那是调用方自己承担风险的显式选择。
230
+ """
231
+ p = context.get("_route_skill")
232
+ if p is None:
233
+ p = getattr(agent, "ROUTE_SKILL_POLICY", None)
234
+ if p is None and os.environ.get("FOXCLAW_ROUTE_SKILL", "") == "0":
235
+ p = False
236
+ if p is False:
237
+ return {"enabled": False, "threshold": AUTO_ROUTE_THRESHOLD, "read_only": True}
238
+ if p is None or p is True:
239
+ p = {}
240
+ return {"enabled": True,
241
+ "threshold": float(p.get("threshold", AUTO_ROUTE_THRESHOLD)),
242
+ "read_only": bool(p.get("read_only", AUTO_ROUTE_READ_ONLY))}
243
+
244
+
245
+ def stage_route(ctx: PipelineContext) -> None:
246
+ """路由决策:记录本任务由谁处理到 ctx.route(target=self 或 skill)。
247
+
248
+ **A·Step1(2026-07-08,铺线,默认关、行为不变)**:开启后(见 `_route_policy`)用 P1-2 router
249
+ 在 Agent 的 `skill_registry` 上选主 skill,命中置信≥阈值 →
250
+ `ctx.route={target:"skill", skill_id, confidence}`;否则/未开启/无 registry/选路异常 →
251
+ `{target:"self"}`(与现状逐字一致,fail-safe 回落)。
252
+ **本步仍不改变执行**:`stage_act` 尚未消费 `ctx.route`(真正按路由 dispatch skill 是 Step2),
253
+ 故即便选中 skill,act 仍走 `_execute`(LLM)。这里只把"路由到 skill 的决策"显式落到 ctx.route,
254
+ 为 Step2 的**受 PolicyGate 治理的 dispatch + confirm 挂起**铺线
255
+ (见 docs/internal/V2_ROADMAP_DESIGN.md「Skill 接入认知管线」)。主管的子 Agent 分派仍在
256
+ SupervisorAgent 流程里、不经本管线;`use_skill`/`ahs_pipeline` 独立面不受影响。"""
257
+ ctx.route = {"target": "self", "reason": "领域 Agent 自处理"}
258
+ policy = _route_policy(ctx.agent, ctx.context)
259
+ if not policy["enabled"]:
260
+ return
261
+ reg = getattr(ctx.agent, "skill_registry", None)
262
+ if reg is None:
263
+ return # 无工具箱(finance/admin/office)→ 保持 self
264
+ try:
265
+ from ..skills.pipeline import route as skill_route
266
+ r = skill_route(ctx.task, reg)
267
+ except Exception:
268
+ return # 选路失败不影响主流程 → 保持 self(fail-safe)
269
+ if r.primary and r.confidence >= policy["threshold"]:
270
+ sk = reg.get(r.primary)
271
+ # 只读闸:有副作用的技能(write/send/pay/destructive)**绝不自动选中**——
272
+ # 自动执行不得越过"会改变世界"的线;要用仍可显式 use_skill(那时用户是知情的)。
273
+ if policy["read_only"] and sk is not None and not _is_read_only(sk):
274
+ return # 保持 self → LLM 作答,不自动动手
275
+ # 外泄闸:问的是**用户自己的数据**(账本/行程/学习)→ 绝不自动发给**外部**技能。
276
+ # 与 needs_search 共用同一判定(单一真源)。否则「查一下最新的本月支出」会把私人
277
+ # 财务问题自动送去外部搜索——那正是 needs_search 已挡住、却从技能这条旁路漏回来的 bug。
278
+ if sk is not None and getattr(sk, "external", False):
279
+ try:
280
+ from ..core.web_search import is_personal_query
281
+ if is_personal_query(ctx.task):
282
+ return # 私人问题不外发 → 保持 self
283
+ except Exception:
284
+ pass # 判定不可用 → 不因此放行外部技能之外的行为(保持原逻辑)
285
+ ctx.route = {"target": "skill", "skill_id": r.primary,
286
+ "confidence": round(float(r.confidence), 3),
287
+ "reason": f"router 选中 skill「{r.primary}」"
288
+ f"(置信 {r.confidence:.2f}≥{policy['threshold']})"}
289
+ stage_route.stage_name = "route"
290
+
291
+
292
+ def _skill_dispatch_enabled(agent, context: dict) -> bool:
293
+ """act 阶段是否按 ctx.route 真正 dispatch skill。**默认开** —— 与 _route_policy 配套:
294
+ route 只会选中「只读 + 高置信」的技能,选中了却不执行等于白选(工具依旧够不着)。
295
+ 真正的安全边界在 route 的只读闸 + SkillAdapter 的 PolicyGate,而非这个开关。
296
+
297
+ 关闭途径:context['_skill_dispatch']=False / Agent 级 PIPELINE_SKILL_DISPATCH=False /
298
+ 环境变量 FOXCLAW_PIPELINE_SKILL_DISPATCH=0。"""
299
+ v = context.get("_skill_dispatch")
300
+ if v is not None:
301
+ return bool(v)
302
+ v = getattr(agent, "PIPELINE_SKILL_DISPATCH", None)
303
+ if v is not None:
304
+ return bool(v)
305
+ return os.environ.get("FOXCLAW_PIPELINE_SKILL_DISPATCH", "") != "0"
306
+
307
+
308
+ def _confirmed_for(context: dict, sid: str) -> bool:
309
+ """本次 skill 是否已获确认。支持三种形态,**绑定优先**(防止确认漂移):
310
+ · `_confirmed=True` → 概括确认(向后兼容);
311
+ · `_confirmed="<skill_id>"` / `{'skill_id': ...}` → **仅当匹配本次 sid 才确认**。
312
+ 深审加固:避免"用户确认了 A、但重跑时 route 选中 B 却被 _confirmed=True 概括放行",
313
+ 也避免会话级 _confirmed=True 概括自动执行非预期的有副作用 skill——确认应绑定到
314
+ pending_action.skill_id(推荐调用方回传该 id 作 _confirmed)。"""
315
+ c = context.get("_confirmed")
316
+ if not c:
317
+ return False
318
+ if c is True:
319
+ return True
320
+ if isinstance(c, str):
321
+ return c == sid
322
+ if isinstance(c, dict):
323
+ return c.get("skill_id") == sid
324
+ return False
325
+
326
+
327
+ async def _act_via_skill(ctx: "PipelineContext"):
328
+ """按 ctx.route 选中的 skill,经 **SkillAdapter(→PolicyGate 四阶段权限)** dispatch。
329
+ 返回 (result, extra),或 None 表示"无法走 skill,回落 _execute"。
330
+
331
+ **安全铁律(A·Step2 的核心)**:
332
+ · 经 adapter.invoke,权限门在 dispatch **之前**裁决——destructive/send/pay 类未确认 →
333
+ needs_confirmation,**根本不执行、零副作用**(与直呼 skill 面同一治理)。
334
+ · 非成功结果(needs_confirmation/blocked/circuit_open/...)**绝不静默当完成**,而是结构化
335
+ 标记 pending/status 回传;**绝不 fail-open**(呼应 P3)。把它提升为 run_task 顶层
336
+ needs_confirmation 是 Step3。
337
+ · 同步 invoke 放 to_thread,避免阻塞事件循环(与 _call_llm 一致)。"""
338
+ agent = ctx.agent
339
+ reg = getattr(agent, "skill_registry", None)
340
+ adp = getattr(agent, "skill_adapter", None)
341
+ if reg is None or adp is None:
342
+ return None # 无工具箱(finance/admin/office)→ 回落 _execute
343
+ sid = (ctx.route or {}).get("skill_id")
344
+ skill = reg.get(sid) if sid else None
345
+ if skill is None:
346
+ return None
347
+ from ..skills.pipeline import _build_args
348
+ from ..skills import set_loop_session, reset_loop_session
349
+ args = _build_args(skill, ctx.task, {})
350
+ confirmed = _confirmed_for(ctx.context, sid)
351
+ token = set_loop_session(getattr(ctx, "session_id", None)) # 循环检测按会话分槽
352
+ try:
353
+ res = await asyncio.to_thread(adp.invoke, skill, args, confirmed)
354
+ except Exception as e:
355
+ # invoke 理应自兜底(catches _dispatch 异常);万一权限评估等意外抛错 → **fail-closed**:
356
+ # 结构化 error 回传,**绝不回落 LLM**(路由到发邮件却让 LLM 代答=幻觉/危险),也不崩整个任务。
357
+ return ({"skill": sid, "status": "error", "message": f"skill dispatch 异常: {e}",
358
+ "pending": False}, {"via": "skill", "skill_id": sid, "skill_status": "error"})
359
+ finally:
360
+ reset_loop_session(token)
361
+ extra = {"via": "skill", "skill_id": sid, "skill_status": res.status}
362
+ if res.success:
363
+ return ({"skill": sid, "output": res.output, "status": res.status}, extra)
364
+ # 非成功:无副作用已发生(权限门+confirmed=False保证)。结构化回传,绝不当完成、绝不回落 LLM
365
+ # (路由到发邮件却让 LLM 代答=危险/幻觉);needs_confirmation 待用户确认后重跑(带 _confirmed)。
366
+ return ({"skill": sid, "status": res.status, "message": res.message,
367
+ "pending": res.status == "needs_confirmation"}, extra)
368
+
369
+
370
+ async def stage_act(ctx: PipelineContext) -> None:
371
+ """领域执行步骤。默认:领域 `_execute`(→ LLM),等价 P1-1 模板。
372
+
373
+ **A·Step2(opt-in,首个真改执行的步骤)**:开启 skill dispatch(见 `_skill_dispatch_enabled`)
374
+ 且 `ctx.route.target=="skill"` 时,经 SkillAdapter(PolicyGate 治理)真正 dispatch 选中的 skill;
375
+ 无工具箱/无匹配 spec/未开启 → 回落 `_execute`(行为不变)。安全见 `_act_via_skill`。"""
376
+ if (ctx.route or {}).get("target") == "skill" \
377
+ and _skill_dispatch_enabled(ctx.agent, ctx.context):
378
+ r = await _act_via_skill(ctx)
379
+ if r is not None:
380
+ ctx.result, ctx.extra = r
381
+ return
382
+ ctx.result, ctx.extra = await ctx.agent._execute(
383
+ ctx.task, ctx.context, ctx.session_id, ctx.memories)
384
+ stage_act.stage_name = "act"
385
+
386
+
387
+ def stage_verify(ctx: PipelineContext) -> None:
388
+ """把本次执行的 Harness 校验**结果**显式化到 ctx.verified(Step 2)。
389
+
390
+ 关键:Harness 校验本就**内联在 _think**(act 期间每个推理节点都过 harness.check),
391
+ BLOCK 会抛 HarnessBlockedError 中断管线。故本 stage **不新增任何校验/拦截点**——
392
+ 只**读取**本 session 已产生的推理节点,汇总健康度(是否 WARN/最大漂移),使"校验结果"
393
+ 成为一等的管线值,供 Step 3 的 reflect 决策(低置信重试)。行为逐字不变。
394
+
395
+ 走到本 stage 即代表 act 未被 BLOCK(否则已在 act 抛异常中断)。"""
396
+ a = ctx.agent
397
+ try:
398
+ nodes = a.thought_chain.get_chain(ctx.session_id, a.AGENT_ID)
399
+ except Exception:
400
+ nodes = []
401
+ warns = sum(1 for n in nodes if getattr(n, "harness_status", "") == "WARN")
402
+ deltas = [n.harness_delta for n in nodes # get_chain 按 timestamp 升序
403
+ if getattr(n, "harness_delta", None) is not None]
404
+ ctx.verified = {
405
+ "passed": True, # 未被 BLOCK
406
+ "nodes": len(nodes),
407
+ "warns": warns,
408
+ "max_delta": round(max(deltas), 3) if deltas else 0.0, # 全 session 峰值(信息量)
409
+ # last_delta = 最近一次推理的漂移(本次尝试的最终状态)。reflect 用它作重试信号——
410
+ # 深审修复:max_delta 是 max over 累积历史(单调不减),重试追加节点后仍是旧峰值,
411
+ # 会让重试永远到上限、无法因"重试改善"提前退出;last_delta 反映当前尝试,可正确早退。
412
+ "last_delta": round(deltas[-1], 3) if deltas else 0.0,
413
+ }
414
+ stage_verify.stage_name = "verify"
415
+
416
+
417
+ def _reflect_policy(agent, context: dict) -> dict:
418
+ """决定 reflect 是否启用重试及其上限。默认**关**(behavior-preserving):
419
+ 不显式开启时 reflect 只记录"accept"决策、绝不重跑。
420
+ 开启途径:context['_reflect']=True/{...} 或 Agent 级 REFLECT_POLICY={...}。"""
421
+ p = context.get("_reflect")
422
+ if p is None:
423
+ p = getattr(agent, "REFLECT_POLICY", None)
424
+ if not p:
425
+ return {"enabled": False, "max_retries": 0, "drift_threshold": 1.1}
426
+ if p is True:
427
+ p = {}
428
+ return {"enabled": True,
429
+ "max_retries": int(p.get("max_retries", 1)), # 硬上限,防成环
430
+ "drift_threshold": float(p.get("drift_threshold", 0.5))}
431
+
432
+
433
+ async def stage_reflect(ctx: PipelineContext) -> None:
434
+ """反思:读 verify 信号(max_delta),决定接受还是**有上限地**重试(Step 3)。
435
+
436
+ 默认接受 → 无行为变化(现有全部任务不受影响)。opt-in 开启后,当漂移超阈值且
437
+ 仍有重试额度时,重跑 act(领域执行)+ verify,直到达标或用尽额度。
438
+ **fail-safe**:重试自身若出错(如重跑又漂移到 BLOCK),回滚到重试前的结果并停止——
439
+ 重试只会改善、绝不让结果变差(呼应 P3 的守门人 fail-safe 教训)。"""
440
+ policy = _reflect_policy(ctx.agent, ctx.context)
441
+ decision, retries = "accept", 0
442
+ if policy["enabled"]:
443
+ # 用 last_delta(当前尝试的最终漂移)作重试信号,而非 max_delta(累积峰值,单调不减)——
444
+ # 否则重试改善了也无法早退。重试若把漂移降到阈值内,下一轮 last_delta 达标即停止。
445
+ while (ctx.verified.get("last_delta", 0.0) > policy["drift_threshold"]
446
+ and retries < policy["max_retries"]):
447
+ # 协作式中断点(P3):每次重试都是一次 `_execute`(LLM 调用)——这里是本管线里最接近
448
+ # 范式"agentic loop 每轮检查中断"的地方。置位则停在下一轮 LLM 调用**之前**(省 token),
449
+ # 保留已有最好结果。opt-in:stop_event=None 恒不触发,重试行为逐字不变。
450
+ if _stop_requested(ctx):
451
+ # 中断:decision 统一记 "interrupted",已发生的重试次数由 retries 字段承载。
452
+ # (B2 审核 F1:此前分 interrupted/retry_interrupted 两值,其中 retries==0 一支仅在
453
+ # 外部线程于"pre-reflect 边界检查"与"首个 while 检查"之间置位才可达——单线程测试无法
454
+ # 稳定覆盖。合并为单值消除该未覆盖分支,信息量不减: retries 已表达重试了几次。)
455
+ ctx.interrupted = True
456
+ decision = "interrupted"
457
+ break
458
+ prev_result, prev_extra = ctx.result, ctx.extra # 重试前的最好结果
459
+ try:
460
+ retries += 1
461
+ decision = "retry"
462
+ ctx.result, ctx.extra = await ctx.agent._execute(
463
+ ctx.task, ctx.context, ctx.session_id, ctx.memories)
464
+ stage_verify(ctx) # 重新汇总校验(刷新 last_delta)
465
+ except Exception:
466
+ # 重试出错(含重跑被 BLOCK)→ 回滚到上一次好结果,停止重试(不让重试变差)
467
+ ctx.result, ctx.extra = prev_result, prev_extra
468
+ decision = "retry_reverted"
469
+ break
470
+ # 重试后 last_delta 已达标 → 视为改善接受
471
+ if decision == "retry" and ctx.verified.get("last_delta", 0.0) <= policy["drift_threshold"]:
472
+ decision = "retry_accepted"
473
+ ctx.reflect = {"decision": decision, "retries": retries,
474
+ "final_last_delta": ctx.verified.get("last_delta", 0.0),
475
+ "final_max_delta": ctx.verified.get("max_delta", 0.0)}
476
+ stage_reflect.stage_name = "reflect"
477
+
478
+
479
+ # 默认管线的 stage 序列(完整八阶段之七;persist=run_task 里的 emit + 领域 _store_memory)。
480
+ DEFAULT_STAGES = [stage_perceive, stage_recall, stage_plan, stage_route,
481
+ stage_act, stage_verify, stage_reflect]