foxclaw 0.1.2 → 0.1.4

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 (368) hide show
  1. package/index.js +1 -8
  2. package/package.json +1 -1
  3. package/python/.env.example +8 -0
  4. package/python/.github/workflows/ci.yml +47 -2
  5. package/python/.github/workflows/release.yml +5 -0
  6. package/python/CHANGELOG.md +100 -0
  7. package/python/CHANGELOG_CN.md +100 -0
  8. package/python/README.md +17 -2
  9. package/python/README_CN.md +38 -20
  10. package/python/bench/bm25_audit.py +1 -14
  11. package/python/bench/bm25_regression.py +0 -1
  12. package/python/bench/bm25_stress.py +0 -4
  13. package/python/bench/cd_audit.py +0 -23
  14. package/python/bench/chaos.py +6 -8
  15. package/python/bench/e2e_real_models.py +0 -1
  16. package/python/bench/llm_retry_test.py +2 -9
  17. package/python/bench/load_test.py +5 -8
  18. package/python/bench/vector_ivf_upgrade.py +1 -7
  19. package/python/docs/CONTENT_GUARD.md +189 -0
  20. package/python/docs/CONTENT_GUARD_CN.md +189 -0
  21. package/python/docs/RELEASE.md +78 -37
  22. package/python/docs/RUN_CN.md +2 -2
  23. package/python/evals/governance/cases.py +0 -37
  24. package/python/evals/governance/run_evals.py +5 -37
  25. package/python/examples/governance_middleware_demo.py +1 -6
  26. package/python/examples/langgraph_governance_demo.py +7 -11
  27. package/python/foxclaw/__init__.py +74 -152
  28. package/python/foxclaw/agents/base.py +82 -136
  29. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  30. package/python/foxclaw/agents/impl/finance.py +4 -21
  31. package/python/foxclaw/agents/impl/office.py +7 -24
  32. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  33. package/python/foxclaw/agents/mixins/_context.py +0 -23
  34. package/python/foxclaw/agents/mixins/augment.py +10 -16
  35. package/python/foxclaw/agents/mixins/llm.py +7 -6
  36. package/python/foxclaw/agents/mixins/memory.py +23 -3
  37. package/python/foxclaw/agents/mixins/preference.py +58 -8
  38. package/python/foxclaw/agents/mixins/prompt.py +59 -12
  39. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  40. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  41. package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
  42. package/python/foxclaw/api/__init__.py +18 -77
  43. package/python/foxclaw/api/_state.py +43 -0
  44. package/python/foxclaw/api/bootstrap.py +114 -0
  45. package/python/foxclaw/api/middleware.py +5 -28
  46. package/python/foxclaw/api/routes/ahs.py +60 -73
  47. package/python/foxclaw/api/routes/attachments.py +5 -6
  48. package/python/foxclaw/api/routes/browser.py +7 -11
  49. package/python/foxclaw/api/routes/domain.py +18 -26
  50. package/python/foxclaw/api/routes/knowledge.py +3 -21
  51. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  52. package/python/foxclaw/api/routes/memory.py +50 -12
  53. package/python/foxclaw/api/routes/orchestration.py +3 -5
  54. package/python/foxclaw/api/routes/recovery.py +4 -3
  55. package/python/foxclaw/api/routes/review.py +2 -11
  56. package/python/foxclaw/api/routes/search.py +3 -7
  57. package/python/foxclaw/api/routes/system.py +33 -49
  58. package/python/foxclaw/api/routes/task.py +31 -49
  59. package/python/foxclaw/api/routes/ws.py +2 -6
  60. package/python/foxclaw/api/spine.py +1 -4
  61. package/python/foxclaw/behavior/__init__.py +20 -0
  62. package/python/foxclaw/behavior/behavior.yaml +62 -0
  63. package/python/foxclaw/behavior/spec.py +287 -0
  64. package/python/foxclaw/browser/action/action_executor.py +1 -5
  65. package/python/foxclaw/browser/action/action_types.py +0 -1
  66. package/python/foxclaw/browser/action/url_policy.py +1 -5
  67. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  68. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  69. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  70. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  71. package/python/foxclaw/browser/service.py +7 -11
  72. package/python/foxclaw/capability/embedding_service.py +4 -6
  73. package/python/foxclaw/channels/__init__.py +9 -11
  74. package/python/foxclaw/channels/discord.py +4 -13
  75. package/python/foxclaw/channels/dispatcher.py +2 -27
  76. package/python/foxclaw/channels/feishu.py +10 -20
  77. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  78. package/python/foxclaw/channels/signing.py +9 -9
  79. package/python/foxclaw/channels/telegram.py +3 -4
  80. package/python/foxclaw/channels/weixin.py +7 -17
  81. package/python/foxclaw/cli.py +42 -13
  82. package/python/foxclaw/cognitive/reasoner.py +9 -10
  83. package/python/foxclaw/cognitive/state.py +18 -24
  84. package/python/foxclaw/core/audit_chain.py +1 -3
  85. package/python/foxclaw/core/audit_sink.py +3 -3
  86. package/python/foxclaw/core/compaction.py +8 -26
  87. package/python/foxclaw/core/config.py +2 -2
  88. package/python/foxclaw/core/confirm_nonce.py +6 -12
  89. package/python/foxclaw/core/content_guard.py +341 -0
  90. package/python/foxclaw/core/envfile.py +2 -2
  91. package/python/foxclaw/core/failure_policy.py +17 -26
  92. package/python/foxclaw/core/gateway.py +6 -10
  93. package/python/foxclaw/core/governance_audit.py +3 -7
  94. package/python/foxclaw/core/idempotency.py +15 -22
  95. package/python/foxclaw/core/injection_guard.py +10 -29
  96. package/python/foxclaw/core/memory_adapter.py +53 -60
  97. package/python/foxclaw/core/metrics.py +4 -5
  98. package/python/foxclaw/core/model_router.py +74 -150
  99. package/python/foxclaw/core/paths.py +1 -1
  100. package/python/foxclaw/core/recovery.py +21 -34
  101. package/python/foxclaw/core/scheduler.py +23 -26
  102. package/python/foxclaw/core/secrets.py +0 -3
  103. package/python/foxclaw/core/sensitive_write.py +283 -0
  104. package/python/foxclaw/core/sessions.py +4 -6
  105. package/python/foxclaw/core/settings.py +16 -32
  106. package/python/foxclaw/core/shared_write.py +11 -23
  107. package/python/foxclaw/core/steering_queue.py +12 -11
  108. package/python/foxclaw/core/system_bus.py +1 -1
  109. package/python/foxclaw/core/thought_chain.py +30 -32
  110. package/python/foxclaw/core/token_meter.py +9 -13
  111. package/python/foxclaw/core/tools.py +4 -12
  112. package/python/foxclaw/core/tracing.py +25 -40
  113. package/python/foxclaw/core/truth_pipeline.py +5 -9
  114. package/python/foxclaw/core/update_check.py +149 -0
  115. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  116. package/python/foxclaw/core/verify.py +6 -14
  117. package/python/foxclaw/core/web_search.py +4 -28
  118. package/python/foxclaw/finance/evidence.py +14 -18
  119. package/python/foxclaw/finance/fx.py +1 -3
  120. package/python/foxclaw/finance/graph.py +9 -18
  121. package/python/foxclaw/finance/models.py +20 -24
  122. package/python/foxclaw/finance/nlparse.py +12 -72
  123. package/python/foxclaw/finance/policy.py +8 -36
  124. package/python/foxclaw/finance/reasoner.py +3 -11
  125. package/python/foxclaw/finance/reasoning.py +13 -34
  126. package/python/foxclaw/finance/reconcile.py +5 -10
  127. package/python/foxclaw/finance/runtime.py +19 -46
  128. package/python/foxclaw/governance/middleware.py +13 -21
  129. package/python/foxclaw/harness/_base.py +2 -8
  130. package/python/foxclaw/harness/_policy.py +19 -38
  131. package/python/foxclaw/harness/drift.py +1 -13
  132. package/python/foxclaw/harness/event_bus.py +2 -5
  133. package/python/foxclaw/harness/harness.py +9 -97
  134. package/python/foxclaw/harness/loop_detection.py +11 -18
  135. package/python/foxclaw/itinerary/amap_client.py +2 -5
  136. package/python/foxclaw/itinerary/models.py +7 -7
  137. package/python/foxclaw/itinerary/reasoner.py +1 -6
  138. package/python/foxclaw/itinerary/reasoning.py +15 -21
  139. package/python/foxclaw/itinerary/runtime.py +8 -12
  140. package/python/foxclaw/kernel/capabilities.py +5 -6
  141. package/python/foxclaw/kernel/policy_gate.py +4 -10
  142. package/python/foxclaw/kernel/process_manager.py +17 -36
  143. package/python/foxclaw/kernel/scheduler.py +3 -9
  144. package/python/foxclaw/kernel/sentinel.py +13 -23
  145. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  146. package/python/foxclaw/kernel/spine.py +9 -9
  147. package/python/foxclaw/knowledge/__init__.py +4 -20
  148. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  149. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  150. package/python/foxclaw/learning/models.py +13 -13
  151. package/python/foxclaw/learning/reasoner.py +1 -4
  152. package/python/foxclaw/learning/runtime.py +19 -31
  153. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  154. package/python/foxclaw/mcp_server.py +2 -3
  155. package/python/foxclaw/orchestration/__init__.py +16 -33
  156. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  157. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  158. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  159. package/python/foxclaw/planning/llm_planner.py +3 -3
  160. package/python/foxclaw/planning/planner.py +6 -11
  161. package/python/foxclaw/search/agent_port.py +7 -11
  162. package/python/foxclaw/search/evidence.py +0 -4
  163. package/python/foxclaw/search/internal_search.py +3 -5
  164. package/python/foxclaw/search/models.py +11 -13
  165. package/python/foxclaw/search/planner.py +3 -3
  166. package/python/foxclaw/search/retriever.py +1 -1
  167. package/python/foxclaw/search/runtime.py +1 -8
  168. package/python/foxclaw/skills/__init__.py +46 -100
  169. package/python/foxclaw/skills/archive.py +111 -0
  170. package/python/foxclaw/skills/fetch.py +226 -0
  171. package/python/foxclaw/skills/hooks_spec.py +21 -25
  172. package/python/foxclaw/skills/install_cmd.py +216 -0
  173. package/python/foxclaw/skills/lifecycle.py +16 -42
  174. package/python/foxclaw/skills/markdown_skill.py +10 -9
  175. package/python/foxclaw/skills/mcp_client.py +5 -11
  176. package/python/foxclaw/skills/mcp_store.py +0 -3
  177. package/python/foxclaw/skills/permission.py +13 -48
  178. package/python/foxclaw/skills/pipeline.py +21 -53
  179. package/python/foxclaw/skills/policy.py +4 -10
  180. package/python/foxclaw/skills/review.py +3 -3
  181. package/python/frontend/deck/auth.js +0 -7
  182. package/python/frontend/deck/chat_model_picker.js +0 -54
  183. package/python/frontend/deck/domain.js +0 -20
  184. package/python/frontend/deck/main.js +0 -144
  185. package/python/frontend/deck/markdown.js +0 -4
  186. package/python/frontend/deck/settings.js +1 -18
  187. package/python/frontend/deck/state.js +0 -1
  188. package/python/frontend/deck/util.js +0 -2
  189. package/python/frontend/deck/visuals.js +0 -22
  190. package/python/frontend/planet/planet.js +18 -201
  191. package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
  192. package/python/frontend/v3/assets/index-tN90dpUH.js +34 -0
  193. package/python/frontend/v3/index.html +3 -3
  194. package/python/frontend/widget_runtime.js +0 -43
  195. package/python/frontend/widgets.js +0 -57
  196. package/python/frontend_src/index.html +1 -1
  197. package/python/frontend_src/package-lock.json +2 -2
  198. package/python/frontend_src/package.json +1 -1
  199. package/python/frontend_src/src/App.vue +3 -7
  200. package/python/frontend_src/src/api.js +0 -7
  201. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  202. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  203. package/python/frontend_src/src/components/Inspector.vue +0 -6
  204. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  205. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  206. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  207. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  208. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  209. package/python/frontend_src/src/components/Stage.vue +71 -25
  210. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  211. package/python/frontend_src/src/components/TopBar.vue +38 -10
  212. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  213. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  214. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  215. package/python/frontend_src/src/components/panels/MemoryPanel.vue +42 -3
  216. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  217. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  218. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  219. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  220. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  221. package/python/frontend_src/src/lib/fmt.js +0 -4
  222. package/python/frontend_src/src/lib/i18n.js +53 -0
  223. package/python/frontend_src/src/lib/locales/en.js +173 -0
  224. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  225. package/python/frontend_src/src/lib/md.js +0 -14
  226. package/python/frontend_src/src/main.js +2 -0
  227. package/python/frontend_src/src/stores/app.js +17 -32
  228. package/python/frontend_src/src/stores/chat.js +0 -34
  229. package/python/frontend_src/src/stores/model.js +0 -8
  230. package/python/frontend_src/src/stores/panels.js +16 -44
  231. package/python/frontend_src/src/stores/profile.js +31 -0
  232. package/python/frontend_src/src/stores/system.js +25 -0
  233. package/python/frontend_src/vite.config.js +0 -7
  234. package/python/pyproject.toml +17 -3
  235. package/python/requirements.txt +311 -0
  236. package/python/tests/conftest.py +0 -22
  237. package/python/tests/run_all.py +4 -66
  238. package/python/tests/test_agent_lifecycle.py +4 -38
  239. package/python/tests/test_agent_rules.py +81 -0
  240. package/python/tests/test_ahs_p2.py +4 -25
  241. package/python/tests/test_anthropic_tools.py +1 -15
  242. package/python/tests/test_api.py +19 -9
  243. package/python/tests/test_api_auth.py +1 -5
  244. package/python/tests/test_attachments.py +9 -25
  245. package/python/tests/test_audit_chain.py +2 -7
  246. package/python/tests/test_audit_sink.py +0 -6
  247. package/python/tests/test_behavior_spec.py +198 -0
  248. package/python/tests/test_browser.py +2 -6
  249. package/python/tests/test_browser_request_guard.py +0 -4
  250. package/python/tests/test_capabilities.py +0 -6
  251. package/python/tests/test_channel_signing.py +2 -9
  252. package/python/tests/test_channels.py +1 -11
  253. package/python/tests/test_config_layering.py +0 -4
  254. package/python/tests/test_confirm_nonce.py +1 -12
  255. package/python/tests/test_conflict_dismiss.py +143 -0
  256. package/python/tests/test_content_guard.py +177 -0
  257. package/python/tests/test_context_compaction_wiring.py +1 -7
  258. package/python/tests/test_dispatch.py +0 -6
  259. package/python/tests/test_domain_evals.py +11 -80
  260. package/python/tests/test_domain_import.py +0 -6
  261. package/python/tests/test_domain_reasoners.py +0 -14
  262. package/python/tests/test_e2e.py +0 -17
  263. package/python/tests/test_emb_cache.py +7 -17
  264. package/python/tests/test_embedding_singleton.py +1 -3
  265. package/python/tests/test_evals_smoke.py +0 -24
  266. package/python/tests/test_event_spine.py +10 -18
  267. package/python/tests/test_evidence_semantic.py +1 -14
  268. package/python/tests/test_evidence_sse.py +3 -6
  269. package/python/tests/test_file_ingest.py +0 -11
  270. package/python/tests/test_finance.py +0 -9
  271. package/python/tests/test_finance_ledger.py +5 -12
  272. package/python/tests/test_finance_persist.py +8 -12
  273. package/python/tests/test_gateway.py +3 -22
  274. package/python/tests/test_governance.py +5 -52
  275. package/python/tests/test_governance_audit.py +4 -39
  276. package/python/tests/test_governance_middleware.py +1 -7
  277. package/python/tests/test_hkc.py +6 -24
  278. package/python/tests/test_hkc_index_build_manager.py +0 -33
  279. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  280. package/python/tests/test_hkc_search_event_sync.py +3 -54
  281. package/python/tests/test_import_path_guard.py +0 -6
  282. package/python/tests/test_injection_guard.py +18 -36
  283. package/python/tests/test_integration.py +5 -29
  284. package/python/tests/test_itinerary.py +0 -3
  285. package/python/tests/test_kee_semantic.py +0 -5
  286. package/python/tests/test_kernel.py +1 -11
  287. package/python/tests/test_learning.py +3 -12
  288. package/python/tests/test_llm_admin.py +4 -19
  289. package/python/tests/test_llm_planner.py +1 -23
  290. package/python/tests/test_markdown_skills.py +3 -14
  291. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  292. package/python/tests/test_mcp_connectors.py +3 -15
  293. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  294. package/python/tests/test_mcp_no_memory.py +3 -3
  295. package/python/tests/test_mcp_policy_gate.py +0 -1
  296. package/python/tests/test_mcp_server.py +6 -9
  297. package/python/tests/test_memory_recall.py +1 -4
  298. package/python/tests/test_metrics.py +0 -8
  299. package/python/tests/test_multi_chain.py +1 -13
  300. package/python/tests/test_multimodal.py +5 -18
  301. package/python/tests/test_negotiation.py +3 -13
  302. package/python/tests/test_orchestration.py +1 -11
  303. package/python/tests/test_orchestration_decoupled.py +0 -6
  304. package/python/tests/test_p3_p5_hardening.py +3 -17
  305. package/python/tests/test_parallel_dispatch.py +4 -12
  306. package/python/tests/test_pipeline.py +17 -58
  307. package/python/tests/test_plan_visible.py +4 -4
  308. package/python/tests/test_planner.py +0 -7
  309. package/python/tests/test_policy_gate.py +2 -3
  310. package/python/tests/test_profile.py +67 -0
  311. package/python/tests/test_readme_claims.py +163 -0
  312. package/python/tests/test_reasoner.py +2 -17
  313. package/python/tests/test_release_check.py +2 -13
  314. package/python/tests/test_resume_checkpoint.py +7 -15
  315. package/python/tests/test_routing.py +2 -12
  316. package/python/tests/test_scheduler.py +7 -10
  317. package/python/tests/test_search.py +1 -11
  318. package/python/tests/test_secrets.py +4 -10
  319. package/python/tests/test_semantic.py +1 -10
  320. package/python/tests/test_sensitive_write.py +181 -0
  321. package/python/tests/test_sessions.py +3 -4
  322. package/python/tests/test_settings.py +2 -18
  323. package/python/tests/test_shadow_learning.py +3 -43
  324. package/python/tests/test_shared_write.py +0 -7
  325. package/python/tests/test_skill_taint_gate.py +1 -10
  326. package/python/tests/test_skills.py +2 -18
  327. package/python/tests/test_skills_fetch.py +282 -0
  328. package/python/tests/test_stats_and_events.py +2 -5
  329. package/python/tests/test_streaming.py +2 -7
  330. package/python/tests/test_supervisor_pipeline.py +4 -5
  331. package/python/tests/test_supervisor_triage.py +4 -10
  332. package/python/tests/test_taint.py +2 -7
  333. package/python/tests/test_task_stream.py +1 -12
  334. package/python/tests/test_thinking_sse.py +0 -1
  335. package/python/tests/test_tool_center.py +1 -17
  336. package/python/tests/test_tool_parallel.py +2 -2
  337. package/python/tests/test_tool_params.py +1 -5
  338. package/python/tests/test_tool_use.py +0 -13
  339. package/python/tests/test_toplevel_concurrency.py +3 -4
  340. package/python/tests/test_tracing.py +7 -41
  341. package/python/tests/test_truth_crystallizer.py +4 -6
  342. package/python/tests/test_truth_flow.py +2 -7
  343. package/python/tests/test_update_check.py +95 -0
  344. package/python/tests/test_verify_evidence.py +8 -21
  345. package/python/tests/test_version_sync.py +35 -6
  346. package/python/tests/test_web_trigger.py +0 -17
  347. package/python/tools/build_content_lexicon.py +218 -0
  348. package/python/tools/bundle_web.py +98 -0
  349. package/python/tools/check_faiss_type.py +0 -1
  350. package/python/tools/check_log.ps1 +0 -8
  351. package/python/tools/mypy_baseline.txt +12 -4
  352. package/python/tools/mypy_ratchet.py +52 -6
  353. package/python/tools/pack.py +0 -5
  354. package/python/tools/recheck_conflicts.py +156 -0
  355. package/python/tools/release_check.py +24 -15
  356. package/python/tools/restart_foxclaw.ps1 +5 -8
  357. package/python/tools/shared_write_audit.py +1 -15
  358. package/python/tools/size_check.py +1 -1
  359. package/python/tools/strip_comments.py +228 -0
  360. package/python/vendor/hkc/hkc_api/routes/ability.py +31 -0
  361. package/python/vendor/hkc/hkc_api/schemas.py +11 -0
  362. package/python/vendor/hkc/hkc_kee/kee.py +124 -3
  363. package/python/vendor/hkc/hkc_sdk/base.py +22 -0
  364. package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
  365. package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
  366. package/python/foxclaw/api/app.py +0 -123
  367. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  368. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -29,7 +29,7 @@ class _PriorityAdmission:
29
29
  self.max = max_slots
30
30
  self._free = max_slots or 0
31
31
  self._cv = threading.Condition()
32
- self._heap: list = [] # (-priority, seq):优先级降序,同优先级 FIFO
32
+ self._heap: list = []
33
33
  self._seq = 0
34
34
 
35
35
  def acquire(self, priority: int = 0) -> None:
@@ -43,7 +43,7 @@ class _PriorityAdmission:
43
43
  self._cv.wait()
44
44
  heapq.heappop(self._heap)
45
45
  self._free -= 1
46
- if self._free > 0 and self._heap: # 还有空槽+等待者 → 唤醒下一个最高优先者
46
+ if self._free > 0 and self._heap:
47
47
  self._cv.notify_all()
48
48
 
49
49
  def release(self) -> None:
@@ -58,11 +58,8 @@ class Scheduler:
58
58
  def __init__(self, max_concurrency: Optional[int] = None, spine=None,
59
59
  process_manager=None):
60
60
  self.max_concurrency = max_concurrency if (max_concurrency and max_concurrency > 0) else None
61
- # Step5:优先级准入(满载时高优先排队者先获空位;不抢占正在跑的)。无上限时零等待。
62
61
  self._adm = _PriorityAdmission(self.max_concurrency)
63
62
  self._spine = spine
64
- # 【Step3】统一生命周期:有 ProcessManager 则委托它发 task.lifecycle.*(唯一词汇、
65
- # 唯一状态机);无 pm(standalone/测试)则调度器自发 task.lifecycle.* 作回退。
66
63
  self.process_manager = process_manager
67
64
  self._lock = threading.Lock()
68
65
  self._stats = {"submitted": 0, "running": 0, "completed": 0,
@@ -80,7 +77,6 @@ class Scheduler:
80
77
  proc = pm.create(task, agent_id=agent_id, schedule={"priority": priority})
81
78
  else:
82
79
  self._emit("scheduled", agent_id, task, priority)
83
- # 优先级准入:满载则排队等空位(高优先先入);无上限时零等待(behavior-preserving)
84
80
  self._adm.acquire(priority)
85
81
  try:
86
82
  with self._lock:
@@ -90,8 +86,6 @@ class Scheduler:
90
86
  if proc is not None:
91
87
  pm.mark_running(proc.process_id)
92
88
  else:
93
- # 【P5-2】回退路径的词汇与 ProcessManager 对齐:started → running
94
- # (pm 的词汇是 created/queued/scheduled/running/completed/failed)
95
89
  self._emit("running", agent_id, task, priority)
96
90
  result = fn()
97
91
  with self._lock:
@@ -116,7 +110,7 @@ class Scheduler:
116
110
 
117
111
  def stats(self) -> dict:
118
112
  with self._lock:
119
- s = dict(self._stats)
113
+ s: dict[str, int | None] = dict(self._stats)
120
114
  s["max_concurrency"] = self.max_concurrency
121
115
  return s
122
116
 
@@ -23,12 +23,9 @@ import secrets
23
23
  import sys
24
24
  import time
25
25
  from pathlib import Path
26
+ from typing import Any
26
27
 
27
28
 
28
- # ── 凭证保险箱(vault)────────────────────────────────────────
29
- # 真实凭证只进守门人进程,主进程内存里从不出现。两个来源:
30
- # 1. 环境变量前缀 FOXCLAW_GUARDIAN_SECRET_<REF>(REF 统一小写);
31
- # 2. FOXCLAW_GUARDIAN_SECRETS 指向的 JSON 文件 {"ref": "secret", ...}。
32
29
  def load_vault() -> dict:
33
30
  vault: dict = {}
34
31
  prefix = "FOXCLAW_GUARDIAN_SECRET_"
@@ -42,13 +39,10 @@ def load_vault() -> dict:
42
39
  if isinstance(d, dict):
43
40
  vault.update({str(k).lower(): v for k, v in d.items()})
44
41
  except Exception:
45
- pass # 损坏的 secrets 文件不拖垮守门人;宁可少借不错借
42
+ pass
46
43
  return vault
47
44
 
48
45
 
49
- # ── MCP server 配置(守门人持有凭证)──────────────────────────
50
- # 与 vault 对称:MCP 的 headers 里可能含真凭证(Authorization/x-api-key 等)。
51
- # 主进程通过 mcp_register 推送配置;也可用 FOXCLAW_GUARDIAN_MCP_SERVERS 指向 JSON 文件做初始加载。
52
46
  def _mcp_servers_path() -> str:
53
47
  """守门人侧 MCP 配置持久化文件。优先 FOXCLAW_GUARDIAN_MCP_SERVERS 显式指定;
54
48
  否则用 FOXCLAW_GUARDIAN_DATA_DIR 下的 mcp_guardian_servers.json。主进程的 mcp_servers.json 已不含 headers。"""
@@ -79,7 +73,7 @@ def load_mcp_servers() -> dict:
79
73
  "transport": c.get("transport") or "streamable_http",
80
74
  }
81
75
  except Exception:
82
- pass # 损坏配置不拖垮守门人;宁可少配不错配
76
+ pass
83
77
  return servers
84
78
 
85
79
 
@@ -94,18 +88,15 @@ def _persist_mcp_servers(servers: dict) -> None:
94
88
  tmp.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
95
89
  tmp.replace(Path(path))
96
90
  except Exception:
97
- pass # 持久化失败不拖垮守门人;重启后主进程会重新 mcp_register
91
+ pass
98
92
 
99
93
 
100
- # ── 短期可撤销能力令牌 ──────────────────────────────────────
101
- # 彻底取代「resolve 真值借出」:主进程只拿得到一个不透明随机 token,真值永不出守门人进程。
102
- # 令牌 = 一次性 + 过期 + 可撤销,进程内状态表(不依赖 HMAC 签名,因为 token 本身高熵、无法伪造/猜中)。
103
94
  class TokenStore:
104
95
  def __init__(self, default_ttl: float = 60.0):
105
96
  self._default_ttl = default_ttl
106
97
  self._tokens: dict = {}
107
98
 
108
- def mint(self, ref: str, ttl: float = None) -> dict:
99
+ def mint(self, ref: str, ttl: float | None = None) -> dict:
109
100
  """为某 ref 签发能力令牌。返回 {token, expires_in},绝不返回真值。"""
110
101
  ttl = ttl if ttl is not None else self._default_ttl
111
102
  token = secrets.token_urlsafe(32)
@@ -137,7 +128,6 @@ class TokenStore:
137
128
  return True
138
129
 
139
130
 
140
- # ── 跨进程重建(守门人侧)─────────────────────────────────────
141
131
  def _rebuild_skill(sd: dict):
142
132
  """主进程只传 evaluate 需要的字段 + 已算好的 trust。重建为鸭子类型对象。"""
143
133
  from types import SimpleNamespace
@@ -145,7 +135,7 @@ def _rebuild_skill(sd: dict):
145
135
  return SimpleNamespace(
146
136
  id=sd.get("id", ""),
147
137
  kind=sd.get("kind", "local"),
148
- side_effect=sd.get("side_effect", "write"), # 未声明 → 有副作用
138
+ side_effect=sd.get("side_effect", "write"),
149
139
  destructive=bool(sd.get("destructive", False)),
150
140
  external=bool(sd.get("external", False)),
151
141
  read_only=sd.get("read_only", None),
@@ -187,7 +177,7 @@ def _handle_mint(msg: dict, vault: dict, store: TokenStore) -> dict:
187
177
  ttl = float(ttl) if ttl else None
188
178
  out = store.mint(ref, ttl)
189
179
  print(f"[Sentinel] mint {ref}", file=sys.stderr, flush=True)
190
- return out # {"token", "expires_in"} —— 不含真值
180
+ return out
191
181
 
192
182
 
193
183
  def _handle_revoke(msg: dict, store: TokenStore) -> dict:
@@ -212,7 +202,7 @@ def _handle_mcp_register(msg: dict, servers: dict) -> dict:
212
202
  headers = dict(c.get("headers") or {})
213
203
  old = servers.get(sid)
214
204
  if not headers and old:
215
- headers = dict(old.get("headers") or {}) # 主进程不持 headers,不覆盖守门人已有凭证
205
+ headers = dict(old.get("headers") or {})
216
206
  new_servers[sid] = {
217
207
  "name": c.get("name") or sid,
218
208
  "url": c.get("url", ""),
@@ -251,7 +241,7 @@ def _handle_mcp_call(msg: dict, servers: dict) -> dict:
251
241
  if out.get("isError"):
252
242
  return SkillResult(skill_id, False, status="blocked",
253
243
  message=f"MCP 工具返回错误: {out.get('content') or 'unknown'}").to_dict()
254
- out = _mark_untrusted(out) # 外部 MCP 返回=不可信,防提示注入
244
+ out = _mark_untrusted(out)
255
245
  print(f"[Sentinel] mcp_call {server_id}.{tool}", file=sys.stderr, flush=True)
256
246
  return SkillResult(skill_id, True, output=out,
257
247
  message="MCP 调用完成(外部不可信数据)").to_dict()
@@ -329,7 +319,7 @@ def _handle_execute_web(msg: dict, vault: dict, store: TokenStore) -> dict:
329
319
  secret = None
330
320
  used_ref = None
331
321
  if token:
332
- ref = store.redeem(token) # 一次性:兑换即作废
322
+ ref = store.redeem(token)
333
323
  if ref is None:
334
324
  return SkillResult(skill_id, False, status="blocked",
335
325
  message="能力令牌无效/已过期/已使用/已撤销").to_dict()
@@ -345,7 +335,6 @@ def _handle_execute_web(msg: dict, vault: dict, store: TokenStore) -> dict:
345
335
  return SkillResult(skill_id, False, status="blocked",
346
336
  message=f"凭证 ref「{secret_ref}」未在守门人 vault 中配置").to_dict()
347
337
 
348
- # SSRF 校验:守门人侧也做(主进程可能被攻破传内网地址,不能只信主进程)
349
338
  block = ssrf_check(endpoint, allow_local=allow_local)
350
339
  if block:
351
340
  return SkillResult(skill_id, False, status="blocked",
@@ -364,7 +353,7 @@ def _handle_execute_web(msg: dict, vault: dict, store: TokenStore) -> dict:
364
353
  return SkillResult(skill_id, False, status="blocked",
365
354
  message="缺少 httpx 依赖,无法发起 Web API 调用").to_dict()
366
355
 
367
- req_kwargs = {"headers": headers, "timeout": timeout, "follow_redirects": False}
356
+ req_kwargs: dict[str, Any] = {"headers": headers, "timeout": timeout, "follow_redirects": False}
368
357
  if loc == "query":
369
358
  req_kwargs["params"] = args
370
359
  else:
@@ -416,6 +405,7 @@ def serve_stdio(vault: dict, store: TokenStore, servers: dict) -> None:
416
405
  mid = msg.get("id")
417
406
  op = msg.get("op")
418
407
  try:
408
+ result: dict[str, object]
419
409
  if op == "ping":
420
410
  result = {"pong": True}
421
411
  elif op == "evaluate":
@@ -438,7 +428,7 @@ def serve_stdio(vault: dict, store: TokenStore, servers: dict) -> None:
438
428
  else:
439
429
  result = {"error": f"unknown op: {op}"}
440
430
  _reply({"id": mid, "ok": True, "result": result})
441
- except Exception as e: # 单条请求失败不影响守门人存活
431
+ except Exception as e:
442
432
  _reply({"id": mid, "ok": False, "error": str(e)})
443
433
 
444
434
 
@@ -22,20 +22,18 @@ class SentinelUnavailable(RuntimeError):
22
22
 
23
23
 
24
24
  def _project_root() -> str:
25
- # foxclaw/kernel/sentinel_client.py → parents[0]=kernel, [1]=foxclaw, [2]=Foxclaw
26
25
  return str(Path(__file__).resolve().parents[2])
27
26
 
28
27
 
29
28
  class SentinelClient:
30
- def __init__(self, timeout: float = 5.0, cmd=None, data_dir: str = None):
29
+ def __init__(self, timeout: float = 5.0, cmd=None, data_dir: str | None = None):
31
30
  self._timeout = timeout
32
31
  self._data_dir = data_dir
33
32
  self._lock = threading.Lock()
34
33
  self._req_id = 0
35
- self._proc = None
34
+ self._proc: subprocess.Popen[str] | None = None
36
35
  self._spawn(cmd)
37
36
 
38
- # ── 进程生命周期 ─────────────────────────────────────────
39
37
  def _spawn(self, cmd) -> None:
40
38
  if cmd is None:
41
39
  cmd = [sys.executable, "-m", "foxclaw.kernel.sentinel"]
@@ -50,7 +48,7 @@ class SentinelClient:
50
48
  except Exception as e:
51
49
  raise RuntimeError(f"Sentinel 启动失败: {e}")
52
50
  try:
53
- self._request("ping", {}) # 握手:启动失败/坏路径在此显式报错,不静默降级
51
+ self._request("ping", {})
54
52
  except Exception as e:
55
53
  self._kill()
56
54
  raise RuntimeError(f"Sentinel 握手失败: {e}")
@@ -73,16 +71,18 @@ class SentinelClient:
73
71
  except Exception:
74
72
  pass
75
73
 
76
- # ── IPC 核心 ─────────────────────────────────────────────
77
74
  def _request(self, op: str, payload: dict):
78
75
  with self._lock:
79
76
  self._req_id += 1
80
77
  mid = self._req_id
81
78
  msg = json.dumps({"id": mid, "op": op, **payload}, ensure_ascii=False)
82
79
  try:
83
- self._proc.stdin.write(msg + "\n")
84
- self._proc.stdin.flush()
85
- line = self._proc.stdout.readline()
80
+ proc = self._proc
81
+ if proc is None or proc.stdin is None or proc.stdout is None:
82
+ raise SentinelUnavailable("守门人进程未启动")
83
+ proc.stdin.write(msg + "\n")
84
+ proc.stdin.flush()
85
+ line = proc.stdout.readline()
86
86
  except Exception as e:
87
87
  raise SentinelUnavailable(f"IPC 通信失败: {e}")
88
88
  if not line:
@@ -95,7 +95,6 @@ class SentinelClient:
95
95
  raise SentinelUnavailable(resp.get("error", "unknown error"))
96
96
  return resp.get("result")
97
97
 
98
- # ── 序列化(只传 evaluate 需要的字段,不含 handler/凭证)────
99
98
  @staticmethod
100
99
  def _serialize_skill(skill) -> dict:
101
100
  _tl = getattr(skill, "trust_level", None)
@@ -106,7 +105,7 @@ class SentinelClient:
106
105
  return {
107
106
  "id": getattr(skill, "id", ""),
108
107
  "kind": getattr(skill, "kind", "local"),
109
- "side_effect": getattr(skill, "side_effect", "write"), # 未声明 → 有副作用
108
+ "side_effect": getattr(skill, "side_effect", "write"),
110
109
  "destructive": bool(getattr(skill, "destructive", False)),
111
110
  "external": bool(getattr(skill, "external", False)),
112
111
  "read_only": getattr(skill, "read_only", None),
@@ -124,7 +123,6 @@ class SentinelClient:
124
123
  "allow": list(getattr(rules, "allow", []) or []),
125
124
  }
126
125
 
127
- # ── 对外接口 ─────────────────────────────────────────────
128
126
  def evaluate(self, skill, args, rules=None, confirmed=False, mode="strict",
129
127
  taint_level=0, taint_threshold=0):
130
128
  """动作裁决(跨进程)。签名对齐 skills.permission.evaluate,返回 PermissionDecision。"""
@@ -141,7 +139,6 @@ class SentinelClient:
141
139
  try:
142
140
  d = self._request("evaluate", payload)
143
141
  except SentinelUnavailable as e:
144
- # fail-closed:守门人不可达 → deny,绝不降级放行
145
142
  return PermissionDecision("deny", 0,
146
143
  reason=f"守门人不可达(fail-closed): {e}",
147
144
  reason_code="SENTINEL_UNAVAILABLE")
@@ -154,9 +151,9 @@ class SentinelClient:
154
151
  reason_code=d.get("reason_code", ""),
155
152
  )
156
153
 
157
- def mint(self, ref: str, ttl: float = None):
154
+ def mint(self, ref: str, ttl: float | None = None):
158
155
  """为 ref 签发一次性能力令牌。返回 token(绝不返回真值);失败返回 None。"""
159
- payload = {"ref": ref}
156
+ payload: dict[str, object] = {"ref": ref}
160
157
  if ttl is not None:
161
158
  payload["ttl"] = ttl
162
159
  try:
@@ -32,7 +32,6 @@ class Event:
32
32
  source: str = ""
33
33
  correlation_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
34
34
 
35
- # ── 与旧 BusMessage 同形,便于 SystemBus 无痛门面化 ──
36
35
  @property
37
36
  def channel(self) -> str:
38
37
  return self.topic
@@ -46,8 +45,8 @@ class EventSpine:
46
45
  """进程内唯一事件脊柱。发布/订阅(前缀匹配) + 投影 + 有界历史。线程安全。"""
47
46
 
48
47
  def __init__(self, history_limit: int = 1000):
49
- self._subs: list[tuple[str, Callable]] = [] # (topic_prefix, handler)
50
- self._projections: list[Callable] = [] # 每条事件都收到
48
+ self._subs: list[tuple[str, Callable]] = []
49
+ self._projections: list[Callable] = []
51
50
  self._history: deque = deque(maxlen=history_limit)
52
51
  self._lock = threading.RLock()
53
52
 
@@ -61,13 +60,15 @@ class EventSpine:
61
60
  with self._lock:
62
61
  self._projections.append(sink)
63
62
 
64
- def publish(self, topic: str, payload: dict = None, source: str = "",
65
- correlation_id: str = None) -> Event:
66
- evt = Event(topic=topic, payload=payload or {}, source=source,
67
- **({"correlation_id": correlation_id} if correlation_id else {}))
63
+ def publish(self, topic: str, payload: dict | None = None, source: str = "",
64
+ correlation_id: str | None = None) -> Event:
65
+ if correlation_id:
66
+ evt = Event(topic=topic, payload=payload or {}, source=source,
67
+ correlation_id=correlation_id)
68
+ else:
69
+ evt = Event(topic=topic, payload=payload or {}, source=source)
68
70
  with self._lock:
69
71
  self._history.append(evt)
70
- # 锁内只做"快照",handler/sink 在锁外执行(避免与 pm/hmr 锁形成环)
71
72
  handlers = [h for pfx, h in self._subs if topic.startswith(pfx)]
72
73
  projections = list(self._projections)
73
74
  for h in handlers:
@@ -90,7 +91,6 @@ class EventSpine:
90
91
 
91
92
  def stats(self) -> dict:
92
93
  with self._lock:
93
- # 【P4-2】补 history_limit(deque.maxlen):只报 history_size 无法判断「是否已满」。
94
94
  return {"subscriptions": len(self._subs),
95
95
  "projections": len(self._projections),
96
96
  "history_size": len(self._history),
@@ -64,7 +64,6 @@ def _read_custom_model(data_dir, name):
64
64
  if not match:
65
65
  return None
66
66
  key = ""
67
- # OS 凭据库优先,回退 base64(旧记录)/ 更早的明文字段
68
67
  from ..core.secrets import load as _load_secret
69
68
  key = _load_secret(name, match.get("api_key_obf") or "")
70
69
  key = key or (match.get("api_key") or "")
@@ -101,7 +100,6 @@ def _selected_llm():
101
100
  return None
102
101
 
103
102
  source = (sel.get("source") or "").strip().lower()
104
- # preset 落盘的是 provider;custom 落盘的是 name。两者都当"选中的名字"用。
105
103
  name = (sel.get("name") or sel.get("provider") or "").strip()
106
104
  model = (sel.get("model_id") or sel.get("model") or "").strip()
107
105
 
@@ -113,7 +111,6 @@ def _selected_llm():
113
111
  return _to_hkc_provider("generic"), key, base, model or custom_model
114
112
 
115
113
  if source == "preset" and name:
116
- # 界面里配过的同名自定义项(更新的 key)优先,env 只作最后兜底
117
114
  got = _read_custom_model(data_dir, name)
118
115
  if got:
119
116
  key, base, custom_model = got
@@ -144,7 +141,6 @@ def _detect_llm(api_key=None):
144
141
 
145
142
  返回 ``(provider, key, base_url, model)``。
146
143
  """
147
- # 1. 显式 HKC_LLM_PROVIDER 覆盖(向后兼容「HKC 独立配」场景)
148
144
  env_provider = (os.environ.get("HKC_LLM_PROVIDER") or "").strip().lower()
149
145
  if env_provider in ("anthropic", "deepseek", "openai", "openai-compatible"):
150
146
  key = (os.environ.get("HKC_LLM_API_KEY")
@@ -158,15 +154,11 @@ def _detect_llm(api_key=None):
158
154
  or "")
159
155
  return env_provider, key or api_key, base, model
160
156
 
161
- # 2. 界面里当前选中的模型(用户显式动作,优先于 env 默认)
162
157
  selected = _selected_llm()
163
158
  if selected:
164
159
  provider, key, base, model = selected
165
160
  return provider, (key or api_key or "").strip(), (base or "").strip(), model
166
161
 
167
- # 3. 单入口 env。未显式指定 provider 时**复用主对话的自动探测**,
168
- # 不能默认成 "generic" —— 那是个空 key 的壳,会让抽取打 api.openai.com
169
- # 拿 Missing credentials,每次摄入都 0 条。
170
162
  provider = (os.environ.get("LLM_PROVIDER") or "").strip().lower()
171
163
  key = (os.environ.get("LLM_API_KEY") or api_key or "").strip()
172
164
  base = os.environ.get("LLM_BASE_URL", "").strip()
@@ -194,7 +186,7 @@ class HKCService:
194
186
 
195
187
  def __init__(self, container, client):
196
188
  self._container = container
197
- self.client = client # DirectClient;供 HSR 检索 / /api/knowledge 路由使用
189
+ self.client = client
198
190
 
199
191
  @property
200
192
  def available(self) -> bool:
@@ -245,22 +237,18 @@ def init_hkc(data_dir, api_key=None):
245
237
  return None
246
238
  try:
247
239
  provider, llm_key, llm_base_url, llm_model = _detect_llm(api_key)
248
- # 【P2·单 LLM 入口】embedding 模型名优先 EMBEDDING_MODEL(系统唯一本地入口),
249
- # 再 HKC_EMBEDDING_MODEL(向后兼容);其他 HKC 行为开关不变。
250
240
  _emb_model = (os.environ.get("EMBEDDING_MODEL")
251
241
  or os.environ.get("HKC_EMBEDDING_MODEL")) or None
252
242
  container = HKCContainer(
253
243
  data_dir=str(Path(data_dir) / "hkc"),
254
- embedding_kind=os.environ.get("HKC_EMBEDDING", "auto"), # 默认真语义(本地优先,无 ST 回退 stub)
244
+ embedding_kind=os.environ.get("HKC_EMBEDDING", "auto"),
255
245
  embedding_url=os.environ.get("HKC_EMBEDDING_URL", "http://localhost:8080"),
256
- embedding_model=_emb_model, # local/auto 模型名(如 bge-m3)
246
+ embedding_model=_emb_model,
257
247
  llm_api_key=llm_key or None,
258
248
  llm_provider=provider,
259
249
  llm_model=llm_model or None,
260
250
  llm_base_url=llm_base_url or None,
261
251
  ).assemble()
262
- # 注册为 hkc_api 全局单例:若将来挂 HKC 自身 /hkc REST 路由,
263
- # 其 Depends(get_container) 能取到这同一个容器,而非"未初始化"。
264
252
  from hkc_api import container as _cm
265
253
  _cm._container = container
266
254
  print(f"[Foxclaw] ✓ HKC 知识核心已进程内融合 (provider={provider}, "
@@ -275,8 +263,6 @@ def init_hkc(data_dir, api_key=None):
275
263
  from .truth_crystallizer import TruthCrystallizer
276
264
 
277
265
 
278
- # HKC 知识生命周期事件 → 桥接到 EventSpine 的类型。**刻意不含 memory.***(那是 HMR 域,
279
- # Foxclaw 已有自己的记忆事件,转发 HKC 的会重复/混淆)。
280
266
  _HKC_BRIDGED_EVENTS = (
281
267
  "knowledge.created", "knowledge.updated", "knowledge.merged", "knowledge.deleted",
282
268
  "conflict.detected", "conflict.resolved", "claim.status_changed",
@@ -351,7 +337,7 @@ def register_abilities_as_skills(registry, hkc) -> list:
351
337
  registered = []
352
338
  for key in keys:
353
339
  try:
354
- ab = client.compile_ability(key) # 覆盖不足抛异常 → 证据 gate 跳过、不注册
340
+ ab = client.compile_ability(key)
355
341
  except Exception:
356
342
  continue
357
343
  sid = f"ability:{key}"
@@ -363,8 +349,6 @@ def register_abilities_as_skills(registry, hkc) -> list:
363
349
  handler=_make_ability_handler(client, ab),
364
350
  params={"query": "str"}))
365
351
  registered.append(sid)
366
- # 【深审修复·陈旧清理】重新同步时,移除此前注册但**本轮不再达标**的 ability skill
367
- # (coverage 掉了/能力定义删了)——register-only 会残留陈旧项;prune 使 registry 反映当前覆盖。
368
352
  fresh = set(registered)
369
353
  for spec in registry.all():
370
354
  if spec.id.startswith("ability:") and spec.id not in fresh:
@@ -15,17 +15,17 @@ Step1(叠加、默认关、行为不变):
15
15
  from __future__ import annotations
16
16
  import threading
17
17
  from concurrent.futures import ThreadPoolExecutor
18
- from typing import Callable, Optional
18
+ from typing import Any, Callable, cast, Optional
19
19
 
20
20
 
21
21
  class TruthCrystallizer:
22
22
  def __init__(self, hkc_provider: Callable[[], object], enabled: bool = False):
23
- self._hkc_provider = hkc_provider # 惰性取 HKCService(或 None)
23
+ self._hkc_provider = hkc_provider
24
24
  self.enabled = enabled
25
25
  self._executor: Optional[ThreadPoolExecutor] = None
26
26
  self._closed = False
27
27
  self._lock = threading.Lock()
28
- self._pending: list = [] # 供测试 drain
28
+ self._pending: list = []
29
29
 
30
30
  def _ex(self) -> Optional[ThreadPoolExecutor]:
31
31
  with self._lock:
@@ -41,7 +41,7 @@ class TruthCrystallizer:
41
41
  if not self.enabled:
42
42
  return
43
43
  p = getattr(msg, "payload", None) or {}
44
- if p.get("blocked"): # 被拦截任务不结晶(与 TruthPipeline 一致)
44
+ if p.get("blocked"):
45
45
  return
46
46
  fact = ((p.get("truth") or {}).get("fact") or "").strip()
47
47
  if not fact:
@@ -52,16 +52,13 @@ class TruthCrystallizer:
52
52
  return
53
53
  fut = ex.submit(self._crystallize, fact, domain)
54
54
  with self._lock:
55
- # 【深审修复·长期运行泄漏】_pending 原先只增不减(drain 仅在 close/测试时调用),
56
- # 启用后每来一次 truth.appended 就永久累积一个已完成 Future → 无界内存泄漏。
57
- # 改为每次提交时 prune 掉已完成的,_pending 只保留在飞行中的任务(仍支持测试 drain)。
58
55
  self._pending = [f for f in self._pending if not f.done()]
59
56
  self._pending.append(fut)
60
57
 
61
58
  def _crystallize(self, fact: str, domain: str) -> bool:
62
59
  """后台执行:惰性取 HKC → ingest_text(KDE→KEE)。任何失败都吞掉,不影响主流程。"""
63
60
  try:
64
- hkc = self._hkc_provider()
61
+ hkc = cast(Any, self._hkc_provider())
65
62
  except Exception:
66
63
  hkc = None
67
64
  if hkc is None or not getattr(hkc, "available", False):
@@ -17,9 +17,9 @@ class KnowledgeGraph:
17
17
  DIM = 256
18
18
 
19
19
  def __init__(self, embedder=None):
20
- self.items: dict = {} # id -> KnowledgeItem
21
- self.edges: list = [] # KnowledgeEdge
22
- self._embedder = embedder # 可选:HMR 的向量能力
20
+ self.items: dict = {}
21
+ self.edges: list = []
22
+ self._embedder = embedder
23
23
 
24
24
  def add_item(self, item: KnowledgeItem):
25
25
  self.items[item.id] = item
@@ -30,7 +30,6 @@ class KnowledgeGraph:
30
30
  self.edges = [e for e in self.edges
31
31
  if e.source_id != item_id and e.target_id != item_id]
32
32
 
33
- # ── 三种连接 ──────────────────────────────────────────
34
33
  def link_manual(self, source_id: str, target_id: str, reason: str = ""):
35
34
  self.edges.append(KnowledgeEdge(
36
35
  source_id=source_id, target_id=target_id,
@@ -72,7 +71,6 @@ class KnowledgeGraph:
72
71
  new_edges += 1
73
72
  return new_edges
74
73
 
75
- # ── 图分析 ────────────────────────────────────────────
76
74
  def neighbors(self, item_id: str) -> list:
77
75
  """某知识点的所有关联点。"""
78
76
  out = []
@@ -105,7 +103,7 @@ class KnowledgeGraph:
105
103
  return result
106
104
 
107
105
  def stats(self) -> dict:
108
- type_count = defaultdict(int)
106
+ type_count: defaultdict[str, int] = defaultdict(int)
109
107
  for e in self.edges:
110
108
  type_count[e.edge_type] += 1
111
109
  return {
@@ -115,7 +113,6 @@ class KnowledgeGraph:
115
113
  "clusters": len(self.clusters()),
116
114
  }
117
115
 
118
- # ── 向量工具 ──────────────────────────────────────────
119
116
  def _embed(self, text: str):
120
117
  if self._embedder is not None:
121
118
  try:
@@ -138,8 +135,6 @@ class KnowledgeGraph:
138
135
 
139
136
  @staticmethod
140
137
  def _cosine(a, b) -> float:
141
- # 自己归一化,不假设输入已是单位向量:外部 embedder(如 HMR)返回的向量
142
- # 未必归一化,直接用点积当余弦会系统性偏高(threshold 失效,什么都连上)。
143
138
  import math
144
139
  dot = sum(x * y for x, y in zip(a, b))
145
140
  na = math.sqrt(sum(x * x for x in a))
@@ -13,17 +13,17 @@ import uuid
13
13
 
14
14
 
15
15
  EdgeType = Literal["same_topic", "concept_ref", "semantic", "manual"]
16
- Grade = Literal[0, 1, 2, 3, 4, 5] # SM-2 回忆质量评分 0=完全忘 5=完美
16
+ Grade = Literal[0, 1, 2, 3, 4, 5]
17
17
 
18
18
 
19
19
  @dataclass
20
20
  class KnowledgeItem:
21
21
  """一个知识条目 = 一次学习采集"""
22
- content: str # 知识点内容
23
- topic: str = "general" # 主题
24
- source: str = "" # 来源:书名/课程/文章/Readwise
22
+ content: str
23
+ topic: str = "general"
24
+ source: str = ""
25
25
  tags: list = field(default_factory=list)
26
- notes: list = field(default_factory=list) # 用户读时加的笔记 [{text, ts}]
26
+ notes: list = field(default_factory=list)
27
27
  created_at: str = field(default_factory=lambda: datetime.now().isoformat())
28
28
  id: str = field(default_factory=lambda: f"ki_{uuid.uuid4().hex[:10]}")
29
29
 
@@ -35,19 +35,19 @@ class KnowledgeItem:
35
35
  class ReviewState:
36
36
  """SM-2 间隔重复状态(固化层核心)"""
37
37
  item_id: str
38
- repetitions: int = 0 # 连续正确次数
39
- interval: int = 0 # 当前间隔(天)
40
- ease_factor: float = 2.5 # 难度因子(SM-2 起始 2.5)
38
+ repetitions: int = 0
39
+ interval: int = 0
40
+ ease_factor: float = 2.5
41
41
  due_date: str = field(default_factory=lambda: datetime.now().isoformat())
42
42
  last_review: Optional[str] = None
43
43
  total_reviews: int = 0
44
- lapses: int = 0 # 遗忘次数(评分<3)
44
+ lapses: int = 0
45
45
 
46
46
  @property
47
47
  def mastery(self) -> float:
48
48
  """掌握度 0~1:综合连续正确次数和难度因子。"""
49
- rep_score = min(self.repetitions / 6.0, 1.0) # 6 次连对算掌握
50
- ease_score = (self.ease_factor - 1.3) / (2.5 - 1.3) # 1.3~2.5 归一
49
+ rep_score = min(self.repetitions / 6.0, 1.0)
50
+ ease_score = (self.ease_factor - 1.3) / (2.5 - 1.3)
51
51
  ease_score = max(0.0, min(1.0, ease_score))
52
52
  return round(0.6 * rep_score + 0.4 * ease_score, 3)
53
53
 
@@ -73,8 +73,8 @@ class KnowledgeEdge:
73
73
  source_id: str
74
74
  target_id: str
75
75
  edge_type: EdgeType
76
- weight: float = 1.0 # 关联强度
77
- reason: str = "" # 为什么连接(可解释)
76
+ weight: float = 1.0
77
+ reason: str = ""
78
78
 
79
79
  def to_dict(self) -> dict:
80
80
  return asdict(self)
@@ -11,7 +11,6 @@ from __future__ import annotations
11
11
  from ..cognitive.reasoner import DomainReasoner, ReasonedVerdict, Evidence
12
12
  from .spaced_repetition import SpacedRepetition
13
13
 
14
- # 负荷偏重阈值:未来窗口日均复习量 ≥ 该值即算偏重,交给可选 LLM 复核是否提醒。
15
14
  HEAVY_DAILY = 20.0
16
15
 
17
16
 
@@ -37,13 +36,12 @@ class LearningReviewReasoner(DomainReasoner):
37
36
  return h or ["学习状态可能健康"]
38
37
 
39
38
  def gather_evidence(self, states, obs: dict) -> list:
40
- risky = SpacedRepetition.forgetting_risk(states, self.horizon_days) # 规则=裁决证据
39
+ risky = SpacedRepetition.forgetting_risk(states, self.horizon_days)
41
40
  ev = [Evidence(factor="forgetting_risk",
42
41
  detail=r["reason"] + f"(item {r['item_id']})", weight=1.0, raw=r)
43
42
  for r in risky]
44
43
  if not risky:
45
44
  ev.append(Evidence("no_urgent_risk", "无即将到期且掌握度低的知识点", 0.5))
46
- # informational(raw=None):学习负荷,不参与裁决
47
45
  ev.append(Evidence("learning_load",
48
46
  f"未来窗口共 {obs['total_due']} 项待复习,日均 {obs['daily_avg']}", 0.3))
49
47
  return ev
@@ -51,7 +49,6 @@ class LearningReviewReasoner(DomainReasoner):
51
49
  def arbitrate(self, states, obs, hyps, ev) -> ReasonedVerdict:
52
50
  triggered = [e for e in ev if e.raw is not None]
53
51
  if triggered:
54
- # 【规则权威】存在高遗忘风险 → confirm(建议立即复习),绝不被 LLM 推翻/隐藏
55
52
  top = triggered[:3]
56
53
  return ReasonedVerdict(
57
54
  "confirm", f"发现 {len(triggered)} 个高遗忘风险知识点,建议优先复习:"