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,281 @@
1
+ """任务执行 / 流式直答 / 流式任务 路由与多模态护栏。
2
+
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
4
+ 晚绑定读取;路由用 `@app` 装饰器在 import 时注册。流式限流器 `_STREAM_LIMITER` 为
5
+ 模块级单例,`__init__.py` 会 re-export(测试复位其桶容量)。
6
+ """
7
+
8
+ import json
9
+ import asyncio
10
+ import threading as _threading
11
+
12
+ from fastapi import Request
13
+ from fastapi.responses import StreamingResponse
14
+ from pydantic import BaseModel
15
+
16
+ import foxclaw.api as _api
17
+ from .. import app
18
+ from ...mcp_server import _RateLimiter as _RL
19
+ from ...core import settings
20
+
21
+
22
+ # ── 请求模型 ───────────────────────────────────────────────
23
+
24
+ class TaskRequest(BaseModel):
25
+ task: str
26
+ agent_id: str = "supervisor"
27
+ context: dict = {}
28
+ images: list = [] # 多模态对话:[{media_type,data(base64)}];仅 /chat/stream 消费
29
+ history: list = [] # 多轮上下文:[{role:'user'/'assistant',content}];前端带最近几轮,
30
+ # 后端拼进 LLM messages,让对话连贯(否则每轮无状态、记不住上一句)
31
+ session_id: str = "" # 会话归属:非空则本轮问答落盘到该会话(供"会话历史"回看)
32
+ attachments: list = [] # 聊天附件:[{name,size,mime,url,text}];text 为提取正文(可为空),
33
+ # 仅 /chat/stream 消费;随问题注入 LLM + 落盘会话供回看
34
+
35
+
36
+ class GoalRequest(BaseModel):
37
+ agent_id: str
38
+ goal: str
39
+ allowed_tools: list[str] = []
40
+ entities: list[str] = []
41
+
42
+
43
+ # ── 任务执行 ───────────────────────────────────────────────
44
+
45
+ @app.post("/api/task")
46
+ async def run_task(req: TaskRequest):
47
+ """执行任务,返回结果 + ThoughtChain 摘要。
48
+
49
+ 把认知处理 offload 到工作线程:每请求的 CPU 密集同步工作(harness 语义嵌入等)不再
50
+ 阻塞事件循环,并发请求的嵌入计算可真正并行(torch/ST CPU 前向释放 GIL),吞吐随核数扩展。
51
+ · contextvars 随 to_thread 复制到工作线程 → 每请求会话状态仍隔离(见 base.py);
52
+ · 内层 LLM 的 to_thread 用各自 asyncio.run 的独立事件循环 executor,不与外层竞争
53
+ 同一线程池,故无嵌套线程池死锁;
54
+ · harness 累积态已在 harness.py 用 RLock 保护(嵌入在锁外,故并行不受锁影响)。"""
55
+ def _work():
56
+ # 经微内核调度器准入(默认无上限=直通;设了 FOXCLAW_MAX_CONCURRENCY 才背压)+ 生命周期事件
57
+ return _api._os_instance.scheduler.run(
58
+ lambda: asyncio.run(_api._os_instance.run(req.task, req.agent_id,
59
+ context=req.context)),
60
+ agent_id=req.agent_id, task=req.task)
61
+ return await asyncio.to_thread(_work)
62
+
63
+
64
+ # 流式限流 + 并发上限(防刷爆/资源耗尽;env 可配)
65
+ _STREAM_LIMITER = _RL(settings.get_int("FOXCLAW_STREAM_RATE")) # 次/分
66
+ _STREAM_SEM = _threading.Semaphore(settings.get_int("FOXCLAW_STREAM_CONCURRENCY"))
67
+
68
+
69
+ _MAX_IMAGES = 6
70
+ _MAX_IMG_B64 = 10 * 1024 * 1024 # 单图 base64 上限 ~10MB(≈7.5MB 原图)
71
+ _MAX_IMG_TOTAL = 20 * 1024 * 1024 # 所有图 base64 累计上限(防 6×10MB=60MB 聚合 payload)
72
+ _ALLOWED_IMG_MT = {"image/png", "image/jpeg", "image/webp", "image/gif"}
73
+
74
+ _MAX_ATTACHMENTS = 6 # 单次问答最多附件数
75
+ _ATTACH_TEXT_MAX = 20000 # 单附件提取正文上限
76
+ _ATTACH_TEXT_TOTAL = 60000 # 全部附件正文累计上限(≈1.5 万 token,防撑爆上下文)
77
+
78
+
79
+ def _sanitize_images(images):
80
+ """校验/裁剪前端传来的多模态图片:限数量、限单图大小、限累计总量、白名单类型。
81
+ 防超大 payload/OOM(前端已缩放,这是对直连/恶意客户端的后端自保)。"""
82
+ out = []
83
+ total = 0
84
+ if not isinstance(images, list):
85
+ return out
86
+ for im in images[:_MAX_IMAGES]:
87
+ if not isinstance(im, dict):
88
+ continue
89
+ mt = str(im.get("media_type", "")).lower()
90
+ d = im.get("data", "")
91
+ if mt not in _ALLOWED_IMG_MT:
92
+ continue
93
+ if not isinstance(d, str) or not d or len(d) > _MAX_IMG_B64:
94
+ continue
95
+ if total + len(d) > _MAX_IMG_TOTAL: # 累计超总上限 → 停(丢弃其余)
96
+ break
97
+ total += len(d)
98
+ out.append({"media_type": mt, "data": d})
99
+ return out
100
+
101
+
102
+ def _sanitize_attachments(attachments):
103
+ """校验/裁剪前端传来的聊天附件:限数量、字段清洗、正文长度上限 + 累计上限。
104
+ 附件是前端经 /api/upload 上传后带回的引用({name,size,mime,url,text});这里只做
105
+ 直连/恶意客户端的自保,不信任 text 之外任何字段用于文件系统操作(下载按 url 里的 id 白名单)。"""
106
+ out = []
107
+ total = 0
108
+ if not isinstance(attachments, list):
109
+ return out
110
+ for a in attachments[:_MAX_ATTACHMENTS]:
111
+ if not isinstance(a, dict):
112
+ continue
113
+ name = str(a.get("name") or "")[:200]
114
+ text = str(a.get("text") or "")[:_ATTACH_TEXT_MAX]
115
+ if not name and not text:
116
+ continue
117
+ if total + len(text) > _ATTACH_TEXT_TOTAL: # 累计正文超总上限 → 截断(丢弃后续正文)
118
+ text = text[:_ATTACH_TEXT_TOTAL - total]
119
+ total += len(text)
120
+ try: # size 可能是脏值("big"/None),护栏绝不因此抛异常
121
+ size = int(a.get("size")) if a.get("size") else 0
122
+ except (TypeError, ValueError):
123
+ size = 0
124
+ out.append({
125
+ "name": name,
126
+ "size": size,
127
+ "mime": str(a.get("mime") or "")[:100],
128
+ "url": str(a.get("url") or "")[:500],
129
+ "text": text,
130
+ })
131
+ return out
132
+
133
+
134
+ @app.post("/api/chat/stream")
135
+ async def chat_stream(req: TaskRequest, request: Request):
136
+ """流式直答(方案A):SSE 逐字返回 Agent LLM 输出。用于对话/问答;走完整认知管线的任务执行
137
+ 用 /api/task(阻塞)或 /api/task/stream(流式,D1);此端点不跑 recall/verify/reflect 等 stage。
138
+ 背压:令牌桶限流 + 并发信号量;客户端断连 → stop_event 终止 LLM 流(省 token)。
139
+ images:多模态对话,随问题发给视觉模型(非视觉模型 router 层报错→降级)。"""
140
+ if not _STREAM_LIMITER.allow():
141
+ async def _rl():
142
+ yield "data: " + json.dumps({"error": "rate limited"}) + "\n\n"
143
+ return StreamingResponse(_rl(), media_type="text/event-stream")
144
+
145
+ agent = _api._os_instance._agents.get(req.agent_id) or _api._os_instance.supervisor
146
+ imgs = _sanitize_images(req.images)
147
+ atts = _sanitize_attachments(req.attachments)
148
+
149
+ async def async_sse():
150
+ if not _STREAM_SEM.acquire(blocking=False): # 超并发上限 → 拒(不排队)
151
+ yield "data: " + json.dumps({"error": "busy, try again later"}) + "\n\n"
152
+ return
153
+ q: asyncio.Queue = asyncio.Queue()
154
+ stop = _threading.Event()
155
+ loop = asyncio.get_running_loop()
156
+
157
+ def _producer():
158
+ _parts = []
159
+
160
+ def _on_thinking(delta: str) -> None:
161
+ # 推理走独立事件,不混进正文:前端折叠展示,正文仍只收 {"delta"}。
162
+ loop.call_soon_threadsafe(q.put_nowait, {"thinking": delta})
163
+
164
+ def _on_evidence(evs: list) -> None:
165
+ # 执行证据(核验结论 + 来源链接/文件)整块送:前端挂在答案下方供核对,
166
+ # 不进正文(正文是给用户读的答案,证据是给人查的凭据)。
167
+ loop.call_soon_threadsafe(q.put_nowait, {"evidence": evs})
168
+
169
+ try:
170
+ for chunk in agent.stream_reply(req.task, stop_event=stop, images=imgs,
171
+ history=req.history, attachments=atts,
172
+ on_thinking=_on_thinking,
173
+ on_evidence=_on_evidence):
174
+ _parts.append(chunk)
175
+ loop.call_soon_threadsafe(q.put_nowait, {"delta": chunk})
176
+ loop.call_soon_threadsafe(q.put_nowait, {"done": True})
177
+ except Exception as e:
178
+ loop.call_soon_threadsafe(q.put_nowait, {"error": str(e)})
179
+ finally:
180
+ # 落盘会话历史(问 + 答),供"会话/所有会话"回看。断连中止也保留已生成部分。
181
+ if req.session_id:
182
+ try:
183
+ _api._os_instance.sessions.append(req.session_id, "user", req.task, attachments=atts)
184
+ _reply = "".join(_parts)
185
+ if _reply:
186
+ _api._os_instance.sessions.append(req.session_id, "assistant", _reply)
187
+ except Exception:
188
+ pass
189
+ loop.call_soon_threadsafe(q.put_nowait, None) # 结束哨兵
190
+
191
+ _threading.Thread(target=_producer, daemon=True, name="chat-stream").start()
192
+ try:
193
+ while True:
194
+ if await request.is_disconnected():
195
+ stop.set(); break # 断连 → 终止 LLM 流
196
+ try:
197
+ item = await asyncio.wait_for(q.get(), timeout=0.5)
198
+ except asyncio.TimeoutError:
199
+ continue
200
+ if item is None:
201
+ break
202
+ if "thinking" in item:
203
+ # 命名事件(SSE 规范):event: thinking + data。老客户端只认 data: 行也不会被
204
+ # 误导成正文(delta 字段不存在);新前端按事件名折叠展示。
205
+ yield ("event: thinking\ndata: "
206
+ + json.dumps({"thinking": item["thinking"]}, ensure_ascii=False)
207
+ + "\n\n")
208
+ continue
209
+ if "evidence" in item:
210
+ # 与 thinking 同理走命名事件:老客户端拿不到这个事件名也不会把凭据
211
+ # 当成正文拼进回答。
212
+ yield ("event: evidence\ndata: "
213
+ + json.dumps({"evidence": item["evidence"]}, ensure_ascii=False)
214
+ + "\n\n")
215
+ continue
216
+ yield "data: " + json.dumps(item, ensure_ascii=False) + "\n\n"
217
+ finally:
218
+ stop.set()
219
+ _STREAM_SEM.release()
220
+
221
+ return StreamingResponse(async_sse(), media_type="text/event-stream",
222
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
223
+
224
+
225
+ @app.post("/api/task/stream")
226
+ async def task_stream(req: TaskRequest, request: Request):
227
+ """流式任务执行(D1):SSE 逐 stage 进度 + act 逐 token + 最终结果。用于任务/分析等走**完整
228
+ 认知管线**的场景(对话直答走 /api/chat/stream)。事件:`{stage}`/`{plan}`/`{delta}`/`{result}`/`{error}`。
229
+ 【P3-3】主管(P1-1 pipeline 化后)同样可走本端点:stage 事件为主管管线的
230
+ recall/meta_check/schedule/route/plan/dispatch/aggregate;分发场景下子 Agent 的 act
231
+ token 随 contextvar 传播一并流出。
232
+ 背压与断连处理与 chat_stream 一致:断连 → stop_event 同时停管线 stage(P3)+ 停 act LLM 生成。"""
233
+ if not _STREAM_LIMITER.allow():
234
+ async def _rl():
235
+ yield "data: " + json.dumps({"error": "rate limited"}) + "\n\n"
236
+ return StreamingResponse(_rl(), media_type="text/event-stream")
237
+
238
+ agent = _api._os_instance._agents.get(req.agent_id)
239
+ # 【P3-3】P1-1 落地后主管走统一 pipeline(supervisor_stages),支持 run_task_stream,
240
+ # 不再回落 assistant(助手已下放为技能)。未知 agent_id 仍回落主管兼容旧客户端
241
+ # (旧前端可能还传 "assistant"/"admin"),但打 warning 不静默。
242
+ if agent is None:
243
+ print(f"[Foxclaw API] ⚠ /api/task/stream 收到未知 agent_id={req.agent_id},回落 supervisor")
244
+ agent = _api._os_instance.supervisor
245
+
246
+ async def async_sse():
247
+ if not _STREAM_SEM.acquire(blocking=False): # 超并发上限 → 拒(不排队)
248
+ yield "data: " + json.dumps({"error": "busy, try again later"}) + "\n\n"
249
+ return
250
+ q: asyncio.Queue = asyncio.Queue()
251
+ stop = _threading.Event()
252
+ loop = asyncio.get_running_loop()
253
+
254
+ def _producer():
255
+ try:
256
+ for evt in agent.run_task_stream(req.task, context=req.context,
257
+ stop_event=stop):
258
+ loop.call_soon_threadsafe(q.put_nowait, evt)
259
+ except Exception as e:
260
+ loop.call_soon_threadsafe(q.put_nowait, {"error": str(e)})
261
+ finally:
262
+ loop.call_soon_threadsafe(q.put_nowait, None) # 结束哨兵
263
+
264
+ _threading.Thread(target=_producer, daemon=True, name="task-stream-sse").start()
265
+ try:
266
+ while True:
267
+ if await request.is_disconnected():
268
+ stop.set(); break # 断连 → 停管线 + 停 act LLM
269
+ try:
270
+ item = await asyncio.wait_for(q.get(), timeout=0.5)
271
+ except asyncio.TimeoutError:
272
+ continue
273
+ if item is None:
274
+ break
275
+ yield "data: " + json.dumps(item, ensure_ascii=False) + "\n\n"
276
+ finally:
277
+ stop.set()
278
+ _STREAM_SEM.release()
279
+
280
+ return StreamingResponse(async_sse(), media_type="text/event-stream",
281
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
@@ -0,0 +1,68 @@
1
+ """WebSocket:实时 Harness 事件流(连接管理 + 端点)。
2
+
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。manager 由 api/__init__.py re-export
4
+ 给后台广播任务 _drain_event_queue 使用。
5
+ """
6
+
7
+ import os
8
+
9
+ from fastapi import WebSocket, WebSocketDisconnect
10
+
11
+ from ..middleware import _constant_time_eq, _loopback_bypass_on, _is_loopback_client
12
+
13
+
14
+ class ConnectionManager:
15
+ def __init__(self):
16
+ self.connections: list[WebSocket] = []
17
+
18
+ async def connect(self, ws: WebSocket):
19
+ await ws.accept()
20
+ self.connections.append(ws)
21
+
22
+ def disconnect(self, ws: WebSocket):
23
+ self.connections.remove(ws)
24
+
25
+ async def broadcast(self, data: dict):
26
+ for ws in self.connections[:]:
27
+ try:
28
+ await ws.send_json(data)
29
+ except Exception:
30
+ self.connections.remove(ws)
31
+
32
+
33
+ manager = ConnectionManager()
34
+
35
+
36
+ async def harness_websocket(websocket: WebSocket):
37
+ """
38
+ 实时 Harness 事件流。
39
+ 事件转发器在启动时已注册到线程安全队列,这里只负责接受连接并保活。
40
+
41
+ 鉴权:HTTP 中间件管不到 websocket scope,故 WS 的鉴权必须在端点内做。
42
+ 浏览器无法给 WS 握手设请求头,因此接受 ?key= 查询参数(也兼容能设头的非浏览器
43
+ 客户端的 X-API-Key 头)。未设 FOXCLAW_API_KEY 时开放;设了时本机(127.0.0.1/::1)
44
+ 客户端也免 ?key=(与 HTTP 中间件同口径的 loopback bypass)。
45
+ """
46
+ api_key = os.environ.get("FOXCLAW_API_KEY", "").strip()
47
+ if api_key:
48
+ # 【P3-loopback】与 HTTP 中间件同口径:本机(127.0.0.1 / ::1)WS 客户端免 ?key=
49
+ # 设 FOXCLAW_LOOPBACK_BYPASS=0 可关。详见 foxclaw.api.middleware._loopback_bypass_on
50
+ ws_host = websocket.client.host if websocket.client else ""
51
+ if not (_loopback_bypass_on() and _is_loopback_client(ws_host)):
52
+ provided = (websocket.query_params.get("key")
53
+ or websocket.headers.get("x-api-key"))
54
+ # 【P3-1】常量时间比较(与 HTTP 中间件同一辅助函数);None 由 or "" 兜底——
55
+ # api_key 非空时 not eq("", api_key) 必为 True,与原 None != api_key 行为一致
56
+ if not _constant_time_eq(provided or "", api_key):
57
+ await websocket.close(code=1008) # policy violation
58
+ return
59
+ await manager.connect(websocket)
60
+ try:
61
+ while True:
62
+ await websocket.receive_text() # 保持连接
63
+ except WebSocketDisconnect:
64
+ manager.disconnect(websocket)
65
+
66
+
67
+ def register(app):
68
+ app.add_api_websocket_route("/ws/harness", harness_websocket)
@@ -0,0 +1,55 @@
1
+ """事件脊柱辅助:WS payload 净化 + HKC 后台预热。
2
+
3
+ 从 `foxclaw/api/__init__.py` 搬移出来的纯辅助函数。`_register_spine_projection`
4
+ 因 `_WS_MUTED` 受测试源码级断言约束(需留在主文件 `__init__.py` 源码内)而未搬移。
5
+ """
6
+ import json
7
+ import threading
8
+ import time as _t
9
+
10
+ import foxclaw.api as _api
11
+
12
+
13
+ def _ws_safe_payload(payload, _maxlen: int = 4000):
14
+ """把任意事件 payload 收敛成 **JSON 安全且有界** 的形式,供 WS 广播。
15
+ 统一投影会把系统事件(execution.completed 等含原始 result/任意对象)也推给 WS,
16
+ 必须防两类真实故障:
17
+ · 非 JSON 可序列化对象 → ws.send_json 抛异常 → broadcast 静默丢弃该连接;
18
+ · 超大 payload(整段 LLM 回复 / 大 result,且随真值链被广播多次)→ 向所有 WS 泛洪。
19
+ 脊柱本身仍保留全保真事件(供 history / 审计 / 其它投影),净化只发生在 WS 出口。"""
20
+ try:
21
+ raw = json.dumps(payload, default=str, ensure_ascii=False)
22
+ except Exception:
23
+ return {"_repr": str(payload)[:_maxlen]}
24
+ if len(raw) > _maxlen:
25
+ return {"_truncated": True, "_preview": raw[:_maxlen]}
26
+ return json.loads(raw)
27
+
28
+
29
+ def _prewarm_hkc(osi):
30
+ """后台预热 HKC:装配容器 + 建搜索索引(bm25+faiss over 全量 KU,顺带加载 HKC
31
+ 多语言 embedding 模型)+ 载入 learning。把"首次知识查询"的一次性冷成本(~几十秒到
32
+ 1 分钟)挪到开机后台完成,之后所有知识查询都不再冷。守护线程、失败静默,绝不影响主应用。
33
+ 索引重建与查询共用 HybridSearch 锁 → 预热期间若有查询会短暂等待,预热完即快。"""
34
+
35
+ def _run():
36
+ t0 = _t.time()
37
+ try:
38
+ svc = getattr(osi, "hkc", None) # 访问即触发 HKC 惰性装配
39
+ if not svc or not getattr(svc, "available", False):
40
+ return
41
+ # 搜索索引已在 assemble() 内构建(向量索引持久化加载优先,避免每次启动全量
42
+ # embedding 把 CPU 打满、启动卡几分钟),这里不再重复 build_index。
43
+ try:
44
+ lr = getattr(osi, "learning_runtime", None)
45
+ if lr is not None:
46
+ lr._ensure_hkc_loaded()
47
+ except Exception:
48
+ pass
49
+ print(f"[Foxclaw API] ✓ HKC 预热完成({_t.time()-t0:.1f}s): 首次知识查询不再冷装配")
50
+ except Exception as e:
51
+ print(f"[Foxclaw API] · HKC 预热未启用(可选): {e}")
52
+
53
+ # 晚绑定:把守护线程挂到主模块 `__init__.py` 的 `_prewarm_thread`(lifespan 关闭前需 join)。
54
+ _api._prewarm_thread = threading.Thread(target=_run, name="hkc-prewarm", daemon=True)
55
+ _api._prewarm_thread.start()
@@ -0,0 +1,13 @@
1
+ """
2
+ foxclaw.browser —— HIR-Browser 融入 Foxclaw 的浏览器子系统("AI 用的浏览器")。
3
+
4
+ 只做一件事:感知网页 → 压缩成 LLM 可读的语义结构 → 把动作翻译成真实浏览器
5
+ 操作 → 校验执行结果。不做规划、不做决策——那是 Agent/Foxclaw 控制面的职责。
6
+
7
+ 对外入口是 BrowserService(async 安全、可选依赖优雅降级、专用工作线程)。
8
+ `import foxclaw.browser` 不会拉入 playwright;只有真正开会话时才需要。
9
+ """
10
+
11
+ from .service import BrowserService
12
+
13
+ __all__ = ["BrowserService"]
@@ -0,0 +1 @@
1
+ """动作层:WebAction 数据形状 + 执行器(翻译成真实操作)+ 校验器(快照 diff 判定生效)。"""
@@ -0,0 +1,54 @@
1
+ """
2
+ ActionExecutor —— 把 WebAction 翻译成真实浏览器操作。
3
+
4
+ 只做翻译和调用,不做判断(判断动作是否该执行、是否合理,
5
+ 不属于这一层,也不属于 HIR-Browser 整体,那是 Foxclaw/HAR 的职责)。
6
+ """
7
+
8
+ from .action_types import WebAction
9
+
10
+
11
+ class ActionExecutor:
12
+
13
+ def __init__(self, driver, element_indexer):
14
+ self.driver = driver
15
+ self.indexer = element_indexer
16
+
17
+ def run(self, action: WebAction):
18
+ handler = getattr(self, f"_do_{action.type}", None)
19
+ if handler is None:
20
+ raise NotImplementedError(f"未实现的动作类型: {action.type}")
21
+ return handler(action)
22
+
23
+ def _do_navigate(self, action: WebAction):
24
+ # URL 准入:此前直接把模型给的字符串交给 driver → file:// 读本机文件、
25
+ # javascript: 执行脚本、内网/云元数据 SSRF。ValueError 由 service.act 转成错误返回。
26
+ from .url_policy import validate_navigate_url
27
+ target = validate_navigate_url(action.target)
28
+ self.driver.navigate(target)
29
+ return {"navigated_to": target}
30
+
31
+ def _do_click(self, action: WebAction):
32
+ handle = self.indexer.resolve(action.target)
33
+ self.driver.click(handle)
34
+ return {"clicked": action.target}
35
+
36
+ def _do_type(self, action: WebAction):
37
+ handle = self.indexer.resolve(action.target)
38
+ self.driver.type_text(handle, action.value or "")
39
+ return {"typed": action.value, "into": action.target}
40
+
41
+ def _do_scroll(self, action: WebAction):
42
+ direction = action.value or "down"
43
+ self.driver.scroll(direction)
44
+ return {"scrolled": direction}
45
+
46
+ def _do_extract(self, action: WebAction):
47
+ # extract 本身不改变页面状态,只是触发一次重新语义化,
48
+ # 具体的语义化交给上层 WebAdapter 统一处理,这里返回空占位即可
49
+ return {"extract_requested": True}
50
+
51
+ def _do_wait(self, action: WebAction):
52
+ seconds = float(action.value or 1.0)
53
+ self.driver.wait(seconds)
54
+ return {"waited": seconds}
@@ -0,0 +1,45 @@
1
+ """
2
+ Action Graph 节点在 Web 场景下的具体形态。
3
+
4
+ 这里只定义"数据形状",不含任何执行逻辑,也不含任何"该不该执行"的判断——
5
+ 那是 HAR / Foxclaw 的职责。
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Optional, Any
10
+
11
+
12
+ # HIR-Browser 支持的动作类型,故意保持极简,不做扩展性设计
13
+ SUPPORTED_ACTIONS = ("navigate", "click", "type", "scroll", "extract", "wait")
14
+
15
+
16
+ @dataclass
17
+ class WebAction:
18
+ """
19
+ 一个标准化的 Web 动作节点。
20
+
21
+ type: navigate | click | type | scroll | extract | wait
22
+ target: 元素编号(由 SemanticReducer 分配的 id),navigate 时为 URL
23
+ value: type 动作的输入文本;scroll 的方向/距离
24
+ meta: 透传字段,供上层(HAR/Foxclaw)附加调试信息,Web Adapter 不解析它
25
+ """
26
+
27
+ type: str
28
+ target: Optional[str] = None
29
+ value: Optional[Any] = None
30
+ meta: dict = field(default_factory=dict)
31
+
32
+ def __post_init__(self):
33
+ if self.type not in SUPPORTED_ACTIONS:
34
+ raise ValueError(
35
+ f"不支持的动作类型: {self.type},仅支持 {SUPPORTED_ACTIONS}"
36
+ )
37
+
38
+ @classmethod
39
+ def from_dict(cls, d: dict) -> "WebAction":
40
+ return cls(
41
+ type=d["type"],
42
+ target=d.get("target"),
43
+ value=d.get("value"),
44
+ meta=d.get("meta", {}),
45
+ )
@@ -0,0 +1,68 @@
1
+ """
2
+ ActionVerifier —— HIR-Browser 的第二个核心壁垒。
3
+
4
+ 问题:LLM 不知道自己的操作有没有生效。selector 没报错 ≠ 操作成功。
5
+ 方案:执行前后各拍一次语义快照,做 diff,用 diff 结果去校验
6
+ "这次动作是否符合预期",而不是简单地"没抛异常就算成功"。
7
+
8
+ 这里的判断规则刻意保持简单、可解释,不引入模型推理——
9
+ "是否成功"这个判断本身应该是确定性的、可复现的。
10
+ """
11
+
12
+ from ..perception.snapshot import Snapshot
13
+ from .action_types import WebAction
14
+
15
+
16
+ class ActionVerifier:
17
+
18
+ def verify(self, before: Snapshot, after: Snapshot, action: WebAction) -> dict:
19
+ diff = self._diff(before, after)
20
+
21
+ success = self._matches_expectation(diff, action)
22
+
23
+ return {
24
+ "success": success,
25
+ "diff": diff,
26
+ "url_changed": before.url != after.url,
27
+ }
28
+
29
+ def _diff(self, before: Snapshot, after: Snapshot) -> dict:
30
+ before_ids = before.element_ids()
31
+ after_ids = after.element_ids()
32
+
33
+ return {
34
+ "url_before": before.url,
35
+ "url_after": after.url,
36
+ "elements_added": len(after_ids - before_ids),
37
+ "elements_removed": len(before_ids - after_ids),
38
+ "title_before": before.title,
39
+ "title_after": after.title,
40
+ }
41
+
42
+ def _matches_expectation(self, diff: dict, action: WebAction) -> bool:
43
+ """
44
+ 按动作类型给出最基本的"是否生效"判断:
45
+
46
+ - navigate: URL 必须变化
47
+ - click: URL 变化,或页面元素结构发生变化(新增/移除元素),
48
+ 或标题变化——三者任一即视为"页面对点击有反应"
49
+ - type: 无法通过快照diff直接验证输入内容是否写入(需要额外读取
50
+ 目标元素的 value),这里保守返回 True,交由上层结合
51
+ extract 结果自行判断
52
+ - scroll / wait / extract: 不强制要求页面发生变化
53
+ """
54
+ if action.type == "navigate":
55
+ return diff["url_before"] != diff["url_after"]
56
+
57
+ if action.type == "click":
58
+ return (
59
+ diff["url_before"] != diff["url_after"]
60
+ or diff["elements_added"] > 0
61
+ or diff["elements_removed"] > 0
62
+ or diff["title_before"] != diff["title_after"]
63
+ )
64
+
65
+ if action.type in ("type", "scroll", "wait", "extract"):
66
+ return True
67
+
68
+ return True