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,375 @@
1
+ """
2
+ 请求链路追踪(tracing.py)
3
+
4
+ 与既有可观测设施的分工(不重复造):
5
+ METRICS 聚合视角:各端点/LLM 的计数、错误率、延迟分位 —— 回答"整体健康吗"
6
+ ThoughtChain 单 Agent 的推理节点 —— 回答"它当时怎么想的"
7
+ EventSpine 事件流(correlation_id 逐事件随机)—— 回答"发生过什么"
8
+ Trace(这里) 单次请求视角:一个 trace_id 贯穿 agent→LLM→skill→memory,带层级 span
9
+ 与耗时 —— 回答"**这一次**请求慢在哪、调了谁"
10
+
11
+ 实现要点:
12
+ · contextvars 传播:与 _suppress_memory 同款。asyncio.to_thread / asyncio.run 会带上
13
+ 上下文,故后台线程里的 span 仍归属发起它的请求。
14
+ · span() 在无 trace 时是**零成本 no-op**(不在请求里跑的后台任务不会报错/不产生垃圾)。
15
+ · 有界环形存储:只留最近 N 条 trace,防内存无限增长(与 metrics「unmatched」同款教训)。
16
+ · 线程安全:Trace.spans 与 TraceStore 各自加锁。
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ import json
22
+ import time
23
+ import uuid
24
+ import random
25
+ import sqlite3
26
+ import logging
27
+ import threading
28
+ import contextvars
29
+ from collections import deque
30
+ from contextlib import contextmanager
31
+ from . import settings
32
+ from typing import Any
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ def _slow_ms() -> float:
38
+ return float(settings.get_safe("FOXCLAW_TRACE_SLOW_MS"))
39
+
40
+
41
+ def _sample_rate() -> float:
42
+ return max(0.0, min(1.0, float(settings.get_safe("FOXCLAW_TRACE_SAMPLE"))))
43
+
44
+
45
+ def _retain() -> int:
46
+ return max(100, int(settings.get_safe("FOXCLAW_TRACE_RETAIN")))
47
+
48
+ _current: contextvars.ContextVar = contextvars.ContextVar("foxclaw_trace", default=None)
49
+
50
+ MAX_SPANS_PER_TRACE = 200
51
+
52
+
53
+ _TP_RE = __import__("re").compile(r"^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$")
54
+
55
+
56
+ def parse_traceparent(header: str):
57
+ """解析入站 traceparent,返回 trace_id(32hex)或 None。非法/缺失一律 None(不信外部输入)。"""
58
+ if not header:
59
+ return None
60
+ m = _TP_RE.match(str(header).strip().lower())
61
+ if not m:
62
+ return None
63
+ tid = m.group(1)
64
+ if tid == "0" * 32:
65
+ return None
66
+ return tid
67
+
68
+
69
+ def to_traceparent(trace_id: str, span_id: str | None = None) -> str:
70
+ """给出站请求生成 traceparent。span_id 缺省随机(本进程作为对端的 parent)。"""
71
+ tid = (trace_id or uuid.uuid4().hex)[:32].rjust(32, "0")
72
+ sid = (span_id or uuid.uuid4().hex[:16])[:16].rjust(16, "0")
73
+ return f"00-{tid}-{sid}-01"
74
+
75
+
76
+ def current_traceparent():
77
+ """当前上下文的 traceparent(无 trace 则 None)——出站调用带上它即可跨进程接链。"""
78
+ tr = _current.get()
79
+ return to_traceparent(tr.trace_id) if tr is not None else None
80
+
81
+
82
+ class Trace:
83
+ def __init__(self, name: str, trace_id: str | None = None):
84
+ self.trace_id = trace_id or uuid.uuid4().hex
85
+ self.name = name
86
+ self.start_ts = time.time()
87
+ self._t0 = time.perf_counter()
88
+ self.duration_ms: float | None = None
89
+ self.spans: list = []
90
+ self._lock = threading.Lock()
91
+ self._dropped = 0
92
+
93
+ def add_span(self, span: dict):
94
+ with self._lock:
95
+ if len(self.spans) >= MAX_SPANS_PER_TRACE:
96
+ self._dropped += 1
97
+ return
98
+ self.spans.append(span)
99
+
100
+ def finish(self):
101
+ self.duration_ms = (time.perf_counter() - self._t0) * 1000.0
102
+
103
+ def has_error(self) -> bool:
104
+ with self._lock:
105
+ return any(not s.get("ok", True) for s in self.spans)
106
+
107
+ def to_dict(self, with_spans: bool = True) -> dict:
108
+ with self._lock:
109
+ spans = list(self.spans)
110
+ d = {
111
+ "trace_id": self.trace_id,
112
+ "name": self.name,
113
+ "start_ts": self.start_ts,
114
+ "duration_ms": round(self.duration_ms, 2) if self.duration_ms is not None else None,
115
+ "span_count": len(spans),
116
+ "dropped_spans": self._dropped,
117
+ }
118
+ if with_spans:
119
+ d["spans"] = spans
120
+ return d
121
+
122
+
123
+ class TraceStore:
124
+ """近期用有界环存(快),值得留的落 SQLite(可查历史)。"""
125
+
126
+ def __init__(self, limit: int = 200):
127
+ self._d: deque = deque(maxlen=limit)
128
+ self._lock = threading.Lock()
129
+ self._db_path: str | None = None
130
+
131
+ def attach_db(self, path):
132
+ """开启持久化并建表。连接**即建即弃、不跨线程复用**(与 ThoughtChain 同款):
133
+ SQLite 连接不可跨线程共享,而 trace 写入发生在请求线程/to_thread 工作线程。"""
134
+ self._db_path = str(path)
135
+ try:
136
+ with self._conn() as c:
137
+ c.execute("""CREATE TABLE IF NOT EXISTS traces(
138
+ trace_id TEXT PRIMARY KEY, name TEXT, start_ts REAL,
139
+ duration_ms REAL, span_count INTEGER, ok INTEGER, spans TEXT)""")
140
+ c.execute("CREATE INDEX IF NOT EXISTS idx_tr_ts ON traces(start_ts)")
141
+ c.execute("CREATE INDEX IF NOT EXISTS idx_tr_dur ON traces(duration_ms)")
142
+ except Exception as e:
143
+ logger.warning("trace 持久化不可用(降级为仅内存): %s", e)
144
+ self._db_path = None
145
+
146
+ def _conn(self):
147
+ if self._db_path is None:
148
+ raise RuntimeError("trace 持久化未启用:先 attach_db")
149
+ conn = sqlite3.connect(self._db_path, timeout=5.0)
150
+ try:
151
+ conn.execute("PRAGMA journal_mode=WAL")
152
+ except sqlite3.OperationalError:
153
+ pass
154
+ return conn
155
+
156
+ def _should_persist(self, tr: Trace) -> bool:
157
+ if tr.has_error():
158
+ return True
159
+ if (tr.duration_ms or 0) >= _slow_ms():
160
+ return True
161
+ return random.random() < _sample_rate()
162
+
163
+ def _persist(self, tr: Trace):
164
+ d = tr.to_dict()
165
+ try:
166
+ with self._conn() as c:
167
+ c.execute("INSERT OR REPLACE INTO traces VALUES(?,?,?,?,?,?,?)",
168
+ (tr.trace_id, tr.name, tr.start_ts, tr.duration_ms,
169
+ d["span_count"], 0 if tr.has_error() else 1,
170
+ json.dumps(d["spans"], ensure_ascii=False)))
171
+ c.execute("DELETE FROM traces WHERE trace_id IN ("
172
+ "SELECT trace_id FROM traces ORDER BY start_ts DESC "
173
+ "LIMIT -1 OFFSET ?)", (_retain(),))
174
+ except Exception as e:
175
+ logger.warning("trace 落库失败(不影响请求): %s", e)
176
+
177
+ def put(self, tr: Trace):
178
+ with self._lock:
179
+ self._d.append(tr)
180
+ if self._db_path and self._should_persist(tr):
181
+ self._persist(tr)
182
+ try:
183
+ OTLP.submit(tr)
184
+ except Exception:
185
+ pass
186
+
187
+ def get(self, trace_id: str):
188
+ with self._lock:
189
+ for tr in reversed(self._d):
190
+ if tr.trace_id == trace_id:
191
+ return tr
192
+ return None
193
+
194
+ def get_dict(self, trace_id: str):
195
+ """先查内存环,未命中再查库(重启后仍能捞出历史慢请求)。"""
196
+ tr = self.get(trace_id)
197
+ if tr is not None:
198
+ return tr.to_dict()
199
+ if not self._db_path:
200
+ return None
201
+ try:
202
+ with self._conn() as c:
203
+ row = c.execute("SELECT trace_id,name,start_ts,duration_ms,span_count,spans "
204
+ "FROM traces WHERE trace_id=?", (trace_id,)).fetchone()
205
+ except Exception:
206
+ return None
207
+ if not row:
208
+ return None
209
+ return {"trace_id": row[0], "name": row[1], "start_ts": row[2],
210
+ "duration_ms": row[3], "span_count": row[4],
211
+ "spans": json.loads(row[5] or "[]"), "persisted": True}
212
+
213
+ def query(self, limit: int = 30, slow_ms: float | None = None, errors_only: bool = False):
214
+ """查已落库的历史 trace(内存环之外)。这才是"找出昨天那次慢请求"的入口。"""
215
+ if not self._db_path:
216
+ return []
217
+ sql = "SELECT trace_id,name,start_ts,duration_ms,span_count,ok FROM traces"
218
+ cond, args = [], []
219
+ if slow_ms is not None:
220
+ cond.append("duration_ms >= ?"); args.append(float(slow_ms))
221
+ if errors_only:
222
+ cond.append("ok = 0")
223
+ if cond:
224
+ sql += " WHERE " + " AND ".join(cond)
225
+ sql += " ORDER BY start_ts DESC LIMIT ?"; args.append(int(limit))
226
+ try:
227
+ with self._conn() as c:
228
+ rows = c.execute(sql, args).fetchall()
229
+ except Exception:
230
+ return []
231
+ return [{"trace_id": r[0], "name": r[1], "start_ts": r[2], "duration_ms": r[3],
232
+ "span_count": r[4], "ok": bool(r[5]), "persisted": True} for r in rows]
233
+
234
+ def list(self, limit: int = 30) -> list:
235
+ with self._lock:
236
+ items = list(self._d)[-limit:]
237
+ return [t.to_dict(with_spans=False) for t in reversed(items)]
238
+
239
+ def clear(self):
240
+ with self._lock:
241
+ self._d.clear()
242
+
243
+
244
+ def _otlp_endpoint() -> str:
245
+ return os.environ.get("FOXCLAW_OTLP_ENDPOINT", "").strip().rstrip("/")
246
+
247
+
248
+ def _otlp_service() -> str:
249
+ return os.environ.get("FOXCLAW_OTLP_SERVICE", "foxclaw").strip()
250
+
251
+
252
+ def _attrs(d: dict) -> list:
253
+ return [{"key": str(k), "value": {"stringValue": str(v)}} for k, v in (d or {}).items()]
254
+
255
+
256
+ def _trace_to_otlp(tr: "Trace") -> dict:
257
+ """把一条 Trace 转成 OTLP/JSON。根 span 代表整个请求,其余作为其子 span。"""
258
+ t_ns = lambda sec: str(int(sec * 1_000_000_000))
259
+ root_id = uuid.uuid4().hex[:16]
260
+ dur = (tr.duration_ms or 0.0) / 1000.0
261
+ spans = [{
262
+ "traceId": tr.trace_id, "spanId": root_id, "name": tr.name, "kind": 2,
263
+ "startTimeUnixNano": t_ns(tr.start_ts),
264
+ "endTimeUnixNano": t_ns(tr.start_ts + dur),
265
+ "status": {"code": 2 if tr.has_error() else 1},
266
+ }]
267
+ for s in tr.to_dict()["spans"]:
268
+ st = s.get("ts", tr.start_ts)
269
+ spans.append({
270
+ "traceId": tr.trace_id, "spanId": uuid.uuid4().hex[:16], "parentSpanId": root_id,
271
+ "name": s.get("name", "span"), "kind": 1,
272
+ "startTimeUnixNano": t_ns(st),
273
+ "endTimeUnixNano": t_ns(st + (s.get("ms", 0.0) / 1000.0)),
274
+ "attributes": _attrs(s.get("meta")),
275
+ "status": {"code": 1 if s.get("ok", True) else 2},
276
+ })
277
+ return {"resourceSpans": [{
278
+ "resource": {"attributes": _attrs({"service.name": _otlp_service()})},
279
+ "scopeSpans": [{"scope": {"name": "foxclaw.tracing"}, "spans": spans}],
280
+ }]}
281
+
282
+
283
+ class _OtlpExporter:
284
+ """后台导出:有界队列 + 守护线程。队列满即丢(计数可见),永不阻塞调用方。"""
285
+
286
+ def __init__(self, maxsize: int = 512):
287
+ import queue
288
+ self._q: queue.Queue[Any] = queue.Queue(maxsize=maxsize)
289
+ self._th: threading.Thread | None = None
290
+ self._lock = threading.Lock()
291
+ self.dropped = 0
292
+ self.sent = 0
293
+ self.failed = 0
294
+
295
+ def _ensure_worker(self):
296
+ with self._lock:
297
+ if self._th is None or not self._th.is_alive():
298
+ self._th = threading.Thread(target=self._run, name="otlp-export", daemon=True)
299
+ self._th.start()
300
+
301
+ def submit(self, tr: "Trace"):
302
+ import queue
303
+ if not _otlp_endpoint():
304
+ return
305
+ self._ensure_worker()
306
+ try:
307
+ self._q.put_nowait(tr)
308
+ except queue.Full:
309
+ self.dropped += 1
310
+
311
+ def _run(self):
312
+ import urllib.request
313
+ while True:
314
+ tr = self._q.get()
315
+ ep = _otlp_endpoint()
316
+ if not ep:
317
+ continue
318
+ try:
319
+ body = json.dumps(_trace_to_otlp(tr)).encode("utf-8")
320
+ req = urllib.request.Request(
321
+ ep + "/v1/traces", data=body, method="POST",
322
+ headers={"Content-Type": "application/json"})
323
+ with urllib.request.urlopen(req, timeout=5) as r:
324
+ r.read()
325
+ self.sent += 1
326
+ except Exception as e:
327
+ self.failed += 1
328
+ if self.failed <= 3:
329
+ logger.warning("OTLP 导出失败(不影响请求): %s", e)
330
+
331
+ def stats(self) -> dict:
332
+ return {"enabled": bool(_otlp_endpoint()), "endpoint": _otlp_endpoint(),
333
+ "sent": self.sent, "failed": self.failed, "dropped": self.dropped,
334
+ "queued": self._q.qsize()}
335
+
336
+
337
+ OTLP = _OtlpExporter()
338
+ TRACES = TraceStore()
339
+
340
+
341
+ def start_trace(name: str, trace_id: str | None = None) -> Trace:
342
+ """开一条 trace 并绑到当前上下文(由 API 中间件按请求调用)。"""
343
+ tr = Trace(name, trace_id)
344
+ _current.set(tr)
345
+ return tr
346
+
347
+
348
+ def current_trace():
349
+ return _current.get()
350
+
351
+
352
+ def clear_trace():
353
+ _current.set(None)
354
+
355
+
356
+ @contextmanager
357
+ def span(name: str, **meta):
358
+ """记录一个 span(嵌套用 parent 串联)。不在 trace 里 → 零成本 no-op。
359
+ 异常也会被记进 span(ok=False + error),再原样抛出(不吞异常)。"""
360
+ tr = _current.get()
361
+ if tr is None:
362
+ yield None
363
+ return
364
+ t0 = time.perf_counter()
365
+ rec = {"name": name, "ts": time.time(), "ok": True,
366
+ **({"meta": meta} if meta else {})}
367
+ try:
368
+ yield rec
369
+ except Exception as e:
370
+ rec["ok"] = False
371
+ rec["error"] = f"{type(e).__name__}: {e}"[:200]
372
+ raise
373
+ finally:
374
+ rec["ms"] = round((time.perf_counter() - t0) * 1000.0, 2)
375
+ tr.add_span(rec)
@@ -0,0 +1,110 @@
1
+ """
2
+ 真值管线 (TruthPipeline)
3
+
4
+ 借鉴 Codex 版 Foxclaw 的 TruthPipeline:用事件总线把"执行结果"自动流转为
5
+ "审计记录 → 记忆更新"的真值链。但接到本项目真实的 ThoughtChain 与 HMR,
6
+ 而非 Codex 版的简化组件。
7
+
8
+ 事件链:
9
+ execution.completed (某 Agent 执行完任务)
10
+ → trace.recorded (写入 ThoughtChain 审计)
11
+ → truth.appended (真值落库)
12
+ → memory.updated (沉淀到 HMR 共享记忆)
13
+
14
+ 好处:执行方只需 publish 一个 execution.completed 事件,后续审计/记忆流转自动发生,
15
+ 执行逻辑与审计/记忆解耦(对齐 AHS 的可观测原则)。
16
+ """
17
+ from __future__ import annotations
18
+ from datetime import datetime
19
+ from typing import Any
20
+
21
+
22
+ class TruthPipeline:
23
+ """事件驱动的真值管线。订阅执行事件,自动流转到审计与记忆。
24
+
25
+ 入队方式:任何持有**同一个 EventBus** 的组件,只要 publish execution.completed 即可
26
+ 启动整条真值链,例如:
27
+
28
+ bus.publish("execution.completed",
29
+ {"agent_id": ..., "task": ..., "result": ..., "blocked": ...})
30
+
31
+ 项目内部的实际入口是 agents/base.py 的 CognitiveAgent._emit_execution()
32
+ (经 EventSpine 转发到同一事件名)。本类**不再自带便捷转发方法** —— 事件名本身就是
33
+ 契约,多留一个转发壳只会让「谁能触发真值链」存在两种说法(P3-9 死代码清理)。
34
+ """
35
+
36
+ def __init__(self, bus, hmr=None):
37
+ self.bus = bus
38
+ self.hmr = hmr
39
+ self.last_trace = None
40
+ self.last_memory_update = None
41
+
42
+ bus.subscribe("execution.completed", self._on_execution_completed, exact=True)
43
+ bus.subscribe("trace.recorded", self._on_trace_recorded, exact=True)
44
+ bus.subscribe("truth.appended", self._on_truth_appended, exact=True)
45
+
46
+ def _on_execution_completed(self, msg):
47
+ """执行完成 → 生成审计 trace,发 trace.recorded。"""
48
+ p = msg.payload
49
+ trace = {
50
+ "agent_id": p.get("agent_id"),
51
+ "task": p.get("task"),
52
+ "result_summary": str(p.get("result", ""))[:200],
53
+ "blocked": p.get("blocked", False),
54
+ "schedule": p.get("schedule"),
55
+ "recorded_at": datetime.now().isoformat(timespec="seconds"),
56
+ }
57
+ self.last_trace = trace
58
+ self.bus.publish("trace.recorded", {**p, "trace": trace})
59
+
60
+ def _on_trace_recorded(self, msg):
61
+ """审计已记录 → 真值落库,发 truth.appended。
62
+
63
+ 【P3-5】blocked=True 的任务不沉淀真值:被拦截 ≠ 真值。只留审计 trace
64
+ (trace.recorded 照常发——审计要记录"发生过什么"),发 truth.skipped 事件
65
+ 供前端/审计观测"这次真值沉淀被跳过",不进入真值链。否则订阅 truth.appended
66
+ 的下游(如 TruthCrystallizer)会把被拦截的任务当真值结晶进 HKC。
67
+ """
68
+ p = msg.payload
69
+ if p.get("blocked"):
70
+ self.last_memory_update = {"persisted": False, "reason": "blocked"}
71
+ self.bus.publish("truth.skipped", {**p, "reason": "blocked"})
72
+ return
73
+ result_brief = str(p.get("result", ""))[:200]
74
+ truth = {
75
+ "fact": (f"任务「{p.get('task')}」由 {p.get('agent_id')} 处理。"
76
+ f"要点:{result_brief}"),
77
+ "trace": p.get("trace"),
78
+ }
79
+ self.bus.publish("truth.appended", {**p, "truth": truth})
80
+
81
+ def _on_truth_appended(self, msg):
82
+ """真值落库 → 沉淀到 HMR 共享记忆,发 memory.updated。
83
+
84
+ 【P3-0】写共享层用 agent_id="system":真值沉淀是系统级管线行为,不是某个
85
+ Agent 的自主写入——若按来源 Agent 归因,memory.shared.write 审计事件会把
86
+ 系统写入误记成该 Agent 的写入(审计归因失真)。来源 Agent 保留在 tags 的
87
+ origin:<id> 里,便于追溯。权限侧由 SharedWritePolicy 的 facet 全局豁免
88
+ (tags 含 "truth_pipeline")+ 策略表 system:{"*"} 双通道保证不被误拦。
89
+
90
+ 【P3-5】双保险:_on_trace_recorded 已挡 blocked,这里再检查一次。
91
+ """
92
+ p = msg.payload
93
+ update: dict[str, Any] = {"persisted": False}
94
+ if self.hmr is not None and not p.get("blocked"):
95
+ try:
96
+ content = p.get("truth", {}).get("fact", "")
97
+ origin_agent = p.get("agent_id", "unknown")
98
+ self.hmr.ingest(
99
+ content,
100
+ type="execution",
101
+ agent_id="system",
102
+ title=f"真值_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
103
+ tags=["truth_pipeline", f"origin:{origin_agent}"],
104
+ shared=True,
105
+ )
106
+ update = {"persisted": True, "content": content}
107
+ except Exception as e:
108
+ update = {"persisted": False, "error": str(e)}
109
+ self.last_memory_update = update
110
+ self.bus.publish("memory.updated", {**p, "memory_update": update})
@@ -0,0 +1,149 @@
1
+ """可选的新版本检查(默认关)。
2
+
3
+ 为什么默认关:foxclaw 的定位是本地优先 —— 默认外呼会让「断网也能用」多一个
4
+ 例外,也给安全审计多一个出网点。**只有** 设置 ``FOXCLAW_UPDATE_CHECK=1`` 时才会
5
+ 访问 PyPI,其余情况 ``status()`` 里 ``enabled=False``、``update_available=False``。
6
+
7
+ 失败一律静默:查版本是锦上添花,不能因为它让 ``/api/system/info`` 变慢或报错。
8
+ - 结果按 24h 缓存到 ``<FOXCLAW_DATA_DIR>/update_check.json``;
9
+ - 查询在 daemon 线程里跑,不阻塞请求;
10
+ - 查失败时保留上次成功的结果(只是不刷新时间),避免一次网络抖动让提示消失。
11
+
12
+ 只发一个 GET ``https://pypi.org/pypi/foxclaw/json``,不带任何本机信息。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os
19
+ import threading
20
+ import time
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+ _PYPI_URL = "https://pypi.org/pypi/foxclaw/json"
25
+ _TTL = 24 * 3600
26
+ _TIMEOUT = 3.0
27
+
28
+ _lock = threading.Lock()
29
+ _inflight = False
30
+ _last_error: str | None = None
31
+
32
+
33
+ def _enabled() -> bool:
34
+ return os.environ.get("FOXCLAW_UPDATE_CHECK", "").strip().lower() in {"1", "true", "yes", "on"}
35
+
36
+
37
+ def _current() -> str:
38
+ from foxclaw import __version__
39
+
40
+ return __version__
41
+
42
+
43
+ def _data_dir() -> Path:
44
+ return Path(os.environ.get("FOXCLAW_DATA_DIR") or "./foxclaw_data").expanduser()
45
+
46
+
47
+ def _cache_path() -> Path:
48
+ return _data_dir() / "update_check.json"
49
+
50
+
51
+ def parse_version(value: str) -> tuple[int, ...]:
52
+ """``"0.1.10"`` → ``(0, 1, 10)``。只取各段的前导数字,预发布/本地后缀丢掉。
53
+
54
+ 不引入 ``packaging``:这里只需要「谁更新」,自己比数字段足够,也少一个依赖。
55
+ """
56
+ parts: list[int] = []
57
+ for chunk in str(value or "").strip().split("."):
58
+ digits = ""
59
+ for ch in chunk:
60
+ if not ch.isdigit():
61
+ break
62
+ digits += ch
63
+ if not digits:
64
+ break
65
+ parts.append(int(digits))
66
+ return tuple(parts)
67
+
68
+
69
+ def is_newer(latest: str, current: str) -> bool:
70
+ """latest 是否严格新于 current(逐段比,缺的段补 0)。"""
71
+ left, right = parse_version(latest), parse_version(current)
72
+ width = max(len(left), len(right))
73
+ return left + (0,) * (width - len(left)) > right + (0,) * (width - len(right))
74
+
75
+
76
+ def _read_cache() -> dict[str, Any]:
77
+ try:
78
+ data = json.loads(_cache_path().read_text(encoding="utf-8"))
79
+ except Exception:
80
+ return {}
81
+ return data if isinstance(data, dict) else {}
82
+
83
+
84
+ def _write_cache(data: dict[str, Any]) -> None:
85
+ try:
86
+ path = _cache_path()
87
+ path.parent.mkdir(parents=True, exist_ok=True)
88
+ path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
89
+ except Exception:
90
+ pass
91
+
92
+
93
+ def _fetch_latest() -> str:
94
+ import urllib.request
95
+
96
+ req = urllib.request.Request(
97
+ _PYPI_URL,
98
+ headers={"Accept": "application/json", "User-Agent": f"foxclaw/{_current()}"},
99
+ )
100
+ with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp: # noqa: S310 - 地址是常量 https
101
+ payload = json.loads(resp.read().decode("utf-8", "replace"))
102
+ version = ((payload or {}).get("info") or {}).get("version") or ""
103
+ if not version:
104
+ raise ValueError("PyPI 响应里没有 info.version")
105
+ return str(version)
106
+
107
+
108
+ def _refresh() -> None:
109
+ global _inflight, _last_error
110
+ try:
111
+ latest = _fetch_latest()
112
+ _write_cache({"latest": latest, "checked_at": time.time(), "current": _current()})
113
+ _last_error = None
114
+ except Exception as exc:
115
+ _last_error = f"{type(exc).__name__}: {exc}"
116
+ finally:
117
+ with _lock:
118
+ _inflight = False
119
+
120
+
121
+ def maybe_refresh() -> None:
122
+ """启用且缓存过期 → 后台查一次。幂等、非阻塞,可放心在每个 system/info 里调。"""
123
+ global _inflight
124
+ if not _enabled():
125
+ return
126
+ checked = _read_cache().get("checked_at")
127
+ if isinstance(checked, (int, float)) and time.time() - checked < _TTL:
128
+ return
129
+ with _lock:
130
+ if _inflight:
131
+ return
132
+ _inflight = True
133
+ threading.Thread(target=_refresh, name="foxclaw-update-check", daemon=True).start()
134
+
135
+
136
+ def status() -> dict[str, Any]:
137
+ """给 ``/api/system/info`` 的只读快照。未启用时 latest 仍是缓存值,但不报可更新。"""
138
+ enabled = _enabled()
139
+ current = _current()
140
+ cache = _read_cache()
141
+ latest = str(cache.get("latest") or "")
142
+ return {
143
+ "enabled": enabled,
144
+ "current": current,
145
+ "latest": latest or None,
146
+ "update_available": bool(enabled and latest and is_newer(latest, current)),
147
+ "checked_at": cache.get("checked_at"),
148
+ "error": _last_error,
149
+ }