foxclaw 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/index.js +1 -8
  2. package/package.json +1 -1
  3. package/python/.env.example +8 -0
  4. package/python/.github/workflows/ci.yml +47 -2
  5. package/python/.github/workflows/release.yml +5 -0
  6. package/python/CHANGELOG.md +100 -0
  7. package/python/CHANGELOG_CN.md +100 -0
  8. package/python/README.md +17 -2
  9. package/python/README_CN.md +38 -20
  10. package/python/bench/bm25_audit.py +1 -14
  11. package/python/bench/bm25_regression.py +0 -1
  12. package/python/bench/bm25_stress.py +0 -4
  13. package/python/bench/cd_audit.py +0 -23
  14. package/python/bench/chaos.py +6 -8
  15. package/python/bench/e2e_real_models.py +0 -1
  16. package/python/bench/llm_retry_test.py +2 -9
  17. package/python/bench/load_test.py +5 -8
  18. package/python/bench/vector_ivf_upgrade.py +1 -7
  19. package/python/docs/CONTENT_GUARD.md +189 -0
  20. package/python/docs/CONTENT_GUARD_CN.md +189 -0
  21. package/python/docs/RELEASE.md +78 -37
  22. package/python/docs/RUN_CN.md +2 -2
  23. package/python/evals/governance/cases.py +0 -37
  24. package/python/evals/governance/run_evals.py +5 -37
  25. package/python/examples/governance_middleware_demo.py +1 -6
  26. package/python/examples/langgraph_governance_demo.py +7 -11
  27. package/python/foxclaw/__init__.py +74 -152
  28. package/python/foxclaw/agents/base.py +82 -136
  29. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  30. package/python/foxclaw/agents/impl/finance.py +4 -21
  31. package/python/foxclaw/agents/impl/office.py +7 -24
  32. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  33. package/python/foxclaw/agents/mixins/_context.py +0 -23
  34. package/python/foxclaw/agents/mixins/augment.py +10 -16
  35. package/python/foxclaw/agents/mixins/llm.py +7 -6
  36. package/python/foxclaw/agents/mixins/memory.py +23 -3
  37. package/python/foxclaw/agents/mixins/preference.py +58 -8
  38. package/python/foxclaw/agents/mixins/prompt.py +59 -12
  39. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  40. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  41. package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
  42. package/python/foxclaw/api/__init__.py +18 -77
  43. package/python/foxclaw/api/_state.py +43 -0
  44. package/python/foxclaw/api/bootstrap.py +114 -0
  45. package/python/foxclaw/api/middleware.py +5 -28
  46. package/python/foxclaw/api/routes/ahs.py +60 -73
  47. package/python/foxclaw/api/routes/attachments.py +5 -6
  48. package/python/foxclaw/api/routes/browser.py +7 -11
  49. package/python/foxclaw/api/routes/domain.py +18 -26
  50. package/python/foxclaw/api/routes/knowledge.py +3 -21
  51. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  52. package/python/foxclaw/api/routes/memory.py +50 -12
  53. package/python/foxclaw/api/routes/orchestration.py +3 -5
  54. package/python/foxclaw/api/routes/recovery.py +4 -3
  55. package/python/foxclaw/api/routes/review.py +2 -11
  56. package/python/foxclaw/api/routes/search.py +3 -7
  57. package/python/foxclaw/api/routes/system.py +33 -49
  58. package/python/foxclaw/api/routes/task.py +31 -49
  59. package/python/foxclaw/api/routes/ws.py +2 -6
  60. package/python/foxclaw/api/spine.py +1 -4
  61. package/python/foxclaw/behavior/__init__.py +20 -0
  62. package/python/foxclaw/behavior/behavior.yaml +62 -0
  63. package/python/foxclaw/behavior/spec.py +287 -0
  64. package/python/foxclaw/browser/action/action_executor.py +1 -5
  65. package/python/foxclaw/browser/action/action_types.py +0 -1
  66. package/python/foxclaw/browser/action/url_policy.py +1 -5
  67. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  68. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  69. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  70. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  71. package/python/foxclaw/browser/service.py +7 -11
  72. package/python/foxclaw/capability/embedding_service.py +4 -6
  73. package/python/foxclaw/channels/__init__.py +9 -11
  74. package/python/foxclaw/channels/discord.py +4 -13
  75. package/python/foxclaw/channels/dispatcher.py +2 -27
  76. package/python/foxclaw/channels/feishu.py +10 -20
  77. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  78. package/python/foxclaw/channels/signing.py +9 -9
  79. package/python/foxclaw/channels/telegram.py +3 -4
  80. package/python/foxclaw/channels/weixin.py +7 -17
  81. package/python/foxclaw/cli.py +42 -13
  82. package/python/foxclaw/cognitive/reasoner.py +9 -10
  83. package/python/foxclaw/cognitive/state.py +18 -24
  84. package/python/foxclaw/core/audit_chain.py +1 -3
  85. package/python/foxclaw/core/audit_sink.py +3 -3
  86. package/python/foxclaw/core/compaction.py +8 -26
  87. package/python/foxclaw/core/config.py +2 -2
  88. package/python/foxclaw/core/confirm_nonce.py +6 -12
  89. package/python/foxclaw/core/content_guard.py +341 -0
  90. package/python/foxclaw/core/envfile.py +2 -2
  91. package/python/foxclaw/core/failure_policy.py +17 -26
  92. package/python/foxclaw/core/gateway.py +6 -10
  93. package/python/foxclaw/core/governance_audit.py +3 -7
  94. package/python/foxclaw/core/idempotency.py +15 -22
  95. package/python/foxclaw/core/injection_guard.py +10 -29
  96. package/python/foxclaw/core/memory_adapter.py +53 -60
  97. package/python/foxclaw/core/metrics.py +4 -5
  98. package/python/foxclaw/core/model_router.py +74 -150
  99. package/python/foxclaw/core/paths.py +1 -1
  100. package/python/foxclaw/core/recovery.py +21 -34
  101. package/python/foxclaw/core/scheduler.py +23 -26
  102. package/python/foxclaw/core/secrets.py +0 -3
  103. package/python/foxclaw/core/sensitive_write.py +283 -0
  104. package/python/foxclaw/core/sessions.py +4 -6
  105. package/python/foxclaw/core/settings.py +16 -32
  106. package/python/foxclaw/core/shared_write.py +11 -23
  107. package/python/foxclaw/core/steering_queue.py +12 -11
  108. package/python/foxclaw/core/system_bus.py +1 -1
  109. package/python/foxclaw/core/thought_chain.py +30 -32
  110. package/python/foxclaw/core/token_meter.py +9 -13
  111. package/python/foxclaw/core/tools.py +4 -12
  112. package/python/foxclaw/core/tracing.py +25 -40
  113. package/python/foxclaw/core/truth_pipeline.py +5 -9
  114. package/python/foxclaw/core/update_check.py +149 -0
  115. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  116. package/python/foxclaw/core/verify.py +6 -14
  117. package/python/foxclaw/core/web_search.py +4 -28
  118. package/python/foxclaw/finance/evidence.py +14 -18
  119. package/python/foxclaw/finance/fx.py +1 -3
  120. package/python/foxclaw/finance/graph.py +9 -18
  121. package/python/foxclaw/finance/models.py +20 -24
  122. package/python/foxclaw/finance/nlparse.py +12 -72
  123. package/python/foxclaw/finance/policy.py +8 -36
  124. package/python/foxclaw/finance/reasoner.py +3 -11
  125. package/python/foxclaw/finance/reasoning.py +13 -34
  126. package/python/foxclaw/finance/reconcile.py +5 -10
  127. package/python/foxclaw/finance/runtime.py +19 -46
  128. package/python/foxclaw/governance/middleware.py +13 -21
  129. package/python/foxclaw/harness/_base.py +2 -8
  130. package/python/foxclaw/harness/_policy.py +19 -38
  131. package/python/foxclaw/harness/drift.py +1 -13
  132. package/python/foxclaw/harness/event_bus.py +2 -5
  133. package/python/foxclaw/harness/harness.py +9 -97
  134. package/python/foxclaw/harness/loop_detection.py +11 -18
  135. package/python/foxclaw/itinerary/amap_client.py +2 -5
  136. package/python/foxclaw/itinerary/models.py +7 -7
  137. package/python/foxclaw/itinerary/reasoner.py +1 -6
  138. package/python/foxclaw/itinerary/reasoning.py +15 -21
  139. package/python/foxclaw/itinerary/runtime.py +8 -12
  140. package/python/foxclaw/kernel/capabilities.py +5 -6
  141. package/python/foxclaw/kernel/policy_gate.py +4 -10
  142. package/python/foxclaw/kernel/process_manager.py +17 -36
  143. package/python/foxclaw/kernel/scheduler.py +3 -9
  144. package/python/foxclaw/kernel/sentinel.py +13 -23
  145. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  146. package/python/foxclaw/kernel/spine.py +9 -9
  147. package/python/foxclaw/knowledge/__init__.py +4 -20
  148. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  149. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  150. package/python/foxclaw/learning/models.py +13 -13
  151. package/python/foxclaw/learning/reasoner.py +1 -4
  152. package/python/foxclaw/learning/runtime.py +19 -31
  153. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  154. package/python/foxclaw/mcp_server.py +2 -3
  155. package/python/foxclaw/orchestration/__init__.py +16 -33
  156. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  157. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  158. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  159. package/python/foxclaw/planning/llm_planner.py +3 -3
  160. package/python/foxclaw/planning/planner.py +6 -11
  161. package/python/foxclaw/search/agent_port.py +7 -11
  162. package/python/foxclaw/search/evidence.py +0 -4
  163. package/python/foxclaw/search/internal_search.py +3 -5
  164. package/python/foxclaw/search/models.py +11 -13
  165. package/python/foxclaw/search/planner.py +3 -3
  166. package/python/foxclaw/search/retriever.py +1 -1
  167. package/python/foxclaw/search/runtime.py +1 -8
  168. package/python/foxclaw/skills/__init__.py +46 -100
  169. package/python/foxclaw/skills/archive.py +111 -0
  170. package/python/foxclaw/skills/fetch.py +226 -0
  171. package/python/foxclaw/skills/hooks_spec.py +21 -25
  172. package/python/foxclaw/skills/install_cmd.py +216 -0
  173. package/python/foxclaw/skills/lifecycle.py +16 -42
  174. package/python/foxclaw/skills/markdown_skill.py +10 -9
  175. package/python/foxclaw/skills/mcp_client.py +5 -11
  176. package/python/foxclaw/skills/mcp_store.py +0 -3
  177. package/python/foxclaw/skills/permission.py +13 -48
  178. package/python/foxclaw/skills/pipeline.py +21 -53
  179. package/python/foxclaw/skills/policy.py +4 -10
  180. package/python/foxclaw/skills/review.py +3 -3
  181. package/python/frontend/deck/auth.js +0 -7
  182. package/python/frontend/deck/chat_model_picker.js +0 -54
  183. package/python/frontend/deck/domain.js +0 -20
  184. package/python/frontend/deck/main.js +0 -144
  185. package/python/frontend/deck/markdown.js +0 -4
  186. package/python/frontend/deck/settings.js +1 -18
  187. package/python/frontend/deck/state.js +0 -1
  188. package/python/frontend/deck/util.js +0 -2
  189. package/python/frontend/deck/visuals.js +0 -22
  190. package/python/frontend/planet/planet.js +18 -201
  191. package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
  192. package/python/frontend/v3/assets/index-tN90dpUH.js +34 -0
  193. package/python/frontend/v3/index.html +3 -3
  194. package/python/frontend/widget_runtime.js +0 -43
  195. package/python/frontend/widgets.js +0 -57
  196. package/python/frontend_src/index.html +1 -1
  197. package/python/frontend_src/package-lock.json +2 -2
  198. package/python/frontend_src/package.json +1 -1
  199. package/python/frontend_src/src/App.vue +3 -7
  200. package/python/frontend_src/src/api.js +0 -7
  201. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  202. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  203. package/python/frontend_src/src/components/Inspector.vue +0 -6
  204. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  205. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  206. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  207. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  208. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  209. package/python/frontend_src/src/components/Stage.vue +71 -25
  210. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  211. package/python/frontend_src/src/components/TopBar.vue +38 -10
  212. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  213. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  214. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  215. package/python/frontend_src/src/components/panels/MemoryPanel.vue +42 -3
  216. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  217. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  218. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  219. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  220. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  221. package/python/frontend_src/src/lib/fmt.js +0 -4
  222. package/python/frontend_src/src/lib/i18n.js +53 -0
  223. package/python/frontend_src/src/lib/locales/en.js +173 -0
  224. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  225. package/python/frontend_src/src/lib/md.js +0 -14
  226. package/python/frontend_src/src/main.js +2 -0
  227. package/python/frontend_src/src/stores/app.js +17 -32
  228. package/python/frontend_src/src/stores/chat.js +0 -34
  229. package/python/frontend_src/src/stores/model.js +0 -8
  230. package/python/frontend_src/src/stores/panels.js +16 -44
  231. package/python/frontend_src/src/stores/profile.js +31 -0
  232. package/python/frontend_src/src/stores/system.js +25 -0
  233. package/python/frontend_src/vite.config.js +0 -7
  234. package/python/pyproject.toml +17 -3
  235. package/python/requirements.txt +311 -0
  236. package/python/tests/conftest.py +0 -22
  237. package/python/tests/run_all.py +4 -66
  238. package/python/tests/test_agent_lifecycle.py +4 -38
  239. package/python/tests/test_agent_rules.py +81 -0
  240. package/python/tests/test_ahs_p2.py +4 -25
  241. package/python/tests/test_anthropic_tools.py +1 -15
  242. package/python/tests/test_api.py +19 -9
  243. package/python/tests/test_api_auth.py +1 -5
  244. package/python/tests/test_attachments.py +9 -25
  245. package/python/tests/test_audit_chain.py +2 -7
  246. package/python/tests/test_audit_sink.py +0 -6
  247. package/python/tests/test_behavior_spec.py +198 -0
  248. package/python/tests/test_browser.py +2 -6
  249. package/python/tests/test_browser_request_guard.py +0 -4
  250. package/python/tests/test_capabilities.py +0 -6
  251. package/python/tests/test_channel_signing.py +2 -9
  252. package/python/tests/test_channels.py +1 -11
  253. package/python/tests/test_config_layering.py +0 -4
  254. package/python/tests/test_confirm_nonce.py +1 -12
  255. package/python/tests/test_conflict_dismiss.py +143 -0
  256. package/python/tests/test_content_guard.py +177 -0
  257. package/python/tests/test_context_compaction_wiring.py +1 -7
  258. package/python/tests/test_dispatch.py +0 -6
  259. package/python/tests/test_domain_evals.py +11 -80
  260. package/python/tests/test_domain_import.py +0 -6
  261. package/python/tests/test_domain_reasoners.py +0 -14
  262. package/python/tests/test_e2e.py +0 -17
  263. package/python/tests/test_emb_cache.py +7 -17
  264. package/python/tests/test_embedding_singleton.py +1 -3
  265. package/python/tests/test_evals_smoke.py +0 -24
  266. package/python/tests/test_event_spine.py +10 -18
  267. package/python/tests/test_evidence_semantic.py +1 -14
  268. package/python/tests/test_evidence_sse.py +3 -6
  269. package/python/tests/test_file_ingest.py +0 -11
  270. package/python/tests/test_finance.py +0 -9
  271. package/python/tests/test_finance_ledger.py +5 -12
  272. package/python/tests/test_finance_persist.py +8 -12
  273. package/python/tests/test_gateway.py +3 -22
  274. package/python/tests/test_governance.py +5 -52
  275. package/python/tests/test_governance_audit.py +4 -39
  276. package/python/tests/test_governance_middleware.py +1 -7
  277. package/python/tests/test_hkc.py +6 -24
  278. package/python/tests/test_hkc_index_build_manager.py +0 -33
  279. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  280. package/python/tests/test_hkc_search_event_sync.py +3 -54
  281. package/python/tests/test_import_path_guard.py +0 -6
  282. package/python/tests/test_injection_guard.py +18 -36
  283. package/python/tests/test_integration.py +5 -29
  284. package/python/tests/test_itinerary.py +0 -3
  285. package/python/tests/test_kee_semantic.py +0 -5
  286. package/python/tests/test_kernel.py +1 -11
  287. package/python/tests/test_learning.py +3 -12
  288. package/python/tests/test_llm_admin.py +4 -19
  289. package/python/tests/test_llm_planner.py +1 -23
  290. package/python/tests/test_markdown_skills.py +3 -14
  291. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  292. package/python/tests/test_mcp_connectors.py +3 -15
  293. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  294. package/python/tests/test_mcp_no_memory.py +3 -3
  295. package/python/tests/test_mcp_policy_gate.py +0 -1
  296. package/python/tests/test_mcp_server.py +6 -9
  297. package/python/tests/test_memory_recall.py +1 -4
  298. package/python/tests/test_metrics.py +0 -8
  299. package/python/tests/test_multi_chain.py +1 -13
  300. package/python/tests/test_multimodal.py +5 -18
  301. package/python/tests/test_negotiation.py +3 -13
  302. package/python/tests/test_orchestration.py +1 -11
  303. package/python/tests/test_orchestration_decoupled.py +0 -6
  304. package/python/tests/test_p3_p5_hardening.py +3 -17
  305. package/python/tests/test_parallel_dispatch.py +4 -12
  306. package/python/tests/test_pipeline.py +17 -58
  307. package/python/tests/test_plan_visible.py +4 -4
  308. package/python/tests/test_planner.py +0 -7
  309. package/python/tests/test_policy_gate.py +2 -3
  310. package/python/tests/test_profile.py +67 -0
  311. package/python/tests/test_readme_claims.py +163 -0
  312. package/python/tests/test_reasoner.py +2 -17
  313. package/python/tests/test_release_check.py +2 -13
  314. package/python/tests/test_resume_checkpoint.py +7 -15
  315. package/python/tests/test_routing.py +2 -12
  316. package/python/tests/test_scheduler.py +7 -10
  317. package/python/tests/test_search.py +1 -11
  318. package/python/tests/test_secrets.py +4 -10
  319. package/python/tests/test_semantic.py +1 -10
  320. package/python/tests/test_sensitive_write.py +181 -0
  321. package/python/tests/test_sessions.py +3 -4
  322. package/python/tests/test_settings.py +2 -18
  323. package/python/tests/test_shadow_learning.py +3 -43
  324. package/python/tests/test_shared_write.py +0 -7
  325. package/python/tests/test_skill_taint_gate.py +1 -10
  326. package/python/tests/test_skills.py +2 -18
  327. package/python/tests/test_skills_fetch.py +282 -0
  328. package/python/tests/test_stats_and_events.py +2 -5
  329. package/python/tests/test_streaming.py +2 -7
  330. package/python/tests/test_supervisor_pipeline.py +4 -5
  331. package/python/tests/test_supervisor_triage.py +4 -10
  332. package/python/tests/test_taint.py +2 -7
  333. package/python/tests/test_task_stream.py +1 -12
  334. package/python/tests/test_thinking_sse.py +0 -1
  335. package/python/tests/test_tool_center.py +1 -17
  336. package/python/tests/test_tool_parallel.py +2 -2
  337. package/python/tests/test_tool_params.py +1 -5
  338. package/python/tests/test_tool_use.py +0 -13
  339. package/python/tests/test_toplevel_concurrency.py +3 -4
  340. package/python/tests/test_tracing.py +7 -41
  341. package/python/tests/test_truth_crystallizer.py +4 -6
  342. package/python/tests/test_truth_flow.py +2 -7
  343. package/python/tests/test_update_check.py +95 -0
  344. package/python/tests/test_verify_evidence.py +8 -21
  345. package/python/tests/test_version_sync.py +35 -6
  346. package/python/tests/test_web_trigger.py +0 -17
  347. package/python/tools/build_content_lexicon.py +218 -0
  348. package/python/tools/bundle_web.py +98 -0
  349. package/python/tools/check_faiss_type.py +0 -1
  350. package/python/tools/check_log.ps1 +0 -8
  351. package/python/tools/mypy_baseline.txt +12 -4
  352. package/python/tools/mypy_ratchet.py +52 -6
  353. package/python/tools/pack.py +0 -5
  354. package/python/tools/recheck_conflicts.py +156 -0
  355. package/python/tools/release_check.py +24 -15
  356. package/python/tools/restart_foxclaw.ps1 +5 -8
  357. package/python/tools/shared_write_audit.py +1 -15
  358. package/python/tools/size_check.py +1 -1
  359. package/python/tools/strip_comments.py +228 -0
  360. package/python/vendor/hkc/hkc_api/routes/ability.py +31 -0
  361. package/python/vendor/hkc/hkc_api/schemas.py +11 -0
  362. package/python/vendor/hkc/hkc_kee/kee.py +124 -3
  363. package/python/vendor/hkc/hkc_sdk/base.py +22 -0
  364. package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
  365. package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
  366. package/python/foxclaw/api/app.py +0 -123
  367. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  368. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -1,12 +1,3 @@
1
- /* 极简 Markdown → HTML。对话气泡与文档阅读共用这一份渲染器。
2
- *
3
- * 之前是两套:DocViewer 自带 readHTML/inl(支持标题、列表、行内代码、粗体、链接,
4
- * 但**不支持 ``` 代码块**),而 MessageStream 直接 {{ m.content }} 原样输出 ——
5
- * 于是模型的 `## 标题`、`**粗体**`、`- 列表`、反引号代码块在对话里全是原文。
6
- *
7
- * 安全边界:先全量转义 & < > ",之后只把识别到的标记换成标签;
8
- * 所有标签都由本文件产出,链接只放行 http(s)。
9
- */
10
1
 
11
2
  const esc = (s) =>
12
3
  String(s ?? '')
@@ -71,8 +62,6 @@ function renderTable(rows) {
71
62
  return `<table><thead><tr>${th}</tr></thead>${body.length ? `<tbody>${trs}</tbody>` : ''}</table>`
72
63
  }
73
64
 
74
- /* src 为空 → 空串。流式时最后一段代码块可能还没闭合,
75
- * 这里把它当"开着"的代码块渲染,而不是把三个反引号当正文甩出来。 */
76
65
  export function mdToHTML(src) {
77
66
  const text = String(src ?? '').replace(/\r\n?/g, '\n').replace(/\n$/, '')
78
67
  if (!text.trim()) return ''
@@ -114,7 +103,6 @@ export function mdToHTML(src) {
114
103
  continue
115
104
  }
116
105
 
117
- /* 围栏代码块 —— 对话里最常出现,优先级最高 */
118
106
  const fence = RE_FENCE.exec(line)
119
107
  if (fence) {
120
108
  flush()
@@ -137,7 +125,6 @@ export function mdToHTML(src) {
137
125
  continue
138
126
  }
139
127
 
140
- /* 表格:当前行是 | ... | 且下一行是分隔行 */
141
128
  if (RE_TABLE_ROW.test(line) && i + 1 < lines.length && RE_TABLE_SEP.test(lines[i + 1]) && lines[i + 1].includes('-')) {
142
129
  flush()
143
130
  const rows = [splitRow(line)]
@@ -154,7 +141,6 @@ export function mdToHTML(src) {
154
141
  const h = RE_HEADING.exec(line)
155
142
  if (h) {
156
143
  flush()
157
- /* # → h2, #### 及以下 → h5,不再往下掉(对话里 h6 太小) */
158
144
  const lv = Math.min(h[1].length + 1, 5)
159
145
  out.push(`<h${lv}>${inline(h[2])}</h${lv}>`)
160
146
  i += 1
@@ -3,5 +3,7 @@ import App from './App.vue'
3
3
  import './styles/tokens.css'
4
4
  import './styles/base.css'
5
5
  import './styles/panel.css'
6
+ import { applyLangTag } from './lib/i18n.js'
6
7
 
8
+ applyLangTag()
7
9
  createApp(App).mount('#app')
@@ -1,52 +1,44 @@
1
1
  import { reactive } from 'vue'
2
2
  import { getJSON } from '../api.js'
3
+ import { t } from '../lib/i18n.js'
3
4
 
4
- /* 单一状态源:所有栏只读这一份,不再有 _syncNav 那类「三个入口各改一遍」的补丁。 */
5
5
  export const state = reactive({
6
- /* 左栏 */
7
6
  sessions: [],
8
7
  sessionsState: 'idle', // idle | loading | error
9
8
  sessionId: null,
10
9
 
11
- /* 中间舞台 */
12
10
  module: 'overview',
13
11
  doc: null, // { id, title, kind:'text'|'code', lang, path, content, meta }
14
12
 
15
- /* 右栏 Inspector:双内容态,同一栏 */
16
13
  railOpen: true,
17
14
  railMode: 'progress', // 'progress' | 'context'
18
15
 
19
- /* 办公进度(OfficeAgent 的态势与进程);学习账本在 panels.review 里 */
20
16
  officeStats: { waypoints: 0, conflicts: 0, alerts: 0, active: 0, total: 0 },
21
17
  })
22
18
 
23
19
  export const MODULES = [
24
- { id: 'overview', label: '概览', tag: 'Overview', group: '' },
25
- { id: 'activity', label: '活动', tag: 'Activity', group: '工作台' },
26
- { id: 'tasks', label: '定时任务', tag: 'Tasks', group: '工作台', icon: 'clock' },
27
- { id: 'agents', label: '代理', tag: 'Agents', group: '工作台', icon: 'bot', beta: true },
28
- { id: 'tools', label: '技能·连接器', tag: 'Tools', group: '能力', icon: 'plug' },
29
- { id: 'usage', label: '使用情况', tag: 'Usage', group: '能力' },
30
- /* 学习库 = 办公/学习的汇总表:知识库里有什么、哪些到期该复习、记了什么,一份数据的三个视图。
31
- 所以它只有一个入口,hover 出「复习 / 记忆」两个快捷 —— 平级摆三行会让同一份数据看着像三份。 */
32
- { id: 'knowledge', label: '学习库', tag: 'Knowledge', group: '知识', icon: 'book', quick: ['review', 'memory'] },
33
- { id: 'review', label: '复习', tag: 'Review', group: '知识', icon: 'review' },
34
- { id: 'memory', label: '记忆中心', tag: 'Memory', group: '知识', icon: 'memory' },
35
- /* 设置不是"干活的地方",所以不进常驻栏;顶栏 ⚙ 与「更多」都能到,
36
- 顶栏那个 ⚙ 之前是死按钮(title 写着 P3 接入)。 */
37
- /* 设置落在「更多」里,那里不画图标;之前挂的 icon:'gear' 在 LeftRail 的 ICONS 里
38
- 并不存在(只有 book/clock/plug/bot/more/review/memory/dot),是个死字段。 */
39
- { id: 'settings', label: '设置', tag: 'Settings', group: '系统' },
20
+ { id: 'overview', label: '概览', tag: 'Overview', group: '', groupKey: '' },
21
+ { id: 'activity', label: '活动', tag: 'Activity', group: '工作台', groupKey: 'group.workspace' },
22
+ { id: 'tasks', label: '定时任务', tag: 'Tasks', group: '工作台', groupKey: 'group.workspace', icon: 'clock' },
23
+ { id: 'agents', label: '代理', tag: 'Agents', group: '工作台', groupKey: 'group.workspace', icon: 'bot', beta: true },
24
+ { id: 'tools', label: '技能·连接器', tag: 'Tools', group: '能力', groupKey: 'group.capability', icon: 'plug' },
25
+ { id: 'usage', label: '使用情况', tag: 'Usage', group: '能力', groupKey: 'group.capability' },
26
+ { id: 'knowledge', label: '学习库', tag: 'Knowledge', group: '知识', groupKey: 'group.knowledge', icon: 'book', quick: ['review', 'memory'] },
27
+ { id: 'review', label: '复习', tag: 'Review', group: '知识', groupKey: 'group.knowledge', icon: 'review' },
28
+ { id: 'memory', label: '记忆中心', tag: 'Memory', group: '知识', groupKey: 'group.knowledge', icon: 'memory' },
29
+ { id: 'settings', label: '设置', tag: 'Settings', group: '系统', groupKey: 'group.system', icon: 'gear' },
40
30
  ]
41
31
 
42
- /* 左栏常驻的功能入口(顺序即渲染顺序)。不进这个数组的模块全部落进底部「更多」浮层。
43
- 常驻项恒可见 = 不用先点开菜单才知道有什么功能;代价是列高固定,所以只放四个。 */
44
32
  export const RAIL = ['knowledge', 'tasks', 'tools', 'agents']
45
33
 
34
+ // 设置是侧栏底部的固定入口,不参与主导航与「更多」弹层
35
+ export const PINNED = ['settings']
36
+
46
37
  export const railItems = () => RAIL.map((id) => MODULES.find((m) => m.id === id)).filter(Boolean)
47
- export const moreItems = () => MODULES.filter((m) => !RAIL.includes(m.id))
38
+ export const moreItems = () => MODULES.filter((m) => !RAIL.includes(m.id) && !PINNED.includes(m.id))
48
39
 
49
- export const moduleLabel = (id) => MODULES.find((m) => m.id === id)?.label ?? id
40
+ export const moduleLabel = (id) => (MODULES.some((m) => m.id === id) ? t(`nav.${id}`) : id)
41
+ export const groupLabel = (key) => (key ? t(key) : '')
50
42
  export const moduleIcon = (id) => MODULES.find((m) => m.id === id)?.icon ?? 'dot'
51
43
 
52
44
  export async function loadSessions() {
@@ -68,13 +60,7 @@ export function openSession(s) {
68
60
  }
69
61
 
70
62
  export async function loadProgress() {
71
- /* 学习账本已改由 panels.review(/api/review/*)提供 —— SM-2 状态写在 KU.extra 里,
72
- 和知识库是同一份数据;/learning/analysis 只统计 learning_note 一类条目,
73
- 两套口径并存会让右栏数字和复习面板对不上。 */
74
63
 
75
- /* 办公 = OfficeAgent(行政职责已并入它)的全局态势 + 进程。
76
- 「已摄入资料」已挪到右栏的「最近知识」(读 /api/knowledge/recent),
77
- 书籍级列表留在知识库面板,这里不再重复拉 /knowledge/sources。 */
78
64
  const [sit, proc] = await Promise.all([
79
65
  getJSON('/orchestration/situation').catch(() => null),
80
66
  getJSON('/processes?limit=5').catch(() => null),
@@ -93,7 +79,6 @@ export function setModule(id) {
93
79
  state.doc = null
94
80
  }
95
81
 
96
- /* 打开一份文件 → 正文进中间舞台(宽度够读代码),右栏同步切到检视态并自动展开 */
97
82
  export function openDoc(doc) {
98
83
  state.doc = doc
99
84
  state.railMode = 'context'
@@ -1,4 +1,3 @@
1
- /* 对话与输入区状态:模式 / 附件 / 图片 / 摄入任务 / 消息流 / SSE */
2
1
  import { reactive } from 'vue'
3
2
  import { API, delJSON, getJSON, getRaw, postForm, postFormRaw, postJSON } from '../api.js'
4
3
  import { loadSessions, state as app } from './app.js'
@@ -14,12 +13,9 @@ export const chat = reactive({
14
13
  ingest: null, // {name,pct,status,ku,msg}
15
14
  toast: '',
16
15
 
17
- /* 对话 */
18
16
  messages: [], // [{role,content,thinking,evidence,error,ts,streaming,stage,plan}]
19
17
  sessionId: null,
20
18
  streamState: 'idle', // idle | streaming | done | error
21
- /* 错误落在具体那条消息的 m.error 上(界面上能看到 + 能点重试),
22
- 这里不再留一个没人读的 lastError */
23
19
  })
24
20
 
25
21
  let toastTimer = 0
@@ -35,7 +31,6 @@ export function setMode(m) {
35
31
  localStorage.setItem(MODE_KEY, m)
36
32
  }
37
33
 
38
- /* ── 附件:POST /api/upload → {id,name,size,mime,url,text} ── */
39
34
  export async function attachFile(file) {
40
35
  if (!file) return
41
36
  if (file.size > MAX_MB * 1048576) return say(`${file.name} 超过 ${MAX_MB}MB`)
@@ -50,7 +45,6 @@ export async function attachFile(file) {
50
45
  }
51
46
  }
52
47
 
53
- /* ── 图片:本地转 dataURL,不落盘 ── */
54
48
  export function attachImage(file) {
55
49
  if (!file) return
56
50
  if (!file.type.startsWith('image/')) return say('不是图片文件')
@@ -67,8 +61,6 @@ export function dropImage(i) {
67
61
  chat.images.splice(i, 1)
68
62
  }
69
63
 
70
- /* ── 摄入知识库:POST /knowledge/ingest/file/async → 轮询 /knowledge/ingest/jobs/{id}
71
- 注意路径无 /api 前缀:vendor/hkc 的 knowledge router 是根路径挂载的。 ── */
72
64
  export async function ingestFile(file, collection = '') {
73
65
  if (!file) return
74
66
  if (chat.ingest) return say('等当前文件摄入完成再传下一个')
@@ -90,7 +82,6 @@ export async function ingestFolder(files) {
90
82
  const list = [...files].filter(Boolean)
91
83
  for (const f of list) {
92
84
  await ingestFile(f)
93
- /* ingestFile 内部已串行守卫:等当前 job 结束再投下一个 */
94
85
  while (chat.ingest && chat.ingest.status !== 'error') {
95
86
  if (chat.ingest.status === 'done') break
96
87
  await new Promise((r) => setTimeout(r, 600))
@@ -123,8 +114,6 @@ async function pollIngest(jobId) {
123
114
  say('摄入超时,后台可能仍在跑')
124
115
  }
125
116
 
126
- /* ══════════ 会话 ══════════ */
127
-
128
117
  const STAGE_CN = {
129
118
  perceive: '感知', recall: '召回', meta_check: '自检', schedule: '排程',
130
119
  route: '路由', plan: '规划', dispatch: '分发', act: '执行',
@@ -150,7 +139,6 @@ function adoptSession(id) {
150
139
  localStorage.setItem(SID_KEY, id)
151
140
  }
152
141
 
153
- /* 启动时恢复上次会话(后端 sessions 落盘,thinking/evidence 不落盘) */
154
142
  export async function bootChat() {
155
143
  const saved = localStorage.getItem(SID_KEY)
156
144
  if (saved) await loadSession(saved, { quiet: true })
@@ -173,14 +161,11 @@ export async function loadSession(id, { quiet = false } = {}) {
173
161
  }
174
162
  }
175
163
 
176
- /* 组件间的一次性信号:斜杠命令要调 ToolMenu 里的文件选择,又不能硬耦合。
177
- (/设置 不再走这里 —— 它现在直接切到设置面板,见 ChatDock 的命令表) */
178
164
  export const ui = reactive({ ingestTick: 0 })
179
165
  export function requestIngest() {
180
166
  ui.ingestTick += 1
181
167
  }
182
168
 
183
- /* 纯本地消息(/帮助 之类),不经过后端 */
184
169
  export function pushLocal(content) {
185
170
  chat.messages.push({
186
171
  role: 'assistant', content, ts: Date.now(),
@@ -188,7 +173,6 @@ export function pushLocal(content) {
188
173
  })
189
174
  }
190
175
 
191
- /* 认知搜索(HSR):POST /api/search/query,结果作为一张证据卡挂在消息流里 */
192
176
  export async function runSearch(rawQuery) {
193
177
  const query = String(rawQuery || '').trim()
194
178
  if (!query) return say('用法:/搜索 关键词')
@@ -205,7 +189,6 @@ export async function runSearch(rawQuery) {
205
189
  const msg = chat.messages[chat.messages.length - 1]
206
190
 
207
191
  try {
208
- /* internal_only=true:只查 HMR 记忆 + HKC 知识 + Agent 态势,不联网 —— 联网分支实测返回空图 */
209
192
  const d = await postJSON('/search/query', { query, agent_id: 'supervisor', internal_only: true })
210
193
  const nodes = (d.graph && d.graph.nodes) || []
211
194
  msg.search = {
@@ -242,7 +225,6 @@ export async function newSession() {
242
225
  }
243
226
  }
244
227
 
245
- /* 改标题:后端 rename 会把空串兜回「新会话」,这里先挡掉空提交,避免误清标题 */
246
228
  export async function renameSession(id, title) {
247
229
  const t = String(title || '').trim().slice(0, 60)
248
230
  if (!id || !t) return
@@ -255,8 +237,6 @@ export async function renameSession(id, title) {
255
237
  }
256
238
  }
257
239
 
258
- /* 删会话。删的是当前会话时落回列表里的下一条;一条不剩就新开一个 ——
259
- 否则界面会停在一个已经不存在的 sessionId 上,下一条消息直接 404。 */
260
240
  export async function deleteSession(id) {
261
241
  if (!id) return
262
242
  try {
@@ -271,7 +251,6 @@ export async function deleteSession(id) {
271
251
  else await newSession()
272
252
  }
273
253
 
274
- /* 重发:把这条回复对应的提问找回来,截断到它之前再投一次(附件/图片一起还原) */
275
254
  export async function retry(msg) {
276
255
  if (chat.streamState === 'streaming') return say('正在生成,先点停止')
277
256
  const i = chat.messages.indexOf(msg)
@@ -301,28 +280,20 @@ async function ensureSession() {
301
280
  adoptSession(saved)
302
281
  return saved
303
282
  } catch {
304
- /* 后端已无此会话 → 落到下面新建 */
305
283
  }
306
284
  }
307
285
  return newSession()
308
286
  }
309
287
 
310
- /* ══════════ 发送 / 流式 ══════════ */
311
-
312
288
  let ctrl = null
313
- /* 每次 send 领一个代号。切会话 / 新建会话时把代号推进,在跑的那一路回来发现
314
- 代号变了就不再碰全局状态 —— 否则它会把新会话的 streamState 覆盖掉、还把 ctrl
315
- 清空,导致新流停不下来,两路 token 同时往不同消息里写。 */
316
289
  let gen = 0
317
290
 
318
- /* 掐断在跑的流:切会话、新建会话、重试前都先调一次 */
319
291
  function cutStream() {
320
292
  gen += 1
321
293
  if (ctrl) {
322
294
  try {
323
295
  ctrl.abort()
324
296
  } catch {
325
- /* 已经收尾了 */
326
297
  }
327
298
  ctrl = null
328
299
  }
@@ -351,7 +322,6 @@ export async function send(rawText) {
351
322
  const attachments = chat.attachments.map((a) => ({
352
323
  name: a.name, size: a.size, mime: a.mime, url: a.url, text: a.text || '',
353
324
  }))
354
- /* 后端只用最近 12 条拼上下文,与旧前端保持一致 */
355
325
  const history = chat.messages
356
326
  .filter((m) => m.content && m.role && !m.error)
357
327
  .slice(-12)
@@ -359,15 +329,12 @@ export async function send(rawText) {
359
329
 
360
330
  chat.messages.push({
361
331
  role: 'user', content: text, ts: Date.now(), attachments, streaming: false, thinking: '',
362
- /* 存一份图片快照:重试时 send() 从 chat.images 取图,不带上就丢图了 */
363
332
  images: chat.images.map((im) => ({ ...im })),
364
333
  })
365
334
  chat.messages.push({
366
335
  role: 'assistant', content: '', thinking: '', evidence: [], error: '', notice: '',
367
336
  ts: Date.now(), streaming: true, stage: '', plan: null,
368
337
  })
369
- /* 必须拿回数组里的响应式代理:直接改 push 进去的原始对象不会触发重渲染,
370
- 表现就是气泡永远停在「···」而内容其实早到了 */
371
338
  const reply = chat.messages[chat.messages.length - 1]
372
339
  chat.attachments = []
373
340
  chat.images = []
@@ -412,7 +379,6 @@ export async function send(rawText) {
412
379
  }
413
380
 
414
381
  function applyEvent(msg, name, ev) {
415
- /* 命名事件与 JSON 键双判据:后端明确要求两者都认 */
416
382
  if (name === 'thinking' || ev.thinking) {
417
383
  msg.thinking += ev.thinking || ''
418
384
  return
@@ -1,4 +1,3 @@
1
- /* 模型选择:全部走 /api/llm/*(llm_admin.py),切换后服务端立即生效,无需重启。 */
2
1
  import { reactive } from 'vue'
3
2
  import { delJSON, getJSON, postJSON, putJSON } from '../api.js'
4
3
 
@@ -28,7 +27,6 @@ export async function loadModels(force = false) {
28
27
  }
29
28
  }
30
29
 
31
- /* 当前 pill 上显示的名字与色点 */
32
30
  export function currentLabel() {
33
31
  const c = model.current
34
32
  if (!c) return { name: 'Auto', color: '#7e93b8', sub: '跟随 .env 默认' }
@@ -49,9 +47,6 @@ export async function selectModel({ source, provider = null, name = null }) {
49
47
  return r
50
48
  }
51
49
 
52
- /* 设置面板里的模型清单:auto / 预设 / 自定义摊平成同一形状。
53
- active 的判定必须和上面的 currentLabel 一致 —— 两处各写一套,切换后 pill 和
54
- 列表就会各说各的(这类"同一状态两套判定"是回归温床)。 */
55
50
  export function modelOptions() {
56
51
  const c = model.current
57
52
  const out = [{
@@ -81,13 +76,10 @@ export async function addCustom(payload) {
81
76
  await loadModels(true)
82
77
  }
83
78
 
84
- /* 连通性测试:后端发一个 "hi" 走真实客户端,失败时 **不抛** 而是返回 {ok:false,error}
85
- —— 那正是要显示给用户看的东西,别当异常吞掉。 */
86
79
  export async function testModel(payload) {
87
80
  return postJSON('/llm/test', payload)
88
81
  }
89
82
 
90
- /* 单个自定义模型详情:api_key 永不出网,只回 has_key(编辑表单里留空即保留原值) */
91
83
  export async function getCustom(name) {
92
84
  return getJSON(`/llm/custom/${encodeURIComponent(name)}`)
93
85
  }
@@ -1,6 +1,3 @@
1
- /* 左栏菜单 8 项的数据层。
2
- 一项一个 loader,内部每个端点单独 .catch —— 某个子系统挂了只让对应区块空着,
3
- 不让整个面板变「后端未连接」。组件只负责渲染,不各自写 fetch。 */
4
1
  import { reactive } from 'vue'
5
2
  import { getJSON, getRaw, delJSON, patchJSON, postJSON } from '../api.js'
6
3
  import { openDoc } from './app.js'
@@ -15,15 +12,10 @@ export const panels = reactive({
15
12
  usage: { state: 'idle', tokens: null, gateway: {}, metrics: null, slow: [] },
16
13
  knowledge: {
17
14
  state: 'idle', stats: {}, index: {}, sources: [], abilities: [],
18
- /* 最近录入的知识单元:右栏只读前 10 条,面板看全量 */
19
15
  recent: [], recentTotal: 0,
20
- /* 录入是同步 LLM 抽取,可能几十秒 —— 状态提到 store 里,
21
- 切到别的面板再回来时不会以为「没提交过」 */
22
16
  ingest: 'idle', ingestMsg: '', ingestErr: '',
23
17
  },
24
- memory: { state: 'idle', stats: {}, recent: [], prefs: [] },
25
- /* SM-2 复习:stats 是全局账本,queue 是「今天这一轮要过的卡」。
26
- session 放 store 是因为复习到一半点开文档再回来,不能让进度归零。 */
18
+ memory: { state: 'idle', stats: {}, recent: [], prefs: [], pending: [] },
27
19
  review: {
28
20
  state: 'idle',
29
21
  stats: { cards: 0, learned: 0, fresh: 0, due: 0, avg_mastery: 0, load7: {}, risk: [] },
@@ -36,7 +28,6 @@ export const panels = reactive({
36
28
  })
37
29
 
38
30
  const LOADERS = {
39
- /* 活动 = 事件总线(系统发生了什么) + 请求链路(哪次调用慢) + 思考链健康度 */
40
31
  async activity(p) {
41
32
  const [bus, traces, thought] = await Promise.all([
42
33
  safe(getJSON('/bus/history?limit=40')),
@@ -49,7 +40,6 @@ const LOADERS = {
49
40
  return !!(bus || traces || thought)
50
41
  },
51
42
 
52
- /* 任务 = 调度器记账 + 进程生命周期(后端把每个任务建模成进程) */
53
43
  async tasks(p) {
54
44
  const [sched, proc] = await Promise.all([
55
45
  safe(getJSON('/scheduler/stats')),
@@ -61,7 +51,6 @@ const LOADERS = {
61
51
  return !!(sched || proc)
62
52
  },
63
53
 
64
- /* 代理 = /api/status(全部 Agent 的 harness + 思考链) + 系统信息 */
65
54
  async agents(p) {
66
55
  const [all, info] = await Promise.all([
67
56
  safe(getJSON('/status')),
@@ -72,7 +61,6 @@ const LOADERS = {
72
61
  return !!(all || info)
73
62
  },
74
63
 
75
- /* 技能 = 内置/HTTP/MCP 技能表 + 指令技能(SKILL.md) + 熔断器 */
76
64
  async tools(p) {
77
65
  const [sk, md, br] = await Promise.all([
78
66
  safe(getJSON('/ahs/skills')),
@@ -85,7 +73,6 @@ const LOADERS = {
85
73
  return !!(sk || md || br)
86
74
  },
87
75
 
88
- /* 使用情况 = token 计量 + 网关配额 + 端点延迟(p95 最慢的几条) */
89
76
  async usage(p) {
90
77
  const [tk, gw, mt] = await Promise.all([
91
78
  safe(getJSON('/tokens')),
@@ -102,8 +89,6 @@ const LOADERS = {
102
89
  return !!(tk || gw || mt)
103
90
  },
104
91
 
105
- /* 知识库 = HKC 根路径 /stats 与 /knowledge/sources,加 Foxclaw 侧能力清单
106
- 与最近录入的 KU(右栏和面板共用这一份) */
107
92
  async knowledge(p) {
108
93
  const [st, src, ab, rc] = await Promise.all([
109
94
  safe(getRaw('/stats')),
@@ -120,8 +105,6 @@ const LOADERS = {
120
105
  return !!(st || src || ab || rc)
121
106
  },
122
107
 
123
- /* 复习 = SM-2 账本 + 今日队列。会话进行中只更新账本,不动队列 ——
124
- 否则右栏刷新一次就把当前卡片换掉了。 */
125
108
  async review(p) {
126
109
  const [st, q] = await Promise.all([
127
110
  safe(getJSON('/review/stats')),
@@ -137,24 +120,24 @@ const LOADERS = {
137
120
  return !!(st || q)
138
121
  },
139
122
 
140
- /* 记忆中心 = HMR 统计 + 最近记忆 + 用户偏好 */
141
123
  async memory(p) {
142
- const [st, rc, pf] = await Promise.all([
124
+ const [st, rc, pf, pd] = await Promise.all([
143
125
  safe(getJSON('/memory/stats')),
144
126
  safe(getJSON('/memory/recent?limit=12')),
145
127
  safe(getJSON('/preferences')),
128
+ safe(getJSON('/memory/pending')),
146
129
  ])
147
130
  p.stats = st || {}
148
131
  p.recent = rc?.memories || []
149
132
  p.prefs = pf?.preferences || []
150
- return !!(st || rc || pf)
133
+ p.pending = pd?.pending || []
134
+ return !!(st || rc || pf || pd)
151
135
  },
152
136
  }
153
137
 
154
138
  export async function loadPanel(id, force = false) {
155
139
  const p = panels[id]
156
140
  if (!p || !LOADERS[id]) return
157
- /* 已有数据就不重拉:切换菜单来回点不重复打后端;要新数据显式刷新 */
158
141
  if (!force && p.state !== 'idle' && p.state !== 'error') return
159
142
  p.state = 'loading'
160
143
  let ok = false
@@ -164,18 +147,11 @@ export async function loadPanel(id, force = false) {
164
147
 
165
148
  export const refreshPanel = (id) => loadPanel(id, true)
166
149
 
167
- /* ── 面板内的写操作(都是真实端点,不是本地假删除) ── */
168
-
169
- /* 指令技能启停:PATCH /ahs/md-skills/{id} → 全量重拉一次保证和服务端一致 */
170
150
  export async function toggleMdSkill(id, enabled) {
171
151
  await patchJSON(`/ahs/md-skills/${encodeURIComponent(id)}`, { enabled })
172
152
  await refreshPanel('tools')
173
153
  }
174
154
 
175
- /* 录入一段短知识:POST /knowledge/ingest → KDE 抽取成 KU → KEE 演化。
176
- 同步 LLM 抽取,可能几十秒,所以状态挂在 store 上而不是组件里。
177
- 成功后**静默重拉**:走 refreshPanel 会把 state 置回 loading,整面板闪骨架,
178
- 而这里只想要新数据。 */
179
155
  export async function ingestKnowledge({ text, title = '', domain = '' }) {
180
156
  const p = panels.knowledge
181
157
  const body = (text || '').trim()
@@ -197,8 +173,6 @@ export async function ingestKnowledge({ text, title = '', domain = '' }) {
197
173
  const n = d?.ku_count ?? 0
198
174
  p.ingestMsg = n
199
175
  ? `已抽取 ${n} 条知识单元${counts ? ` · ${counts}` : ''}`
200
- /* 走到这里说明 LLM 调用是成功的(全失败会 502 进 catch),只是这段文字里
201
- 没抽出 Concept/Fact/Claim/Entity。别提示"没配模型"误导用户去改 key。 */
202
176
  : '模型没从这段文字里抽出知识条目 —— 试试写得更具体(谁 / 是什么 / 为什么)'
203
177
  try { await LOADERS.knowledge(p); p.state = 'ok' } catch {}
204
178
  return d
@@ -209,9 +183,6 @@ export async function ingestKnowledge({ text, title = '', domain = '' }) {
209
183
  }
210
184
  }
211
185
 
212
- /* ── SM-2 复习会话 ── */
213
-
214
- /* 开一轮:队列空了就先拉一次。会话状态在 store 里,点开文档再回来不丢进度。 */
215
186
  export async function startReview() {
216
187
  const p = panels.review
217
188
  if (!p.queue.length) await loadPanel('review', true)
@@ -226,9 +197,6 @@ export function revealCard() {
226
197
  panels.review.revealed = true
227
198
  }
228
199
 
229
- /* 评分 → 服务端按 SM-2 算下一次间隔 → 翻到下一张。
230
- 四档对应 SM-2 的 0/3/4/5:0 完全忘记(重置)、3 勉强、4 记住、5 轻松。
231
- 只更新账本不动队列 —— 队列是这一轮的现场。 */
232
200
  export async function gradeCard(grade) {
233
201
  const p = panels.review
234
202
  const card = p.queue[p.index]
@@ -244,13 +212,22 @@ export async function gradeCard(grade) {
244
212
  return d
245
213
  }
246
214
 
247
- /* 删除一条误存的偏好:DELETE /preferences/{id} */
248
215
  export async function dropPreference(id) {
249
216
  await delJSON(`/preferences/${encodeURIComponent(id)}`)
250
217
  await refreshPanel('memory')
251
218
  }
252
219
 
253
- /* 技能 / KU 正文 → 中间舞台。kind 决定皮肤:code 走 CLI 黑底,text 走排版正文 */
220
+ // 高敏感内容被闸门拦下后停在这里,用户点头才落库(见 foxclaw/core/sensitive_write.py)
221
+ export async function approveSensitiveWrite(id) {
222
+ await postJSON(`/memory/pending/${encodeURIComponent(id)}/approve`, {})
223
+ await refreshPanel('memory')
224
+ }
225
+
226
+ export async function rejectSensitiveWrite(id) {
227
+ await postJSON(`/memory/pending/${encodeURIComponent(id)}/reject`, {})
228
+ await refreshPanel('memory')
229
+ }
230
+
254
231
  export async function openMdSkill(id) {
255
232
  const d = await getJSON(`/ahs/md-skills/${encodeURIComponent(id)}`)
256
233
  openDoc({
@@ -264,8 +241,6 @@ export async function openMdSkill(id) {
264
241
  })
265
242
  }
266
243
 
267
- /* extra 里的业务字段逐类型不同(Fact 有 statement、Evidence 有 source、Entity 有 aliases),
268
- 按类型硬编码会漏;这里统一按「有值就列出来」处理,任何 KU 打开都有内容可读。 */
269
244
  const KU_FIELDS = [
270
245
  ['statement', '陈述'],
271
246
  ['definition', '定义'],
@@ -289,8 +264,6 @@ const kuVal = (v) => {
289
264
  return v === 0 || v === '' || v == null ? '' : String(v)
290
265
  }
291
266
 
292
- /* 单个 KU 的正文 → 中间舞台。走 HKC 根路径 /knowledge/ku/{id}
293
- (那条路径带 source_text,列表/图谱的响应为了瘦身把它剔掉了) */
294
267
  export async function openKu(id) {
295
268
  const d = await getRaw(`/knowledge/ku/${encodeURIComponent(id)}`)
296
269
  const extra = d.extra || {}
@@ -298,7 +271,6 @@ export async function openKu(id) {
298
271
  const rows = KU_FIELDS
299
272
  .map(([k, label]) => [label, kuVal(extra[k])])
300
273
  .filter(([, v]) => v)
301
- /* statement 已经在标题下一行重复过一次,正文里不再复读 */
302
274
  const body = rows.filter(([, v]) => v !== d.summary && v !== d.name)
303
275
 
304
276
  const parts = []
@@ -0,0 +1,31 @@
1
+ import { reactive } from 'vue'
2
+ import { getJSON, putJSON } from '../api.js'
3
+
4
+ export const profile = reactive({
5
+ nickname: '',
6
+ loaded: false,
7
+ saving: false,
8
+ })
9
+
10
+ export async function loadProfile(force = false) {
11
+ if (profile.loaded && !force) return profile
12
+ try {
13
+ const d = await getJSON('/profile')
14
+ profile.nickname = d.nickname || ''
15
+ profile.loaded = true
16
+ } catch {
17
+ }
18
+ return profile
19
+ }
20
+
21
+ export async function saveNickname(nick) {
22
+ profile.saving = true
23
+ try {
24
+ const d = await putJSON('/profile', { nickname: nick || '' })
25
+ profile.nickname = d.nickname || ''
26
+ profile.loaded = true
27
+ return profile.nickname
28
+ } finally {
29
+ profile.saving = false
30
+ }
31
+ }
@@ -0,0 +1,25 @@
1
+ import { reactive } from 'vue'
2
+ import { getJSON } from '../api.js'
3
+
4
+ export const system = reactive({
5
+ version: '',
6
+ update: null, // { enabled, current, latest, update_available, checked_at, error }
7
+ loaded: false,
8
+ })
9
+
10
+ let inflight = null
11
+
12
+ export function loadSystemInfo(force = false) {
13
+ if (system.loaded && !force) return Promise.resolve(system)
14
+ if (inflight) return inflight
15
+ inflight = getJSON('/system/info')
16
+ .then((d) => {
17
+ system.version = d.version || ''
18
+ system.update = d.update || null
19
+ system.loaded = true
20
+ return system
21
+ })
22
+ .catch(() => system) // 拿不到就维持旧值:顶栏不显示版本,不显示更新
23
+ .finally(() => { inflight = null })
24
+ return inflight
25
+ }
@@ -3,8 +3,6 @@ import { defineConfig } from 'vite'
3
3
  import vue from '@vitejs/plugin-vue'
4
4
  import { fileURLToPath, URL } from 'node:url'
5
5
 
6
- /* 设置面板底部的版本行照 OpenClaw 写成 git@<sha> · <N 天前>。
7
- 构建时抓一次 git 信息;没有 git(打包机 / 压缩包)就退回 dev,不让构建失败。 */
8
6
  function gitInfo() {
9
7
  try {
10
8
  const sha = execSync('git rev-parse --short HEAD', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
@@ -15,9 +13,6 @@ function gitInfo() {
15
13
  }
16
14
  }
17
15
 
18
- /* 产物直接写进 frontend/v3/,后端 (StaticFiles(directory=frontend, html=True)) 零改动:
19
- /v3/ -> frontend/v3/index.html
20
- base 必须是 '/v3/',否则 assets 会打到站点根。 */
21
16
  export default defineConfig({
22
17
  base: '/v3/',
23
18
  plugins: [vue()],
@@ -34,12 +29,10 @@ export default defineConfig({
34
29
  },
35
30
  server: {
36
31
  port: 5173,
37
- /* dev 时把 API 与 WS 透到本地后端,前端代码里一律用相对路径 /api */
38
32
  proxy: {
39
33
  '/api': { target: 'http://127.0.0.1:8000', changeOrigin: true },
40
34
  '/ws': { target: 'ws://127.0.0.1:8000', ws: true },
41
35
  '/planet': { target: 'http://127.0.0.1:8000', changeOrigin: true },
42
- /* /knowledge/* 无 /api 前缀:HKC 自带 router 由后端根路径挂载 */
43
36
  '/knowledge': { target: 'http://127.0.0.1:8000', changeOrigin: true },
44
37
  },
45
38
  },