@turmind/halo 0.1.9

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 (296) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/admin-out/404.html +1 -0
  4. package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_buildManifest.js +1 -0
  5. package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_ssgManifest.js +1 -0
  6. package/admin-out/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js +18 -0
  7. package/admin-out/_next/static/chunks/200.3b8f8a74f005a134.js +1 -0
  8. package/admin-out/_next/static/chunks/220.3dc41cd6269b88e6.js +1 -0
  9. package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +1 -0
  10. package/admin-out/_next/static/chunks/304eda7d.b1ee7f523653e027.js +1 -0
  11. package/admin-out/_next/static/chunks/330.3b784cf0dcd69163.js +1 -0
  12. package/admin-out/_next/static/chunks/442.f4302087bddb1ec1.js +1 -0
  13. package/admin-out/_next/static/chunks/443.d1b289781793f8de.js +25 -0
  14. package/admin-out/_next/static/chunks/503-7c6d9935df6e0cea.js +1 -0
  15. package/admin-out/_next/static/chunks/533.5f2d4bfe3d076095.js +1 -0
  16. package/admin-out/_next/static/chunks/579.a2718ddd395a2f3a.js +1 -0
  17. package/admin-out/_next/static/chunks/635-3dd0043ed0520b9d.js +1 -0
  18. package/admin-out/_next/static/chunks/65.859665ba6717967a.js +1 -0
  19. package/admin-out/_next/static/chunks/661.a4fe93f3f2a5e871.js +1 -0
  20. package/admin-out/_next/static/chunks/688.4e014ac6cfc7e152.js +1 -0
  21. package/admin-out/_next/static/chunks/868.0c95dfe283400a00.js +1 -0
  22. package/admin-out/_next/static/chunks/app/_not-found/page-8e35399c8fd5016d.js +1 -0
  23. package/admin-out/_next/static/chunks/app/layout-58f773336e6fce61.js +1 -0
  24. package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +1 -0
  25. package/admin-out/_next/static/chunks/db534642.09d3c650ff587175.js +1 -0
  26. package/admin-out/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js +1 -0
  27. package/admin-out/_next/static/chunks/framework-c3031ae54a63fe16.js +1 -0
  28. package/admin-out/_next/static/chunks/main-14b48e3c2588354f.js +1 -0
  29. package/admin-out/_next/static/chunks/main-app-483b436873410ddc.js +1 -0
  30. package/admin-out/_next/static/chunks/pages/_app-b2fc6e5f920d1b1a.js +1 -0
  31. package/admin-out/_next/static/chunks/pages/_error-f3696a8f3d67d150.js +1 -0
  32. package/admin-out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  33. package/admin-out/_next/static/chunks/webpack-8bd7ef88a6527feb.js +1 -0
  34. package/admin-out/_next/static/css/659eccb5db697b76.css +1 -0
  35. package/admin-out/_next/static/css/8fce4e1a54dac12a.css +3 -0
  36. package/admin-out/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
  37. package/admin-out/_next/static/media/21350d82a1f187e9-s.woff2 +0 -0
  38. package/admin-out/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
  39. package/admin-out/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
  40. package/admin-out/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
  41. package/admin-out/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  42. package/admin-out/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
  43. package/admin-out/favicon.ico +0 -0
  44. package/admin-out/icon.svg +18 -0
  45. package/admin-out/index.html +1 -0
  46. package/admin-out/index.txt +23 -0
  47. package/admin-out/monaco/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
  48. package/admin-out/monaco/vs/abap-D-t0cyap.js +1 -0
  49. package/admin-out/monaco/vs/apex-CcIm7xu6.js +1 -0
  50. package/admin-out/monaco/vs/assets/css.worker-HnVq6Ewq.js +93 -0
  51. package/admin-out/monaco/vs/assets/editor.worker-Be8ye1pW.js +26 -0
  52. package/admin-out/monaco/vs/assets/html.worker-B51mlPHg.js +470 -0
  53. package/admin-out/monaco/vs/assets/json.worker-DKiEKt88.js +58 -0
  54. package/admin-out/monaco/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
  55. package/admin-out/monaco/vs/azcli-BA0tQDCg.js +1 -0
  56. package/admin-out/monaco/vs/basic-languages/monaco.contribution.js +1 -0
  57. package/admin-out/monaco/vs/bat-C397hTD6.js +1 -0
  58. package/admin-out/monaco/vs/bicep-DF5aW17k.js +2 -0
  59. package/admin-out/monaco/vs/cameligo-plsz8qhj.js +1 -0
  60. package/admin-out/monaco/vs/clojure-Y2auQMzK.js +1 -0
  61. package/admin-out/monaco/vs/coffee-Bu45yuWE.js +1 -0
  62. package/admin-out/monaco/vs/cpp-CkKPQIni.js +1 -0
  63. package/admin-out/monaco/vs/csharp-CX28MZyh.js +1 -0
  64. package/admin-out/monaco/vs/csp-D8uWnyxW.js +1 -0
  65. package/admin-out/monaco/vs/css-CaeNmE3S.js +3 -0
  66. package/admin-out/monaco/vs/cssMode-CjiAH6dQ.js +1 -0
  67. package/admin-out/monaco/vs/cypher-DVThT8BS.js +1 -0
  68. package/admin-out/monaco/vs/dart-CmGfCvrO.js +1 -0
  69. package/admin-out/monaco/vs/dockerfile-CZqqYdch.js +1 -0
  70. package/admin-out/monaco/vs/ecl-30fUercY.js +1 -0
  71. package/admin-out/monaco/vs/editor/editor.main.css +1 -0
  72. package/admin-out/monaco/vs/editor/editor.main.js +5 -0
  73. package/admin-out/monaco/vs/editor.api-CalNCsUg.js +903 -0
  74. package/admin-out/monaco/vs/elixir-xjPaIfzF.js +1 -0
  75. package/admin-out/monaco/vs/flow9-DqtmStfK.js +1 -0
  76. package/admin-out/monaco/vs/freemarker2-Cz_sV6Md.js +3 -0
  77. package/admin-out/monaco/vs/fsharp-BOMdg4U1.js +1 -0
  78. package/admin-out/monaco/vs/go-D_hbi-Jt.js +1 -0
  79. package/admin-out/monaco/vs/graphql-CKUU4kLG.js +1 -0
  80. package/admin-out/monaco/vs/handlebars-OwglfO-1.js +1 -0
  81. package/admin-out/monaco/vs/hcl-DTaboeZW.js +1 -0
  82. package/admin-out/monaco/vs/html-Pa1xEWsY.js +1 -0
  83. package/admin-out/monaco/vs/htmlMode-Bz67EXwp.js +1 -0
  84. package/admin-out/monaco/vs/ini-CsNwO04R.js +1 -0
  85. package/admin-out/monaco/vs/java-CI4ZMsH9.js +1 -0
  86. package/admin-out/monaco/vs/javascript-PczUCGdz.js +1 -0
  87. package/admin-out/monaco/vs/jsonMode-DULH5oaX.js +7 -0
  88. package/admin-out/monaco/vs/julia-BwzEvaQw.js +1 -0
  89. package/admin-out/monaco/vs/kotlin-IUYPiTV8.js +1 -0
  90. package/admin-out/monaco/vs/language/css/monaco.contribution.js +1 -0
  91. package/admin-out/monaco/vs/language/html/monaco.contribution.js +1 -0
  92. package/admin-out/monaco/vs/language/json/monaco.contribution.js +1 -0
  93. package/admin-out/monaco/vs/language/typescript/monaco.contribution.js +1 -0
  94. package/admin-out/monaco/vs/less-C0eDYdqa.js +2 -0
  95. package/admin-out/monaco/vs/lexon-iON-Kj97.js +1 -0
  96. package/admin-out/monaco/vs/liquid-DqKjdPGy.js +1 -0
  97. package/admin-out/monaco/vs/loader.js +1368 -0
  98. package/admin-out/monaco/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
  99. package/admin-out/monaco/vs/lua-DtygF91M.js +1 -0
  100. package/admin-out/monaco/vs/m3-CsR4AuFi.js +1 -0
  101. package/admin-out/monaco/vs/markdown-C_rD0bIw.js +1 -0
  102. package/admin-out/monaco/vs/mdx-DEWtB1K5.js +1 -0
  103. package/admin-out/monaco/vs/mips-CiYP61RB.js +1 -0
  104. package/admin-out/monaco/vs/monaco.contribution-D2OdxNBt.js +1 -0
  105. package/admin-out/monaco/vs/monaco.contribution-DO3azKX8.js +1 -0
  106. package/admin-out/monaco/vs/monaco.contribution-EcChJV6a.js +1 -0
  107. package/admin-out/monaco/vs/monaco.contribution-qLAYrEOP.js +1 -0
  108. package/admin-out/monaco/vs/msdax-C38-sJlp.js +1 -0
  109. package/admin-out/monaco/vs/mysql-CdtbpvbG.js +1 -0
  110. package/admin-out/monaco/vs/nls.messages-loader.js +1 -0
  111. package/admin-out/monaco/vs/nls.messages.cs.js.js +17 -0
  112. package/admin-out/monaco/vs/nls.messages.de.js.js +17 -0
  113. package/admin-out/monaco/vs/nls.messages.es.js.js +17 -0
  114. package/admin-out/monaco/vs/nls.messages.fr.js.js +15 -0
  115. package/admin-out/monaco/vs/nls.messages.it.js.js +15 -0
  116. package/admin-out/monaco/vs/nls.messages.ja.js.js +17 -0
  117. package/admin-out/monaco/vs/nls.messages.js.js +10 -0
  118. package/admin-out/monaco/vs/nls.messages.ko.js.js +25 -0
  119. package/admin-out/monaco/vs/nls.messages.pl.js.js +17 -0
  120. package/admin-out/monaco/vs/nls.messages.pt-br.js.js +6 -0
  121. package/admin-out/monaco/vs/nls.messages.ru.js.js +17 -0
  122. package/admin-out/monaco/vs/nls.messages.tr.js.js +15 -0
  123. package/admin-out/monaco/vs/nls.messages.zh-cn.js.js +17 -0
  124. package/admin-out/monaco/vs/nls.messages.zh-tw.js.js +15 -0
  125. package/admin-out/monaco/vs/objective-c-CntZFaHX.js +1 -0
  126. package/admin-out/monaco/vs/pascal-r6kuqfl_.js +1 -0
  127. package/admin-out/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
  128. package/admin-out/monaco/vs/perl-DABw_TcH.js +1 -0
  129. package/admin-out/monaco/vs/pgsql-me_jFXeX.js +1 -0
  130. package/admin-out/monaco/vs/php-D_kh-9LK.js +1 -0
  131. package/admin-out/monaco/vs/pla-VfZjczW0.js +1 -0
  132. package/admin-out/monaco/vs/postiats-BBSzz8Pk.js +1 -0
  133. package/admin-out/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
  134. package/admin-out/monaco/vs/powershell-B-7ap1zc.js +1 -0
  135. package/admin-out/monaco/vs/protobuf-BmtuEB1A.js +2 -0
  136. package/admin-out/monaco/vs/pug-BRpRNeEb.js +1 -0
  137. package/admin-out/monaco/vs/python-Cr0UkIbn.js +1 -0
  138. package/admin-out/monaco/vs/qsharp-BzsFaUU9.js +1 -0
  139. package/admin-out/monaco/vs/r-f8dDdrp4.js +1 -0
  140. package/admin-out/monaco/vs/razor-BYAHOTkz.js +1 -0
  141. package/admin-out/monaco/vs/redis-fvZQY4PI.js +1 -0
  142. package/admin-out/monaco/vs/redshift-45Et0LQi.js +1 -0
  143. package/admin-out/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
  144. package/admin-out/monaco/vs/ruby-CZO8zYTz.js +1 -0
  145. package/admin-out/monaco/vs/rust-Bfetafyc.js +1 -0
  146. package/admin-out/monaco/vs/sb-3GYllVck.js +1 -0
  147. package/admin-out/monaco/vs/scala-foMgrKo1.js +1 -0
  148. package/admin-out/monaco/vs/scheme-CHdMtr7p.js +1 -0
  149. package/admin-out/monaco/vs/scss-C1cmLt9V.js +3 -0
  150. package/admin-out/monaco/vs/shell-ClXCKCEW.js +1 -0
  151. package/admin-out/monaco/vs/solidity-MZ6ExpPy.js +1 -0
  152. package/admin-out/monaco/vs/sophia-DWkuSsPQ.js +1 -0
  153. package/admin-out/monaco/vs/sparql-AUGFYSyk.js +1 -0
  154. package/admin-out/monaco/vs/sql-32GpJSV2.js +1 -0
  155. package/admin-out/monaco/vs/st-CuDFIVZ_.js +1 -0
  156. package/admin-out/monaco/vs/swift-n-2HociN.js +3 -0
  157. package/admin-out/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
  158. package/admin-out/monaco/vs/tcl-D74tq1nH.js +1 -0
  159. package/admin-out/monaco/vs/tsMode-CZz1Umrk.js +11 -0
  160. package/admin-out/monaco/vs/twig-C6taOxMV.js +1 -0
  161. package/admin-out/monaco/vs/typescript-DfOrAzoV.js +1 -0
  162. package/admin-out/monaco/vs/typespec-D-PIh9Xw.js +1 -0
  163. package/admin-out/monaco/vs/vb-Dyb2648j.js +1 -0
  164. package/admin-out/monaco/vs/wgsl-BhLXMOR0.js +298 -0
  165. package/admin-out/monaco/vs/workers-DcJshg-q.js +1 -0
  166. package/admin-out/monaco/vs/xml-CdsdnY8S.js +1 -0
  167. package/admin-out/monaco/vs/yaml-DYGvmE88.js +1 -0
  168. package/bin/halo.js +9 -0
  169. package/bundled-docs/design/agent.md +145 -0
  170. package/bundled-docs/design/architecture.md +270 -0
  171. package/bundled-docs/design/background-dispatch.md +151 -0
  172. package/bundled-docs/design/command.md +58 -0
  173. package/bundled-docs/design/cron.md +88 -0
  174. package/bundled-docs/design/evolution.md +383 -0
  175. package/bundled-docs/design/express-self.md +145 -0
  176. package/bundled-docs/design/feishu.md +224 -0
  177. package/bundled-docs/design/halo-city.md +112 -0
  178. package/bundled-docs/design/prompt-system.md +411 -0
  179. package/bundled-docs/design/session.md +321 -0
  180. package/bundled-docs/design/slack.md +212 -0
  181. package/bundled-docs/design/storage.md +578 -0
  182. package/bundled-docs/design/telegram.md +161 -0
  183. package/bundled-docs/design/web.md +222 -0
  184. package/bundled-docs/design/wechat.md +146 -0
  185. package/bundled-docs/design/ws.md +144 -0
  186. package/bundled-docs/dev/acp-adapter.md +337 -0
  187. package/bundled-docs/dev/add-channel.md +376 -0
  188. package/bundled-docs/dev/add-model-provider.md +386 -0
  189. package/bundled-docs/dev/api.md +686 -0
  190. package/bundled-docs/dev/deploy.md +198 -0
  191. package/bundled-docs/dev/desktop-packaging.md +327 -0
  192. package/bundled-docs/dev/env.md +158 -0
  193. package/bundled-docs/dev/logging.md +85 -0
  194. package/bundled-docs/dev/previews.md +225 -0
  195. package/bundled-docs/dev/tools.md +331 -0
  196. package/bundled-docs/guide/README.md +29 -0
  197. package/bundled-docs/guide/agents.md +264 -0
  198. package/bundled-docs/guide/channels/README.md +62 -0
  199. package/bundled-docs/guide/channels/acp.md +150 -0
  200. package/bundled-docs/guide/channels/feishu.md +154 -0
  201. package/bundled-docs/guide/channels/slack.md +156 -0
  202. package/bundled-docs/guide/channels/telegram.md +112 -0
  203. package/bundled-docs/guide/channels/web.md +149 -0
  204. package/bundled-docs/guide/channels/wechat.md +110 -0
  205. package/bundled-docs/guide/chat.md +112 -0
  206. package/bundled-docs/guide/cli.md +197 -0
  207. package/bundled-docs/guide/getting-started.md +93 -0
  208. package/bundled-docs/guide/secrets-and-credentials.md +161 -0
  209. package/bundled-docs/guide/sessions.md +104 -0
  210. package/bundled-docs/guide/skills.md +302 -0
  211. package/bundled-docs/guide/testing-agents-and-skills.md +136 -0
  212. package/bundled-docs/guide/workspace.md +131 -0
  213. package/bundled-docs/plans/cron-tasks.md +362 -0
  214. package/bundled-docs/plans/design-suggestions.md +203 -0
  215. package/bundled-docs/plans/issue-session-deep-nesting.md +208 -0
  216. package/bundled-docs/plans/launch-plan.md +111 -0
  217. package/bundled-docs/plans/md-and-agent-refactor.md +197 -0
  218. package/bundled-docs/plans/remote-mac-worker.md +196 -0
  219. package/bundled-docs/plans/roadmap.md +40 -0
  220. package/bundled-docs/plans/self-evolution.md +1379 -0
  221. package/bundled-docs/plans/todo.md +32 -0
  222. package/bundled-docs/plans/weixin-channel.md +255 -0
  223. package/bundled-docs/requirements/agent.md +92 -0
  224. package/bundled-docs/requirements/chat.md +81 -0
  225. package/bundled-docs/requirements/command.md +130 -0
  226. package/bundled-docs/requirements/editor.md +82 -0
  227. package/bundled-docs/requirements/explorer.md +89 -0
  228. package/bundled-docs/requirements/overview.md +62 -0
  229. package/bundled-docs/requirements/session.md +73 -0
  230. package/bundled-docs/requirements/settings.md +224 -0
  231. package/bundled-docs/requirements/skill.md +125 -0
  232. package/bundled-docs/requirements/source-control.md +82 -0
  233. package/bundled-docs/requirements/terminal.md +118 -0
  234. package/bundled-docs/requirements/workspace.md +80 -0
  235. package/bundled-docs/test/docs-seeding.md +101 -0
  236. package/bundled-docs/test/session.md +374 -0
  237. package/dist/index.js +26750 -0
  238. package/package.json +67 -0
  239. package/templates/INSTRUCTIONS.md +25 -0
  240. package/templates/agents/__apply_agent__/AGENT.md +229 -0
  241. package/templates/agents/__apply_agent__/agent.yaml +30 -0
  242. package/templates/agents/__evo_agent__/AGENT.md +323 -0
  243. package/templates/agents/__evo_agent__/agent.yaml +29 -0
  244. package/templates/agents/__score__/AGENT.md +177 -0
  245. package/templates/agents/__score__/agent.yaml +29 -0
  246. package/templates/agents/deep-executor/AGENT.md +60 -0
  247. package/templates/agents/deep-executor/agent.yaml +25 -0
  248. package/templates/agents/default/AGENT.md +38 -0
  249. package/templates/agents/default/agent.yaml +51 -0
  250. package/templates/agents/executor/AGENT.md +38 -0
  251. package/templates/agents/executor/agent.yaml +25 -0
  252. package/templates/aliases.yaml +26 -0
  253. package/templates/canvas/self.html +660 -0
  254. package/templates/config.yaml +69 -0
  255. package/templates/models/anthropic.yaml +47 -0
  256. package/templates/models/aws-bedrock-claude-invoke.yaml +156 -0
  257. package/templates/models/aws-bedrock-mantle.yaml +89 -0
  258. package/templates/models/deepseek.yaml +62 -0
  259. package/templates/models/doubao.yaml +137 -0
  260. package/templates/models/hunyuan.yaml +56 -0
  261. package/templates/models/kimi.yaml +41 -0
  262. package/templates/models/mimo-token-plan-china.yaml +61 -0
  263. package/templates/models/minimax.yaml +80 -0
  264. package/templates/models/openai.yaml +51 -0
  265. package/templates/models/qwen.yaml +78 -0
  266. package/templates/optional-skills/tavily-web-search/SKILL.md +91 -0
  267. package/templates/optional-skills/tavily-web-search/config.yaml +10 -0
  268. package/templates/prompts/all/TOOL_GUIDELINES.md +35 -0
  269. package/templates/prompts/all/TOOL_SHELL.md +4 -0
  270. package/templates/prompts/all/TOOL_SHELL.windows.md +7 -0
  271. package/templates/prompts/bootstrap/BOOTSTRAP.md +26 -0
  272. package/templates/prompts/root/WORKSPACE_MEMORY.md +11 -0
  273. package/templates/schema.sql +30 -0
  274. package/templates/skills/acp/SKILL.md +236 -0
  275. package/templates/skills/acp/templates/SKILL.md.tmpl +74 -0
  276. package/templates/skills/acp/templates/ask.py +176 -0
  277. package/templates/skills/acp/templates/config.yaml.tmpl +33 -0
  278. package/templates/skills/agent/SKILL.md +128 -0
  279. package/templates/skills/aws-knowledge/SKILL.md +82 -0
  280. package/templates/skills/aws-knowledge/scripts/aws_kb.py +61 -0
  281. package/templates/skills/cron/SKILL.md +217 -0
  282. package/templates/skills/cron/manage-cron.py +434 -0
  283. package/templates/skills/halo/SKILL.md +98 -0
  284. package/templates/skills/nova-web-search/SKILL.md +65 -0
  285. package/templates/skills/self/SKILL.md +139 -0
  286. package/templates/skills/send-file/SKILL.md +28 -0
  287. package/templates/skills/send-file/feishu.md +16 -0
  288. package/templates/skills/send-file/slack.md +7 -0
  289. package/templates/skills/send-file/telegram.md +9 -0
  290. package/templates/skills/send-file/web.md +6 -0
  291. package/templates/skills/send-file/wechat.md +8 -0
  292. package/templates/skills/skill/SKILL.md +131 -0
  293. package/templates/skills/workspace/SKILL.md +28 -0
  294. package/templates/skills/workspace/setup-tidy.md +265 -0
  295. package/templates/skills/workspace/share.md +187 -0
  296. package/templates/skills/workspace/stage.py +487 -0
@@ -0,0 +1,224 @@
1
+ # Feishu/Lark Channel — Design
2
+
3
+ Let the user talk to Halo from Feishu (Lark) on their phone, sharing the same workspace + sessions as the web side. All channels are peers — each one is a subscriber + caller against SessionManager.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ┌── ws/ (web channel) ─┐
9
+ ├── channels/telegram/ │
10
+ Halo server (9527) ──┤├── channels/wechat/ ├── SessionManager
11
+ ├── channels/slack/ │ (per workspace, via Registry)
12
+ └── channels/feishu/ ←─ long-connect (wss) ─┘
13
+ ↕ Lark.WSClient
14
+ https://open.feishu.cn
15
+ ```
16
+
17
+ Feishu uses the official SDK's `Lark.WSClient` to maintain a persistent WebSocket connection for event delivery. Unlike Slack/Telegram (which use webhooks + polling respectively), Feishu's long-connect receives `im.message.receive_v1` events pushed by the server over wss.
18
+
19
+ ## Data model
20
+
21
+ ### Workspace ↔ Bot mapping
22
+
23
+ One Feishu app (= one appId + appSecret pair) is bound to one workspace. A workspace can bind multiple apps.
24
+
25
+ ### Session strategy
26
+
27
+ - **One Feishu user → many sessions (one active at a time, per thread)**
28
+ - Session ID format: `feishu_<chatId>:<rootId>_<createdAtBase36>`
29
+ - In p2p (DM): rootId anchored to `dm` for a stable key across multiple messages
30
+ - In group: each thread is its own bounded session, keyed by the thread's root message id
31
+ - Sessions live under the bot's bound workspace and use the highest-priority agent (falls back to `default` only when none exists)
32
+ - Access level inherited from the account's `accessLevel` field (see below)
33
+
34
+ ### Access level
35
+
36
+ Each account carries `accessLevel: 'full' | 'workspace' | 'readonly'` (default `readonly`):
37
+ - `full` — no restrictions (no sandbox)
38
+ - `workspace` — tool execution runs inside a bwrap sandbox with the workspace mounted read-write
39
+ - `readonly` — tool execution runs inside a bwrap sandbox with the workspace mounted read-only; readonly sessions only receive read-only tools
40
+
41
+ ### Storage
42
+
43
+ Feishu bot accounts are stored in the unified channel DB: `~/.halo/secrets/channels/channels.db`, table `channel_accounts` with `channel_type = 'feishu'`. See [storage.md](storage.md#channel_accounts) for the full schema.
44
+
45
+ Feishu-specific config JSON fields: `appId`, `appSecret`, `verificationToken` (legacy), `encryptKey` (optional), `botOpenId`, `lastActiveChatId`.
46
+
47
+ - `lastActiveChatId` is a runtime cache of the most recent `<chatId>:<rootId>` pair. Written by `rememberLastActiveChat()` in `channels/shared/accounts.ts` on every inbound message (idempotent). Not consumed by cron dispatch (the dispatcher requires an explicit `chatId`); it is kept as a runtime cache for any future "reply to whoever talked last" feature.
48
+
49
+ ### Authentication model
50
+
51
+ - `appId` + `appSecret` are long-lived credentials from the Feishu open platform
52
+ - Used to mint short-lived `tenant_access_token` (~2 hour lifetime) for all API calls
53
+ - `verificationToken` and `encryptKey` handle webhook security (for legacy webhook setups; not used with long-connect)
54
+ - `botOpenId` is the bot's own open_id, used to detect mentions in group chats
55
+
56
+ Token caching is in-memory per-app in `api.ts:tokenCache`; refresh happens on-demand 60s before expiry.
57
+
58
+ ### Proactive sending (cron)
59
+
60
+ The feishu cron-dispatcher (`channels/feishu/cron-dispatcher.ts`) requires an explicit `chatId` — there is no fallback to `lastActiveChatId`. When a cron fires:
61
+
62
+ 1. **Explicit `chatId` on the target** — set when the cron was created from inside a Feishu chat (e.g. cron created from a group thread auto-pins to that thread). Sends only to that chat (top-level message, not nested in the thread — Feishu's open API v1 doesn't support thread-targeting in sendMessage).
63
+ 2. **Admin-UI cron without explicit target** — runs silently if no chatId is provided.
64
+
65
+ ## Modules
66
+
67
+ Files: `packages/server/src/channels/feishu/`
68
+
69
+ - `types.ts` — FeishuAccount, FeishuEventEnvelope, FeishuMessageEvent interfaces
70
+ - `accounts.ts` — DAL (listAccounts / getAccount / insertAccount / updateAccount / deleteAccount / findAccountByAppId)
71
+ - `handler.ts` — Lark.WSClient setup, event dispatch, message handling, session routing, mention detection, media ingestion
72
+ - `event-adapter.ts` — AgentSessionEvent stream → coalesced Feishu message replies (buffer + flush at paragraph boundaries)
73
+ - `api.ts` — HTTP client (tenant token caching / getBotInfo / sendMessage / replyMessage / uploadImage / uploadFile / downloadResource / decryptWebhookBody / searchFeishuTargets / openLongConnection)
74
+ - `cron-dispatcher.ts` — registers the cron dispatcher, requires explicit chatId targets
75
+ - `descriptor.ts` — ServerChannelDescriptor entry point
76
+
77
+ Routes: `packages/server/src/routes/feishu.ts`
78
+
79
+ - `GET /api/feishu/accounts` — list bot accounts (admin)
80
+ - `POST /api/feishu/accounts` — register a bot (validates credentials via `getBotInfo`, auto-resolves botOpenId)
81
+ - `PATCH /api/feishu/accounts/:id` — update config / enable / disable
82
+ - `DELETE /api/feishu/accounts/:id` — remove + stop
83
+ - `GET /api/feishu/accounts/:id/search` — search chats by name (for cron target picker)
84
+
85
+ ## Bot lifecycle
86
+
87
+ 1. User registers a bot via the API (provides appId + appSecret + workspace)
88
+ 2. Server calls `getBotInfo` to validate credentials and auto-resolve botOpenId; stores account; starts long-connect
89
+ 3. Lark.WSClient handles the wss connection, ping/pong, URL rotation (~30 min), and automatic reconnection
90
+ 4. On graceful shutdown, `wsClient.close()` is called for each active bot
91
+
92
+ ## Long-connect frame protocol
93
+
94
+ On connect to `wss://…` (negotiated via `POST /callback/ws/endpoint`):
95
+
96
+ 1. Client sends a `register` frame containing the `conn_id` received from the negotiation response
97
+ 2. Server pushes event frames typed by `type`:
98
+ - `1` = `frame` (event payload — `im.message.receive_v1` event data we care about)
99
+ - `2` = `pong` (heartbeat reply)
100
+ - `8` = `disconnect` (server is rotating the URL)
101
+ 3. Client periodically sends `ping` (type=0) to keep the connection alive
102
+
103
+ The SDK wraps protobuf marshalling; we just register an `EventDispatcher` callback for `im.message.receive_v1` events.
104
+
105
+ ## Message handling flow
106
+
107
+ `im.message.receive_v1` event:
108
+
109
+ 1. Extract sender (open_id or user_id), message content, chat_type (p2p or group), mentions array
110
+ 2. Check if bot was mentioned (p2p: always yes; group: check mentions array for bot's open_id)
111
+ 3. Drop non-user senders (bots, apps)
112
+ 4. Determine session key: p2p → `(chatId, 'dm')`; group → `(chatId, rootId ?? messageId)` to anchor each thread
113
+ 5. Parse content (text / image / file / post) — images extracted as base64 for vision, files noted as `[文件: name]` markers
114
+ 6. Download inbound images, save to workspace, generate base64 payloads
115
+ 7. Strip mention markup from text (Feishu inserts `<at user_id="ou_xxx">@bot</at>` or `@_user_123` tokens)
116
+ 8. Slash command dispatch (p2p only — group threads don't benefit from `/session new`, `/session list`, etc. since each thread is already its own session)
117
+ 9. Create or retrieve active session for this thread (with inherited access level)
118
+ 10. If session is compacting → reply "⏳ 正在整理上下文,请稍后再发消息(通常 30 秒内完成)" and skip queueing
119
+ 11. If session is busy → reply "已收到,会在当前轮结束后处理。" and enqueue
120
+ 12. Register a `FeishuResponder` event listener (once per session) that coalesces outbound text
121
+ 13. `sm.sendUserMessage(sessionId, agentInput, images?)` with channel + user + thread context
122
+
123
+ ## Slash commands (native Feishu /commands)
124
+
125
+ Implemented via `channels/shared/commands.ts` (shared across Telegram, WeChat, Slack, Feishu):
126
+
127
+ | Command | Purpose |
128
+ |---|---|
129
+ | `/start` | Welcome message (p2p only) |
130
+ | `/session new` | Create a new session (p2p only) |
131
+ | `/session list` | List recent sessions (p2p only) |
132
+ | `/session switch <index>` | Switch active session (p2p only) |
133
+ | `/session stop` | Abort running task |
134
+ | `/session compact` | Compress context |
135
+ | `/workspace info` | Show workspace; `/workspace switch <path>` changes it (full access only) |
136
+ | `/help` | List commands |
137
+
138
+ ## Event coalescing (FeishuResponder)
139
+
140
+ Feishu's text message limit (~5000 chars), smaller than Slack. Strategy:
141
+
142
+ - Buffer streamed text until `complete` event
143
+ - Flush fires on **any** `complete` — the responder doesn't read its `batchBoundary` flag, so a multi-round queue drain ships each merged turn as its own message instead of one blob (see [session.md](session.md#message-queue-and-drain))
144
+ - If buffer exceeds 4500 chars, split at paragraph boundary (`\n\n`)
145
+ - Otherwise hard-cut at 4500 chars
146
+ - `system` and `error` events flush early so users always see something before the run ends
147
+ - Media markers (`MEDIA:<path>`) intercepted and sent via native media upload
148
+ - Sub-agent events dropped (visible in web UI only)
149
+
150
+ ## Media support
151
+
152
+ **Inbound:** text, images (base64 attached to agent message), files (noted as `[文件: name]` markers), voice, video, rich posts.
153
+
154
+ **Outbound routing:**
155
+ - `.jpg/.png/.gif/.webp/.bmp` → `uploadImage` → `msg_type: 'image'`
156
+ - `.opus` → `uploadFile(fileType: 'opus')` → `msg_type: 'audio'` (non-opus voice files sent as generic files)
157
+ - `.mp4/.mov/.webm/.m4v/.avi` → `uploadFile(fileType: 'mp4')` → `msg_type: 'media'` (with optional cover frame)
158
+ - everything else → `uploadFile(fileType: 'stream')` → `msg_type: 'file'`
159
+
160
+ Inbound images are downloaded via `/im/v1/messages/{messageId}/resources/{imageKey}?type=image`, decrypted if needed, saved under `<workspace>/.halo/assets/feishu/inbound/<accountId>/<date>/`, and fed to vision.
161
+
162
+ ## Message routing (thread vs p2p)
163
+
164
+ **p2p (DM):** Replies sent via `sendMessage` targeting the chat (Feishu p2p doesn't support threads).
165
+
166
+ **Group (chat):** Replies sent via `replyMessage` with `reply_in_thread: true` and the inbound message id. This auto-creates a thread rooted at the inbound message if it doesn't exist, or appends to the existing thread.
167
+
168
+ ## Similarities vs Slack and Telegram
169
+
170
+ | Aspect | Feishu | Slack | Telegram |
171
+ |--------|--------|-------|----------|
172
+ | **Inbound delivery** | Long-connect (wss) | Webhook + slash commands | Long-polling |
173
+ | **Session model** | Per-thread in groups, p2p anchored | Per-thread + channels | Per-user (one active) |
174
+ | **Mention required** | Yes in groups, no in p2p | Yes in channels, no in DMs | No (all messages routed to agent) |
175
+ | **Cron target** | Explicit chatId only | Explicit chatId only (DM / channel / thread) | Explicit numeric chatId only |
176
+ | **Media upload** | Separate image + file endpoints | Unified File upload API | Bot API sendPhoto / sendVideo / sendDocument |
177
+ | **Text limit** | ~5000 chars | ~4000 chars | ~4000 chars |
178
+ | **Thread support** | Native (root_id in messages) | Native (thread_ts replies) | N/A (group chats not supported) |
179
+
180
+ ## Configuration
181
+
182
+ ### Feishu app setup
183
+
184
+ 1. Go to https://open.feishu.cn/
185
+ 2. Create a new app (register as a custom app in your org, or create ISV app)
186
+ 3. Under Settings → Permissions, enable:
187
+ - `im:message:readonly` — receive message events
188
+ - `im:message` — send messages
189
+ - `im:resource` — upload images / files
190
+ - `im:chat:readonly` — search chats (for cron target picker)
191
+ 4. Under Settings → Bot, enable long-connect: "Get Card / Bot / Message Callbacks" → subscribe to `im.message.receive_v1`
192
+ 5. Copy `App ID` and `App Secret` (displayed on the settings page, or regenerate if lost)
193
+
194
+ ### Register to Halo
195
+
196
+ ```bash
197
+ curl -X POST http://localhost:9527/api/feishu/accounts \
198
+ -H 'Content-Type: application/json' \
199
+ -d '{
200
+ "appId": "cli_xxxxxxxxxxxxxxxx",
201
+ "appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
202
+ "workspacePath": "/home/user/my-workspace",
203
+ "accessLevel": "full",
204
+ "label": "My Feishu Bot"
205
+ }'
206
+ ```
207
+
208
+ Server auto-resolves `botOpenId` from the credentials, validates them, and starts the long-connect immediately.
209
+
210
+ ## Scope and out-of-scope
211
+
212
+ Supported: p2p text, group text with mention-required; threads; images (base64 attached to agent); audio, video, files (uploaded and referenced); slash commands (p2p only); per-session access level; media sending.
213
+
214
+ Not supported: group chat without mention (we require explicit @mention for groups to avoid noise); rich message composition in responses (text only, plus media as separate messages); reactions; bot callbacks for button clicks / form submissions.
215
+
216
+ ## Key file references
217
+
218
+ - Long-connect: `packages/server/src/channels/feishu/handler.ts:connect()` (line 241)
219
+ - Token caching: `packages/server/src/channels/feishu/api.ts:getTenantAccessToken()` (line 39)
220
+ - Message parsing: `packages/server/src/channels/feishu/handler.ts:parseContent()` (line 135)
221
+ - Event dispatch: `packages/server/src/channels/feishu/handler.ts:handleInbound()` (line 301)
222
+ - Cron dispatch: `packages/server/src/channels/feishu/cron-dispatcher.ts:dispatch()` (line 27)
223
+ - Event coalescing: `packages/server/src/channels/feishu/event-adapter.ts:FeishuResponder` (line 24)
224
+ - REST routes: `packages/server/src/routes/feishu.ts` (all account CRUD + search)
@@ -0,0 +1,112 @@
1
+ # Halo City · 像素街区可视化
2
+
3
+ Halo 服务器运行态的**街景式**像素可视化:侧视、剖面的像素城,实时天色下,
4
+ 每个 **workspace 是一栋楼**,每个 **agent session 是一只 Q 版动物市民**,爬真楼梯
5
+ 到真工位上班。点任何人打开他的**真实会话日志**(服务端流式拉取,和 admin 面板同源)。
6
+
7
+ 只读、零构建、零贴图素材(每个像素都是代码画的)。网络流量:一个
8
+ `GET /api/show/state` 轮询,外加面板打开时的 `GET /api/show/session`。
9
+
10
+ > 接口契约见 [dev/api.md](../dev/api.md#show-world-snapshot)(`/api/show/state`、
11
+ > `/api/show/session` 的字段与鉴权)。本文只讲前端可视化设计。
12
+
13
+ ## 街区结构
14
+
15
+ ```
16
+ ☀/☾ parallax skyline · clouds · stars ──────────────────────────
17
+ ┌─[roof: water tank / AC / antenna / billboard]─┐
18
+ │ W3 desks ×3 · skill station · flavor prop │
19
+ ├───────────────────────────────────────────────┤ ┌──────────┐
20
+ │ C 公共层: 冰箱·水吧·书架·沙发·街机/鱼缸/猫 ─[阳台]│ │ next │
21
+ ├───────────────────────────────────────────────┤ │ building │
22
+ │ W2 / W1 work floors … │ │ │
23
+ ├───────────────────────────────────────────────┤ │ │
24
+ │ 大堂: 前台 · 咖啡机 · 沙发 · 大门 │ │ │
25
+ └─⌐stairs──────────────────────────────awning───┘ └──────────┘
26
+ ──┴── sidewalk · 餐车 · 野餐桌 · 篮筐 · 单车架 · 路灯 ──┴───────────
27
+ road (楼距加宽:巷子就是楼下放风的地方)
28
+ ```
29
+
30
+ | 看到的 | 意思 |
31
+ |---|---|
32
+ | 🏬 一栋楼 | 一个 workspace。**每 3 个工作层插 1 个公共层**(楼够高时),楼层数**贴合当前 root 会话数**(每层对应一个真实会话,顶上不留空层;增减都跟,但保留 1 层缓冲——会话数在两次轮询间 ±1 抖动不会让楼顶反复抽搐);砖材/霓虹竖牌/楼顶装置由 id hash 决定 |
33
+ | 🐾 动物市民 | 一个 agent session。**制服**(衬衫+条纹)认 agent 名 → 在哪都认得出"researcher";**物种/毛色/花纹/配件**(10 物种 × 多毛色 × 眼镜/耳机/斑纹)认 session id → 同 agent 并发的 session 是穿同款衬衫的不同动物。会眨眼,说话动嘴,狗走路摇尾巴 |
34
+ | 🪜 楼梯间 | 大楼左侧,真的逐层爬上爬下(没有传送) |
35
+ | 💺 工作层 | 每层 3 张工位(按 agent 名固定)+ 1 个技能站 + 随机小设施(盆栽/小书架/白板/售货机);干活时屏幕滚代码 |
36
+ | 🍵 公共层 | 冰箱(偶尔开门漏光)、水吧(水壶冒汽)、书报架、沙发,外加街机/鱼缸/猫爬架三选一;右侧伸出**阳台**(烟灰桶+盆栽)——抽烟点 |
37
+ | 🚬 习惯 | 每个 session 由 id hash 派生 **3 个固定偏好**(抽烟/咖啡/泡茶/翻冰箱/看书/街机/看鱼/撸猫/打电话/下楼遛弯/沙发),摸鱼时偏好权重 ~4×——"这个会话就爱在阳台抽烟"是稳定人设,不是随机骰子 |
38
+ | 🧭 就近原则 | 市民**生在自己的归属层**(自己的工位层;子代理用父代理的工位层),不再从大堂爬上来。休息活动以**归属层**为锚点就近找公共层(每 3 层就有),不会为了倒杯茶爬通天,**也不会随时间往低层漂**——12 楼的人永远回 F11 休息,不会一路沉到大堂。高层会话不下大堂:`下楼遛弯` 只有 F5 及以下才真下街,F6+ 改在就近休息层放风 |
39
+ | 💡 灯光 | 有人在的楼层灯亮(running 或 idle 都算——摸鱼也开灯),只有彻底 stopped 的层才熄;天黑后街灯/霓虹/远景窗火全亮 |
40
+ | ↳ 小一号 | 子代理:生在并聚在父代理那层桌边,不占工位 |
41
+
42
+ 进出有戏:新会话直接出现在自己的工位层(从本层楼梯口走到工位,不再从街上爬通天);结束的走出门沿街远去淡出。
43
+
44
+ ## 点击 = 真实数据
45
+
46
+ 点任何市民,面板里是**真东西**:
47
+
48
+ - **会话日志**:`/api/show/session` 拉来的最近 40 条消息流——用户输入、助手回复、
49
+ 工具调用(带耗时),像 `tail -f` 一样贴底自动滚,面板开着每 6s 只刷日志框
50
+ (面板本体原地补丁,不再整页重建闪烁)
51
+ - **上下文仪表**:`134k / 200k (67%)` —— 分母是该会话的**真实** `maxContextTokens`
52
+ - **委派链**:父/子会话可点击跳转,每行带 token 数和消息数
53
+ - **点楼层**:该层的会话委派关系树(缩进、可点跳转)——工作层看"谁在这层干活、
54
+ 带着哪些子代理",公共层看"谁在摸鱼"
55
+ - 点大楼:全员 roster + token 合计;点技能站:谁在用
56
+
57
+ ## 街景与载具
58
+
59
+ 街区不只是静态背景,会按**真实时间**自己演(`traffic.js`):
60
+
61
+ | 事件 | 节奏 | 说明 |
62
+ |---|---|---|
63
+ | 🛸 UFO | 每天 0:00 | 午夜从街头飘过 |
64
+ | ✈️ 飞机 | 每半点(`:30`) | 高空掠过,方向随机 |
65
+ | 🚌 巴士 | 每 3 分钟 | 进站、开门、接走在站台等车的人,再开走 |
66
+ | 🚗 进出场载具 | 跟随会话 | 新会话打车/电动车/跑车送到门口;结束的会话走到站台**体面地等车离场**,不再凭空消失 |
67
+ | 🏗️ 盖楼 / 拆楼 | 跟随楼层增减 | 长楼层有施工动效;拆楼留 ~30s 缓冲(先让人撤离再慢慢消散,不穿帮) |
68
+
69
+ > 定时全用真实 `Date.now()` 计算(`?hour=` 只冻结天色、不冻结这些事件)。
70
+
71
+ ## 交互与显示
72
+
73
+ 操作:滚轮缩放(九档 `0.3×–6×`)· 拖拽平移 · 点击查看 · **双击**在 1×/3× 间快速切 ·
74
+ **F** 全街区 · **H**(或点左上角 logo)切纯净模式 · **Esc** 逐层退(先关面板,再退纯净)·
75
+ 右上角 **中/EN** 一键切语言。闲置 10s 后镜头沿街慢慢巡游。
76
+
77
+ - **纯净模式(zen)**:淡出所有 HUD(统计/动态/图例/缩放条/面板),只留街景,适合大屏常驻展示
78
+ - **多语言(i18n)**:中文 / English 一键切;`?lang=en`/`?lang=zh` 直达,选择存 localStorage。
79
+ 运行时文案走 `t()`,静态 DOM 走 `data-i18n` 属性填充
80
+
81
+ ## 调试参数
82
+
83
+ - `?hour=22.5` — 把昼夜定格在某个时刻(支持小数;只冻结天色,不冻结街景事件)
84
+ - `?lang=en` / `?lang=zh` — 指定语言
85
+ - kiosk 直链:`/#api=…&token=…`(token 立即从地址栏抹掉)
86
+
87
+ ## 文件结构
88
+
89
+ ```
90
+ js/
91
+ main.js 入口:setup → world → 输入/轮询
92
+ api.js state 轮询 + session 详情客户端(content-type 防呆)
93
+ world.js 快照 → 城市/市民 diff,渲染循环,拾取
94
+ city.js 街区:楼(材质/楼层/霓虹/楼顶)、大堂、街道、视差天际线
95
+ citizen.js 市民:楼层×楼梯 2D 路径规划 + 行为 FSM
96
+ people.js 人物绘制:动物物种×毛色×配件池,眨眼/口型
97
+ props.js 家具:工位/技能站/咖啡机/街机/鱼缸/猫爬架/窗/灯
98
+ palette.js EDG32 + 建筑材质 ramp + 关键帧天空
99
+ camera.js 整数吸附镜头(九档缩放 0.3×–6×)
100
+ inspector.js 面板 + 实时会话日志查看器
101
+ ticker.js 街区动态 feed
102
+ traffic.js 街景事件:UFO(0点)/飞机(每半点)/进出场载具/每 3 分钟 bus/盖楼·拆楼
103
+ i18n.js 多语言词典(zh/en)+ 运行时 t() + 静态 data-i18n 填充
104
+ util.js hash/PRNG/格式化
105
+ ```
106
+
107
+ ## 服务端配套
108
+
109
+ - `GET /api/show/session?ws=&id=` — 只读会话详情:裁剪后的消息日志 + 真实
110
+ token 上限。鉴权同 `/api/show/state`(x-token;非 full 只能看自己 workspace)
111
+ - `/api/show/state` — 无活跃 UIState 的会话(idle/stopped/重启后)token
112
+ 从会话文件头读取(mtime 缓存,不随轮询刷盘),不显示 0;含 `messageCount` 字段