@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,236 @@
1
+ ---
2
+ name: acp
3
+ description: Talk to other agents over ACP — `/acp kiro <q>` (local Kiro), `/acp claude <q>` (local Claude Code) — and manage `ask-<label>` bindings for remote halo servers. Activate when the user wants to ask another agent something or wire up an ACP connection.
4
+ command: /acp
5
+ requiresAccess: full
6
+ verbs:
7
+ - { name: kiro, desc: Ask the local Kiro (rest of the line is the question) }
8
+ - { name: claude, desc: Ask the local Claude Code (rest of the line is the question) }
9
+ - { name: add, desc: Generate an ask-<label> binding for a remote halo }
10
+ - { name: list, desc: List generated ask-* binding skills }
11
+ - { name: remove, desc: Remove a generated ask-* binding skill }
12
+ ---
13
+
14
+ # acp
15
+
16
+ The requested action is **`$1`**; everything after it is the payload
17
+ (`$ARGUMENTS` minus the first token). With natural language, infer both.
18
+
19
+ ## Direct ask verbs — kiro / claude
20
+
21
+ The question is the rest of the line. The helper lives in this skill's
22
+ directory under `templates/ask.py` — the workspace copy wins if it exists,
23
+ else the global one (deterministic paths, no glob needed):
24
+
25
+ - `<workspace>/.halo/skills/acp/templates/ask.py`
26
+ - `~/.halo/global/skills/acp/templates/ask.py`
27
+
28
+ Pick ASK = the first of those that exists, then run and relay the reply
29
+ (quote short answers; summarize long ones — never paste the SESSION
30
+ bookkeeping line):
31
+
32
+ ```bash
33
+ # /acp kiro <question> — local Kiro CLI
34
+ shell_exec: python3 $ASK "<question>" --kind kiro --cwd {{workspace_root}}
35
+
36
+ # /acp claude <question> — local Claude Code
37
+ shell_exec: python3 $ASK "<question>" --kind claude --cwd {{workspace_root}}
38
+ ```
39
+
40
+ Remote halo servers are reached through generated bindings (`/ask-<label>`),
41
+ not a direct verb — see add/list/remove below.
42
+
43
+ - Follow-ups on the same topic: reuse the `SESSION:` id from the previous
44
+ call's stdout with `--session-id <id>` so the other agent keeps context.
45
+ If it errors with unknown session, drop the id and start fresh.
46
+ - **The question text is passed verbatim** — including the OTHER agent's own
47
+ slash commands. Verified with kiro: sending `/model` as the question lists
48
+ its available models; `/model <full-model-id>` switches its model and saves
49
+ it as default (fuzzy names like `claude` are rejected — full id only, e.g.
50
+ `claude-opus-4.7`). Use this when the user asks to change the remote/local
51
+ agent's model or invoke its built-in commands. Other kinds may support their
52
+ own command sets the same way.
53
+ - Failure modes: `not found in PATH` → that CLI isn't installed (kiro-cli /
54
+ claude-agent-acp / halo). Exit 124 → timed out, suggest a narrower question.
55
+ stopReason≠end_turn on stderr → pass it along with the partial reply.
56
+
57
+ ## Remote halo bindings — add / list / remove
58
+
59
+ These verbs manage the GENERATED `ask-<label>` skills (one per remote halo,
60
+ each with its own `/ask-<label>` command and settings namespace holding that
61
+ remote's host/token).
62
+
63
+ - **list** — enumerate `ask-*` skill dirs (workspace + global); report each
64
+ label and target.
65
+ - **remove** — confirm, delete the chosen `ask-<label>` skill directory, and
66
+ mention the leftover `ask-<label>:` block in settings.yaml can be cleaned.
67
+ - **add** — the generator flow below.
68
+
69
+ ## Add — generator flow
70
+
71
+ Stamps out a new skill named `ask-<label>` that bridges this local agent to a different halo agent over the [ACP adapter](../../docs/dev/acp-adapter.md). One binding = one remote (host + port + token + workspace). Multiple bindings can coexist — each gets its own slash command (`/ask-sa-agent`, `/ask-prod`, …) and its own settings namespace.
72
+
73
+ This is the **only supported path** to set up a new ACP binding — there's no longer a generic `ask-acp-agent` skill. The split is intentional: each remote needs its own `params.token`, and skill namespaces are 1:1 with skills.
74
+
75
+ ## Step 1 — collect inputs
76
+
77
+ Required from the user:
78
+
79
+ | Field | Notes |
80
+ |---|---|
81
+ | `label` | Slug — lowercase, hex/dash only (regex `^[a-z][a-z0-9-]*$`). Used for the skill id (`ask-<label>`), slash command (`/ask-<label>`), and settings namespace. Must not collide with an existing skill. Example: `sa-agent`, `prod-cluster`, `audit-ws`. |
82
+ | `host` | Remote halo server hostname / IP. |
83
+ | `port` | Remote halo server port. (halo kind only) |
84
+ | `workspace` | Absolute path of the remote workspace on its server. |
85
+ | `token` | Web-channel token from the remote halo's admin (Channels → Web → copy). Should be a `full` access token if you'll later want to override workspace per call. |
86
+
87
+ Optional:
88
+
89
+ | Field | Default |
90
+ |---|---|
91
+ | `label_display` | Pretty display name (e.g. "SA Agent"). Defaults to `label` capitalized. |
92
+ | `agent_id` | Remote agent profile to use. Blank = remote `default`. |
93
+
94
+ Also ask **scope**: install the skill **globally** (`~/.halo/global/skills/`, available to every workspace's agents) or **only in this workspace** (`<workspace>/.halo/skills/`). Default to "this workspace" if unsure — share-workspace and reorg are easier.
95
+
96
+ ## Step 2 — validate
97
+
98
+ Before writing files:
99
+
100
+ 1. **Slug**: regex `^[a-z][a-z0-9-]*$`. Reject anything else.
101
+ 2. **Collision**: check whether `ask-<label>` already exists in the chosen scope. If yes, ask the user to pick a different label OR confirm overwrite.
102
+ 3. **`halo` binary**: confirm `halo` is on the PATH the local agent's `shell_exec` sees (`shell_exec: which halo`). If not, the binding will install but won't run — surface this and stop.
103
+
104
+ ## Step 3 — stage files
105
+
106
+ The template directory is `~/.halo/global/skills/acp/templates/`. Copy + substitute placeholders. Three files per binding:
107
+
108
+ ```
109
+ <scope-dir>/ask-<label>/
110
+ ├── SKILL.md (from templates/SKILL.md.tmpl)
111
+ ├── config.yaml (from templates/config.yaml.tmpl)
112
+ └── ask.py (verbatim copy of templates/ask.py — stays unchanged)
113
+ ```
114
+
115
+ Where `<scope-dir>` is:
116
+
117
+ - Global: `~/.halo/global/skills/`
118
+ - Workspace: `<current-workspace>/.halo/skills/`
119
+
120
+ ### Placeholders
121
+
122
+ Both `SKILL.md.tmpl` and `config.yaml.tmpl` use `{{NAME}}` markers. Substitute these literally — they're NOT halo `{{params.X}}` template syntax (those `{{params.X}}` strings should remain in the rendered output as-is so the runtime expands them at shell_exec time).
123
+
124
+ | Placeholder | Replace with |
125
+ |---|---|
126
+ | `{{LABEL}}` | the slug (e.g. `sa-agent`) |
127
+ | `{{LABEL_DISPLAY}}` | display name (defaults to `label_display` or capitalized label) |
128
+ | `{{HOST}}` | host |
129
+ | `{{PORT}}` | port |
130
+ | `{{WORKSPACE}}` | remote workspace path |
131
+ | `{{SKILL_DIR}}` | absolute path of the new skill dir; use this in the `python3 …/ask.py` line so the cmd works regardless of cwd |
132
+
133
+ Implementation hint — small Python / shell pipeline works fine:
134
+
135
+ ```bash
136
+ LABEL=sa-agent
137
+ LABEL_DISPLAY="SA Agent"
138
+ HOST=ec2-1-2-3-4.compute.amazonaws.com
139
+ PORT=9527
140
+ WORKSPACE=/home/ubuntu/sa-agent
141
+ SCOPE_DIR=$HOME/.halo/global/skills # or the workspace's .halo/skills
142
+ SKILL_DIR=$SCOPE_DIR/ask-$LABEL
143
+ TPL=$HOME/.halo/global/skills/acp/templates
144
+
145
+ mkdir -p "$SKILL_DIR"
146
+ sed -e "s|{{LABEL}}|$LABEL|g" \
147
+ -e "s|{{LABEL_DISPLAY}}|$LABEL_DISPLAY|g" \
148
+ -e "s|{{HOST}}|$HOST|g" \
149
+ -e "s|{{PORT}}|$PORT|g" \
150
+ -e "s|{{WORKSPACE}}|$WORKSPACE|g" \
151
+ -e "s|{{SKILL_DIR}}|$SKILL_DIR|g" \
152
+ "$TPL/SKILL.md.tmpl" > "$SKILL_DIR/SKILL.md"
153
+ sed -e "s|{{LABEL}}|$LABEL|g" \
154
+ -e "s|{{LABEL_DISPLAY}}|$LABEL_DISPLAY|g" \
155
+ -e "s|{{HOST}}|$HOST|g" \
156
+ -e "s|{{PORT}}|$PORT|g" \
157
+ -e "s|{{WORKSPACE}}|$WORKSPACE|g" \
158
+ "$TPL/config.yaml.tmpl" > "$SKILL_DIR/config.yaml"
159
+ cp "$TPL/ask.py" "$SKILL_DIR/ask.py"
160
+ chmod +x "$SKILL_DIR/ask.py"
161
+ ```
162
+
163
+ If any path contains `|` (rare), pick a different sed delimiter or use Python.
164
+
165
+ ## Step 4 — write **all** params to settings.yaml
166
+
167
+ Halo's shell_exec `{{<id>.params.X}}` substitution reads from `settings.yaml` and does NOT fall back to `config.yaml` `default:`. Config.yaml defaults are *only* used as form placeholders in the admin Settings UI. So at install time we must write every value the helper script needs (host, port, workspace, token, label) into settings.yaml — otherwise the SKILL.md's `python3 ask.py ... --host {{params.host}}` will pass through as a literal `{{...}}` to shell_exec and break.
168
+
169
+ Choose the file by scope:
170
+
171
+ - **Workspace scope**: `<workspace>/.halo/settings.yaml`
172
+ - **Global scope**: `~/.halo/secrets/settings.yaml`
173
+
174
+ Merge into the file (don't overwrite — there may be other namespaces already there):
175
+
176
+ ```yaml
177
+ ask-<label>:
178
+ params:
179
+ host: <host>
180
+ port: "<port>" # quote so YAML keeps it as a string
181
+ workspace: <workspace>
182
+ label: <label_display>
183
+ token: <token>
184
+ # agent_id intentionally omitted (default empty); ask.py drops blank flags
185
+ ```
186
+
187
+ Use Python for the merge — yaml-aware, won't trash existing keys:
188
+
189
+ ```python
190
+ import yaml, pathlib
191
+ p = pathlib.Path("<settings-path>")
192
+ data = yaml.safe_load(p.read_text()) if p.exists() else {}
193
+ if not isinstance(data, dict):
194
+ data = {}
195
+ ns = data.setdefault("ask-<label>", {})
196
+ params = ns.setdefault("params", {})
197
+ params["host"] = "<host>"
198
+ params["port"] = "<port>"
199
+ params["workspace"] = "<workspace>"
200
+ params["label"] = "<label_display>"
201
+ params["token"] = "<token>"
202
+ p.parent.mkdir(parents=True, exist_ok=True)
203
+ p.write_text(yaml.safe_dump(data, allow_unicode=True, sort_keys=False))
204
+ ```
205
+
206
+ ## Step 5 — wire into the agent that should use it
207
+
208
+ The new skill exists but no agent will activate it until it's listed in an agent's `skills:` array. Two paths:
209
+
210
+ - **The current workspace's `default` agent**: read `<workspace>/.halo/agents/default/agent.yaml` (or whichever agent the user is talking to right now), add `ask-<label>` to its `skills:` list, write back.
211
+ - **Skip and let the user do it**: surface the path + tell them.
212
+
213
+ Default behavior: edit the **current agent's** yaml automatically; if the agent has no workspace-local yaml (i.e. it inherits a global agent), tell the user "add `ask-<label>` to your agent's skills list to enable" and surface the path.
214
+
215
+ ## Step 6 — confirm
216
+
217
+ Reply with:
218
+
219
+ - The four paths created (SKILL.md, config.yaml, ask.py, settings.yaml entry).
220
+ - The new slash command (`/ask-<label>`) the user can now type.
221
+ - A reminder that admin Settings → Skills → Ask <Label_Display> will show the form with the token already filled.
222
+ - One example invocation:
223
+
224
+ > All set! Two ways to use it:
225
+ >
226
+ > - Type the slash command directly: `/ask-{{label}} What was our EC2 spend this month?`
227
+ > - Or just mention the remote in chat: "ask {{label_display}} for this month's EC2 spend"
228
+ >
229
+ > The token is stored at {{settings_path}}; you can edit it in Admin → Settings → Skills → Ask {{label_display}}.
230
+
231
+ ## Patterns that go sideways
232
+
233
+ - **Picking a label that contains uppercase / underscores** — file system ok, but `command: /ask-<label>` slash command names are case-sensitive and ugly. Stick to lowercase + dashes.
234
+ - **Forgetting to wire the agent** — the binding installs cleanly but the agent never sees it (silently absent). Always do step 5 or be explicit about why you skipped.
235
+ - **Putting the token into config.yaml `default:`** — config.yaml is committed code; settings.yaml is per-install secrets. Don't cross the streams.
236
+ - **Reusing the same `label` for two different remotes** — Settings namespace collision. Detect at step 2.
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: Ask {{LABEL_DISPLAY}}
3
+ description: Ask a question to the {{LABEL_DISPLAY}} halo agent (running on {{HOST}}:{{PORT}}, workspace {{WORKSPACE}}) over ACP. Activate when the user names this remote ("ask {{LABEL}}…", "the {{LABEL_DISPLAY}} agent") or asks something only that workspace's agent has data / tools for. Returns the remote agent's reply as text.
4
+ command: /ask-{{LABEL}}
5
+ ---
6
+
7
+ # Ask {{LABEL_DISPLAY}}
8
+
9
+ This binding talks to the **{{LABEL_DISPLAY}}** halo agent at `{{HOST}}:{{PORT}}`, workspace `{{WORKSPACE}}`. It spawns `halo acp`, pipes JSON-RPC over stdio, forwards the prompt, streams the reply back as text.
10
+
11
+ **When to use this**:
12
+
13
+ - The user names this remote ("ask {{LABEL}}…", "the {{LABEL_DISPLAY}} agent") — direct signal.
14
+ - The user asks something only `{{WORKSPACE}}` has the credentials / files / context for, and your local workspace doesn't.
15
+
16
+ **When NOT to use it**:
17
+
18
+ - The local agent can answer it itself.
19
+ - The user's question is workspace-meta ("list my files", "what skills do I have") — that's local.
20
+ - A different remote binding fits better (run `/help` to see all `/ask-*` commands; pick the closest match by label).
21
+
22
+ ## Invoke
23
+
24
+ Sibling helper: this skill's directory contains `ask.py`. Always quote the user's question as the FIRST positional arg:
25
+
26
+ ```bash
27
+ shell_exec: python3 {{SKILL_DIR}}/ask.py \
28
+ "<the user's question, in the language they asked it>" \
29
+ --host {{params.host}} \
30
+ --port {{params.port}} \
31
+ --workspace {{params.workspace}} \
32
+ --token {{params.token}}
33
+ ```
34
+
35
+ If `params.agent_id` is non-blank, append `--agent-id {{params.agent_id}}`. Skip when blank — `ask.py` also drops blank / unsubstituted-`{{…}}` agent-ids defensively, but cleaner not to pass it at all.
36
+
37
+ stdout looks like:
38
+
39
+ ```
40
+ SESSION: <session-id>
41
+ ---
42
+ <remote agent's reply text>
43
+ ```
44
+
45
+ The session id is the ACP session — keep it for follow-up turns.
46
+
47
+ ### Threading (multi-turn on the same remote conversation)
48
+
49
+ Sequences of related questions ("what's the AWS bill", "now break down Bedrock", "now show RDS by region") should reuse the same remote session so the remote agent sees full history. Parse the `SESSION:` line from the first call's stdout; on follow-ups append `--session-id <id>`:
50
+
51
+ ```bash
52
+ shell_exec: python3 {{SKILL_DIR}}/ask.py \
53
+ "<follow-up question>" \
54
+ --host {{params.host}} --port {{params.port}} \
55
+ --workspace {{params.workspace}} --token {{params.token}} \
56
+ --session-id <id-from-previous-call>
57
+ ```
58
+
59
+ If a follow-up says `unknown session: <id>` on stderr, the remote session was deleted — drop the id and start fresh.
60
+
61
+ ## Reply format
62
+
63
+ > Asked **{{params.label}}** — here's the reply:
64
+ >
65
+ > <copy or summarize the response>
66
+
67
+ Quote directly when short. Summarize multi-section replies and offer to dig deeper. Don't paste the SESSION line — that's bookkeeping.
68
+
69
+ ## Failure modes
70
+
71
+ - **HTTP 401**: token wrong / disabled. Tell the user to fix `ask-{{LABEL}}.params.token` in Settings → Skills → Ask {{LABEL_DISPLAY}}.
72
+ - **`fetch failed`**: server `{{HOST}}:{{PORT}}` unreachable.
73
+ - **Timeout (exit 124)**: remote prompt > 10 min. Offer narrower question.
74
+ - **stopReason ≠ end_turn**: stderr includes `stopReason=cancelled|refusal`. Pass along with the partial reply.
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env python3
2
+ """ask.py — call an agent over ACP: remote halo server, local Claude Code, or local Kiro. See SKILL.md."""
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import threading
12
+ import time
13
+ from typing import Any
14
+
15
+
16
+ def fail(msg: str, code: int = 1) -> None:
17
+ sys.stderr.write(f"[ask-acp-agent] {msg}\n")
18
+ sys.exit(code)
19
+
20
+
21
+ def main() -> None:
22
+ p = argparse.ArgumentParser(prog="ask.py", description="Ask a remote agent over ACP.")
23
+ p.add_argument("question")
24
+ p.add_argument("--kind", default="halo", choices=["halo", "claude", "kiro"],
25
+ help="halo: remote halo server via `halo acp`; claude: local Claude Code via `claude-agent-acp`; kiro: local Kiro via `kiro-cli acp`.")
26
+ p.add_argument("--host")
27
+ p.add_argument("--port")
28
+ p.add_argument("--token")
29
+ p.add_argument("--workspace")
30
+ p.add_argument("--cwd", default=None, help="claude kind: working directory for the session (defaults to CWD).")
31
+ p.add_argument("--agent-id", default=None)
32
+ p.add_argument("--session-id", default=None)
33
+ p.add_argument("--timeout", type=int, default=600)
34
+ p.add_argument("--halo-bin", default=None)
35
+ p.add_argument("--claude-bin", default=None)
36
+ p.add_argument("--kiro-bin", default=None)
37
+ args = p.parse_args()
38
+
39
+ if args.kind == "claude":
40
+ bin_ = args.claude_bin or os.environ.get("CLAUDE_ACP_BIN") or "claude-agent-acp"
41
+ if shutil.which(bin_) is None and not os.path.isabs(bin_):
42
+ fail(f"`{bin_}` not found in PATH. npm install -g @agentclientprotocol/claude-agent-acp, or set --claude-bin.", 2)
43
+ cmd = [bin_]
44
+ elif args.kind == "kiro":
45
+ bin_ = args.kiro_bin or os.environ.get("KIRO_ACP_BIN") or "kiro-cli"
46
+ if shutil.which(bin_) is None and not os.path.isabs(bin_):
47
+ fail(f"`{bin_}` not found in PATH. Install Kiro CLI, or set --kiro-bin.", 2)
48
+ # --trust-all-tools: the binding is a headless relay; there's no human
49
+ # at this end to answer kiro's per-tool permission prompts.
50
+ cmd = [bin_, "acp", "--trust-all-tools"]
51
+ if args.agent_id and not args.agent_id.startswith("{{"):
52
+ cmd += ["--agent", args.agent_id]
53
+ else:
54
+ for k in ("host", "port", "token", "workspace"):
55
+ if not getattr(args, k):
56
+ fail(f"--{k} is required for --kind halo", 2)
57
+ halo = args.halo_bin or os.environ.get("HALO_BIN") or "halo"
58
+ if shutil.which(halo) is None and not os.path.isabs(halo):
59
+ fail(f"`{halo}` not found in PATH. Install @turmind/halo or set --halo-bin.", 2)
60
+ cmd = [halo, "acp", "--host", args.host, "--port", str(args.port),
61
+ "--token", args.token, "--workspace", args.workspace]
62
+ # Drop `--agent-id` when blank OR an unsubstituted `{{...}}` literal.
63
+ # Both mean "user hasn't configured this optional field" — the first
64
+ # because settings.yaml has `agent_id: ""`, the second because the
65
+ # key isn't in settings.yaml at all and halo's shell_exec
66
+ # substitute leaves the placeholder verbatim (its "fail loud"
67
+ # contract for required params). For an optional flag like this, we
68
+ # want graceful degrade, not a literal-as-value going to acp.
69
+ aid = (args.agent_id or "").strip()
70
+ if aid and not (aid.startswith("{{") and aid.endswith("}}")):
71
+ cmd += ["--agent-id", aid]
72
+
73
+ proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
74
+ stderr=subprocess.PIPE, text=True, bufsize=1)
75
+ assert proc.stdin and proc.stdout and proc.stderr
76
+
77
+ def _drain_stderr() -> None:
78
+ for line in proc.stderr:
79
+ sys.stderr.write(line)
80
+ threading.Thread(target=_drain_stderr, daemon=True).start()
81
+
82
+ next_id = 1
83
+ pending: dict[int, dict[str, Any]] = {}
84
+ chunks: list[str] = []
85
+
86
+ def send(method: str, params: dict[str, Any] | None = None) -> int:
87
+ nonlocal next_id
88
+ rid = next_id
89
+ next_id += 1
90
+ msg = {"jsonrpc": "2.0", "id": rid, "method": method}
91
+ if params is not None:
92
+ msg["params"] = params
93
+ proc.stdin.write(json.dumps(msg) + "\n")
94
+ proc.stdin.flush()
95
+ return rid
96
+
97
+ def wait_for(rid: int, deadline: float) -> dict[str, Any]:
98
+ while True:
99
+ if rid in pending:
100
+ return pending.pop(rid)
101
+ if time.time() > deadline:
102
+ proc.kill()
103
+ fail(f"timeout waiting for response to request {rid}", 124)
104
+ line = proc.stdout.readline()
105
+ if line == "":
106
+ rc = proc.wait(timeout=1)
107
+ fail(f"adapter exited prematurely (rc={rc})", rc or 1)
108
+ line = line.strip()
109
+ if not line:
110
+ continue
111
+ try:
112
+ msg = json.loads(line)
113
+ except json.JSONDecodeError:
114
+ sys.stderr.write(f"[ask-acp-agent] non-JSON: {line[:200]}\n")
115
+ continue
116
+ if "id" in msg and ("result" in msg or "error" in msg):
117
+ pending[msg["id"]] = msg
118
+ continue
119
+ method = msg.get("method")
120
+ if method == "session/update":
121
+ update = (msg.get("params") or {}).get("update") or {}
122
+ if update.get("sessionUpdate") == "agent_message_chunk":
123
+ text = (update.get("content") or {}).get("text", "")
124
+ if text:
125
+ chunks.append(text)
126
+
127
+ deadline = time.time() + args.timeout
128
+ try:
129
+ rid = send("initialize", {"protocolVersion": 1, "clientCapabilities": {}})
130
+ resp = wait_for(rid, deadline)
131
+ if "error" in resp:
132
+ fail(f"initialize failed: {resp['error']}")
133
+
134
+ if args.session_id:
135
+ # ACP session/load requires cwd + mcpServers (same as session/new);
136
+ # kiro-cli exits silently if they're missing.
137
+ load_params: dict[str, Any] = {"sessionId": args.session_id}
138
+ if args.kind in ("claude", "kiro"):
139
+ load_params["cwd"] = args.cwd or os.getcwd()
140
+ load_params["mcpServers"] = []
141
+ rid = send("session/load", load_params)
142
+ resp = wait_for(rid, deadline)
143
+ if "error" in resp:
144
+ fail(f"session/load failed: {resp['error']}")
145
+ session_id = args.session_id
146
+ else:
147
+ new_params = {"cwd": args.cwd or os.getcwd(), "mcpServers": []} if args.kind in ("claude", "kiro") else {}
148
+ rid = send("session/new", new_params)
149
+ resp = wait_for(rid, deadline)
150
+ if "error" in resp:
151
+ fail(f"session/new failed: {resp['error']}")
152
+ session_id = resp["result"]["sessionId"]
153
+
154
+ rid = send("session/prompt", {
155
+ "sessionId": session_id,
156
+ "prompt": [{"type": "text", "text": args.question}],
157
+ })
158
+ resp = wait_for(rid, deadline)
159
+ if "error" in resp:
160
+ fail(f"session/prompt failed: {resp['error']}")
161
+ stop_reason = resp["result"].get("stopReason", "unknown")
162
+
163
+ sys.stdout.write(f"SESSION: {session_id}\n---\n")
164
+ sys.stdout.write("".join(chunks).rstrip() + "\n")
165
+
166
+ if stop_reason != "end_turn":
167
+ sys.stderr.write(f"[ask-acp-agent] stopReason={stop_reason}\n")
168
+ finally:
169
+ try: proc.stdin.close()
170
+ except Exception: pass
171
+ try: proc.wait(timeout=5)
172
+ except subprocess.TimeoutExpired: proc.kill()
173
+
174
+
175
+ if __name__ == "__main__":
176
+ main()
@@ -0,0 +1,33 @@
1
+ displayName: Ask {{LABEL_DISPLAY}}
2
+ description: Forward a question to the {{LABEL_DISPLAY}} halo agent over ACP.
3
+
4
+ params:
5
+ - key: host
6
+ description: Remote halo server hostname or IP.
7
+ description_zh: 远端 halo server 主机名 / IP。
8
+ default: "{{HOST}}"
9
+
10
+ - key: port
11
+ description: Remote halo server port.
12
+ description_zh: 远端 halo server 端口。
13
+ default: "{{PORT}}"
14
+
15
+ - key: workspace
16
+ description: Absolute path of the remote workspace to drive.
17
+ description_zh: 要驱动的远端 workspace 绝对路径。
18
+ default: "{{WORKSPACE}}"
19
+
20
+ - key: label
21
+ description: Friendly name for the remote, shown when reporting back to the user.
22
+ description_zh: 报告给用户时使用的称呼。
23
+ default: "{{LABEL_DISPLAY}}"
24
+
25
+ - key: agent_id
26
+ description: Optional remote agent profile id. Blank = remote 'default'.
27
+ description_zh: 可选的远端 agent id,留空则用远端 default。
28
+ default: ""
29
+
30
+ - key: token
31
+ description: Web-channel token from the remote halo admin. Must be a full-access token if cross-workspace addressing is needed.
32
+ description_zh: 远端 halo 后台 Web channel token。若需跨 workspace 调用必须是 full 权限。
33
+ secret: true
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: agent
3
+ description: Create or update agent configurations (agent.yaml + AGENT.md). Activate when the user wants to add a new agent or change an existing agent's model / tools / skills / behavior.
4
+ command: /agent
5
+ verbs:
6
+ # builtin verbs — access is set in code (SUBCOMMAND_ROUTES); shown here for reference
7
+ - { name: list, builtin: true, desc: List usable agents }
8
+ - { name: switch, builtin: true, desc: Start a session with an agent }
9
+ - { name: desc, builtin: true, desc: Show an agent's model / tools / skills }
10
+ - { name: delete, builtin: true, requiresAccess: full, desc: Delete an agent (workspace or global) }
11
+ # skill verbs — access enforced from here
12
+ - { name: create, builtin: false, requiresAccess: full, desc: Create a new agent }
13
+ - { name: update, builtin: false, requiresAccess: full, desc: Modify an existing agent }
14
+ ---
15
+
16
+ # agent
17
+
18
+ Create or modify agent configurations. An agent lives in a directory holding
19
+ `agent.yaml` (model, tools, skills, context) and optional `AGENT.md` (behavior)
20
+ / `USER.md`.
21
+
22
+ This skill body handles **create** and **update** — the actions that need to
23
+ author files. `list` / `switch` / `desc` / `delete` are handled directly by the
24
+ `/agent` command and never reach here. The requested action is **`$1`** (full
25
+ args: `$ARGUMENTS`); if invoked by natural language, infer it from the request.
26
+
27
+ ## Locations & scope rules (read before create/update)
28
+
29
+ - `<workspace>/.halo/agents/<agent-id>/` — workspace-scoped
30
+ - `~/.halo/global/agents/<agent-id>/` — shared across all workspaces
31
+
32
+ A workspace agent **overrides** a global one with the same id at runtime.
33
+ Consequences you must apply:
34
+
35
+ - **Create**: ask (or infer from the request) which scope. Default to
36
+ **workspace** unless the user says "global" / "all workspaces". If creating a
37
+ workspace agent whose id already exists globally, say so — the new file will
38
+ shadow the global one in this workspace.
39
+ - **Update**: resolve which file is actually in effect FIRST — check the
40
+ workspace path, fall back to global. Edit the file that exists; if both
41
+ exist, edit the workspace one (it's the live one here) unless the user
42
+ explicitly says to change the global. Never edit global to fix behavior in
43
+ one workspace when a workspace copy shadows it — that edit would have no
44
+ effect here.
45
+ - **Built-in agents** (`default`, `executor`, `deep-executor`, internal
46
+ `__*__`): their global copies are force re-seeded on every server start —
47
+ direct edits to those global files are lost. To customize a built-in, copy
48
+ it to workspace scope and edit there.
49
+
50
+ ## Create
51
+
52
+ Generate `agent.yaml`:
53
+
54
+ ```yaml
55
+ name: <AgentName>
56
+ description: <one-line description shown in the agent picker>
57
+ priority: 50 # higher = appears earlier in the picker
58
+ model:
59
+ provider: aws-bedrock-claude-invoke
60
+ id: global.anthropic.claude-sonnet-4-6
61
+ endpoint: https://bedrock-runtime.us-east-1.amazonaws.com
62
+ promptCaching: 1h # optional: Bedrock prompt caching ('1h' or '5m')
63
+ thinking:
64
+ enabled: true
65
+ effort: medium # off | low | medium | high | xhigh
66
+ system_prompt: >
67
+ <Brief one-paragraph role statement. Keep behavioral detail in AGENT.md.>
68
+ context:
69
+ maxTokens: 200000
70
+ compressAt: 0.8 # auto-compact at 80%
71
+ tools:
72
+ - file_read
73
+ - file_write
74
+ - file_edit
75
+ - file_list
76
+ - shell_exec
77
+ - grep
78
+ - glob
79
+ - web_fetch
80
+ - view_image
81
+ # Session/delegation tools (start_session, query_session, query_agent, …) are
82
+ # NOT listed here — they're granted automatically by a non-empty `team`
83
+ # (see below). Listing them in `tools:` has no effect.
84
+ team: # optional — presence is the delegation switch
85
+ - executor # add the agent ids this one may spawn
86
+ skills:
87
+ - <skill-id>
88
+ ```
89
+
90
+ **Required:** `model.provider` (must match an id in `~/.halo/global/models/<provider>.yaml`),
91
+ `model.id`, `model.endpoint` — the server rejects sessions missing any of these.
92
+
93
+ **Model choice:** Sonnet 4.6 is the default workhorse. Opus 4.8
94
+ (`global.anthropic.claude-opus-4-8`) when the task visibly needs deeper
95
+ reasoning (costs more — not the default). Haiku 4.5 for fast, lightweight work.
96
+
97
+ **Thinking:** `medium` is a good default; `high`/`xhigh` only for deep-reasoning
98
+ agents; `enabled: false` to turn it off entirely.
99
+
100
+ **Delegation (`team`):** the `team` field is the on/off switch for delegation,
101
+ not just a filter. A **non-empty** `team: [agent-id, ...]` is what grants the
102
+ agent the whole session-tool bundle — `start_session`, `query_session`,
103
+ `interrupt_session`, `stop_session`, `archive_session`, `session_list`,
104
+ `get_session_output`, `query_agent` — plus the injected roster. The listed ids
105
+ are also exactly who it can reach: the roster, `start_session`, and
106
+ `query_agent` all honor the list. **Omit `team` (or leave it empty) and the
107
+ agent cannot delegate at all** — no session tools, no roster. There's no way to
108
+ hand-list the session tools under `tools:` anymore; the capability rides
109
+ entirely on `team`. To let an agent spawn parallel copies of itself, include
110
+ its own id in the list (as the `default` agent does with `default`).
111
+
112
+ Then write **`AGENT.md`** (behavior doc, read as part of the system prompt) —
113
+ keep `system_prompt` brief and put the detail here:
114
+
115
+ 1. Role and scope — what it's for, what it isn't
116
+ 2. When to use which tool — concrete decision rules
117
+ 3. Output style — format, tone, length
118
+ 4. Constraints — what it must not do, when to ask
119
+
120
+ Optionally write **`USER.md`** if the agent needs to know who the user is
121
+ (name, role, language, domain). Workspace `USER.md` overrides global.
122
+
123
+ ## Update
124
+
125
+ `file_read` the target `agent.yaml`, change only the requested fields, write it
126
+ back. Common edits: switch `model.id`, adjust `thinking.effort`, add/remove a
127
+ tool or skill, rewrite `system_prompt` (or edit `AGENT.md` for behavior). Don't
128
+ rewrite untouched fields. Confirm the agent id and scope first if ambiguous.