foxclaw 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) 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/CHANGELOG.md +57 -0
  6. package/python/CHANGELOG_CN.md +57 -0
  7. package/python/README.md +11 -2
  8. package/python/README_CN.md +32 -20
  9. package/python/bench/bm25_audit.py +1 -14
  10. package/python/bench/bm25_regression.py +0 -1
  11. package/python/bench/bm25_stress.py +0 -4
  12. package/python/bench/cd_audit.py +0 -23
  13. package/python/bench/chaos.py +6 -8
  14. package/python/bench/e2e_real_models.py +0 -1
  15. package/python/bench/llm_retry_test.py +2 -9
  16. package/python/bench/load_test.py +5 -8
  17. package/python/bench/vector_ivf_upgrade.py +1 -7
  18. package/python/docs/RELEASE.md +4 -3
  19. package/python/docs/RUN_CN.md +2 -2
  20. package/python/evals/governance/cases.py +0 -37
  21. package/python/evals/governance/run_evals.py +5 -37
  22. package/python/examples/governance_middleware_demo.py +1 -6
  23. package/python/examples/langgraph_governance_demo.py +7 -11
  24. package/python/foxclaw/__init__.py +34 -152
  25. package/python/foxclaw/agents/base.py +65 -136
  26. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  27. package/python/foxclaw/agents/impl/finance.py +4 -21
  28. package/python/foxclaw/agents/impl/office.py +7 -24
  29. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  30. package/python/foxclaw/agents/mixins/_context.py +0 -23
  31. package/python/foxclaw/agents/mixins/augment.py +10 -16
  32. package/python/foxclaw/agents/mixins/llm.py +7 -6
  33. package/python/foxclaw/agents/mixins/memory.py +7 -1
  34. package/python/foxclaw/agents/mixins/preference.py +58 -8
  35. package/python/foxclaw/agents/mixins/prompt.py +0 -6
  36. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  37. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  38. package/python/foxclaw/api/__init__.py +18 -77
  39. package/python/foxclaw/api/_state.py +43 -0
  40. package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
  41. package/python/foxclaw/api/middleware.py +5 -28
  42. package/python/foxclaw/api/routes/ahs.py +60 -73
  43. package/python/foxclaw/api/routes/attachments.py +5 -6
  44. package/python/foxclaw/api/routes/browser.py +7 -11
  45. package/python/foxclaw/api/routes/domain.py +18 -26
  46. package/python/foxclaw/api/routes/knowledge.py +3 -21
  47. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  48. package/python/foxclaw/api/routes/memory.py +28 -12
  49. package/python/foxclaw/api/routes/orchestration.py +3 -5
  50. package/python/foxclaw/api/routes/recovery.py +4 -3
  51. package/python/foxclaw/api/routes/review.py +2 -11
  52. package/python/foxclaw/api/routes/search.py +3 -7
  53. package/python/foxclaw/api/routes/system.py +33 -49
  54. package/python/foxclaw/api/routes/task.py +31 -49
  55. package/python/foxclaw/api/routes/ws.py +2 -6
  56. package/python/foxclaw/api/spine.py +1 -4
  57. package/python/foxclaw/browser/action/action_executor.py +1 -5
  58. package/python/foxclaw/browser/action/action_types.py +0 -1
  59. package/python/foxclaw/browser/action/url_policy.py +1 -5
  60. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  61. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  62. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  63. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  64. package/python/foxclaw/browser/service.py +7 -11
  65. package/python/foxclaw/capability/embedding_service.py +4 -6
  66. package/python/foxclaw/channels/__init__.py +9 -11
  67. package/python/foxclaw/channels/discord.py +4 -13
  68. package/python/foxclaw/channels/dispatcher.py +2 -27
  69. package/python/foxclaw/channels/feishu.py +10 -20
  70. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  71. package/python/foxclaw/channels/signing.py +9 -9
  72. package/python/foxclaw/channels/telegram.py +3 -4
  73. package/python/foxclaw/channels/weixin.py +7 -17
  74. package/python/foxclaw/cli.py +42 -13
  75. package/python/foxclaw/cognitive/reasoner.py +9 -10
  76. package/python/foxclaw/cognitive/state.py +18 -24
  77. package/python/foxclaw/core/audit_chain.py +1 -3
  78. package/python/foxclaw/core/audit_sink.py +3 -3
  79. package/python/foxclaw/core/compaction.py +8 -26
  80. package/python/foxclaw/core/config.py +2 -2
  81. package/python/foxclaw/core/confirm_nonce.py +6 -12
  82. package/python/foxclaw/core/envfile.py +2 -2
  83. package/python/foxclaw/core/failure_policy.py +17 -26
  84. package/python/foxclaw/core/gateway.py +6 -10
  85. package/python/foxclaw/core/governance_audit.py +3 -7
  86. package/python/foxclaw/core/idempotency.py +15 -22
  87. package/python/foxclaw/core/injection_guard.py +10 -29
  88. package/python/foxclaw/core/memory_adapter.py +30 -57
  89. package/python/foxclaw/core/metrics.py +4 -5
  90. package/python/foxclaw/core/model_router.py +36 -148
  91. package/python/foxclaw/core/paths.py +1 -1
  92. package/python/foxclaw/core/recovery.py +21 -34
  93. package/python/foxclaw/core/scheduler.py +23 -26
  94. package/python/foxclaw/core/secrets.py +0 -3
  95. package/python/foxclaw/core/sessions.py +4 -6
  96. package/python/foxclaw/core/settings.py +16 -32
  97. package/python/foxclaw/core/shared_write.py +11 -23
  98. package/python/foxclaw/core/steering_queue.py +12 -11
  99. package/python/foxclaw/core/system_bus.py +1 -1
  100. package/python/foxclaw/core/thought_chain.py +30 -32
  101. package/python/foxclaw/core/token_meter.py +9 -13
  102. package/python/foxclaw/core/tools.py +4 -12
  103. package/python/foxclaw/core/tracing.py +25 -40
  104. package/python/foxclaw/core/truth_pipeline.py +5 -9
  105. package/python/foxclaw/core/update_check.py +149 -0
  106. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  107. package/python/foxclaw/core/verify.py +6 -14
  108. package/python/foxclaw/core/web_search.py +4 -28
  109. package/python/foxclaw/finance/evidence.py +14 -18
  110. package/python/foxclaw/finance/fx.py +1 -3
  111. package/python/foxclaw/finance/graph.py +9 -18
  112. package/python/foxclaw/finance/models.py +20 -24
  113. package/python/foxclaw/finance/nlparse.py +12 -72
  114. package/python/foxclaw/finance/policy.py +8 -36
  115. package/python/foxclaw/finance/reasoner.py +3 -11
  116. package/python/foxclaw/finance/reasoning.py +13 -34
  117. package/python/foxclaw/finance/reconcile.py +5 -10
  118. package/python/foxclaw/finance/runtime.py +19 -46
  119. package/python/foxclaw/governance/middleware.py +13 -21
  120. package/python/foxclaw/harness/_base.py +2 -8
  121. package/python/foxclaw/harness/_policy.py +19 -38
  122. package/python/foxclaw/harness/drift.py +1 -13
  123. package/python/foxclaw/harness/event_bus.py +2 -5
  124. package/python/foxclaw/harness/harness.py +9 -97
  125. package/python/foxclaw/harness/loop_detection.py +11 -18
  126. package/python/foxclaw/itinerary/amap_client.py +2 -5
  127. package/python/foxclaw/itinerary/models.py +7 -7
  128. package/python/foxclaw/itinerary/reasoner.py +1 -6
  129. package/python/foxclaw/itinerary/reasoning.py +15 -21
  130. package/python/foxclaw/itinerary/runtime.py +8 -12
  131. package/python/foxclaw/kernel/capabilities.py +5 -6
  132. package/python/foxclaw/kernel/policy_gate.py +4 -10
  133. package/python/foxclaw/kernel/process_manager.py +17 -36
  134. package/python/foxclaw/kernel/scheduler.py +3 -9
  135. package/python/foxclaw/kernel/sentinel.py +13 -23
  136. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  137. package/python/foxclaw/kernel/spine.py +9 -9
  138. package/python/foxclaw/knowledge/__init__.py +4 -20
  139. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  140. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  141. package/python/foxclaw/learning/models.py +13 -13
  142. package/python/foxclaw/learning/reasoner.py +1 -4
  143. package/python/foxclaw/learning/runtime.py +19 -31
  144. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  145. package/python/foxclaw/mcp_server.py +2 -3
  146. package/python/foxclaw/orchestration/__init__.py +16 -33
  147. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  148. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  149. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  150. package/python/foxclaw/planning/llm_planner.py +3 -3
  151. package/python/foxclaw/planning/planner.py +6 -11
  152. package/python/foxclaw/search/agent_port.py +7 -11
  153. package/python/foxclaw/search/evidence.py +0 -4
  154. package/python/foxclaw/search/internal_search.py +3 -5
  155. package/python/foxclaw/search/models.py +11 -13
  156. package/python/foxclaw/search/planner.py +3 -3
  157. package/python/foxclaw/search/retriever.py +1 -1
  158. package/python/foxclaw/search/runtime.py +1 -8
  159. package/python/foxclaw/skills/__init__.py +46 -100
  160. package/python/foxclaw/skills/archive.py +111 -0
  161. package/python/foxclaw/skills/fetch.py +226 -0
  162. package/python/foxclaw/skills/hooks_spec.py +21 -25
  163. package/python/foxclaw/skills/install_cmd.py +216 -0
  164. package/python/foxclaw/skills/lifecycle.py +16 -42
  165. package/python/foxclaw/skills/markdown_skill.py +10 -9
  166. package/python/foxclaw/skills/mcp_client.py +5 -11
  167. package/python/foxclaw/skills/mcp_store.py +0 -3
  168. package/python/foxclaw/skills/permission.py +13 -48
  169. package/python/foxclaw/skills/pipeline.py +21 -53
  170. package/python/foxclaw/skills/policy.py +4 -10
  171. package/python/foxclaw/skills/review.py +3 -3
  172. package/python/frontend/deck/auth.js +0 -7
  173. package/python/frontend/deck/chat_model_picker.js +0 -54
  174. package/python/frontend/deck/domain.js +0 -20
  175. package/python/frontend/deck/main.js +0 -144
  176. package/python/frontend/deck/markdown.js +0 -4
  177. package/python/frontend/deck/settings.js +1 -18
  178. package/python/frontend/deck/state.js +0 -1
  179. package/python/frontend/deck/util.js +0 -2
  180. package/python/frontend/deck/visuals.js +0 -22
  181. package/python/frontend/planet/planet.js +0 -201
  182. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  183. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  184. package/python/frontend/v3/index.html +3 -3
  185. package/python/frontend/widget_runtime.js +0 -43
  186. package/python/frontend/widgets.js +0 -57
  187. package/python/frontend_src/index.html +1 -1
  188. package/python/frontend_src/package-lock.json +2 -2
  189. package/python/frontend_src/package.json +1 -1
  190. package/python/frontend_src/src/App.vue +3 -7
  191. package/python/frontend_src/src/api.js +0 -7
  192. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  193. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  194. package/python/frontend_src/src/components/Inspector.vue +0 -6
  195. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  196. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  197. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  198. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  199. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  200. package/python/frontend_src/src/components/Stage.vue +71 -25
  201. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  202. package/python/frontend_src/src/components/TopBar.vue +38 -10
  203. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  204. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  205. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  206. package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
  207. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  208. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  209. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  210. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  211. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  212. package/python/frontend_src/src/lib/fmt.js +0 -4
  213. package/python/frontend_src/src/lib/i18n.js +53 -0
  214. package/python/frontend_src/src/lib/locales/en.js +173 -0
  215. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  216. package/python/frontend_src/src/lib/md.js +0 -14
  217. package/python/frontend_src/src/main.js +2 -0
  218. package/python/frontend_src/src/stores/app.js +17 -32
  219. package/python/frontend_src/src/stores/chat.js +0 -34
  220. package/python/frontend_src/src/stores/model.js +0 -8
  221. package/python/frontend_src/src/stores/panels.js +0 -41
  222. package/python/frontend_src/src/stores/profile.js +31 -0
  223. package/python/frontend_src/src/stores/system.js +25 -0
  224. package/python/frontend_src/vite.config.js +0 -7
  225. package/python/pyproject.toml +6 -3
  226. package/python/requirements.txt +311 -0
  227. package/python/tests/conftest.py +0 -22
  228. package/python/tests/run_all.py +3 -66
  229. package/python/tests/test_agent_lifecycle.py +4 -38
  230. package/python/tests/test_ahs_p2.py +4 -25
  231. package/python/tests/test_anthropic_tools.py +1 -15
  232. package/python/tests/test_api.py +19 -9
  233. package/python/tests/test_api_auth.py +1 -5
  234. package/python/tests/test_attachments.py +9 -25
  235. package/python/tests/test_audit_chain.py +2 -7
  236. package/python/tests/test_audit_sink.py +0 -6
  237. package/python/tests/test_browser.py +2 -6
  238. package/python/tests/test_browser_request_guard.py +0 -4
  239. package/python/tests/test_capabilities.py +0 -6
  240. package/python/tests/test_channel_signing.py +2 -9
  241. package/python/tests/test_channels.py +1 -11
  242. package/python/tests/test_config_layering.py +0 -4
  243. package/python/tests/test_confirm_nonce.py +1 -12
  244. package/python/tests/test_context_compaction_wiring.py +1 -7
  245. package/python/tests/test_dispatch.py +0 -6
  246. package/python/tests/test_domain_evals.py +11 -80
  247. package/python/tests/test_domain_import.py +0 -6
  248. package/python/tests/test_domain_reasoners.py +0 -14
  249. package/python/tests/test_e2e.py +0 -17
  250. package/python/tests/test_emb_cache.py +7 -17
  251. package/python/tests/test_embedding_singleton.py +1 -3
  252. package/python/tests/test_evals_smoke.py +0 -24
  253. package/python/tests/test_event_spine.py +10 -18
  254. package/python/tests/test_evidence_semantic.py +1 -14
  255. package/python/tests/test_evidence_sse.py +3 -6
  256. package/python/tests/test_file_ingest.py +0 -11
  257. package/python/tests/test_finance.py +0 -9
  258. package/python/tests/test_finance_ledger.py +5 -12
  259. package/python/tests/test_finance_persist.py +8 -12
  260. package/python/tests/test_gateway.py +3 -22
  261. package/python/tests/test_governance.py +5 -52
  262. package/python/tests/test_governance_audit.py +4 -39
  263. package/python/tests/test_governance_middleware.py +1 -7
  264. package/python/tests/test_hkc.py +6 -24
  265. package/python/tests/test_hkc_index_build_manager.py +0 -33
  266. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  267. package/python/tests/test_hkc_search_event_sync.py +3 -54
  268. package/python/tests/test_import_path_guard.py +0 -6
  269. package/python/tests/test_injection_guard.py +18 -36
  270. package/python/tests/test_integration.py +5 -29
  271. package/python/tests/test_itinerary.py +0 -3
  272. package/python/tests/test_kee_semantic.py +0 -5
  273. package/python/tests/test_kernel.py +1 -11
  274. package/python/tests/test_learning.py +3 -12
  275. package/python/tests/test_llm_admin.py +4 -19
  276. package/python/tests/test_llm_planner.py +1 -23
  277. package/python/tests/test_markdown_skills.py +3 -14
  278. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  279. package/python/tests/test_mcp_connectors.py +3 -15
  280. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  281. package/python/tests/test_mcp_no_memory.py +3 -3
  282. package/python/tests/test_mcp_policy_gate.py +0 -1
  283. package/python/tests/test_mcp_server.py +6 -9
  284. package/python/tests/test_memory_recall.py +1 -4
  285. package/python/tests/test_metrics.py +0 -8
  286. package/python/tests/test_multi_chain.py +1 -13
  287. package/python/tests/test_multimodal.py +5 -18
  288. package/python/tests/test_negotiation.py +3 -13
  289. package/python/tests/test_orchestration.py +1 -11
  290. package/python/tests/test_orchestration_decoupled.py +0 -6
  291. package/python/tests/test_p3_p5_hardening.py +3 -17
  292. package/python/tests/test_parallel_dispatch.py +4 -12
  293. package/python/tests/test_pipeline.py +17 -58
  294. package/python/tests/test_plan_visible.py +4 -4
  295. package/python/tests/test_planner.py +0 -7
  296. package/python/tests/test_policy_gate.py +2 -3
  297. package/python/tests/test_profile.py +67 -0
  298. package/python/tests/test_readme_claims.py +163 -0
  299. package/python/tests/test_reasoner.py +2 -17
  300. package/python/tests/test_release_check.py +2 -13
  301. package/python/tests/test_resume_checkpoint.py +7 -15
  302. package/python/tests/test_routing.py +2 -12
  303. package/python/tests/test_scheduler.py +7 -10
  304. package/python/tests/test_search.py +1 -11
  305. package/python/tests/test_secrets.py +4 -10
  306. package/python/tests/test_semantic.py +1 -10
  307. package/python/tests/test_sessions.py +3 -4
  308. package/python/tests/test_settings.py +2 -18
  309. package/python/tests/test_shadow_learning.py +3 -43
  310. package/python/tests/test_shared_write.py +0 -7
  311. package/python/tests/test_skill_taint_gate.py +1 -10
  312. package/python/tests/test_skills.py +2 -18
  313. package/python/tests/test_skills_fetch.py +282 -0
  314. package/python/tests/test_stats_and_events.py +2 -5
  315. package/python/tests/test_streaming.py +2 -7
  316. package/python/tests/test_supervisor_pipeline.py +4 -5
  317. package/python/tests/test_supervisor_triage.py +4 -10
  318. package/python/tests/test_taint.py +2 -7
  319. package/python/tests/test_task_stream.py +1 -12
  320. package/python/tests/test_thinking_sse.py +0 -1
  321. package/python/tests/test_tool_center.py +1 -17
  322. package/python/tests/test_tool_parallel.py +2 -2
  323. package/python/tests/test_tool_params.py +1 -5
  324. package/python/tests/test_tool_use.py +0 -13
  325. package/python/tests/test_toplevel_concurrency.py +3 -4
  326. package/python/tests/test_tracing.py +7 -41
  327. package/python/tests/test_truth_crystallizer.py +4 -6
  328. package/python/tests/test_truth_flow.py +2 -7
  329. package/python/tests/test_update_check.py +95 -0
  330. package/python/tests/test_verify_evidence.py +8 -21
  331. package/python/tests/test_version_sync.py +35 -6
  332. package/python/tests/test_web_trigger.py +0 -17
  333. package/python/tools/check_faiss_type.py +0 -1
  334. package/python/tools/check_log.ps1 +0 -8
  335. package/python/tools/mypy_baseline.txt +12 -4
  336. package/python/tools/mypy_ratchet.py +52 -6
  337. package/python/tools/pack.py +0 -5
  338. package/python/tools/release_check.py +7 -7
  339. package/python/tools/restart_foxclaw.ps1 +5 -8
  340. package/python/tools/shared_write_audit.py +1 -15
  341. package/python/tools/size_check.py +1 -1
  342. package/python/tools/strip_comments.py +228 -0
  343. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  344. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -1,4 +1,3 @@
1
- // 缓存刷新:改动子模块时,连同顶栏版本号一起把 ?v= 递增(零构建下的显式 cache-bust)
2
1
  import './auth.js?v=5.37';
3
2
  import { escapeHtml } from './util.js?v=2.9';
4
3
  import { renderMarkdown, renderReply } from './markdown.js?v=2.9';
@@ -8,22 +7,14 @@ import { fetchAMapConfig, kpSet, renderFinance, renderLearning, renderItinerary,
8
7
  import { openSetMenu, switchProvider, uiPref, openAuthSetting } from './settings.js?v=5.36'; // uiPref.ballAnim 默认 false
9
8
  import './chat_model_picker.js?v=1.4'; // chat 输入框旁的下拉式模型选择器;v1.4 保存即切换 + BASE URL 自动补 /v1 + 模型 ID 软提示
10
9
 
11
- /* ════════════ 后端数据层:接真实 Foxclaw ════════════ */
12
-
13
-
14
-
15
10
  let activeAgent='supervisor';
16
- // 三个 Agent(supervisor/finance/office) + 一个**视图** admin(行程地图,已不是 Agent)
17
11
  const AGENT_CN={supervisor:'主管',finance:'财务',office:'办公学习',admin:'行程'};
18
12
 
19
- /* 在线状态 */
20
13
  function setUplink(on){
21
14
  const el=document.getElementById('upStat');
22
15
  if(el){el.textContent=on?'100%':'离线'; el.style.color=on?'var(--green)':'var(--amber)';}
23
- // 副标题固定为品牌语,不再被状态文字覆盖(状态已在右上角显示)
24
16
  }
25
17
 
26
- /* 鉴权提示条:后端 401 → 顶部弹一条,引导去「访问密钥」配置(只弹一次) */
27
18
  function showAuthHint(){
28
19
  if(document.getElementById('authHint')) return;
29
20
  const bar=document.createElement('div');
@@ -34,10 +25,8 @@ function showAuthHint(){
34
25
  document.getElementById('ah-go').onclick=()=>{ bar.remove(); openAuthSetting(); };
35
26
  document.getElementById('ah-x').onclick=()=>bar.remove();
36
27
  }
37
- // 鉴权注入模块(auth.js)检测到 401 时回调此函数弹提示条
38
28
  window.__foxclawAuthHint=showAuthHint;
39
29
 
40
- /* 1. 加载五 Agent 状态 */
41
30
  async function loadStatus(){
42
31
  try{
43
32
  const r=await fetch(`${API}/status`); if(!r.ok)throw 0;
@@ -50,10 +39,6 @@ async function loadStatus(){
50
39
  }catch(e){ setUplink(false); }
51
40
  }
52
41
 
53
- /* 2. 记忆中心真实统计 */
54
- /* 右栏「记忆」面板:四联统计 + 最近记忆信息条。
55
- 沿用 loadMemStats 这个名字 —— 全文已有 8 处调用点(状态刷新/录入后/定时),
56
- 改这一个函数即可让新面板处处自动刷新,无需再散布新调用。 */
57
42
  async function loadMemStats(){
58
43
  const set=(id,v)=>{const el=document.getElementById(id); if(el)el.textContent=v;};
59
44
  try{
@@ -66,10 +51,8 @@ async function loadMemStats(){
66
51
  set('memVec',(d.total_memories||0).toLocaleString()); // 兼容旧元素(若仍存在)
67
52
  set('memRel',(d.relations||0).toLocaleString());
68
53
  }catch(e){}
69
- // 会话列表现由 loadSessions() 渲染进同一右栏(记忆面板下),这里不再管"最近记忆列表"。
70
54
  }
71
55
 
72
- /* 右栏「知识库」面板:知识信息条 */
73
56
  async function loadKnowList(){
74
57
  const list=document.getElementById('knowList'); if(!list)return;
75
58
  try{
@@ -89,7 +72,6 @@ async function loadKnowList(){
89
72
  }catch(e){ list.innerHTML='<div class="kp-empty">后端未连接</div>'; }
90
73
  }
91
74
 
92
- /* 3. Agent 卡片点击切换 */
93
75
  document.querySelectorAll('.acard').forEach(card=>{
94
76
  card.addEventListener('click',()=>{
95
77
  document.querySelectorAll('.acard').forEach(c=>c.classList.remove('on'));
@@ -102,8 +84,6 @@ document.querySelectorAll('.acard').forEach(card=>{
102
84
  refreshAgentStatus();
103
85
  });
104
86
  });
105
- /* 顶栏右侧「财务/地图」面板入口:只开中央仪表盘,不改 activeAgent —— 对话统一走
106
- 输入框(主管自动分诊)。再点一次收起面板。 */
107
87
  document.querySelectorAll('.abtn').forEach(btn=>{
108
88
  btn.addEventListener('click',()=>{
109
89
  const ag=btn.dataset.agent, wasOn=btn.classList.contains('on');
@@ -113,7 +93,6 @@ document.querySelectorAll('.abtn').forEach(btn=>{
113
93
  loadKernel(ag); showInCore(ag);
114
94
  });
115
95
  });
116
- /* 统一刷新各卡状态文字:工作中 / 在线(选中) / 离线(灰) */
117
96
  function refreshAgentStatus(){
118
97
  document.querySelectorAll('.acard').forEach(c=>{
119
98
  const st=c.querySelector('.st-tx'); if(!st)return;
@@ -124,13 +103,6 @@ function refreshAgentStatus(){
124
103
  }
125
104
  refreshAgentStatus();
126
105
 
127
-
128
- /* ===== 中央焦点空间:激活 Agent 显示其当前内容,空闲回到全息球 ===== */
129
- /* ═══ 中间栏输出中枢(Center)═══════════════════════════════════════════════
130
- 中间栏有两个叠放的输出层 —— 对话浮层 coreChat(z5)/ 面板层 coreStage(z3),外加背后的 3D 核心。
131
- 过去每个入口自己开关这两层,常忘关另一层 → 页面盖着不退、白白渲染(用户实测踩到)。
132
- 现在**统一由 Center 管**:凡要在中间栏显示东西,一律走 Center.panel()/chat()/home(),
133
- 同一时刻只有一个视图活着,天然互斥,不必再各处补"记得关另一层"。 */
134
106
  const Center = {
135
107
  mode:'home',
136
108
  panel(){ // 打开面板层(知识/模块/领域视图);返回 #coreStage 供调用方写 innerHTML
@@ -157,10 +129,8 @@ const Center = {
157
129
  function clearCore(){ Center.home(); _syncNav('overview'); } // 兼容旧调用名(9 处)→ 统一走中枢
158
130
  async function showInCore(agent){
159
131
  const wrap=document.querySelector('.corewrap'), stage=document.getElementById('coreStage');
160
- // 打开中间栏面板前先关掉对话浮层(core-chat z5 会盖住 core-stage z3,否则点侧栏"像没反应")
161
132
  document.getElementById('coreChat')?.classList.remove('show');
162
133
  const m=KP_META[agent]||KP_META.office;
163
- // 主管空闲态:回到全息球(它没有"专属任务画面")
164
134
  if(agent==='supervisor'){ clearCore(); return; }
165
135
  Center.panel();
166
136
  stage.innerHTML=`<div class="cs-head"><div class="t"><span class="dot"></span>${m.title} <em>${m.tag}</em></div>
@@ -178,7 +148,6 @@ async function showInCore(agent){
178
148
  setTimeout(()=>{drawAMapInto('coreMap',d.map_data||[]); initMapSearch();},60);
179
149
  }else if(agent==='office'){
180
150
  const items=(d.items||[]);
181
- // 摄入入口必须常在(此前只挂在右栏内核面板,右栏改版后就消失了 —— 回归过一次,别再删)
182
151
  const ingest='<div class="ingest-row" style="max-width:520px;margin:0 auto 14px">'
183
152
  +'<button class="ing-btn" onclick="document.getElementById(\'cmdAttach\')?.click()">📄 导入文件</button>'
184
153
  +'<button class="ing-btn" onclick="document.getElementById(\'cmdFolder\')?.click()">📁 导入文件夹</button>'
@@ -195,7 +164,6 @@ async function showInCore(agent){
195
164
  }catch(e){ body.innerHTML='<div class="kp-empty">后端未连接</div>'; }
196
165
  }
197
166
 
198
- /* 3b. 内核详情面板:随 Agent 切换展示对应内核数据 */
199
167
  const KP_META={
200
168
  supervisor:{title:'全局态势',tag:'Situation'},
201
169
  finance:{title:'财务驾驶舱',tag:'Finance'},
@@ -206,8 +174,6 @@ const KP_META={
206
174
  async function loadKernel(agent){
207
175
  const m=KP_META[agent]||KP_META.office;
208
176
  try{
209
- // 每个分支独立容错:数据请求失败(如首次 HKC 装配慢/500)→ 用空数据 {} 渲染面板,
210
- // **保留录入/导入入口**(它们不依赖数据),而不是抛到外层 catch 把整个面板变成"后端未连接"。
211
177
  if(agent==='finance'){
212
178
  const d=await fetch(`${API}/finance/analysis?period=2026-06&current_balance=11000`).then(r=>r.json()).catch(()=>({}));
213
179
  _lastKernelData.finance=d; kpSet(m.title,m.tag,renderFinance(d||{}));
@@ -227,7 +193,6 @@ async function loadKernel(agent){
227
193
  }catch(e){ kpSet(m.title,m.tag,'<div class="kp-empty">后端未连接 · 该面板需真实数据</div>'); }
228
194
  }
229
195
 
230
- /* 领域文件导入:上传 csv/xlsx/json → /api/{domain}/import/file(超越 csv_path)*/
231
196
  function importDomainFile(agent){
232
197
  const map={finance:{d:'finance',cn:'交易'}, office:{d:'learning',cn:'知识点'}, admin:{d:'itinerary',cn:'行程点'}};
233
198
  const m=map[agent]||map.finance;
@@ -249,8 +214,6 @@ function importDomainFile(agent){
249
214
  inp.click();
250
215
  }
251
216
 
252
- /* 4. 发任务到后端 */
253
- /* Agent 忙碌态:有任务时卡片亮主题色 + 显示任务标题 */
254
217
  const agentTasks={};
255
218
  function setAgentBusy(agent, task){
256
219
  agentTasks[agent]=task;
@@ -275,20 +238,15 @@ async function transmit(){
275
238
  const inp=document.getElementById('cmd'); let task=(inp.value||'').trim();
276
239
  const hasImgs=_pendingImages.length>0, hasFiles=_pendingFiles.length>0;
277
240
  if(!task && !hasImgs && !hasFiles)return;
278
- // 斜杠指令:以 / 开头交给命令处理器,不发给后端(如 /搜索 记忆架构、/工具、/清库 确认)
279
241
  if(task[0]==='/'){ inp.value=''; hideSlashMenu(); runSlash(task); return; }
280
242
  inp.value='';
281
243
  if((hasImgs||hasFiles) && !task) task=hasImgs?'请描述并分析这张图片':'请阅读并总结这个文件';
282
244
  prependTask(task,activeAgent,'pending');
283
- // 标记当前 Agent 忙碌 + 显示任务标题
284
245
  setAgentBusy(activeAgent, task);
285
- // 数据流:核心连发数束光点射向当前 Agent(看得见它在工作)
286
246
  if(window._emitStream){ for(let i=0;i<3;i++) setTimeout(()=>window._emitStream(activeAgent), i*180); }
287
- // 弹出回复浮窗(加载态);带图则把用户图片缩略图渲染进提问区
288
247
  const qThumbs=hasImgs?_pendingImages.map(x=>x.url):null;
289
248
  const qFiles=hasFiles?_pendingFiles.map(x=>({name:x.name,size:x.size,url:x.url})):null;
290
249
  showReplyPop(task, activeAgent, false, qThumbs, qFiles);
291
- // 多模态/附件:带图或带文件必走流式(/chat/stream 消费 images/attachments);发送后清空附件条
292
250
  if(hasImgs || hasFiles){
293
251
  const imgs=hasImgs?_pendingImages.map(x=>({media_type:x.media_type, data:x.data})):null;
294
252
  const atts=hasFiles?_pendingFiles.map(x=>({name:x.name,size:x.size,mime:x.mime,url:x.url,text:x.text})):null;
@@ -297,10 +255,7 @@ async function transmit(){
297
255
  return transmitStream(task, activeAgent, imgs, atts);
298
256
  }
299
257
  if(chatModeStream){ _refTriggers={}; return transmitStream(task, activeAgent); }
300
- // 任务模式:@引用把技能触发词带入后端 task(用户可见输入保留 @名称)
301
258
  const outTask=applyRefs(task);
302
- // 领域 Agent 任务 → 流式认知管线(逐 stage 进度 + act 逐字);主管编排任务不支持流式
303
- // (/api/task/stream 只跑单 Agent 管线、不做多 Agent 分派),仍走阻塞 /api/task 保住编排能力。
304
259
  if(activeAgent!=='supervisor'){ _refTriggers={}; return transmitTaskStream(outTask, activeAgent); }
305
260
  try{
306
261
  const r=await fetch(`${API}/task`,{method:'POST',headers:{'Content-Type':'application/json'},
@@ -312,7 +267,6 @@ async function transmit(){
312
267
  }else{
313
268
  markTask(task,'ok');
314
269
  const res=data.result||{};
315
- // 多 Agent 顺序执行:展示每一步的子任务 + 处理 Agent + 回复
316
270
  if(res.dispatched_to==='multi' && Array.isArray(res.multi_steps)){
317
271
  let html=`<div style="margin-bottom:10px;color:var(--cyan);font-weight:600">${escapeHtml(res.summary||'多步任务')}</div>`;
318
272
  res.multi_steps.forEach(s=>{
@@ -332,7 +286,6 @@ async function transmit(){
332
286
  let via='';
333
287
  if(res.dispatched_to&&res.dispatched_to!=='supervisor'&&res.dispatched_to!==activeAgent){
334
288
  via=`⤷ 主管已分派给 ${AGENT_CN[res.dispatched_to]||res.dispatched_to} Agent 处理`;
335
- // 实际处理的 Agent 也标忙碌
336
289
  setAgentBusy(res.dispatched_to, task);
337
290
  }else if(res.self_answered){
338
291
  via='⤷ 主管直接答复';
@@ -345,13 +298,9 @@ async function transmit(){
345
298
  fillReplyPop('', '请求失败:后端未连接,或模型未配置 Key', 'err');
346
299
  }
347
300
  }
348
- /* 回复显示在中央对话层(最上层)*/
349
301
  function showReplyPop(task, agent, force, qImages, qFiles){
350
- // 偏好关闭则不弹被动回复;但 force=true(如用户主动发起的文件摄入)必须可见,
351
- // 否则点了 📎 却因偏好静默 → 屏幕零反应,以为坏了。
352
302
  if(!force && typeof uiPref!=='undefined' && !uiPref.autoChat) return;
353
303
  document.getElementById('ccAgent').textContent=`${AGENT_CN[agent]||agent} Agent`;
354
- // 重置为单轮结构(对话线程 renderChatThread 可能已替换过 cc-body,这里恢复 ccQ/ccReply/ccStatus)
355
304
  const _b=document.querySelector('#coreChat .cc-body');
356
305
  if(_b && !document.getElementById('ccQ'))
357
306
  _b.innerHTML='<div class="cc-q" id="ccQ"></div><div id="ccReply"></div><div class="cc-status" id="ccStatus"></div>';
@@ -380,10 +329,6 @@ document.getElementById('ccClose')?.addEventListener('click',()=>document.getEle
380
329
  document.querySelector('.cmdbar .send')?.addEventListener('click',transmit);
381
330
  document.getElementById('cmd')?.addEventListener('keydown',e=>{if(e.key==='Enter')transmit();});
382
331
 
383
-
384
- /* ── 附件摄入:chat 栏 📎 → 文件进 HKC 知识库(异步 + 进度轮询)──────────────
385
- 交互层与知识层打通:对话框既能聊天,也能直接喂文件蒸馏成长期知识。
386
- 走 /knowledge/ingest/file/async(秒返 job_id,后台线程池抽取),前端轮询进度。*/
387
332
  const _INGEST_MAX_BYTES=20*1024*1024;
388
333
  let _ingestBusy=false; // 串行守卫:一次只摄入一个文件,避免并发轮询串写同一进度弹层
389
334
  (function(){
@@ -400,21 +345,16 @@ let _ingestBusy=false; // 串行守卫:一次只摄入一个文件,避免并
400
345
  try{ await ingestChatFile(f); } finally{ _ingestBusy=false; }
401
346
  });
402
347
  })();
403
- /* 若知识星球 iframe 已打开,刷新它拉最新图(同源 /planet/);未打开无需动作(下次懒加载自然是新数据)*/
404
348
  function _refreshPlanet(){
405
349
  try{ const fr=document.getElementById('planetFrame'); const w=fr&&fr.contentWindow;
406
350
  if(w && fr.src && fr.src!=='about:blank' && typeof w.loadData==='function') w.loadData(); }catch(e){}
407
351
  }
408
- /* 摄入单个文件:上传 + 轮询,返回 {ok,ku_count,error,skipped};不弹窗(由调用方展示),不抛异常。
409
- onStatus(msg) 可选进度回调。供单文件(📎)与文件夹(📁)逐个摄入共用。 */
410
352
  async function _ingestFileOnce(file, onStatus, collection){
411
353
  if(file.size>_INGEST_MAX_BYTES) return {ok:false, skipped:true, error:`过大(${(file.size/1048576).toFixed(1)}MB>20MB)`};
412
354
  let job;
413
355
  try{
414
356
  const fd=new FormData(); fd.append('file', file);
415
- // 文件名(去扩展名)当来源标题 → 文件 Evidence 以「文件名/章节名」命名(二级)
416
357
  fd.append('source_title', (file.name||'').replace(/\.[^.]+$/,''));
417
- // 文件夹名 → 合集(一级);后端据此建/挂合集节点、并让 KEE 按合集判同源
418
358
  if(collection) fd.append('collection', collection);
419
359
  const r=await fetch(`${_origin}/knowledge/ingest/file/async`,{method:'POST',body:fd});
420
360
  if(!r.ok) return {ok:false, error: r.status===415?'不支持的格式': r.status===503?'知识核心未启用':`上传失败(${r.status})`};
@@ -448,7 +388,6 @@ async function ingestChatFile(file){
448
388
  }
449
389
  }
450
390
 
451
- /* ── 文件夹摄入:📁 一次选一个文件夹 → 过滤支持格式 → 顺序逐个摄入(不做多文件夹/并发批量)── */
452
391
  const _INGEST_EXTS=['.pdf','.md','.markdown','.txt','.epub','.mobi','.azw3','.azw','.docx','.xlsx'];
453
392
  const _FOLDER_MAX_FILES=100; // 单文件夹一次最多摄入的文件数(防选到超大目录顺序摄入几小时+烧 LLM)
454
393
  function _supportedFile(name){ const n=(name||'').toLowerCase(); return _INGEST_EXTS.some(e=>n.endsWith(e)); }
@@ -472,8 +411,6 @@ async function ingestFolder(fileList){
472
411
  files=files.slice(0, _FOLDER_MAX_FILES);
473
412
  }
474
413
  const N=files.length; let doneCount=0, failCount=0, totalKu=0, done=0; const log=[];
475
- // 并发摄入:后端线程池 max_workers=2,前端开 3 个 worker 正好把池占满(匹配星球吞吐)。
476
- // 之前严格串行(一次 1 个文件)比星球慢一倍。合集 find-or-create 已加锁,并发安全。
477
414
  const CONC=Math.min(3, N); let idx=0;
478
415
  async function _worker(){
479
416
  while(idx<N){
@@ -495,8 +432,6 @@ async function ingestFolder(fileList){
495
432
  if(!btn||!inp) return;
496
433
  btn.addEventListener('click',()=>inp.click());
497
434
  inp.addEventListener('change',async ()=>{
498
- // 先把 FileList 拷成数组再清空输入 —— inp.value='' 会清空 inp.files,
499
- // 而 fs 若是它的活引用就会一起变空(fs.length→0)导致下面直接 return、什么都不做。
500
435
  const fs=[...inp.files]; inp.value=''; if(!fs.length) return;
501
436
  if(_ingestBusy){ showReplyPop('📁 文件夹', activeAgent, true); fillReplyPop('', '请等当前摄入完成再选文件夹', 'err'); return; }
502
437
  _ingestBusy=true;
@@ -504,7 +439,6 @@ async function ingestFolder(fileList){
504
439
  });
505
440
  })();
506
441
 
507
- /* 流式/任务 模式开关:默认流式(逐字直答);任务模式走编排(/api/task,不流式) */
508
442
  let chatModeStream=true;
509
443
  (function(){
510
444
  const el=document.getElementById('chatMode');
@@ -515,9 +449,6 @@ let chatModeStream=true;
515
449
  sync();
516
450
  })();
517
451
 
518
- /* ===== 工具菜单:输入框左侧「+」→ 上方竖排弹窗(模式/上传/文件夹/图片/连接器)=====
519
- 原先这些是 chat 栏一排横向图标,太挤。现收进弹窗;菜单项点击的是**隐藏保留的原按钮**,
520
- 因此上传/文件夹/图片/连接器/模式切换全部复用既有逻辑,零重复实现。 */
521
452
  function toolMenuOpen(){ const m=document.getElementById('toolMenu'); return !!(m&&m.classList.contains('show')); }
522
453
  function hideToolMenu(){ const m=document.getElementById('toolMenu'); if(m)m.classList.remove('show'); }
523
454
  function _toolRows(){
@@ -554,16 +485,11 @@ function renderToolMenu(){
554
485
  const m=document.getElementById('toolMenu'), tools=document.getElementById('cmdTools');
555
486
  if(!toolMenuOpen() || !m) return;
556
487
  if(m.contains(e.target) || e.target.id==='cmdMore') return;
557
- // 菜单项内部是用 .click() 触发隐藏原按钮的,那个合成事件会冒泡到 document——
558
- // 不能把它当"点外部"而关掉菜单(否则切模式时菜单会意外收起)
559
488
  if(tools && tools.contains(e.target)) return;
560
489
  hideToolMenu();
561
490
  });
562
491
  })();
563
492
 
564
- /* ===== 斜杠指令:输入框以 / 开头弹命令菜单,复用现有操作(纯前端,零新后端)=====
565
- 输入 / → 弹菜单;↑↓ 选择、Enter/Tab 执行或补全、Esc 关闭。
566
- 带 <参数> 的命令(如 /搜索)Enter 先补全命令名,再输参数回车执行。 */
567
493
  const SLASH_COMMANDS=[
568
494
  {cmd:'搜索', icon:'🔍', arg:'关键词', desc:'检索知识库', aliases:['search'], run:(q)=>slashSearch(q)},
569
495
  {cmd:'摄入', icon:'📎', desc:'上传文件进知识库', aliases:['上传','upload'], run:()=>{ const f=document.getElementById('cmdFile'); if(f)f.click(); }},
@@ -631,7 +557,6 @@ async function slashReset(arg){
631
557
  if(w&&fr.src&&fr.src!=='about:blank'&&typeof w.loadData==='function')w.loadData(); }catch(e){}
632
558
  }catch(e){ fillReplyPop('', '清库失败:后端未连接', 'err'); }
633
559
  }
634
- /* 菜单 UI + 键盘导航 */
635
560
  let _slashSel=0, _slashList=[];
636
561
  function slashFilter(text){
637
562
  const q=text.slice(1).toLowerCase();
@@ -669,7 +594,6 @@ function onSlashInput(){
669
594
  const inp=document.getElementById('cmd'); if(!inp)return;
670
595
  inp.addEventListener('input', onSlashInput);
671
596
  inp.addEventListener('blur', ()=>setTimeout(hideSlashMenu,150)); // 失焦关(留时间给 mousedown)
672
- // 捕获阶段拦截:菜单开时,Enter/方向键先于 transmit 的 bubble 监听被处理
673
597
  document.addEventListener('keydown', function(e){
674
598
  if(e.target!==inp || !slashMenuOpen()) return;
675
599
  if(e.key==='ArrowDown'){ e.preventDefault(); e.stopPropagation(); _slashSel=(_slashSel+1)%_slashList.length; updateSlashSel(); }
@@ -679,9 +603,7 @@ function onSlashInput(){
679
603
  }, true);
680
604
  })();
681
605
 
682
- /* ===== 连接器/插件入口:🔌 面板 + @引用(纯前端,复用现有 AHS API)===== */
683
606
  let _atList=[], _atSel=0, _atCache=null, _atCtx=null, _refTriggers={};
684
- /* --- 🔌 面板:列出 MCP 连接器 / 技能 / SKILL.md,徽章 + 启停 --- */
685
607
  async function openConnPanel(){
686
608
  const p=document.getElementById('connPanel'); if(!p)return;
687
609
  if(p.classList.contains('show')){ p.classList.remove('show'); return; }
@@ -736,7 +658,6 @@ function renderConnPanel(p, skills, servers, md){
736
658
  if(p&&p.classList.contains('show')&&!p.contains(e.target)&&e.target.id!=='cmdConn') p.classList.remove('show'); });
737
659
  })();
738
660
 
739
- /* --- @引用:输入 @ 弹技能/连接器 → 插入 @名称;发送(任务模式)时把其触发词带入 task,让技能匹配可靠命中 --- */
740
661
  async function ensureAtCache(){
741
662
  if(_atCache) return _atCache;
742
663
  try{
@@ -795,7 +716,6 @@ async function onAtInput(){
795
716
  _atList=all.filter(c=> !q || c.name.toLowerCase().includes(q) || (c.trigger||'').toLowerCase().includes(q)).slice(0,8);
796
717
  _atSel=0; renderAtMenu();
797
718
  }
798
- /* 发送任务模式时,把已引用技能的触发词带入 task(不改用户可见输入),让后端 match 命中 */
799
719
  function applyRefs(task){
800
720
  let out=task;
801
721
  for(const nm in _refTriggers){
@@ -817,7 +737,6 @@ function applyRefs(task){
817
737
  }, true);
818
738
  })();
819
739
 
820
- /* ===== 图片附件 → 多模态对话:附图 + 缩放压缩 + 随问题发给视觉模型 ===== */
821
740
  let _pendingImages=[]; // [{media_type,data(base64),url(预览),name}]
822
741
  const _IMG_MAX=6, _IMG_MAXDIM=1280, _IMG_Q=0.85;
823
742
  function _downscaleImage(file){
@@ -875,7 +794,6 @@ async function visionAdvisory(){
875
794
  }
876
795
  })();
877
796
 
878
- /* ===== 文件附件 → 聊天附件:上传落盘 + 随问题发给 LLM(文本类提取正文,其它降级元数据) ===== */
879
797
  let _pendingFiles=[]; // [{id,name,size,mime,url,text}]
880
798
  const _FILE_MAX=6, _FILE_MAXB=20*1024*1024;
881
799
  function _fileSize(sz){
@@ -924,11 +842,9 @@ function clearPendingFiles(){ _pendingFiles=[]; renderFileStrip(); }
924
842
  }
925
843
  })();
926
844
 
927
- /* 流式直答:SSE 逐字追加进回复浮窗 */
928
845
  let _chatHistory=[]; // 多轮对话历史 [{role,content}];随请求带给后端 + 界面渲染成对话线程
929
846
  function clearChatHistory(){ _chatHistory=[]; } // 新话题/清屏可调
930
847
 
931
- /* ═══ 会话历史:对话不再刷新即丢,落盘到后端、左栏可回看 ═══ */
932
848
  let _sessionId = localStorage.getItem('foxclawSessionId') || '';
933
849
  async function ensureSession(){
934
850
  if(_sessionId){
@@ -957,7 +873,6 @@ async function openSession(id){
957
873
  await loadSessions();
958
874
  }catch(e){}
959
875
  }
960
- /* 渲染已存在会话(只有历史,没有"当前问"和空答槽)*/
961
876
  function renderSessionThread(agent){
962
877
  const body=document.querySelector('#coreChat .cc-body'); if(!body)return;
963
878
  const a=document.getElementById('ccAgent'); if(a)a.textContent=`${AGENT_CN[agent]||agent} Agent`;
@@ -970,8 +885,6 @@ function renderSessionThread(agent){
970
885
  Center.chat();
971
886
  body.scrollTop=body.scrollHeight;
972
887
  }
973
- /* 会话列表:渲染进左栏 #nsList(会话历史是这个界面的主体内容)。
974
- 每条 → openSession(id) 真实打开对应会话;✕ → 删除。 */
975
888
  async function loadSessions(){
976
889
  const list=document.getElementById('nsList'); if(!list)return;
977
890
  const cnt=document.getElementById('nsCount');
@@ -1006,8 +919,6 @@ async function delSession(id){
1006
919
  }
1007
920
  await loadSessions();
1008
921
  }
1009
- // 把 _chatHistory + 当前问 重建成双方对称的气泡对话线程(助手左/用户右,各自独立圆角框),
1010
- // cc-body 可滚动 = 下拉浏览历史。保留上下文,不再只显示最新一条。
1011
922
  function _fmtTime(ts){ const d=new Date(ts||Date.now()); return ('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2); }
1012
923
  function _chatRow(role, inner, agent, ts, isSlot){
1013
924
  const t=_fmtTime(ts);
@@ -1040,9 +951,6 @@ function renderChatThread(currentQ, agent, attachments){
1040
951
  Center.chat();
1041
952
  body.scrollTop=body.scrollHeight;
1042
953
  }
1043
- /* 执行证据渲染:把后端每步的核验结论摊开给人看。
1044
- 三态有意不做成"全部打勾"——unverified 显示为「未核验」,failed 显示为「未完成」,
1045
- 因为把没凭据的步骤画成绿色,正是这套机制要消灭的东西。 */
1046
954
  function renderEvidence(items){
1047
955
  const wrap=document.getElementById('ccEvidence'); if(!wrap)return;
1048
956
  if(!items||!items.length){ wrap.hidden=true; wrap.innerHTML=''; return; }
@@ -1070,8 +978,6 @@ async function transmitStream(task, agent, images, attachments){
1070
978
  const rp=document.getElementById('ccReply'), st=document.getElementById('ccStatus');
1071
979
  rp.textContent=''; rp.style.color='var(--ink)'; st.style.color='var(--ink-dim)'; st.textContent='◌ 流式生成中…';
1072
980
  let acc='';
1073
- // 推理过程折叠块:每轮重置。后端以 SSE `event: thinking` 单独送出思考增量,
1074
- // 正文(delta)不含思考,这里只做展示,不参与落库/复制。
1075
981
  let _thinkWrap=document.getElementById('ccThink');
1076
982
  if(!_thinkWrap && rp.parentNode){
1077
983
  _thinkWrap=document.createElement('details');
@@ -1082,8 +988,6 @@ async function transmitStream(task, agent, images, attachments){
1082
988
  const _thinkBody=document.getElementById('ccThinkBody');
1083
989
  if(_thinkBody) _thinkBody.textContent='';
1084
990
  if(_thinkWrap){ _thinkWrap.hidden=true; _thinkWrap.open=false; }
1085
- // 执行证据:工具轮每步「核验到没到位」+ 可点开的凭据(来源链接 / 真实文件)。
1086
- // 挂在答案**下方**——正文是给用户读的结论,这里是给人查的凭据,两回事。
1087
991
  let _evWrap=document.getElementById('ccEvidence');
1088
992
  if(!_evWrap && rp.parentNode){
1089
993
  _evWrap=document.createElement('div');
@@ -1106,7 +1010,6 @@ async function transmitStream(task, agent, images, attachments){
1106
1010
  let idx;
1107
1011
  while((idx=buf.indexOf('\n\n'))>=0){
1108
1012
  const block=buf.slice(0,idx); buf=buf.slice(idx+2);
1109
- // 兼容命名事件(SSE 规范):`event: thinking` + `data: {...}`;无 event 行按默认事件处理。
1110
1013
  const evName=((block.match(/^event:\s*(\S+)/m)||[])[1])||'message';
1111
1014
  const dataLine=((block.match(/^data:\s*([\s\S]*)$/m)||[])[1]||'').trim();
1112
1015
  if(!dataLine)continue;
@@ -1138,15 +1041,11 @@ async function transmitStream(task, agent, images, attachments){
1138
1041
  if(typeof loadMemStats==='function') loadMemStats();
1139
1042
  }
1140
1043
 
1141
- /* 任务模式流式(/api/task/stream):SSE 逐 stage 进度 + act 逐字 + 最终结果。仅领域 Agent 用——
1142
- 该端点跑单 Agent 认知管线,不做主管的多 Agent 分派(主管编排任务仍走阻塞 /api/task)。 */
1143
1044
  async function transmitTaskStream(task, agent){
1144
1045
  const rp=document.getElementById('ccReply'), st=document.getElementById('ccStatus');
1145
1046
  rp.textContent=''; rp.style.color='var(--ink)'; st.style.color='var(--ink-dim)'; st.textContent='◌ 认知管线启动…';
1146
1047
  let acc='', stages=[];
1147
1048
  const STAGE_CN={perceive:'感知',recall:'召回',plan:'规划',route:'路由',act:'执行',verify:'校验',reflect:'反思'};
1148
- // 显式计划块:后端在 plan stage 之后补发 {"plan":{steps,flow,multi_step}} → 列出计划步骤,
1149
- // 让"它打算怎么做"在执行前就可见(此前只在跑完后才能从 thought chain 里翻)。
1150
1049
  let _planWrap=document.getElementById('ccPlan');
1151
1050
  if(!_planWrap && rp.parentNode){
1152
1051
  _planWrap=document.createElement('details');
@@ -1198,7 +1097,6 @@ async function transmitTaskStream(task, agent){
1198
1097
  if(typeof loadMemStats==='function') loadMemStats();
1199
1098
  }
1200
1099
 
1201
- /* 任务流操作 */
1202
1100
  function prependTask(name,agent,status){
1203
1101
  const list=document.getElementById('taskList'); if(!list)return;
1204
1102
  const now=new Date(); const hh=String(now.getHours()).padStart(2,'0')+':'+String(now.getMinutes()).padStart(2,'0');
@@ -1216,11 +1114,9 @@ function markTask(name,status){
1216
1114
  row.querySelector('.ti').textContent=status==='ok'?'✓':status==='blocked'?'✕':'◌';}
1217
1115
  }
1218
1116
 
1219
- /* 5. WebSocket 实时事件流 */
1220
1117
  window.actPulse=0;
1221
1118
  function connectWS(){
1222
1119
  try{
1223
- // 后端若开了鉴权,WS 用 ?key= 传(浏览器无法给 WS 设请求头)
1224
1120
  const _k=localStorage.getItem('foxclawApiKey');
1225
1121
  const ws=new WebSocket(WS + (_k?('?key='+encodeURIComponent(_k)):''));
1226
1122
  ws.onmessage=ev=>{
@@ -1240,7 +1136,6 @@ function flashCmd(c){
1240
1136
  setTimeout(()=>{bar.style.boxShadow=''; bar.style.borderColor='';},700);
1241
1137
  }
1242
1138
 
1243
- /* ===== 全屏 overlay 浮层 ===== */
1244
1139
  function openOverlay(agent){
1245
1140
  const m=KP_META[agent]||KP_META.office;
1246
1141
  document.getElementById('ovTitle').textContent=m.title;
@@ -1248,7 +1143,6 @@ function openOverlay(agent){
1248
1143
  const body=document.getElementById('ovBody');
1249
1144
  const d=_lastKernelData[agent]||{};
1250
1145
  if(agent==='admin'){
1251
- // 行程:左留白 + 地图 + 右栏(录入行程),左右等宽对称
1252
1146
  body.innerHTML=`<div class="ov-grid">
1253
1147
  <div class="ov-spacer"></div>
1254
1148
  <div style="position:relative;height:100%">
@@ -1279,11 +1173,9 @@ document.getElementById('ovClose')?.addEventListener('click',closeOverlay);
1279
1173
  document.getElementById('ovMask')?.addEventListener('click',e=>{if(e.target.id==='ovMask')closeOverlay();});
1280
1174
  document.addEventListener('keydown',e=>{if(e.key==='Escape')closeOverlay();});
1281
1175
 
1282
- /* 地图自动全屏开关(持久化到内存) */
1283
1176
  let autoMap=false;
1284
1177
  document.getElementById('ovAutoMap')?.addEventListener('change',e=>{autoMap=e.target.checked;});
1285
1178
 
1286
- /* 全屏知识图谱(Canvas 节点连线) */
1287
1179
  function drawKnowledgeGraph(canvasId,d){
1288
1180
  const c=document.getElementById(canvasId); if(!c)return;
1289
1181
  const x=c.getContext('2d');
@@ -1301,7 +1193,6 @@ function drawKnowledgeGraph(canvasId,d){
1301
1193
  x.clearRect(0,0,c.width,c.height);rot+=0.002;
1302
1194
  const cx=c.width/2,cy=c.height/2,R=Math.min(cx,cy)*0.8;
1303
1195
  nodes.forEach(n=>{n._x=cx+Math.cos(n.a+rot)*n.r*R;n._y=cy+Math.sin(n.a+rot)*n.r*R;});
1304
- // 连线(同主题相连)
1305
1196
  x.lineWidth=1;
1306
1197
  for(let i=0;i<nodes.length;i++)for(let j=i+1;j<nodes.length;j++){
1307
1198
  if(nodes[i].topic&&nodes[i].topic===nodes[j].topic){
@@ -1316,8 +1207,6 @@ function drawKnowledgeGraph(canvasId,d){
1316
1207
  loop();
1317
1208
  }
1318
1209
 
1319
-
1320
- /* ===== 左侧导航 hover 弹出精简条目 + 点击进中央大屏 ===== */
1321
1210
  const MODULE_META={
1322
1211
  tasks:{title:'任务中心',tag:'Tasks'},
1323
1212
  memory:{title:'记忆中心',tag:'Memory'},
@@ -1327,7 +1216,6 @@ const MODULE_META={
1327
1216
  agents:{title:'代理',tag:'Agents'},
1328
1217
  usage:{title:'使用情况',tag:'Usage'},
1329
1218
  };
1330
- /* 取模块精简条目(供轮播框 + 中央大屏用)*/
1331
1219
  async function fetchModuleBrief(mod){
1332
1220
  try{
1333
1221
  if(mod==='tasks'){
@@ -1357,19 +1245,16 @@ async function fetchModuleBrief(mod){
1357
1245
  return [{title:'加载失败',sub:'后端未连接'}];
1358
1246
  }
1359
1247
 
1360
- /* 填充三个轮播框 + 自动滚动 */
1361
1248
  function fillFeed(mod, trackId){
1362
1249
  const track=document.getElementById(trackId); if(!track)return;
1363
1250
  fetchModuleBrief(mod).then(items=>{
1364
1251
  if(!items||!items.length){ track.innerHTML='<div class="feed-empty">暂无数据</div>'; return; }
1365
1252
  const render=()=>items.map((it,idx)=>`<div class="feed-item" data-mod="${mod}" data-idx="${idx}"><b>${escapeHtml(it.title)}</b>${it.sub?`<span>${escapeHtml(it.sub)}</span>`:''}</div>`).join('');
1366
- // 内容较多才滚动:复制一份首尾相接,无缝循环
1367
1253
  const enough=items.length>=4;
1368
1254
  track.innerHTML=render()+(enough?render():'');
1369
1255
  track.classList.toggle('rolling', enough);
1370
1256
  if(enough){ track.style.animationDuration=(items.length*2.2)+'s'; }
1371
1257
  else{ track.style.animationDuration=''; }
1372
- // 点条目 → 中央大屏
1373
1258
  track.querySelectorAll('.feed-item').forEach(el=>el.onclick=()=>{
1374
1259
  if(mod==='knowledge'){ const it=items[+el.dataset.idx]; if(it) showKnowledgeArticle(it); }
1375
1260
  else showModuleInCore(mod);
@@ -1381,7 +1266,6 @@ function refreshFeeds(){
1381
1266
  fillFeed('memory','feedMemory');
1382
1267
  fillFeed('knowledge','feedKnowledge');
1383
1268
  }
1384
- /* 单篇全文卡片(含删除 + 我的笔记)*/
1385
1269
  function _knowCardHtml(k){
1386
1270
  const notes=(k.notes||[]);
1387
1271
  return `<div class="know-card">
@@ -1399,7 +1283,6 @@ function _knowCardHtml(k){
1399
1283
  </div>
1400
1284
  </div>`;
1401
1285
  }
1402
- /* 列表视图:每篇一行(标题+摘要+删除),点击进详情。reload=删除/刷新后重新加载 */
1403
1286
  function _renderKnowList(body, items, reload){
1404
1287
  body.innerHTML='<div class="know-index">'+items.map((k,i)=>`
1405
1288
  <div class="know-row" data-i="${i}">
@@ -1409,8 +1292,6 @@ function _renderKnowList(body, items, reload){
1409
1292
  body.querySelectorAll('.know-row').forEach(el=>el.onclick=(e)=>{ if(e.target.closest('.know-del'))return; _renderKnowDetail(body, items, +el.dataset.i, reload); });
1410
1293
  _wireKnowDelete(body, reload);
1411
1294
  }
1412
- /* 详情视图:只显示这一篇的全文 + 返回列表 */
1413
- /* 笔记保存(有内容才提交;保存后回调 onSaved 重渲染),多处复用 */
1414
1295
  function _wireKnowNote(body, item, onSaved){
1415
1296
  const saveBtn=body.querySelector('.know-note-save');
1416
1297
  if(!saveBtn)return;
@@ -1432,7 +1313,6 @@ function _renderKnowDetail(body, items, i, reload){
1432
1313
  _wireKnowDelete(body, reload);
1433
1314
  _wireKnowNote(body, k, ()=>_renderKnowDetail(body, items, i, reload));
1434
1315
  }
1435
- /* 左栏点单条文章 → 中央「独立阅读器」只显示这一篇(不显示全部列表) */
1436
1316
  function showKnowledgeArticle(item){
1437
1317
  const wrap=document.querySelector('.corewrap'), stage=document.getElementById('coreStage');
1438
1318
  if(!wrap||!stage||!item)return;
@@ -1455,7 +1335,6 @@ function _wireKnowDelete(container, onDone){
1455
1335
  }catch(e){ alert('删除失败:'+e); }
1456
1336
  });
1457
1337
  }
1458
- /* 知识库 → 中央展开全文卡片(读完整录入内容,保留换行) */
1459
1338
  async function showKnowledgeFull(){
1460
1339
  const wrap=document.querySelector('.corewrap'), stage=document.getElementById('coreStage');
1461
1340
  if(!wrap||!stage)return;
@@ -1472,8 +1351,6 @@ async function showKnowledgeFull(){
1472
1351
  _renderKnowList(body, items, showKnowledgeFull);
1473
1352
  }catch(e){ body.innerHTML='<div class="kp-empty" style="padding:30px 0">后端未连接</div>'; }
1474
1353
  }
1475
- /* 左栏「菜单」按钮:把原来平铺的八项功能收进一个分类菜单(浮层贴着按钮右侧展开)。
1476
- 靠按钮的实时位置定位,不写死坐标 —— 窗口缩放 / 侧栏宽度变化都不会错位。 */
1477
1354
  const _navMenu=document.getElementById('navMenu');
1478
1355
  const _navMenuBtn=document.getElementById('navMenuBtn');
1479
1356
  function setNavMenu(open){
@@ -1500,8 +1377,6 @@ document.addEventListener('click',e=>{
1500
1377
  document.addEventListener('keydown',e=>{ if(e.key==='Escape') setNavMenu(false); });
1501
1378
  window.addEventListener('resize',()=>setNavMenu(false));
1502
1379
 
1503
- /* 当前视图 → 菜单按钮文案 + 菜单内高亮(视图可能从菜单、右栏 ›、slash 命令等多处打开,
1504
- 统一在这里同步,免得各处各写一份 active 状态) */
1505
1380
  function _syncNav(mod){
1506
1381
  let name='';
1507
1382
  document.querySelectorAll('#navList .nav-link').forEach(x=>{
@@ -1510,11 +1385,9 @@ function _syncNav(mod){
1510
1385
  if(hit) name=x.querySelector('b')?.textContent||'';
1511
1386
  });
1512
1387
  const lbl=document.getElementById('navMenuLabel');
1513
- // 回显成「菜单 · 任务」:既保留"这是个菜单"的语义,又能一眼看出在哪一屏
1514
1388
  if(lbl) lbl.textContent=(name&&mod!=='overview')?`菜单 · ${name}`:'菜单';
1515
1389
  }
1516
1390
 
1517
- // 左栏导航:点击 → 中央大屏显示对应视图(overview 回核心;knowledge 走全屏知识视图)
1518
1391
  document.querySelectorAll('#navList .nav-link').forEach(el=>{
1519
1392
  el.addEventListener('click',()=>{
1520
1393
  const mod=el.dataset.module;
@@ -1526,7 +1399,6 @@ document.querySelectorAll('#navList .nav-link').forEach(el=>{
1526
1399
  });
1527
1400
  });
1528
1401
 
1529
- /* 中央大屏显示模块详情 */
1530
1402
  async function showModuleInCore(mod){
1531
1403
  const meta=MODULE_META[mod]||{title:mod,tag:''};
1532
1404
  const wrap=document.querySelector('.corewrap'), stage=document.getElementById('coreStage');
@@ -1544,7 +1416,6 @@ async function showModuleInCore(mod){
1544
1416
  if(mod==='memory') wirePrefDelete(body);
1545
1417
  }catch(e){ body.innerHTML='<div class="kp-empty">后端未连接</div>'; }
1546
1418
  }
1547
- /* 偏好删除按钮:删一条误记的偏好 → 刷新记忆中心大屏(F3 前端闭环) */
1548
1419
  function wirePrefDelete(body){
1549
1420
  body.querySelectorAll('.pref-del').forEach(btn=>{
1550
1421
  btn.onclick=async()=>{
@@ -1573,7 +1444,6 @@ async function renderModuleBig(mod){
1573
1444
  const d=await fetch(`${API}/bus/history?limit=40`).then(x=>x.json()).catch(()=>({}));
1574
1445
  const evs=(d.events||[]);
1575
1446
  if(!evs.length) return '<div class="kp-empty" style="padding:40px">暂无活动记录 · 系统事件(执行→审计→记忆)会实时汇入这里</div>';
1576
- // 事件真实字段:{channel, topic, payload, ts(epoch 秒·浮点), source, correlation_id}
1577
1447
  return '<div class="big-list">'+evs.slice().reverse().map(e=>{
1578
1448
  const topic=e.topic||e.channel||'事件';
1579
1449
  const ts=Number(e.ts);
@@ -1849,9 +1719,6 @@ async function wireToolCenter(root){
1849
1719
  };
1850
1720
  }
1851
1721
 
1852
-
1853
-
1854
- /* ===== 数据录入表单 ===== */
1855
1722
  const ENTRY_FORMS={
1856
1723
  finance:{title:'录入交易',sub:'添加一笔收支记录',endpoint:'/finance/import',
1857
1724
  fields:[
@@ -1891,7 +1758,6 @@ function openEntry(kind){
1891
1758
  document.getElementById('entrySub').textContent=f.sub;
1892
1759
  document.getElementById('entryMsg').textContent='';
1893
1760
  let h='';
1894
- // 行程点的经纬度并排
1895
1761
  f.fields.forEach(fd=>{
1896
1762
  const ctl = fd.type==='textarea'
1897
1763
  ? `<textarea data-k="${fd.k}" placeholder="${fd.ph||''}" rows="8" style="width:100%;resize:vertical;font-family:inherit;line-height:1.6"></textarea>`
@@ -1917,9 +1783,6 @@ document.getElementById('entrySubmit')?.addEventListener('click',async()=>{
1917
1783
  }catch(e){ msg.style.color='var(--amber)'; msg.textContent='录入失败:后端未连接'; }
1918
1784
  });
1919
1785
 
1920
-
1921
-
1922
- /* 启动 —— 全部包 try-catch,任一失败不影响其他;还失败的也在 upStat 显示具体错 */
1923
1786
  const _bootTry=(fn,name)=>{ try{ const r=fn(); if(r&&r.catch) r.catch(e=>{ try{document.getElementById('upStat').textContent='启动失败:'+name}catch(_){ } }); }
1924
1787
  catch(e){ try{document.getElementById('upStat').textContent='启动失败:'+name}catch(_){ } } };
1925
1788
  _bootTry(fetchAMapConfig, 'fetchAMapConfig');
@@ -1931,20 +1794,13 @@ _bootTry(()=>loadKernel('supervisor'), 'loadKernel');
1931
1794
  try{ setInterval(loadStatus,15000); }catch(e){}
1932
1795
  try{ setInterval(loadMemStats,20000); }catch(e){}
1933
1796
  try{ setInterval(loadKnowList,30000); }catch(e){}
1934
- // 右栏面板标题的 "›" → 打开对应中央大屏
1935
1797
  document.getElementById('memMore')?.addEventListener('click',()=>showModuleInCore('memory'));
1936
1798
  document.getElementById('knowMore')?.addEventListener('click',()=>showKnowledgeFull());
1937
- // 知识摄入入口:复用底部指令栏里那三个隐藏的原有控件(逻辑不重复实现)
1938
1799
  document.getElementById('ingFile')?.addEventListener('click',()=>document.getElementById('cmdAttach')?.click());
1939
1800
  document.getElementById('ingFolder')?.addEventListener('click',()=>document.getElementById('cmdFolder')?.click());
1940
1801
  document.getElementById('ingNote')?.addEventListener('click',()=>{ try{ openEntry('office'); }catch(e){} });
1941
- // 会话历史:只渲染列表,**不预建空会话**(否则每次刷新都堆一条 0 条的空壳);
1942
- // 真正建会话推迟到用户发第一条消息时(transmitStream 里 ensureSession)。
1943
1802
  _bootTry(loadSessions, 'loadSessions');
1944
1803
  document.getElementById('sessNew')?.addEventListener('click',()=>newSession(true));
1945
1804
 
1946
-
1947
-
1948
- /* 内联 onclick 桥接:模块作用域下,HTML 里 onclick="fn()" 需从 window 解析 */
1949
1805
  Object.assign(window, { openEntry, importDomainFile, switchProvider });
1950
1806