@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,82 @@
1
+ # Canvas — Requirements
2
+
3
+ Monaco-based multi-tab code editor + binary file previewer — surfaced in the UI as **Canvas**.
4
+
5
+ > Naming note: "Canvas" is the user-visible name (header label, this doc). Source code still uses `editor`
6
+ > (`packages/admin/src/features/editor/`, `useEditorStore`, `EditorPanel`) — those names are internal.
7
+
8
+ > Store scoping: `useEditorStore` is the **default singleton** powering the main Explorer Canvas.
9
+ > Nested `EditorPanel` instances that need isolated tabs/fileTree/selection (e.g. Skills editor)
10
+ > wrap their subtree in `<EditorStoreProvider>`; inside the provider, `useScopedEditorStore()`
11
+ > returns a fresh store instance instead of the singleton. Without a provider the hook falls
12
+ > back to the default, so regular call sites don't change. `EditorPanel` also accepts
13
+ > `showMaximize={false}` for nested contexts where viewport-fullscreen makes no sense.
14
+
15
+ ## Core behaviour
16
+
17
+ ### Multi-tab editing
18
+ - Click a file in Explorer (single click = select, double click = open tab)
19
+ - Each tab independently tracks content, original content, language, mtime
20
+ - Unsaved tabs show a small red dot
21
+ - Closing an unsaved tab prompts for confirmation
22
+
23
+ ### Save
24
+ - `Cmd+S` / `Ctrl+S` calls `PUT /api/files`
25
+ - Server returns the new `modifiedAt`; the tab updates its mtime
26
+
27
+ ### Auto-refresh (mtime-based)
28
+ When agents write files through tools, the canvas detects it:
29
+ 1. Periodically poll `GET /api/files/stat`
30
+ 2. If `diskMtime > tab.mtime`, pull fresh content
31
+ 3. Skip if the user has unsaved edits (don't overwrite)
32
+
33
+ ### Tab persistence
34
+ Open tabs and the active tab live in localStorage; survive refresh.
35
+
36
+ ### Diff view
37
+ - Tracked files → `GET /api/files/diff` for git diff
38
+ - Monaco left/right compare view
39
+
40
+ ### Binary previews
41
+ Non-text files go through `FilePreview`, which looks up a **plugin** in the preview registry by file extension. Built-in plugins:
42
+ - **PDF** — browser-native iframe
43
+ - **DOCX / DOC** — `mammoth` → HTML (parsed in a Web Worker so the UI stays responsive)
44
+ - **XLSX / XLS / CSV** — `xlsx` (SheetJS) → table (parsed in a Web Worker; CSV decoded as UTF-8 with BOM strip)
45
+ - **PPTX / PPT** — `pptx-preview` list mode. Fidelity is approximate — complex animations / SmartArt / embedded fonts may not render perfectly; users can download for the exact source. Flagged `heavy: true` so only the *active* pptx mounts (canvas-based rendering needs the main thread)
46
+ - **Images / video / audio** — native `<img>` / `<video>` / `<audio>`, supports HTTP Range for seek-without-full-download
47
+
48
+ All previews share a **standard header** (`PreviewShell`) with filename, Download, Open-as-Text, and an `extraToolbar` slot for plugin-specific buttons (e.g. DOCX Print, XLSX sheet tabs).
49
+
50
+ Fetched via `GET /api/files/download?inline=1` (supports Range, streams on the server).
51
+
52
+ Adding new file types is a plugin concern — see `dev/previews.md` for the extension guide.
53
+
54
+ ### Renderable text formats (Markdown / HTML)
55
+ Markdown and HTML open as text in Monaco *and* have a rendered view — Canvas defaults to the **rendered** view since the primary audience is an AI generating reports / pages for humans to read.
56
+ - Header shows an **Edit / Preview** toggle (same `textRenderMode` state for both formats)
57
+ - **Markdown** → `MarkdownPreview` (react-markdown + GFM; relative image `src` rewritten to the download endpoint so local images work)
58
+ - **HTML** → sandboxed iframe (`sandbox="allow-same-origin"`; scripts, top-navigation, forms, pop-ups all blocked — safe against hostile HTML)
59
+ - Toggle to Edit → Monaco source, Cmd+S saves as usual
60
+
61
+ ### Preview caching (MRU)
62
+ Recently opened preview tabs stay mounted (up to 5, MRU) so switching between them doesn't re-fetch or re-parse. Plugins flagged `heavy: true` bypass the cache — only the active instance mounts, others unmount. Closing a preview tab removes it from the cache immediately (aborting any in-flight fetch).
63
+
64
+ ### File metadata in header
65
+ The Canvas header shows `(size · Created … · Modified …)` for the active tab. Both text tabs and preview tabs populate this — preview tabs fetch `GET /api/files/stat` on open (and on tab restore from localStorage) since they never read content.
66
+
67
+ ### Selection tracking
68
+ Canvas tracks current selection and cursor. When `contextEnabled` is on, the selection is auto-injected into chat messages as context.
69
+
70
+ ### Maximize
71
+ The header has a maximize button on the far right. Toggling it expands Canvas to fill the entire viewport — activity bar, sidebar, and bottom panel are hidden. The state persists in localStorage so reloads keep the mode. Press `Esc` to exit — except when focus is inside an input, Monaco, or Quick Open, so those can handle `Esc` first.
72
+
73
+ Canvas (including Monaco instances, file tree, and open tab contents) stays mounted across activity-tab switches and maximize toggles — switching away and back does **not** reload content.
74
+
75
+ ## Shortcuts
76
+
77
+ | Shortcut | Action |
78
+ |---|---|
79
+ | Cmd/Ctrl + P | Quick Open (fuzzy file search) |
80
+ | Cmd/Ctrl + S | Save current tab |
81
+ | Alt + W | Close current tab (Cmd+W cannot be overridden in browsers) |
82
+ | Esc | Exit maximized Canvas (when not inside an input / Monaco / Quick Open) |
@@ -0,0 +1,89 @@
1
+ # Explorer — Requirements
2
+
3
+ VS Code-style file tree sidebar with multi-select, drag-drop, right-click menu.
4
+
5
+ ## Workspace switching
6
+
7
+ ### Initialization
8
+ - URL `?folder=/abs/path` → use that directly as workspace
9
+ - No `?folder` → call `GET /api/fs/home`, take the home directory as fallback, write it back into the URL; UI behaves identically
10
+ - There's always a valid workspace; every frontend feature (file tree / sessions / agents / settings) depends on it
11
+
12
+ ### Path input
13
+ - Explorer top bar has a path input + 📁🔍 picker button
14
+ - Pressing Enter calls `GET /api/fs/exists?path=...` first — a miss shows an alert, no switch
15
+
16
+ ### Directory picker (FolderPicker modal)
17
+ Visual directory browser opened by the 📁🔍 button:
18
+ - Top: breadcrumb input (paste paths here) + ⬆ up + 🏠 home
19
+ - Middle: `GET /api/fs/browse?path=...` pulls the current directory's children
20
+ - Drops entries starting with `.`
21
+ - Single click = select (updates the current path only)
22
+ - Double click = enter
23
+ - Bottom: Cancel / Open. Open triggers the same switch flow as Enter (including existence check).
24
+
25
+ ## Core behaviour
26
+
27
+ ### File tree (lazy)
28
+ - Root level: `GET /api/files/tree?projectId=xxx` returns only the first level
29
+ - Subdirectories: only fetched on expand via `?path=<subdir>`
30
+ - Every directory node carries `hasChildren: boolean` so the arrow renders correctly
31
+ - Directories first, alphabetical
32
+ - Hides only well-known noise: `.git`, `.DS_Store`, `node_modules`, `__pycache__`. Other dotfiles (`.gitignore`, `.env`, `.vscode/` etc.) are shown — modern IDE convention
33
+ - No max depth limit
34
+ - Expansion state lives in localStorage; restored on refresh and lazily reloaded
35
+ - WebSocket `file:changed` events incrementally update loaded branches; unloaded branches are left alone (re-fetched on expand)
36
+
37
+ ### Selection model
38
+ VS Code-style highlight selection (no checkboxes):
39
+ - **Click**: select and highlight
40
+ - **Double click**: open in editor tab
41
+ - **Ctrl/Cmd + click**: toggle multi-select
42
+ - **Shift + click**: range select
43
+
44
+ ### Drag-move
45
+ - Selected files/folders drag onto a target dir
46
+ - Multi-file drag supported
47
+ - Each file goes through `POST /api/files/rename` with `{oldPath, newPath}`
48
+
49
+ ### Right-click menu
50
+
51
+ Items shown depend on the click target:
52
+
53
+ | Action | Shown when | API / behavior |
54
+ |---|---|---|
55
+ | New File / New Folder | Always | `POST /api/files/new` · `mkdir`; auto-expands the parent folder so the inline input is visible |
56
+ | Open in Integrated Terminal | Always | Spawns a terminal at the target dir (or file's parent) |
57
+ | Open to the Side | File only | Splits the editor and opens the file in the right pane |
58
+ | Download | File only | `GET /api/files/download?path=...` |
59
+ | Rename | Single file/folder | `POST /api/files/rename` |
60
+ | Delete | Single or multi-select | `DELETE /api/files?path=...` (with confirm) |
61
+ | Open as Workspace | Folder only | Switches the active workspace to that folder, reusing the path-input switch flow (validate → persist → reload). Shown at the bottom of the menu |
62
+
63
+ The menu auto-clamps to the viewport, so right-clicking near the window edge does not clip the bottom items.
64
+
65
+ ### Modification indicator
66
+ Files with unsaved editor edits show a coloured dot in the tree (synced from `editorStore.modifiedPaths`).
67
+
68
+ ### Git decorations
69
+ When the workspace is a git repo, the tree also colors file/folder names + badges by git status, and dims `.gitignore`'d paths — driven by `/api/git/status` + `/api/git/ignored`. See [source-control.md](source-control.md#explorer-git-decorations). (A real change wins over ignored; a non-repo workspace carries no decorations.)
70
+
71
+ ### File type icons
72
+ Extension-to-icon mapping across common filetypes.
73
+
74
+ ### Quick Open
75
+ - `Cmd+P` / `Ctrl+P` opens fuzzy file search
76
+ - Query via `GET /api/files/search?projectId=X&q=...&limit=50` (150 ms debounce)
77
+ - Empty query doesn't display (avoids scanning the whole project)
78
+ - Enter opens in a new tab
79
+
80
+ ### `@` file mention (chat input)
81
+ - Typing `@` in the chat input opens the file selector
82
+ - Query via `GET /api/files/search?projectId=X&q=<after-@>&limit=15` (120 ms debounce)
83
+ - Selecting inserts a path chip
84
+
85
+ ### `@scope` directory reference (chat input)
86
+ - Typing `@scope ` opens a directory-only selector (query via `GET /api/files/search?...&dirsOnly=1`)
87
+ - Unlike `@` mention (which lifts the path into a separate chip list), `@scope <dir>` stays as **literal text** in the message — the server expands it into that directory's scoped `.halo/INSTRUCTIONS.md` for the turn (see [prompt-system.md](../design/prompt-system.md#directory-scoped-instructions-scope))
88
+
89
+ Quick Open and `@` mention both use the search API (not the lazy tree) so unexpanded files can still be found.
@@ -0,0 +1,62 @@
1
+ # Product Overview
2
+
3
+ > "Tiangong Kaiwu" — throw in an idea, the agents build it.
4
+ > Halo = the international spelling of "Kaiwu"; "halo" in Finnish hints at a spring source.
5
+
6
+ ## One-liner
7
+
8
+ **Halo is a multi-agent collaborative workspace. The user drives the entire delivery of complex projects through natural language.**
9
+
10
+ ## Problems it solves
11
+
12
+ ### 1. Multi-agent collaboration is opaque to users
13
+
14
+ Cursor / Claude Code / Devin are developer tools at heart — users have to configure agents, manage context, wire up workflows. Halo hides the orchestration: users just chat; the main agent decides how to split up work.
15
+
16
+ > Halo is a **self-orchestrating harness** wrapped in a human-readable workspace.
17
+
18
+ ### 2. Agent work products get lost
19
+
20
+ Existing memory systems are patchwork — MEMORY.md is manual notes, vector search is semantic crumbs. An agent makes a slide deck and remembers "I made a slide deck", but the design thinking, iterations, reasoning chain are gone.
21
+
22
+ Halo's answer: **everything lives in the workspace. Knowledge is tied to the project, not the agent.**
23
+
24
+ ## Role model
25
+
26
+ ```
27
+ User
28
+ | natural language
29
+ v
30
+ +------------------+
31
+ | Main Agent |
32
+ | (Orchestrator) |
33
+ | Understands intent,
34
+ | decomposes tasks,
35
+ | creates/schedules sub-agents,
36
+ | escalates key decisions
37
+ +---+------+------+-+
38
+ | | |
39
+ v v v
40
+ Sub-A Sub-B Sub-C
41
+ (each with its own tools + skills)
42
+ ```
43
+
44
+ **The main agent is a proposer, not a dictator.** It lays out the plan, the user asks "why", adjusts, confirms, and only then does it execute. The user can pause, revert, or redirect at any step.
45
+
46
+ ## Core principles
47
+
48
+ 1. **Thinking is visible** — the user sees task breakdowns, progress, file changes, tool calls. Not a black box.
49
+ 2. **User in the loop** — the user always has veto power. Any step can be paused, modified, or rejected.
50
+ 3. **Structured workspace** — all knowledge, decisions, rejected alternatives, and intermediate state are saved as project files. Agents don't need "memory" — they have archives.
51
+ 4. **Progressive autonomy** — v1: every step needs the human. v2: user-defined autonomy scope. v3: the agent decides what to ask and what to just do.
52
+
53
+ ## Competitive comparison
54
+
55
+ | Dimension | Cursor / Claude Code | Devin | MetaGPT | Manus | **Halo** |
56
+ |---|---|---|---|---|---|
57
+ | Target user | Developers | Developers | Developers | General | **Anyone** |
58
+ | Agent count | 1 | 1 | Multiple (preset) | 1 | **1 main + N sub** |
59
+ | Orchestration | Manual | Black-box | Pipeline | Black-box | **Self-orchestrating + user override** |
60
+ | Process visibility | Low | Low | None | Low | **High (the key differentiator)** |
61
+ | Mid-run intervention | Yes | No | No | Limited | **Yes** |
62
+ | Project memory | Limited | Limited | None | None | **Structured workspace** |
@@ -0,0 +1,73 @@
1
+ # Session — Requirements
2
+
3
+ Session history viewer: hierarchy tree, message playback, debug mode, system prompt inspection.
4
+
5
+ ## Core behaviour
6
+
7
+ ### Session tree view
8
+ - Top-level sessions (no parentSessionId) act as roots; child sessions render indented
9
+ - Each row shows: title, agent-name badge, message count, relative time, and status icons (StopCircle for stopped, amber Archive for archived)
10
+ - Clicking loads the full message list into SessionChatPanel
11
+ - Collapse / expand via arrow buttons
12
+ - Clicking the count badge (e.g. "+3") shows total descendant count
13
+ - Inline title rename (admin-only): a hover pencil on any row — root **or** sub-agent — opens an inline input (Enter commits, Escape cancels, blur commits); persists via `PATCH /api/sessions/logs/:id`
14
+ - Infinite scroll loads more roots in pages; a silent reload (after streaming ends, or a delete / create / archive elsewhere) re-fetches the **same depth** already scrolled to rather than snapping back to the first page. Capped at 300 top-level rows — past that, "load more" stops (older sessions, e.g. from a busy Slack channel, aren't worth scrolling to)
15
+
16
+ ### Message viewer
17
+ - All messages rendered by role
18
+ - Assistant messages render Markdown
19
+ - System messages summarise tool calls
20
+
21
+ ### Debug mode
22
+ Top Debug toggle (Bug icon). When on:
23
+ - **Normal mode**: user message + assistant reply (with inline tool-call cards); sub-agent notifications hidden
24
+ - **Debug mode**: every message, including:
25
+ - **Context / System Prompt** (purple) — full injected prompt, expandable
26
+ - **Tool Call** (blue) — full tool input JSON, expandable
27
+ - **Tool Result** (green/red) — full tool output JSON, expandable
28
+ - **Usage** — token counts, latency (ttft / e2e), model ID, cache hit ratio
29
+ - Sub-agent messages carry an agent-name badge
30
+
31
+ ### System prompt viewer
32
+ The Prompt button (FileText icon) shows the system prompt used by this session. Extracted from the first `context`-type message; displays the full injected prompt (AGENT.md + INSTRUCTIONS.md + INDEX.md + TOOL_GUIDELINES + skills).
33
+
34
+ ### Session lifecycle
35
+ - Created on the first `chat` WS message (with a new sessionId)
36
+ - Auto-saved after every `complete` event
37
+ - Sessions restore from disk on refresh / WS reconnect (loaded at subscribe time; running sessions reattach from the detached pool)
38
+ - Deleting a session removes the JSON file and cascade-deletes all descendants in SQLite
39
+ - Assistant messages embed a `toolCalls` array so tool-call cards persist across refresh
40
+
41
+ ### Non-destructive /session new
42
+ `/session new` (session:clear) **does not** destroy the old session:
43
+ 1. Save current session to disk
44
+ 2. Create a background handler to keep the old session's in-flight events flowing
45
+ 3. Reset client state, enter empty conversation
46
+ 4. The old session's sub-agents keep running independently
47
+ 5. Switching back loads from the file
48
+
49
+ ## Session categorisation
50
+
51
+ | Scenario | Display |
52
+ |---|---|
53
+ | Main session | Sidebar top level |
54
+ | Child session (has parentSessionId) | Nested under parent |
55
+ | Stopped session | Icon decorated with StopCircle |
56
+ | Archived session | Shown dimmed (50% opacity) with an amber Archive icon — the list fetches `includeArchived=1`, so archived rows stay visible for inspection rather than being hidden |
57
+
58
+ ## API
59
+
60
+ Unified session logs API (recommended):
61
+
62
+ | Operation | Endpoint |
63
+ |---|---|
64
+ | List | `GET /api/sessions/logs?projectId=...` |
65
+ | Read | `GET /api/sessions/logs/:id?projectId=...` |
66
+ | Delete | `DELETE /api/sessions/logs/:id?projectId=...` |
67
+
68
+ The List endpoint serves two consumers: this tree view (default — roots + all
69
+ descendants, paginated, rebuilt into a tree client-side) and the chat-header
70
+ recent-sessions dropdown (`rootOnly=1` — a flat list of roots). Both paginate
71
+ via `cursor` / `nextCursor`. See [dev/api.md](../dev/api.md#get-apisessionslogsprojectidabsrootonly01includearchived01cursormslimitn) for the full contract.
72
+
73
+ Implementation detail: [design/session.md](../design/session.md).
@@ -0,0 +1,224 @@
1
+ # Settings — Requirements
2
+
3
+ Global + workspace configuration. The shape mirrors VSCode's `contributes.configuration` model: **schema** (declared by each package — provider yaml, skill yaml, server built-ins) is separate from **values** (stored in the user's `settings.yaml`).
4
+
5
+ ## Storage layout
6
+
7
+ ```yaml
8
+ # ~/.halo/secrets/settings.yaml — values only
9
+ general: # built-in declarer (server itself)
10
+ session:
11
+ max_queue_size: 256
12
+ max_nesting_depth: 16
13
+ compact:
14
+ keep_messages: 5
15
+ max_summary_input: 15000
16
+ ...
17
+ sandbox:
18
+ hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker"
19
+ hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig"
20
+ writable_dirs: "" # e.g. ~/.kiro,~/.local/share/kiro-cli — rw bind-mounts in bwrap; ignored for readonly sessions
21
+ logging:
22
+ level: warn
23
+
24
+ aws-bedrock-claude-invoke: # provider id from models/<id>.yaml
25
+ secrets:
26
+ access_key_id: ""
27
+ secret_access_key: ""
28
+
29
+ kimi:
30
+ secrets:
31
+ api_key: <<KIMI_API_KEY>>
32
+
33
+ deepseek:
34
+ secrets:
35
+ api_key: <<DEEPSEEK_API_KEY>>
36
+
37
+ tavily-search: # skill id from skills/<id>/config.yaml
38
+ params:
39
+ api_key: <<TAVILY_API_KEY>>
40
+ ```
41
+
42
+ The path always reads as `<namespace-id>.<kind>s.<key>`:
43
+ - `<namespace-id>` is `general`, a provider id, or a skill id
44
+ - `<kind>` is `param` or `secret`
45
+ - `<key>` is the leaf, dotted for grouping (e.g. `general.compact.keep_messages`)
46
+
47
+ `general` is the only namespace that doesn't follow the `<id>.{params|secrets}.<key>` pattern — its declared keys are flat (`general.<key>`) since the server is the implicit declarer.
48
+
49
+ ## Two kinds of fields
50
+
51
+ | Kind | Reachable from agent? | UI render |
52
+ |---|---|---|
53
+ | **`params`** | Yes — via `{{<id>.params.<key>}}` placeholders. Substituted at `shell_exec` time. | Plain text / number input. |
54
+ | **`secrets`** | Never. Server-side only (model providers, signing keys). The placeholder renderer rejects `{{<id>.secrets.…}}`; the API returns masked values to the browser. | Masked password input; `<<ENV>>` references shown plainly (they're not the real value). |
55
+
56
+ ## Schema declaration
57
+
58
+ Schema lives **inside the package**, alongside its other files:
59
+
60
+ ### Provider secrets — `models/<provider-id>.yaml`
61
+
62
+ ```yaml
63
+ id: aws-bedrock-claude-invoke
64
+ displayName: AWS Bedrock Claude (Invoke API)
65
+ defaultEndpoint: https://bedrock-runtime.us-west-2.amazonaws.com
66
+
67
+ secrets:
68
+ - key: access_key_id
69
+ description: AWS Access Key ID
70
+ description_zh: AWS Access Key ID
71
+ secret: true
72
+ - key: secret_access_key
73
+ description: AWS Secret Access Key
74
+ description_zh: AWS Secret Access Key
75
+ secret: true
76
+
77
+ models: [...]
78
+ ```
79
+
80
+ ### Skill params/secrets — `skills/<skill-id>/config.yaml`
81
+
82
+ ```yaml
83
+ params:
84
+ - key: api_key
85
+ description: Tavily API Key
86
+ description_zh: Tavily 搜索 API Key
87
+ default: <<TAVILY_API_KEY>>
88
+ secret: true
89
+ secrets: []
90
+ ```
91
+
92
+ ### General — built-in
93
+
94
+ Declared in [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`. The server itself is the implicit declarer. Keys: `language`, `agent.*`, `session.*`, `compact.*`, `sandbox.*`, `logging.*`, `evolution.*`, `limits.*`.
95
+
96
+ `evolution.*` controls the self-evolution subsystem (see [plans/self-evolution.md](../plans/self-evolution.md)). All `evolution.*` keys are `globalOnly` — they live in `~/.halo/secrets/settings.yaml` only, not workspace settings. Notable knobs: `evolution.level` (`L0` = off, `L1` = human + LLM assist), `evolution.max_concurrent_run` / `max_concurrent_apply` (wrapper concurrency caps), `evolution.run_timeout_minutes` / `apply_timeout_minutes` (heartbeat timeouts), `evolution.max_attempts` (per-row retry cap), `evolution.triggers.pre_compact` (snapshot session before compaction).
97
+
98
+ `agent.default_provider` is rendered as an `enum` whose options are the provider ids found under `~/.halo/global/models/*.yaml`. It controls **which provider a freshly scaffolded agent.yaml uses** — the model id, endpoint, prompt-caching TTL and thinking defaults are then derived from that provider's YAML (`defaultModelId`, `defaultEndpoint`, per-model `capabilities.promptCaching.default`, `capabilities.thinking.defaultEnabled / default / defaultBudgetTokens`). Existing agents are not retroactively touched. Implementation: [packages/server/src/routes/agent-configs.ts](../../../packages/server/src/routes/agent-configs.ts) `buildScaffoldModelBlock()`.
99
+
100
+ ### Field attributes
101
+
102
+ | Attribute | Required | Purpose |
103
+ |---|---|---|
104
+ | `key` | yes | Leaf key under the namespace |
105
+ | `description` | no | English description rendered as help text |
106
+ | `description_zh` | no | Chinese description (UI picks based on lang) |
107
+ | `default` | no | Placeholder shown when the value is unset; supports `<<ENV>>` |
108
+ | `secret` | no | `true` → masked in API responses + password input in UI |
109
+
110
+ ## Scope: global vs. workspace
111
+
112
+ | Scope | File | Priority |
113
+ |---|---|---|
114
+ | Global | `~/.halo/secrets/settings.yaml` | Base |
115
+ | Workspace | `<project>/.halo/settings.yaml` | Overrides global, key by key |
116
+
117
+ Read order: `<schema default> <- <global> <- <workspace>`.
118
+
119
+ The Settings page shows source badges per field:
120
+ - `workspace` (green-blue, override applied here)
121
+ - `inherited from global` (grey, value pulled from global because workspace has none)
122
+ - `unset` (no value at any layer; the `default` is shown as placeholder)
123
+
124
+ A Reset button on each field removes the value at the current scope, letting it fall back to the lower scope (or unset).
125
+
126
+ ## Environment variable injection
127
+
128
+ Values can carry `<<ENV_NAME>>` placeholders. They're expanded:
129
+ - At `shell_exec` time inside values resolved through `{{<id>.params.<key>}}` — see [workspace-tools.ts](../../../packages/server/src/tools/workspace-tools.ts) `substituteSecrets`.
130
+ - At read time when server-side code calls `getServerSecret(namespaceId, key)` — see [config.ts](../../../packages/server/src/config.ts).
131
+
132
+ **Trust boundary**: `<<ENV>>` is only expanded inside settings-resolved values. Raw cmd text the agent writes is not scanned — `shell_exec "echo <<HOME>>"` keeps the literal. This prevents an agent from naming an env var and forcing the server to dump it.
133
+
134
+ Env var unset → the `<<ENV_NAME>>` literal stays verbatim, plus `[md-vars] Env var "X" not set — keeping <<X>> literal` in the server log. The Settings UI returns the literal too — the browser never sees the real env value.
135
+
136
+ ## Agent visibility
137
+
138
+ Placeholder syntax in MD bodies (SKILL.md / AGENT.md):
139
+
140
+ | In MD body | Result |
141
+ |---|---|
142
+ | `{{<skill-id>.params.<key>}}` (long form) | Replaced with the value (after `<<ENV>>` resolution) at `shell_exec` time |
143
+ | `{{params.<key>}}` (short form) inside a SKILL.md | Auto-rewritten to `{{<this-skill-id>.params.<key>}}` at `activate_skill` |
144
+ | `{{<id>.secrets.<key>}}` | Hard-rejected — kept as literal, server logs a whitelist warning |
145
+ | `{{general.compact.keep_messages}}` | Same — kept literal |
146
+ | `{{args}}`, `{{workspace_root}}`, etc. | Built-ins, replaced at render time |
147
+
148
+ Enforced in two places:
149
+ - [md-vars.ts](../../../packages/server/src/prompts/md-vars.ts) `renderMdBody` — only matches `^[\w-]+\.params\.[\w-][\w.-]*$`.
150
+ - [workspace-tools.ts](../../../packages/server/src/tools/workspace-tools.ts) `substituteSecrets` — same regex on `{{}}` placeholders.
151
+
152
+ A malicious skill that tries `curl -H "Bearer {{aws-bedrock-claude-invoke.secrets.secret_access_key}}"` gets the literal placeholder, not the value.
153
+
154
+ ## Orphans
155
+
156
+ Values present in `settings.yaml` whose namespace doesn't appear in any current schema declaration are surfaced as **orphans** in a dedicated tab. They aren't deleted automatically — uninstalling a skill keeps its values around so re-installing pops them back in. Users prune them on their own schedule via the orphan tab's per-key Remove buttons.
157
+
158
+ `general.*` is intentionally excluded from orphan detection — its declared keys are enumerated by the built-in schema, so anything else there is treated as either a typo or a forward-compat field, not an orphan.
159
+
160
+ ## API
161
+
162
+ | Operation | Method | Endpoint | Purpose |
163
+ |---|---|---|---|
164
+ | Read merged settings (legacy) | GET | `/api/settings?projectId=xxx` | Raw read, used by older tooling |
165
+ | Read schema + resolved values | GET | `/api/settings/schema?projectId=xxx` | Drives the new Settings page |
166
+ | Replace scope | PUT | `/api/settings` | Bulk replace one yaml file |
167
+ | Patch single key | PATCH | `/api/settings` | Set a leaf at `<dotted-key>` |
168
+ | Delete key | DELETE | `/api/settings` | Remove a leaf (used for Reset / Remove orphan) |
169
+
170
+ ### `/api/settings/schema` response
171
+
172
+ ```json
173
+ {
174
+ "scope": "global" | "workspace",
175
+ "sections": [
176
+ {
177
+ "namespaceId": "aws-bedrock-claude-invoke",
178
+ "source": "provider",
179
+ "displayName": "AWS Bedrock Claude (Invoke API)",
180
+ "description": "...",
181
+ "fields": [
182
+ {
183
+ "key": "access_key_id",
184
+ "kind": "secret",
185
+ "description": "AWS Access Key ID",
186
+ "description_zh": "...",
187
+ "default": null,
188
+ "secret": true,
189
+ "value": "AK****ST",
190
+ "hasValue": true,
191
+ "source": "global",
192
+ "inheritedFromGlobal": false
193
+ }
194
+ ]
195
+ }
196
+ ],
197
+ "orphans": [
198
+ { "namespaceId": "tavily", "kind": "param", "key": "api_key" }
199
+ ]
200
+ }
201
+ ```
202
+
203
+ ### PATCH body
204
+
205
+ ```json
206
+ {
207
+ "scope": "global" | "workspace",
208
+ "projectId": "...", // required if scope=workspace
209
+ "key": "aws-bedrock-claude-invoke.secrets.access_key_id",
210
+ "value": "AKIA…"
211
+ }
212
+ ```
213
+
214
+ ### DELETE body
215
+
216
+ Same shape minus `value`. Removes the leaf at `key`. For Reset behaviour: workspace scope DELETE → field falls back to global / default; global scope DELETE → field becomes unset.
217
+
218
+ ## i18n
219
+
220
+ The admin UI is bilingual (en/zh). Field descriptions are localized via `description_zh` with fallback to `description`. Fixed UI labels live in `packages/admin/src/shared/i18n/`.
221
+
222
+ ## Config caching
223
+
224
+ `config.ts` reads `settings.yaml` lazily with mtime-watching: every read stats the file and reparses if the mtime has changed. UI saves bump the mtime, so the server picks up new secrets on the next call without a restart. See [packages/server/src/config.ts](../../../packages/server/src/config.ts) `getSettings()`.
@@ -0,0 +1,125 @@
1
+ # Skill — Requirements
2
+
3
+ A skill is a knowledge pack an agent can reference — prompts, templates, scripts, reference docs.
4
+
5
+ ## Core behaviour
6
+
7
+ ### Mini workspace
8
+ Clicking a skill in the sidebar turns the main area into a mini workspace:
9
+ - File tree: every file in the skill directory
10
+ - Monaco editor: edit any file (SKILL.md, templates, scripts)
11
+ - Reuses the main workspace's editor / explorer components
12
+
13
+ The skill editor runs in an **isolated** `EditorStoreProvider` — its tabs, file tree, and
14
+ selection are independent of the main Explorer Canvas, so switching back to Explorer
15
+ restores that Canvas untouched. The provider is keyed on `${id}:${scope}`, so switching
16
+ between skills also resets the mini workspace (no cross-skill tab bleed). The maximize
17
+ button is hidden in this mode (`showMaximize={false}`).
18
+
19
+ ### CRUD
20
+
21
+ | Operation | API |
22
+ |---|---|
23
+ | List | `GET /api/skills?projectId=xxx` — returns BOTH global and workspace skills as separate entries (no merge); global entries shadowed by a same-id workspace skill are flagged `overridden: true` |
24
+ | Create | `POST /api/skills` (body: name, description, scope, projectId) |
25
+ | Delete | `DELETE /api/skills/:id?scope=...&projectId=...` |
26
+ | Toggle disabled | `PATCH /api/skills/:id/toggle?scope=...&projectId=...` → `{ ok, disabled }` |
27
+
28
+ ### Scope (Global / Workspace)
29
+
30
+ | Scope | Path | Notes |
31
+ |---|---|---|
32
+ | Global | `~/.halo/global/skills/<id>/` | Shared across projects |
33
+ | Workspace | `<project>/.halo/skills/<id>/` | Wins over a same-id global skill at runtime |
34
+
35
+ ### Management UI
36
+
37
+ The Skills sidebar mirrors the Agents sidebar layout:
38
+ - Two collapsible sections — **Global** (globe icon) and **Workspace** (folder icon). Expanded state persists in `halo_skills_expandedScopes` (localStorage)
39
+ - Each section header has its own **+ button** — scope is implicit from which button is clicked, no pop-up picker
40
+ - Creating a name that collides with the other scope prompts a one-time confirmation explaining the runtime override
41
+ - Items overridden by a workspace skill are rendered dimmed with "overridden" subtitle
42
+ - Disabled skills are rendered dimmed with "disabled" subtitle and a toggle switch (always visible). Disabled state is stored per workspace in the `disabled_items` table of `halo.db` (not in SKILL.md frontmatter). Both global and workspace skills can be independently toggled per workspace. Disabled skills are excluded from system prompt injection, activate_skill tool, agent form skill picker, and `/workspace share` export.
43
+ - Selection uses a composite `id:scope` key (localStorage `halo_skills_selectedKey`) so the same id in different scopes can coexist
44
+
45
+ ### Auto-sync behaviour (workspace vs. global)
46
+
47
+ The Skills sidebar and any open mini-workspace editor stay in sync with the file system via `file:changed` WS events — but the server's watcher is scoped to the current workspace, so the guarantees differ by scope:
48
+
49
+ | Change | Workspace skills | Global skills |
50
+ |---|---|---|
51
+ | Create / delete a skill dir | Live (watcher emits add/unlink → `skill-bus` bump → sidebar refetches) | Refreshed opportunistically on **window focus** |
52
+ | Edit a file inside a skill while the mini-workspace editor has it open | Live (EditorPanel translates workspace-rel path into its own tab.path and refetches) | Live for the currently-open tab (same code path) — but the file tree change won't show new/removed sibling files until focus refresh |
53
+ | Edit from external CLI / SSH | Same as above | Same as above |
54
+
55
+ So when an agent (or the `skill` skill) creates a **global** skill, the Skills sidebar won't reflect it until the user refocuses the browser window. The `skill` skill documents this so agents can warn the user.
56
+
57
+ ### SKILL.md protocol
58
+
59
+ ```markdown
60
+ ---
61
+ name: code-review
62
+ description: Automated code review with checklist
63
+ command: /review
64
+ ---
65
+
66
+ # Code Review
67
+
68
+ Review the code for:
69
+ 1. Correctness
70
+ 2. Performance
71
+ ...
72
+ ```
73
+
74
+ - **Frontmatter** (required): `name` (kebab-case, = directory name) + `description` — injected into the agent system prompt for discovery
75
+ - **Body**: the full instructions, lazily loaded by the agent via `activate_skill`
76
+ - **Optional fields**: `allowed-tools` (space-separated), `metadata` (custom kv), `command` (slash command registration — opt-in, only declared commands exist), `requiresAccess` (one of `full` / `workspace` / `readonly`), `verbs` (Halo extension — subcommand list, each `{ name, builtin?, requiresAccess?, desc? }`; skill verbs' access gates are declared here), `disable-model-invocation` (standard — command exists but skill is not injected for model auto-activation), `user-invocable` (standard — `false` = never a slash command, model can still activate)
77
+ - **Arguments**: user command-line args fill `$ARGUMENTS` / `$1`–`$9` placeholders in the body (quotes respected, `\$` escapes, `$5.00`/`$PATH` untouched); `{{...}}` placeholders (params/channel/workspace_root etc.) carry platform-injected values — the two coexist without cross-translation. A verb reaches the body as `$1` for dispatch; args are no longer appended verbatim to the body end
78
+
79
+ ### Skill-as-command
80
+ When a `command` field is present (e.g. `command: /review`), the skill auto-registers as a slash command. Users trigger it with `/review`. See [commands](command.md).
81
+
82
+ If the `command` collides with a built-in slash command (or another skill's `command`), it is dropped from the command list with a warning — built-ins win, then first-come among skills. Only the slash command is lost; the skill stays usable via `activate_skill`. See [conflict detection](command.md#conflict-detection).
83
+
84
+ ### Access-level gate (`requiresAccess`)
85
+
86
+ A skill can declare a minimum session access level in frontmatter:
87
+
88
+ ```yaml
89
+ ---
90
+ name: workspace
91
+ description: ...
92
+ command: /workspace
93
+ requiresAccess: workspace
94
+ ---
95
+ ```
96
+
97
+ When set, the skill is hidden from agents whose session access level is more restricted (e.g. a `readonly` Telegram channel can't see or invoke a `requiresAccess: full` skill). The check runs both at metadata-load time (so it never enters the system prompt's `<available_skills>` block) and at slash-command execution time (server-side gate, so a user typing the slash manually still hits it). `requiresAccess` is independent of `command`: an agent-activated-only skill (no `command`) is still hidden from too-restricted sessions.
98
+
99
+ Default is unset → the skill is visible to all access levels. `full` is the standard "admin-only" marker for skills that mutate global state (cron, self, etc.).
100
+
101
+ ### Settings integration
102
+
103
+ When a skill needs configuration (API keys, endpoints, preset values):
104
+ 1. Declare them in a `config.yaml` next to `SKILL.md` — schema lives with the package
105
+ 2. Reference them from the SKILL.md body with the short form `{{params.<key>}}` (auto-qualified to `{{<skill-id>.params.<key>}}` at activation)
106
+ 3. Values live in `~/.halo/secrets/settings.yaml` at `<skill-id>.params.<key>` — written by the user via the Settings page or by hand
107
+ 4. Use `<<ENV_NAME>>` placeholders for sensitive values; the real value comes from env vars at render time
108
+
109
+ Full rules: [guide/skills.md](../guide/skills.md#placeholders-template-variables) and [requirements/settings.md](settings.md).
110
+
111
+ ### Agent references
112
+ Agents reference skills by id in `agent.yaml`'s `skills` field:
113
+
114
+ ```yaml
115
+ skills:
116
+ - code-review
117
+ - test-writer
118
+ ```
119
+
120
+ Runtime uses **progressive disclosure**:
121
+ 1. **Discovery**: only skill metadata (name + description) is injected into the system prompt
122
+ 2. **Activation**: the agent calls `activate_skill(skill_id)` to load the full SKILL.md
123
+ 3. **Execution**: the agent follows the loaded instructions; `file_read` can pull resources from the skill directory
124
+
125
+ Avoids bloating the system prompt with unused skill content.