@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,62 @@
|
|
|
1
|
+
# Channel Onboarding
|
|
2
|
+
|
|
3
|
+
Halo lets users talk to the same agent from many places — a browser, a phone IM, an IDE. Each "place" is a **channel**. Every channel maps a remote identity (a chat id, a token, a JSON-RPC peer) to a workspace + access level on the server.
|
|
4
|
+
|
|
5
|
+
This folder has one onboarding guide per channel. Pick the one you want and follow it end-to-end:
|
|
6
|
+
|
|
7
|
+
| Channel | Best for | What you set up | Transport |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| [Web](web.md) | Browsers, custom HTTP clients, any frontend you control | Auto-generated token bound to a workspace | HTTP + SSE |
|
|
10
|
+
| [Telegram](telegram.md) | Personal use on phone, small whitelisted teams | BotFather token | Long-poll |
|
|
11
|
+
| [Slack](slack.md) | Team workspaces (multi-user, channels, threads) | Slack App + Socket Mode (`xoxb-` + `xapp-` tokens) | WebSocket (Socket Mode) |
|
|
12
|
+
| [Feishu / Lark](feishu.md) | Chinese enterprise teams | Feishu open-platform App (`appId` + `appSecret`) + long connection | WebSocket (long-connect) |
|
|
13
|
+
| [WeChat](wechat.md) | China-side personal use; mobile-first | iLink-style bot, login by scanning a QR | Long-poll |
|
|
14
|
+
| [ACP](acp.md) | Claude Code (or other ACP clients) driving a remote halo | Web token + adapter CLI | JSON-RPC over stdio |
|
|
15
|
+
|
|
16
|
+
## How channels are wired
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌── ws/ (admin panel)
|
|
20
|
+
├── channels/web/ ── HTTP + SSE
|
|
21
|
+
halo server ─────────┤── channels/telegram/ ── grammy long-poll
|
|
22
|
+
(port 9527) ├── channels/slack/ ── Socket Mode wss
|
|
23
|
+
├── channels/feishu/ ── long-connect wss
|
|
24
|
+
└── channels/wechat/ ── ilinkai long-poll
|
|
25
|
+
↓
|
|
26
|
+
SessionManager (per workspace)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Each channel handler is a thin adapter between its native protocol and halo's `SessionManager`. Slash commands (`/session` `/agent` `/skill` `/workspace` `/cron` `/acp` `/evo` `/help`) are shared across all of them — see `channels/shared/commands.ts`.
|
|
30
|
+
|
|
31
|
+
The ACP adapter is **not** a channel — it's a stdio bridge that translates ACP JSON-RPC into the web channel's HTTP+SSE. Counted here only because users go through the same "set up an account, get a token" flow.
|
|
32
|
+
|
|
33
|
+
## Account fields at a glance
|
|
34
|
+
|
|
35
|
+
What the admin UI actually asks you for. Auto-filled fields (botUsername, botUserId, teamId, botOpenId) are not listed because the server resolves them from the credentials you paste.
|
|
36
|
+
|
|
37
|
+
| Channel | Required | Optional | How to get the credentials |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| Web | Workspace path | Label, access level, language | None — admin generates a token on Create |
|
|
40
|
+
| Telegram | Bot token, workspace path | Label, access level, language, allowed users | BotFather `/newbot` |
|
|
41
|
+
| Slack | Bot token (`xoxb-`), App token (`xapp-`), workspace path | Label, access level, language | api.slack.com → Create App → install + Socket Mode |
|
|
42
|
+
| Feishu | App ID, App Secret, workspace path | Verification token, encrypt key, label, access level, language | open.feishu.cn → Create App + add bot capability |
|
|
43
|
+
| WeChat | Workspace path (set before scanning) | Label, access level, language | QR scan in admin UI; bot token comes back from the scan |
|
|
44
|
+
|
|
45
|
+
## Access level
|
|
46
|
+
|
|
47
|
+
Every channel account carries an `accessLevel`:
|
|
48
|
+
|
|
49
|
+
- `readonly` (default) — sandboxed, workspace mounted read-only, secrets dir not mounted, only read-only tools (`file_read`, `view_image`, `file_list`, `grep`, `glob`) injected
|
|
50
|
+
- `workspace` — sandboxed, workspace mounted read-write, secrets dir not mounted
|
|
51
|
+
- `full` — no sandbox, all tools, can `/workspace <path>` to another workspace
|
|
52
|
+
|
|
53
|
+
When in doubt start at `readonly` and raise it later. The setting is on the **account row**, so a single Slack workspace can have one `readonly` bot account in #general and a separate `full` account in #ops.
|
|
54
|
+
|
|
55
|
+
## Where to go next
|
|
56
|
+
|
|
57
|
+
- Already know which channel you want → click the row above
|
|
58
|
+
- New to halo and just want to chat in a browser → [Web](web.md)
|
|
59
|
+
- Want to run halo from your IDE (Claude Code etc.) → [ACP](acp.md)
|
|
60
|
+
- Setting up a team workspace → [Slack](slack.md) or [Feishu](feishu.md)
|
|
61
|
+
|
|
62
|
+
For the design rationale and protocol details (not user-facing), see [docs/design/](../../design/) — `wechat.md`, `telegram.md`, `web.md`. Slack and Feishu design docs aren't written yet; the source of truth is the code under `packages/server/src/channels/{slack,feishu}/`.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# ACP (Agent Client Protocol)
|
|
2
|
+
|
|
3
|
+
Drive a halo server from any [ACP](https://github.com/zed-industries/agent-client-protocol)-speaking client. The most common use is **Claude Code on a developer's laptop, talking to a halo agent on EC2** — but any ACP client works.
|
|
4
|
+
|
|
5
|
+
ACP isn't a channel of its own. The `halo acp` command is a **stdio bridge** that translates ACP JSON-RPC into the Web channel's HTTP+SSE. So setup = "create a Web account, then point an adapter at it."
|
|
6
|
+
|
|
7
|
+
## When to reach for it
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
[Mac] [EC2]
|
|
11
|
+
Claude Code halo server (port 9527)
|
|
12
|
+
│ ACP / JSON-RPC over stdio │
|
|
13
|
+
▼ │
|
|
14
|
+
halo acp adapter ────HTTP/SSE────────────────▶│
|
|
15
|
+
│
|
|
16
|
+
halo agent
|
|
17
|
+
(workspace files
|
|
18
|
+
live on EC2)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Use ACP when:
|
|
22
|
+
- You want to use Claude Code locally as the chat UI but the agent's tools / workspace live on a remote machine
|
|
23
|
+
- You want one halo server to delegate to **another** halo server on a different host (the `/acp add` flow below)
|
|
24
|
+
- You're integrating with any tool that already speaks ACP
|
|
25
|
+
|
|
26
|
+
If you just want a browser UI, use the [Web](web.md) channel directly — ACP is overkill.
|
|
27
|
+
|
|
28
|
+
## Step 1 — Provision a Web token
|
|
29
|
+
|
|
30
|
+
ACP rides on the Web channel. Follow [Web channel onboarding](web.md) Steps 1-2 to get a token. **Pick `full` access** if you need multi-workspace support — `readonly` and `workspace` tokens can't override the workspace per request, so each adapter is locked to one workspace.
|
|
31
|
+
|
|
32
|
+
Save:
|
|
33
|
+
- **Token** (the `…` from Web admin)
|
|
34
|
+
- **Server host + port** (e.g. `localhost:9527`, or `ec2-1-2-3-4.compute.amazonaws.com:9527`)
|
|
35
|
+
- **Workspace path** on the server (absolute, e.g. `/home/ubuntu/my-project`)
|
|
36
|
+
|
|
37
|
+
## Step 2 — Launch the adapter from your ACP client
|
|
38
|
+
|
|
39
|
+
The adapter is published as the `halo acp` subcommand in `@turmind/halo` (the same npm package that ships `halo server`).
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
halo acp \
|
|
43
|
+
--host my-ec2-or-localhost \
|
|
44
|
+
--port 9527 \
|
|
45
|
+
--token <web-token-from-step-1> \
|
|
46
|
+
--workspace /abs/path/on/server \
|
|
47
|
+
--agent-id default # optional; defaults to 'default'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Flag | Required | Notes |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `--host` | yes | Halo server hostname / IP |
|
|
53
|
+
| `--port` | yes | Halo server port |
|
|
54
|
+
| `--token` | yes | Web-channel token. `full` access required for multi-workspace use |
|
|
55
|
+
| `--workspace` | yes | Absolute server-side path for this adapter |
|
|
56
|
+
| `--agent-id` | no | Halo agent profile to use when ACP `session/new` creates a fresh session. Default `default` |
|
|
57
|
+
|
|
58
|
+
Each adapter process binds to **one** workspace. To drive multiple workspaces concurrently from one token, run multiple adapter processes — one per workspace.
|
|
59
|
+
|
|
60
|
+
The adapter reads/writes JSON-RPC frames on **stdin / stdout** (one message per line, no LSP-style framing). **Stderr** is reserved for human-readable diagnostics — never parse it.
|
|
61
|
+
|
|
62
|
+
### Wiring into Claude Code
|
|
63
|
+
|
|
64
|
+
Claude Code lets you register a custom agent. Use its `claude-code config agent add` command (see Claude Code's own docs for the exact incantation) and point it at the `halo acp …` invocation above.
|
|
65
|
+
|
|
66
|
+
After registration, you can launch a session in Claude Code that streams to the remote halo server transparently — Claude Code thinks it's talking to a local agent, halo thinks it's talking to a Web-channel client.
|
|
67
|
+
|
|
68
|
+
## Step 3 — (Halo-to-halo) bind a remote with `/acp add`
|
|
69
|
+
|
|
70
|
+
The most common use of this adapter isn't a third-party ACP client — it's **another halo agent** delegating out to a remote halo workspace. Halo ships a builtin `acp` skill for this (slash command `/acp`, full access). Type `/acp add` — or just ask in chat (e.g. "add an ACP binding to my other halo").
|
|
71
|
+
|
|
72
|
+
It walks you through `(label, host, port, workspace, token)` and **stamps out a new skill** named `ask-<label>` containing:
|
|
73
|
+
|
|
74
|
+
- `SKILL.md` — slash command `/ask-<label>`, instructions tailored to this remote
|
|
75
|
+
- `config.yaml` — declares the binding's params so admin Settings shows a form
|
|
76
|
+
- `ask.py` — bundled JSON-RPC ↔ stdio helper (one copy per binding, intentional — keeps `/workspace share` bundles self-contained)
|
|
77
|
+
|
|
78
|
+
It also writes the connection values into `settings.yaml` (workspace or global, you pick).
|
|
79
|
+
|
|
80
|
+
After install, the local agent can do `shell_exec: python3 .../ask-<label>/ask.py "<question>"` and halo's runtime substitutes the configured values. **Multiple bindings coexist** — each gets its own slash command, settings namespace, and Admin Settings page. `/acp list` shows the bindings you've generated.
|
|
81
|
+
|
|
82
|
+
To remove a binding: `/acp remove` (deletes the skill directory and points out the leftover `ask-<label>:` block in `settings.yaml`).
|
|
83
|
+
|
|
84
|
+
This is the **only** supported way to set up a halo-to-halo binding — there's no generic single-target `ask-acp-agent` skill, because per-binding namespaces (one token-host-workspace triple per skill id) are required for multi-remote use.
|
|
85
|
+
|
|
86
|
+
### Direct asks — `/acp kiro` / `/acp claude`
|
|
87
|
+
|
|
88
|
+
Bindings are for **remote halo servers**. For agents on the **same machine** no binding is needed — the `acp` skill talks to them directly, zero config:
|
|
89
|
+
|
|
90
|
+
- `/acp claude <question>` — local Claude Code (via npm `@agentclientprotocol/claude-agent-acp`)
|
|
91
|
+
- `/acp kiro <question>` — local Kiro (via `kiro-cli acp`)
|
|
92
|
+
|
|
93
|
+
The question is passed verbatim — including the other agent's own slash commands (e.g. `/acp kiro /model <full-model-id>` switches Kiro's model). Both coexist with `/ask-<label>` bindings.
|
|
94
|
+
|
|
95
|
+
## ACP method coverage
|
|
96
|
+
|
|
97
|
+
| Method | Implemented | Notes |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `initialize` | ✅ | Declares `protocolVersion: 1`, `loadSession: true`, no auth methods |
|
|
100
|
+
| `authenticate` | ✅ (no-op) | Token is passed via launch flags; ACP-side auth has nothing to do |
|
|
101
|
+
| `session/new` | ✅ | Mints a sessionId of shape `web_acp_<ts>_<rand>`. Halo creates the row lazily on first `/web/chat` |
|
|
102
|
+
| `session/load` | ✅ | Verifies the supplied id still exists on the halo server, then registers it locally |
|
|
103
|
+
| `session/prompt` | ✅ | Forwards text + image content blocks. Resource / embedded-context blocks log a stderr warning and are dropped (see "Reverse fs" below) |
|
|
104
|
+
| `session/cancel` | ✅ | Aborts the in-flight HTTP/SSE stream and POSTs `/web/stop` |
|
|
105
|
+
| Reverse `fs/*` | ❌ | See "Reverse fs" below |
|
|
106
|
+
| Reverse `terminal/*` | ❌ | Same reasoning |
|
|
107
|
+
| `requestPermission` | ❌ | Halo has its own access-level system at the channel-account level |
|
|
108
|
+
|
|
109
|
+
### Session id model
|
|
110
|
+
|
|
111
|
+
ACP `sessionId` **is** the halo session id — there's no extra mapping layer. When `session/new` mints `web_acp_<ts>_<rand>`, that exact string is what gets created in `agent_sessions` (lazily on first `/web/chat`). The ACP client persists ids itself; the adapter holds no on-disk state. Losing the adapter's in-memory map on restart is harmless because the conversation lives on the halo server.
|
|
112
|
+
|
|
113
|
+
The ACP client is the source of truth for "which sessions are mine" — a Mac-side Claude Code knows about *its* sessions, the EC2-side halo agent doesn't need to enumerate them.
|
|
114
|
+
|
|
115
|
+
## Multi-workspace
|
|
116
|
+
|
|
117
|
+
A Web-channel token in halo is bound to one workspace at the database level. The adapter works around this by sending `workspace` + `sessionId` overrides on every Web-channel call:
|
|
118
|
+
|
|
119
|
+
- `/api/web/chat`, `/api/web/stop`, `/api/web/history`, `/api/web/subscribe` accept `workspace=<path>` and `sessionId=<id>` overrides
|
|
120
|
+
- The server gates the `workspace` override on `accessLevel === 'full'` — readonly / workspace tokens cannot escape their bound workspace
|
|
121
|
+
- The adapter sends both fields on every request, so concurrent adapters on the same token but different `--workspace` flags don't step on each other
|
|
122
|
+
|
|
123
|
+
**Caveat:** sending `/workspace switch <path>` from inside an ACP session still mutates the bound workspace at the **db level** (changing the account's default for everybody using that token). Avoid `/workspace switch` from an adapter — use `--workspace` at adapter launch instead.
|
|
124
|
+
|
|
125
|
+
## Reverse fs (parked)
|
|
126
|
+
|
|
127
|
+
ACP optionally lets the agent (running on the server) request files from the client (the laptop on the user's side) via `fs/read_text_file` and `fs/write_text_file`. We don't implement this in v1 because:
|
|
128
|
+
|
|
129
|
+
1. The Web channel is HTTP + SSE — a one-way stream. Reverse fs would need WebSocket or long-poll
|
|
130
|
+
2. Halo agents currently use `file_read` / `file_write` against the **server's** workspace; supporting reverse fs would need a parallel toolset
|
|
131
|
+
|
|
132
|
+
For now: if you want the agent to see a Mac-side file, paste it into the prompt. The adapter logs a stderr warning when it sees a `resource` content block in `session/prompt`, so the failure mode is obvious.
|
|
133
|
+
|
|
134
|
+
## Common problems
|
|
135
|
+
|
|
136
|
+
| Symptom | Cause / fix |
|
|
137
|
+
|---|---|
|
|
138
|
+
| Adapter exits immediately on launch | Missing required flag, or the token / host are wrong. Check stderr |
|
|
139
|
+
| `401` on first prompt | Token typo, or token was deleted from admin |
|
|
140
|
+
| `403` when launching with `--workspace /some/other/path` | Token is `readonly` / `workspace` access — use a `full` token or omit `--workspace` |
|
|
141
|
+
| Tool calls don't appear in Claude Code | Expected — halo doesn't translate every event back as ACP `tool_call`. See [docs/dev/acp-adapter.md](../../dev/acp-adapter.md) for the full mapping |
|
|
142
|
+
| Two `session/prompt` calls on the same id, second hangs | Halo queues messages when a session is busy; ACP adapter ends the response with `[queued]`. Wait for the first to finish |
|
|
143
|
+
| `/workspace switch <path>` worked but other tokens broke | You changed the db-level default. Switch back with another `/workspace switch`, or stop using slash commands from the adapter |
|
|
144
|
+
|
|
145
|
+
## Reference
|
|
146
|
+
|
|
147
|
+
- CLI source: `packages/acp-adapter/src/index.ts`
|
|
148
|
+
- Adapter source: `packages/acp-adapter/src/adapter.ts`, `halo-client.ts`, `jsonrpc.ts`
|
|
149
|
+
- Full design + protocol mapping: [../../dev/acp-adapter.md](../../dev/acp-adapter.md) — read this before changing the wire format
|
|
150
|
+
- Web channel: [web.md](web.md) — the underlying transport
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Feishu (Lark)
|
|
2
|
+
|
|
3
|
+
Talk to a halo agent from Feishu — DM the bot, or `@`-mention it in any group it's been added to. Halo uses Feishu's **long-connect** event delivery (an outbound WebSocket from the server to Feishu's open platform), so **no public webhook URL is required**.
|
|
4
|
+
|
|
5
|
+
## What you'll end up with
|
|
6
|
+
|
|
7
|
+
- A self-built Feishu app named (e.g.) `halo` published in your tenant
|
|
8
|
+
- Two credentials stored in halo: **App ID** (`cli_…`) and **App Secret**
|
|
9
|
+
- A bot account row pointing those credentials at one halo workspace
|
|
10
|
+
|
|
11
|
+
## Step 1 — Create a self-built app
|
|
12
|
+
|
|
13
|
+
1. Go to https://open.feishu.cn/app and click **创建应用 / Create App** → **自建应用 / Custom App** (do NOT pick "应用商店应用 / Marketplace App")
|
|
14
|
+
2. Fill name (e.g. `halo`), upload an icon, write a description
|
|
15
|
+
3. After creation you land on **凭证与基础信息 / Credentials and Basic Info**
|
|
16
|
+
|
|
17
|
+
## Step 2 — Copy App ID and App Secret
|
|
18
|
+
|
|
19
|
+
Same page (**凭证与基础信息**):
|
|
20
|
+
|
|
21
|
+
- **App ID** — value like `cli_a1b2c3d4`. Public-ish; goes into halo
|
|
22
|
+
- **App Secret** — click 显示 / Show. Treat like a password
|
|
23
|
+
|
|
24
|
+
You'll paste both into halo in Step 7.
|
|
25
|
+
|
|
26
|
+
## Step 3 — Enable bot capability
|
|
27
|
+
|
|
28
|
+
A Feishu app with no bot capability can't receive IM events at all.
|
|
29
|
+
|
|
30
|
+
1. Left sidebar → **添加应用能力 / Add App Capabilities** (or 应用能力 in the new UI)
|
|
31
|
+
2. Find **机器人 / Bot** → click 启用 / Enable
|
|
32
|
+
3. Skip optional bot config (welcome message, menu, etc.) for v1
|
|
33
|
+
|
|
34
|
+
## Step 4 — Apply for API permissions
|
|
35
|
+
|
|
36
|
+
Left sidebar → **权限管理 / Permissions** → **API 权限 / API Permissions** → search and **申请 / Apply** for each:
|
|
37
|
+
|
|
38
|
+
**Required:**
|
|
39
|
+
- `im:message` — receive messages
|
|
40
|
+
- `im:message:send_as_bot` — send messages as the bot
|
|
41
|
+
- `im:resource` — download user-uploaded images / files
|
|
42
|
+
- `im:chat` — read group info (needed when the bot is added to a group)
|
|
43
|
+
|
|
44
|
+
**For target search in cron jobs** (lets you `@`-search users / chats in the admin's cron form):
|
|
45
|
+
- `im:chat:readonly`
|
|
46
|
+
- `contact:user.id:readonly` (optional, for ID lookups)
|
|
47
|
+
- `contact:user.base:readonly` (optional, for name / email lookups)
|
|
48
|
+
|
|
49
|
+
After apply, permissions go to **待审批 / Pending Approval**. If you're a tenant admin, approve them yourself in **管理员后台 / Admin Console** → **应用管理 / App Management** → the app → **权限审批 / Permission Approvals**.
|
|
50
|
+
|
|
51
|
+
## Step 5 — Switch event delivery to long-connect
|
|
52
|
+
|
|
53
|
+
Halo uses long-connect, **not** webhook callbacks. This is the most error-prone step in the whole flow.
|
|
54
|
+
|
|
55
|
+
1. Left sidebar → **事件与回调 / Events & Callbacks** → **事件配置 / Event Config**
|
|
56
|
+
2. Look for the **mode toggle** at the top of this page:
|
|
57
|
+
- **长连接 / Long Connection** ← pick this
|
|
58
|
+
- 回调地址 / Callback URL — webhook mode, do not pick
|
|
59
|
+
3. With long-connect selected, the Request URL / Verification Token / Encrypt Key fields become **optional / unused**. You can leave them blank.
|
|
60
|
+
|
|
61
|
+
> If you don't see a mode toggle, your tenant might be on an older console UI. In that case the "configure long-connect" entry is somewhere on the same page; if you can't find it, ping the user — Feishu has reshuffled this UI several times.
|
|
62
|
+
|
|
63
|
+
4. Same page → scroll to **添加事件 / Add Events** → search and add `im.message.receive_v1` (the v2 event payload, named with a v1 suffix for backwards compat — yes, it's confusing, this is the right one)
|
|
64
|
+
|
|
65
|
+
Save changes.
|
|
66
|
+
|
|
67
|
+
## Step 6 — Publish a version (mandatory)
|
|
68
|
+
|
|
69
|
+
> This is the step everyone forgets. Without publishing, the bot has zero presence in Feishu — you can't find it in search, can't add it to groups, can't DM it, can't receive events.
|
|
70
|
+
|
|
71
|
+
1. Left sidebar → **版本管理与发布 / Version Management** → **创建版本 / Create Version**
|
|
72
|
+
2. Bump the version number, write a one-line changelog ("initial setup")
|
|
73
|
+
3. **提交审核 / Submit for Review**
|
|
74
|
+
4. If you're a tenant admin: **管理员后台** → **应用管理** → the app → **审核中 / Pending** → approve
|
|
75
|
+
|
|
76
|
+
After approval the bot is visible in your Feishu tenant. Repeat this step **every time** you change permissions or events — Feishu doesn't honor changes until a new version is published and approved.
|
|
77
|
+
|
|
78
|
+
## Step 7 — Add the account in halo admin
|
|
79
|
+
|
|
80
|
+
Open halo admin → **Channels** → **Feishu** → **Add Account**:
|
|
81
|
+
|
|
82
|
+
| Field | Value |
|
|
83
|
+
|---|---|
|
|
84
|
+
| App ID | from Step 2 |
|
|
85
|
+
| App Secret | from Step 2 |
|
|
86
|
+
| Verification token | leave blank (long-connect doesn't use it) |
|
|
87
|
+
| Encrypt key | leave blank (long-connect doesn't encrypt the wire) |
|
|
88
|
+
| Workspace path | absolute path, e.g. `/home/ubuntu/my-project` |
|
|
89
|
+
| Label | optional |
|
|
90
|
+
| Access level | `readonly` (default), `workspace`, or `full` |
|
|
91
|
+
| Language | `en` or `zh` |
|
|
92
|
+
|
|
93
|
+
On submit halo calls Feishu's `tenant_access_token` + `bot/info` API to validate your credentials and auto-fill `botOpenId`. If that call fails the account isn't created and you'll see Feishu's error code inline (typical: `99991663` = wrong app secret, `99991668` = bot capability not enabled).
|
|
94
|
+
|
|
95
|
+
## Step 8 — Test it
|
|
96
|
+
|
|
97
|
+
In Feishu desktop / mobile:
|
|
98
|
+
|
|
99
|
+
1. Top-bar search → type the bot's name → click into the bot's profile → start a DM → send `hello` → expect a streamed reply
|
|
100
|
+
2. Add the bot to a group: open a group → 设置 → 群机器人 → add → `@halo hi` → expect a reply
|
|
101
|
+
|
|
102
|
+
If nothing happens, check halo server logs (`/tmp/halo-server.log`) for `[feishu]` lines.
|
|
103
|
+
|
|
104
|
+
## How halo handles inbound
|
|
105
|
+
|
|
106
|
+
- **P2P** (1:1 DMs) — every message routes to the bot
|
|
107
|
+
- **Groups** — only `@`-mentions wake the bot up
|
|
108
|
+
- **Threads** — Feishu's `root_id` is used as the session boundary; replies stay in-thread
|
|
109
|
+
- **Files** — images go to the LLM as multimodal content; other files are saved under `<workspace>/.halo/assets/feishu/inbound/<accountId>/<date>/`
|
|
110
|
+
- **Self-loop** — bot-authored events are dropped to prevent the bot replying to itself
|
|
111
|
+
|
|
112
|
+
## Slash commands
|
|
113
|
+
|
|
114
|
+
Same set as the other channels — type as plain text in a DM or thread:
|
|
115
|
+
|
|
116
|
+
| Command | Effect |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
|
|
119
|
+
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
120
|
+
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
121
|
+
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
122
|
+
| `/help` | List commands — object commands show only the verbs you can run |
|
|
123
|
+
|
|
124
|
+
## Cron jobs targeting Feishu
|
|
125
|
+
|
|
126
|
+
When a cron job is created from inside a Feishu chat, the dispatcher remembers the chat id and sends back there. To target a specific Feishu chat from the admin UI:
|
|
127
|
+
|
|
128
|
+
- Pick the Feishu target in the cron form
|
|
129
|
+
- Type a user name or chat name in the search box — autocomplete matches via the contacts / chat APIs
|
|
130
|
+
- Selected entries become explicit chat IDs on the cron run
|
|
131
|
+
|
|
132
|
+
Search depends on the contact and `im:chat:readonly` scopes from Step 4. If autocomplete returns nothing, the most likely cause is that the new permissions haven't been approved + republished yet (Step 6).
|
|
133
|
+
|
|
134
|
+
## Common problems
|
|
135
|
+
|
|
136
|
+
| Symptom | Cause / fix |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `99991663` on Add Account | App Secret wrong — copy it again from Step 2 |
|
|
139
|
+
| `99991668` on Add Account | Bot capability not enabled — go back to Step 3 |
|
|
140
|
+
| Bot exists but receives no events | Step 5 mode is set to webhook (not long-connect), or Step 6 wasn't done |
|
|
141
|
+
| Permission errors when sending images / reading group info | New scope was approved but app version wasn't republished — go to Step 6 |
|
|
142
|
+
| Can't find the bot in Feishu search | Step 6 wasn't done. Search results are tenant-wide and only show **published** apps |
|
|
143
|
+
| Multiple halo instances reply to the same message | Only one process should hold the long-connect. Check `~/.halo/global/server.pid` for the lock |
|
|
144
|
+
|
|
145
|
+
## Multi-tenant setup
|
|
146
|
+
|
|
147
|
+
Feishu apps are per-tenant (= per company / org account). To support a second Feishu tenant, create a separate self-built app under that tenant, repeat Steps 1-7, add a second account row in halo. Halo routes inbound events by `app_id` so multiple accounts coexist cleanly.
|
|
148
|
+
|
|
149
|
+
## Reference
|
|
150
|
+
|
|
151
|
+
- Code: `packages/server/src/channels/feishu/`
|
|
152
|
+
- Routes: `packages/server/src/routes/feishu.ts`
|
|
153
|
+
- Admin UI: `packages/admin/src/features/feishu/feishu-settings.tsx`
|
|
154
|
+
- API client: `packages/server/src/channels/feishu/api.ts` (long-connect open, message send, file download, optional webhook decrypt for legacy bodies)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Slack
|
|
2
|
+
|
|
3
|
+
Talk to a halo agent from a Slack workspace — DM the bot, or `@`-mention it in any channel where it's invited. Halo uses **Socket Mode** (an outbound WebSocket from the server to Slack), so **you don't need a public webhook URL** or an HTTPS reverse proxy.
|
|
4
|
+
|
|
5
|
+
## What you'll end up with
|
|
6
|
+
|
|
7
|
+
- A Slack App named (e.g.) `halo` installed in your workspace
|
|
8
|
+
- Two tokens stored in halo: a **Bot Token** (`xoxb-…`) for outbound API calls and an **App-Level Token** (`xapp-…`) for the Socket Mode connection
|
|
9
|
+
- A bot account row pointing those tokens at one halo workspace
|
|
10
|
+
|
|
11
|
+
## Step 1 — Create a Slack App
|
|
12
|
+
|
|
13
|
+
1. Go to https://api.slack.com/apps and click **Create New App** → **From scratch**
|
|
14
|
+
2. Name it (`halo` is fine), pick the workspace it should live in
|
|
15
|
+
3. You're now on the app's **Basic Information** page — keep this tab open, you'll come back to it for the App-Level Token
|
|
16
|
+
|
|
17
|
+
## Step 2 — Enable Socket Mode
|
|
18
|
+
|
|
19
|
+
Halo uses Socket Mode, not webhooks. Turn it on first so the rest of the config falls into place.
|
|
20
|
+
|
|
21
|
+
1. Left sidebar → **Socket Mode** → toggle **Enable Socket Mode** on
|
|
22
|
+
2. Slack prompts you to create an **App-Level Token**:
|
|
23
|
+
- Token Name: anything (e.g. `socket`)
|
|
24
|
+
- Scopes: select `connections:write` (the only one needed)
|
|
25
|
+
- Click **Generate** → save the resulting `xapp-…` token. You won't see it again.
|
|
26
|
+
|
|
27
|
+
Keep the App-Level Token in a password manager or paste it directly into the halo admin form when you get there.
|
|
28
|
+
|
|
29
|
+
## Step 3 — Subscribe to Bot Events
|
|
30
|
+
|
|
31
|
+
1. Left sidebar → **Event Subscriptions** → toggle **Enable Events** on
|
|
32
|
+
2. Note: there's **no Request URL field** when Socket Mode is enabled — Slack pushes events over the wss connection instead
|
|
33
|
+
3. Expand **Subscribe to bot events** and add:
|
|
34
|
+
- `app_mention` — required, fires when someone `@`-mentions the bot
|
|
35
|
+
- `message.im` — required, fires on direct messages to the bot
|
|
36
|
+
- `message.channels` — optional, every public-channel message the bot can see (only meaningful if you want the bot to react without a mention)
|
|
37
|
+
- `message.groups` / `message.mpim` — optional, private channels and group DMs
|
|
38
|
+
|
|
39
|
+
Click **Save Changes** at the bottom.
|
|
40
|
+
|
|
41
|
+
## Step 4 — Add Bot Token Scopes
|
|
42
|
+
|
|
43
|
+
The Bot Token (`xoxb-…`) needs scopes for everything the bot might do.
|
|
44
|
+
|
|
45
|
+
Left sidebar → **OAuth & Permissions** → scroll to **Scopes** → **Bot Token Scopes** → add:
|
|
46
|
+
|
|
47
|
+
**Required:**
|
|
48
|
+
- `chat:write` — post messages
|
|
49
|
+
- `app_mentions:read` — receive `@`-mentions
|
|
50
|
+
- `im:history` — read DM history (so the bot can see the user's message)
|
|
51
|
+
- `im:read` — list IM channels
|
|
52
|
+
- `im:write` — open IM channels
|
|
53
|
+
- `channels:history` — read public-channel messages
|
|
54
|
+
- `groups:history` — read private-channel messages
|
|
55
|
+
- `mpim:history` — read group-DM messages
|
|
56
|
+
- `files:read` — download user-uploaded images / files
|
|
57
|
+
|
|
58
|
+
**For target search in cron jobs** (lets you `@`-search users / channels in the admin's cron form):
|
|
59
|
+
- `users:read`
|
|
60
|
+
- `users:read.email` (optional, only needed if you want email-based user lookup)
|
|
61
|
+
- `channels:read`
|
|
62
|
+
- `groups:read`
|
|
63
|
+
- `mpim:read`
|
|
64
|
+
|
|
65
|
+
## Step 5 — Install to Workspace
|
|
66
|
+
|
|
67
|
+
Same page (**OAuth & Permissions**) → top of the page → **Install to Workspace** → review scopes → **Allow**.
|
|
68
|
+
|
|
69
|
+
After install you get a **Bot User OAuth Token** that starts with `xoxb-…` — this is the second token you need.
|
|
70
|
+
|
|
71
|
+
> If you change scopes later, hit **Reinstall to Workspace** on the same page. The `xoxb-` token value doesn't change; the install just refreshes its scope list. No need to update the token in halo after a reinstall.
|
|
72
|
+
|
|
73
|
+
## Step 6 — Open up the Messages tab (so users can DM the bot)
|
|
74
|
+
|
|
75
|
+
By default Slack hides the input box on the bot's DM page. To allow direct messages:
|
|
76
|
+
|
|
77
|
+
1. Left sidebar → **App Home**
|
|
78
|
+
2. Scroll to **Show Tabs** → **Messages Tab** → toggle on
|
|
79
|
+
3. Check the box **"Allow users to send Slash commands and messages from the messages tab"**
|
|
80
|
+
4. Save
|
|
81
|
+
|
|
82
|
+
In the Slack desktop client, fully quit and reopen (or `Cmd+R` to reload) — otherwise the bot's DM page still shows the cached "messaging is disabled" banner.
|
|
83
|
+
|
|
84
|
+
## Step 7 — Add the account in halo admin
|
|
85
|
+
|
|
86
|
+
Open halo admin → **Channels** → **Slack** → **Add Account**:
|
|
87
|
+
|
|
88
|
+
| Field | Value |
|
|
89
|
+
|---|---|
|
|
90
|
+
| Bot token | the `xoxb-…` from Step 5 |
|
|
91
|
+
| App token | the `xapp-…` from Step 2 |
|
|
92
|
+
| Workspace path | absolute path to the workspace this bot drives, e.g. `/home/ubuntu/my-project` |
|
|
93
|
+
| Label | optional, e.g. "Engineering bot" |
|
|
94
|
+
| Access level | `readonly` (default), `workspace`, or `full` |
|
|
95
|
+
| Language | `en` or `zh` |
|
|
96
|
+
|
|
97
|
+
On submit halo calls Slack's `auth.test` to validate the bot token and auto-fills `botUserId` and `teamId`. If that call fails the account isn't created and you'll see the Slack-returned error inline.
|
|
98
|
+
|
|
99
|
+
## Step 8 — Test it
|
|
100
|
+
|
|
101
|
+
1. In Slack, click the bot's name in the sidebar to start a DM → type `hello` → expect a streamed reply
|
|
102
|
+
2. Invite the bot into a channel: `/invite @halo` → mention it: `@halo what files are in this workspace?` → expect a reply in the same thread
|
|
103
|
+
|
|
104
|
+
If nothing happens, check the halo server logs (`/tmp/halo-server.log`) for `[slack]` lines.
|
|
105
|
+
|
|
106
|
+
## How halo handles inbound
|
|
107
|
+
|
|
108
|
+
- **DMs** (channel id starts with `D`) — every message routes to the bot, no mention needed
|
|
109
|
+
- **Channels / groups** — only `@`-mentions wake the bot up, plain messages are ignored
|
|
110
|
+
- **Threads** — the bot replies in-thread; one halo session per thread, so a long thread stays in one conversation
|
|
111
|
+
- **Files** — images go to the LLM as multimodal content; other files are saved under `<workspace>/.halo/assets/slack/inbound/<accountId>/<date>/`
|
|
112
|
+
- **`bot_id` self-loop** — messages posted by any bot (including this one) are dropped, so the agent can't reply to itself
|
|
113
|
+
|
|
114
|
+
## Slash commands
|
|
115
|
+
|
|
116
|
+
Same set as the other channels — type these as plain text in a DM or thread:
|
|
117
|
+
|
|
118
|
+
| Command | Effect |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
|
|
121
|
+
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
122
|
+
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
123
|
+
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
124
|
+
| `/help` | List commands — object commands show only the verbs you can run |
|
|
125
|
+
|
|
126
|
+
## Cron jobs targeting Slack
|
|
127
|
+
|
|
128
|
+
When a cron job is created from inside a Slack thread, the dispatcher remembers the channel + thread id and sends back to the same place. To target a specific Slack chat from the admin UI:
|
|
129
|
+
|
|
130
|
+
- Pick the Slack target in the cron form
|
|
131
|
+
- Type the channel name (`#general`) or `@user` in the search box — autocomplete matches by Slack's `users.list` / `conversations.list`
|
|
132
|
+
- Selected entries become explicit chat IDs on the cron run
|
|
133
|
+
|
|
134
|
+
The search depends on the `users:read` / `channels:read` scopes added in Step 4. If autocomplete returns nothing, that's the first thing to check.
|
|
135
|
+
|
|
136
|
+
## Common problems
|
|
137
|
+
|
|
138
|
+
| Symptom | Cause / fix |
|
|
139
|
+
|---|---|
|
|
140
|
+
| **"向此应用发送消息的功能已关闭"** banner on the DM page | Step 6 wasn't done — open App Home, enable Messages Tab, reload Slack |
|
|
141
|
+
| Account created but no events arrive | Socket Mode token (`xapp-`) is wrong, or `connections:write` scope missing on it |
|
|
142
|
+
| `not_authed` / `invalid_auth` error on Add Account | Bot token (`xoxb-`) typo, or you pasted the App token in the Bot field by mistake |
|
|
143
|
+
| Bot replies to itself in a loop | Should never happen — halo drops all `bot_id`-tagged messages. If it does, file a bug |
|
|
144
|
+
| Search dropdown empty in cron form | Missing `users:read` / `channels:read` scopes — add them, then Reinstall to Workspace |
|
|
145
|
+
| Multiple halo instances reply to the same message | Only one process should hold the Socket Mode connection. Check `~/.halo/global/server.pid` for the lock |
|
|
146
|
+
|
|
147
|
+
## Multi-workspace setup
|
|
148
|
+
|
|
149
|
+
Slack apps are per-Slack-workspace. To support a second Slack workspace, repeat Steps 1-7 with a fresh app installed in that second workspace, and add a second account row in halo. The two accounts can point at the same halo workspace path or different ones — halo uses `teamId` to route inbound events.
|
|
150
|
+
|
|
151
|
+
## Reference
|
|
152
|
+
|
|
153
|
+
- Code: `packages/server/src/channels/slack/`
|
|
154
|
+
- Routes: `packages/server/src/routes/slack.ts`
|
|
155
|
+
- Admin UI: `packages/admin/src/features/slack/slack-settings.tsx`
|
|
156
|
+
- API client: `packages/server/src/channels/slack/api.ts` (covers Socket Mode connection, `chat.postMessage`, `auth.test`, file download, target search)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Telegram
|
|
2
|
+
|
|
3
|
+
Talk to a halo agent from Telegram. Halo uses Telegram's **long-poll** mode (no webhook URL needed).
|
|
4
|
+
|
|
5
|
+
## What you'll end up with
|
|
6
|
+
|
|
7
|
+
- A Telegram bot created via BotFather
|
|
8
|
+
- A bot token like `123456:ABC-DEF…` stored in halo
|
|
9
|
+
- A bot account row pointing that token at one halo workspace
|
|
10
|
+
|
|
11
|
+
## Step 1 — Create a bot with BotFather
|
|
12
|
+
|
|
13
|
+
1. In any Telegram client, search `@BotFather` and start a chat
|
|
14
|
+
2. Send `/newbot`
|
|
15
|
+
3. Pick a display name (free text, e.g. `Halo Dev`)
|
|
16
|
+
4. Pick a username — must end in `bot` or `_bot`, must be globally unique. e.g. `halo_dev_bot`
|
|
17
|
+
5. BotFather replies with a token like `123456789:ABCdef-GhI...`. **This is the only credential you need.** Save it.
|
|
18
|
+
|
|
19
|
+
## Step 2 — (Optional) Register the slash-command menu
|
|
20
|
+
|
|
21
|
+
Sending `/setcommands` to BotFather lets Telegram clients show a menu next to the input box. This is purely cosmetic — halo handles all these commands whether they're registered or not.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
/setcommands → pick your bot → paste:
|
|
25
|
+
|
|
26
|
+
start - 开始 / start
|
|
27
|
+
new - 新会话 / new session
|
|
28
|
+
list - 会话列表 / list sessions
|
|
29
|
+
switch - 切换会话 / switch session
|
|
30
|
+
stop - 中断任务 / stop running task
|
|
31
|
+
compact - 压缩上下文 / compact context
|
|
32
|
+
ws - 查看/切换 workspace
|
|
33
|
+
help - 帮助 / help
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Step 3 — Add the account in halo admin
|
|
37
|
+
|
|
38
|
+
Open halo admin → **Channels** → **Telegram** → **Add Account**:
|
|
39
|
+
|
|
40
|
+
| Field | Value |
|
|
41
|
+
|---|---|
|
|
42
|
+
| Bot token | the `…:ABC…` from Step 1 |
|
|
43
|
+
| Workspace path | absolute path, e.g. `/home/ubuntu/my-project` |
|
|
44
|
+
| Label | optional |
|
|
45
|
+
| Access level | `readonly` (default), `workspace`, or `full` |
|
|
46
|
+
| Language | `en` or `zh` |
|
|
47
|
+
| Allowed users | optional whitelist; comma-separated user IDs and/or `@usernames`. Empty = anyone can talk to the bot |
|
|
48
|
+
|
|
49
|
+
On submit halo calls Telegram's `getMe` to validate the token and auto-fill `botUsername`. If that call fails the account isn't created.
|
|
50
|
+
|
|
51
|
+
### About `allowedUsers`
|
|
52
|
+
|
|
53
|
+
- **Empty** — anyone who finds the bot can chat with it. Fine for personal bots, dangerous if the bot is `full`-access
|
|
54
|
+
- **Numeric IDs** (e.g. `123456789`) — recommended. A user's id is stable and you can find it by sending `/start` to `@userinfobot` in Telegram
|
|
55
|
+
- **`@username`** — works for inbound filtering but **cron fan-out skips them** (Telegram's Bot API needs a numeric chat id, and `@username` lookup isn't always reliable)
|
|
56
|
+
|
|
57
|
+
For team bots prefer numeric IDs.
|
|
58
|
+
|
|
59
|
+
## Step 4 — Test it
|
|
60
|
+
|
|
61
|
+
In Telegram, search the bot's username (`@halo_dev_bot`) → press **Start** → say `hello` → expect a streamed reply.
|
|
62
|
+
|
|
63
|
+
If nothing happens, check halo server logs for `[telegram]` lines.
|
|
64
|
+
|
|
65
|
+
## How halo handles inbound
|
|
66
|
+
|
|
67
|
+
- **Private chats** — every message routes to the bot
|
|
68
|
+
- **Group chats** — not supported in v1 (group messages are dropped)
|
|
69
|
+
- **Photos / documents** — the file URL is included in the agent's input text; images aren't yet sent to vision automatically (by design — Telegram's photo URLs are short-lived and the upstream LLM can't always reach them)
|
|
70
|
+
- **Slash commands** — handled by halo, not by Telegram. The BotFather menu in Step 2 is just a UI hint
|
|
71
|
+
|
|
72
|
+
## Slash commands
|
|
73
|
+
|
|
74
|
+
| Command | Effect |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `/start` | Welcome message |
|
|
77
|
+
| `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
|
|
78
|
+
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
79
|
+
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
80
|
+
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
81
|
+
| `/help` | List commands — object commands show only the verbs you can run |
|
|
82
|
+
|
|
83
|
+
## Cron jobs targeting Telegram
|
|
84
|
+
|
|
85
|
+
When a cron job is created from inside a Telegram chat, the dispatcher targets that chat. From the admin UI you can also enter chat IDs directly (comma-separated for fan-out). The dispatcher resolves recipients in this order:
|
|
86
|
+
|
|
87
|
+
1. Explicit chat IDs on the cron target row (if provided in the form)
|
|
88
|
+
2. Fan-out to every numeric ID in the account's `allowedUsers` whitelist
|
|
89
|
+
3. The most-recent inbound chat ID, as a single-recipient fallback
|
|
90
|
+
|
|
91
|
+
Want to fan out a daily report to your whole team? Add their numeric IDs to `allowedUsers` and create a cron without an explicit chat ID — halo delivers to each one and records per-recipient ✓/✗ in the cron run history.
|
|
92
|
+
|
|
93
|
+
## Common problems
|
|
94
|
+
|
|
95
|
+
| Symptom | Cause / fix |
|
|
96
|
+
|---|---|
|
|
97
|
+
| "Unauthorized" / `getMe` fails on Add Account | Bot token has a typo, or the bot was deleted in BotFather |
|
|
98
|
+
| Bot exists but doesn't reply | Check `allowedUsers` — if non-empty, your user must be in it |
|
|
99
|
+
| Bot used to work, now silent | Maybe two halo processes are running and stealing each other's `getUpdates` long-poll. Check `~/.halo/global/server.pid` |
|
|
100
|
+
| Group messages don't trigger the bot | Expected — groups aren't supported in v1 |
|
|
101
|
+
| Cron fan-out skips a whitelisted user | The user is listed as `@username`. Add their numeric ID instead |
|
|
102
|
+
|
|
103
|
+
## Multi-bot setup
|
|
104
|
+
|
|
105
|
+
Each BotFather token = one bot account in halo. To run two separate bots, create two tokens with BotFather and add two account rows. They can point at the same workspace (e.g. one `readonly` for general team chat + one `full` for ops) or different workspaces.
|
|
106
|
+
|
|
107
|
+
## Reference
|
|
108
|
+
|
|
109
|
+
- Code: `packages/server/src/channels/telegram/`
|
|
110
|
+
- Routes: `packages/server/src/routes/telegram.ts`
|
|
111
|
+
- Admin UI: `packages/admin/src/features/telegram/telegram-settings.tsx`
|
|
112
|
+
- Design notes: [../../design/telegram.md](../../design/telegram.md)
|