@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,686 @@
1
+ # API Routes
2
+
3
+ All REST endpoints are served by Hono on port 9527 at `/api/`.
4
+
5
+ Auth: most `/api/*` routes require a valid JWT cookie (`halo_token`). Exceptions in `PUBLIC_PATHS` (`middleware/auth.ts`) bypass the cookie: `/api/auth/*`, the web-channel routes (`/api/web/chat|stop|history|subscribe|file`), `/api/show/state|session`, and `/api/metrics` — these are unauthenticated or use a web-channel `x-token` instead.
6
+
7
+ ## Health
8
+
9
+ | Method | Path | Purpose |
10
+ |---|---|---|
11
+ | GET | `/api/health` | Health check — returns `{status, timestamp, uptime, engine}` |
12
+
13
+ ## Authentication
14
+
15
+ File: `packages/server/src/middleware/auth.ts`
16
+
17
+ | Method | Path | Purpose |
18
+ |---|---|---|
19
+ | POST | `/api/auth/login` | Password login, returns a JWT cookie |
20
+ | POST | `/api/auth/logout` | Clears the auth cookie |
21
+ | GET | `/api/auth/check` | Validates the current token; refreshes stale tokens |
22
+
23
+ ## Files
24
+
25
+ File: `packages/server/src/routes/files.ts`
26
+
27
+ | Method | Path | Purpose |
28
+ |---|---|---|
29
+ | GET | `/api/files/tree?projectId=[&path=]` | List one level (lazy). Every directory carries `hasChildren`. No `path` = project root. |
30
+ | GET | `/api/files/search?projectId=&q=[&limit=][&dirsOnly=1]` | Recursive name search. Default limit 200, max 1000. Scans up to 50000 entries then truncates; returns `{matches, truncated}`. `dirsOnly=1` matches directories instead of files (powers the chat `@scope` directory completion). |
31
+ | GET | `/api/files?path=&projectId=` | Read file content (max 10 MB) |
32
+ | GET | `/api/files/stat?path=&projectId=` | Lightweight mtime + size |
33
+ | GET | `/api/files/diff?path=&projectId=` | Git diff |
34
+ | GET | `/api/files/download?path=&projectId=&inline=` | Download or inline-preview — streams the file (no full read into memory), supports `Range` (206 Partial Content) so `<video>`/`<audio>` can seek + partial-load; aborts the read if the client disconnects |
35
+ | PUT | `/api/files` | Save file (body: `{path, content, projectId}`) |
36
+ | POST | `/api/files/new` | Create empty file |
37
+ | POST | `/api/files/mkdir` | Create directory |
38
+ | POST | `/api/files/rename` | Rename / move |
39
+ | POST | `/api/files/upload` | Multipart upload |
40
+ | DELETE | `/api/files?path=&projectId=` | Delete file or directory (recursive) |
41
+
42
+ All file operations validate the path stays inside the project root (prevents path traversal). One exception: `GET /api/files/download` also accepts absolute paths under `/tmp/` so that agent-produced working files (e.g. Playwright screenshots) can be inline-previewed from chat media chips.
43
+
44
+ ### Filesystem browse (not project-scoped — for the workspace picker)
45
+
46
+ | Method | Path | Purpose |
47
+ |---|---|---|
48
+ | GET | `/api/fs/home` | Returns server `homedir()` — fallback when the frontend has no `?folder=` URL param. Returns `{ home }`. |
49
+ | GET | `/api/fs/exists?path=/abs` | Validates an absolute path exists and whether it's a directory. Returns `{exists, isDirectory?}`. |
50
+ | GET | `/api/fs/browse?path=/abs` | Lists immediate directory children (hidden ones dropped). Returns `{path, parent, entries: [{name, path}]}`. |
51
+ | POST | `/api/fs/workspace/resolve` | Resolve `{path}` to an absolute path and run `ensureWorkspaceHalo()`. Returns `{id, path}`. Used by the workspace-picker on switch. |
52
+
53
+ Absolute paths only. Purpose: Explorer's workspace picker and switching validation. Reading/writing files still goes through `/api/files/*` and remains project-sandboxed.
54
+
55
+ ## Session Logs (unified)
56
+
57
+ File: `packages/server/src/routes/sessions.ts`
58
+
59
+ Unified session log API — list + read session files across all agents.
60
+
61
+ | Method | Path | Purpose |
62
+ |---|---|---|
63
+ | GET | `/api/sessions/logs?projectId=` | List session metadata, keyset-paginated. Default returns each top-level row + all descendants (sidebar tree); `rootOnly=1` returns roots only (chat-header dropdown) |
64
+ | GET | `/api/sessions/logs/:id?projectId=` | Full session log (scans across agent dirs) |
65
+ | DELETE | `/api/sessions/logs/:id?projectId=` | Delete the session log |
66
+ | PATCH | `/api/sessions/logs/:id?projectId=` | Rename a session (admin-only) — updates the log file's title. Accepts any session id (root or sub-agent); the sidebar exposes the rename affordance on every row |
67
+
68
+ The list endpoint returns flat metadata (id / agentId / agentName / title / timestamps / messageCount / parentSessionId / stoppedAt / contextTokens / totalOutputTokens). The frontend builds the tree from `parentSessionId`.
69
+
70
+ The get endpoint returns the full session file. If only `rawMessages` is present (no event-log `messages`), `convertRawMessages()` transforms it into display format on the fly.
71
+
72
+ ### Legacy DB sessions
73
+
74
+ | Method | Path | Purpose |
75
+ |---|---|---|
76
+ | GET | `/api/sessions?projectId=` | List sessions (DB, legacy) |
77
+ | GET | `/api/sessions/:id` | Full session (DB, legacy) |
78
+ | DELETE | `/api/sessions/:id` | Delete session (DB, legacy) |
79
+
80
+ ## Weixin Channel
81
+
82
+ File: `packages/server/src/routes/weixin.ts`
83
+
84
+ | Method | Path | Purpose |
85
+ |---|---|---|
86
+ | POST | `/api/weixin/login/start` | Start the QR login flow, returns `{qrcodeUrl, sessionKey}` |
87
+ | POST | `/api/weixin/login/wait` | Poll QR status. body: `{sessionKey, workspacePath, label?, language?}`; on success the account is inserted and long-polling starts |
88
+ | GET | `/api/weixin/accounts` | List every bot account |
89
+ | PATCH | `/api/weixin/accounts/:id` | Change label / workspacePath / enabled / accessLevel / language |
90
+ | DELETE | `/api/weixin/accounts/:id` | Stop long-polling and delete the DB row |
91
+
92
+ See [design/wechat.md](../design/wechat.md).
93
+
94
+ ## Telegram Channel
95
+
96
+ File: `packages/server/src/routes/telegram.ts`. Admin cookie auth. Mounted under `/api/`.
97
+
98
+ | Method | Path | Purpose |
99
+ |---|---|---|
100
+ | GET | `/api/telegram/accounts` | List bot accounts. Each row carries `accountId`, `botUsername`, `workspacePath`, `workspaceMissing`, `label`, `enabled`, `accessLevel`, `allowedUsers`, `language`, timestamps |
101
+ | POST | `/api/telegram/accounts` | Create / upsert. Body: `{botToken, workspacePath, label?, accessLevel?, allowedUsers?, language?}`. Validates the token via `getMe`; `accountId = botUsername.toLowerCase()`. (Re)starts long-poll. Returns `{accountId, botUsername, workspacePath}` |
102
+ | PATCH | `/api/telegram/accounts/:id` | Update `label` / `workspacePath` / `enabled` / `accessLevel` / `allowedUsers` / `language`. Stops then restarts the bot if still enabled |
103
+ | DELETE | `/api/telegram/accounts/:id` | Stop long-poll and delete the row |
104
+
105
+ `workspacePath` must be absolute and exist; `ensureWorkspaceHalo()` runs on POST/PATCH.
106
+
107
+ ## Slack Channel
108
+
109
+ File: `packages/server/src/routes/slack.ts`. Admin cookie auth. Inbound events use Socket Mode (no webhook).
110
+
111
+ | Method | Path | Purpose |
112
+ |---|---|---|
113
+ | GET | `/api/slack/accounts` | List accounts. Row: `accountId`, `botUserId`, `teamId`, `workspacePath`, `workspaceMissing`, `label`, `enabled`, `accessLevel`, `language`, timestamps |
114
+ | POST | `/api/slack/accounts` | Create / upsert. Body: `{botToken, appToken, workspacePath, label?, accessLevel?, language?}`. `appToken` must start with `xapp-`. Resolves identity via `auth.test`; `accountId = team_id.toLowerCase()`. Returns `{accountId, botUserId, teamId}` |
115
+ | PATCH | `/api/slack/accounts/:id` | Update `label` / `workspacePath` / `enabled` / `accessLevel` / `language` / `appToken` |
116
+ | DELETE | `/api/slack/accounts/:id` | Stop Socket Mode connection and delete the row |
117
+ | GET | `/api/slack/accounts/:id/search?q=` | Search users / channels visible to this bot's token (used by the cron form). Returns `{hits}` (max 20) |
118
+
119
+ ## Feishu Channel
120
+
121
+ File: `packages/server/src/routes/feishu.ts`. Admin cookie auth. Inbound uses long-connect wss (no webhook).
122
+
123
+ | Method | Path | Purpose |
124
+ |---|---|---|
125
+ | GET | `/api/feishu/accounts` | List accounts. Row: `accountId`, `appId`, `botOpenId`, `hasEncryptKey`, `workspacePath`, `workspaceMissing`, `label`, `enabled`, `accessLevel`, `language`, timestamps |
126
+ | POST | `/api/feishu/accounts` | Create / upsert. Body: `{appId, appSecret, verificationToken?, encryptKey?, workspacePath, label?, accessLevel?, language?}`. Resolves `botOpenId` via `/bot/v3/info`; `accountId = appId.toLowerCase()`. Returns `{accountId, appId, botOpenId}` |
127
+ | PATCH | `/api/feishu/accounts/:id` | Update `label` / `workspacePath` / `enabled` / `accessLevel` / `language` / `verificationToken` / `encryptKey`. `botOpenId` is **not** patchable — re-POST to re-resolve |
128
+ | DELETE | `/api/feishu/accounts/:id` | Stop the wss stream and delete the row |
129
+ | GET | `/api/feishu/accounts/:id/search?q=` | Search chats the bot is a member of (used by the cron form). Returns `{hits}` (max 20) |
130
+
131
+ ## Cron
132
+
133
+ File: `packages/server/src/routes/cron.ts`. Admin cookie auth. REST CRUD over `cron_jobs` + read-only `cron_runs`. Mutations call `reloadAll()` / `scheduleJob()` / `unscheduleJob()` so in-memory croner stays in sync with the db. Each mutation also broadcasts `cron:job_changed`.
134
+
135
+ ### Jobs
136
+
137
+ | Method | Path | Purpose |
138
+ |---|---|---|
139
+ | GET | `/api/cron/jobs?limit=&before=` | Cursor-paginated newest-first by `createdAt`. Default `limit` 20, max 100; `before` is a `createdAt` cursor. Returns `{jobs, hasMore, nextCursor}`. Each job has `targets` decoded and a live `nextRunAt` (computed from schedule/runAt) |
140
+ | POST | `/api/cron/jobs` | Create a job. Body: `{label?, workspacePath, agentId, userPrompt, schedule, runAt?, timezone?, targets?, enabled?}`. Exactly one of `schedule` (5-field cron) or `runAt` (epoch ms, future) is required; mutually exclusive. Returns `{ok, id}` |
141
+ | PUT | `/api/cron/jobs/:id` | Partial update over the same fields. Re-validates `schedule` / `runAt`; flips schedule/unschedule on `enabled`. Returns `{ok}` |
142
+ | DELETE | `/api/cron/jobs/:id` | Unschedule, delete every `cron_runs` row for the job, then delete the job. Returns `{ok}` |
143
+ | POST | `/api/cron/jobs/:id/run-now` | Fire the job immediately (fire-and-forget). Returns `{ok}` |
144
+ | POST | `/api/cron/reload` | Re-read every job from db and rebuild schedules. Returns `{ok}` |
145
+
146
+ `targets` is `Array<{channelType, accountId, chatId?}>`. New job ids look like `cron-<base36-ts>-<rand>`.
147
+
148
+ ### Runs
149
+
150
+ | Method | Path | Purpose |
151
+ |---|---|---|
152
+ | GET | `/api/cron/jobs/:id/runs?limit=&before=` | Cursor-paginated newest-first by `cron_runs.id` (ISO-prefixed; sorts as text). Default `limit` 20, max 100. Returns `{runs, hasMore, nextCursor}`; each row has `dispatchResults` JSON-decoded |
153
+ | GET | `/api/cron/runs/:runId/log` | Read the raw log file for one run. Returns `{log}` (string or null when the file is missing / past retention) |
154
+
155
+ ### Reference data (for the create form)
156
+
157
+ | Method | Path | Purpose |
158
+ |---|---|---|
159
+ | GET | `/api/cron/channel-targets` | List channel accounts available as targets, aggregated from the cron-dispatcher registry. Returns `{targets}` |
160
+ | GET | `/api/cron/meta` | Server-side metadata. Returns `{hostTimezone}` — the IANA tz this server resolves an unset `cron_jobs.timezone` to |
161
+
162
+ ## Source Control (Git)
163
+
164
+ File: `packages/server/src/routes/git.ts`. Admin cookie auth. Backs the Source Control panel + Explorer git decorations. All project-scoped reads take `projectId=<absPath>`; writes take it in the JSON body. A write (stage/unstage/commit/push/pull/init/remote) broadcasts `file:changed` (path `.git`) so the panel, graph and explorer decorations auto-refresh without polling — the file watcher ignores `.git`, so the route re-broadcasts it itself. Path params on diff are traversal-checked against the project root.
165
+
166
+ ### Repo state (read)
167
+
168
+ | Method | Path | Purpose |
169
+ |---|---|---|
170
+ | GET | `/api/git/status?projectId=` | Structured working-tree status. A folder that isn't a git work-tree **root** (no repo, or merely nested inside an ancestor's repo) returns `{isRepo: false}` (200, not 500) so the panel shows its initialize/empty state. Otherwise `{isRepo: true, branch, tracking, ahead, behind, files: [{path, index, workingDir, from?}]}` — `index`/`workingDir` are the two porcelain status chars (X staged / Y working) |
171
+ | GET | `/api/git/ignored?projectId=` | `.gitignore`'d paths for graying out in the explorer; ignored directories are collapsed to a single entry (e.g. `node_modules`) rather than expanded. Returns `{ignored: string[]}`. Runs with `core.quotepath=false` so non-ASCII (e.g. Chinese) paths come back literal, not octal-escaped — the frontend prefix-matches them against tree node paths |
172
+ | GET | `/api/git/diff?projectId=&path=[&staged=0\|1][&from=][&commit=]` | Two sides for the Monaco diff editor. With `commit`, shows that commit's own change (parent vs commit); without it, the working-tree (or `staged=1`) diff. `from` carries the old path on a rename. Returns `{path, original, modified, ...}`. 400 if `path` missing, 403 on traversal |
173
+ | GET | `/api/git/log?projectId=[&limit=50]` | Recent commits for the graph. `limit` default 50, capped at 2000. Returns `{commits: [{hash, shortHash, message, author, date, refs, pushed}]}` — `pushed: boolean` marks whether the commit is on a remote (graph dims pushed nodes gray, paints local-only ones blue). No-upstream branches compute it via `rev-list HEAD --not --remotes`, not `rev-list HEAD` |
174
+ | GET | `/api/git/commit-files?projectId=&hash=` | Files changed by one commit. Returns `{files: [{path, status, from?}]}` (`status` = M/A/D/R/C; `from` set on rename/copy). 400 if `hash` missing |
175
+ | GET | `/api/git/remotes?projectId=` | Configured remotes. Returns `{remotes: [{name, url}]}` (`[]` when none) |
176
+
177
+ ### Mutations (write — each broadcasts `file:changed`)
178
+
179
+ | Method | Path | Purpose |
180
+ |---|---|---|
181
+ | POST | `/api/git/stage` | Body `{projectId, paths: string[]}`. `git add`. 400 if `paths` empty |
182
+ | POST | `/api/git/unstage` | Body `{projectId, paths: string[]}`. 400 if `paths` empty |
183
+ | POST | `/api/git/commit` | Body `{projectId, message}`. Returns `{ok, hash}`. 400 if `message` blank |
184
+ | POST | `/api/git/push` | Body `{projectId}`. First push of an untracked branch sets upstream (`-u origin <branch>`). Git errors (missing creds, SSH passphrase, rejected) are surfaced verbatim for the panel to display |
185
+ | POST | `/api/git/pull` | Body `{projectId}` |
186
+ | POST | `/api/git/init` | Body `{projectId}`. `git init` |
187
+ | POST | `/api/git/remote` | Body `{projectId, name?, url}`. Adds a remote (`name` defaults to `origin`); drives the "no remote configured" publish flow. 400 if `url` missing |
188
+
189
+ ### Credentials & SSH
190
+
191
+ | Method | Path | Purpose |
192
+ |---|---|---|
193
+ | GET | `/api/git/credentials` | List stored HTTPS credentials — `{credentials: [{host, username}]}`. **Never returns the token.** Source of truth is `~/.git-credentials` (one line per host) |
194
+ | POST | `/api/git/credentials` | Body `{host, username, token}`. Upserts the `https://user:token@host` line in `~/.git-credentials` (0600). 400 if any field missing |
195
+ | DELETE | `/api/git/credentials/:host` | Remove the credential line(s) for one host (idempotent). `:host` is sent `encodeURIComponent`'d; Hono decodes it |
196
+ | GET | `/api/git/ssh/keys` | Private keys found in `~/.ssh` + an `encrypted` flag each. Never returns key contents. Returns `{keys: [...]}` |
197
+ | GET | `/api/git/ssh/agent` | ssh-agent reachability + loaded key count |
198
+ | POST | `/api/git/ssh/unlock` | Body `{keyPath, passphrase}`. Loads a passphrase-protected key into the shared ssh-agent. `keyPath` must resolve to a file **directly inside `~/.ssh`** (rejects traversal / arbitrary paths, 400). Passphrase is fed to `ssh-add` via a throwaway `SSH_ASKPASS` helper (never argv / disk / log); the helper is answer-once so a wrong passphrase returns immediately. Returns `{ok}` on success, `{ok:false, error}` (normalized — never ssh-add's raw stderr) on failure |
199
+ | GET | `/api/git/remote/protocol?projectId=` | Current `origin` url + detected protocol. Returns `{url, protocol}` |
200
+ | POST | `/api/git/remote/protocol` | Body `{projectId, to: 'https'\|'ssh'}`. Rewrites `origin` between HTTPS and scp-style SSH. Returns `{ok, url}` |
201
+
202
+ See [requirements/source-control.md](../requirements/source-control.md).
203
+
204
+ ## Web Channel
205
+
206
+ File: `packages/server/src/routes/web.ts`
207
+
208
+ ### Admin endpoints (cookie auth)
209
+
210
+ | Method | Path | Purpose |
211
+ |---|---|---|
212
+ | GET | `/api/web/accounts` | List all web accounts |
213
+ | POST | `/api/web/accounts` | Create account (body: `{workspacePath, label?, accessLevel?, language?}`) → `{accountId, token, workspacePath}` |
214
+ | PATCH | `/api/web/accounts/:id` | Update account fields (label, workspacePath, enabled, accessLevel, language) |
215
+ | DELETE | `/api/web/accounts/:id` | Delete account |
216
+
217
+ ### Public endpoints (token auth via `x-token` header or `?token=` query)
218
+
219
+ | Method | Path | Purpose |
220
+ |---|---|---|
221
+ | POST | `/api/web/chat` | Send message, receive SSE stream. Body: `{message, images?, workspace?, sessionId?, agentId?}` (overrides also accepted as `?workspace=`/`?sessionId=` query or `x-workspace`/`x-session-id` headers; `workspace` only honored when token has `accessLevel: full`) |
222
+ | POST | `/api/web/stop` | Stop running task → `{stopped: boolean}` |
223
+ | GET | `/api/web/history` | Active session history → `{sessionId, messages[], running}` |
224
+ | GET | `/api/web/subscribe` | Reconnect SSE to running session |
225
+ | GET | `/api/web/file?path=` | Inline-serve a workspace-relative file (image / video / pdf etc.). Path-traversal-checked against the token's bound workspace. |
226
+
227
+ See [design/web.md](../design/web.md).
228
+
229
+ ## Show (world snapshot)
230
+
231
+ File: `packages/server/src/routes/show.ts`. Token auth (same `x-token` as the
232
+ Web channel; shares its brute-force lockout bucket). Read-only, cross-workspace
233
+ snapshot powering the `halo-city` pixel visualizer — one call returns the whole
234
+ runtime so the frontend can render rooms (workspaces) + characters (sessions).
235
+
236
+ | Method | Path | Purpose |
237
+ |---|---|---|
238
+ | GET | `/api/show/state` | Full-access token → every known workspace; otherwise the account's own. Returns `{ serverTime, uptime, accessLevel, skills[], workspaces[] }` |
239
+ | GET | `/api/show/session?ws=&id=` | Inspector-panel detail for a single session. Trimmed message log (last 40, content/tool I/O capped) plus `contextTokens` / `outputTokens` / `maxContextTokens` / `isRunning`. Non-full tokens may only read their own workspace. |
240
+
241
+ Each `workspace` = `{ path, key, label, counts{running,idle,stopped}, totalSessions, skills[], sessions[] }`; each `session` = `{ id, parentId, depth, agentName, description, status, lastTool, activeSkill, contextTokens, outputTokens, updatedAt }`. `lastTool` / `activeSkill` come from the live in-memory UI log (empty when the session isn't loaded). Sessions per room are capped (`totalSessions` reports the true total). Frontend: `halo-city/` at repo root.
242
+
243
+ ## Agent Configs
244
+
245
+ File: `packages/server/src/routes/agent-configs.ts`
246
+
247
+ | Method | Path | Purpose |
248
+ |---|---|---|
249
+ | GET | `/api/agent-configs?projectId=` | List agents (global + workspace merged) |
250
+ | GET | `/api/agent-configs/tools` | Available workspace tools |
251
+ | GET | `/api/agent-configs/models` | Available model providers and models |
252
+ | POST | `/api/agent-configs` | Create a new agent |
253
+ | GET | `/api/agent-configs/:id/yaml?scope=&projectId=` | Read agent.yaml |
254
+ | PUT | `/api/agent-configs/:id/yaml?scope=&projectId=` | Write agent.yaml |
255
+ | DELETE | `/api/agent-configs/:id?scope=&projectId=` | Delete agent (global has "last one" protection) |
256
+ | PATCH | `/api/agent-configs/:id/toggle?scope=&projectId=` | Toggle disabled in workspace DB. `projectId` required. Returns `{ ok, disabled }`. |
257
+ | GET | `/api/agent-configs/:id/md/:fileType` | Read an MD file (AGENT.md / INSTRUCTIONS.md / INDEX.md) |
258
+ | PUT | `/api/agent-configs/:id/md/:fileType` | Write an MD file (AGENT.md / INSTRUCTIONS.md) |
259
+ | GET | `/api/agent-configs/:id/md-all` | Read every MD at once |
260
+ | GET | `/api/agent-configs/:id/sessions?projectId=` | List the agent's session files |
261
+ | GET | `/api/agent-configs/:id/sessions/:sessionId` | Read a session |
262
+ | POST | `/api/agent-configs/:id/sessions` | Save / update a session |
263
+ | DELETE | `/api/agent-configs/:id/sessions/:sessionId` | Delete a session |
264
+ | DELETE | `/api/agent-configs/:id/sessions?all=1` | Delete every session for the agent |
265
+
266
+ ## Skills
267
+
268
+ File: `packages/server/src/routes/skills.ts`
269
+
270
+ | Method | Path | Purpose |
271
+ |---|---|---|
272
+ | GET | `/api/skills?projectId=` | List skills (global + workspace unmerged — two distinct entries). Global entries shadowed by a same-id workspace skill are marked `overridden: true`. Runtime override happens in `agent-loader.ts`, not in this route. |
273
+ | POST | `/api/skills` | Create a skill directory + SKILL.md |
274
+ | DELETE | `/api/skills/:id?scope=&projectId=` | Delete skill directory + settings entry |
275
+ | PATCH | `/api/skills/:id/toggle?scope=&projectId=` | Toggle disabled in workspace DB. `projectId` required. Returns `{ ok, disabled }`. |
276
+
277
+ ## Commands
278
+
279
+ File: `packages/server/src/routes/commands.ts`
280
+
281
+ | Method | Path | Purpose |
282
+ |---|---|---|
283
+ | GET | `/api/commands?projectId=[&sessionId=][&agentId=]` | List registered commands (built-in + skill; excludes hidden) |
284
+
285
+ Returns `{ commands: CommandDescriptor[] }`. Skill commands are only included with session/agent context: `sessionId+projectId` (or pre-session `agentId+projectId`) filters them by the agent's skill whitelist + access level. Without that context the response is **builtins only** — listing skills unfiltered leaked full-access commands into readonly palettes. See [design/command.md](../design/command.md).
286
+
287
+ ## Settings
288
+
289
+ File: `packages/server/src/routes/settings.ts`
290
+
291
+ | Method | Path | Purpose |
292
+ |---|---|---|
293
+ | GET | `/api/settings/schema?projectId=` | Schema (provider/skill declarations) + resolved values + orphans — drives the new Settings page |
294
+ | PUT | `/api/settings` | Replace a scope's full settings |
295
+ | PATCH | `/api/settings` | Update a single key |
296
+ | DELETE | `/api/settings` | Delete a key |
297
+
298
+ ## Metrics
299
+
300
+ File: `packages/server/src/routes/metrics.ts`.
301
+
302
+ | Method | Path | Purpose |
303
+ |---|---|---|
304
+ | GET | `/api/metrics` | Prometheus text exposition (gauges) of deployment-wide runtime: `halo_uptime_seconds`, `halo_workspaces`, `halo_sessions{status=}`, `halo_sessions_total`, `halo_context_tokens`, `halo_output_tokens` |
305
+
306
+ In `PUBLIC_PATHS` (no admin cookie). Auth is a web-channel `x-token` (header or `?token=`) with a **global** scope — `full` or `observer` (the read-only role minted for dashboards/scrapes); a workspace-scoped token gets 403. Session counts come from each workspace's `listSessions`; token sums read the in-memory UIState of sessions this process actively drives.
307
+
308
+ ## Self-Evolution
309
+
310
+ File: `packages/server/src/routes/evolution.ts`. Surfaces the global `evolution_runs` / `evolution_applies` queues to the admin UI's Evolution tab; see [plans/self-evolution.md](../plans/self-evolution.md) for the full design.
311
+
312
+ | Method | Path | Purpose |
313
+ |---|---|---|
314
+ | GET | `/api/evolution/runs` | List all evolution runs across workspaces. Carries the latest `apply_id`/`apply_status` per run. (Score isn't surfaced in the list — it's read from `score.json` on the detail fetch only.) |
315
+ | GET | `/api/evolution/runs/:id` | Detail: db row + `patch.md` + `score.json` + `system-suggestions.md` (optional, evo writes when it has platform-level feedback) + `.skip.md` (when `status='skipped'`) + a snapshot summary (first user message + first assistant reply + message count). |
316
+ | POST | `/api/evolution/runs/:id/approve` `{reviewerHint?}` | Move run from `awaiting_review` → `approved`, insert a pending `evolution_applies` row that the ticker will pick up. |
317
+ | POST | `/api/evolution/runs/:id/reject` | Move run from `awaiting_review` → `rejected`. |
318
+ | POST | `/api/evolution/runs/:id/retry` `{hint}` | Reset a finished run back to `pending` with the supplied (required) hint so the wrapper picks it up again. Rejected with 409 when the run is already `running` / `pending`. |
319
+ | POST | `/api/evolution/runs/:id/hint` `{hint}` | Append text to `user_hint` — memo only, doesn't change status. |
320
+ | DELETE | `/api/evolution/runs/:id` | Delete a finished run: its on-disk artifacts (run dir + archive zip) **and** the DB row. Rejected with 409 for in-flight states (`pending` / `running` / `approved`) so a live wrapper / queued apply isn't pulled out from under. Broadcasts `evolution:run_changed` with `kind:'deleted'`. |
321
+ | GET | `/api/evolution/applies` | List apply rows (used for status badges). |
322
+
323
+ ---
324
+
325
+ ## Request / Response schemas
326
+
327
+ Bodies and response shapes for the endpoints agents most commonly help users call. Source lines cited — verify against the route file if in doubt.
328
+
329
+ ### POST `/api/auth/login`
330
+
331
+ Source: [packages/server/src/middleware/auth.ts:123-134](../../../packages/server/src/middleware/auth.ts#L123-L134)
332
+
333
+ ```json
334
+ // Request
335
+ { "password": "..." }
336
+
337
+ // 200 OK — sets halo_token cookie
338
+ { "ok": true }
339
+
340
+ // 401 Invalid password
341
+ { "error": "Invalid password" }
342
+ ```
343
+
344
+ ### GET `/api/auth/check`
345
+
346
+ ```json
347
+ // 200 authenticated
348
+ { "authenticated": true }
349
+
350
+ // 401 not authenticated
351
+ { "authenticated": false }
352
+ ```
353
+
354
+ ### GET `/api/files/tree?projectId=<absPath>&path=<relPath>`
355
+
356
+ Source: [packages/server/src/routes/files.ts](../../../packages/server/src/routes/files.ts). One directory level, lazy.
357
+
358
+ ```json
359
+ // 200
360
+ {
361
+ "tree": [
362
+ { "name": "packages", "path": "packages", "type": "directory", "hasChildren": true },
363
+ { "name": "README.md", "path": "README.md", "type": "file" }
364
+ ]
365
+ }
366
+ ```
367
+
368
+ Skipped entries: dotfiles (except `.halo`), `node_modules`, `__pycache__`.
369
+
370
+ ### GET `/api/files?path=<rel>&projectId=<abs>`
371
+
372
+ Reads file content. Max 10 MB.
373
+
374
+ ```json
375
+ // 200
376
+ { "content": "..." }
377
+
378
+ // 404 if path doesn't exist
379
+ { "error": "File not found" }
380
+ ```
381
+
382
+ ### POST `/api/agent-configs`
383
+
384
+ Source: [packages/server/src/routes/agent-configs.ts:199-255](../../../packages/server/src/routes/agent-configs.ts#L199-L255)
385
+
386
+ ```json
387
+ // Request
388
+ {
389
+ "name": "Coder",
390
+ "description": "Full-stack coder",
391
+ "scope": "workspace", // "global" | "workspace"
392
+ "projectId": "/abs/path/to/ws" // required when scope=workspace
393
+ }
394
+
395
+ // 201 Created — agent + possible cross-scope conflict flag
396
+ {
397
+ "agent": {
398
+ "id": "coder",
399
+ "name": "Coder",
400
+ "description": "Full-stack coder",
401
+ "model": "global.anthropic.claude-sonnet-4-6",
402
+ "path": "/abs/path/to/ws/.halo/agents/coder",
403
+ "scope": "workspace",
404
+ "priority": 0
405
+ },
406
+ "conflictScope": null // or "global" / "workspace" to warn of override
407
+ }
408
+
409
+ // 409 already exists
410
+ { "error": "Agent already exists" }
411
+ ```
412
+
413
+ Files created:
414
+ - `<agentDir>/agent.yaml` — scaffold with `SCAFFOLD_MODEL`, empty tools/skills
415
+ - `<agentDir>/AGENT.md` — `# <name>\n\n<description>\n`
416
+
417
+ ### PUT `/api/agent-configs/:id/yaml?scope=&projectId=`
418
+
419
+ ```json
420
+ // Request
421
+ { "yaml": "name: Coder\nmodel:\n provider: aws-bedrock-claude-invoke\n...", "scope": "workspace", "projectId": "/abs/ws" }
422
+
423
+ // 200 OK — returns re-parsed metadata
424
+ { "agent": { "id": "coder", "name": "Coder", "description": "...", "model": "...", "path": "...", "scope": "workspace" } }
425
+
426
+ // 400 invalid YAML
427
+ { "error": "Invalid YAML: <parser message>" }
428
+
429
+ // 404
430
+ { "error": "Agent not found" }
431
+ ```
432
+
433
+ ### POST `/api/skills`
434
+
435
+ Source: [packages/server/src/routes/skills.ts](../../../packages/server/src/routes/skills.ts)
436
+
437
+ ```json
438
+ // Request
439
+ {
440
+ "name": "Code Review",
441
+ "description": "Review code for correctness and style",
442
+ "scope": "workspace",
443
+ "projectId": "/abs/ws",
444
+ "command": "/review" // optional — registers as slash command
445
+ }
446
+
447
+ // 201
448
+ {
449
+ "skill": {
450
+ "id": "code-review",
451
+ "name": "Code Review",
452
+ "description": "Review code for correctness and style",
453
+ "path": "/abs/ws/.halo/skills/code-review/SKILL.md",
454
+ "scope": "workspace"
455
+ }
456
+ }
457
+ ```
458
+
459
+ ### GET `/api/sessions/logs?projectId=<abs>&rootOnly=0|1&includeArchived=0|1&cursor=<ms>&limit=<n>`
460
+
461
+ Source: [packages/server/src/routes/sessions.ts:189-258](../../../packages/server/src/routes/sessions.ts#L189-L258)
462
+
463
+ Keyset-paginated over `updatedAt` (descending). `limit` defaults to 50; the
464
+ response's `nextCursor` (epoch ms of the last row's `updatedAt`, or `null` on
465
+ the last page) is passed back as `cursor` to fetch the next page.
466
+
467
+ Two shapes, selected by `rootOnly`:
468
+
469
+ - **Default (`rootOnly` omitted)** — returns each top-level row (`parent_id IS
470
+ NULL`) *plus all of its descendants*, flattened. The admin **Sessions
471
+ sidebar** consumes this and rebuilds the tree from `parentSessionId` in one
472
+ shot, no per-expand round-trips. `limit` bounds the top-level rows; their
473
+ descendants are appended on top.
474
+ - **`rootOnly=1`** — returns root sessions only, no descendants. The
475
+ **chat-header dropdown** uses this for a flat "recent sessions" list. `limit`
476
+ bounds the roots directly, so the page count is exact.
477
+
478
+ ```json
479
+ // 200 — rootOnly=1: roots only. Default shape is the same rows + descendants.
480
+ {
481
+ "sessions": [
482
+ {
483
+ "id": "sid_abc",
484
+ "agentId": "default",
485
+ "agentName": "Default",
486
+ "title": "First user message...",
487
+ "source": "explorer",
488
+ "createdAt": "2026-04-30T08:00:00Z",
489
+ "updatedAt": "2026-04-30T08:05:00Z",
490
+ "messageCount": 12,
491
+ "parentSessionId": null,
492
+ "contextTokens": 5975,
493
+ "totalOutputTokens": 6058,
494
+ "stoppedAt": null,
495
+ "archivedAt": null
496
+ }
497
+ ],
498
+ "nextCursor": 1779890684254
499
+ }
500
+ ```
501
+
502
+ Archived sessions are excluded by default; pass `?includeArchived=1` to include them.
503
+
504
+ ### GET `/api/sessions/logs/:id?projectId=<abs>`
505
+
506
+ Returns the full session file. If only `rawMessages` is present (sub-agent sessions from SessionManager), `convertRawMessages()` transforms them into the display `messages` array on the fly.
507
+
508
+ ```json
509
+ // 200 — same shape as the on-disk JSON
510
+ {
511
+ "version": 1,
512
+ "id": "sid_abc",
513
+ "agentId": "default",
514
+ "agentName": "Default",
515
+ "title": "...",
516
+ "source": "explorer",
517
+ "createdAt": "...", "updatedAt": "...",
518
+ "messageCount": 12,
519
+ "contextTokens": 5975,
520
+ "totalOutputTokens": 6058,
521
+ "messages": [ /* SessionMessage[] — see design/storage.md */ ],
522
+ "rawMessages": [ /* optional */ ]
523
+ }
524
+ ```
525
+
526
+ ### GET `/api/settings/schema?projectId=<abs>`
527
+
528
+ Resolves declared schema (from `models/<id>.yaml` `secrets:` and `skills/<id>/config.yaml`) against current settings, returning per-field source/state and a list of orphan keys. Drives the Settings page.
529
+
530
+ ```json
531
+ // 200
532
+ {
533
+ "scope": "global", // or "workspace" when projectId set
534
+ "sections": [
535
+ {
536
+ "namespaceId": "aws-bedrock-claude-invoke", // 'general' | provider id | skill id
537
+ "source": "provider", // 'general' | 'provider' | 'skill'
538
+ "displayName": "AWS Bedrock Claude (Invoke API)",
539
+ "displayName_zh": "AWS Bedrock Claude(Invoke API)",
540
+ "description": "...",
541
+ "description_zh": "...",
542
+ "fields": [
543
+ {
544
+ "key": "access_key_id",
545
+ "kind": "secret", // 'param' | 'secret'
546
+ "description": "...",
547
+ "description_zh": "...",
548
+ "default": null, // schema-default placeholder
549
+ "secret": true, // UI masks input + value
550
+ "value": "AK****ST", // already masked when secret:true; null = unset
551
+ "hasValue": true, // any layer has a non-empty value
552
+ "source": "global", // 'workspace' | 'global' | 'unset'
553
+ "inheritedFromGlobal": false // true when scope=workspace + value came from global
554
+ }
555
+ ]
556
+ }
557
+ ],
558
+ "orphans": [
559
+ { "namespaceId": "tavily-old", "kind": "param", "key": "api_key" }
560
+ ]
561
+ }
562
+ ```
563
+
564
+ `<<ENV_NAME>>` references in stored values are returned **as literals** (the browser never sees the resolved env var). Secret values are masked (`AK****ST`); env-var refs pass through unmasked since they're not real secrets.
565
+
566
+ ### PATCH `/api/settings`
567
+
568
+ ```json
569
+ // Request — dotted-path key, single value (no `.value` suffix; values are flat now)
570
+ {
571
+ "scope": "workspace", // 'global' | 'workspace'
572
+ "projectId": "/abs/ws", // required when scope=workspace
573
+ "key": "tavily-web-search.params.api_key",
574
+ "value": "<<TAVILY_API_KEY>>"
575
+ }
576
+
577
+ // 200
578
+ { "ok": true }
579
+ ```
580
+
581
+ PUT replaces the full scope; DELETE takes `{scope, projectId, key}` and removes the key. The Settings page uses DELETE for both Reset (current scope removed → falls back to lower scope / unset) and orphan Remove.
582
+
583
+ ### POST `/api/weixin/login/start`
584
+
585
+ Source: [packages/server/src/routes/weixin.ts:42-46](../../../packages/server/src/routes/weixin.ts#L42-L46)
586
+
587
+ ```json
588
+ // Request
589
+ { "sessionKey": "optional-resume-key", "force": false }
590
+
591
+ // 200
592
+ { "qrcodeUrl": "https://...", "sessionKey": "abc123" }
593
+ ```
594
+
595
+ ### POST `/api/weixin/login/wait`
596
+
597
+ ```json
598
+ // Request
599
+ {
600
+ "sessionKey": "abc123",
601
+ "workspacePath": "/abs/path", // required, must be absolute
602
+ "label": "My Bot", // optional
603
+ "accessLevel": "readonly", // "full" | "readonly", default readonly
604
+ "language": "en", // "en" | "zh", default "en" — controls system message language
605
+ "timeoutMs": 120000 // optional, how long to wait for scan
606
+ }
607
+
608
+ // 200 waiting for scan (retry polling)
609
+ { "connected": false, "message": "..." }
610
+
611
+ // 200 connected — account inserted + long-poll started
612
+ { "connected": true, "accountId": "abc-im-bot" }
613
+
614
+ // 400 bad input
615
+ { "error": "workspacePath required" }
616
+ ```
617
+
618
+ ---
619
+
620
+ ## WebSocket message envelopes
621
+
622
+ Full WS protocol in [design/ws.md](../design/ws.md). The four high-traffic client messages:
623
+
624
+ ### `chat` (C→S)
625
+
626
+ ```json
627
+ {
628
+ "type": "chat",
629
+ "sessionId": "sid_abc",
630
+ "projectId": "/abs/ws",
631
+ "message": "hello",
632
+ "agentId": "default", // optional override
633
+ "images": [ { "data": "<base64>", "mimeType": "image/png" } ] // optional
634
+ }
635
+ ```
636
+
637
+ Server behaviour ([handler.ts `handleChat`](../../../packages/server/src/ws/handler.ts#L263-L344)):
638
+ - Creates / reuses a session with the specified agent
639
+ - Persists pasted images to `<ws>/.halo/web/inbound/<date>/`
640
+ - If the model does not support image input (`capabilities.image: false`), images are filtered out and a text notice is appended instead of sending to the API
641
+ - Queues if busy/compacting; otherwise runs the agent turn
642
+ - Streams events back via `message`, `tool_call`, `usage`, etc.
643
+
644
+ ### `subscribe` (C→S)
645
+
646
+ Attach this connection to a session's event stream. Sent on initial connect and whenever the active session changes.
647
+
648
+ ```json
649
+ {
650
+ "type": "subscribe",
651
+ "sessionId": "sid_abc",
652
+ "projectId": "/abs/ws"
653
+ }
654
+ ```
655
+
656
+ ### `session:clear` (C→S)
657
+
658
+ Detach from the current session (for `/session new`). Server unsubscribes the event listener without deleting the session.
659
+
660
+ ```json
661
+ { "type": "session:clear", "sessionId": "sid_abc" }
662
+ ```
663
+
664
+ ### `session:delete` (C→S)
665
+
666
+ Archive (soft-delete) a session.
667
+
668
+ ```json
669
+ { "type": "session:delete", "sessionId": "sid_abc", "projectId": "/abs/ws" }
670
+ ```
671
+
672
+ Hard delete (JSON + SQLite rows, cascades to descendants) goes through the REST route `DELETE /api/sessions/logs/:id`, not this WS message.
673
+
674
+ ### Server-sent events (S→C, selected)
675
+
676
+ | Type | Fields | Purpose |
677
+ |---|---|---|
678
+ | `message` | `content`, `role`, `taskId?` | Text chunk from the agent |
679
+ | `tool_call` | `toolName`, `toolInput`, `taskId?` | Tool invocation card |
680
+ | `tool_result` | `toolName`, `toolOutput`, `durationMs`, `taskId?` | Tool result |
681
+ | `usage` | `usage`, `modelId`, `turnId`, `taskId?` | Token accounting per turn |
682
+ | `complete` | `stopReason`, `taskId?` | Turn finished |
683
+ | `error` | `error` | Error message |
684
+ | `chat:queued` | `reason`, `message` | Message queued (compact/busy) |
685
+
686
+ Events with `taskId` set belong to sub-agent turns (nested sessions); events without are the root agent's. Full list in [design/ws.md](../design/ws.md).