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,794 @@
1
+ """
2
+ CognitiveAgent — 认知增强 Agent 基类
3
+ 内建 HMR + ThoughtChain + Harness,不需要外部配置
4
+ 每个 Agent 实例拥有:
5
+ - 独立的 HMR Workspace(记忆隔离)
6
+ - 独立的 ThoughtChain(推理轨迹)
7
+ - 独立的 Harness 实例(策略边界)
8
+ - 共享的 Anthropic LLM 接口
9
+ """
10
+
11
+ import threading
12
+ import uuid
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ from ..core.thought_chain import ThoughtChain, ThoughtNode
16
+ from ..harness.harness import Harness, HarnessResult
17
+
18
+ if TYPE_CHECKING:
19
+ from ..core.recovery import RestartRecovery
20
+ from ..core.system_bus import SystemBus
21
+ from ..kernel import ProcessManager
22
+ from ..skills.lifecycle import HookManager
23
+ from ..skills.markdown_skill import MarkdownSkillRegistry
24
+
25
+ _THINK_LOCK = threading.RLock()
26
+
27
+ _GATE_UNSET = object()
28
+
29
+ from .mixins._context import (
30
+ _agent_ctx_get,
31
+ _agent_ctx_set,
32
+ _taint_get,
33
+ _taint_raise,
34
+ _taint_reset,
35
+ _untrusted_set,
36
+ _evidence_reset,
37
+ _evidence_take,
38
+ _suppress_memory,
39
+ _act_stream,
40
+ _reasoning_inject,
41
+ _resume_steps,
42
+ HarnessBlockedError,
43
+ )
44
+ from .mixins.augment import AugmentMixin
45
+ from .mixins.llm import LlmMixin
46
+ from .mixins.memory import MemoryMixin
47
+ from .mixins.skillbox import SkillBoxMixin
48
+ from .mixins.preference import PreferenceMixin
49
+ from .mixins.prompt import PromptMixin, KERNEL_PROMPT
50
+ from .mixins.tool_exec import ToolExecMixin
51
+
52
+ __all__ = [
53
+ "CognitiveAgent", "HarnessBlockedError", "KERNEL_PROMPT",
54
+ "_suppress_memory", "_act_stream", "_taint_get", "_taint_raise", "_taint_reset",
55
+ ]
56
+
57
+
58
+ def _plan_payload(ctx) -> dict | None:
59
+ """从管线 ctx 提取计划负载:{steps, flow, multi_step};没有计划 → None。
60
+
61
+ 领域管线把计划写 `ctx.plan`(pipeline.stage_plan),主管管线写 `ctx.plan_obj`
62
+ (pipeline.supervisor_stages.sv_plan),两种形态都兼容 → 两条链路都能回显计划。
63
+ 纯只读、不抛异常(坏了就当没有计划,不拖垮任务)。
64
+ """
65
+ for attr in ("plan", "plan_obj"):
66
+ p = getattr(ctx, attr, None)
67
+ if p is None:
68
+ continue
69
+ try:
70
+ steps = p.get("steps") if isinstance(p, dict) else getattr(p, "steps", None)
71
+ if not steps:
72
+ continue
73
+ steps = [str(s) for s in steps]
74
+ flow = (p.get("flow", "single") if isinstance(p, dict)
75
+ else getattr(p, "flow", "single")) or "single"
76
+ multi = (p.get("multi_step") if isinstance(p, dict)
77
+ else getattr(p, "multi_step", False))
78
+ return {"steps": steps, "flow": flow,
79
+ "multi_step": bool(multi) or len(steps) > 1}
80
+ except Exception:
81
+ continue
82
+ return None
83
+
84
+
85
+ class CognitiveAgent(AugmentMixin, LlmMixin, MemoryMixin,
86
+ PreferenceMixin, PromptMixin, ToolExecMixin, SkillBoxMixin):
87
+ """
88
+ 所有 Foxclaw Agent 的基类
89
+ 子类只需实现:_role_prompt、allowed_tools、run_task
90
+ (system_prompt 由基类统一 = KERNEL_PROMPT 内核前缀 + 子类 _role_prompt 角色职责)
91
+ """
92
+
93
+ AGENT_ID: str = "base"
94
+ AGENT_NAME: str = "Base Agent"
95
+ ALLOWED_TOOL_CATEGORIES: set[str] = {"read"}
96
+
97
+ md_skills: "MarkdownSkillRegistry"
98
+ hooks: "HookManager"
99
+ _system_bus: "SystemBus"
100
+ _process_manager: "ProcessManager"
101
+ _recovery: "RestartRecovery"
102
+
103
+ def __init__(
104
+ self,
105
+ hmr,
106
+ thought_chain: ThoughtChain,
107
+ client=None,
108
+ data_dir: str = "./foxclaw_data",
109
+ shadow_rounds: int = 20,
110
+ router=None,
111
+ spine=None,
112
+ caps=None,
113
+ ):
114
+ self.hmr = hmr
115
+ self.thought_chain = thought_chain
116
+ self.client = client
117
+ if router is None:
118
+ from ..core.model_router import ModelRouter
119
+ router = ModelRouter()
120
+ self.router = router
121
+ self.data_dir = data_dir
122
+
123
+ self.harness = Harness(
124
+ agent_id=self.AGENT_ID,
125
+ thought_chain=thought_chain,
126
+ policy_path=f"{data_dir}/policy_{self.AGENT_ID}.yaml",
127
+ shadow_rounds=shadow_rounds,
128
+ spine=spine,
129
+ )
130
+ self.harness.block_on_semantic = not getattr(self, "IS_ROUTER", False)
131
+
132
+ self.workspace = hmr.get_workspace(self.AGENT_ID)
133
+
134
+ self._ctx_token = uuid.uuid4().hex
135
+
136
+ self._caps = caps
137
+
138
+ def search_internal(self, query: str, top_k: int = 5):
139
+ """Agent 反向调用内部搜索(HMR记忆 + HKC知识 + Agent态势),只查内部、不联网。
140
+ 这是「串连 Agent」双向连通的 Agent→搜索 方向。经注册中枢惰性解析 search_runtime。"""
141
+ caps = getattr(self, "_caps", None)
142
+ rt = caps.get("search_runtime") if caps is not None else None
143
+ if rt is None:
144
+ return None
145
+ return rt.search(query, agent_id=self.AGENT_ID, internal_only=True)
146
+
147
+ def available_abilities(self, keys: list | None = None) -> list:
148
+ """【HKC 第2档 Step2·能力自省】证据驱动地查 HKC 的 ACE 能力:哪些的知识覆盖已达标
149
+ (可编译 sufficient=True)、哪些还差(sufficient=False + missing_skills)。
150
+
151
+ **只读、informational——不改任何执行**。HKC 未启用/未 vendor → []。coverage 是确定性
152
+ (无 LLM),但对每个能力算一遍图覆盖,故**按需调用**(非热路径)。注:HKC 知识是全局共享
153
+ 的(非按 Agent 分区),故各 Agent 看到的能力覆盖一致——这是"当前知识能支撑哪些能力"的
154
+ 系统级自省,挂在 Agent 上是"我能取用哪些能力"的语义。"""
155
+ caps = getattr(self, "_caps", None)
156
+ hkc = caps.get("hkc") if caps is not None else None
157
+ if hkc is None or not getattr(hkc, "available", False):
158
+ return []
159
+ client = hkc.client
160
+ try:
161
+ all_keys = list(keys) if keys else list(client.list_abilities())
162
+ except Exception:
163
+ return []
164
+ out = []
165
+ for key in all_keys:
166
+ try:
167
+ ab = client.compile_ability(key)
168
+ out.append({"ability_key": ab.ability_key, "display_name": ab.display_name,
169
+ "domain": ab.domain, "sufficient": True, "coverage": ab.coverage})
170
+ except Exception as e:
171
+ if hasattr(e, "missing_skills"):
172
+ out.append({"ability_key": key, "sufficient": False,
173
+ "coverage": getattr(e, "coverage", {}),
174
+ "missing_skills": getattr(e, "missing_skills", [])})
175
+ return out
176
+
177
+ @property
178
+ def _session_id(self):
179
+ return _agent_ctx_get(self._ctx_token).get("session_id")
180
+
181
+ @_session_id.setter
182
+ def _session_id(self, value):
183
+ _agent_ctx_set(self._ctx_token, session_id=value)
184
+
185
+ @property
186
+ def _parent_node_id(self):
187
+ return _agent_ctx_get(self._ctx_token).get("parent_node_id")
188
+
189
+ @_parent_node_id.setter
190
+ def _parent_node_id(self, value):
191
+ _agent_ctx_set(self._ctx_token, parent_node_id=value)
192
+
193
+
194
+ def start_session(self, goal: str, entities: list[str] | None = None) -> str:
195
+ """开始一个新任务会话,注册目标基线"""
196
+ self._session_id = f"sess_{uuid.uuid4().hex[:10]}"
197
+ self._parent_node_id = None
198
+ _taint_reset(self)
199
+ _evidence_reset(self)
200
+
201
+ self.harness.register_goal(
202
+ goal_text=goal,
203
+ allowed_tool_categories=self.ALLOWED_TOOL_CATEGORIES,
204
+ baseline_entities=set(entities or []),
205
+ )
206
+
207
+ goal_node = ThoughtNode.create(
208
+ session_id=self._session_id,
209
+ agent_id=self.AGENT_ID,
210
+ type="goal",
211
+ content=goal,
212
+ conf=1.0,
213
+ entities=entities or [],
214
+ )
215
+ self.thought_chain.add_node(goal_node)
216
+ self.thought_chain.update_harness(goal_node.id, "BASELINE")
217
+ self._parent_node_id = goal_node.id
218
+
219
+ return self._session_id
220
+
221
+
222
+ def _reasoning_result(self, node):
223
+ """经统一 PolicyGate 做推理准入,返回 HarnessResult(与直接 harness.check 逐字一致)。
224
+ gate 经能力中枢惰性解析并缓存;无 gate(standalone/治理场景)则直接 harness.check。"""
225
+ gate: Any = getattr(self, "_gate_cache", _GATE_UNSET)
226
+ if gate is _GATE_UNSET:
227
+ caps = getattr(self, "_caps", None)
228
+ gate = caps.get("policy_gate") if caps is not None else None
229
+ self._gate_cache = gate
230
+ if gate is not None:
231
+ return gate.check_reasoning(self.harness, node).raw
232
+ return self.harness.check(node)
233
+
234
+ def _think(
235
+ self,
236
+ content: str,
237
+ node_type="reasoning",
238
+ conf: float = 0.9,
239
+ mem_refs: list[str] | None = None,
240
+ tool_name: str | None = None,
241
+ entities: list[str] | None = None,
242
+ metadata: dict | None = None,
243
+ ) -> tuple[ThoughtNode, HarnessResult]:
244
+ """
245
+ 记录一个推理步骤,经过 Harness 校验
246
+ 如果 BLOCK,抛出 HarnessBlockedError
247
+ """
248
+ node = ThoughtNode.create(
249
+ session_id=self._session_id or "no_session",
250
+ agent_id=self.AGENT_ID,
251
+ type=node_type,
252
+ content=content,
253
+ conf=conf,
254
+ mem_refs=mem_refs or [],
255
+ parent_ids=[self._parent_node_id] if self._parent_node_id else [],
256
+ tool_name=tool_name,
257
+ entities=entities or [],
258
+ metadata=metadata,
259
+ )
260
+ with _THINK_LOCK:
261
+ self.thought_chain.add_node(node)
262
+
263
+ result = self._reasoning_result(node)
264
+
265
+ if result.is_blocked:
266
+ raise HarnessBlockedError(result, node)
267
+
268
+ if result.is_warned:
269
+ node.conf = max(node.conf - 0.15, 0.1)
270
+
271
+ with _THINK_LOCK:
272
+ self._parent_node_id = node.id
273
+ return node, result
274
+
275
+ @staticmethod
276
+ def _sanitize_history(history, max_turns: int = 12):
277
+ """清洗前端传来的多轮历史 → 合法 messages:只留 {role∈user/assistant, content:非空str},
278
+ 截到最近 max_turns 条。防注入非法角色/超长上下文。"""
279
+ out = []
280
+ for m in (history or [])[-max_turns:]:
281
+ if not isinstance(m, dict):
282
+ continue
283
+ role, content = m.get("role"), m.get("content")
284
+ if role in ("user", "assistant") and isinstance(content, str) and content.strip():
285
+ out.append({"role": role, "content": content})
286
+ return out
287
+
288
+ def _compact_context(self, messages: list, stage: str = "") -> list:
289
+ """LLM 调用前的上下文预算闸:超阈值则摘要/截断,任何失败都原样返回。
290
+
291
+ 历史:`core/compaction.py` 的 Compactor 早已实现(含 tool_calls 原子组切分、
292
+ LLM 摘要→部分摘要→截断三级降级),但**没有任何生产调用点**,实际生效的
293
+ 只有 `_sanitize_history` 的「按条数砍到 12 条」——长对话、多工具轮会
294
+ 直接把 provider 上下文撑爆。这里补上真实接线。
295
+
296
+ 预算由 FOXCLAW_CONTEXT_MAX_TOKENS 控制(默认 6000);置 0 可整体关闭。
297
+ """
298
+ import os as _os
299
+ try:
300
+ limit = int(_os.environ.get("FOXCLAW_CONTEXT_MAX_TOKENS", "6000") or 6000)
301
+ except Exception:
302
+ limit = 6000
303
+ if limit <= 0:
304
+ return messages
305
+ try:
306
+ from ..core.compaction import Compactor
307
+ compactor = Compactor(router=getattr(self, "router", None), max_tokens=limit)
308
+ compacted, res = compactor.maybe_compact(messages)
309
+ if res is not None:
310
+ try:
311
+ self._think(
312
+ content=(f"上下文压缩[{stage or 'llm'}] {res.method}: "
313
+ f"{res.original_count}→{res.compacted_count} 条, "
314
+ f"{res.original_tokens}→{res.compacted_tokens} token"
315
+ + (f"; {res.note}" if res.note else "")),
316
+ node_type="reflection", conf=0.7)
317
+ except Exception:
318
+ pass
319
+ return compacted
320
+ except Exception:
321
+ return messages
322
+
323
+ @staticmethod
324
+ def _attachments_context(attachments) -> str:
325
+ """把聊天附件拼成一段给 LLM 的上下文:文本类贴正文、其它降级为「文件名 + 元数据」提示。
326
+
327
+ 附件正文已在路由层 _sanitize_attachments 清洗(限长/限量);这里再按**不可信数据**隔离:
328
+ 附件可能是别人转发的文件,与用户当面对话不是同一信任级别,而手写中文定界符可被正文
329
+ 伪造。统一走 wrap_untrusted(随机 nonce 边界 + 破界 defang + 注入特征告警)。"""
330
+ if not attachments:
331
+ return ""
332
+ from ..core.injection_guard import wrap_untrusted
333
+ parts = []
334
+ for a in attachments:
335
+ if not isinstance(a, dict):
336
+ continue
337
+ name = a.get("name") or "文件"
338
+ text = (a.get("text") or "").strip()
339
+ if text:
340
+ block = wrap_untrusted(text, source="attachment")
341
+ parts.append(f"【附件「{name}」内容如下】\n{block}\n【附件「{name}」结束】")
342
+ else:
343
+ mime = a.get("mime") or "未知类型"
344
+ size = a.get("size") or 0
345
+ parts.append(f"【附件「{name}」({mime}, {size} 字节)】用户上传了此文件,"
346
+ f"但无法提取为可读文本;若需要其内容,请说明并提示用户改用文本/PDF 等可读格式。")
347
+ return "\n\n" + "\n\n".join(parts) if parts else ""
348
+
349
+ def stream_reply(self, task: str, stop_event=None, images: list | None = None, history: list | None = None,
350
+ attachments: list | None = None, on_thinking=None, on_evidence=None):
351
+ """流式直答路径(方案A):对话/问答/分析,不做多步编排(那走 run_task)。同步生成器,
352
+ 由端点在线程池迭代成 SSE。stop_event 置位(客户端断连)则提前终止,省 token。
353
+ images(可选)= 多模态图片,随问题发给视觉模型(模型无关,非视觉模型 router 层降级)。
354
+ history(可选)= 前端带来的最近几轮对话 → 拼在当前问题前,让多轮连贯。
355
+ attachments(可选)= 聊天附件 [{name,size,mime,url,text}]:text 为提取正文,拼进问题末尾
356
+ 供 LLM 阅读;text 为空的(二进制/图片)降级为「文件名 + 元数据」提示。
357
+ on_thinking(可选)= 推理增量回调。模型的原生 reasoning 与正文内嵌 <think> 块都经此
358
+ 出去,由端点作为独立 SSE `thinking` 事件送前端折叠显示;正文 (yield) 仍走净化。
359
+ on_evidence(可选)= 执行证据回调。工具轮的每步核验结论(verified/unverified/failed)
360
+ + 可核对凭据(来源链接、真实存在的文件)在本轮结束时一次性送出,由端点作为
361
+ SSE `evidence` 事件给前端展示 —— 「做成了」这句话从此可以点开看凭什么。
362
+
363
+ 两条路径(单一决策源之争,见 TOOL_USE_PLAN.md):
364
+ ① FOXCLAW_TOOL_USE=1 且 provider 支持 tools 且无图 → **LLM 自主工具循环**(_reply_with_tools),
365
+ 最终答案 chunk 输出;联网/召回由模型自己决策,不再靠关键词表。
366
+ ② 否则(默认 / 不支持 tools / 带图)→ **现有关键词增强链 + 真流式**(fallback,行为不变)。"""
367
+ from ..core import tools as _tools
368
+ from ..core import injection_guard as _ig
369
+ from ..core.model_router import _chunk_text
370
+ task = str(task or "")
371
+ _evidence_reset(self)
372
+ if attachments:
373
+ ctx = self._attachments_context(attachments)
374
+ if ctx:
375
+ task = task + ctx
376
+ hist = self._sanitize_history(history)
377
+ messages = hist + [{"role": "user", "content": task}]
378
+ parts = []
379
+ used_tools = False
380
+ guarded_out = False
381
+
382
+ def _emit_thinking(delta: str) -> None:
383
+ """推理内容的最后一道关:思考也不许带密钥/系统提示原文(与正文同等级净化)。"""
384
+ if not delta or on_thinking is None:
385
+ return
386
+ try:
387
+ if _ig.scan_output(delta):
388
+ on_thinking("(推理内容已被安全护栏过滤)")
389
+ return
390
+ delta = _ig.redact_secrets(delta)
391
+ except Exception:
392
+ pass
393
+ try:
394
+ on_thinking(delta)
395
+ except Exception:
396
+ pass
397
+
398
+ if _tools.tool_use_enabled() and self.router.supports_tools() and not images:
399
+ try:
400
+ tmsgs = self._ability_augment(task, messages)
401
+ tmsgs = self._skill_augment(task, tmsgs)
402
+ tmsgs = self._pref_augment(tmsgs)
403
+ except Exception:
404
+ tmsgs = messages
405
+ _compact = getattr(self, "_compact_context", None)
406
+ if _compact is not None:
407
+ tmsgs = _compact(tmsgs, "tools")
408
+ try:
409
+ final = self._reply_with_tools(task, tmsgs, stop_event=stop_event)
410
+ except Exception:
411
+ final = None
412
+ if final is not None:
413
+ final, _oiss = _ig.guard_output(final, kernel=KERNEL_PROMPT)
414
+ guarded_out = bool(_oiss)
415
+ used_tools = True
416
+ for chunk in _chunk_text(final):
417
+ if stop_event is not None and stop_event.is_set():
418
+ break
419
+ parts.append(chunk)
420
+ yield chunk
421
+ if not used_tools:
422
+ try:
423
+ messages = self._web_augment(task, messages)
424
+ messages = self._ability_augment(task, messages)
425
+ messages = self._skill_augment(task, messages)
426
+ messages = self._pref_augment(messages)
427
+ except Exception:
428
+ messages = hist + [{"role": "user", "content": task}]
429
+ _compact = getattr(self, "_compact_context", None)
430
+ if _compact is not None:
431
+ messages = _compact(messages, "chat")
432
+ _tail = ""
433
+ _WIN = 256
434
+ for chunk in self.router.stream(self.system_prompt, messages,
435
+ agent_id=self.AGENT_ID, stop_event=stop_event,
436
+ images=images,
437
+ on_thinking=_emit_thinking if on_thinking else None):
438
+ _probe = _tail + chunk
439
+ if _ig.scan_output(_probe):
440
+ guarded_out = True
441
+ break
442
+ _tail = _probe[-_WIN:]
443
+ parts.append(chunk)
444
+ yield chunk
445
+ if guarded_out:
446
+ _notice = "\n\n(⚠ 已触发输出安全护栏,中断后续内容。)"
447
+ parts.append(_notice)
448
+ yield _notice
449
+ reply = "".join(parts)
450
+ stopped = stop_event is not None and stop_event.is_set()
451
+ if on_evidence is not None:
452
+ try:
453
+ _evs = _evidence_take(self)
454
+ if _evs:
455
+ on_evidence(_evs)
456
+ except Exception:
457
+ pass
458
+ if reply and not guarded_out and not used_tools and _ig.scan_output(reply, kernel=KERNEL_PROMPT):
459
+ guarded_out = True
460
+ try:
461
+ self._think(content=("流式直答:" + reply)[:500], node_type="reflection", conf=0.8)
462
+ except Exception:
463
+ pass
464
+ try:
465
+ _caps = getattr(self, "_caps", None)
466
+ _spine = _caps.get("event_spine") if _caps is not None else None
467
+ if _spine is not None and reply:
468
+ _spine.publish("chat.completed", {
469
+ "agent_id": self.AGENT_ID,
470
+ "task": (task or "")[:120],
471
+ "reply_chars": len(reply),
472
+ "guarded": bool(guarded_out),
473
+ }, source=self.AGENT_ID)
474
+ except Exception:
475
+ pass
476
+ _degraded = False
477
+ try:
478
+ _degraded = bool(self.router.take_call_degraded())
479
+ except Exception:
480
+ _degraded = False
481
+ if reply and not stopped and not guarded_out and not _degraded:
482
+ try:
483
+ safe_reply = _ig.redact_secrets(reply)
484
+ self._store_memory(f"对话:{task} → {safe_reply}"[:800], mem_type="conversation")
485
+ except Exception:
486
+ pass
487
+ self._maybe_store_preference(task)
488
+ elif _degraded and reply and not stopped:
489
+ try:
490
+ _spine = _caps.get("event_spine") if _caps is not None else None
491
+ if _spine is not None:
492
+ _spine.publish("chat.degraded", {
493
+ "agent_id": self.AGENT_ID,
494
+ "task": (task or "")[:120],
495
+ "reason": getattr(self.router, "degraded_reason", "")[:200],
496
+ }, source=self.AGENT_ID)
497
+ except Exception:
498
+ pass
499
+
500
+
501
+ _recall_topk: int = 5
502
+
503
+ def _task_entities(self, task: str) -> list:
504
+ """本任务的基线实体(用于 Harness 目标基线)。领域 Agent 覆盖。"""
505
+ return []
506
+
507
+ def _recall_query(self, task: str) -> str:
508
+ """记忆召回查询(领域 Agent 可加前缀,如财务/行程)。默认用原任务。"""
509
+ return task
510
+
511
+ def _on_session_started(self, session_id: str, task: str, context: dict) -> None:
512
+ """会话已开、管线未跑:落 task_started(崩溃后才认得出"上次跑到一半")。"""
513
+ rec = getattr(self, "_recovery", None)
514
+ if rec is None:
515
+ return
516
+ try:
517
+ rec.task_started(task_id=session_id, description=task,
518
+ session_id=session_id,
519
+ channel=(context or {}).get("channel", ""))
520
+ except Exception:
521
+ pass
522
+
523
+ def _on_task_settled(self, session_id: str, status: str) -> None:
524
+ """任务终结(done/failed):落 task_finished,把该 task 从"进行中"摘掉。"""
525
+ rec = getattr(self, "_recovery", None)
526
+ if rec is None:
527
+ return
528
+ try:
529
+ rec.task_finished(session_id, status=status)
530
+ except Exception:
531
+ pass
532
+
533
+ def _record_stage(self, session_id: str, stage: str) -> None:
534
+ """管线 stage 级 checkpoint(fail-safe):断连/崩溃后能回答"跑到哪一步"。"""
535
+ rec = getattr(self, "_recovery", None)
536
+ if rec is None or not session_id:
537
+ return
538
+ try:
539
+ rec.record_step(session_id, {"kind": "stage", "name": stage})
540
+ except Exception:
541
+ pass
542
+
543
+ def _load_resume_cache(self, task_id: str) -> dict:
544
+ """装载上次运行已完成的工具步骤 → {步骤指纹: {"name", "result"}}。
545
+
546
+ 只收「成功 + 未被截断」的步:失败步复用了等于把失败当完成,截断结果复用了等于
547
+ 喂给模型半份事实 —— 宁可不复用(重跑),也不给错的上下文。
548
+ """
549
+ rec = getattr(self, "_recovery", None)
550
+ if rec is None or not task_id:
551
+ return {}
552
+ cache: dict = {}
553
+ try:
554
+ for st in rec.steps_of(task_id):
555
+ if st.get("kind") != "tool" or not st.get("key") or st.get("truncated"):
556
+ continue
557
+ res = st.get("result")
558
+ if isinstance(res, str) and res.strip():
559
+ cache[st["key"]] = {"name": st.get("name", ""), "result": res}
560
+ except Exception:
561
+ return {}
562
+ return cache
563
+
564
+ def _cognitive_pipeline(self):
565
+ """本 Agent 的认知管线(v2 ②)。默认 recall→act 两个 stage;领域 Agent 可覆盖以
566
+ 插入/替换 stage(后续步骤加 verify/reflect/plan/route/perceive)。缓存一份即可
567
+ (stage 无状态,只操作传入的 ctx)。"""
568
+ pipe = getattr(self, "_pipeline_cache", None)
569
+ if pipe is None:
570
+ from ..pipeline import CognitivePipeline, DEFAULT_STAGES
571
+ pipe = self._pipeline_cache = CognitivePipeline(DEFAULT_STAGES)
572
+ return pipe
573
+
574
+ async def run_task(self, task: str, context: dict | None = None, stop_event=None) -> dict:
575
+ """统一任务生命周期:开会话 → [认知管线:recall → act] → 组装返回。
576
+ Harness BLOCK 统一在此兜底为结构化错误。返回
577
+ {"result",...,"session_id","agent","thought_summary", **extra}。
578
+ v2 ②:执行体由 CognitivePipeline 驱动(Step1 抽壳,行为与 P1-1 模板逐字一致)。
579
+
580
+ stop_event(P3,opt-in,与 stream_reply 同一约定):threading/asyncio.Event,置位则让
581
+ 认知管线在下一 stage 边界 / reflect 重试前优雅停止;默认 None → 永不停,行为逐字不变。
582
+ 中断时返回体带 `"interrupted": True`,result 为已完成 stage 的部分产物。
583
+
584
+ 断点续跑(P1,opt-in):`context={"_resume_task_id": <上次 task_id>}` → 装载上次已完成
585
+ 的工具步骤,命中即复用结果不再重复执行;返回体带 `resumed_from` / `resumed_steps`
586
+ 说明续了哪一次、复用了多少步。"""
587
+ return await self._run_task_core(task, context, stop_event=stop_event, on_stage=None)
588
+
589
+ async def _run_task_core(self, task: str, context: dict | None = None,
590
+ stop_event=None, on_stage=None) -> dict:
591
+ """run_task 的执行核心 —— 供同步 `run_task` 与流式 `run_task_stream` **共用**,避免两份
592
+ 生命周期逻辑漂移。`on_stage`(D1):逐 stage 进度回调,透传给管线;None=同步路径不回调,
593
+ 与改前 run_task 行为逐字一致。"""
594
+ from ..pipeline import PipelineContext
595
+ context = context or {}
596
+ session_id = self.start_session(goal=task, entities=self._task_entities(task))
597
+ _untrusted_set(self, bool(context.get("untrusted_source")))
598
+ self._on_session_started(session_id, task, context)
599
+ _hooks = getattr(self, "hooks", None)
600
+ _inject_parts = []
601
+ if _hooks is not None:
602
+ _hr = _hooks.fire("agent_start", "", {"task": task, "agent_id": self.AGENT_ID})
603
+ if _hr.additional_context:
604
+ _inject_parts.append(_hr.additional_context)
605
+ _pref = self._prefs_context()
606
+ if _pref:
607
+ _inject_parts.append(_pref)
608
+ _inject_tok = _reasoning_inject.set("\n\n".join(_inject_parts)) if _inject_parts else None
609
+ _resume_tid = str(context.get("_resume_task_id") or "").strip()
610
+ _resume_cache = self._load_resume_cache(_resume_tid) if _resume_tid else {}
611
+ _resume_tok = _resume_steps.set(_resume_cache) if _resume_cache else None
612
+ ctx = PipelineContext(agent=self, task=task, context=context,
613
+ session_id=session_id, stop_event=stop_event)
614
+
615
+ def _stage_hook(name, _ctx):
616
+ """stage 边界:记一条 checkpoint(跑到哪一步),再转发给调用方的流式回调。"""
617
+ self._record_stage(session_id, name)
618
+ if on_stage is not None:
619
+ on_stage(name, _ctx)
620
+
621
+ try:
622
+ await self._cognitive_pipeline().run(ctx, on_stage=_stage_hook)
623
+ resp = {
624
+ "result": ctx.result,
625
+ "session_id": session_id,
626
+ "agent": self.AGENT_ID,
627
+ "thought_summary": self.thought_chain.get_summary(self.AGENT_ID),
628
+ **(ctx.extra or {}),
629
+ }
630
+ _plan = _plan_payload(ctx)
631
+ if _plan:
632
+ resp["plan"] = _plan
633
+ if _resume_cache:
634
+ resp["resumed_from"] = _resume_tid
635
+ resp["resumed_steps"] = len(_resume_cache)
636
+ if ctx.interrupted:
637
+ resp["interrupted"] = True
638
+ skill_status = (ctx.extra or {}).get("skill_status")
639
+ skill_incomplete = bool(skill_status) and skill_status != "done"
640
+ if skill_incomplete:
641
+ resp["status"] = skill_status
642
+ if skill_status == "needs_confirmation":
643
+ resp["pending_action"] = {
644
+ "skill_id": (ctx.extra or {}).get("skill_id"),
645
+ "task": task,
646
+ "message": (ctx.result or {}).get("message", "")
647
+ if isinstance(ctx.result, dict) else "",
648
+ "hint": "确认后带 context={'_confirmed': <skill_id>, ...} 重跑本任务以执行"
649
+ "(绑定到 skill_id 更安全;_confirmed=True 为概括确认)",
650
+ }
651
+ blocked = skill_incomplete or ctx.interrupted
652
+ self._emit_execution(task, ctx.result, blocked=blocked, context=context)
653
+ self._maybe_store_preference(task)
654
+ if _hooks is not None:
655
+ _hooks.fire("agent_end", "", {"task": task, "agent_id": self.AGENT_ID,
656
+ "result": resp.get("result"),
657
+ "interrupted": bool(ctx.interrupted)})
658
+ self._on_task_settled(session_id, "done")
659
+ return resp
660
+ except HarnessBlockedError as e:
661
+ self._emit_execution(
662
+ task, {"error": "harness_blocked", "reason": e.result.reason},
663
+ blocked=True, context=context)
664
+ if _hooks is not None:
665
+ _hooks.fire("agent_end", "", {"task": task, "agent_id": self.AGENT_ID,
666
+ "error": "harness_blocked"})
667
+ self._on_task_settled(session_id, "done")
668
+ return {"error": "harness_blocked", "reason": e.result.reason,
669
+ "session_id": session_id}
670
+ except Exception:
671
+ self._on_task_settled(session_id, "failed")
672
+ raise
673
+ finally:
674
+ if _inject_tok is not None:
675
+ _reasoning_inject.reset(_inject_tok)
676
+ if _resume_tok is not None:
677
+ _resume_steps.reset(_resume_tok)
678
+
679
+ def run_task_stream(self, task: str, context: dict | None = None, stop_event=None):
680
+ """流式任务执行(D1):**同步 generator**,逐 stage 吐进度 + act 逐 token 出字 + 最终结果。
681
+ 产出事件(dict,逐条给 SSE):
682
+ {"stage": name} 每完成一个认知 stage(perceive→…→reflect)
683
+ {"plan": {...}} 紧跟 plan stage:显式计划 {steps, flow, multi_step}(计划可见)
684
+ {"delta": text} act 的 LLM 逐 token(仅纯 LLM 路径;skill dispatch 路径无 token)
685
+ {"result": {...}} 最终 run_task 返回体(含 interrupted/status/pending_action/plan 等)
686
+ {"error": msg} HarnessBlock 之外的异常(HarnessBlock 已被核心兜底进 result)
687
+
688
+ 与 `stream_reply` 同构(同步 generator,由 SSE 端点在后台线程迭代),复用共享的
689
+ `_run_task_core`,故生命周期/返回体与非流式 `run_task` **完全一致**——只是多了逐 stage /
690
+ 逐 token 的即时反馈。`stop_event`(客户端断连)一处置位、两处生效:停管线 stage(P3)+
691
+ 停 act 的 LLM 生成(省 token)。opt-in:不走此方法的 `run_task` 路径完全不受影响。
692
+
693
+ 实现:worker 线程跑 `asyncio.run(_run_task_core(..., on_stage=…))`(与 /api/task 一样在
694
+ 工作线程内起独立事件循环),stage 回调 + token 回调把事件塞进线程安全 `queue.Queue`,
695
+ 本 generator 主体 drain 该队列并 yield。`_act_stream` contextvar 在 worker 内设置,随
696
+ asyncio Task / to_thread 的上下文复制传播到 `_call_llm` → 触发 act 流式。"""
697
+ import queue as _queue, threading as _threading, asyncio as _asyncio
698
+ bridge: "_queue.Queue" = _queue.Queue()
699
+ _SENTINEL = object()
700
+
701
+ def _on_stage(name, _ctx):
702
+ bridge.put({"stage": name})
703
+ if name == "plan":
704
+ _plan = _plan_payload(_ctx)
705
+ if _plan:
706
+ bridge.put({"plan": _plan})
707
+
708
+ def _on_token(chunk):
709
+ bridge.put({"delta": chunk})
710
+
711
+ def _worker():
712
+ token = _act_stream.set({"on_token": _on_token, "stop_event": stop_event})
713
+ try:
714
+ resp = _asyncio.run(self._run_task_core(
715
+ task, context, stop_event=stop_event, on_stage=_on_stage))
716
+ bridge.put({"result": resp})
717
+ except Exception as e:
718
+ bridge.put({"error": str(e)})
719
+ finally:
720
+ _act_stream.reset(token)
721
+ bridge.put(_SENTINEL)
722
+
723
+ _threading.Thread(target=_worker, daemon=True, name="task-stream").start()
724
+ while True:
725
+ item = bridge.get()
726
+ if item is _SENTINEL:
727
+ break
728
+ yield item
729
+
730
+ def _emit_execution(self, task, result, blocked: bool, context: dict):
731
+ """【P2·全路径触发】把本 Agent 的任务执行发到事件脊柱 → TruthPipeline(共享真值记录)
732
+ + CognitiveState/WS 可观测。修复"共享真值记录只在主管分发路径产生"的不一致:
733
+ 现在直连领域 Agent 的任务也统一走这条真值链。
734
+
735
+ 被主管分发/路由调用时(context['_no_emit']=True)跳过——由主管任务级别的统一
736
+ `_emit_execution` 承载(P1-1 后主管也走本模板),避免同一子任务双发。
737
+ 经能力中枢解析事件脊柱;无中枢(standalone/治理场景)则静默跳过。"""
738
+ if context.get("_no_emit"):
739
+ return
740
+ caps = getattr(self, "_caps", None)
741
+ spine = caps.get("event_spine") if caps is not None else None
742
+ if spine is None:
743
+ return
744
+ try:
745
+ spine.publish("execution.completed", {
746
+ "agent_id": self.AGENT_ID, "task": task,
747
+ "result": result, "blocked": blocked}, source=self.AGENT_ID)
748
+ except Exception:
749
+ pass
750
+
751
+ async def _execute(self, task: str, context: dict, session_id: str,
752
+ memories: list) -> tuple:
753
+ """领域执行步骤(召回之后 → 返回之前)。返回 (result, extra_dict)。
754
+ 领域 Agent 必须实现。SupervisorAgent 例外:它的 pipeline 不含 act stage
755
+ (路由分发由 supervisor_stages 的 dispatch stage 承担),不会走到本钩子。"""
756
+ raise NotImplementedError(f"{self.AGENT_ID} 未实现 _execute()")
757
+
758
+ def situation(self) -> dict:
759
+ """本 Agent 的领域态势:声明自己的 domain + 状态 + 告警。默认无领域态势。
760
+ 领域 Agent 覆盖。编排层据此**通用聚合**,不需懂任何领域内部 API。
761
+ 返回 {} 或 {"domain": str, "status": dict, "alerts": [ {domain,severity,msg} ]}。"""
762
+ return {}
763
+
764
+ def concede(self, req, verdict: str):
765
+ """【协商闭环】本 Agent 作为**发起方**被拒后,能否让步?
766
+
767
+ 返回修订后的 payload(dict)→ 编排层拿去再裁决一轮;返回 None → 让无可让,认输。
768
+ 默认 None:**不让步**,行为与协商前逐字一致 —— 所以给所有未实现本钩子的 Agent
769
+ 接上协商协议是零风险的(只有实现了 concede 的领域 Agent 才会真的多跑回合)。
770
+
771
+ 让步逻辑必须留在**发起方领域**(编排层不懂"改高铁/换酒店"是什么意思)。
772
+ """
773
+ return None
774
+
775
+ def handle_cross_request(self, req) -> None:
776
+ """处理跨 Agent 请求,用**本领域守门人**裁决(就地写 req.status / req.verdict)。
777
+ 默认:无特殊审批,放行。领域 Agent 覆盖。req 为鸭子类型(读 request_type/payload,
778
+ 写 status/verdict),故不与 orchestration 形成 import 依赖。"""
779
+ req.status = "approved"
780
+ req.verdict = "无需特殊审批"
781
+
782
+
783
+ def get_status(self) -> dict:
784
+ return {
785
+ "agent_id": self.AGENT_ID,
786
+ "agent_name": self.AGENT_NAME,
787
+ "session_id": self._session_id,
788
+ "harness": {
789
+ "shadow_mode": self.harness.is_shadow_mode,
790
+ "thresholds": self.harness.thresholds,
791
+ "policy": self.harness.get_policy_summary(),
792
+ },
793
+ "thought_chain": self.thought_chain.get_summary(self.AGENT_ID),
794
+ }