@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.
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/admin-out/404.html +1 -0
- package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_buildManifest.js +1 -0
- package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_ssgManifest.js +1 -0
- package/admin-out/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js +18 -0
- package/admin-out/_next/static/chunks/200.3b8f8a74f005a134.js +1 -0
- package/admin-out/_next/static/chunks/220.3dc41cd6269b88e6.js +1 -0
- package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +1 -0
- package/admin-out/_next/static/chunks/304eda7d.b1ee7f523653e027.js +1 -0
- package/admin-out/_next/static/chunks/330.3b784cf0dcd69163.js +1 -0
- package/admin-out/_next/static/chunks/442.f4302087bddb1ec1.js +1 -0
- package/admin-out/_next/static/chunks/443.d1b289781793f8de.js +25 -0
- package/admin-out/_next/static/chunks/503-7c6d9935df6e0cea.js +1 -0
- package/admin-out/_next/static/chunks/533.5f2d4bfe3d076095.js +1 -0
- package/admin-out/_next/static/chunks/579.a2718ddd395a2f3a.js +1 -0
- package/admin-out/_next/static/chunks/635-3dd0043ed0520b9d.js +1 -0
- package/admin-out/_next/static/chunks/65.859665ba6717967a.js +1 -0
- package/admin-out/_next/static/chunks/661.a4fe93f3f2a5e871.js +1 -0
- package/admin-out/_next/static/chunks/688.4e014ac6cfc7e152.js +1 -0
- package/admin-out/_next/static/chunks/868.0c95dfe283400a00.js +1 -0
- package/admin-out/_next/static/chunks/app/_not-found/page-8e35399c8fd5016d.js +1 -0
- package/admin-out/_next/static/chunks/app/layout-58f773336e6fce61.js +1 -0
- package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +1 -0
- package/admin-out/_next/static/chunks/db534642.09d3c650ff587175.js +1 -0
- package/admin-out/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js +1 -0
- package/admin-out/_next/static/chunks/framework-c3031ae54a63fe16.js +1 -0
- package/admin-out/_next/static/chunks/main-14b48e3c2588354f.js +1 -0
- package/admin-out/_next/static/chunks/main-app-483b436873410ddc.js +1 -0
- package/admin-out/_next/static/chunks/pages/_app-b2fc6e5f920d1b1a.js +1 -0
- package/admin-out/_next/static/chunks/pages/_error-f3696a8f3d67d150.js +1 -0
- package/admin-out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/admin-out/_next/static/chunks/webpack-8bd7ef88a6527feb.js +1 -0
- package/admin-out/_next/static/css/659eccb5db697b76.css +1 -0
- package/admin-out/_next/static/css/8fce4e1a54dac12a.css +3 -0
- package/admin-out/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- package/admin-out/_next/static/media/21350d82a1f187e9-s.woff2 +0 -0
- package/admin-out/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
- package/admin-out/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- package/admin-out/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- package/admin-out/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
- package/admin-out/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
- package/admin-out/favicon.ico +0 -0
- package/admin-out/icon.svg +18 -0
- package/admin-out/index.html +1 -0
- package/admin-out/index.txt +23 -0
- package/admin-out/monaco/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
- package/admin-out/monaco/vs/abap-D-t0cyap.js +1 -0
- package/admin-out/monaco/vs/apex-CcIm7xu6.js +1 -0
- package/admin-out/monaco/vs/assets/css.worker-HnVq6Ewq.js +93 -0
- package/admin-out/monaco/vs/assets/editor.worker-Be8ye1pW.js +26 -0
- package/admin-out/monaco/vs/assets/html.worker-B51mlPHg.js +470 -0
- package/admin-out/monaco/vs/assets/json.worker-DKiEKt88.js +58 -0
- package/admin-out/monaco/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
- package/admin-out/monaco/vs/azcli-BA0tQDCg.js +1 -0
- package/admin-out/monaco/vs/basic-languages/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/bat-C397hTD6.js +1 -0
- package/admin-out/monaco/vs/bicep-DF5aW17k.js +2 -0
- package/admin-out/monaco/vs/cameligo-plsz8qhj.js +1 -0
- package/admin-out/monaco/vs/clojure-Y2auQMzK.js +1 -0
- package/admin-out/monaco/vs/coffee-Bu45yuWE.js +1 -0
- package/admin-out/monaco/vs/cpp-CkKPQIni.js +1 -0
- package/admin-out/monaco/vs/csharp-CX28MZyh.js +1 -0
- package/admin-out/monaco/vs/csp-D8uWnyxW.js +1 -0
- package/admin-out/monaco/vs/css-CaeNmE3S.js +3 -0
- package/admin-out/monaco/vs/cssMode-CjiAH6dQ.js +1 -0
- package/admin-out/monaco/vs/cypher-DVThT8BS.js +1 -0
- package/admin-out/monaco/vs/dart-CmGfCvrO.js +1 -0
- package/admin-out/monaco/vs/dockerfile-CZqqYdch.js +1 -0
- package/admin-out/monaco/vs/ecl-30fUercY.js +1 -0
- package/admin-out/monaco/vs/editor/editor.main.css +1 -0
- package/admin-out/monaco/vs/editor/editor.main.js +5 -0
- package/admin-out/monaco/vs/editor.api-CalNCsUg.js +903 -0
- package/admin-out/monaco/vs/elixir-xjPaIfzF.js +1 -0
- package/admin-out/monaco/vs/flow9-DqtmStfK.js +1 -0
- package/admin-out/monaco/vs/freemarker2-Cz_sV6Md.js +3 -0
- package/admin-out/monaco/vs/fsharp-BOMdg4U1.js +1 -0
- package/admin-out/monaco/vs/go-D_hbi-Jt.js +1 -0
- package/admin-out/monaco/vs/graphql-CKUU4kLG.js +1 -0
- package/admin-out/monaco/vs/handlebars-OwglfO-1.js +1 -0
- package/admin-out/monaco/vs/hcl-DTaboeZW.js +1 -0
- package/admin-out/monaco/vs/html-Pa1xEWsY.js +1 -0
- package/admin-out/monaco/vs/htmlMode-Bz67EXwp.js +1 -0
- package/admin-out/monaco/vs/ini-CsNwO04R.js +1 -0
- package/admin-out/monaco/vs/java-CI4ZMsH9.js +1 -0
- package/admin-out/monaco/vs/javascript-PczUCGdz.js +1 -0
- package/admin-out/monaco/vs/jsonMode-DULH5oaX.js +7 -0
- package/admin-out/monaco/vs/julia-BwzEvaQw.js +1 -0
- package/admin-out/monaco/vs/kotlin-IUYPiTV8.js +1 -0
- package/admin-out/monaco/vs/language/css/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/html/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/json/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/typescript/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/less-C0eDYdqa.js +2 -0
- package/admin-out/monaco/vs/lexon-iON-Kj97.js +1 -0
- package/admin-out/monaco/vs/liquid-DqKjdPGy.js +1 -0
- package/admin-out/monaco/vs/loader.js +1368 -0
- package/admin-out/monaco/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
- package/admin-out/monaco/vs/lua-DtygF91M.js +1 -0
- package/admin-out/monaco/vs/m3-CsR4AuFi.js +1 -0
- package/admin-out/monaco/vs/markdown-C_rD0bIw.js +1 -0
- package/admin-out/monaco/vs/mdx-DEWtB1K5.js +1 -0
- package/admin-out/monaco/vs/mips-CiYP61RB.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-D2OdxNBt.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-DO3azKX8.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-EcChJV6a.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-qLAYrEOP.js +1 -0
- package/admin-out/monaco/vs/msdax-C38-sJlp.js +1 -0
- package/admin-out/monaco/vs/mysql-CdtbpvbG.js +1 -0
- package/admin-out/monaco/vs/nls.messages-loader.js +1 -0
- package/admin-out/monaco/vs/nls.messages.cs.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.de.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.es.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.fr.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.it.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.ja.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.js.js +10 -0
- package/admin-out/monaco/vs/nls.messages.ko.js.js +25 -0
- package/admin-out/monaco/vs/nls.messages.pl.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.pt-br.js.js +6 -0
- package/admin-out/monaco/vs/nls.messages.ru.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.tr.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.zh-cn.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.zh-tw.js.js +15 -0
- package/admin-out/monaco/vs/objective-c-CntZFaHX.js +1 -0
- package/admin-out/monaco/vs/pascal-r6kuqfl_.js +1 -0
- package/admin-out/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
- package/admin-out/monaco/vs/perl-DABw_TcH.js +1 -0
- package/admin-out/monaco/vs/pgsql-me_jFXeX.js +1 -0
- package/admin-out/monaco/vs/php-D_kh-9LK.js +1 -0
- package/admin-out/monaco/vs/pla-VfZjczW0.js +1 -0
- package/admin-out/monaco/vs/postiats-BBSzz8Pk.js +1 -0
- package/admin-out/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
- package/admin-out/monaco/vs/powershell-B-7ap1zc.js +1 -0
- package/admin-out/monaco/vs/protobuf-BmtuEB1A.js +2 -0
- package/admin-out/monaco/vs/pug-BRpRNeEb.js +1 -0
- package/admin-out/monaco/vs/python-Cr0UkIbn.js +1 -0
- package/admin-out/monaco/vs/qsharp-BzsFaUU9.js +1 -0
- package/admin-out/monaco/vs/r-f8dDdrp4.js +1 -0
- package/admin-out/monaco/vs/razor-BYAHOTkz.js +1 -0
- package/admin-out/monaco/vs/redis-fvZQY4PI.js +1 -0
- package/admin-out/monaco/vs/redshift-45Et0LQi.js +1 -0
- package/admin-out/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/admin-out/monaco/vs/ruby-CZO8zYTz.js +1 -0
- package/admin-out/monaco/vs/rust-Bfetafyc.js +1 -0
- package/admin-out/monaco/vs/sb-3GYllVck.js +1 -0
- package/admin-out/monaco/vs/scala-foMgrKo1.js +1 -0
- package/admin-out/monaco/vs/scheme-CHdMtr7p.js +1 -0
- package/admin-out/monaco/vs/scss-C1cmLt9V.js +3 -0
- package/admin-out/monaco/vs/shell-ClXCKCEW.js +1 -0
- package/admin-out/monaco/vs/solidity-MZ6ExpPy.js +1 -0
- package/admin-out/monaco/vs/sophia-DWkuSsPQ.js +1 -0
- package/admin-out/monaco/vs/sparql-AUGFYSyk.js +1 -0
- package/admin-out/monaco/vs/sql-32GpJSV2.js +1 -0
- package/admin-out/monaco/vs/st-CuDFIVZ_.js +1 -0
- package/admin-out/monaco/vs/swift-n-2HociN.js +3 -0
- package/admin-out/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/admin-out/monaco/vs/tcl-D74tq1nH.js +1 -0
- package/admin-out/monaco/vs/tsMode-CZz1Umrk.js +11 -0
- package/admin-out/monaco/vs/twig-C6taOxMV.js +1 -0
- package/admin-out/monaco/vs/typescript-DfOrAzoV.js +1 -0
- package/admin-out/monaco/vs/typespec-D-PIh9Xw.js +1 -0
- package/admin-out/monaco/vs/vb-Dyb2648j.js +1 -0
- package/admin-out/monaco/vs/wgsl-BhLXMOR0.js +298 -0
- package/admin-out/monaco/vs/workers-DcJshg-q.js +1 -0
- package/admin-out/monaco/vs/xml-CdsdnY8S.js +1 -0
- package/admin-out/monaco/vs/yaml-DYGvmE88.js +1 -0
- package/bin/halo.js +9 -0
- package/bundled-docs/design/agent.md +145 -0
- package/bundled-docs/design/architecture.md +270 -0
- package/bundled-docs/design/background-dispatch.md +151 -0
- package/bundled-docs/design/command.md +58 -0
- package/bundled-docs/design/cron.md +88 -0
- package/bundled-docs/design/evolution.md +383 -0
- package/bundled-docs/design/express-self.md +145 -0
- package/bundled-docs/design/feishu.md +224 -0
- package/bundled-docs/design/halo-city.md +112 -0
- package/bundled-docs/design/prompt-system.md +411 -0
- package/bundled-docs/design/session.md +321 -0
- package/bundled-docs/design/slack.md +212 -0
- package/bundled-docs/design/storage.md +578 -0
- package/bundled-docs/design/telegram.md +161 -0
- package/bundled-docs/design/web.md +222 -0
- package/bundled-docs/design/wechat.md +146 -0
- package/bundled-docs/design/ws.md +144 -0
- package/bundled-docs/dev/acp-adapter.md +337 -0
- package/bundled-docs/dev/add-channel.md +376 -0
- package/bundled-docs/dev/add-model-provider.md +386 -0
- package/bundled-docs/dev/api.md +686 -0
- package/bundled-docs/dev/deploy.md +198 -0
- package/bundled-docs/dev/desktop-packaging.md +327 -0
- package/bundled-docs/dev/env.md +158 -0
- package/bundled-docs/dev/logging.md +85 -0
- package/bundled-docs/dev/previews.md +225 -0
- package/bundled-docs/dev/tools.md +331 -0
- package/bundled-docs/guide/README.md +29 -0
- package/bundled-docs/guide/agents.md +264 -0
- package/bundled-docs/guide/channels/README.md +62 -0
- package/bundled-docs/guide/channels/acp.md +150 -0
- package/bundled-docs/guide/channels/feishu.md +154 -0
- package/bundled-docs/guide/channels/slack.md +156 -0
- package/bundled-docs/guide/channels/telegram.md +112 -0
- package/bundled-docs/guide/channels/web.md +149 -0
- package/bundled-docs/guide/channels/wechat.md +110 -0
- package/bundled-docs/guide/chat.md +112 -0
- package/bundled-docs/guide/cli.md +197 -0
- package/bundled-docs/guide/getting-started.md +93 -0
- package/bundled-docs/guide/secrets-and-credentials.md +161 -0
- package/bundled-docs/guide/sessions.md +104 -0
- package/bundled-docs/guide/skills.md +302 -0
- package/bundled-docs/guide/testing-agents-and-skills.md +136 -0
- package/bundled-docs/guide/workspace.md +131 -0
- package/bundled-docs/plans/cron-tasks.md +362 -0
- package/bundled-docs/plans/design-suggestions.md +203 -0
- package/bundled-docs/plans/issue-session-deep-nesting.md +208 -0
- package/bundled-docs/plans/launch-plan.md +111 -0
- package/bundled-docs/plans/md-and-agent-refactor.md +197 -0
- package/bundled-docs/plans/remote-mac-worker.md +196 -0
- package/bundled-docs/plans/roadmap.md +40 -0
- package/bundled-docs/plans/self-evolution.md +1379 -0
- package/bundled-docs/plans/todo.md +32 -0
- package/bundled-docs/plans/weixin-channel.md +255 -0
- package/bundled-docs/requirements/agent.md +92 -0
- package/bundled-docs/requirements/chat.md +81 -0
- package/bundled-docs/requirements/command.md +130 -0
- package/bundled-docs/requirements/editor.md +82 -0
- package/bundled-docs/requirements/explorer.md +89 -0
- package/bundled-docs/requirements/overview.md +62 -0
- package/bundled-docs/requirements/session.md +73 -0
- package/bundled-docs/requirements/settings.md +224 -0
- package/bundled-docs/requirements/skill.md +125 -0
- package/bundled-docs/requirements/source-control.md +82 -0
- package/bundled-docs/requirements/terminal.md +118 -0
- package/bundled-docs/requirements/workspace.md +80 -0
- package/bundled-docs/test/docs-seeding.md +101 -0
- package/bundled-docs/test/session.md +374 -0
- package/dist/index.js +26750 -0
- package/package.json +67 -0
- package/templates/INSTRUCTIONS.md +25 -0
- package/templates/agents/__apply_agent__/AGENT.md +229 -0
- package/templates/agents/__apply_agent__/agent.yaml +30 -0
- package/templates/agents/__evo_agent__/AGENT.md +323 -0
- package/templates/agents/__evo_agent__/agent.yaml +29 -0
- package/templates/agents/__score__/AGENT.md +177 -0
- package/templates/agents/__score__/agent.yaml +29 -0
- package/templates/agents/deep-executor/AGENT.md +60 -0
- package/templates/agents/deep-executor/agent.yaml +25 -0
- package/templates/agents/default/AGENT.md +38 -0
- package/templates/agents/default/agent.yaml +51 -0
- package/templates/agents/executor/AGENT.md +38 -0
- package/templates/agents/executor/agent.yaml +25 -0
- package/templates/aliases.yaml +26 -0
- package/templates/canvas/self.html +660 -0
- package/templates/config.yaml +69 -0
- package/templates/models/anthropic.yaml +47 -0
- package/templates/models/aws-bedrock-claude-invoke.yaml +156 -0
- package/templates/models/aws-bedrock-mantle.yaml +89 -0
- package/templates/models/deepseek.yaml +62 -0
- package/templates/models/doubao.yaml +137 -0
- package/templates/models/hunyuan.yaml +56 -0
- package/templates/models/kimi.yaml +41 -0
- package/templates/models/mimo-token-plan-china.yaml +61 -0
- package/templates/models/minimax.yaml +80 -0
- package/templates/models/openai.yaml +51 -0
- package/templates/models/qwen.yaml +78 -0
- package/templates/optional-skills/tavily-web-search/SKILL.md +91 -0
- package/templates/optional-skills/tavily-web-search/config.yaml +10 -0
- package/templates/prompts/all/TOOL_GUIDELINES.md +35 -0
- package/templates/prompts/all/TOOL_SHELL.md +4 -0
- package/templates/prompts/all/TOOL_SHELL.windows.md +7 -0
- package/templates/prompts/bootstrap/BOOTSTRAP.md +26 -0
- package/templates/prompts/root/WORKSPACE_MEMORY.md +11 -0
- package/templates/schema.sql +30 -0
- package/templates/skills/acp/SKILL.md +236 -0
- package/templates/skills/acp/templates/SKILL.md.tmpl +74 -0
- package/templates/skills/acp/templates/ask.py +176 -0
- package/templates/skills/acp/templates/config.yaml.tmpl +33 -0
- package/templates/skills/agent/SKILL.md +128 -0
- package/templates/skills/aws-knowledge/SKILL.md +82 -0
- package/templates/skills/aws-knowledge/scripts/aws_kb.py +61 -0
- package/templates/skills/cron/SKILL.md +217 -0
- package/templates/skills/cron/manage-cron.py +434 -0
- package/templates/skills/halo/SKILL.md +98 -0
- package/templates/skills/nova-web-search/SKILL.md +65 -0
- package/templates/skills/self/SKILL.md +139 -0
- package/templates/skills/send-file/SKILL.md +28 -0
- package/templates/skills/send-file/feishu.md +16 -0
- package/templates/skills/send-file/slack.md +7 -0
- package/templates/skills/send-file/telegram.md +9 -0
- package/templates/skills/send-file/web.md +6 -0
- package/templates/skills/send-file/wechat.md +8 -0
- package/templates/skills/skill/SKILL.md +131 -0
- package/templates/skills/workspace/SKILL.md +28 -0
- package/templates/skills/workspace/setup-tidy.md +265 -0
- package/templates/skills/workspace/share.md +187 -0
- 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.
|