@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,82 @@
|
|
|
1
|
+
# Canvas — Requirements
|
|
2
|
+
|
|
3
|
+
Monaco-based multi-tab code editor + binary file previewer — surfaced in the UI as **Canvas**.
|
|
4
|
+
|
|
5
|
+
> Naming note: "Canvas" is the user-visible name (header label, this doc). Source code still uses `editor`
|
|
6
|
+
> (`packages/admin/src/features/editor/`, `useEditorStore`, `EditorPanel`) — those names are internal.
|
|
7
|
+
|
|
8
|
+
> Store scoping: `useEditorStore` is the **default singleton** powering the main Explorer Canvas.
|
|
9
|
+
> Nested `EditorPanel` instances that need isolated tabs/fileTree/selection (e.g. Skills editor)
|
|
10
|
+
> wrap their subtree in `<EditorStoreProvider>`; inside the provider, `useScopedEditorStore()`
|
|
11
|
+
> returns a fresh store instance instead of the singleton. Without a provider the hook falls
|
|
12
|
+
> back to the default, so regular call sites don't change. `EditorPanel` also accepts
|
|
13
|
+
> `showMaximize={false}` for nested contexts where viewport-fullscreen makes no sense.
|
|
14
|
+
|
|
15
|
+
## Core behaviour
|
|
16
|
+
|
|
17
|
+
### Multi-tab editing
|
|
18
|
+
- Click a file in Explorer (single click = select, double click = open tab)
|
|
19
|
+
- Each tab independently tracks content, original content, language, mtime
|
|
20
|
+
- Unsaved tabs show a small red dot
|
|
21
|
+
- Closing an unsaved tab prompts for confirmation
|
|
22
|
+
|
|
23
|
+
### Save
|
|
24
|
+
- `Cmd+S` / `Ctrl+S` calls `PUT /api/files`
|
|
25
|
+
- Server returns the new `modifiedAt`; the tab updates its mtime
|
|
26
|
+
|
|
27
|
+
### Auto-refresh (mtime-based)
|
|
28
|
+
When agents write files through tools, the canvas detects it:
|
|
29
|
+
1. Periodically poll `GET /api/files/stat`
|
|
30
|
+
2. If `diskMtime > tab.mtime`, pull fresh content
|
|
31
|
+
3. Skip if the user has unsaved edits (don't overwrite)
|
|
32
|
+
|
|
33
|
+
### Tab persistence
|
|
34
|
+
Open tabs and the active tab live in localStorage; survive refresh.
|
|
35
|
+
|
|
36
|
+
### Diff view
|
|
37
|
+
- Tracked files → `GET /api/files/diff` for git diff
|
|
38
|
+
- Monaco left/right compare view
|
|
39
|
+
|
|
40
|
+
### Binary previews
|
|
41
|
+
Non-text files go through `FilePreview`, which looks up a **plugin** in the preview registry by file extension. Built-in plugins:
|
|
42
|
+
- **PDF** — browser-native iframe
|
|
43
|
+
- **DOCX / DOC** — `mammoth` → HTML (parsed in a Web Worker so the UI stays responsive)
|
|
44
|
+
- **XLSX / XLS / CSV** — `xlsx` (SheetJS) → table (parsed in a Web Worker; CSV decoded as UTF-8 with BOM strip)
|
|
45
|
+
- **PPTX / PPT** — `pptx-preview` list mode. Fidelity is approximate — complex animations / SmartArt / embedded fonts may not render perfectly; users can download for the exact source. Flagged `heavy: true` so only the *active* pptx mounts (canvas-based rendering needs the main thread)
|
|
46
|
+
- **Images / video / audio** — native `<img>` / `<video>` / `<audio>`, supports HTTP Range for seek-without-full-download
|
|
47
|
+
|
|
48
|
+
All previews share a **standard header** (`PreviewShell`) with filename, Download, Open-as-Text, and an `extraToolbar` slot for plugin-specific buttons (e.g. DOCX Print, XLSX sheet tabs).
|
|
49
|
+
|
|
50
|
+
Fetched via `GET /api/files/download?inline=1` (supports Range, streams on the server).
|
|
51
|
+
|
|
52
|
+
Adding new file types is a plugin concern — see `dev/previews.md` for the extension guide.
|
|
53
|
+
|
|
54
|
+
### Renderable text formats (Markdown / HTML)
|
|
55
|
+
Markdown and HTML open as text in Monaco *and* have a rendered view — Canvas defaults to the **rendered** view since the primary audience is an AI generating reports / pages for humans to read.
|
|
56
|
+
- Header shows an **Edit / Preview** toggle (same `textRenderMode` state for both formats)
|
|
57
|
+
- **Markdown** → `MarkdownPreview` (react-markdown + GFM; relative image `src` rewritten to the download endpoint so local images work)
|
|
58
|
+
- **HTML** → sandboxed iframe (`sandbox="allow-same-origin"`; scripts, top-navigation, forms, pop-ups all blocked — safe against hostile HTML)
|
|
59
|
+
- Toggle to Edit → Monaco source, Cmd+S saves as usual
|
|
60
|
+
|
|
61
|
+
### Preview caching (MRU)
|
|
62
|
+
Recently opened preview tabs stay mounted (up to 5, MRU) so switching between them doesn't re-fetch or re-parse. Plugins flagged `heavy: true` bypass the cache — only the active instance mounts, others unmount. Closing a preview tab removes it from the cache immediately (aborting any in-flight fetch).
|
|
63
|
+
|
|
64
|
+
### File metadata in header
|
|
65
|
+
The Canvas header shows `(size · Created … · Modified …)` for the active tab. Both text tabs and preview tabs populate this — preview tabs fetch `GET /api/files/stat` on open (and on tab restore from localStorage) since they never read content.
|
|
66
|
+
|
|
67
|
+
### Selection tracking
|
|
68
|
+
Canvas tracks current selection and cursor. When `contextEnabled` is on, the selection is auto-injected into chat messages as context.
|
|
69
|
+
|
|
70
|
+
### Maximize
|
|
71
|
+
The header has a maximize button on the far right. Toggling it expands Canvas to fill the entire viewport — activity bar, sidebar, and bottom panel are hidden. The state persists in localStorage so reloads keep the mode. Press `Esc` to exit — except when focus is inside an input, Monaco, or Quick Open, so those can handle `Esc` first.
|
|
72
|
+
|
|
73
|
+
Canvas (including Monaco instances, file tree, and open tab contents) stays mounted across activity-tab switches and maximize toggles — switching away and back does **not** reload content.
|
|
74
|
+
|
|
75
|
+
## Shortcuts
|
|
76
|
+
|
|
77
|
+
| Shortcut | Action |
|
|
78
|
+
|---|---|
|
|
79
|
+
| Cmd/Ctrl + P | Quick Open (fuzzy file search) |
|
|
80
|
+
| Cmd/Ctrl + S | Save current tab |
|
|
81
|
+
| Alt + W | Close current tab (Cmd+W cannot be overridden in browsers) |
|
|
82
|
+
| Esc | Exit maximized Canvas (when not inside an input / Monaco / Quick Open) |
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Explorer — Requirements
|
|
2
|
+
|
|
3
|
+
VS Code-style file tree sidebar with multi-select, drag-drop, right-click menu.
|
|
4
|
+
|
|
5
|
+
## Workspace switching
|
|
6
|
+
|
|
7
|
+
### Initialization
|
|
8
|
+
- URL `?folder=/abs/path` → use that directly as workspace
|
|
9
|
+
- No `?folder` → call `GET /api/fs/home`, take the home directory as fallback, write it back into the URL; UI behaves identically
|
|
10
|
+
- There's always a valid workspace; every frontend feature (file tree / sessions / agents / settings) depends on it
|
|
11
|
+
|
|
12
|
+
### Path input
|
|
13
|
+
- Explorer top bar has a path input + 📁🔍 picker button
|
|
14
|
+
- Pressing Enter calls `GET /api/fs/exists?path=...` first — a miss shows an alert, no switch
|
|
15
|
+
|
|
16
|
+
### Directory picker (FolderPicker modal)
|
|
17
|
+
Visual directory browser opened by the 📁🔍 button:
|
|
18
|
+
- Top: breadcrumb input (paste paths here) + ⬆ up + 🏠 home
|
|
19
|
+
- Middle: `GET /api/fs/browse?path=...` pulls the current directory's children
|
|
20
|
+
- Drops entries starting with `.`
|
|
21
|
+
- Single click = select (updates the current path only)
|
|
22
|
+
- Double click = enter
|
|
23
|
+
- Bottom: Cancel / Open. Open triggers the same switch flow as Enter (including existence check).
|
|
24
|
+
|
|
25
|
+
## Core behaviour
|
|
26
|
+
|
|
27
|
+
### File tree (lazy)
|
|
28
|
+
- Root level: `GET /api/files/tree?projectId=xxx` returns only the first level
|
|
29
|
+
- Subdirectories: only fetched on expand via `?path=<subdir>`
|
|
30
|
+
- Every directory node carries `hasChildren: boolean` so the arrow renders correctly
|
|
31
|
+
- Directories first, alphabetical
|
|
32
|
+
- Hides only well-known noise: `.git`, `.DS_Store`, `node_modules`, `__pycache__`. Other dotfiles (`.gitignore`, `.env`, `.vscode/` etc.) are shown — modern IDE convention
|
|
33
|
+
- No max depth limit
|
|
34
|
+
- Expansion state lives in localStorage; restored on refresh and lazily reloaded
|
|
35
|
+
- WebSocket `file:changed` events incrementally update loaded branches; unloaded branches are left alone (re-fetched on expand)
|
|
36
|
+
|
|
37
|
+
### Selection model
|
|
38
|
+
VS Code-style highlight selection (no checkboxes):
|
|
39
|
+
- **Click**: select and highlight
|
|
40
|
+
- **Double click**: open in editor tab
|
|
41
|
+
- **Ctrl/Cmd + click**: toggle multi-select
|
|
42
|
+
- **Shift + click**: range select
|
|
43
|
+
|
|
44
|
+
### Drag-move
|
|
45
|
+
- Selected files/folders drag onto a target dir
|
|
46
|
+
- Multi-file drag supported
|
|
47
|
+
- Each file goes through `POST /api/files/rename` with `{oldPath, newPath}`
|
|
48
|
+
|
|
49
|
+
### Right-click menu
|
|
50
|
+
|
|
51
|
+
Items shown depend on the click target:
|
|
52
|
+
|
|
53
|
+
| Action | Shown when | API / behavior |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| New File / New Folder | Always | `POST /api/files/new` · `mkdir`; auto-expands the parent folder so the inline input is visible |
|
|
56
|
+
| Open in Integrated Terminal | Always | Spawns a terminal at the target dir (or file's parent) |
|
|
57
|
+
| Open to the Side | File only | Splits the editor and opens the file in the right pane |
|
|
58
|
+
| Download | File only | `GET /api/files/download?path=...` |
|
|
59
|
+
| Rename | Single file/folder | `POST /api/files/rename` |
|
|
60
|
+
| Delete | Single or multi-select | `DELETE /api/files?path=...` (with confirm) |
|
|
61
|
+
| Open as Workspace | Folder only | Switches the active workspace to that folder, reusing the path-input switch flow (validate → persist → reload). Shown at the bottom of the menu |
|
|
62
|
+
|
|
63
|
+
The menu auto-clamps to the viewport, so right-clicking near the window edge does not clip the bottom items.
|
|
64
|
+
|
|
65
|
+
### Modification indicator
|
|
66
|
+
Files with unsaved editor edits show a coloured dot in the tree (synced from `editorStore.modifiedPaths`).
|
|
67
|
+
|
|
68
|
+
### Git decorations
|
|
69
|
+
When the workspace is a git repo, the tree also colors file/folder names + badges by git status, and dims `.gitignore`'d paths — driven by `/api/git/status` + `/api/git/ignored`. See [source-control.md](source-control.md#explorer-git-decorations). (A real change wins over ignored; a non-repo workspace carries no decorations.)
|
|
70
|
+
|
|
71
|
+
### File type icons
|
|
72
|
+
Extension-to-icon mapping across common filetypes.
|
|
73
|
+
|
|
74
|
+
### Quick Open
|
|
75
|
+
- `Cmd+P` / `Ctrl+P` opens fuzzy file search
|
|
76
|
+
- Query via `GET /api/files/search?projectId=X&q=...&limit=50` (150 ms debounce)
|
|
77
|
+
- Empty query doesn't display (avoids scanning the whole project)
|
|
78
|
+
- Enter opens in a new tab
|
|
79
|
+
|
|
80
|
+
### `@` file mention (chat input)
|
|
81
|
+
- Typing `@` in the chat input opens the file selector
|
|
82
|
+
- Query via `GET /api/files/search?projectId=X&q=<after-@>&limit=15` (120 ms debounce)
|
|
83
|
+
- Selecting inserts a path chip
|
|
84
|
+
|
|
85
|
+
### `@scope` directory reference (chat input)
|
|
86
|
+
- Typing `@scope ` opens a directory-only selector (query via `GET /api/files/search?...&dirsOnly=1`)
|
|
87
|
+
- Unlike `@` mention (which lifts the path into a separate chip list), `@scope <dir>` stays as **literal text** in the message — the server expands it into that directory's scoped `.halo/INSTRUCTIONS.md` for the turn (see [prompt-system.md](../design/prompt-system.md#directory-scoped-instructions-scope))
|
|
88
|
+
|
|
89
|
+
Quick Open and `@` mention both use the search API (not the lazy tree) so unexpanded files can still be found.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Product Overview
|
|
2
|
+
|
|
3
|
+
> "Tiangong Kaiwu" — throw in an idea, the agents build it.
|
|
4
|
+
> Halo = the international spelling of "Kaiwu"; "halo" in Finnish hints at a spring source.
|
|
5
|
+
|
|
6
|
+
## One-liner
|
|
7
|
+
|
|
8
|
+
**Halo is a multi-agent collaborative workspace. The user drives the entire delivery of complex projects through natural language.**
|
|
9
|
+
|
|
10
|
+
## Problems it solves
|
|
11
|
+
|
|
12
|
+
### 1. Multi-agent collaboration is opaque to users
|
|
13
|
+
|
|
14
|
+
Cursor / Claude Code / Devin are developer tools at heart — users have to configure agents, manage context, wire up workflows. Halo hides the orchestration: users just chat; the main agent decides how to split up work.
|
|
15
|
+
|
|
16
|
+
> Halo is a **self-orchestrating harness** wrapped in a human-readable workspace.
|
|
17
|
+
|
|
18
|
+
### 2. Agent work products get lost
|
|
19
|
+
|
|
20
|
+
Existing memory systems are patchwork — MEMORY.md is manual notes, vector search is semantic crumbs. An agent makes a slide deck and remembers "I made a slide deck", but the design thinking, iterations, reasoning chain are gone.
|
|
21
|
+
|
|
22
|
+
Halo's answer: **everything lives in the workspace. Knowledge is tied to the project, not the agent.**
|
|
23
|
+
|
|
24
|
+
## Role model
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
User
|
|
28
|
+
| natural language
|
|
29
|
+
v
|
|
30
|
+
+------------------+
|
|
31
|
+
| Main Agent |
|
|
32
|
+
| (Orchestrator) |
|
|
33
|
+
| Understands intent,
|
|
34
|
+
| decomposes tasks,
|
|
35
|
+
| creates/schedules sub-agents,
|
|
36
|
+
| escalates key decisions
|
|
37
|
+
+---+------+------+-+
|
|
38
|
+
| | |
|
|
39
|
+
v v v
|
|
40
|
+
Sub-A Sub-B Sub-C
|
|
41
|
+
(each with its own tools + skills)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**The main agent is a proposer, not a dictator.** It lays out the plan, the user asks "why", adjusts, confirms, and only then does it execute. The user can pause, revert, or redirect at any step.
|
|
45
|
+
|
|
46
|
+
## Core principles
|
|
47
|
+
|
|
48
|
+
1. **Thinking is visible** — the user sees task breakdowns, progress, file changes, tool calls. Not a black box.
|
|
49
|
+
2. **User in the loop** — the user always has veto power. Any step can be paused, modified, or rejected.
|
|
50
|
+
3. **Structured workspace** — all knowledge, decisions, rejected alternatives, and intermediate state are saved as project files. Agents don't need "memory" — they have archives.
|
|
51
|
+
4. **Progressive autonomy** — v1: every step needs the human. v2: user-defined autonomy scope. v3: the agent decides what to ask and what to just do.
|
|
52
|
+
|
|
53
|
+
## Competitive comparison
|
|
54
|
+
|
|
55
|
+
| Dimension | Cursor / Claude Code | Devin | MetaGPT | Manus | **Halo** |
|
|
56
|
+
|---|---|---|---|---|---|
|
|
57
|
+
| Target user | Developers | Developers | Developers | General | **Anyone** |
|
|
58
|
+
| Agent count | 1 | 1 | Multiple (preset) | 1 | **1 main + N sub** |
|
|
59
|
+
| Orchestration | Manual | Black-box | Pipeline | Black-box | **Self-orchestrating + user override** |
|
|
60
|
+
| Process visibility | Low | Low | None | Low | **High (the key differentiator)** |
|
|
61
|
+
| Mid-run intervention | Yes | No | No | Limited | **Yes** |
|
|
62
|
+
| Project memory | Limited | Limited | None | None | **Structured workspace** |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Session — Requirements
|
|
2
|
+
|
|
3
|
+
Session history viewer: hierarchy tree, message playback, debug mode, system prompt inspection.
|
|
4
|
+
|
|
5
|
+
## Core behaviour
|
|
6
|
+
|
|
7
|
+
### Session tree view
|
|
8
|
+
- Top-level sessions (no parentSessionId) act as roots; child sessions render indented
|
|
9
|
+
- Each row shows: title, agent-name badge, message count, relative time, and status icons (StopCircle for stopped, amber Archive for archived)
|
|
10
|
+
- Clicking loads the full message list into SessionChatPanel
|
|
11
|
+
- Collapse / expand via arrow buttons
|
|
12
|
+
- Clicking the count badge (e.g. "+3") shows total descendant count
|
|
13
|
+
- Inline title rename (admin-only): a hover pencil on any row — root **or** sub-agent — opens an inline input (Enter commits, Escape cancels, blur commits); persists via `PATCH /api/sessions/logs/:id`
|
|
14
|
+
- Infinite scroll loads more roots in pages; a silent reload (after streaming ends, or a delete / create / archive elsewhere) re-fetches the **same depth** already scrolled to rather than snapping back to the first page. Capped at 300 top-level rows — past that, "load more" stops (older sessions, e.g. from a busy Slack channel, aren't worth scrolling to)
|
|
15
|
+
|
|
16
|
+
### Message viewer
|
|
17
|
+
- All messages rendered by role
|
|
18
|
+
- Assistant messages render Markdown
|
|
19
|
+
- System messages summarise tool calls
|
|
20
|
+
|
|
21
|
+
### Debug mode
|
|
22
|
+
Top Debug toggle (Bug icon). When on:
|
|
23
|
+
- **Normal mode**: user message + assistant reply (with inline tool-call cards); sub-agent notifications hidden
|
|
24
|
+
- **Debug mode**: every message, including:
|
|
25
|
+
- **Context / System Prompt** (purple) — full injected prompt, expandable
|
|
26
|
+
- **Tool Call** (blue) — full tool input JSON, expandable
|
|
27
|
+
- **Tool Result** (green/red) — full tool output JSON, expandable
|
|
28
|
+
- **Usage** — token counts, latency (ttft / e2e), model ID, cache hit ratio
|
|
29
|
+
- Sub-agent messages carry an agent-name badge
|
|
30
|
+
|
|
31
|
+
### System prompt viewer
|
|
32
|
+
The Prompt button (FileText icon) shows the system prompt used by this session. Extracted from the first `context`-type message; displays the full injected prompt (AGENT.md + INSTRUCTIONS.md + INDEX.md + TOOL_GUIDELINES + skills).
|
|
33
|
+
|
|
34
|
+
### Session lifecycle
|
|
35
|
+
- Created on the first `chat` WS message (with a new sessionId)
|
|
36
|
+
- Auto-saved after every `complete` event
|
|
37
|
+
- Sessions restore from disk on refresh / WS reconnect (loaded at subscribe time; running sessions reattach from the detached pool)
|
|
38
|
+
- Deleting a session removes the JSON file and cascade-deletes all descendants in SQLite
|
|
39
|
+
- Assistant messages embed a `toolCalls` array so tool-call cards persist across refresh
|
|
40
|
+
|
|
41
|
+
### Non-destructive /session new
|
|
42
|
+
`/session new` (session:clear) **does not** destroy the old session:
|
|
43
|
+
1. Save current session to disk
|
|
44
|
+
2. Create a background handler to keep the old session's in-flight events flowing
|
|
45
|
+
3. Reset client state, enter empty conversation
|
|
46
|
+
4. The old session's sub-agents keep running independently
|
|
47
|
+
5. Switching back loads from the file
|
|
48
|
+
|
|
49
|
+
## Session categorisation
|
|
50
|
+
|
|
51
|
+
| Scenario | Display |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Main session | Sidebar top level |
|
|
54
|
+
| Child session (has parentSessionId) | Nested under parent |
|
|
55
|
+
| Stopped session | Icon decorated with StopCircle |
|
|
56
|
+
| Archived session | Shown dimmed (50% opacity) with an amber Archive icon — the list fetches `includeArchived=1`, so archived rows stay visible for inspection rather than being hidden |
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
Unified session logs API (recommended):
|
|
61
|
+
|
|
62
|
+
| Operation | Endpoint |
|
|
63
|
+
|---|---|
|
|
64
|
+
| List | `GET /api/sessions/logs?projectId=...` |
|
|
65
|
+
| Read | `GET /api/sessions/logs/:id?projectId=...` |
|
|
66
|
+
| Delete | `DELETE /api/sessions/logs/:id?projectId=...` |
|
|
67
|
+
|
|
68
|
+
The List endpoint serves two consumers: this tree view (default — roots + all
|
|
69
|
+
descendants, paginated, rebuilt into a tree client-side) and the chat-header
|
|
70
|
+
recent-sessions dropdown (`rootOnly=1` — a flat list of roots). Both paginate
|
|
71
|
+
via `cursor` / `nextCursor`. See [dev/api.md](../dev/api.md#get-apisessionslogsprojectidabsrootonly01includearchived01cursormslimitn) for the full contract.
|
|
72
|
+
|
|
73
|
+
Implementation detail: [design/session.md](../design/session.md).
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Settings — Requirements
|
|
2
|
+
|
|
3
|
+
Global + workspace configuration. The shape mirrors VSCode's `contributes.configuration` model: **schema** (declared by each package — provider yaml, skill yaml, server built-ins) is separate from **values** (stored in the user's `settings.yaml`).
|
|
4
|
+
|
|
5
|
+
## Storage layout
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
# ~/.halo/secrets/settings.yaml — values only
|
|
9
|
+
general: # built-in declarer (server itself)
|
|
10
|
+
session:
|
|
11
|
+
max_queue_size: 256
|
|
12
|
+
max_nesting_depth: 16
|
|
13
|
+
compact:
|
|
14
|
+
keep_messages: 5
|
|
15
|
+
max_summary_input: 15000
|
|
16
|
+
...
|
|
17
|
+
sandbox:
|
|
18
|
+
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker"
|
|
19
|
+
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig"
|
|
20
|
+
writable_dirs: "" # e.g. ~/.kiro,~/.local/share/kiro-cli — rw bind-mounts in bwrap; ignored for readonly sessions
|
|
21
|
+
logging:
|
|
22
|
+
level: warn
|
|
23
|
+
|
|
24
|
+
aws-bedrock-claude-invoke: # provider id from models/<id>.yaml
|
|
25
|
+
secrets:
|
|
26
|
+
access_key_id: ""
|
|
27
|
+
secret_access_key: ""
|
|
28
|
+
|
|
29
|
+
kimi:
|
|
30
|
+
secrets:
|
|
31
|
+
api_key: <<KIMI_API_KEY>>
|
|
32
|
+
|
|
33
|
+
deepseek:
|
|
34
|
+
secrets:
|
|
35
|
+
api_key: <<DEEPSEEK_API_KEY>>
|
|
36
|
+
|
|
37
|
+
tavily-search: # skill id from skills/<id>/config.yaml
|
|
38
|
+
params:
|
|
39
|
+
api_key: <<TAVILY_API_KEY>>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The path always reads as `<namespace-id>.<kind>s.<key>`:
|
|
43
|
+
- `<namespace-id>` is `general`, a provider id, or a skill id
|
|
44
|
+
- `<kind>` is `param` or `secret`
|
|
45
|
+
- `<key>` is the leaf, dotted for grouping (e.g. `general.compact.keep_messages`)
|
|
46
|
+
|
|
47
|
+
`general` is the only namespace that doesn't follow the `<id>.{params|secrets}.<key>` pattern — its declared keys are flat (`general.<key>`) since the server is the implicit declarer.
|
|
48
|
+
|
|
49
|
+
## Two kinds of fields
|
|
50
|
+
|
|
51
|
+
| Kind | Reachable from agent? | UI render |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| **`params`** | Yes — via `{{<id>.params.<key>}}` placeholders. Substituted at `shell_exec` time. | Plain text / number input. |
|
|
54
|
+
| **`secrets`** | Never. Server-side only (model providers, signing keys). The placeholder renderer rejects `{{<id>.secrets.…}}`; the API returns masked values to the browser. | Masked password input; `<<ENV>>` references shown plainly (they're not the real value). |
|
|
55
|
+
|
|
56
|
+
## Schema declaration
|
|
57
|
+
|
|
58
|
+
Schema lives **inside the package**, alongside its other files:
|
|
59
|
+
|
|
60
|
+
### Provider secrets — `models/<provider-id>.yaml`
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
id: aws-bedrock-claude-invoke
|
|
64
|
+
displayName: AWS Bedrock Claude (Invoke API)
|
|
65
|
+
defaultEndpoint: https://bedrock-runtime.us-west-2.amazonaws.com
|
|
66
|
+
|
|
67
|
+
secrets:
|
|
68
|
+
- key: access_key_id
|
|
69
|
+
description: AWS Access Key ID
|
|
70
|
+
description_zh: AWS Access Key ID
|
|
71
|
+
secret: true
|
|
72
|
+
- key: secret_access_key
|
|
73
|
+
description: AWS Secret Access Key
|
|
74
|
+
description_zh: AWS Secret Access Key
|
|
75
|
+
secret: true
|
|
76
|
+
|
|
77
|
+
models: [...]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Skill params/secrets — `skills/<skill-id>/config.yaml`
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
params:
|
|
84
|
+
- key: api_key
|
|
85
|
+
description: Tavily API Key
|
|
86
|
+
description_zh: Tavily 搜索 API Key
|
|
87
|
+
default: <<TAVILY_API_KEY>>
|
|
88
|
+
secret: true
|
|
89
|
+
secrets: []
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### General — built-in
|
|
93
|
+
|
|
94
|
+
Declared in [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`. The server itself is the implicit declarer. Keys: `language`, `agent.*`, `session.*`, `compact.*`, `sandbox.*`, `logging.*`, `evolution.*`, `limits.*`.
|
|
95
|
+
|
|
96
|
+
`evolution.*` controls the self-evolution subsystem (see [plans/self-evolution.md](../plans/self-evolution.md)). All `evolution.*` keys are `globalOnly` — they live in `~/.halo/secrets/settings.yaml` only, not workspace settings. Notable knobs: `evolution.level` (`L0` = off, `L1` = human + LLM assist), `evolution.max_concurrent_run` / `max_concurrent_apply` (wrapper concurrency caps), `evolution.run_timeout_minutes` / `apply_timeout_minutes` (heartbeat timeouts), `evolution.max_attempts` (per-row retry cap), `evolution.triggers.pre_compact` (snapshot session before compaction).
|
|
97
|
+
|
|
98
|
+
`agent.default_provider` is rendered as an `enum` whose options are the provider ids found under `~/.halo/global/models/*.yaml`. It controls **which provider a freshly scaffolded agent.yaml uses** — the model id, endpoint, prompt-caching TTL and thinking defaults are then derived from that provider's YAML (`defaultModelId`, `defaultEndpoint`, per-model `capabilities.promptCaching.default`, `capabilities.thinking.defaultEnabled / default / defaultBudgetTokens`). Existing agents are not retroactively touched. Implementation: [packages/server/src/routes/agent-configs.ts](../../../packages/server/src/routes/agent-configs.ts) `buildScaffoldModelBlock()`.
|
|
99
|
+
|
|
100
|
+
### Field attributes
|
|
101
|
+
|
|
102
|
+
| Attribute | Required | Purpose |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `key` | yes | Leaf key under the namespace |
|
|
105
|
+
| `description` | no | English description rendered as help text |
|
|
106
|
+
| `description_zh` | no | Chinese description (UI picks based on lang) |
|
|
107
|
+
| `default` | no | Placeholder shown when the value is unset; supports `<<ENV>>` |
|
|
108
|
+
| `secret` | no | `true` → masked in API responses + password input in UI |
|
|
109
|
+
|
|
110
|
+
## Scope: global vs. workspace
|
|
111
|
+
|
|
112
|
+
| Scope | File | Priority |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| Global | `~/.halo/secrets/settings.yaml` | Base |
|
|
115
|
+
| Workspace | `<project>/.halo/settings.yaml` | Overrides global, key by key |
|
|
116
|
+
|
|
117
|
+
Read order: `<schema default> <- <global> <- <workspace>`.
|
|
118
|
+
|
|
119
|
+
The Settings page shows source badges per field:
|
|
120
|
+
- `workspace` (green-blue, override applied here)
|
|
121
|
+
- `inherited from global` (grey, value pulled from global because workspace has none)
|
|
122
|
+
- `unset` (no value at any layer; the `default` is shown as placeholder)
|
|
123
|
+
|
|
124
|
+
A Reset button on each field removes the value at the current scope, letting it fall back to the lower scope (or unset).
|
|
125
|
+
|
|
126
|
+
## Environment variable injection
|
|
127
|
+
|
|
128
|
+
Values can carry `<<ENV_NAME>>` placeholders. They're expanded:
|
|
129
|
+
- At `shell_exec` time inside values resolved through `{{<id>.params.<key>}}` — see [workspace-tools.ts](../../../packages/server/src/tools/workspace-tools.ts) `substituteSecrets`.
|
|
130
|
+
- At read time when server-side code calls `getServerSecret(namespaceId, key)` — see [config.ts](../../../packages/server/src/config.ts).
|
|
131
|
+
|
|
132
|
+
**Trust boundary**: `<<ENV>>` is only expanded inside settings-resolved values. Raw cmd text the agent writes is not scanned — `shell_exec "echo <<HOME>>"` keeps the literal. This prevents an agent from naming an env var and forcing the server to dump it.
|
|
133
|
+
|
|
134
|
+
Env var unset → the `<<ENV_NAME>>` literal stays verbatim, plus `[md-vars] Env var "X" not set — keeping <<X>> literal` in the server log. The Settings UI returns the literal too — the browser never sees the real env value.
|
|
135
|
+
|
|
136
|
+
## Agent visibility
|
|
137
|
+
|
|
138
|
+
Placeholder syntax in MD bodies (SKILL.md / AGENT.md):
|
|
139
|
+
|
|
140
|
+
| In MD body | Result |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `{{<skill-id>.params.<key>}}` (long form) | Replaced with the value (after `<<ENV>>` resolution) at `shell_exec` time |
|
|
143
|
+
| `{{params.<key>}}` (short form) inside a SKILL.md | Auto-rewritten to `{{<this-skill-id>.params.<key>}}` at `activate_skill` |
|
|
144
|
+
| `{{<id>.secrets.<key>}}` | Hard-rejected — kept as literal, server logs a whitelist warning |
|
|
145
|
+
| `{{general.compact.keep_messages}}` | Same — kept literal |
|
|
146
|
+
| `{{args}}`, `{{workspace_root}}`, etc. | Built-ins, replaced at render time |
|
|
147
|
+
|
|
148
|
+
Enforced in two places:
|
|
149
|
+
- [md-vars.ts](../../../packages/server/src/prompts/md-vars.ts) `renderMdBody` — only matches `^[\w-]+\.params\.[\w-][\w.-]*$`.
|
|
150
|
+
- [workspace-tools.ts](../../../packages/server/src/tools/workspace-tools.ts) `substituteSecrets` — same regex on `{{}}` placeholders.
|
|
151
|
+
|
|
152
|
+
A malicious skill that tries `curl -H "Bearer {{aws-bedrock-claude-invoke.secrets.secret_access_key}}"` gets the literal placeholder, not the value.
|
|
153
|
+
|
|
154
|
+
## Orphans
|
|
155
|
+
|
|
156
|
+
Values present in `settings.yaml` whose namespace doesn't appear in any current schema declaration are surfaced as **orphans** in a dedicated tab. They aren't deleted automatically — uninstalling a skill keeps its values around so re-installing pops them back in. Users prune them on their own schedule via the orphan tab's per-key Remove buttons.
|
|
157
|
+
|
|
158
|
+
`general.*` is intentionally excluded from orphan detection — its declared keys are enumerated by the built-in schema, so anything else there is treated as either a typo or a forward-compat field, not an orphan.
|
|
159
|
+
|
|
160
|
+
## API
|
|
161
|
+
|
|
162
|
+
| Operation | Method | Endpoint | Purpose |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| Read merged settings (legacy) | GET | `/api/settings?projectId=xxx` | Raw read, used by older tooling |
|
|
165
|
+
| Read schema + resolved values | GET | `/api/settings/schema?projectId=xxx` | Drives the new Settings page |
|
|
166
|
+
| Replace scope | PUT | `/api/settings` | Bulk replace one yaml file |
|
|
167
|
+
| Patch single key | PATCH | `/api/settings` | Set a leaf at `<dotted-key>` |
|
|
168
|
+
| Delete key | DELETE | `/api/settings` | Remove a leaf (used for Reset / Remove orphan) |
|
|
169
|
+
|
|
170
|
+
### `/api/settings/schema` response
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"scope": "global" | "workspace",
|
|
175
|
+
"sections": [
|
|
176
|
+
{
|
|
177
|
+
"namespaceId": "aws-bedrock-claude-invoke",
|
|
178
|
+
"source": "provider",
|
|
179
|
+
"displayName": "AWS Bedrock Claude (Invoke API)",
|
|
180
|
+
"description": "...",
|
|
181
|
+
"fields": [
|
|
182
|
+
{
|
|
183
|
+
"key": "access_key_id",
|
|
184
|
+
"kind": "secret",
|
|
185
|
+
"description": "AWS Access Key ID",
|
|
186
|
+
"description_zh": "...",
|
|
187
|
+
"default": null,
|
|
188
|
+
"secret": true,
|
|
189
|
+
"value": "AK****ST",
|
|
190
|
+
"hasValue": true,
|
|
191
|
+
"source": "global",
|
|
192
|
+
"inheritedFromGlobal": false
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"orphans": [
|
|
198
|
+
{ "namespaceId": "tavily", "kind": "param", "key": "api_key" }
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### PATCH body
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"scope": "global" | "workspace",
|
|
208
|
+
"projectId": "...", // required if scope=workspace
|
|
209
|
+
"key": "aws-bedrock-claude-invoke.secrets.access_key_id",
|
|
210
|
+
"value": "AKIA…"
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### DELETE body
|
|
215
|
+
|
|
216
|
+
Same shape minus `value`. Removes the leaf at `key`. For Reset behaviour: workspace scope DELETE → field falls back to global / default; global scope DELETE → field becomes unset.
|
|
217
|
+
|
|
218
|
+
## i18n
|
|
219
|
+
|
|
220
|
+
The admin UI is bilingual (en/zh). Field descriptions are localized via `description_zh` with fallback to `description`. Fixed UI labels live in `packages/admin/src/shared/i18n/`.
|
|
221
|
+
|
|
222
|
+
## Config caching
|
|
223
|
+
|
|
224
|
+
`config.ts` reads `settings.yaml` lazily with mtime-watching: every read stats the file and reparses if the mtime has changed. UI saves bump the mtime, so the server picks up new secrets on the next call without a restart. See [packages/server/src/config.ts](../../../packages/server/src/config.ts) `getSettings()`.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Skill — Requirements
|
|
2
|
+
|
|
3
|
+
A skill is a knowledge pack an agent can reference — prompts, templates, scripts, reference docs.
|
|
4
|
+
|
|
5
|
+
## Core behaviour
|
|
6
|
+
|
|
7
|
+
### Mini workspace
|
|
8
|
+
Clicking a skill in the sidebar turns the main area into a mini workspace:
|
|
9
|
+
- File tree: every file in the skill directory
|
|
10
|
+
- Monaco editor: edit any file (SKILL.md, templates, scripts)
|
|
11
|
+
- Reuses the main workspace's editor / explorer components
|
|
12
|
+
|
|
13
|
+
The skill editor runs in an **isolated** `EditorStoreProvider` — its tabs, file tree, and
|
|
14
|
+
selection are independent of the main Explorer Canvas, so switching back to Explorer
|
|
15
|
+
restores that Canvas untouched. The provider is keyed on `${id}:${scope}`, so switching
|
|
16
|
+
between skills also resets the mini workspace (no cross-skill tab bleed). The maximize
|
|
17
|
+
button is hidden in this mode (`showMaximize={false}`).
|
|
18
|
+
|
|
19
|
+
### CRUD
|
|
20
|
+
|
|
21
|
+
| Operation | API |
|
|
22
|
+
|---|---|
|
|
23
|
+
| List | `GET /api/skills?projectId=xxx` — returns BOTH global and workspace skills as separate entries (no merge); global entries shadowed by a same-id workspace skill are flagged `overridden: true` |
|
|
24
|
+
| Create | `POST /api/skills` (body: name, description, scope, projectId) |
|
|
25
|
+
| Delete | `DELETE /api/skills/:id?scope=...&projectId=...` |
|
|
26
|
+
| Toggle disabled | `PATCH /api/skills/:id/toggle?scope=...&projectId=...` → `{ ok, disabled }` |
|
|
27
|
+
|
|
28
|
+
### Scope (Global / Workspace)
|
|
29
|
+
|
|
30
|
+
| Scope | Path | Notes |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Global | `~/.halo/global/skills/<id>/` | Shared across projects |
|
|
33
|
+
| Workspace | `<project>/.halo/skills/<id>/` | Wins over a same-id global skill at runtime |
|
|
34
|
+
|
|
35
|
+
### Management UI
|
|
36
|
+
|
|
37
|
+
The Skills sidebar mirrors the Agents sidebar layout:
|
|
38
|
+
- Two collapsible sections — **Global** (globe icon) and **Workspace** (folder icon). Expanded state persists in `halo_skills_expandedScopes` (localStorage)
|
|
39
|
+
- Each section header has its own **+ button** — scope is implicit from which button is clicked, no pop-up picker
|
|
40
|
+
- Creating a name that collides with the other scope prompts a one-time confirmation explaining the runtime override
|
|
41
|
+
- Items overridden by a workspace skill are rendered dimmed with "overridden" subtitle
|
|
42
|
+
- Disabled skills are rendered dimmed with "disabled" subtitle and a toggle switch (always visible). Disabled state is stored per workspace in the `disabled_items` table of `halo.db` (not in SKILL.md frontmatter). Both global and workspace skills can be independently toggled per workspace. Disabled skills are excluded from system prompt injection, activate_skill tool, agent form skill picker, and `/workspace share` export.
|
|
43
|
+
- Selection uses a composite `id:scope` key (localStorage `halo_skills_selectedKey`) so the same id in different scopes can coexist
|
|
44
|
+
|
|
45
|
+
### Auto-sync behaviour (workspace vs. global)
|
|
46
|
+
|
|
47
|
+
The Skills sidebar and any open mini-workspace editor stay in sync with the file system via `file:changed` WS events — but the server's watcher is scoped to the current workspace, so the guarantees differ by scope:
|
|
48
|
+
|
|
49
|
+
| Change | Workspace skills | Global skills |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| Create / delete a skill dir | Live (watcher emits add/unlink → `skill-bus` bump → sidebar refetches) | Refreshed opportunistically on **window focus** |
|
|
52
|
+
| Edit a file inside a skill while the mini-workspace editor has it open | Live (EditorPanel translates workspace-rel path into its own tab.path and refetches) | Live for the currently-open tab (same code path) — but the file tree change won't show new/removed sibling files until focus refresh |
|
|
53
|
+
| Edit from external CLI / SSH | Same as above | Same as above |
|
|
54
|
+
|
|
55
|
+
So when an agent (or the `skill` skill) creates a **global** skill, the Skills sidebar won't reflect it until the user refocuses the browser window. The `skill` skill documents this so agents can warn the user.
|
|
56
|
+
|
|
57
|
+
### SKILL.md protocol
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
---
|
|
61
|
+
name: code-review
|
|
62
|
+
description: Automated code review with checklist
|
|
63
|
+
command: /review
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# Code Review
|
|
67
|
+
|
|
68
|
+
Review the code for:
|
|
69
|
+
1. Correctness
|
|
70
|
+
2. Performance
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- **Frontmatter** (required): `name` (kebab-case, = directory name) + `description` — injected into the agent system prompt for discovery
|
|
75
|
+
- **Body**: the full instructions, lazily loaded by the agent via `activate_skill`
|
|
76
|
+
- **Optional fields**: `allowed-tools` (space-separated), `metadata` (custom kv), `command` (slash command registration — opt-in, only declared commands exist), `requiresAccess` (one of `full` / `workspace` / `readonly`), `verbs` (Halo extension — subcommand list, each `{ name, builtin?, requiresAccess?, desc? }`; skill verbs' access gates are declared here), `disable-model-invocation` (standard — command exists but skill is not injected for model auto-activation), `user-invocable` (standard — `false` = never a slash command, model can still activate)
|
|
77
|
+
- **Arguments**: user command-line args fill `$ARGUMENTS` / `$1`–`$9` placeholders in the body (quotes respected, `\$` escapes, `$5.00`/`$PATH` untouched); `{{...}}` placeholders (params/channel/workspace_root etc.) carry platform-injected values — the two coexist without cross-translation. A verb reaches the body as `$1` for dispatch; args are no longer appended verbatim to the body end
|
|
78
|
+
|
|
79
|
+
### Skill-as-command
|
|
80
|
+
When a `command` field is present (e.g. `command: /review`), the skill auto-registers as a slash command. Users trigger it with `/review`. See [commands](command.md).
|
|
81
|
+
|
|
82
|
+
If the `command` collides with a built-in slash command (or another skill's `command`), it is dropped from the command list with a warning — built-ins win, then first-come among skills. Only the slash command is lost; the skill stays usable via `activate_skill`. See [conflict detection](command.md#conflict-detection).
|
|
83
|
+
|
|
84
|
+
### Access-level gate (`requiresAccess`)
|
|
85
|
+
|
|
86
|
+
A skill can declare a minimum session access level in frontmatter:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
---
|
|
90
|
+
name: workspace
|
|
91
|
+
description: ...
|
|
92
|
+
command: /workspace
|
|
93
|
+
requiresAccess: workspace
|
|
94
|
+
---
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
When set, the skill is hidden from agents whose session access level is more restricted (e.g. a `readonly` Telegram channel can't see or invoke a `requiresAccess: full` skill). The check runs both at metadata-load time (so it never enters the system prompt's `<available_skills>` block) and at slash-command execution time (server-side gate, so a user typing the slash manually still hits it). `requiresAccess` is independent of `command`: an agent-activated-only skill (no `command`) is still hidden from too-restricted sessions.
|
|
98
|
+
|
|
99
|
+
Default is unset → the skill is visible to all access levels. `full` is the standard "admin-only" marker for skills that mutate global state (cron, self, etc.).
|
|
100
|
+
|
|
101
|
+
### Settings integration
|
|
102
|
+
|
|
103
|
+
When a skill needs configuration (API keys, endpoints, preset values):
|
|
104
|
+
1. Declare them in a `config.yaml` next to `SKILL.md` — schema lives with the package
|
|
105
|
+
2. Reference them from the SKILL.md body with the short form `{{params.<key>}}` (auto-qualified to `{{<skill-id>.params.<key>}}` at activation)
|
|
106
|
+
3. Values live in `~/.halo/secrets/settings.yaml` at `<skill-id>.params.<key>` — written by the user via the Settings page or by hand
|
|
107
|
+
4. Use `<<ENV_NAME>>` placeholders for sensitive values; the real value comes from env vars at render time
|
|
108
|
+
|
|
109
|
+
Full rules: [guide/skills.md](../guide/skills.md#placeholders-template-variables) and [requirements/settings.md](settings.md).
|
|
110
|
+
|
|
111
|
+
### Agent references
|
|
112
|
+
Agents reference skills by id in `agent.yaml`'s `skills` field:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
skills:
|
|
116
|
+
- code-review
|
|
117
|
+
- test-writer
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Runtime uses **progressive disclosure**:
|
|
121
|
+
1. **Discovery**: only skill metadata (name + description) is injected into the system prompt
|
|
122
|
+
2. **Activation**: the agent calls `activate_skill(skill_id)` to load the full SKILL.md
|
|
123
|
+
3. **Execution**: the agent follows the loaded instructions; `file_read` can pull resources from the skill directory
|
|
124
|
+
|
|
125
|
+
Avoids bloating the system prompt with unused skill content.
|