@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,686 @@
|
|
|
1
|
+
# API Routes
|
|
2
|
+
|
|
3
|
+
All REST endpoints are served by Hono on port 9527 at `/api/`.
|
|
4
|
+
|
|
5
|
+
Auth: most `/api/*` routes require a valid JWT cookie (`halo_token`). Exceptions in `PUBLIC_PATHS` (`middleware/auth.ts`) bypass the cookie: `/api/auth/*`, the web-channel routes (`/api/web/chat|stop|history|subscribe|file`), `/api/show/state|session`, and `/api/metrics` — these are unauthenticated or use a web-channel `x-token` instead.
|
|
6
|
+
|
|
7
|
+
## Health
|
|
8
|
+
|
|
9
|
+
| Method | Path | Purpose |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| GET | `/api/health` | Health check — returns `{status, timestamp, uptime, engine}` |
|
|
12
|
+
|
|
13
|
+
## Authentication
|
|
14
|
+
|
|
15
|
+
File: `packages/server/src/middleware/auth.ts`
|
|
16
|
+
|
|
17
|
+
| Method | Path | Purpose |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| POST | `/api/auth/login` | Password login, returns a JWT cookie |
|
|
20
|
+
| POST | `/api/auth/logout` | Clears the auth cookie |
|
|
21
|
+
| GET | `/api/auth/check` | Validates the current token; refreshes stale tokens |
|
|
22
|
+
|
|
23
|
+
## Files
|
|
24
|
+
|
|
25
|
+
File: `packages/server/src/routes/files.ts`
|
|
26
|
+
|
|
27
|
+
| Method | Path | Purpose |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| GET | `/api/files/tree?projectId=[&path=]` | List one level (lazy). Every directory carries `hasChildren`. No `path` = project root. |
|
|
30
|
+
| GET | `/api/files/search?projectId=&q=[&limit=][&dirsOnly=1]` | Recursive name search. Default limit 200, max 1000. Scans up to 50000 entries then truncates; returns `{matches, truncated}`. `dirsOnly=1` matches directories instead of files (powers the chat `@scope` directory completion). |
|
|
31
|
+
| GET | `/api/files?path=&projectId=` | Read file content (max 10 MB) |
|
|
32
|
+
| GET | `/api/files/stat?path=&projectId=` | Lightweight mtime + size |
|
|
33
|
+
| GET | `/api/files/diff?path=&projectId=` | Git diff |
|
|
34
|
+
| GET | `/api/files/download?path=&projectId=&inline=` | Download or inline-preview — streams the file (no full read into memory), supports `Range` (206 Partial Content) so `<video>`/`<audio>` can seek + partial-load; aborts the read if the client disconnects |
|
|
35
|
+
| PUT | `/api/files` | Save file (body: `{path, content, projectId}`) |
|
|
36
|
+
| POST | `/api/files/new` | Create empty file |
|
|
37
|
+
| POST | `/api/files/mkdir` | Create directory |
|
|
38
|
+
| POST | `/api/files/rename` | Rename / move |
|
|
39
|
+
| POST | `/api/files/upload` | Multipart upload |
|
|
40
|
+
| DELETE | `/api/files?path=&projectId=` | Delete file or directory (recursive) |
|
|
41
|
+
|
|
42
|
+
All file operations validate the path stays inside the project root (prevents path traversal). One exception: `GET /api/files/download` also accepts absolute paths under `/tmp/` so that agent-produced working files (e.g. Playwright screenshots) can be inline-previewed from chat media chips.
|
|
43
|
+
|
|
44
|
+
### Filesystem browse (not project-scoped — for the workspace picker)
|
|
45
|
+
|
|
46
|
+
| Method | Path | Purpose |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| GET | `/api/fs/home` | Returns server `homedir()` — fallback when the frontend has no `?folder=` URL param. Returns `{ home }`. |
|
|
49
|
+
| GET | `/api/fs/exists?path=/abs` | Validates an absolute path exists and whether it's a directory. Returns `{exists, isDirectory?}`. |
|
|
50
|
+
| GET | `/api/fs/browse?path=/abs` | Lists immediate directory children (hidden ones dropped). Returns `{path, parent, entries: [{name, path}]}`. |
|
|
51
|
+
| POST | `/api/fs/workspace/resolve` | Resolve `{path}` to an absolute path and run `ensureWorkspaceHalo()`. Returns `{id, path}`. Used by the workspace-picker on switch. |
|
|
52
|
+
|
|
53
|
+
Absolute paths only. Purpose: Explorer's workspace picker and switching validation. Reading/writing files still goes through `/api/files/*` and remains project-sandboxed.
|
|
54
|
+
|
|
55
|
+
## Session Logs (unified)
|
|
56
|
+
|
|
57
|
+
File: `packages/server/src/routes/sessions.ts`
|
|
58
|
+
|
|
59
|
+
Unified session log API — list + read session files across all agents.
|
|
60
|
+
|
|
61
|
+
| Method | Path | Purpose |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| GET | `/api/sessions/logs?projectId=` | List session metadata, keyset-paginated. Default returns each top-level row + all descendants (sidebar tree); `rootOnly=1` returns roots only (chat-header dropdown) |
|
|
64
|
+
| GET | `/api/sessions/logs/:id?projectId=` | Full session log (scans across agent dirs) |
|
|
65
|
+
| DELETE | `/api/sessions/logs/:id?projectId=` | Delete the session log |
|
|
66
|
+
| PATCH | `/api/sessions/logs/:id?projectId=` | Rename a session (admin-only) — updates the log file's title. Accepts any session id (root or sub-agent); the sidebar exposes the rename affordance on every row |
|
|
67
|
+
|
|
68
|
+
The list endpoint returns flat metadata (id / agentId / agentName / title / timestamps / messageCount / parentSessionId / stoppedAt / contextTokens / totalOutputTokens). The frontend builds the tree from `parentSessionId`.
|
|
69
|
+
|
|
70
|
+
The get endpoint returns the full session file. If only `rawMessages` is present (no event-log `messages`), `convertRawMessages()` transforms it into display format on the fly.
|
|
71
|
+
|
|
72
|
+
### Legacy DB sessions
|
|
73
|
+
|
|
74
|
+
| Method | Path | Purpose |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| GET | `/api/sessions?projectId=` | List sessions (DB, legacy) |
|
|
77
|
+
| GET | `/api/sessions/:id` | Full session (DB, legacy) |
|
|
78
|
+
| DELETE | `/api/sessions/:id` | Delete session (DB, legacy) |
|
|
79
|
+
|
|
80
|
+
## Weixin Channel
|
|
81
|
+
|
|
82
|
+
File: `packages/server/src/routes/weixin.ts`
|
|
83
|
+
|
|
84
|
+
| Method | Path | Purpose |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| POST | `/api/weixin/login/start` | Start the QR login flow, returns `{qrcodeUrl, sessionKey}` |
|
|
87
|
+
| POST | `/api/weixin/login/wait` | Poll QR status. body: `{sessionKey, workspacePath, label?, language?}`; on success the account is inserted and long-polling starts |
|
|
88
|
+
| GET | `/api/weixin/accounts` | List every bot account |
|
|
89
|
+
| PATCH | `/api/weixin/accounts/:id` | Change label / workspacePath / enabled / accessLevel / language |
|
|
90
|
+
| DELETE | `/api/weixin/accounts/:id` | Stop long-polling and delete the DB row |
|
|
91
|
+
|
|
92
|
+
See [design/wechat.md](../design/wechat.md).
|
|
93
|
+
|
|
94
|
+
## Telegram Channel
|
|
95
|
+
|
|
96
|
+
File: `packages/server/src/routes/telegram.ts`. Admin cookie auth. Mounted under `/api/`.
|
|
97
|
+
|
|
98
|
+
| Method | Path | Purpose |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| GET | `/api/telegram/accounts` | List bot accounts. Each row carries `accountId`, `botUsername`, `workspacePath`, `workspaceMissing`, `label`, `enabled`, `accessLevel`, `allowedUsers`, `language`, timestamps |
|
|
101
|
+
| POST | `/api/telegram/accounts` | Create / upsert. Body: `{botToken, workspacePath, label?, accessLevel?, allowedUsers?, language?}`. Validates the token via `getMe`; `accountId = botUsername.toLowerCase()`. (Re)starts long-poll. Returns `{accountId, botUsername, workspacePath}` |
|
|
102
|
+
| PATCH | `/api/telegram/accounts/:id` | Update `label` / `workspacePath` / `enabled` / `accessLevel` / `allowedUsers` / `language`. Stops then restarts the bot if still enabled |
|
|
103
|
+
| DELETE | `/api/telegram/accounts/:id` | Stop long-poll and delete the row |
|
|
104
|
+
|
|
105
|
+
`workspacePath` must be absolute and exist; `ensureWorkspaceHalo()` runs on POST/PATCH.
|
|
106
|
+
|
|
107
|
+
## Slack Channel
|
|
108
|
+
|
|
109
|
+
File: `packages/server/src/routes/slack.ts`. Admin cookie auth. Inbound events use Socket Mode (no webhook).
|
|
110
|
+
|
|
111
|
+
| Method | Path | Purpose |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| GET | `/api/slack/accounts` | List accounts. Row: `accountId`, `botUserId`, `teamId`, `workspacePath`, `workspaceMissing`, `label`, `enabled`, `accessLevel`, `language`, timestamps |
|
|
114
|
+
| POST | `/api/slack/accounts` | Create / upsert. Body: `{botToken, appToken, workspacePath, label?, accessLevel?, language?}`. `appToken` must start with `xapp-`. Resolves identity via `auth.test`; `accountId = team_id.toLowerCase()`. Returns `{accountId, botUserId, teamId}` |
|
|
115
|
+
| PATCH | `/api/slack/accounts/:id` | Update `label` / `workspacePath` / `enabled` / `accessLevel` / `language` / `appToken` |
|
|
116
|
+
| DELETE | `/api/slack/accounts/:id` | Stop Socket Mode connection and delete the row |
|
|
117
|
+
| GET | `/api/slack/accounts/:id/search?q=` | Search users / channels visible to this bot's token (used by the cron form). Returns `{hits}` (max 20) |
|
|
118
|
+
|
|
119
|
+
## Feishu Channel
|
|
120
|
+
|
|
121
|
+
File: `packages/server/src/routes/feishu.ts`. Admin cookie auth. Inbound uses long-connect wss (no webhook).
|
|
122
|
+
|
|
123
|
+
| Method | Path | Purpose |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| GET | `/api/feishu/accounts` | List accounts. Row: `accountId`, `appId`, `botOpenId`, `hasEncryptKey`, `workspacePath`, `workspaceMissing`, `label`, `enabled`, `accessLevel`, `language`, timestamps |
|
|
126
|
+
| POST | `/api/feishu/accounts` | Create / upsert. Body: `{appId, appSecret, verificationToken?, encryptKey?, workspacePath, label?, accessLevel?, language?}`. Resolves `botOpenId` via `/bot/v3/info`; `accountId = appId.toLowerCase()`. Returns `{accountId, appId, botOpenId}` |
|
|
127
|
+
| PATCH | `/api/feishu/accounts/:id` | Update `label` / `workspacePath` / `enabled` / `accessLevel` / `language` / `verificationToken` / `encryptKey`. `botOpenId` is **not** patchable — re-POST to re-resolve |
|
|
128
|
+
| DELETE | `/api/feishu/accounts/:id` | Stop the wss stream and delete the row |
|
|
129
|
+
| GET | `/api/feishu/accounts/:id/search?q=` | Search chats the bot is a member of (used by the cron form). Returns `{hits}` (max 20) |
|
|
130
|
+
|
|
131
|
+
## Cron
|
|
132
|
+
|
|
133
|
+
File: `packages/server/src/routes/cron.ts`. Admin cookie auth. REST CRUD over `cron_jobs` + read-only `cron_runs`. Mutations call `reloadAll()` / `scheduleJob()` / `unscheduleJob()` so in-memory croner stays in sync with the db. Each mutation also broadcasts `cron:job_changed`.
|
|
134
|
+
|
|
135
|
+
### Jobs
|
|
136
|
+
|
|
137
|
+
| Method | Path | Purpose |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| GET | `/api/cron/jobs?limit=&before=` | Cursor-paginated newest-first by `createdAt`. Default `limit` 20, max 100; `before` is a `createdAt` cursor. Returns `{jobs, hasMore, nextCursor}`. Each job has `targets` decoded and a live `nextRunAt` (computed from schedule/runAt) |
|
|
140
|
+
| POST | `/api/cron/jobs` | Create a job. Body: `{label?, workspacePath, agentId, userPrompt, schedule, runAt?, timezone?, targets?, enabled?}`. Exactly one of `schedule` (5-field cron) or `runAt` (epoch ms, future) is required; mutually exclusive. Returns `{ok, id}` |
|
|
141
|
+
| PUT | `/api/cron/jobs/:id` | Partial update over the same fields. Re-validates `schedule` / `runAt`; flips schedule/unschedule on `enabled`. Returns `{ok}` |
|
|
142
|
+
| DELETE | `/api/cron/jobs/:id` | Unschedule, delete every `cron_runs` row for the job, then delete the job. Returns `{ok}` |
|
|
143
|
+
| POST | `/api/cron/jobs/:id/run-now` | Fire the job immediately (fire-and-forget). Returns `{ok}` |
|
|
144
|
+
| POST | `/api/cron/reload` | Re-read every job from db and rebuild schedules. Returns `{ok}` |
|
|
145
|
+
|
|
146
|
+
`targets` is `Array<{channelType, accountId, chatId?}>`. New job ids look like `cron-<base36-ts>-<rand>`.
|
|
147
|
+
|
|
148
|
+
### Runs
|
|
149
|
+
|
|
150
|
+
| Method | Path | Purpose |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| GET | `/api/cron/jobs/:id/runs?limit=&before=` | Cursor-paginated newest-first by `cron_runs.id` (ISO-prefixed; sorts as text). Default `limit` 20, max 100. Returns `{runs, hasMore, nextCursor}`; each row has `dispatchResults` JSON-decoded |
|
|
153
|
+
| GET | `/api/cron/runs/:runId/log` | Read the raw log file for one run. Returns `{log}` (string or null when the file is missing / past retention) |
|
|
154
|
+
|
|
155
|
+
### Reference data (for the create form)
|
|
156
|
+
|
|
157
|
+
| Method | Path | Purpose |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| GET | `/api/cron/channel-targets` | List channel accounts available as targets, aggregated from the cron-dispatcher registry. Returns `{targets}` |
|
|
160
|
+
| GET | `/api/cron/meta` | Server-side metadata. Returns `{hostTimezone}` — the IANA tz this server resolves an unset `cron_jobs.timezone` to |
|
|
161
|
+
|
|
162
|
+
## Source Control (Git)
|
|
163
|
+
|
|
164
|
+
File: `packages/server/src/routes/git.ts`. Admin cookie auth. Backs the Source Control panel + Explorer git decorations. All project-scoped reads take `projectId=<absPath>`; writes take it in the JSON body. A write (stage/unstage/commit/push/pull/init/remote) broadcasts `file:changed` (path `.git`) so the panel, graph and explorer decorations auto-refresh without polling — the file watcher ignores `.git`, so the route re-broadcasts it itself. Path params on diff are traversal-checked against the project root.
|
|
165
|
+
|
|
166
|
+
### Repo state (read)
|
|
167
|
+
|
|
168
|
+
| Method | Path | Purpose |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| GET | `/api/git/status?projectId=` | Structured working-tree status. A folder that isn't a git work-tree **root** (no repo, or merely nested inside an ancestor's repo) returns `{isRepo: false}` (200, not 500) so the panel shows its initialize/empty state. Otherwise `{isRepo: true, branch, tracking, ahead, behind, files: [{path, index, workingDir, from?}]}` — `index`/`workingDir` are the two porcelain status chars (X staged / Y working) |
|
|
171
|
+
| GET | `/api/git/ignored?projectId=` | `.gitignore`'d paths for graying out in the explorer; ignored directories are collapsed to a single entry (e.g. `node_modules`) rather than expanded. Returns `{ignored: string[]}`. Runs with `core.quotepath=false` so non-ASCII (e.g. Chinese) paths come back literal, not octal-escaped — the frontend prefix-matches them against tree node paths |
|
|
172
|
+
| GET | `/api/git/diff?projectId=&path=[&staged=0\|1][&from=][&commit=]` | Two sides for the Monaco diff editor. With `commit`, shows that commit's own change (parent vs commit); without it, the working-tree (or `staged=1`) diff. `from` carries the old path on a rename. Returns `{path, original, modified, ...}`. 400 if `path` missing, 403 on traversal |
|
|
173
|
+
| GET | `/api/git/log?projectId=[&limit=50]` | Recent commits for the graph. `limit` default 50, capped at 2000. Returns `{commits: [{hash, shortHash, message, author, date, refs, pushed}]}` — `pushed: boolean` marks whether the commit is on a remote (graph dims pushed nodes gray, paints local-only ones blue). No-upstream branches compute it via `rev-list HEAD --not --remotes`, not `rev-list HEAD` |
|
|
174
|
+
| GET | `/api/git/commit-files?projectId=&hash=` | Files changed by one commit. Returns `{files: [{path, status, from?}]}` (`status` = M/A/D/R/C; `from` set on rename/copy). 400 if `hash` missing |
|
|
175
|
+
| GET | `/api/git/remotes?projectId=` | Configured remotes. Returns `{remotes: [{name, url}]}` (`[]` when none) |
|
|
176
|
+
|
|
177
|
+
### Mutations (write — each broadcasts `file:changed`)
|
|
178
|
+
|
|
179
|
+
| Method | Path | Purpose |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| POST | `/api/git/stage` | Body `{projectId, paths: string[]}`. `git add`. 400 if `paths` empty |
|
|
182
|
+
| POST | `/api/git/unstage` | Body `{projectId, paths: string[]}`. 400 if `paths` empty |
|
|
183
|
+
| POST | `/api/git/commit` | Body `{projectId, message}`. Returns `{ok, hash}`. 400 if `message` blank |
|
|
184
|
+
| POST | `/api/git/push` | Body `{projectId}`. First push of an untracked branch sets upstream (`-u origin <branch>`). Git errors (missing creds, SSH passphrase, rejected) are surfaced verbatim for the panel to display |
|
|
185
|
+
| POST | `/api/git/pull` | Body `{projectId}` |
|
|
186
|
+
| POST | `/api/git/init` | Body `{projectId}`. `git init` |
|
|
187
|
+
| POST | `/api/git/remote` | Body `{projectId, name?, url}`. Adds a remote (`name` defaults to `origin`); drives the "no remote configured" publish flow. 400 if `url` missing |
|
|
188
|
+
|
|
189
|
+
### Credentials & SSH
|
|
190
|
+
|
|
191
|
+
| Method | Path | Purpose |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| GET | `/api/git/credentials` | List stored HTTPS credentials — `{credentials: [{host, username}]}`. **Never returns the token.** Source of truth is `~/.git-credentials` (one line per host) |
|
|
194
|
+
| POST | `/api/git/credentials` | Body `{host, username, token}`. Upserts the `https://user:token@host` line in `~/.git-credentials` (0600). 400 if any field missing |
|
|
195
|
+
| DELETE | `/api/git/credentials/:host` | Remove the credential line(s) for one host (idempotent). `:host` is sent `encodeURIComponent`'d; Hono decodes it |
|
|
196
|
+
| GET | `/api/git/ssh/keys` | Private keys found in `~/.ssh` + an `encrypted` flag each. Never returns key contents. Returns `{keys: [...]}` |
|
|
197
|
+
| GET | `/api/git/ssh/agent` | ssh-agent reachability + loaded key count |
|
|
198
|
+
| POST | `/api/git/ssh/unlock` | Body `{keyPath, passphrase}`. Loads a passphrase-protected key into the shared ssh-agent. `keyPath` must resolve to a file **directly inside `~/.ssh`** (rejects traversal / arbitrary paths, 400). Passphrase is fed to `ssh-add` via a throwaway `SSH_ASKPASS` helper (never argv / disk / log); the helper is answer-once so a wrong passphrase returns immediately. Returns `{ok}` on success, `{ok:false, error}` (normalized — never ssh-add's raw stderr) on failure |
|
|
199
|
+
| GET | `/api/git/remote/protocol?projectId=` | Current `origin` url + detected protocol. Returns `{url, protocol}` |
|
|
200
|
+
| POST | `/api/git/remote/protocol` | Body `{projectId, to: 'https'\|'ssh'}`. Rewrites `origin` between HTTPS and scp-style SSH. Returns `{ok, url}` |
|
|
201
|
+
|
|
202
|
+
See [requirements/source-control.md](../requirements/source-control.md).
|
|
203
|
+
|
|
204
|
+
## Web Channel
|
|
205
|
+
|
|
206
|
+
File: `packages/server/src/routes/web.ts`
|
|
207
|
+
|
|
208
|
+
### Admin endpoints (cookie auth)
|
|
209
|
+
|
|
210
|
+
| Method | Path | Purpose |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| GET | `/api/web/accounts` | List all web accounts |
|
|
213
|
+
| POST | `/api/web/accounts` | Create account (body: `{workspacePath, label?, accessLevel?, language?}`) → `{accountId, token, workspacePath}` |
|
|
214
|
+
| PATCH | `/api/web/accounts/:id` | Update account fields (label, workspacePath, enabled, accessLevel, language) |
|
|
215
|
+
| DELETE | `/api/web/accounts/:id` | Delete account |
|
|
216
|
+
|
|
217
|
+
### Public endpoints (token auth via `x-token` header or `?token=` query)
|
|
218
|
+
|
|
219
|
+
| Method | Path | Purpose |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| POST | `/api/web/chat` | Send message, receive SSE stream. Body: `{message, images?, workspace?, sessionId?, agentId?}` (overrides also accepted as `?workspace=`/`?sessionId=` query or `x-workspace`/`x-session-id` headers; `workspace` only honored when token has `accessLevel: full`) |
|
|
222
|
+
| POST | `/api/web/stop` | Stop running task → `{stopped: boolean}` |
|
|
223
|
+
| GET | `/api/web/history` | Active session history → `{sessionId, messages[], running}` |
|
|
224
|
+
| GET | `/api/web/subscribe` | Reconnect SSE to running session |
|
|
225
|
+
| GET | `/api/web/file?path=` | Inline-serve a workspace-relative file (image / video / pdf etc.). Path-traversal-checked against the token's bound workspace. |
|
|
226
|
+
|
|
227
|
+
See [design/web.md](../design/web.md).
|
|
228
|
+
|
|
229
|
+
## Show (world snapshot)
|
|
230
|
+
|
|
231
|
+
File: `packages/server/src/routes/show.ts`. Token auth (same `x-token` as the
|
|
232
|
+
Web channel; shares its brute-force lockout bucket). Read-only, cross-workspace
|
|
233
|
+
snapshot powering the `halo-city` pixel visualizer — one call returns the whole
|
|
234
|
+
runtime so the frontend can render rooms (workspaces) + characters (sessions).
|
|
235
|
+
|
|
236
|
+
| Method | Path | Purpose |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| GET | `/api/show/state` | Full-access token → every known workspace; otherwise the account's own. Returns `{ serverTime, uptime, accessLevel, skills[], workspaces[] }` |
|
|
239
|
+
| GET | `/api/show/session?ws=&id=` | Inspector-panel detail for a single session. Trimmed message log (last 40, content/tool I/O capped) plus `contextTokens` / `outputTokens` / `maxContextTokens` / `isRunning`. Non-full tokens may only read their own workspace. |
|
|
240
|
+
|
|
241
|
+
Each `workspace` = `{ path, key, label, counts{running,idle,stopped}, totalSessions, skills[], sessions[] }`; each `session` = `{ id, parentId, depth, agentName, description, status, lastTool, activeSkill, contextTokens, outputTokens, updatedAt }`. `lastTool` / `activeSkill` come from the live in-memory UI log (empty when the session isn't loaded). Sessions per room are capped (`totalSessions` reports the true total). Frontend: `halo-city/` at repo root.
|
|
242
|
+
|
|
243
|
+
## Agent Configs
|
|
244
|
+
|
|
245
|
+
File: `packages/server/src/routes/agent-configs.ts`
|
|
246
|
+
|
|
247
|
+
| Method | Path | Purpose |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| GET | `/api/agent-configs?projectId=` | List agents (global + workspace merged) |
|
|
250
|
+
| GET | `/api/agent-configs/tools` | Available workspace tools |
|
|
251
|
+
| GET | `/api/agent-configs/models` | Available model providers and models |
|
|
252
|
+
| POST | `/api/agent-configs` | Create a new agent |
|
|
253
|
+
| GET | `/api/agent-configs/:id/yaml?scope=&projectId=` | Read agent.yaml |
|
|
254
|
+
| PUT | `/api/agent-configs/:id/yaml?scope=&projectId=` | Write agent.yaml |
|
|
255
|
+
| DELETE | `/api/agent-configs/:id?scope=&projectId=` | Delete agent (global has "last one" protection) |
|
|
256
|
+
| PATCH | `/api/agent-configs/:id/toggle?scope=&projectId=` | Toggle disabled in workspace DB. `projectId` required. Returns `{ ok, disabled }`. |
|
|
257
|
+
| GET | `/api/agent-configs/:id/md/:fileType` | Read an MD file (AGENT.md / INSTRUCTIONS.md / INDEX.md) |
|
|
258
|
+
| PUT | `/api/agent-configs/:id/md/:fileType` | Write an MD file (AGENT.md / INSTRUCTIONS.md) |
|
|
259
|
+
| GET | `/api/agent-configs/:id/md-all` | Read every MD at once |
|
|
260
|
+
| GET | `/api/agent-configs/:id/sessions?projectId=` | List the agent's session files |
|
|
261
|
+
| GET | `/api/agent-configs/:id/sessions/:sessionId` | Read a session |
|
|
262
|
+
| POST | `/api/agent-configs/:id/sessions` | Save / update a session |
|
|
263
|
+
| DELETE | `/api/agent-configs/:id/sessions/:sessionId` | Delete a session |
|
|
264
|
+
| DELETE | `/api/agent-configs/:id/sessions?all=1` | Delete every session for the agent |
|
|
265
|
+
|
|
266
|
+
## Skills
|
|
267
|
+
|
|
268
|
+
File: `packages/server/src/routes/skills.ts`
|
|
269
|
+
|
|
270
|
+
| Method | Path | Purpose |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| GET | `/api/skills?projectId=` | List skills (global + workspace unmerged — two distinct entries). Global entries shadowed by a same-id workspace skill are marked `overridden: true`. Runtime override happens in `agent-loader.ts`, not in this route. |
|
|
273
|
+
| POST | `/api/skills` | Create a skill directory + SKILL.md |
|
|
274
|
+
| DELETE | `/api/skills/:id?scope=&projectId=` | Delete skill directory + settings entry |
|
|
275
|
+
| PATCH | `/api/skills/:id/toggle?scope=&projectId=` | Toggle disabled in workspace DB. `projectId` required. Returns `{ ok, disabled }`. |
|
|
276
|
+
|
|
277
|
+
## Commands
|
|
278
|
+
|
|
279
|
+
File: `packages/server/src/routes/commands.ts`
|
|
280
|
+
|
|
281
|
+
| Method | Path | Purpose |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| GET | `/api/commands?projectId=[&sessionId=][&agentId=]` | List registered commands (built-in + skill; excludes hidden) |
|
|
284
|
+
|
|
285
|
+
Returns `{ commands: CommandDescriptor[] }`. Skill commands are only included with session/agent context: `sessionId+projectId` (or pre-session `agentId+projectId`) filters them by the agent's skill whitelist + access level. Without that context the response is **builtins only** — listing skills unfiltered leaked full-access commands into readonly palettes. See [design/command.md](../design/command.md).
|
|
286
|
+
|
|
287
|
+
## Settings
|
|
288
|
+
|
|
289
|
+
File: `packages/server/src/routes/settings.ts`
|
|
290
|
+
|
|
291
|
+
| Method | Path | Purpose |
|
|
292
|
+
|---|---|---|
|
|
293
|
+
| GET | `/api/settings/schema?projectId=` | Schema (provider/skill declarations) + resolved values + orphans — drives the new Settings page |
|
|
294
|
+
| PUT | `/api/settings` | Replace a scope's full settings |
|
|
295
|
+
| PATCH | `/api/settings` | Update a single key |
|
|
296
|
+
| DELETE | `/api/settings` | Delete a key |
|
|
297
|
+
|
|
298
|
+
## Metrics
|
|
299
|
+
|
|
300
|
+
File: `packages/server/src/routes/metrics.ts`.
|
|
301
|
+
|
|
302
|
+
| Method | Path | Purpose |
|
|
303
|
+
|---|---|---|
|
|
304
|
+
| GET | `/api/metrics` | Prometheus text exposition (gauges) of deployment-wide runtime: `halo_uptime_seconds`, `halo_workspaces`, `halo_sessions{status=}`, `halo_sessions_total`, `halo_context_tokens`, `halo_output_tokens` |
|
|
305
|
+
|
|
306
|
+
In `PUBLIC_PATHS` (no admin cookie). Auth is a web-channel `x-token` (header or `?token=`) with a **global** scope — `full` or `observer` (the read-only role minted for dashboards/scrapes); a workspace-scoped token gets 403. Session counts come from each workspace's `listSessions`; token sums read the in-memory UIState of sessions this process actively drives.
|
|
307
|
+
|
|
308
|
+
## Self-Evolution
|
|
309
|
+
|
|
310
|
+
File: `packages/server/src/routes/evolution.ts`. Surfaces the global `evolution_runs` / `evolution_applies` queues to the admin UI's Evolution tab; see [plans/self-evolution.md](../plans/self-evolution.md) for the full design.
|
|
311
|
+
|
|
312
|
+
| Method | Path | Purpose |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| GET | `/api/evolution/runs` | List all evolution runs across workspaces. Carries the latest `apply_id`/`apply_status` per run. (Score isn't surfaced in the list — it's read from `score.json` on the detail fetch only.) |
|
|
315
|
+
| GET | `/api/evolution/runs/:id` | Detail: db row + `patch.md` + `score.json` + `system-suggestions.md` (optional, evo writes when it has platform-level feedback) + `.skip.md` (when `status='skipped'`) + a snapshot summary (first user message + first assistant reply + message count). |
|
|
316
|
+
| POST | `/api/evolution/runs/:id/approve` `{reviewerHint?}` | Move run from `awaiting_review` → `approved`, insert a pending `evolution_applies` row that the ticker will pick up. |
|
|
317
|
+
| POST | `/api/evolution/runs/:id/reject` | Move run from `awaiting_review` → `rejected`. |
|
|
318
|
+
| POST | `/api/evolution/runs/:id/retry` `{hint}` | Reset a finished run back to `pending` with the supplied (required) hint so the wrapper picks it up again. Rejected with 409 when the run is already `running` / `pending`. |
|
|
319
|
+
| POST | `/api/evolution/runs/:id/hint` `{hint}` | Append text to `user_hint` — memo only, doesn't change status. |
|
|
320
|
+
| DELETE | `/api/evolution/runs/:id` | Delete a finished run: its on-disk artifacts (run dir + archive zip) **and** the DB row. Rejected with 409 for in-flight states (`pending` / `running` / `approved`) so a live wrapper / queued apply isn't pulled out from under. Broadcasts `evolution:run_changed` with `kind:'deleted'`. |
|
|
321
|
+
| GET | `/api/evolution/applies` | List apply rows (used for status badges). |
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Request / Response schemas
|
|
326
|
+
|
|
327
|
+
Bodies and response shapes for the endpoints agents most commonly help users call. Source lines cited — verify against the route file if in doubt.
|
|
328
|
+
|
|
329
|
+
### POST `/api/auth/login`
|
|
330
|
+
|
|
331
|
+
Source: [packages/server/src/middleware/auth.ts:123-134](../../../packages/server/src/middleware/auth.ts#L123-L134)
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
// Request
|
|
335
|
+
{ "password": "..." }
|
|
336
|
+
|
|
337
|
+
// 200 OK — sets halo_token cookie
|
|
338
|
+
{ "ok": true }
|
|
339
|
+
|
|
340
|
+
// 401 Invalid password
|
|
341
|
+
{ "error": "Invalid password" }
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### GET `/api/auth/check`
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
// 200 authenticated
|
|
348
|
+
{ "authenticated": true }
|
|
349
|
+
|
|
350
|
+
// 401 not authenticated
|
|
351
|
+
{ "authenticated": false }
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### GET `/api/files/tree?projectId=<absPath>&path=<relPath>`
|
|
355
|
+
|
|
356
|
+
Source: [packages/server/src/routes/files.ts](../../../packages/server/src/routes/files.ts). One directory level, lazy.
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
// 200
|
|
360
|
+
{
|
|
361
|
+
"tree": [
|
|
362
|
+
{ "name": "packages", "path": "packages", "type": "directory", "hasChildren": true },
|
|
363
|
+
{ "name": "README.md", "path": "README.md", "type": "file" }
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Skipped entries: dotfiles (except `.halo`), `node_modules`, `__pycache__`.
|
|
369
|
+
|
|
370
|
+
### GET `/api/files?path=<rel>&projectId=<abs>`
|
|
371
|
+
|
|
372
|
+
Reads file content. Max 10 MB.
|
|
373
|
+
|
|
374
|
+
```json
|
|
375
|
+
// 200
|
|
376
|
+
{ "content": "..." }
|
|
377
|
+
|
|
378
|
+
// 404 if path doesn't exist
|
|
379
|
+
{ "error": "File not found" }
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### POST `/api/agent-configs`
|
|
383
|
+
|
|
384
|
+
Source: [packages/server/src/routes/agent-configs.ts:199-255](../../../packages/server/src/routes/agent-configs.ts#L199-L255)
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
// Request
|
|
388
|
+
{
|
|
389
|
+
"name": "Coder",
|
|
390
|
+
"description": "Full-stack coder",
|
|
391
|
+
"scope": "workspace", // "global" | "workspace"
|
|
392
|
+
"projectId": "/abs/path/to/ws" // required when scope=workspace
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 201 Created — agent + possible cross-scope conflict flag
|
|
396
|
+
{
|
|
397
|
+
"agent": {
|
|
398
|
+
"id": "coder",
|
|
399
|
+
"name": "Coder",
|
|
400
|
+
"description": "Full-stack coder",
|
|
401
|
+
"model": "global.anthropic.claude-sonnet-4-6",
|
|
402
|
+
"path": "/abs/path/to/ws/.halo/agents/coder",
|
|
403
|
+
"scope": "workspace",
|
|
404
|
+
"priority": 0
|
|
405
|
+
},
|
|
406
|
+
"conflictScope": null // or "global" / "workspace" to warn of override
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// 409 already exists
|
|
410
|
+
{ "error": "Agent already exists" }
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Files created:
|
|
414
|
+
- `<agentDir>/agent.yaml` — scaffold with `SCAFFOLD_MODEL`, empty tools/skills
|
|
415
|
+
- `<agentDir>/AGENT.md` — `# <name>\n\n<description>\n`
|
|
416
|
+
|
|
417
|
+
### PUT `/api/agent-configs/:id/yaml?scope=&projectId=`
|
|
418
|
+
|
|
419
|
+
```json
|
|
420
|
+
// Request
|
|
421
|
+
{ "yaml": "name: Coder\nmodel:\n provider: aws-bedrock-claude-invoke\n...", "scope": "workspace", "projectId": "/abs/ws" }
|
|
422
|
+
|
|
423
|
+
// 200 OK — returns re-parsed metadata
|
|
424
|
+
{ "agent": { "id": "coder", "name": "Coder", "description": "...", "model": "...", "path": "...", "scope": "workspace" } }
|
|
425
|
+
|
|
426
|
+
// 400 invalid YAML
|
|
427
|
+
{ "error": "Invalid YAML: <parser message>" }
|
|
428
|
+
|
|
429
|
+
// 404
|
|
430
|
+
{ "error": "Agent not found" }
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### POST `/api/skills`
|
|
434
|
+
|
|
435
|
+
Source: [packages/server/src/routes/skills.ts](../../../packages/server/src/routes/skills.ts)
|
|
436
|
+
|
|
437
|
+
```json
|
|
438
|
+
// Request
|
|
439
|
+
{
|
|
440
|
+
"name": "Code Review",
|
|
441
|
+
"description": "Review code for correctness and style",
|
|
442
|
+
"scope": "workspace",
|
|
443
|
+
"projectId": "/abs/ws",
|
|
444
|
+
"command": "/review" // optional — registers as slash command
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// 201
|
|
448
|
+
{
|
|
449
|
+
"skill": {
|
|
450
|
+
"id": "code-review",
|
|
451
|
+
"name": "Code Review",
|
|
452
|
+
"description": "Review code for correctness and style",
|
|
453
|
+
"path": "/abs/ws/.halo/skills/code-review/SKILL.md",
|
|
454
|
+
"scope": "workspace"
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### GET `/api/sessions/logs?projectId=<abs>&rootOnly=0|1&includeArchived=0|1&cursor=<ms>&limit=<n>`
|
|
460
|
+
|
|
461
|
+
Source: [packages/server/src/routes/sessions.ts:189-258](../../../packages/server/src/routes/sessions.ts#L189-L258)
|
|
462
|
+
|
|
463
|
+
Keyset-paginated over `updatedAt` (descending). `limit` defaults to 50; the
|
|
464
|
+
response's `nextCursor` (epoch ms of the last row's `updatedAt`, or `null` on
|
|
465
|
+
the last page) is passed back as `cursor` to fetch the next page.
|
|
466
|
+
|
|
467
|
+
Two shapes, selected by `rootOnly`:
|
|
468
|
+
|
|
469
|
+
- **Default (`rootOnly` omitted)** — returns each top-level row (`parent_id IS
|
|
470
|
+
NULL`) *plus all of its descendants*, flattened. The admin **Sessions
|
|
471
|
+
sidebar** consumes this and rebuilds the tree from `parentSessionId` in one
|
|
472
|
+
shot, no per-expand round-trips. `limit` bounds the top-level rows; their
|
|
473
|
+
descendants are appended on top.
|
|
474
|
+
- **`rootOnly=1`** — returns root sessions only, no descendants. The
|
|
475
|
+
**chat-header dropdown** uses this for a flat "recent sessions" list. `limit`
|
|
476
|
+
bounds the roots directly, so the page count is exact.
|
|
477
|
+
|
|
478
|
+
```json
|
|
479
|
+
// 200 — rootOnly=1: roots only. Default shape is the same rows + descendants.
|
|
480
|
+
{
|
|
481
|
+
"sessions": [
|
|
482
|
+
{
|
|
483
|
+
"id": "sid_abc",
|
|
484
|
+
"agentId": "default",
|
|
485
|
+
"agentName": "Default",
|
|
486
|
+
"title": "First user message...",
|
|
487
|
+
"source": "explorer",
|
|
488
|
+
"createdAt": "2026-04-30T08:00:00Z",
|
|
489
|
+
"updatedAt": "2026-04-30T08:05:00Z",
|
|
490
|
+
"messageCount": 12,
|
|
491
|
+
"parentSessionId": null,
|
|
492
|
+
"contextTokens": 5975,
|
|
493
|
+
"totalOutputTokens": 6058,
|
|
494
|
+
"stoppedAt": null,
|
|
495
|
+
"archivedAt": null
|
|
496
|
+
}
|
|
497
|
+
],
|
|
498
|
+
"nextCursor": 1779890684254
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Archived sessions are excluded by default; pass `?includeArchived=1` to include them.
|
|
503
|
+
|
|
504
|
+
### GET `/api/sessions/logs/:id?projectId=<abs>`
|
|
505
|
+
|
|
506
|
+
Returns the full session file. If only `rawMessages` is present (sub-agent sessions from SessionManager), `convertRawMessages()` transforms them into the display `messages` array on the fly.
|
|
507
|
+
|
|
508
|
+
```json
|
|
509
|
+
// 200 — same shape as the on-disk JSON
|
|
510
|
+
{
|
|
511
|
+
"version": 1,
|
|
512
|
+
"id": "sid_abc",
|
|
513
|
+
"agentId": "default",
|
|
514
|
+
"agentName": "Default",
|
|
515
|
+
"title": "...",
|
|
516
|
+
"source": "explorer",
|
|
517
|
+
"createdAt": "...", "updatedAt": "...",
|
|
518
|
+
"messageCount": 12,
|
|
519
|
+
"contextTokens": 5975,
|
|
520
|
+
"totalOutputTokens": 6058,
|
|
521
|
+
"messages": [ /* SessionMessage[] — see design/storage.md */ ],
|
|
522
|
+
"rawMessages": [ /* optional */ ]
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### GET `/api/settings/schema?projectId=<abs>`
|
|
527
|
+
|
|
528
|
+
Resolves declared schema (from `models/<id>.yaml` `secrets:` and `skills/<id>/config.yaml`) against current settings, returning per-field source/state and a list of orphan keys. Drives the Settings page.
|
|
529
|
+
|
|
530
|
+
```json
|
|
531
|
+
// 200
|
|
532
|
+
{
|
|
533
|
+
"scope": "global", // or "workspace" when projectId set
|
|
534
|
+
"sections": [
|
|
535
|
+
{
|
|
536
|
+
"namespaceId": "aws-bedrock-claude-invoke", // 'general' | provider id | skill id
|
|
537
|
+
"source": "provider", // 'general' | 'provider' | 'skill'
|
|
538
|
+
"displayName": "AWS Bedrock Claude (Invoke API)",
|
|
539
|
+
"displayName_zh": "AWS Bedrock Claude(Invoke API)",
|
|
540
|
+
"description": "...",
|
|
541
|
+
"description_zh": "...",
|
|
542
|
+
"fields": [
|
|
543
|
+
{
|
|
544
|
+
"key": "access_key_id",
|
|
545
|
+
"kind": "secret", // 'param' | 'secret'
|
|
546
|
+
"description": "...",
|
|
547
|
+
"description_zh": "...",
|
|
548
|
+
"default": null, // schema-default placeholder
|
|
549
|
+
"secret": true, // UI masks input + value
|
|
550
|
+
"value": "AK****ST", // already masked when secret:true; null = unset
|
|
551
|
+
"hasValue": true, // any layer has a non-empty value
|
|
552
|
+
"source": "global", // 'workspace' | 'global' | 'unset'
|
|
553
|
+
"inheritedFromGlobal": false // true when scope=workspace + value came from global
|
|
554
|
+
}
|
|
555
|
+
]
|
|
556
|
+
}
|
|
557
|
+
],
|
|
558
|
+
"orphans": [
|
|
559
|
+
{ "namespaceId": "tavily-old", "kind": "param", "key": "api_key" }
|
|
560
|
+
]
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
`<<ENV_NAME>>` references in stored values are returned **as literals** (the browser never sees the resolved env var). Secret values are masked (`AK****ST`); env-var refs pass through unmasked since they're not real secrets.
|
|
565
|
+
|
|
566
|
+
### PATCH `/api/settings`
|
|
567
|
+
|
|
568
|
+
```json
|
|
569
|
+
// Request — dotted-path key, single value (no `.value` suffix; values are flat now)
|
|
570
|
+
{
|
|
571
|
+
"scope": "workspace", // 'global' | 'workspace'
|
|
572
|
+
"projectId": "/abs/ws", // required when scope=workspace
|
|
573
|
+
"key": "tavily-web-search.params.api_key",
|
|
574
|
+
"value": "<<TAVILY_API_KEY>>"
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// 200
|
|
578
|
+
{ "ok": true }
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
PUT replaces the full scope; DELETE takes `{scope, projectId, key}` and removes the key. The Settings page uses DELETE for both Reset (current scope removed → falls back to lower scope / unset) and orphan Remove.
|
|
582
|
+
|
|
583
|
+
### POST `/api/weixin/login/start`
|
|
584
|
+
|
|
585
|
+
Source: [packages/server/src/routes/weixin.ts:42-46](../../../packages/server/src/routes/weixin.ts#L42-L46)
|
|
586
|
+
|
|
587
|
+
```json
|
|
588
|
+
// Request
|
|
589
|
+
{ "sessionKey": "optional-resume-key", "force": false }
|
|
590
|
+
|
|
591
|
+
// 200
|
|
592
|
+
{ "qrcodeUrl": "https://...", "sessionKey": "abc123" }
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### POST `/api/weixin/login/wait`
|
|
596
|
+
|
|
597
|
+
```json
|
|
598
|
+
// Request
|
|
599
|
+
{
|
|
600
|
+
"sessionKey": "abc123",
|
|
601
|
+
"workspacePath": "/abs/path", // required, must be absolute
|
|
602
|
+
"label": "My Bot", // optional
|
|
603
|
+
"accessLevel": "readonly", // "full" | "readonly", default readonly
|
|
604
|
+
"language": "en", // "en" | "zh", default "en" — controls system message language
|
|
605
|
+
"timeoutMs": 120000 // optional, how long to wait for scan
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// 200 waiting for scan (retry polling)
|
|
609
|
+
{ "connected": false, "message": "..." }
|
|
610
|
+
|
|
611
|
+
// 200 connected — account inserted + long-poll started
|
|
612
|
+
{ "connected": true, "accountId": "abc-im-bot" }
|
|
613
|
+
|
|
614
|
+
// 400 bad input
|
|
615
|
+
{ "error": "workspacePath required" }
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## WebSocket message envelopes
|
|
621
|
+
|
|
622
|
+
Full WS protocol in [design/ws.md](../design/ws.md). The four high-traffic client messages:
|
|
623
|
+
|
|
624
|
+
### `chat` (C→S)
|
|
625
|
+
|
|
626
|
+
```json
|
|
627
|
+
{
|
|
628
|
+
"type": "chat",
|
|
629
|
+
"sessionId": "sid_abc",
|
|
630
|
+
"projectId": "/abs/ws",
|
|
631
|
+
"message": "hello",
|
|
632
|
+
"agentId": "default", // optional override
|
|
633
|
+
"images": [ { "data": "<base64>", "mimeType": "image/png" } ] // optional
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
Server behaviour ([handler.ts `handleChat`](../../../packages/server/src/ws/handler.ts#L263-L344)):
|
|
638
|
+
- Creates / reuses a session with the specified agent
|
|
639
|
+
- Persists pasted images to `<ws>/.halo/web/inbound/<date>/`
|
|
640
|
+
- If the model does not support image input (`capabilities.image: false`), images are filtered out and a text notice is appended instead of sending to the API
|
|
641
|
+
- Queues if busy/compacting; otherwise runs the agent turn
|
|
642
|
+
- Streams events back via `message`, `tool_call`, `usage`, etc.
|
|
643
|
+
|
|
644
|
+
### `subscribe` (C→S)
|
|
645
|
+
|
|
646
|
+
Attach this connection to a session's event stream. Sent on initial connect and whenever the active session changes.
|
|
647
|
+
|
|
648
|
+
```json
|
|
649
|
+
{
|
|
650
|
+
"type": "subscribe",
|
|
651
|
+
"sessionId": "sid_abc",
|
|
652
|
+
"projectId": "/abs/ws"
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### `session:clear` (C→S)
|
|
657
|
+
|
|
658
|
+
Detach from the current session (for `/session new`). Server unsubscribes the event listener without deleting the session.
|
|
659
|
+
|
|
660
|
+
```json
|
|
661
|
+
{ "type": "session:clear", "sessionId": "sid_abc" }
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### `session:delete` (C→S)
|
|
665
|
+
|
|
666
|
+
Archive (soft-delete) a session.
|
|
667
|
+
|
|
668
|
+
```json
|
|
669
|
+
{ "type": "session:delete", "sessionId": "sid_abc", "projectId": "/abs/ws" }
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Hard delete (JSON + SQLite rows, cascades to descendants) goes through the REST route `DELETE /api/sessions/logs/:id`, not this WS message.
|
|
673
|
+
|
|
674
|
+
### Server-sent events (S→C, selected)
|
|
675
|
+
|
|
676
|
+
| Type | Fields | Purpose |
|
|
677
|
+
|---|---|---|
|
|
678
|
+
| `message` | `content`, `role`, `taskId?` | Text chunk from the agent |
|
|
679
|
+
| `tool_call` | `toolName`, `toolInput`, `taskId?` | Tool invocation card |
|
|
680
|
+
| `tool_result` | `toolName`, `toolOutput`, `durationMs`, `taskId?` | Tool result |
|
|
681
|
+
| `usage` | `usage`, `modelId`, `turnId`, `taskId?` | Token accounting per turn |
|
|
682
|
+
| `complete` | `stopReason`, `taskId?` | Turn finished |
|
|
683
|
+
| `error` | `error` | Error message |
|
|
684
|
+
| `chat:queued` | `reason`, `message` | Message queued (compact/busy) |
|
|
685
|
+
|
|
686
|
+
Events with `taskId` set belong to sub-agent turns (nested sessions); events without are the root agent's. Full list in [design/ws.md](../design/ws.md).
|