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,154 @@
1
+ """
2
+ Foxclaw Core — 运行中干预队列 (Steering Queue)
3
+
4
+ 借鉴 OpenClaw 的"租约队列"原理,适配 Foxclaw。解决的问题:
5
+ 子任务完成的结果,如何安全地回收给主任务——
6
+ · 不丢:主任务崩了/没及时取,结果不能消失(租约过期 → 重新入队)
7
+ · 不重复:已被某个消费者租走的结果,不会被另一个消费者再拿一次
8
+
9
+ 当前用途:多 Agent 顺序执行其实用不上(直接收集即可)。
10
+ 设计目标:为**未来并行子 Agent**预留——主管并行派发多个子任务,
11
+ 各自完成时间不定,本队列负责"完成一个安全回收一个,不丢不重"。
12
+
13
+ 租约(Lease)机制:
14
+ · publish:子任务完成 → 结果入队(pending)
15
+ · lease:消费者租走一批结果(标记 leased + 租约到期时间),拿去注入主任务
16
+ · ack:消费者确认处理完 → 结果移除
17
+ · 过期未 ack 的 leased 项 → 自动回到 pending(主任务崩溃/超时的兜底)
18
+ """
19
+ from __future__ import annotations
20
+ import threading
21
+ import time
22
+ import uuid
23
+ from dataclasses import dataclass, field
24
+ from typing import Any
25
+
26
+
27
+ @dataclass
28
+ class SteeringItem:
29
+ item_id: str
30
+ requester: str
31
+ source_agent: str
32
+ payload: dict
33
+ created: float = field(default_factory=time.time)
34
+ leased_until: float = 0.0
35
+ lease_id: str = ""
36
+
37
+ @property
38
+ def is_leased(self) -> bool:
39
+ return self.leased_until > time.time()
40
+
41
+ def to_dict(self) -> dict:
42
+ return {"item_id": self.item_id, "requester": self.requester,
43
+ "source_agent": self.source_agent, "payload": self.payload,
44
+ "created": self.created, "leased": self.is_leased}
45
+
46
+
47
+ class SteeringQueue:
48
+ """租约队列。线程安全,支持多消费者并发租取不重复。"""
49
+
50
+ def __init__(self, lease_seconds: float = 30.0):
51
+ self.lease_seconds = lease_seconds
52
+ self._items: dict[str, SteeringItem] = {}
53
+ self._lock = threading.RLock()
54
+
55
+ def publish(self, requester: str, source_agent: str, payload: dict) -> str:
56
+ """子任务完成 → 结果入队。返回 item_id。"""
57
+ with self._lock:
58
+ item_id = uuid.uuid4().hex[:16]
59
+ self._items[item_id] = SteeringItem(
60
+ item_id=item_id, requester=requester,
61
+ source_agent=source_agent, payload=payload)
62
+ return item_id
63
+
64
+ def lease(self, requester: str | None = None, max_items: int = 10) -> list[SteeringItem]:
65
+ """租走一批待处理结果(可按 requester 过滤)。
66
+
67
+ 原子地:挑出未被租用(或租约已过期)的项,标记为 leased,返回。
68
+ 同一项不会被两个消费者同时租到(锁内完成挑选+标记)。
69
+ """
70
+ with self._lock:
71
+ self._reclaim_expired()
72
+ now = time.time()
73
+ leased: list[Any] = []
74
+ lease_id = uuid.uuid4().hex[:12]
75
+ for item in self._items.values():
76
+ if len(leased) >= max_items:
77
+ break
78
+ if item.is_leased:
79
+ continue
80
+ if requester is not None and item.requester != requester:
81
+ continue
82
+ item.leased_until = now + self.lease_seconds
83
+ item.lease_id = lease_id
84
+ leased.append(item)
85
+ return leased
86
+
87
+ def ack(self, item_id: str, lease_id: str | None = None) -> bool:
88
+ """消费者确认处理完 → 移除该项。
89
+
90
+ 校验 lease_id:只有持有当前租约的消费者能 ack(防止过期租约误删
91
+ 已被别人重新租走的项)。lease_id=None 时跳过校验(兼容简单场景)。
92
+ """
93
+ with self._lock:
94
+ item = self._items.get(item_id)
95
+ if item is None:
96
+ return False
97
+ if lease_id is not None and item.lease_id != lease_id:
98
+ return False
99
+ del self._items[item_id]
100
+ return True
101
+
102
+ def nack(self, item_id: str):
103
+ """消费者放弃处理 → 立即释放租约,回到 pending(可被重新租取)。"""
104
+ with self._lock:
105
+ item = self._items.get(item_id)
106
+ if item:
107
+ item.leased_until = 0.0
108
+ item.lease_id = ""
109
+
110
+ def _reclaim_expired(self):
111
+ """过期未 ack 的 leased 项 → 回到 pending(主任务崩溃/超时的兜底)。"""
112
+ now = time.time()
113
+ for item in self._items.values():
114
+ if 0 < item.leased_until <= now:
115
+ item.leased_until = 0.0
116
+ item.lease_id = ""
117
+
118
+ def pending_count(self, requester: str | None = None) -> int:
119
+ """待处理(未被有效租用)的项数。"""
120
+ with self._lock:
121
+ self._reclaim_expired()
122
+ return sum(1 for it in self._items.values()
123
+ if not it.is_leased
124
+ and (requester is None or it.requester == requester))
125
+
126
+ def stats(self) -> dict:
127
+ with self._lock:
128
+ self._reclaim_expired()
129
+ total = len(self._items)
130
+ leased = sum(1 for it in self._items.values() if it.is_leased)
131
+ return {"total": total, "leased": leased, "pending": total - leased,
132
+ "lease_seconds": self.lease_seconds}
133
+
134
+ def drain(self, requester: str) -> list[dict]:
135
+ """便捷方法(顺序场景用):取出某 requester 的待处理结果并清空。
136
+
137
+ 适合"主任务等所有子任务完成后一次性收集"的顺序模式。
138
+ 尊重租约:**跳过正被 lease 租用的项**,避免与 lease/ack 混用时
139
+ 同一结果被处理两次(drain 拿一次 + 租约消费者再拿一次)。
140
+ """
141
+ with self._lock:
142
+ self._reclaim_expired()
143
+ out = []
144
+ to_del = []
145
+ for item_id, item in self._items.items():
146
+ if item.requester != requester:
147
+ continue
148
+ if item.is_leased:
149
+ continue
150
+ out.append(item.payload)
151
+ to_del.append(item_id)
152
+ for item_id in to_del:
153
+ del self._items[item_id]
154
+ return out
@@ -0,0 +1,84 @@
1
+ """
2
+ 系统事件总线 (SystemBus)
3
+
4
+ 进程内发布订阅总线,用于系统级事件流转(执行→审计→记忆)。
5
+ 借鉴 Codex 版 Foxclaw 的 SystemBus,提升为本项目的全局事件中枢。
6
+
7
+ 与 HarnessEventBus 的区别:HarnessEventBus 局限于单个 Agent 的 Harness 内部;
8
+ SystemBus 是系统级的,跨 Agent / 跨模块,且保留事件历史供审计回放。
9
+ """
10
+ from __future__ import annotations
11
+ import time
12
+ from dataclasses import dataclass, field
13
+ from typing import Callable
14
+
15
+
16
+ @dataclass
17
+ class BusMessage:
18
+ channel: str
19
+ payload: dict
20
+ ts: float = field(default_factory=time.time)
21
+
22
+ def to_dict(self) -> dict:
23
+ return {"channel": self.channel, "payload": self.payload, "ts": self.ts}
24
+
25
+
26
+ class SystemBus:
27
+ """系统级发布订阅总线。
28
+
29
+ 【P0·统一事件脊柱】SystemBus 现已退化为 EventSpine 的**薄门面**:系统级事件
30
+ 与 harness 事件汇入同一条脊柱,被同一个 WS 投影统一观测(修复"truth/process
31
+ 到不了前端")。对既有消费者(TruthPipeline/ProcessManager/CognitiveState/主管分发)
32
+ 零改动:publish/subscribe/history 签名不变;handler 收到的 Event 与旧 BusMessage
33
+ 同形(都有 .channel/.payload/.to_dict())。
34
+
35
+ 【P3-6/P4-1】subscribe/history 加 exact 参数,语义显式化:
36
+ · exact=False(默认):前缀匹配(EventSpine 原生行为)。订阅 "execution.completed"
37
+ 会同时收到 "execution.completed" 和 "execution.completed.extra"(如果有)。
38
+ 旧版独立 SystemBus 是精确匹配,门面化后语义悄悄变了——精确 channel 名的
39
+ 订阅点应显式传 exact=True,前缀意图保持默认。
40
+ · exact=True:包一层过滤,只放行 topic 完全相等的事件(与旧版语义一致)。
41
+
42
+ 【P4-1】调用点审查结论(grep `.subscribe(` 全仓):
43
+ ✓ truth_pipeline.py ×3(execution.completed/trace.recorded/truth.appended)→ exact=True
44
+ ✓ foxclaw/__init__.py(truth.appended → TruthCrystallizer)→ exact=True
45
+ ✓ cognitive/state.py ×2(execution.completed/memory.updated)→ exact=True
46
+ · cognitive/state.py(task.lifecycle)→ 前缀意图(注释明示),保持 exact=False
47
+ · core/audit_sink.py(memory.shared.write)→ 直接持 spine 引用,不经本门面,不在范围
48
+ · knowledge/__init__.py → HKC 自有 event bus,非本总线,不在范围
49
+ """
50
+
51
+ def __init__(self, history_limit: int = 500, spine=None):
52
+ if spine is None:
53
+ from ..kernel.spine import EventSpine
54
+ spine = EventSpine(history_limit=history_limit)
55
+ self.spine = spine
56
+
57
+ def subscribe(self, channel: str, handler: Callable, exact: bool = False):
58
+ """订阅 channel。
59
+
60
+ exact=False(默认):前缀匹配。subscribe("execution") 收到
61
+ execution.completed / execution.failed 等。
62
+ exact=True:精确匹配。subscribe("execution.completed", exact=True)
63
+ 只收到 topic 完全等于 "execution.completed" 的事件。
64
+ """
65
+ if exact:
66
+ def _exact_handler(msg):
67
+ if getattr(msg, "topic", None) == channel:
68
+ handler(msg)
69
+ self.spine.subscribe(channel, _exact_handler)
70
+ else:
71
+ self.spine.subscribe(channel, handler)
72
+
73
+ def publish(self, channel: str, payload: dict):
74
+ return self.spine.publish(channel, payload, source="system_bus")
75
+
76
+ def history(self, channel: str | None = None, limit: int = 50,
77
+ exact: bool = False) -> list:
78
+ """历史事件(dict 列表)。exact=True 时只返回 topic 完全匹配的事件
79
+ (默认前缀匹配,与 EventSpine 原生一致)。"""
80
+ events = self.spine.history(channel, limit)
81
+ if exact and channel:
82
+ events = [e for e in events
83
+ if (e.get("topic") or e.get("channel")) == channel]
84
+ return events
@@ -0,0 +1,316 @@
1
+ """
2
+ ThoughtChain — 带 Harness 标注的推理图
3
+ 每个节点是一个推理步骤,边是因果依赖
4
+ Harness 校验结果作为节点属性附着其上
5
+ """
6
+
7
+ import os
8
+ import sqlite3
9
+ import json
10
+ import uuid
11
+ from contextlib import contextmanager
12
+ from datetime import datetime, timezone
13
+ from dataclasses import dataclass, asdict
14
+ from typing import Literal, Optional
15
+ from pathlib import Path
16
+
17
+
18
+ NodeType = Literal[
19
+ "goal",
20
+ "reasoning",
21
+ "memory_retrieval",
22
+ "tool_intent",
23
+ "tool_use",
24
+ "plan",
25
+ "decision",
26
+ "policy_update",
27
+ "consolidation",
28
+ "reflection",
29
+ ]
30
+
31
+ HarnessStatus = Literal["BASELINE", "PASS", "WARN", "BLOCK", "PENDING", "REGRESSION"]
32
+
33
+
34
+ @dataclass
35
+ class ThoughtNode:
36
+ """ThoughtChain 的基本单元"""
37
+ id: str
38
+ session_id: str
39
+ agent_id: str
40
+ type: NodeType
41
+ content: str
42
+ conf: float
43
+ mem_refs: list[str]
44
+ parent_ids: list[str]
45
+ harness_status: HarnessStatus
46
+ harness_reason: Optional[str]
47
+ harness_delta: Optional[float]
48
+ dominant_dim: Optional[str]
49
+ tool_name: Optional[str]
50
+ entities: list[str]
51
+ timestamp: str
52
+ metadata: dict
53
+
54
+ @classmethod
55
+ def create(
56
+ cls,
57
+ session_id: str,
58
+ agent_id: str,
59
+ type: NodeType,
60
+ content: str,
61
+ conf: float = 1.0,
62
+ mem_refs: list[str] | None = None,
63
+ parent_ids: list[str] | None = None,
64
+ tool_name: str | None = None,
65
+ entities: list[str] | None = None,
66
+ metadata: dict | None = None,
67
+ ) -> "ThoughtNode":
68
+ return cls(
69
+ id=f"tn_{uuid.uuid4().hex[:12]}",
70
+ session_id=session_id,
71
+ agent_id=agent_id,
72
+ type=type,
73
+ content=content,
74
+ conf=conf,
75
+ mem_refs=mem_refs or [],
76
+ parent_ids=parent_ids or [],
77
+ harness_status="PENDING",
78
+ harness_reason=None,
79
+ harness_delta=None,
80
+ dominant_dim=None,
81
+ tool_name=tool_name,
82
+ entities=entities or [],
83
+ timestamp=datetime.now(timezone.utc).isoformat(),
84
+ metadata=metadata or {},
85
+ )
86
+
87
+ def to_dict(self) -> dict:
88
+ return asdict(self)
89
+
90
+ @classmethod
91
+ def from_dict(cls, d: dict) -> "ThoughtNode":
92
+ return cls(**d)
93
+
94
+
95
+ class ThoughtChain:
96
+ """
97
+ ThoughtChain 存储与查询引擎
98
+ 底层使用 SQLite,接口设计为对外标准化可替换
99
+ """
100
+
101
+ def __init__(self, db_path: str = "./foxclaw_data/thoughtchain.db"):
102
+ self.db_path = Path(db_path).resolve()
103
+ os.makedirs(self.db_path.parent, exist_ok=True)
104
+ self._init_db()
105
+
106
+ def _init_db(self):
107
+ with self._conn() as conn:
108
+ conn.execute("""
109
+ CREATE TABLE IF NOT EXISTS thought_nodes (
110
+ id TEXT PRIMARY KEY,
111
+ session_id TEXT NOT NULL,
112
+ agent_id TEXT NOT NULL,
113
+ type TEXT NOT NULL,
114
+ content TEXT NOT NULL,
115
+ conf REAL NOT NULL,
116
+ mem_refs TEXT NOT NULL,
117
+ parent_ids TEXT NOT NULL,
118
+ harness_status TEXT NOT NULL,
119
+ harness_reason TEXT,
120
+ harness_delta REAL,
121
+ dominant_dim TEXT,
122
+ tool_name TEXT,
123
+ entities TEXT NOT NULL,
124
+ timestamp TEXT NOT NULL,
125
+ metadata TEXT NOT NULL
126
+ )
127
+ """)
128
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_session ON thought_nodes(session_id)")
129
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_agent ON thought_nodes(agent_id)")
130
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_harness ON thought_nodes(harness_status)")
131
+
132
+ @contextmanager
133
+ def _conn(self):
134
+ """新建一条 SQLite 连接(每次调用即建即弃,不跨线程复用连接对象)。
135
+
136
+ 并发加固:ThoughtChain 是**五个 Agent 共享**的审计存储,而写入发生在
137
+ `_call_llm` 的 `asyncio.to_thread` 工作线程 + FastAPI 请求线程里,必然跨线程并发写。
138
+ SQLite 默认 busy_timeout=0,锁竞争时会立刻抛 `OperationalError: database is locked`。
139
+ · timeout=5.0 —— 即 busy_timeout,写者遇锁最多等 5s 而非立刻报错;
140
+ · journal_mode=WAL —— 单写多读、读写不互斥,大幅降低锁竞争(审计读多写多)。
141
+ WAL 是数据库级持久设置,重复设置幂等无害。
142
+
143
+ 生命周期语义:`with self._conn() as conn:` 退出时——正常则 commit,异常则
144
+ rollback,并**无论正常/异常都 close**。不能依赖 sqlite3.Connection 自带的
145
+ `with`(它退出只 commit/rollback 不 close,WAL 的 -wal/-shm 句柄会残留,
146
+ Windows 上删库会 WinError 32);也不能只用 closing()(它只 close 不 commit,
147
+ 会把 INSERT/UPDATE 回滚导致并发写全丢)。
148
+ """
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
+ try:
155
+ yield conn
156
+ conn.commit()
157
+ except BaseException:
158
+ conn.rollback()
159
+ raise
160
+ finally:
161
+ conn.close()
162
+
163
+ def add_node(self, node: ThoughtNode) -> ThoughtNode:
164
+ """写入节点,返回写入后的节点(Harness 标注前状态)"""
165
+ with self._conn() as conn:
166
+ conn.execute("""
167
+ INSERT INTO thought_nodes VALUES (
168
+ :id, :session_id, :agent_id, :type, :content, :conf,
169
+ :mem_refs, :parent_ids, :harness_status, :harness_reason,
170
+ :harness_delta, :dominant_dim, :tool_name, :entities,
171
+ :timestamp, :metadata
172
+ )
173
+ """, {
174
+ **node.to_dict(),
175
+ "mem_refs": json.dumps(node.mem_refs),
176
+ "parent_ids": json.dumps(node.parent_ids),
177
+ "entities": json.dumps(node.entities),
178
+ "metadata": json.dumps(node.metadata),
179
+ })
180
+ return node
181
+
182
+ def update_harness(
183
+ self,
184
+ node_id: str,
185
+ status: HarnessStatus,
186
+ reason: str | None = None,
187
+ delta: float | None = None,
188
+ dominant_dim: str | None = None,
189
+ ):
190
+ """Harness 校验完成后更新节点标注"""
191
+ with self._conn() as conn:
192
+ conn.execute("""
193
+ UPDATE thought_nodes
194
+ SET harness_status=?, harness_reason=?, harness_delta=?, dominant_dim=?
195
+ WHERE id=?
196
+ """, (status, reason, delta, dominant_dim, node_id))
197
+
198
+ def get_node(self, node_id: str) -> Optional[ThoughtNode]:
199
+ with self._conn() as conn:
200
+ row = conn.execute(
201
+ "SELECT * FROM thought_nodes WHERE id=?", (node_id,)
202
+ ).fetchone()
203
+ return self._row_to_node(row) if row else None
204
+
205
+ def get_chain(self, session_id: str, agent_id: str | None = None) -> list[ThoughtNode]:
206
+ """获取一个会话的完整推理链,按时间排序"""
207
+ with self._conn() as conn:
208
+ if agent_id:
209
+ rows = conn.execute(
210
+ "SELECT * FROM thought_nodes WHERE session_id=? AND agent_id=? ORDER BY timestamp",
211
+ (session_id, agent_id)
212
+ ).fetchall()
213
+ else:
214
+ rows = conn.execute(
215
+ "SELECT * FROM thought_nodes WHERE session_id=? ORDER BY timestamp",
216
+ (session_id,)
217
+ ).fetchall()
218
+ return [self._row_to_node(r) for r in rows]
219
+
220
+ def get_blocked_nodes(self, agent_id: str | None = None, limit: int = 50) -> list[ThoughtNode]:
221
+ """获取被 Harness 截断的节点,用于审计"""
222
+ with self._conn() as conn:
223
+ if agent_id:
224
+ rows = conn.execute(
225
+ "SELECT * FROM thought_nodes WHERE harness_status='BLOCK' AND agent_id=? ORDER BY timestamp DESC LIMIT ?",
226
+ (agent_id, limit)
227
+ ).fetchall()
228
+ else:
229
+ rows = conn.execute(
230
+ "SELECT * FROM thought_nodes WHERE harness_status='BLOCK' ORDER BY timestamp DESC LIMIT ?",
231
+ (limit,)
232
+ ).fetchall()
233
+ return [self._row_to_node(r) for r in rows]
234
+
235
+ def search_nodes(self, keyword: str, agent_id: str | None = None,
236
+ limit: int = 20) -> list[ThoughtNode]:
237
+ """按关键词在推理节点内容里检索(LIKE 子串匹配)。供 HSR 的 AgentSourcePort
238
+ 把「各 Agent 想过/判过什么」连通进内部搜索。只读。"""
239
+ kw = (keyword or "").strip()
240
+ if not kw:
241
+ return []
242
+ like = f"%{kw}%"
243
+ with self._conn() as conn:
244
+ if agent_id:
245
+ rows = conn.execute(
246
+ "SELECT * FROM thought_nodes WHERE content LIKE ? AND agent_id=? "
247
+ "ORDER BY timestamp DESC LIMIT ?", (like, agent_id, limit)
248
+ ).fetchall()
249
+ else:
250
+ rows = conn.execute(
251
+ "SELECT * FROM thought_nodes WHERE content LIKE ? "
252
+ "ORDER BY timestamp DESC LIMIT ?", (like, limit)
253
+ ).fetchall()
254
+ return [self._row_to_node(r) for r in rows]
255
+
256
+ def get_delta_trend(self, agent_id: str, limit: int = 100) -> list[dict]:
257
+ """获取漂移趋势数据,用于前端可视化"""
258
+ with self._conn() as conn:
259
+ rows = conn.execute("""
260
+ SELECT timestamp, harness_delta, harness_status, dominant_dim
261
+ FROM thought_nodes
262
+ WHERE agent_id=? AND harness_delta IS NOT NULL
263
+ ORDER BY timestamp DESC LIMIT ?
264
+ """, (agent_id, limit)).fetchall()
265
+ return [
266
+ {"timestamp": r[0], "delta": r[1], "status": r[2], "dim": r[3]}
267
+ for r in rows
268
+ ]
269
+
270
+ def get_summary(self, agent_id: str) -> dict:
271
+ """获取 Agent 的 ThoughtChain 健康摘要"""
272
+ with self._conn() as conn:
273
+ total = conn.execute(
274
+ "SELECT COUNT(*) FROM thought_nodes WHERE agent_id=?", (agent_id,)
275
+ ).fetchone()[0]
276
+ blocked = conn.execute(
277
+ "SELECT COUNT(*) FROM thought_nodes WHERE agent_id=? AND harness_status='BLOCK'",
278
+ (agent_id,)
279
+ ).fetchone()[0]
280
+ warned = conn.execute(
281
+ "SELECT COUNT(*) FROM thought_nodes WHERE agent_id=? AND harness_status='WARN'",
282
+ (agent_id,)
283
+ ).fetchone()[0]
284
+ avg_conf = conn.execute(
285
+ "SELECT AVG(conf) FROM thought_nodes WHERE agent_id=? AND harness_status='PASS'",
286
+ (agent_id,)
287
+ ).fetchone()[0] or 0.0
288
+ avg_delta = conn.execute(
289
+ "SELECT AVG(harness_delta) FROM thought_nodes WHERE agent_id=? AND harness_delta IS NOT NULL",
290
+ (agent_id,)
291
+ ).fetchone()[0] or 0.0
292
+ return {
293
+ "agent_id": agent_id,
294
+ "total_nodes": total,
295
+ "blocked_nodes": blocked,
296
+ "warned_nodes": warned,
297
+ "block_rate": blocked / total if total > 0 else 0.0,
298
+ "avg_confidence": round(avg_conf, 3),
299
+ "avg_delta": round(avg_delta, 3),
300
+ }
301
+
302
+ def _row_to_node(self, row) -> ThoughtNode:
303
+ cols = [
304
+ "id", "session_id", "agent_id", "type", "content", "conf",
305
+ "mem_refs", "parent_ids", "harness_status", "harness_reason",
306
+ "harness_delta", "dominant_dim", "tool_name", "entities",
307
+ "timestamp", "metadata"
308
+ ]
309
+ d = dict(zip(cols, row))
310
+ for field, default in (("mem_refs", []), ("parent_ids", []),
311
+ ("entities", []), ("metadata", {})):
312
+ try:
313
+ d[field] = json.loads(d[field])
314
+ except (json.JSONDecodeError, TypeError):
315
+ d[field] = default
316
+ return ThoughtNode(**d)