foxclaw 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,48 @@
1
+ import { escapeHtml } from './util.js';
2
+
3
+ /* ===== 安全 Markdown 渲染(模型输出不可信 → 先整体转义,再只发白名单标签;URL 校验 scheme)=====
4
+ 支持:标题/粗斜体/行内码/代码块/列表/引用/分割线/链接/图片,以及裸图/音/视频 URL 内嵌。
5
+ 代码块占位哨兵用私用区字符 (源码里是 ASCII 转义、运行时是该字符,模型文本不会出现 → 无碰撞)。*/
6
+ function _safeUrl(u){ u=(u||'').trim(); return (/^https?:\/\//i.test(u)||/^mailto:/i.test(u))?u:''; }
7
+ function _safeImgUrl(u){ u=(u||'').trim(); return (/^https?:\/\//i.test(u)||/^data:image\/(png|jpe?g|gif|webp);base64,/i.test(u))?u:''; }
8
+ function _mdInline(s){
9
+ // s 已在 renderMarkdown 里被 escapeHtml,故 <>&" 均为文本;这里只做 markdown 内联生成白名单标签
10
+ s=s.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g,(m,alt,url)=>{ const u=_safeImgUrl(url); return u?`<img class="cc-img" src="${u}" alt="${alt}" loading="lazy">`:m; });
11
+ s=s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g,(m,txt,url)=>{ const u=_safeUrl(url); return u?`<a href="${u}" target="_blank" rel="noopener noreferrer">${txt}</a>`:m; });
12
+ s=s.replace(/(^|\s)(https?:\/\/[^\s<]+?\.(?:png|jpe?g|gif|webp))(?=\s|$)/gi,(m,pre,url)=>`${pre}<img class="cc-img" src="${url}" alt="" loading="lazy">`);
13
+ s=s.replace(/(^|\s)(https?:\/\/[^\s<]+?\.(?:mp3|wav|ogg|m4a))(?=\s|$)/gi,(m,pre,url)=>`${pre}<audio class="cc-media" controls src="${url}"></audio>`);
14
+ s=s.replace(/(^|\s)(https?:\/\/[^\s<]+?\.(?:mp4|webm|mov))(?=\s|$)/gi,(m,pre,url)=>`${pre}<video class="cc-media" controls src="${url}"></video>`);
15
+ s=s.replace(/`([^`]+)`/g,(m,c)=>`<code class="cc-ic">${c}</code>`);
16
+ s=s.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/__([^_]+)__/g,'<strong>$1</strong>');
17
+ s=s.replace(/(^|[^*])\*([^*\n]+)\*/g,'$1<em>$2</em>').replace(/(^|[^_\w])_([^_\n]+)_/g,'$1<em>$2</em>');
18
+ return s;
19
+ }
20
+ export function renderMarkdown(src){
21
+ if(!src) return '';
22
+ const blocks=[];
23
+ src=src.replace(/```(\w*)\n?([\s\S]*?)```/g,(m,lang,code)=>{ blocks.push(`<pre class="cc-code"><code>${escapeHtml(code.replace(/\n$/,''))}</code></pre>`); return `B${blocks.length-1}`; });
24
+ const t=escapeHtml(src); // 整体转义:此后所有原始 <>&" 都是纯文本
25
+ const lines=t.split('\n'); const out=[]; let inUl=false, inOl=false;
26
+ const closeLists=()=>{ if(inUl){out.push('</ul>');inUl=false;} if(inOl){out.push('</ol>');inOl=false;} };
27
+ for(const raw of lines){
28
+ const line=raw, tr=line.trim(); let m;
29
+ if(/^B\d+$/.test(tr)){ closeLists(); out.push(tr); continue; }
30
+ if((m=line.match(/^(#{1,6})\s+(.*)$/))){ closeLists(); const n=m[1].length; out.push(`<h${n} class="cc-h">${_mdInline(m[2])}</h${n}>`); continue; }
31
+ if(/^\s*([-*_])\s*\1\s*\1\s*$/.test(line)){ closeLists(); out.push('<hr class="cc-hr">'); continue; }
32
+ if((m=line.match(/^\s*&gt;\s?(.*)$/))){ closeLists(); out.push(`<blockquote class="cc-bq">${_mdInline(m[1])}</blockquote>`); continue; }
33
+ if((m=line.match(/^\s*[-*+]\s+(.*)$/))){ if(inOl){out.push('</ol>');inOl=false;} if(!inUl){out.push('<ul class="cc-ul">');inUl=true;} out.push(`<li>${_mdInline(m[1])}</li>`); continue; }
34
+ if((m=line.match(/^\s*\d+\.\s+(.*)$/))){ if(inUl){out.push('</ul>');inUl=false;} if(!inOl){out.push('<ol class="cc-ol">');inOl=true;} out.push(`<li>${_mdInline(m[1])}</li>`); continue; }
35
+ if(tr===''){ closeLists(); continue; }
36
+ closeLists(); out.push(`<p class="cc-p">${_mdInline(line)}</p>`);
37
+ }
38
+ closeLists();
39
+ let html=out.join('');
40
+ html=html.replace(/B(\d+)/g,(m,i)=>blocks[+i]||'');
41
+ return html;
42
+ }
43
+ /* 把 markdown 结果渲染进回复区(带 .cc-md 容器,关掉 pre-wrap)*/
44
+ export function renderReply(md){
45
+ const rp=document.getElementById('ccReply');
46
+ rp.innerHTML='<div class="cc-md">'+renderMarkdown(md)+'</div>';
47
+ rp.scrollTop=rp.scrollHeight;
48
+ }
@@ -0,0 +1,324 @@
1
+ import { API } from './state.js';
2
+
3
+ /* ===== 设置面板 ===== */
4
+ const SETTINGS_TITLE={
5
+ model:'模型路由',runtime:'运行参数',ui:'界面偏好',data:'数据管理',auth:'访问密钥',about:'关于',
6
+ 'ag-supervisor':'主管 Agent','ag-finance':'财务 Agent','ag-admin':'行政 Agent',
7
+ 'ag-office':'办公 Agent','ag-assistant':'助手 Agent',
8
+ 'mem-backend':'记忆后端','mem-layer':'双层记忆','mem-recall':'召回参数','mem-manage':'记忆管理',
9
+ };
10
+ // 界面偏好(纯前端,存内存)
11
+ export const uiPref={ autoChat:true, ballAnim:false, mapStyle:'light' }; // ballAnim 默认 false(球体特效默认关,省 GPU;需要时手动勾)
12
+ let _sysInfo=null;
13
+ async function fetchSysInfo(){ try{ _sysInfo=await(await fetch(`${API}/system/info`)).json(); }catch(e){ _sysInfo=null; } return _sysInfo; }
14
+
15
+ async function renderSettingDetail(key){
16
+ const body=document.getElementById('sdBody');
17
+ body.innerHTML='<div class="sd-ph">加载中…</div>';
18
+ // ── Foxclaw 主系统 ──
19
+ if(key==='model'){
20
+ const d=await fetchSysInfo(); const m=d?.model||{};
21
+ let h=`<div class="sd-card"><div class="sd-k">当前模型</div>
22
+ <div class="sd-big">${(m.provider||'—')} <span>${m.active?m.model:'(降级 stub)'}</span></div></div>`;
23
+ h+='<div class="sd-sec">可用 Provider · 点击切换<span class="badge edit">可调</span></div>';
24
+ (m.available_providers||[]).forEach(p=>{
25
+ const cur=p.provider===m.provider, dis=!p.configured;
26
+ h+=`<div class="sd-row ${cur?'cur':''} ${dis?'dis':''}" ${dis||cur?'':`onclick="switchProvider('${p.provider}')"`}>
27
+ <span>${PROVIDER_LABEL[p.provider]?.n||p.provider}<small>${p.default_model}</small></span>
28
+ <span class="tag">${cur?'当前':dis?'未配置':'可用'}</span></div>`;
29
+ });
30
+ h+='<div class="sd-note">模型 Key 在项目根目录 <code>.env</code> 配置,改后重启生效。</div>';
31
+ body.innerHTML=h;
32
+ }else if(key==='runtime'){
33
+ const d=await fetchSysInfo();
34
+ body.innerHTML=`
35
+ <div class="sd-sec">运行参数<span class="badge ro">只读</span></div>
36
+ <div class="sd-ro">
37
+ <div class="sd-kv"><span>数据目录</span><b>${d?.data_dir||'—'}</b></div>
38
+ <div class="sd-kv"><span>Harness 影子轮数</span><b>${d?.shadow_rounds??'—'}</b></div>
39
+ </div>
40
+ <div class="sd-note">运行参数在 <code>.env</code> 配置(FOXCLAW_DATA_DIR / FOXCLAW_SHADOW_ROUNDS),改后重启生效。影子轮数越小,Harness 越快进入漂移拦截模式。</div>`;
41
+ }else if(key==='ui'){
42
+ body.innerHTML=`
43
+ <div class="sd-sec">界面偏好<span class="badge edit">可调</span></div>
44
+ <div class="sd-toggle"><span>发任务自动弹中央对话层</span><label class="sw"><input type="checkbox" id="pf-autoChat" ${uiPref.autoChat?'checked':''}><i></i></label></div>
45
+ <div class="sd-toggle"><span>背景全息球动画</span><label class="sw"><input type="checkbox" id="pf-ballAnim" ${uiPref.ballAnim?'checked':''}><i></i></label></div>
46
+ <div class="sd-kv2"><span>地图默认样式</span>
47
+ <select id="pf-mapStyle"><option value="light" ${uiPref.mapStyle==='light'?'selected':''}>亮色(官网风格)</option><option value="dark" ${uiPref.mapStyle==='dark'?'selected':''}>暗色</option></select></div>
48
+ <div class="sd-note">界面偏好实时生效,保存在当前会话。</div>`;
49
+ document.getElementById('pf-autoChat').onchange=e=>uiPref.autoChat=e.target.checked;
50
+ document.getElementById('pf-ballAnim').onchange=e=>{uiPref.ballAnim=e.target.checked; toggleBallAnim(e.target.checked);};
51
+ document.getElementById('pf-mapStyle').onchange=e=>uiPref.mapStyle=e.target.value;
52
+ }else if(key==='data'){
53
+ const s=await(await fetch(`${API}/memory/stats`)).json().catch(()=>({}));
54
+ body.innerHTML=`
55
+ <div class="sd-kv"><span>记忆后端</span><b>${s.backend||'—'}</b></div>
56
+ <div class="sd-kv"><span>记忆总条数</span><b>${s.total_memories??0}</b></div>
57
+ <div class="sd-kv"><span>知识关系数</span><b>${s.relations??0}</b></div>
58
+ <div class="sd-note">运行数据存于本地 <code>foxclaw_data/</code> 目录。清空数据请在本地删除该目录后重启(网页端不直接删除,避免误操作)。</div>`;
59
+ }else if(key==='auth'){
60
+ const cur=localStorage.getItem('foxclawApiKey')||'';
61
+ body.innerHTML=`
62
+ <div class="sd-sec">后端访问密钥<span class="badge ${cur?'ro':'edit'}">${cur?'已配置':'未配置'}</span></div>
63
+ <div class="sd-desc">后端 <code>.env</code> 若设置了 <code>FOXCLAW_API_KEY</code>,则所有 <code>/api</code>、<code>/knowledge</code> 请求与 WebSocket 都需携带此密钥,否则返回 401/403、数据无法加载。填入与 <code>.env</code> 相同的值即可恢复。</div>
64
+ <div class="api-key-row">
65
+ <input type="password" id="pf-apiKey" class="api-key-inp" placeholder="粘贴 FOXCLAW_API_KEY 的值" value="${cur}" autocomplete="off" spellcheck="false">
66
+ </div>
67
+ <div class="api-key-btns">
68
+ <button id="pf-apiKey-save" class="api-key-btn primary">保存并重连</button>
69
+ <button id="pf-apiKey-clear" class="api-key-btn">清除</button>
70
+ </div>
71
+ <div class="sd-note">密钥仅保存在本机浏览器 <code>localStorage</code>,不会上传到服务器。后端未设 <code>FOXCLAW_API_KEY</code> 时此项留空即可(默认开放)。</div>`;
72
+ document.getElementById('pf-apiKey-save').onclick=()=>{
73
+ const v=document.getElementById('pf-apiKey').value.trim();
74
+ if(v) localStorage.setItem('foxclawApiKey', v);
75
+ else localStorage.removeItem('foxclawApiKey');
76
+ location.reload();
77
+ };
78
+ document.getElementById('pf-apiKey-clear').onclick=()=>{
79
+ localStorage.removeItem('foxclawApiKey');
80
+ location.reload();
81
+ };
82
+ }else if(key==='about'){
83
+ const d=await fetchSysInfo();
84
+ body.innerHTML=`
85
+ <div class="sd-card"><div class="sd-big">Foxclaw <span>v${d?.version||'1.0.0'}</span></div>
86
+ <div class="sd-desc">有记忆的个人智能体 · 记忆/认知层与 LLM 解耦</div></div>
87
+ <div class="sd-sec">五个 Agent</div>
88
+ <div class="sd-kv"><span>主管 Supervisor</span><b>分诊调度 · 全局态势</b></div>
89
+ <div class="sd-kv"><span>财务 Finance</span><b>理财 · 风控 · 守门人</b></div>
90
+ <div class="sd-kv"><span>行政 Admin</span><b>行程 · 路线 · 高德地图</b></div>
91
+ <div class="sd-kv"><span>办公 Office</span><b>学习 · 知识图谱</b></div>
92
+ <div class="sd-kv"><span>助手 Assistant</span><b>通用 · Skill 接入</b></div>
93
+ <div class="sd-sec">三大模块</div>
94
+ <div class="sd-desc">Foxclaw(主系统) · Agents(五智能体) · HMR(记忆系统)</div>
95
+ <div class="sd-note">配置说明见项目 <code>docs/配置指南.md</code></div>`;
96
+ }else if(key && key.startsWith('mem-')){
97
+ const s=await(await fetch(`${API}/memory/stats`)).json().catch(()=>({}));
98
+ if(key==='mem-backend'){
99
+ body.innerHTML=`
100
+ <div class="sd-card"><div class="sd-big">${s.backend||'HMR'} <span>v${s.version||'—'}</span></div>
101
+ <div class="sd-desc">分层记忆系统 · 记忆/认知层与 LLM 解耦</div></div>
102
+ <div class="sd-sec">后端信息<span class="badge ro">只读</span></div>
103
+ <div class="sd-kv"><span>记忆后端</span><b>${s.backend||'—'}</b></div>
104
+ <div class="sd-kv"><span>版本</span><b>v${s.version||'—'}</b></div>
105
+ <div class="sd-kv"><span>Embedding 模型</span><b>${s.embedding||'—'}</b></div>
106
+ <div class="sd-kv"><span>知识图谱关系数</span><b>${s.relations??0}</b></div>
107
+ <div class="sd-note">安装 <code>openai</code> 或 <code>sentence-transformers</code> 可启用更精确的语义向量(当前默认 TF-IDF n-gram 降级)。</div>`;
108
+ }else if(key==='mem-layer'){
109
+ const ba=s.by_agent||{}; const entries=Object.entries(ba).sort((a,b)=>b[1]-a[1]);
110
+ let rows=entries.length?entries.map(([a,n])=>`<div class="sd-kv"><span>${AGENT_META[a]?.cn||a}</span><b>${n} 条</b></div>`).join(''):'<div class="sd-desc">暂无分层记忆数据。</div>';
111
+ body.innerHTML=`
112
+ <div class="sd-sec">双层记忆架构<span class="badge ro">只读</span></div>
113
+ <div class="sd-kv"><span>私有层(各 Agent 独立)</span><b>${s.private??0} 条</b></div>
114
+ <div class="sd-kv"><span>共享层(跨 Agent 可召回)</span><b>${s.shared??0} 条</b></div>
115
+ <div class="sd-sec">按 Agent 分布<span class="badge ro">只读</span></div>
116
+ ${rows}
117
+ <div class="sd-note">私有层:每个 Agent 只能召回自己的记忆;共享层:标记 shared 的记忆所有 Agent 都可召回(如主管的全局决策)。</div>`;
118
+ }else if(key==='mem-recall'){
119
+ body.innerHTML=`
120
+ <div class="sd-sec">召回参数<span class="badge ro">只读</span></div>
121
+ <div class="sd-kv"><span>默认召回数量 (top-k)</span><b>5</b></div>
122
+ <div class="sd-kv"><span>相似度阈值</span><b>自适应</b></div>
123
+ <div class="sd-kv"><span>召回范围</span><b>本 Agent 私有 + 共享层</b></div>
124
+ <div class="sd-note">召回采用语义向量相似度排序,默认返回最相关的 top-k 条记忆 + 共享层记忆。参数在记忆适配器中定义,随召回质量自调。</div>`;
125
+ }else if(key==='mem-manage'){
126
+ body.innerHTML=`
127
+ <div class="sd-card"><div class="sd-big">${s.total_memories??0} <span>条记忆</span></div>
128
+ <div class="sd-desc">私有 ${s.private??0} · 共享 ${s.shared??0} · 关系 ${s.relations??0}</div></div>
129
+ <div class="sd-sec">记忆管理<span class="badge ro">只读</span></div>
130
+ <div class="sd-kv"><span>记忆总条数</span><b>${s.total_memories??0}</b></div>
131
+ <div class="sd-kv"><span>知识关系数</span><b>${s.relations??0}</b></div>
132
+ <div class="sd-note">运行数据存于本地 <code>foxclaw_data/</code> 目录。清空记忆请在本地删除该目录后重启(网页端不直接删除,避免误操作)。导出功能可通过 HMR 后端 API 实现。</div>`;
133
+ }
134
+ }else if(key && key.startsWith('ag-')){
135
+ await renderAgentDetail(key.slice(3), body);
136
+ }else{
137
+ body.innerHTML=`<div class="sd-ph">该模块设置开发中</div>`;
138
+ }
139
+ }
140
+
141
+ /* 单个 Agent 设置详情(5 个共用 + 领域特定块)*/
142
+ const AGENT_META={
143
+ supervisor:{cn:'主管',en:'Supervisor',ac:'#ffc24b',role:'分诊调度 · 全局态势编排'},
144
+ finance:{cn:'财务',en:'Finance',ac:'#00ffb2',role:'理财 · 预算 · 风控守门'},
145
+ admin:{cn:'行政',en:'Admin',ac:'#ff9d00',role:'行程 · 日程 · 高德地图'},
146
+ office:{cn:'办公',en:'Office',ac:'#d99a3c',role:'学习 · 知识 · 间隔重复'},
147
+ assistant:{cn:'助手',en:'Assistant',ac:'#38bdf8',role:'通用任务 · Skill 接入'},
148
+ };
149
+ async function renderAgentDetail(aid, body){
150
+ const meta=AGENT_META[aid]||{cn:aid,en:aid,ac:'#00d9ff',role:''};
151
+ let hs=null, sys=null;
152
+ try{ hs=await(await fetch(`${API}/harness/${aid}/status`)).json(); }catch(e){}
153
+ try{ sys=await fetchSysInfo(); }catch(e){}
154
+ const ag=sys?.agents?.[aid]||{};
155
+ const shadow=ag.shadow_mode, rounds=ag.shadow_rounds??'—', run=ag.run_count??0;
156
+ const th=hs?.thresholds||{}, w=hs?.weights||{}, pol=hs?.policy||{};
157
+
158
+ let h=`<div class="sd-card" style="border-color:${meta.ac}55">
159
+ <div class="sd-big" style="color:${meta.ac}">${meta.cn} <span>${meta.en}</span></div>
160
+ <div class="sd-desc">${meta.role}</div></div>`;
161
+
162
+ // Harness 漂移检测
163
+ h+='<div class="sd-sec">Harness 漂移检测<span class="badge ro">只读</span></div>';
164
+ h+=`<div class="sd-kv"><span>运行模式</span><b style="color:${shadow?'var(--amber)':'var(--green)'}">${shadow?'影子校准中':'已激活拦截'}</b></div>`;
165
+ h+=`<div class="sd-kv"><span>校准进度</span><b>${run} / ${rounds} 轮</b></div>`;
166
+ if(th.warn!=null||th.block!=null){
167
+ h+=`<div class="sd-kv"><span>预警阈值 (WARN)</span><b>${fmt(th.warn)}</b></div>`;
168
+ h+=`<div class="sd-kv"><span>拦截阈值 (BLOCK)</span><b>${fmt(th.block)}</b></div>`;
169
+ }
170
+ if(w.semantic!=null){
171
+ h+=`<div class="sd-kv"><span>三维权重</span><b>语义 ${fmt(w.semantic)} · 工具 ${fmt(w.tool)} · 范围 ${fmt(w.scope)}</b></div>`;
172
+ }
173
+
174
+ // 守门人 Policy
175
+ h+='<div class="sd-sec">守门人 Policy<span class="badge ro">只读</span></div>';
176
+ if(pol.version!=null||pol.total_rules!=null){
177
+ h+=`<div class="sd-kv"><span>Policy 版本</span><b>v${pol.version??'—'}</b></div>`;
178
+ h+=`<div class="sd-kv"><span>规则总数</span><b>${pol.total_rules??0}</b></div>`;
179
+ h+=`<div class="sd-kv"><span>手工规则 / 自学习</span><b>${pol.manual_rules??0} / ${pol.learned_rules??0}</b></div>`;
180
+ }else{
181
+ h+='<div class="sd-desc">策略随运行自学习更新,当前无自定义规则覆盖。</div>';
182
+ }
183
+
184
+ // 领域特定块
185
+ if(aid==='admin'){
186
+ h+='<div class="sd-sec">高德地图(行政专属)<span class="badge ro">只读</span></div>';
187
+ const a=sys?.amap||{};
188
+ h+=`<div class="sd-kv"><span>Web服务 Key(路线/POI)</span><b style="color:${a.web_key?'var(--green)':'var(--ink-faint)'}">${a.web_key?'已配置':'未配置'}</b></div>`;
189
+ h+=`<div class="sd-kv"><span>JSAPI Key(前端地图)</span><b style="color:${a.js_key?'var(--green)':'var(--ink-faint)'}">${a.js_key?'已配置':'未配置'}</b></div>`;
190
+ h+=`<div class="sd-kv"><span>JSAPI 安全密钥</span><b style="color:${a.js_scode?'var(--green)':'var(--ink-faint)'}">${a.js_scode?'已配置':'未配置'}</b></div>`;
191
+ h+=`<div class="sd-toggle"><span>地图默认样式(暗色)</span><label class="sw"><input type="checkbox" id="pf-mapDark2" ${uiPref.mapStyle==='dark'?'checked':''}><i></i></label></div>`;
192
+ h+='<div class="sd-note">高德三个 Key 在 <code>.env</code> 配置(AMAP_MAPS_API_KEY / AMAP_JS_KEY / AMAP_JS_SCODE)。</div>';
193
+ }else if(aid==='finance'){
194
+ h+='<div class="sd-sec">财务领域参数<span class="badge ro">只读</span></div>';
195
+ h+='<div class="sd-desc">预算阈值、MAD 异常灵敏度、风控规则在财务内核 policy 中定义,随交易数据自学习。守门人对大额/越权操作执行确认或冻结。</div>';
196
+ }else if(aid==='office'){
197
+ h+='<div class="sd-sec">学习领域参数<span class="badge ro">只读</span></div>';
198
+ h+='<div class="sd-desc">采用 SM-2 间隔重复算法管理知识复习节奏;知识图谱关系自动抽取,守门人防止知识污染。</div>';
199
+ }else if(aid==='supervisor'){
200
+ h+='<div class="sd-sec">路由编排<span class="badge ro">只读</span></div>';
201
+ h+='<div class="sd-desc">元问题(介绍/系统级)由主管自答;业务任务按关键词分诊到财务/行政/办公/助手。可在 agents.py 的 ROUTING_KEYWORDS / META_KEYWORDS 调整。</div>';
202
+ }else if(aid==='assistant'){
203
+ h+='<div class="sd-sec">Skill 接入<span class="badge ro">只读</span></div>';
204
+ h+='<div class="sd-desc">通用任务兜底处理,可接入外部 Skill 工具扩展能力。主管无法明确归类的任务默认派给助手。</div>';
205
+ }
206
+
207
+ h+='<div class="sd-note">Agent 行为参数大多在后端 policy 与代码中定义,此处为只读状态展示。漂移阈值由影子校准期自动学习得出。</div>';
208
+ body.innerHTML=h;
209
+
210
+ // 行政地图暗色开关联动
211
+ const md=document.getElementById('pf-mapDark2');
212
+ if(md) md.onchange=e=>{ uiPref.mapStyle=e.target.checked?'dark':'light'; };
213
+ }
214
+ function fmt(v){ return (v==null||isNaN(v))?'—':(typeof v==='number'?v.toFixed(3):v); }
215
+ export async function switchProvider(prov){
216
+ try{ await fetch(`${API}/model/switch`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({provider:prov})});
217
+ _sysInfo=null; renderSettingDetail('model'); loadModel(); }catch(e){}
218
+ }
219
+ function toggleBallAnim(on){
220
+ const c=document.getElementById('coreCanvas'); if(c)c.style.display=on?'block':'none';
221
+ // 仅 display:none 不够(loop 还在 60fps 跑,吃 GPU)→ 同步设置 localStorage 标志,
222
+ // 引导用户硬刷一次(自动 reload)。visuals.js 顶部 _lowpower 检查,刷新后即生效。
223
+ try{ localStorage.setItem('foxclaw_lowpower', on ? '0' : '1'); }catch{}
224
+ // 仅当「关」才 reload(开不影响,因为持久态 = 0 时不阻止)
225
+ if (!on) {
226
+ // 提示一下,避免用户疑惑
227
+ console.info('[foxclaw] 关掉球动画后需刷新一次才停止 GPU 占用');
228
+ }
229
+ }
230
+
231
+ /* 二级子项定义 */
232
+ const SETTINGS_SUBS={
233
+ foxclaw:[['model','模型路由'],['runtime','运行参数'],['ui','界面偏好'],['data','数据管理'],['auth','访问密钥'],['about','关于']],
234
+ agents:[['ag-supervisor','主管 Agent'],['ag-finance','财务 Agent'],['ag-admin','行政 Agent'],['ag-office','办公 Agent'],['ag-assistant','助手 Agent']],
235
+ hmr:[['mem-backend','记忆后端'],['mem-layer','双层记忆'],['mem-recall','召回参数'],['mem-manage','记忆管理']],
236
+ };
237
+ const _setMenu=document.getElementById('setMenu');
238
+ const _smL2=document.getElementById('smL2');
239
+ const _sdPop=document.getElementById('sdPop');
240
+ let _setHideTimer=null;
241
+
242
+ export function openSetMenu(){ clearTimeout(_setHideTimer); _setMenu.classList.add('show'); }
243
+ /* 直接打开「访问密钥」设置详情(供 main.js 的鉴权提示条调用) */
244
+ export function openAuthSetting(){ _setMenu.classList.add('show'); openDetailPop('auth'); }
245
+ function closeSetMenu(){
246
+ clearTimeout(_setHideTimer);
247
+ _setHideTimer=setTimeout(()=>{ _setMenu.classList.remove('show'); _smL2.innerHTML=''; document.querySelectorAll('.sm-l1 .sm-btn').forEach(b=>b.classList.remove('active')); }, 400);
248
+ }
249
+ // 一级悬停 → 展开二级
250
+ function showL2(group, l1btn){
251
+ document.querySelectorAll('.sm-l1 .sm-btn').forEach(b=>b.classList.remove('active'));
252
+ l1btn.classList.add('active');
253
+ _smL2.innerHTML=(SETTINGS_SUBS[group]||[]).map(([k,name])=>
254
+ `<div class="sm-btn" data-sub="${k}"><b>${name}</b><span class="chev">›</span></div>`).join('');
255
+ _smL2.querySelectorAll('.sm-btn').forEach(b=>{
256
+ b.addEventListener('click',()=>openDetailPop(b.dataset.sub));
257
+ });
258
+ }
259
+ function openDetailPop(key){
260
+ document.getElementById('sdTitle').textContent=SETTINGS_TITLE[key]||'详情';
261
+ _sdPop.classList.add('show');
262
+ renderSettingDetail(key);
263
+ }
264
+ function closeDetailPop(){ _sdPop.classList.remove('show'); }
265
+
266
+ const _gear=document.getElementById('settingsBtn');
267
+ _gear.addEventListener('mouseenter', openSetMenu);
268
+ _gear.addEventListener('mouseleave', closeSetMenu);
269
+ _gear.addEventListener('click', openSetMenu);
270
+ _setMenu.addEventListener('mouseenter', ()=>clearTimeout(_setHideTimer));
271
+ _setMenu.addEventListener('mouseleave', closeSetMenu);
272
+ document.querySelectorAll('.sm-l1 .sm-btn').forEach(b=>{
273
+ b.addEventListener('mouseenter', ()=>showL2(b.dataset.group, b));
274
+ });
275
+ document.getElementById('settingsBtn2')?.addEventListener('click', ()=>{ openSetMenu(); clearTimeout(_setHideTimer); });
276
+ document.getElementById('sdClose').addEventListener('click', closeDetailPop);
277
+
278
+ document.querySelectorAll('.nav-mini, .feed-item').forEach(it=>{
279
+ it.addEventListener('click',()=>{ it.classList.add('pressed'); setTimeout(()=>it.classList.remove('pressed'),180); });
280
+ });
281
+
282
+
283
+ /* ===== 模型选择器 ===== */
284
+ const PROVIDER_LABEL=
285
+ {anthropic:{n:'Claude',c:'#ff9d00'},openai:{n:'GPT',c:'#00ffb2'},
286
+ google:{n:'Gemini',c:'#38bdf8'},qwen:{n:'通义千问',c:'#ffc24b'},
287
+ local:{n:'本地模型',c:'#d99a3c'},stub:{n:'Stub 降级',c:'#7e93b8'}};
288
+ async function loadModel(){
289
+ try{
290
+ const d=await(await fetch(`${API}/model`)).json();
291
+ const lbl=PROVIDER_LABEL[d.provider]||{n:d.provider,c:'#7e93b8'};
292
+ document.getElementById('modelName').textContent=lbl.n;
293
+ renderModelMenu(d);
294
+ }catch(e){}
295
+ }
296
+ function renderModelMenu(d){
297
+ const menu=document.getElementById('modelMenu'); if(!menu)return;
298
+ let h='<div class="mtitle">模型路由 · 点击切换</div>';
299
+ (d.available_providers||[]).forEach(p=>{
300
+ const lbl=PROVIDER_LABEL[p.provider]||{n:p.provider,c:'#7e93b8'};
301
+ const cur=p.provider===d.provider;
302
+ const dis=!p.configured;
303
+ h+=`<div class="model-opt ${cur?'active':''} ${dis?'disabled':''}" data-prov="${p.provider}">
304
+ <span class="mdot" style="background:${lbl.c};box-shadow:0 0 6px ${lbl.c}"></span>
305
+ <span class="mname"><b>${lbl.n}</b><span>${p.default_model}</span></span>
306
+ ${cur?'<span class="mtag">当前</span>':dis?'<span class="mtag" style="background:rgba(255,157,0,.12);color:var(--amber)">未配置</span>':'<span class="mtag">可用</span>'}</div>`;
307
+ });
308
+ menu.innerHTML=h;
309
+ menu.querySelectorAll('.model-opt:not(.disabled):not(.active)').forEach(opt=>{
310
+ opt.addEventListener('click',async()=>{
311
+ const prov=opt.dataset.prov;
312
+ try{await fetch(`${API}/model/switch`,{method:'POST',headers:{'Content-Type':'application/json'},
313
+ body:JSON.stringify({provider:prov})});
314
+ menu.classList.remove('show'); loadModel();
315
+ }catch(e){}
316
+ });
317
+ });
318
+ }
319
+ document.getElementById('modelPill').addEventListener('click',e=>{
320
+ e.stopPropagation();
321
+ document.getElementById('modelMenu').classList.toggle('show');
322
+ });
323
+ document.addEventListener('click',()=>document.getElementById('modelMenu')?.classList.remove('show'));
324
+ loadModel();
@@ -0,0 +1,6 @@
1
+ /* 跨模块共享:URL 常量 + 内核数据缓存(零构建,显式 live-binding 导出) */
2
+ const _origin=(location.protocol==='http:'||location.protocol==='https:')?location.origin:'http://localhost:8000';
3
+ const API=_origin+'/api';
4
+ const WS=_origin.replace(/^http/,'ws')+'/ws/harness';
5
+ let _lastKernelData={}; // 缓存各 Agent 最近一次内核数据,供全屏复用
6
+ export { _origin, API, WS, _lastKernelData };
@@ -0,0 +1,6 @@
1
+ /* 安全:HTML 转义 —— 模型输出/用户输入/外部数据写入 innerHTML 前必须转义,防 XSS
2
+ (模型输出不可信,可能被提示注入产出 <img onerror> 等)。*/
3
+ export function escapeHtml(s){
4
+ return String(s==null?'':s).replace(/[&<>"']/g,c=>(
5
+ {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
6
+ }
@@ -0,0 +1,212 @@
1
+ /* ═══════ 球体特效开关:默认关,需要时手动开 ═══════
2
+ ─ 手动开(任一即生效):
3
+ 1) URL 加 ?visual=1 临时(本次会话)
4
+ 2) localStorage.foxclaw_visual = "1" 永久
5
+ 3) 设置 → 界面偏好 → 背景全息球动画 勾选 永久(写 localStorage.foxclaw_lowpower=0,兼容旧 toggle)
6
+ ─ 系统级默认关(无需操作):
7
+ 4) matchMedia('(prefers-reduced-motion: reduce)').matches
8
+ 5) navigator.connection.saveData === true
9
+ ─ 默认:true(不启动 4 个 canvas 动画 + 粒子 emit,省 GPU/CPU,启动秒开) */
10
+ const _lowpower = (() => {
11
+ try {
12
+ // 显式开:URL / localStorage / 旧版 toggleBallAnim
13
+ if (new URLSearchParams(location.search).get('visual') === '1') return false;
14
+ if (localStorage.getItem('foxclaw_visual') === '1') return false;
15
+ if (localStorage.getItem('foxclaw_lowpower') === '0') return false; // 兼容旧版开关
16
+ // 显式关:URL ?low=1 仍生效(应急)
17
+ if (new URLSearchParams(location.search).get('low') === '1') return true;
18
+ // 系统级 → 关
19
+ if (matchMedia('(prefers-reduced-motion: reduce)').matches) return true;
20
+ if (navigator.connection && navigator.connection.saveData) return true;
21
+ } catch {}
22
+ return true; // ← 默认:不启动(保护 GPU + 启动秒开)
23
+ })();
24
+ window._foxclawLowpower = _lowpower; // 暴露给其他模块读
25
+ // 暴露切换 API(控制台手动开: foxclawVisual.on() / 永久关: foxclawVisual.off() / 状态: foxclawVisual.isOn())
26
+ window.foxclawVisual = {
27
+ isOn: () => !_lowpower,
28
+ on() { try { localStorage.setItem('foxclaw_visual', '1'); } catch{} location.reload(); },
29
+ off() { try { localStorage.removeItem('foxclaw_visual'); localStorage.setItem('foxclaw_lowpower', '1'); } catch{} location.reload(); },
30
+ toggle() { this.isOn() ? this.off() : this.on(); },
31
+ };
32
+ if (_lowpower) console.info('[foxclaw] 球体特效已关闭(默认) — 想看动画:控制台跑 foxclawVisual.on() 或 URL 加 ?visual=1');
33
+ function _lpThrottle(fn, fps) { // 30fps 节流(不关,但降频)— 关闭模式直接 noop
34
+ if (_lowpower) return () => {};
35
+ let last = 0; const gap = 1000 / fps;
36
+ return function(t) { if (t - last >= gap) { last = t; fn(t); } };
37
+ }
38
+
39
+ /* ====== 中央全息核心 ====== */
40
+ (function(){
41
+ if (_lowpower) return; // 省电:不开中央全息核心(520 球面点 + 经纬弧 + 渐变 60fps)
42
+ const c=document.getElementById('coreCanvas'), x=c.getContext('2d');
43
+ function size(){const r=c.parentElement.getBoundingClientRect();c.width=r.width;c.height=r.height;}
44
+ size(); window.addEventListener('resize',size);
45
+ const N=520, pts=[];
46
+ for(let i=0;i<N;i++){const t=Math.acos(2*Math.random()-1),p=Math.random()*Math.PI*2;
47
+ pts.push({t,p});}
48
+ let rot=0;
49
+ function loop(){
50
+ x.clearRect(0,0,c.width,c.height);
51
+ const cx=c.width/2, cy=c.height/2, R=Math.min(cx,cy)*0.62;
52
+ rot+=0.0024;
53
+ // 光环底座
54
+ for(let k=0;k<3;k++){
55
+ x.beginPath();
56
+ x.ellipse(cx,cy+R*0.92,R*(1.05+k*0.18),R*(0.26+k*0.05),0,0,Math.PI*2);
57
+ x.strokeStyle=`rgba(0,217,255,${0.18-k*0.04})`; x.lineWidth=2; x.stroke();
58
+ }
59
+ // 球面点
60
+ for(const pt of pts){
61
+ const t=pt.t, p=pt.p+rot;
62
+ const xx=Math.sin(t)*Math.cos(p), yy=Math.cos(t), zz=Math.sin(t)*Math.sin(p);
63
+ const sx=cx+xx*R, sy=cy+yy*R;
64
+ const depth=(zz+1)/2;
65
+ x.beginPath(); x.arc(sx,sy,depth*1.6+0.3,0,Math.PI*2);
66
+ const hue=depth>0.5?'0,217,255':'255,194,75';
67
+ x.fillStyle=`rgba(${hue},${depth*0.85+0.1})`; x.fill();
68
+ }
69
+ // 经纬弧
70
+ x.strokeStyle='rgba(41,243,255,.12)';x.lineWidth=1;
71
+ for(let m=0;m<6;m++){
72
+ x.beginPath();
73
+ for(let a=0;a<=64;a++){const t=a/64*Math.PI, p=m/6*Math.PI*2+rot;
74
+ const xx=Math.sin(t)*Math.cos(p),yy=Math.cos(t),zz=Math.sin(t)*Math.sin(p);
75
+ const sx=cx+xx*R,sy=cy+yy*R; a?x.lineTo(sx,sy):x.moveTo(sx,sy);}
76
+ x.stroke();
77
+ }
78
+ // 中央辉光
79
+ const g=x.createRadialGradient(cx,cy,0,cx,cy,R*0.5);
80
+ g.addColorStop(0,'rgba(0,217,255,.28)');g.addColorStop(1,'transparent');
81
+ x.fillStyle=g;x.beginPath();x.arc(cx,cy,R*0.5,0,Math.PI*2);x.fill();
82
+ requestAnimationFrame(loop);
83
+ }
84
+ loop();
85
+ })();
86
+
87
+ /* ====== 记忆向量空间 ====== */
88
+ (function(){
89
+ if (_lowpower) return; // 省电:不开记忆向量空间动画
90
+ const c=document.getElementById('memCanvas'); if(!c) return; // 右栏改版后无此装饰画布 → 静默跳过
91
+ const x=c.getContext('2d');
92
+ function size(){const r=c.parentElement.getBoundingClientRect();c.width=r.width;c.height=r.height;}
93
+ size();window.addEventListener('resize',size);
94
+ const nodes=[];for(let i=0;i<46;i++)nodes.push({x:Math.random(),y:Math.random(),z:Math.random(),s:Math.random()*2+1});
95
+ let rot=0;
96
+ function loop(){
97
+ x.clearRect(0,0,c.width,c.height);rot+=0.005;
98
+ const cx=c.width/2,cy=c.height/2;
99
+ nodes.forEach(n=>{
100
+ const a=n.x*Math.PI*2+rot, rad=(n.y*0.4+0.15)*Math.min(cx,cy);
101
+ const px=cx+Math.cos(a)*rad, py=cy+Math.sin(a)*rad*0.6+(n.z-0.5)*40;
102
+ n._x=px;n._y=py;
103
+ x.beginPath();x.arc(px,py,n.s,0,Math.PI*2);
104
+ x.fillStyle=n.z>0.5?'rgba(255,194,75,.9)':'rgba(0,217,255,.85)';x.fill();
105
+ });
106
+ x.strokeStyle='rgba(255,194,75,.12)';x.lineWidth=.5;
107
+ for(let i=0;i<nodes.length;i++)for(let j=i+1;j<nodes.length;j++){
108
+ const dx=nodes[i]._x-nodes[j]._x,dy=nodes[i]._y-nodes[j]._y,d=Math.hypot(dx,dy);
109
+ if(d<46){x.beginPath();x.moveTo(nodes[i]._x,nodes[i]._y);x.lineTo(nodes[j]._x,nodes[j]._y);x.stroke();}
110
+ }
111
+ requestAnimationFrame(loop);
112
+ }
113
+ loop();
114
+ })();
115
+
116
+ /* ====== 中枢数据流:核心球↔Agent 信息流动 ====== */
117
+ (function(){
118
+ if (_lowpower) return; // 省电:不开中枢数据流 + 2.2s 粒子 emit
119
+ const cv=document.getElementById('streamCanvas'); if(!cv)return;
120
+ const ctx=cv.getContext('2d');
121
+ const center=document.querySelector('.center');
122
+ function resize(){ const r=center.getBoundingClientRect(); cv.width=r.width; cv.height=r.height; }
123
+ resize(); window.addEventListener('resize',resize);
124
+ function pts(){
125
+ const cr=center.getBoundingClientRect();
126
+ const core=document.querySelector('.corewrap');
127
+ const coreC=core?core.getBoundingClientRect():cr;
128
+ const origin={x:coreC.left-cr.left+coreC.width/2, y:coreC.top-cr.top+coreC.height/2};
129
+ const targets=[...document.querySelectorAll('.acard')].map(a=>{
130
+ const b=a.getBoundingClientRect();
131
+ return {x:b.left-cr.left+b.width/2, y:b.top-cr.top+b.height/2,
132
+ color:getComputedStyle(a).getPropertyValue('--ac').trim()||'#00d9ff'};
133
+ });
134
+ return {origin,targets};
135
+ }
136
+ let particles=[];
137
+ function emit(targetAgent){
138
+ const {origin,targets}=pts();
139
+ if(!targets.length)return;
140
+ let t;
141
+ if(targetAgent){
142
+ // 定向:射向指定 Agent(发任务时用)
143
+ const cards=[...document.querySelectorAll('.acard')];
144
+ const idx=cards.findIndex(c=>c.dataset.agent===targetAgent);
145
+ t=idx>=0?targets[idx]:targets[Math.floor(Math.random()*targets.length)];
146
+ }else{
147
+ t=targets[Math.floor(Math.random()*targets.length)];
148
+ }
149
+ const reverse=targetAgent?false:Math.random()<0.4; // 发任务恒为 核心→Agent
150
+ particles.push({
151
+ sx:reverse?t.x:origin.x, sy:reverse?t.y:origin.y,
152
+ ex:reverse?origin.x:t.x, ey:reverse?origin.y:t.y,
153
+ x:reverse?t.x:origin.x, y:reverse?t.y:origin.y,
154
+ p:0, speed:0.012+Math.random()*0.01, color:t.color, trail:[]
155
+ });
156
+ }
157
+ window._emitStream=emit; // 暴露给发任务联动
158
+ function ease(t){ return t<.5?2*t*t:1-Math.pow(-2*t+2,2)/2; }
159
+ function hexA(c,a){
160
+ c=c||'#00d9ff';
161
+ if(c.startsWith('rgb'))return c.replace(/rgba?\(([^)]+)\)/,(m,p)=>{const v=p.split(',').slice(0,3).join(','); return `rgba(${v},${a})`;});
162
+ let h=c.replace('#',''); if(h.length===3)h=h.split('').map(x=>x+x).join('');
163
+ const n=parseInt(h,16); return `rgba(${(n>>16)&255},${(n>>8)&255},${n&255},${a})`;
164
+ }
165
+ function loop(){
166
+ ctx.clearRect(0,0,cv.width,cv.height);
167
+ particles.forEach(pt=>{
168
+ pt.p+=pt.speed;
169
+ pt.x=pt.sx+(pt.ex-pt.sx)*ease(pt.p);
170
+ pt.y=pt.sy+(pt.ey-pt.sy)*ease(pt.p);
171
+ pt.trail.push({x:pt.x,y:pt.y}); if(pt.trail.length>14)pt.trail.shift();
172
+ pt.trail.forEach((tp,i)=>{
173
+ const a=(i/pt.trail.length)*(1-pt.p)*0.7;
174
+ ctx.beginPath(); ctx.arc(tp.x,tp.y,1.6,0,7);
175
+ ctx.fillStyle=hexA(pt.color,a); ctx.fill();
176
+ });
177
+ ctx.beginPath(); ctx.arc(pt.x,pt.y,2.4,0,7);
178
+ ctx.fillStyle=hexA(pt.color,(1-pt.p)*0.9); ctx.shadowBlur=8; ctx.shadowColor=pt.color; ctx.fill(); ctx.shadowBlur=0;
179
+ });
180
+ particles=particles.filter(pt=>pt.p<1);
181
+ requestAnimationFrame(loop);
182
+ }
183
+ loop();
184
+ setInterval(emit, 2200);
185
+ setTimeout(emit, 800);
186
+ })();
187
+
188
+ /* ====== 系统活动波形(已移除,保留空值保护)====== */
189
+ (function(){
190
+ if (_lowpower) return; // 省电:不开系统活动波形
191
+ const c=document.getElementById('actCanvas'); if(!c)return;
192
+ const x=c.getContext('2d');
193
+ function size(){const r=c.parentElement.getBoundingClientRect();c.width=r.width;c.height=90;}
194
+ size();window.addEventListener('resize',size);
195
+ let t=0;
196
+ function loop(){
197
+ x.clearRect(0,0,c.width,c.height);t+=0.03;
198
+ if(window.actPulse>0)window.actPulse*=0.96;
199
+ const boost=1+(window.actPulse||0);
200
+ [[ '0,217,255',0],['255,194,75',1.5]].forEach(([col,ph])=>{
201
+ x.beginPath();
202
+ for(let i=0;i<=c.width;i+=4){
203
+ const y=c.height/2+Math.sin(i*0.025+t+ph)*18*boost*Math.sin(i*0.006+t*0.5)+
204
+ Math.sin(i*0.05+t*1.3+ph)*8*boost;
205
+ i?x.lineTo(i,y):x.moveTo(i,y);
206
+ }
207
+ x.strokeStyle=`rgba(${col},.7)`;x.lineWidth=2;x.shadowBlur=8;x.shadowColor=`rgba(${col},.6)`;x.stroke();x.shadowBlur=0;
208
+ });
209
+ requestAnimationFrame(loop);
210
+ }
211
+ loop();
212
+ })();