@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,198 @@
|
|
|
1
|
+
# Deployment
|
|
2
|
+
|
|
3
|
+
## Architecture overview
|
|
4
|
+
|
|
5
|
+
Halo only needs **one Node process** (Hono on port 9527 by default). API, WebSocket, and static frontend live in the same process.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Browser ──────────────▶ Hono (:9527)
|
|
9
|
+
├── /api/* → API routes
|
|
10
|
+
├── /ws → WebSocket (chat + terminal)
|
|
11
|
+
└── /* → Static files (packages/admin/out/)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Next.js is build-time only (`next build` → static export to `out/`) — no Next.js process at runtime.
|
|
15
|
+
|
|
16
|
+
Nginx is **not required**. Use it only for domain routing, SSL termination, or when the port is shared with other services.
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- Node.js ≥ 22
|
|
21
|
+
- pnpm ≥ 9
|
|
22
|
+
- AWS credentials configured (`~/.aws/credentials` or env vars) with Bedrock access
|
|
23
|
+
|
|
24
|
+
## 1. Install dependencies and build
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd /path/to/halo
|
|
28
|
+
pnpm install
|
|
29
|
+
|
|
30
|
+
pnpm --filter @turmind/halo-core build
|
|
31
|
+
pnpm --filter @turmind/halo-server build
|
|
32
|
+
pnpm --filter @turmind/halo-admin build # next build + copy-monaco; never a bare next build (Monaco would 404)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 2. Create the runtime directory
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
mkdir -p /path/to/halo/data
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
SQLite databases are created automatically under `data/` on first start.
|
|
42
|
+
|
|
43
|
+
## 3. Run `halo setup`
|
|
44
|
+
|
|
45
|
+
Seeds `~/.halo/global/` with templates (agents, skills, prompts, models, docs), creates `secrets/config.yaml`, and walks you through password / port / model API keys / optional skills.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
halo setup # interactive — picks up arrow-key UI on TTYs
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Re-run any time to change the password, refresh model keys, or toggle optional skills. Built-in agent / skill files are force-overwritten on every run; user-created agents and skills are left alone.
|
|
52
|
+
|
|
53
|
+
For Docker / CI builds where stdin isn't a TTY, see the **Docker** section below.
|
|
54
|
+
|
|
55
|
+
## 4. Environment variables (optional)
|
|
56
|
+
|
|
57
|
+
Full list in [env.md](env.md). The most common one is `HALO_PASSWORD`, which acts as a plaintext password and bypasses the scrypt hash stored by `halo setup`. Use it when an external secret store (k8s / systemd / Docker secrets) already protects the value:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
echo 'export HALO_PASSWORD=your_password_here' >> ~/.bashrc
|
|
61
|
+
source ~/.bashrc
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
When `HALO_PASSWORD` is set, the password chosen via `halo setup` is ignored at runtime.
|
|
65
|
+
|
|
66
|
+
## 5. Start the server
|
|
67
|
+
|
|
68
|
+
> **单实例锁**:server 启动时会写 `~/.halo/global/server.lock`(Linux 用 flock,macOS/Windows 回退到 pid 探测),退出时自动清理。如果 lock 里记录的进程还活着,新 server 会拒绝启动并打印 `kill <pid>` 提示。原因是 WeChat 长轮询循环跟 HTTP server 解耦,多个进程并存会导致同一条微信消息被 fan out 到多个 session。陈旧 lock(进程已不在)会被自动识别并清除。要手动重启先 kill 旧的:`kill $(cat ~/.halo/global/server.lock)`。
|
|
69
|
+
|
|
70
|
+
### Option A: quick start
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cd /path/to/halo/packages/server
|
|
74
|
+
HALO_PASSWORD=your_password nohup node dist/index.js > /tmp/server.log 2>&1 &
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Option B: systemd
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
sudo tee /etc/systemd/system/halo.service <<'EOF'
|
|
81
|
+
[Unit]
|
|
82
|
+
Description=Halo Server
|
|
83
|
+
After=network.target
|
|
84
|
+
|
|
85
|
+
[Service]
|
|
86
|
+
Type=simple
|
|
87
|
+
User=YOUR_USER
|
|
88
|
+
WorkingDirectory=/path/to/halo/packages/server
|
|
89
|
+
ExecStart=/path/to/node dist/index.js
|
|
90
|
+
Restart=on-failure
|
|
91
|
+
RestartSec=5
|
|
92
|
+
Environment=NODE_ENV=production
|
|
93
|
+
Environment=HALO_PASSWORD=your_password
|
|
94
|
+
|
|
95
|
+
[Install]
|
|
96
|
+
WantedBy=multi-user.target
|
|
97
|
+
EOF
|
|
98
|
+
|
|
99
|
+
sudo systemctl daemon-reload
|
|
100
|
+
sudo systemctl enable --now halo
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 6. Verify
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
curl http://localhost:9527/api/health
|
|
107
|
+
# expect: {"status":"ok", ...}
|
|
108
|
+
|
|
109
|
+
curl -s -o /dev/null -w "%{http_code}" http://localhost:9527
|
|
110
|
+
# expect: 200
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Browser: http://localhost:9527
|
|
114
|
+
|
|
115
|
+
## 7. Redeploy after code changes
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd /path/to/halo
|
|
119
|
+
|
|
120
|
+
cd packages/admin && npx next build --no-lint && node scripts/copy-monaco.mjs && cd ../..
|
|
121
|
+
pnpm --filter @turmind/halo-server build
|
|
122
|
+
|
|
123
|
+
# 用 lock 文件里的 pid 确保旧进程彻底退出(避免孤儿进程继续长轮询)
|
|
124
|
+
kill $(cat ~/.halo/global/server.lock) 2>/dev/null
|
|
125
|
+
sleep 2
|
|
126
|
+
cd packages/server && HALO_PASSWORD=your_password nohup node dist/index.js > /tmp/server.log 2>&1 &
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## 8. Install via npm (recommended)
|
|
130
|
+
|
|
131
|
+
The whole monorepo is bundled and published to npm as a single package:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm install -g @turmind/halo
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This installs the `halo` binary on `$PATH`. Subcommands available:
|
|
138
|
+
|
|
139
|
+
| Command | Purpose |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `halo setup` | Interactive password / port / model keys / optional skills setup |
|
|
142
|
+
| `halo setup --non-interactive` (alias `-y`) | Skip every prompt — seed templates only, supply password via `HALO_PASSWORD` env. Use in Dockerfiles / CI. |
|
|
143
|
+
| `halo upgrade` | Bump the npm install in place. Compares the bundled version against `npm view @turmind/halo version`; no-op if already latest, otherwise runs `npm install -g @turmind/halo@latest` and prints a server-restart hint. On EACCES, suggests retrying with `sudo`. |
|
|
144
|
+
| `halo server start` | Launch HTTP/WS server (foreground). Add `-d` for daemon. |
|
|
145
|
+
| `halo server stop` / `restart` / `status` / `logs` | Server lifecycle |
|
|
146
|
+
| `halo tui` | Interactive TUI client |
|
|
147
|
+
| `halo cli "<prompt>"` | One-shot prompt → reply, exit |
|
|
148
|
+
| `halo agents` / `halo sessions` | List agents / sessions |
|
|
149
|
+
|
|
150
|
+
### Upgrade flow
|
|
151
|
+
|
|
152
|
+
1. `halo upgrade` — bumps the on-disk npm package
|
|
153
|
+
2. `halo server restart` — server's startup check sees `~/.halo/global/.template-version` is behind the new bundled `TEMPLATE_VERSION`, runs `ensureHaloHome` automatically, then starts. Refreshes `docs/`, built-in agents, built-in skills, system prompts, and the model registry. User-owned files (USER.md, custom agents/skills, INSTRUCTIONS.md overrides) are left alone. See `init.ts` for the per-category overwrite policy.
|
|
154
|
+
|
|
155
|
+
The published package contains a single bundled JS entry (~620 KB), all built-in templates (agents / skills / prompts / models), bundled platform docs, and the admin Web UI static export. Total install footprint ≈ 120 MB after npm dedupes shared deps.
|
|
156
|
+
|
|
157
|
+
### Non-interactive (Docker / CI) details
|
|
158
|
+
|
|
159
|
+
`halo setup --non-interactive` (alias `-y`) skips every prompt. It only:
|
|
160
|
+
|
|
161
|
+
- Seeds / refreshes `~/.halo/global/` from the bundled templates (per the per-category overwrite policy in `init.ts`)
|
|
162
|
+
- Generates `server.jwt_secret` if missing
|
|
163
|
+
- **Does not** set a password — supply one via `HALO_PASSWORD` env
|
|
164
|
+
|
|
165
|
+
Minimal Dockerfile:
|
|
166
|
+
|
|
167
|
+
```dockerfile
|
|
168
|
+
FROM node:22-slim
|
|
169
|
+
RUN apt-get update && apt-get install -y --no-install-recommends bubblewrap && rm -rf /var/lib/apt/lists/*
|
|
170
|
+
RUN npm install -g @turmind/halo
|
|
171
|
+
ENV HALO_PASSWORD=changeme
|
|
172
|
+
ENV HALO_PORT=9527
|
|
173
|
+
EXPOSE 9527
|
|
174
|
+
CMD halo setup -y && halo server start
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 9. Optional: Nginx reverse proxy
|
|
178
|
+
|
|
179
|
+
```nginx
|
|
180
|
+
server {
|
|
181
|
+
listen 80;
|
|
182
|
+
server_name your-domain.com;
|
|
183
|
+
|
|
184
|
+
client_max_body_size 50m;
|
|
185
|
+
|
|
186
|
+
location / {
|
|
187
|
+
proxy_pass http://127.0.0.1:9527;
|
|
188
|
+
proxy_http_version 1.1;
|
|
189
|
+
proxy_set_header Host $host;
|
|
190
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
191
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
192
|
+
proxy_set_header Connection "upgrade";
|
|
193
|
+
proxy_buffering off;
|
|
194
|
+
proxy_read_timeout 86400s;
|
|
195
|
+
proxy_send_timeout 86400s;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Desktop Packaging (Electron / DMG)
|
|
2
|
+
|
|
3
|
+
How to build the macOS desktop app (`Halo-0.1.5-arm64.dmg`). The desktop
|
|
4
|
+
build is a thin Electron shell (`packages/desktop`) that bundles a self-
|
|
5
|
+
contained server runtime + a private `node` binary + the admin static export,
|
|
6
|
+
then spawns the server as a child process and points a `BrowserWindow` at it.
|
|
7
|
+
|
|
8
|
+
See [design](../design/) for the server itself; this doc is only about the
|
|
9
|
+
packaging pipeline.
|
|
10
|
+
|
|
11
|
+
> **Before packaging, check for `desktop-packaging.local.md` in this directory**
|
|
12
|
+
> (gitignored, per-host). If present it carries operator/machine-specific setup
|
|
13
|
+
> not captured here — e.g. building on a Linux host (wine/32-bit, workspace
|
|
14
|
+
> build order, `corepack enable`) and artifact-distribution gotchas. Read it
|
|
15
|
+
> first; absence just means none were recorded on this machine.
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Halo.app
|
|
21
|
+
├── Electron main (packages/desktop/src/main.cjs)
|
|
22
|
+
│ • picks a free port, seeds ~/.halo, spawns the server, waits /api/health
|
|
23
|
+
│ • on quit: SIGTERM the server, SIGKILL fallback after 1.5s
|
|
24
|
+
└── resources/ (electron-builder extraResources)
|
|
25
|
+
├── server-runtime/ ← pnpm-deployed @turmind/halo-server tree (dist + node_modules + templates), node-linker=hoisted (flat, no .pnpm symlink farm)
|
|
26
|
+
├── admin-out/ ← Next.js static export
|
|
27
|
+
└── node ← private node v22 binary (server child runs on this, NOT Electron's node)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Why a private `node`: the server's native addons (`better-sqlite3`,
|
|
31
|
+
`node-pty`) are prebuilt for node 22. Electron's embedded node is a different
|
|
32
|
+
ABI, so the server must run on a real node 22 — hence we download and bundle
|
|
33
|
+
one.
|
|
34
|
+
|
|
35
|
+
## One-shot build
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd packages/halo # repo root
|
|
39
|
+
# 1. build the upstream artifacts the stage script consumes, IN ORDER.
|
|
40
|
+
# - core MUST build before server: server imports @turmind/halo-core
|
|
41
|
+
# (e.g. GitManager in routes/git.ts). A stale/missing core/dist makes
|
|
42
|
+
# server's tsc fail with "GitManager has no method X" / type errors.
|
|
43
|
+
# - admin MUST use the full `pnpm build` (next build + copy-monaco.mjs),
|
|
44
|
+
# NOT a bare `next build` — the latter skips Monaco and stage-runtime.mjs
|
|
45
|
+
# now FATAL-aborts on the missing admin-out/monaco/vs/loader.js.
|
|
46
|
+
pnpm --filter @turmind/halo-core build
|
|
47
|
+
pnpm --filter @turmind/halo-server build
|
|
48
|
+
pnpm --filter @turmind/halo-admin build
|
|
49
|
+
|
|
50
|
+
# 2. stage + package (arm64)
|
|
51
|
+
cd packages/desktop
|
|
52
|
+
CI=true pnpm dist:arm64
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Output: `packages/desktop/dist/Halo-0.1.5-arm64.dmg` (~143 MB).
|
|
56
|
+
|
|
57
|
+
`pnpm dist:arm64` runs three steps (see `package.json` scripts):
|
|
58
|
+
`gen-icon` → `prepack-dmg` (`scripts/stage-runtime.mjs`) → `electron-builder
|
|
59
|
+
--mac --arm64`.
|
|
60
|
+
|
|
61
|
+
Other targets: `pnpm dist:x64` (Intel mac), `pnpm dist:win` (Windows nsis,
|
|
62
|
+
cross-staged from mac).
|
|
63
|
+
|
|
64
|
+
## Faster rebuilds
|
|
65
|
+
|
|
66
|
+
The staging pipeline is split so a code-only change doesn't redo the heavy
|
|
67
|
+
dependency work. Three levers, in order of how often they help:
|
|
68
|
+
|
|
69
|
+
1. **Auto-fast (automatic).** A full `pnpm dist:arm64` now fingerprints the
|
|
70
|
+
dependency inputs (`packages/server/package.json`, `core/package.json`, the
|
|
71
|
+
cli bundle's `dist-pub/package.json`, `pnpm-lock.yaml`, + target/node
|
|
72
|
+
version) and records it in `resources/.stage-meta`. On the next run, if a
|
|
73
|
+
prior full stage exists for the same target **and the fingerprint is
|
|
74
|
+
unchanged**, staging auto-takes the fast path: it re-syncs only the compiled
|
|
75
|
+
outputs (server `dist`, admin `out`, cli bundle) and reuses the existing
|
|
76
|
+
`node_modules` + bundled `node`. Measured: full stage ~29 s → fast ~1.5 s.
|
|
77
|
+
So after the first build, "I only changed TS" rebuilds skip the install
|
|
78
|
+
entirely with no flag to remember.
|
|
79
|
+
- Force a clean re-stage (e.g. you suspect a corrupt tree): `HALO_STAGE_FULL=1 pnpm dist:arm64`.
|
|
80
|
+
- Any dependency edit changes the fingerprint → next build does a real stage automatically.
|
|
81
|
+
|
|
82
|
+
2. **Explicit fast (`--fast`).** `pnpm dist:arm64:fast` / `prepack-dmg:fast`
|
|
83
|
+
force the fast path and hard-fail if no prior full stage exists. Same effect
|
|
84
|
+
as auto-fast but explicit; auto-fast makes this rarely necessary.
|
|
85
|
+
|
|
86
|
+
3. **`npmRebuild: false`** (electron-builder.yml). The desktop package has no
|
|
87
|
+
native deps of its own — `better-sqlite3` / `node-pty` / `@parcel/watcher`
|
|
88
|
+
all live in the staged `server-runtime` / `cli-runtime` trees, fixed up for
|
|
89
|
+
the target *by `stage-runtime.mjs`* (not by electron-builder). Skipping
|
|
90
|
+
`@electron/rebuild` removes dead time every pack. Note the staging fixups are
|
|
91
|
+
what actually make these correct per-target — see the cross-stage gotchas.
|
|
92
|
+
|
|
93
|
+
**What's left (the ~70 s floor):** electron-builder itself — asar packing, dmg
|
|
94
|
+
generation, and Gatekeeper scanning the ~350 MB of `extraResources`. Neither
|
|
95
|
+
fast-staging nor `npmRebuild:false` touches that; it runs on every build.
|
|
96
|
+
A further win would be de-duplicating `cli-runtime` (~137 MB) against
|
|
97
|
+
`server-runtime` (~92 MB) — they share most deps (lark sdk, drizzle,
|
|
98
|
+
better-sqlite3, protobufjs) — but that needs the two trees to physically share
|
|
99
|
+
files without breaking cli's standalone require paths; deferred.
|
|
100
|
+
|
|
101
|
+
## Why `CI=true` is mandatory
|
|
102
|
+
|
|
103
|
+
`stage-runtime.mjs` runs `pnpm deploy --prod`, which under the hood does a
|
|
104
|
+
`pnpm install`. In a non-TTY shell (which is how these builds run) pnpm aborts
|
|
105
|
+
with `[ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY]` unless `CI=true` is set.
|
|
106
|
+
|
|
107
|
+
**Side effect (now auto-handled)**: `pnpm deploy --prod` prunes the *root
|
|
108
|
+
workspace*'s `node_modules` to prod-only as a side effect — the devDep symlinks
|
|
109
|
+
(`typescript`, `electron-builder`, `sharp`, `png-to-ico`) vanish, which used to
|
|
110
|
+
break the next `gen-icon` / `tsc` / build with `command not found` /
|
|
111
|
+
`ERR_MODULE_NOT_FOUND`. `stage-runtime.mjs` now runs `pnpm install` right after
|
|
112
|
+
the deploy to restore the full workspace, so this no longer bites. If you ever
|
|
113
|
+
hit a half-pruned tree manually, recover with:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
CI=true pnpm install --config.verifyDepsBeforeRun=false
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Still good practice: build server + admin **before** `pnpm dist:arm64`.
|
|
120
|
+
|
|
121
|
+
## Build steps in detail (`stage-runtime.mjs`)
|
|
122
|
+
|
|
123
|
+
0. **Stamp the build version** → write `<package.json version>-<short git sha>`
|
|
124
|
+
(`-dirty` suffix if the tree has uncommitted changes) to
|
|
125
|
+
`resources/halo-version`, on every path (full / fast / auto-fast — the sha
|
|
126
|
+
changes per commit even when deps don't). Shipped via `extraResources`;
|
|
127
|
+
`main.cjs` reads it and passes it as `HALO_VERSION` to the spawned server, so
|
|
128
|
+
`GET /api/health` reports it and the admin settings sidebar shows `Halo
|
|
129
|
+
v<version>`. Without it the server falls back to `'dev'`. Same
|
|
130
|
+
`<version>-<sha>` scheme as the CLI bundle (`halo --version`), so all three
|
|
131
|
+
agree. Dev (`electron .`) computes it live from git instead.
|
|
132
|
+
1. **`pnpm deploy --legacy --prod --config.node-linker=hoisted`** → flattens
|
|
133
|
+
`@turmind/halo-server` (+ the `@turmind/halo-core` workspace dep) into
|
|
134
|
+
`resources/server-runtime/node_modules` as a flat, real-directory tree (no
|
|
135
|
+
`.pnpm` symlink farm), so the app is self-contained. `hoisted` on **every**
|
|
136
|
+
target — see the dangling-self-symlink gotcha for why macOS can't use the
|
|
137
|
+
default isolated layout.
|
|
138
|
+
2. **Copy `templates/`** — `pnpm deploy` doesn't pick these up; copied
|
|
139
|
+
explicitly. This is what carries the built-in skills (incl. `send-file`),
|
|
140
|
+
agents, prompts, models into the dmg.
|
|
141
|
+
3. **Cross-arch native fixup** — when target arch/platform ≠ host, re-fetch
|
|
142
|
+
`better-sqlite3` prebuilds via `prebuild-install`.
|
|
143
|
+
4. **Trim bloat** — drop non-target `node-pty` prebuilds, `better-sqlite3`'s
|
|
144
|
+
C source, and the server's `src/`/`tests/`.
|
|
145
|
+
5. **Copy `admin-out/`** — the Next.js static export.
|
|
146
|
+
6. **Download + stage node** — fetch `node-v22.11.0-<platform>-<arch>` from
|
|
147
|
+
nodejs.org, extract the `node` binary into `resources/`.
|
|
148
|
+
7. **codesign (ad-hoc)** — `codesign --force --sign -` on the node binary for
|
|
149
|
+
Gatekeeper. **We deliberately do NOT `strip`** — see gotcha below.
|
|
150
|
+
|
|
151
|
+
## Gotchas (learned the hard way)
|
|
152
|
+
|
|
153
|
+
- **`pnpm deploy` (default isolated linker) leaves a dangling self-symlink that
|
|
154
|
+
escapes the .app — use `node-linker=hoisted` on EVERY target.** The default
|
|
155
|
+
isolated layout creates `server-runtime/node_modules/.pnpm/node_modules/@turmind/halo-server
|
|
156
|
+
-> ../../../../../../../server`, a relative climb whose depth only resolves
|
|
157
|
+
*inside the monorepo* (back to `packages/server`). Once copied into
|
|
158
|
+
`Halo.app/Contents/Resources/`, the same relative path points outside the
|
|
159
|
+
bundle (`Halo.app/server`) and dangles. Symptom: a colleague who installs the
|
|
160
|
+
dmg hits `No such file: /Applications/Halo.app/Contents/Resources/server-runtime/node_modules/.pnpm/node_modules/@turmind/halo-server`
|
|
161
|
+
— even though the build machine runs fine, because there the symlink target
|
|
162
|
+
*does* exist. The build also carried ~1100 symlinks (the whole `.pnpm` farm),
|
|
163
|
+
slowing Gatekeeper's first-launch scan. Windows already used `hoisted` (mac-
|
|
164
|
+
created Unix symlinks don't survive the copy into the .exe); we now use it on
|
|
165
|
+
**all** targets — a flat, real-directory `node_modules` has neither the
|
|
166
|
+
escaping self-symlink nor the farm. Verify: `find Halo.app -type l | wc -l`
|
|
167
|
+
should be ~36 (Electron framework links only), and
|
|
168
|
+
`find Halo.app -path '*@turmind/halo-server'` should be empty.
|
|
169
|
+
*Note this is separate from the "is damaged" Gatekeeper prompt — that's the
|
|
170
|
+
ad-hoc signature + download quarantine (`identity: null` in the yml), fixed
|
|
171
|
+
per-machine with `sudo xattr -dr com.apple.quarantine /Applications/Halo.app`,
|
|
172
|
+
or properly with a Developer ID signature + notarization.*
|
|
173
|
+
|
|
174
|
+
- **Never `strip` the bundled node binary.** Recent macOS `strip` (Xcode 26
|
|
175
|
+
toolchain) trims the *dynamic* symbol table, not just debug symbols. The
|
|
176
|
+
result: `dlopen()` of native addons (`better-sqlite3`, `node-pty`) crashes
|
|
177
|
+
with SIGSEGV (server exits `code=null` / 139) before any JS runs. The dmg
|
|
178
|
+
looks fine but won't boot. Keep the full ~114 MB binary; the ~30 MB saved
|
|
179
|
+
isn't worth a dead app. (`stage-runtime.mjs` has the strip step removed.)
|
|
180
|
+
|
|
181
|
+
- **Desktop port is sequential from 9527 (NOT random), and that's load-bearing
|
|
182
|
+
for persistence.** `main.cjs` `findFreePort()` tries 9527 first, then scans
|
|
183
|
+
upward for the first free one, and passes it to the server child. It used to
|
|
184
|
+
use `listen(0)` (an OS-random ephemeral port) — but the window loads
|
|
185
|
+
`http://127.0.0.1:PORT`, and **localStorage is partitioned by origin**, so a
|
|
186
|
+
changing port gave a brand-new origin every launch and silently wiped *all*
|
|
187
|
+
persisted state (last folder, open session, expanded tree, editor tabs). A
|
|
188
|
+
stable preferred port keeps the origin stable so localStorage survives
|
|
189
|
+
restarts, while still stepping aside if 9527 is genuinely taken. (The
|
|
190
|
+
user-facing `HALO_PORT` still only matters for CLI / web deployments — the
|
|
191
|
+
server reads it from its own env.)
|
|
192
|
+
|
|
193
|
+
- **macOS GUI PATH is minimal — `spawn('halo')` can't find the launcher.**
|
|
194
|
+
An app launched from Finder/Dock inherits launchd's bare PATH
|
|
195
|
+
(`/usr/bin:/bin:/usr/sbin:/sbin`), which excludes `/usr/local/bin` where the
|
|
196
|
+
"Install 'halo' Command" menu drops the launcher. The server child inherits
|
|
197
|
+
this PATH, so the evo wrapper / cron runner's `spawn('halo', …)`
|
|
198
|
+
(`resolveHaloCli()` returns bare `halo`) hit `spawn halo ENOENT` even
|
|
199
|
+
though the launcher exists. `startServer()` in `main.cjs` therefore prepends
|
|
200
|
+
the standard CLI dirs (`/usr/local/bin`, `/opt/homebrew/bin`, + sbin
|
|
201
|
+
variants — the same set `tools/sandbox.js` assumes) to the child's
|
|
202
|
+
`env.PATH`, **guarded by `process.platform === 'darwin'`** (Windows PATH uses
|
|
203
|
+
`;` and these dirs don't exist there). Note this still requires the user to
|
|
204
|
+
have run the menu action that installs `/usr/local/bin/halo`; a fresh
|
|
205
|
+
install that never did has no `halo` on any PATH.
|
|
206
|
+
|
|
207
|
+
- **Server child lifecycle.** On `before-quit` the main process sends SIGTERM,
|
|
208
|
+
then force-SIGKILLs after 1.5 s. This is the backstop for a wedged server
|
|
209
|
+
whose main thread is stuck in a syscall and never delivers SIGTERM. Without
|
|
210
|
+
it, a zombie server keeps holding the port / `server.lock`. (The file watcher
|
|
211
|
+
is `@parcel/watcher` — VS Code's engine — using native recursive FSEvents/
|
|
212
|
+
inotify with an ignore list; it replaced chokidar, whose recursive mode
|
|
213
|
+
overflowed the macOS FSEvents queue on large repos and could deadlock on
|
|
214
|
+
close. See `ws/file-watcher.ts`.)
|
|
215
|
+
|
|
216
|
+
- **Stale `server.lock`.** If a prior server was `kill -9`'d, its PID is left
|
|
217
|
+
in `~/.halo/global/server.lock`. The server now probes whether that PID is
|
|
218
|
+
alive on startup and self-heals (removes the lock + continues) instead of
|
|
219
|
+
exiting 1. See `acquireSingleInstanceLock` in `packages/server/src/index.ts`.
|
|
220
|
+
|
|
221
|
+
- **Build admin with `pnpm --filter @turmind/halo-admin build`, NOT a bare
|
|
222
|
+
`next build` — Monaco won't ship otherwise.** The editor self-hosts Monaco
|
|
223
|
+
(no CDN at runtime): `scripts/copy-monaco.mjs` stages `monaco-editor/min/vs`
|
|
224
|
+
into `admin/out/monaco/vs`, and the admin `build` script chains it
|
|
225
|
+
(`next build && copy-monaco.mjs`). Both downstream consumers (cli
|
|
226
|
+
`build-bundle.mjs`, desktop `stage-runtime.mjs`) copy `admin/out` wholesale,
|
|
227
|
+
so a missing `out/monaco/` propagates into every artifact. Symptom: editor
|
|
228
|
+
opening *any* file throws `Uncaught SyntaxError: Unexpected token '<'` at
|
|
229
|
+
`loader.js` — the server returned the 404 HTML page because Monaco's loader
|
|
230
|
+
isn't there. This is exactly how a monaco-less 0.1.7 shipped: packaging ran a
|
|
231
|
+
bare `npx next build --no-lint` (skipping copy-monaco), and cli's backstop
|
|
232
|
+
copy silently failed because it resolved `monaco-editor` from cli's
|
|
233
|
+
node_modules (it's *admin's* devDependency). Now hard-gated: both
|
|
234
|
+
`build-bundle.mjs` and `stage-runtime.mjs` `process.exit(1)` if
|
|
235
|
+
`admin-out/monaco/vs/loader.js` is absent, and every doc's build command ends
|
|
236
|
+
with `&& node scripts/copy-monaco.mjs`. If you must run `next build`
|
|
237
|
+
directly, append the copy step yourself.
|
|
238
|
+
|
|
239
|
+
- **Changing anything under `templates/` requires bumping `TEMPLATE_VERSION`
|
|
240
|
+
(`packages/server/src/init.ts`) — otherwise the change never reaches existing
|
|
241
|
+
installs.** Server startup only re-runs `ensureHaloHome` (the template
|
|
242
|
+
reseed) when the on-disk `~/.halo/global/.template-version` is *strictly less
|
|
243
|
+
than* the compiled `TEMPLATE_VERSION` (`index.ts` startup block). Equal → the
|
|
244
|
+
whole reseed is skipped, so a template edit with no version bump is invisible
|
|
245
|
+
to every machine that already has the prior version stamped. This bit the
|
|
246
|
+
`team`-delegation rewrite: `default/AGENT.md` gained the "team whitelist"
|
|
247
|
+
paragraph but `TEMPLATE_VERSION` stayed put, so upgraded users' `default`
|
|
248
|
+
agent kept reading the old AGENT.md and didn't know what `team` was. It's an
|
|
249
|
+
integer, not a hash — it must increase monotonically (a git sha would compare
|
|
250
|
+
nonsensically and can decrease). Fresh `halo setup` always reseeds regardless,
|
|
251
|
+
which is why it's easy to miss in dev. Rule: touch `templates/` → `+1` the
|
|
252
|
+
version in the same commit.
|
|
253
|
+
|
|
254
|
+
- **Crash diagnostics.** Server stdout/stderr is tee'd to
|
|
255
|
+
`~/.halo/logs/desktop.log`; the last 30 lines also surface in the crash
|
|
256
|
+
dialog. Check that file first when a packaged build won't start.
|
|
257
|
+
|
|
258
|
+
- **No synchronous `window.confirm` / `window.prompt`.** Electron's renderer
|
|
259
|
+
can't block on a native dialog, so the preload (`packages/desktop/src/preload.cjs`)
|
|
260
|
+
shims both: the sync `window.confirm` shows an overlay but always returns
|
|
261
|
+
`false`, and `window.prompt` returns `null`. Admin code that needs the real
|
|
262
|
+
answer **must** use the async helpers in `packages/admin/src/shared/utils.ts`
|
|
263
|
+
— `confirmAction()` (→ `window.haloConfirm`) and `promptInput()` (→
|
|
264
|
+
`window.haloPrompt`) — and `await` them. Calling `window.confirm()` directly
|
|
265
|
+
silently no-ops in the desktop app (the symptom: clicking OK on a delete
|
|
266
|
+
confirm does nothing, because `if (!confirm(...)) return` always returns). In
|
|
267
|
+
a plain browser the helpers fall back to the native sync dialogs.
|
|
268
|
+
|
|
269
|
+
- **Cross-staging needs a full restage — auto-fast skips the native fixup.**
|
|
270
|
+
After editing `stage-runtime.mjs`, or any time you cross-stage a target whose
|
|
271
|
+
`node_modules` differs from what's on disk, run with `HALO_STAGE_FULL=1`. The
|
|
272
|
+
fast path only re-syncs compiled outputs and **reuses the existing
|
|
273
|
+
`node_modules`**, so it never re-runs the per-target native fixups
|
|
274
|
+
(`@parcel/watcher` swap, `better-sqlite3` prebuild-install). Symptom of
|
|
275
|
+
forgetting: the win/x64 dmg still carries the host's arm64 binaries.
|
|
276
|
+
|
|
277
|
+
- **`@parcel/watcher` binary doesn't cross-stage via `npm install`.** Its
|
|
278
|
+
per-platform binary package (`@parcel/watcher-win32-x64` etc.) declares
|
|
279
|
+
`os`/`cpu`, so `npm install` on a mac host **silently skips it** — leaving
|
|
280
|
+
only the host's `watcher-darwin-arm64` (dragged in by `pnpm deploy`), and the
|
|
281
|
+
packaged app crashes with *"no prebuild or local build of @parcel/watcher
|
|
282
|
+
found"*. `stage-runtime.mjs` `installParcelWatcherBinary()` fetches the right
|
|
283
|
+
one with `npm pack` (no os/cpu gate) and extracts it into every `@parcel`
|
|
284
|
+
scope it finds under `node_modules` (`findParcelScopesWithWatcher` walks for
|
|
285
|
+
them — one under the flat `hoisted` layout, several if a tree is ever staged
|
|
286
|
+
the default isolated way), removing the host's. Verify after
|
|
287
|
+
a build: `find …/win-unpacked -path '*watcher-*' -name '*.node' | xargs file`
|
|
288
|
+
should show only PE32 (win) / the target's Mach-O — no foreign arch.
|
|
289
|
+
(`better-sqlite3` and `node-pty` don't hit this — they use `prebuild-install`
|
|
290
|
+
and bundled multi-platform prebuilds respectively.)
|
|
291
|
+
|
|
292
|
+
- **`ENOTEMPTY` / `directory not empty` mid-stage or in electron-builder.** A
|
|
293
|
+
prior stage's `resources/{cli,server}-runtime/node_modules` (or
|
|
294
|
+
`dist/win-unpacked`) can fail to delete cleanly on macOS (open handles, deep
|
|
295
|
+
nesting). Hard-clean before a full restage:
|
|
296
|
+
`rm -rf resources/{cli-runtime,server-runtime,admin-out,node} dist/{win-unpacked,mac,mac-arm64}`
|
|
297
|
+
then rebuild.
|
|
298
|
+
|
|
299
|
+
- **A `--x64` / `--win` pack also emits a stray `Halo-0.1.5-arm64.dmg` — it's
|
|
300
|
+
poisoned, discard it.** electron-builder honours the yml's `arm64` mac target
|
|
301
|
+
even when you pass `--x64`, so it re-packages the *current* (x64/win-staged)
|
|
302
|
+
`resources/` as an arm64 dmg. That dmg's native binaries are the wrong arch.
|
|
303
|
+
The real per-target artifacts are `Halo-0.1.5.dmg` (x64) and `Halo Setup
|
|
304
|
+
0.1.5.exe` (win); only trust the arm64 dmg from an actual `dist:arm64` run.
|
|
305
|
+
|
|
306
|
+
## Verifying a build
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
# staged skills include send-file (and the other 6 built-ins)
|
|
310
|
+
ls packages/desktop/resources/server-runtime/templates/skills/
|
|
311
|
+
|
|
312
|
+
# compiled init carries the current template version + send-file
|
|
313
|
+
grep -o "TEMPLATE_VERSION = [0-9]*" packages/desktop/resources/server-runtime/dist/init.js
|
|
314
|
+
|
|
315
|
+
# build version stamp is clean (<version>-<sha>, no -dirty) — surfaces as
|
|
316
|
+
# "Halo v<version>" in the settings sidebar via GET /api/health
|
|
317
|
+
cat packages/desktop/dist/win-unpacked/resources/halo-version # or resources/halo-version pre-pack
|
|
318
|
+
|
|
319
|
+
# the dmg exists
|
|
320
|
+
ls -lh packages/desktop/dist/*.dmg
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Then mount the dmg, drag to /Applications, launch. First run shows the dark
|
|
324
|
+
setup window (password prompt); subsequent runs go straight to the workspace.
|
|
325
|
+
On upgrade, the server reseeds `~/.halo/global/skills/` from the bundled
|
|
326
|
+
templates because `TEMPLATE_VERSION` bumped — that's how a newly-added built-in
|
|
327
|
+
skill like `send-file` reaches existing users without manual steps.
|