@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,158 @@
|
|
|
1
|
+
# Development Environment
|
|
2
|
+
|
|
3
|
+
## Ports
|
|
4
|
+
|
|
5
|
+
- **8080** is code-server — don't touch, don't occupy, don't kill
|
|
6
|
+
- **9527** is Hono (Halo server); one process serves API + WebSocket + static frontend
|
|
7
|
+
|
|
8
|
+
## Browser testing
|
|
9
|
+
|
|
10
|
+
- URL: `http://localhost:9527/?folder=<url-encoded absolute workspace path>`
|
|
11
|
+
- Password: set via `halo setup` (stored as scrypt hash in `~/.halo/secrets/config.yaml server.password`) **or** `HALO_PASSWORD` env (plaintext — preferred for Docker / CI). Env wins over hash if both are set.
|
|
12
|
+
- Workspace path: any absolute directory you want to bind
|
|
13
|
+
|
|
14
|
+
## Deploy commands
|
|
15
|
+
|
|
16
|
+
### Frontend build (web only)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd packages/admin && npx next build --no-lint && node scripts/copy-monaco.mjs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Backend redeploy (server only)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd packages/server && npx tsc
|
|
26
|
+
kill -9 $(ss -tlnp | grep ':9527' | grep -oP 'pid=\K\d+') 2>/dev/null
|
|
27
|
+
sleep 1
|
|
28
|
+
nohup node dist/index.js >> /dev/null 2>&1 & # HALO_PASSWORD read from env/config
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Full deploy in one go
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd packages/admin && npx next build --no-lint && node scripts/copy-monaco.mjs
|
|
35
|
+
cd ../server && npx tsc
|
|
36
|
+
kill -9 $(ss -tlnp | grep ':9527' | grep -oP 'pid=\K\d+') 2>/dev/null
|
|
37
|
+
sleep 1
|
|
38
|
+
nohup node dist/index.js >> /dev/null 2>&1 & # HALO_PASSWORD read from env/config
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
- Node.js ≥ 22 (better-sqlite3 native binding is bound to v22)
|
|
44
|
+
- pnpm ≥ 9
|
|
45
|
+
- AWS credentials configured (`~/.aws/credentials` or env vars) with Bedrock access
|
|
46
|
+
- bubblewrap (`sudo apt install bubblewrap`) — OS-level sandbox for non-full access levels. Without it, only app-level path validation is active and `shell_exec` is blocked for non-full sessions
|
|
47
|
+
|
|
48
|
+
### nvm PATH
|
|
49
|
+
|
|
50
|
+
If the shell doesn't auto-load nvm:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export PATH=$HOME/.nvm/versions/node/v22.21.1/bin:$PATH
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Your deployment scripts should also prepend this line.
|
|
57
|
+
|
|
58
|
+
## Runtime directories
|
|
59
|
+
|
|
60
|
+
- SQLite: `data/halo.db` (auto-created on first start)
|
|
61
|
+
- Session files: `.halo/sessions/{agentId}/{sessionId}.json`
|
|
62
|
+
- Global config: `~/.halo/global/`
|
|
63
|
+
- Per-project config: `<workspace>/.halo/`
|
|
64
|
+
|
|
65
|
+
## Configuration sources
|
|
66
|
+
|
|
67
|
+
Three config file types, precedence **env vars > config.yaml / settings.yaml > code defaults**:
|
|
68
|
+
|
|
69
|
+
| File | Scope | Contents |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `~/.halo/secrets/config.yaml` | System | Port, password, CORS, timeouts, limits, logging — "infrastructure" settings |
|
|
72
|
+
| `~/.halo/secrets/settings.yaml` | User | Model, region, session behaviour — "preferences" |
|
|
73
|
+
| `<project>/.halo/settings.yaml` | Project | Overrides of global settings |
|
|
74
|
+
|
|
75
|
+
`init.ts` seeds these on first run with a per-category policy. Refresh trigger: `halo setup` always re-runs the seed, and the server's startup check re-runs it automatically when `~/.halo/global/.template-version` is behind the bundled `TEMPLATE_VERSION` (see `init.ts:TEMPLATE_VERSION` + `index.ts` startup block).
|
|
76
|
+
|
|
77
|
+
- **Always overwritten** (platform-owned, refreshed when the template version moves): `~/.halo/global/{prompts,models,docs}/`, `INSTRUCTIONS.md`, the built-in agent ids (`default`, `executor`, `deep-executor`, `__evo_agent__`, `__score__`, `__apply_agent__`), built-in skill ids (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `nova-web-search`, `halo`).
|
|
78
|
+
- **Built-in agents** keep the user's `model:` block on overwrite — the admin UI lets users change which model an agent uses, and that choice survives upgrades.
|
|
79
|
+
- **Optional skills** (`tavily-web-search`) install only when picked via `halo setup`; the opt-in list is `~/.halo/global/.installed-optional-skills`. Picked skills are force-overwritten alongside the always-overwritten set.
|
|
80
|
+
- **`secrets/config.yaml`** is leaf-merged: existing leaf `value`s preserved, new leaves added when a server upgrade introduces them.
|
|
81
|
+
- **`secrets/settings.yaml`** is created empty if missing and never touched again. Defaults live in `settings-schema.ts`.
|
|
82
|
+
|
|
83
|
+
## Environment variable overrides
|
|
84
|
+
|
|
85
|
+
Source: `packages/server/src/config.ts`
|
|
86
|
+
|
|
87
|
+
| Variable | Default | Config path | Description |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| `HALO_PORT` | `9527` | `config.yaml server.port` | Hono listen port |
|
|
90
|
+
| `HALO_PASSWORD` | (none) | `config.yaml server.password` (scrypt hash) | Plaintext login password. When set, takes precedence over the stored hash and bypasses scrypt entirely — intended for Docker / systemd / CI. The hash is set by `halo setup` for interactive installs. |
|
|
91
|
+
| `HALO_CORS_ORIGINS` | empty (reflect any origin) | `config.yaml server.cors_origins` | CORS allowlist (comma-separated). Empty = reflect any incoming Origin so credentials work cross-origin. Set explicit list to enforce strict CORS. |
|
|
92
|
+
| `HALO_FRONTEND_DIR` | `packages/admin/out` | — | Static frontend dir (resolved as absolute path from project root) |
|
|
93
|
+
| `HALO_MAX_CONTEXT_TOKENS` | `200000` | — | Model max context |
|
|
94
|
+
| `HALO_SHELL_TIMEOUT` | `120000` | `config.yaml timeout.shell_exec` | Shell command timeout (ms) |
|
|
95
|
+
| `HALO_WEB_FETCH_TIMEOUT` | `10000` | `config.yaml timeout.web_fetch` | web_fetch timeout |
|
|
96
|
+
| `HALO_MODEL_TIMEOUT` | `1800000` | `config.yaml timeout.model_request` | Per model-call wall-clock cap (ms, 30 min). A bare fetch has no default timeout, so a half-open connection would hang the request forever; on expiry the call aborts and the agent loop retries with backoff. |
|
|
97
|
+
| `HALO_SESSION_GRACE` | `300000` | `config.yaml timeout.session_grace` | WS-disconnect session grace |
|
|
98
|
+
| `HALO_TERMINAL_GRACE` | `300000` | `config.yaml timeout.terminal_grace` | Terminal disconnect grace |
|
|
99
|
+
| `HALO_MAX_CACHED_SESSIONS` | `50` | — | In-memory session cache |
|
|
100
|
+
| `HALO_LOG_MAX_SIZE` | `10485760` | `config.yaml logging.max_file_size` | Log file size cap (10 MB) |
|
|
101
|
+
| `HALO_LOG_MAX_FILES` | `5` | `config.yaml logging.max_files` | Retained rotated logs |
|
|
102
|
+
|
|
103
|
+
settings.yaml only (no env override):
|
|
104
|
+
- `general.session.max_queue_size` (default 256)
|
|
105
|
+
- `general.session.max_nesting_depth` (default 16)
|
|
106
|
+
- `general.agent.max_retries` (default 5)
|
|
107
|
+
- `general.compact.keep_messages` (default 5) — recent messages to keep uncompacted
|
|
108
|
+
- `general.compact.max_summary_input` (default 15000) — local compaction fallback input cap
|
|
109
|
+
- `general.compact.max_message_slice` (default 800) — local compaction per-message cap
|
|
110
|
+
- `general.compact.summarize_timeout_sec` (default 300) — self-compact timeout
|
|
111
|
+
- `general.sandbox.hidden_dirs` (default `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker`) — bwrap tmpfs overlays, scope: global
|
|
112
|
+
- `general.sandbox.hidden_files` (default `~/.npmrc,~/.bash_history,~/.gitconfig`) — bwrap /dev/null binds, scope: global
|
|
113
|
+
- `general.logging.level` (default `warn`) — log level: debug | info | warn | error
|
|
114
|
+
|
|
115
|
+
`<provider-id>.secrets.*` (server-only, hard-rejected by `{{}}` substitution; declared in `models/<provider-id>.yaml` `secrets:`):
|
|
116
|
+
- `aws-bedrock-claude-invoke.secrets.access_key_id` / `.secret_access_key` — AWS / Bedrock credentials
|
|
117
|
+
- `kimi.secrets.api_key` — Kimi (Moonshot AI)
|
|
118
|
+
- `deepseek.secrets.api_key` — DeepSeek
|
|
119
|
+
|
|
120
|
+
`<skill-id>.params.*` (referenceable from skills via `{{<skill-id>.params.<key>}}`, or short form `{{params.<key>}}` inside SKILL.md; declared in `skills/<id>/config.yaml`):
|
|
121
|
+
- `tavily-search.params.api_key` — example only; declared by whichever skill needs it
|
|
122
|
+
|
|
123
|
+
Hardcoded (config.ts, no override):
|
|
124
|
+
- `auth.tokenMaxAge` — 14 days
|
|
125
|
+
- `auth.refreshAfter` — 24 hours
|
|
126
|
+
- `model.compressAt` — default 0.8 (auto-compact threshold; configurable via `general.compact.compress_at` in settings.yaml)
|
|
127
|
+
|
|
128
|
+
settings.yaml-driven `general.limits.*` (no env override):
|
|
129
|
+
- `general.limits.shell_output_bytes` (default 5 MiB)
|
|
130
|
+
- `general.limits.web_fetch_bytes` (default 50 KiB)
|
|
131
|
+
- `general.limits.grep_default_matches` (default 50)
|
|
132
|
+
- `general.limits.tool_result_render_chars` (default 8000)
|
|
133
|
+
- `general.limits.ws_event_buffer` (default 5000)
|
|
134
|
+
- `general.limits.terminal_scrollback_bytes` (default 50000)
|
|
135
|
+
|
|
136
|
+
Two ways to authenticate:
|
|
137
|
+
|
|
138
|
+
1. **Interactive** — run `halo setup`, pick "Set / change password". The value is scrypt-hashed and written to `~/.halo/secrets/config.yaml server.password`. Best for personal installs.
|
|
139
|
+
2. **Env** — export `HALO_PASSWORD=...` before launching the server. The plaintext value is compared directly (no hashing) and takes precedence over any stored hash. Best for Docker / systemd / CI where a secrets manager already protects the env.
|
|
140
|
+
|
|
141
|
+
## Verify
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
curl http://localhost:9527/api/health
|
|
145
|
+
# expect: {"status":"ok", ...}
|
|
146
|
+
|
|
147
|
+
curl -s -o /dev/null -w "%{http_code}" http://localhost:9527
|
|
148
|
+
# expect: 200
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Coding conventions
|
|
152
|
+
|
|
153
|
+
- TypeScript strict, ESM only
|
|
154
|
+
- camelCase variables/functions, PascalCase types, kebab-case filenames
|
|
155
|
+
- React functional components; styling via Tailwind only; UI prefers shadcn/ui
|
|
156
|
+
- Log format `[Module] message`
|
|
157
|
+
- Bedrock model ID: `global.anthropic.claude-sonnet-4-6`, default region `us-east-1` (configured per agent via `agent.yaml model.endpoint`)
|
|
158
|
+
- File operations are sandboxed by bwrap (OS-level) + `assertPathAllowed` (app-level fallback) for non-full sessions
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Logging
|
|
2
|
+
|
|
3
|
+
File-backed logs: intercepts console, auto-rotates.
|
|
4
|
+
|
|
5
|
+
File: `packages/server/src/logger.ts`
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
On startup the logger intercepts `console.log` / `console.error` / `console.warn` / `console.debug`. Every call writes twice:
|
|
10
|
+
1. The original console (stdout/stderr) — live monitoring
|
|
11
|
+
2. A disk log file — persistent history
|
|
12
|
+
|
|
13
|
+
## Log locations
|
|
14
|
+
|
|
15
|
+
| Scenario | Directory | File |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Default (no workspace) | `~/.halo/logs/` | `server.log` |
|
|
18
|
+
| Workspace open | `<project>/.halo/logs/` | `server.log` |
|
|
19
|
+
|
|
20
|
+
When the WS handler receives a `subscribe` or `chat` with `projectId`, it calls `setLogDir()` to switch to the project's log directory.
|
|
21
|
+
|
|
22
|
+
## Log format
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
2026-04-19T10:30:00.000Z INFO [Server] Hono server listening on http://localhost:9527
|
|
26
|
+
2026-04-19T10:30:01.234Z ERROR [WS] Chat error: ThrottlingException
|
|
27
|
+
2026-04-19T10:30:02.567Z WARN [Orchestrator] Context overflow, auto-compacting...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Every line: `ISO timestamp` + `level (DEBUG/INFO/WARN/ERROR)` + `message`.
|
|
31
|
+
|
|
32
|
+
## Rotation
|
|
33
|
+
|
|
34
|
+
When the log file exceeds the cap:
|
|
35
|
+
1. Delete `server.log.{maxFiles}` (oldest)
|
|
36
|
+
2. Shift `server.log.N` → `server.log.N+1`
|
|
37
|
+
3. Rename `server.log` → `server.log.1`
|
|
38
|
+
4. New `server.log` starts fresh
|
|
39
|
+
|
|
40
|
+
### Config
|
|
41
|
+
|
|
42
|
+
| Config key | Default | Env var | Purpose |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| `logging.maxFileSize` | 10 MB | `HALO_LOG_MAX_SIZE` | Rotation threshold |
|
|
45
|
+
| `logging.maxFiles` | 5 | `HALO_LOG_MAX_FILES` | Retained rotated files |
|
|
46
|
+
|
|
47
|
+
Defined in `packages/server/src/config.ts`.
|
|
48
|
+
|
|
49
|
+
## Initialization
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { initLogger } from './logger.js'
|
|
53
|
+
initLogger()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Called once from `packages/server/src/index.ts` at startup. Steps:
|
|
57
|
+
1. Create the log directory
|
|
58
|
+
2. Replace console methods with the interceptor
|
|
59
|
+
3. Each interceptor calls the original method and appends to the log file
|
|
60
|
+
|
|
61
|
+
## Workspace switching
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { setLogDir } from './logger.js'
|
|
65
|
+
setLogDir('/path/to/project')
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Called from `packages/server/src/ws/handler.ts` when the client subscribes to a workspace.
|
|
69
|
+
|
|
70
|
+
## Error handling
|
|
71
|
+
|
|
72
|
+
Log writes use synchronous `fs.appendFileSync` and fail silently — logging must not crash the server.
|
|
73
|
+
|
|
74
|
+
## Viewing logs
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# live stdout (if started with nohup > /tmp/server.log)
|
|
78
|
+
tail -f /tmp/server.log
|
|
79
|
+
|
|
80
|
+
# file logs (always written, regardless of nohup redirection)
|
|
81
|
+
tail -f ~/.halo/logs/server.log
|
|
82
|
+
|
|
83
|
+
# project-specific logs
|
|
84
|
+
tail -f /path/to/your-project/.halo/logs/server.log
|
|
85
|
+
```
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Preview Plugin System
|
|
2
|
+
|
|
3
|
+
Canvas renders non-text files via a **plugin registry**. Each plugin declares the extensions it handles and a React component. Adding a new file type = write one plugin, register it. No changes to the core framework or Canvas panel.
|
|
4
|
+
|
|
5
|
+
Location: [`packages/admin/src/features/editor/previews/`](../../../packages/admin/src/features/editor/previews/)
|
|
6
|
+
|
|
7
|
+
## Layout
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
previews/
|
|
11
|
+
├── FilePreview.tsx Public entry. Looks up plugin by extension,
|
|
12
|
+
│ renders <Suspense><Component/></Suspense>.
|
|
13
|
+
├── types.ts PreviewPlugin, PreviewProps
|
|
14
|
+
├── registry.ts register() / getPlugin() / registeredExtensions() / isHeavyPreview()
|
|
15
|
+
├── ui/
|
|
16
|
+
│ ├── preview-shell.tsx Standard header (filename + Open-as-Text + Download + extraToolbar)
|
|
17
|
+
│ ├── use-preview-fetch.ts Hook: fetch + AbortController + parse, returns {data, error, loading}
|
|
18
|
+
│ └── print.ts Pop-up print helper
|
|
19
|
+
├── workers/ Parse workers (one per heavy format)
|
|
20
|
+
│ ├── worker-client.ts Generic WorkerClient<T> class — id-routed postMessage
|
|
21
|
+
│ ├── xlsx.worker.ts
|
|
22
|
+
│ └── docx.worker.ts
|
|
23
|
+
└── plugins/
|
|
24
|
+
├── index.ts Registers all built-in plugins
|
|
25
|
+
├── pdf.tsx Metadata (id, extensions, lazy Component)
|
|
26
|
+
├── pdf-view.tsx Actual React component
|
|
27
|
+
├── docx.tsx / docx-view.tsx
|
|
28
|
+
├── xlsx.tsx / xlsx-view.tsx
|
|
29
|
+
├── pptx.tsx / pptx-view.tsx
|
|
30
|
+
└── media.tsx / media-view.tsx
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Two-file-per-plugin pattern**: `foo.tsx` is tiny metadata (no runtime deps). `foo-view.tsx` holds the component and its heavy dependencies. The metadata file uses `React.lazy()` so the view file (and its deps) only loads when a user actually opens that file type.
|
|
34
|
+
|
|
35
|
+
## PreviewPlugin interface
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
interface PreviewPlugin {
|
|
39
|
+
id: string // stable id, e.g. 'pdf'
|
|
40
|
+
extensions: readonly string[] // lowercase, no dot — e.g. ['xlsx', 'xls', 'csv']
|
|
41
|
+
Component: React.ComponentType<PreviewProps>
|
|
42
|
+
heavy?: boolean // true = main-thread-heavy; active-only mount, skip MRU cache
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface PreviewProps {
|
|
46
|
+
name: string // full filename including extension
|
|
47
|
+
path: string // relative workspace path (or absolute for /tmp files)
|
|
48
|
+
viewUrl: string // for inline viewing, supports HTTP Range
|
|
49
|
+
downloadUrl: string // for forced download (used by the shell's Download button)
|
|
50
|
+
onOpenAsText?: () => void // set when the file can also be force-opened as text
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Adding a new file type
|
|
55
|
+
|
|
56
|
+
### 1. Create the view component
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// plugins/foo-view.tsx
|
|
60
|
+
'use client'
|
|
61
|
+
|
|
62
|
+
import type { PreviewProps } from '../types'
|
|
63
|
+
import { PreviewShell } from '../ui/preview-shell'
|
|
64
|
+
import { usePreviewFetch } from '../ui/use-preview-fetch'
|
|
65
|
+
|
|
66
|
+
export function FooPreview(props: PreviewProps) {
|
|
67
|
+
const { name, viewUrl, downloadUrl, onOpenAsText } = props
|
|
68
|
+
const { data, error, loading } = usePreviewFetch(viewUrl, async (buf) => {
|
|
69
|
+
// Parse `buf` here. For heavy parsing, call into a Worker (see Workers below).
|
|
70
|
+
return parseFoo(buf)
|
|
71
|
+
})
|
|
72
|
+
return (
|
|
73
|
+
<PreviewShell
|
|
74
|
+
name={name}
|
|
75
|
+
downloadUrl={downloadUrl}
|
|
76
|
+
onOpenAsText={onOpenAsText}
|
|
77
|
+
loading={loading}
|
|
78
|
+
error={error}
|
|
79
|
+
>
|
|
80
|
+
{data && <div className="h-full overflow-auto">{/* render your data */}</div>}
|
|
81
|
+
</PreviewShell>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 2. Declare the plugin
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// plugins/foo.tsx
|
|
90
|
+
'use client'
|
|
91
|
+
|
|
92
|
+
import { lazy } from 'react'
|
|
93
|
+
import type { PreviewPlugin } from '../types'
|
|
94
|
+
|
|
95
|
+
export const fooPlugin: PreviewPlugin = {
|
|
96
|
+
id: 'foo',
|
|
97
|
+
extensions: ['foo', 'foobar'],
|
|
98
|
+
Component: lazy(() => import('./foo-view').then((m) => ({ default: m.FooPreview }))),
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Register it
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// plugins/index.ts
|
|
106
|
+
import { fooPlugin } from './foo'
|
|
107
|
+
register(fooPlugin)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Done. The Canvas panel will:
|
|
111
|
+
- Treat `.foo` / `.foobar` as non-text (routes to preview instead of Monaco)
|
|
112
|
+
- Mount `FooPreview` inside an MRU cache (up to 5 concurrent plugins cached)
|
|
113
|
+
|
|
114
|
+
## PreviewShell — the standard header
|
|
115
|
+
|
|
116
|
+
Every plugin should wrap its content in `<PreviewShell>` for consistency. The shell gives you:
|
|
117
|
+
- Filename on the left
|
|
118
|
+
- Your plugin-specific buttons via `extraToolbar` (right-aligned, before the standard buttons)
|
|
119
|
+
- Standard Open-as-Text + Download buttons
|
|
120
|
+
- Loading overlay (pass `loading={true}`)
|
|
121
|
+
- Error state (pass `error={'…'}` — the children are hidden)
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<PreviewShell
|
|
125
|
+
name={name}
|
|
126
|
+
downloadUrl={downloadUrl}
|
|
127
|
+
onOpenAsText={onOpenAsText}
|
|
128
|
+
extraToolbar={myButtons} // optional
|
|
129
|
+
loading={loading}
|
|
130
|
+
error={error}
|
|
131
|
+
>
|
|
132
|
+
{content}
|
|
133
|
+
</PreviewShell>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Plugin-specific buttons use the shared `<ToolbarButton>`:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
<ToolbarButton onClick={...} title="Print">
|
|
140
|
+
<Printer className="h-3 w-3" />
|
|
141
|
+
<span>Print</span>
|
|
142
|
+
</ToolbarButton>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Workers — parsing off the main thread
|
|
146
|
+
|
|
147
|
+
If your format needs CPU-heavy parsing (non-trivial ArrayBuffer → structured data), run it in a Web Worker so the UI stays responsive even with several tabs parsing concurrently.
|
|
148
|
+
|
|
149
|
+
### 1. Write the worker
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// workers/foo.worker.ts
|
|
153
|
+
/// <reference lib="webworker" />
|
|
154
|
+
|
|
155
|
+
type Req = { id: number; buf: ArrayBuffer; meta?: unknown }
|
|
156
|
+
type Res = { id: number; ok: true; data: FooResult } | { id: number; ok: false; error: string }
|
|
157
|
+
|
|
158
|
+
self.addEventListener('message', (e: MessageEvent<Req>) => {
|
|
159
|
+
const { id, buf } = e.data
|
|
160
|
+
try {
|
|
161
|
+
const data = parseFoo(buf)
|
|
162
|
+
;(self as any).postMessage({ id, ok: true, data } satisfies Res)
|
|
163
|
+
} catch (err) {
|
|
164
|
+
;(self as any).postMessage({ id, ok: false, error: String(err) } satisfies Res)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 2. Wire it in the view
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { WorkerClient } from '../workers/worker-client'
|
|
173
|
+
|
|
174
|
+
let client: WorkerClient | null = null
|
|
175
|
+
function getClient() {
|
|
176
|
+
if (!client) {
|
|
177
|
+
client = new WorkerClient(
|
|
178
|
+
() => new Worker(new URL('../workers/foo.worker.ts', import.meta.url), { type: 'module' }),
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
return client
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Inside your component:
|
|
185
|
+
const { data } = usePreviewFetch(viewUrl, (buf, signal) =>
|
|
186
|
+
getClient().call<FooResult>(signal, buf),
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`WorkerClient.call(signal, buf, meta?)` handles id routing, transfer of the ArrayBuffer, and abort (when the caller's signal fires, the reply is dropped).
|
|
191
|
+
|
|
192
|
+
## `heavy: true` — when to use it
|
|
193
|
+
|
|
194
|
+
Set `heavy: true` when the preview:
|
|
195
|
+
- Needs DOM access (can't run in a Worker — e.g. `pptx-preview` draws to canvas)
|
|
196
|
+
- Is the dominant memory cost of the page (large canvas, many cached elements)
|
|
197
|
+
|
|
198
|
+
Effect: the MRU cache skips this plugin. Only the **active** instance mounts; switching away unmounts the component (releases memory and stops any in-flight work). Switching back re-fetches/re-renders.
|
|
199
|
+
|
|
200
|
+
Only pptx currently uses this. Don't set it by default — the MRU cache gives much faster switches.
|
|
201
|
+
|
|
202
|
+
## `onOpenAsText`
|
|
203
|
+
|
|
204
|
+
When the user right-clicks a previewable file in Explorer and picks "Open as Text", Canvas closes the preview tab and opens the raw file in Monaco. The preview plugin receives this as `props.onOpenAsText` — pass it straight through to `PreviewShell` and the button appears automatically.
|
|
205
|
+
|
|
206
|
+
## Server side
|
|
207
|
+
|
|
208
|
+
All previews fetch via `GET /api/files/download?inline=1` which:
|
|
209
|
+
- Streams the file (doesn't read entire buffer into memory)
|
|
210
|
+
- Supports HTTP `Range` (206 Partial Content) for video/audio seek + progressive load
|
|
211
|
+
- Aborts the read when the client disconnects (tab closed / URL changed)
|
|
212
|
+
|
|
213
|
+
See [`dev/api.md`](api.md) for the route details.
|
|
214
|
+
|
|
215
|
+
## Testing a new plugin
|
|
216
|
+
|
|
217
|
+
1. Drop a test file into your workspace
|
|
218
|
+
2. Click it in the Explorer — Canvas should open it in preview mode automatically
|
|
219
|
+
3. Verify:
|
|
220
|
+
- Loading overlay appears briefly
|
|
221
|
+
- Download button works (forces attachment download)
|
|
222
|
+
- Open-as-Text button works (closes preview, opens in Monaco)
|
|
223
|
+
- Closing the preview tab cleanly aborts any in-flight fetch/parse
|
|
224
|
+
- Opening a second file of the same type reuses the worker (no second worker instance — check DevTools → Application → Service Workers / the process graph)
|
|
225
|
+
- Switching between up to 5 preview tabs is instant (MRU cache); the 6th oldest unmounts
|