@turmind/halo 2.2.4 → 2.2.6
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/README.md +2 -1
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/1989-3ab059aa2c368f46.js +1 -0
- package/admin-out/_next/static/chunks/4320-0fb65ed376b666a8.js +1 -0
- package/admin-out/_next/static/chunks/app/page-99eba94ecd0f8e81.js +1 -0
- package/admin-out/_next/static/css/7eb37d975d4027da.css +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/agent.md +0 -1
- package/bundled-docs/design/architecture.md +1 -1
- package/bundled-docs/design/canvas-extensions.md +2 -0
- package/bundled-docs/design/cron.md +2 -2
- package/bundled-docs/design/feishu.md +10 -0
- package/bundled-docs/design/storage.md +4 -2
- package/bundled-docs/design/stt-local.md +1 -1
- package/bundled-docs/design/ws.md +1 -1
- package/bundled-docs/dev/add-model-provider.md +18 -0
- package/bundled-docs/dev/api.md +7 -1
- package/bundled-docs/dev/deploy.md +26 -1
- package/bundled-docs/dev/desktop-packaging.md +3 -2
- package/bundled-docs/dev/env.md +1 -2
- package/bundled-docs/guide/agents.md +2 -2
- package/bundled-docs/guide/cli.md +2 -2
- package/bundled-docs/guide/skills.md +1 -1
- package/bundled-docs/requirements/explorer.md +10 -3
- package/bundled-docs/requirements/settings.md +24 -1
- package/dist/feishu-sdk.cjs +23128 -0
- package/dist/feishu-sdk.licenses.txt +1465 -0
- package/dist/index.js +418 -220
- package/package.json +1 -2
- package/templates/models/aws-bedrock-claude-invoke.yaml +62 -1
- package/admin-out/_next/static/chunks/1607-bf227fc5e4db45b7.js +0 -1
- package/admin-out/_next/static/chunks/1989-9d30cdb1a86e4beb.js +0 -1
- package/admin-out/_next/static/chunks/app/page-c9d174a5432b0007.js +0 -1
- package/admin-out/_next/static/css/87d4fc049f865389.css +0 -1
- /package/admin-out/_next/static/{vf5W8US5y8B2f1E6zJJw3 → 6I6qmeIcucaJczGu5oP3C}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{vf5W8US5y8B2f1E6zJJw3 → 6I6qmeIcucaJczGu5oP3C}/_ssgManifest.js +0 -0
package/bundled-docs/dev/api.md
CHANGED
|
@@ -36,7 +36,7 @@ File: `packages/server/src/routes/files.ts`
|
|
|
36
36
|
| GET | `/api/files?path=&projectId=` | Read file content (max 10 MB) |
|
|
37
37
|
| GET | `/api/files/stat?path=&projectId=` | Lightweight mtime + size |
|
|
38
38
|
| 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. A **relative** `path` is confined to the workspace (`..` out of it → 403). An **absolute** `path` may be any file on the host, under `/api/fs/raw`'s rules: no `..` segment (→ 400), judged by realpath, the sandbox hidden lists answer like a missing file; admin cookie only (this is how chat `MEDIA:` chips load files from other workspaces / temp dirs). `projectId` is still required. Missing or hidden file → 404 `{error:'File not found'}`; a directory → 400 |
|
|
39
|
-
| GET | `/api/files/download-folder?path
|
|
39
|
+
| GET | `/api/files/download-folder?path=[&path=…]&projectId=[&include=excluded][&estimate=1]` | Folder → one zip, built by yazl while it streams (zip64, UTF-8 names; already-compressed extensions stored, the rest deflated; one file open at a time, paced by backpressure; never staged on disk or in memory). `path` must be a directory inside the project, `''` = workspace root; it may itself be a folder symlink inside the workspace (resolved, then checked like any target); no `/tmp` exception. `Content-Disposition: attachment` with RFC 5987 `filename*` = `<folder name>.zip` (root → workspace dir name); no `Content-Length`, no Range. **`path` repeated** (Explorer multi-select, `planSelectionZip`; a single `path` behaves exactly as above): each may be a file or a folder and must pass the same in-project check; one failure fails the request (403 traversal / 404 project, plus 403 when the realpath is inside but the given path is not, e.g. an outside link pointing in; a missing item → 404). Duplicates and items under another selected folder are dropped. Entries are relative to the nearest common parent of the remaining items, and the file is `<parent name>-selection.zip` (parent = root → workspace dir name). Every selected item gets the walk's per-entry checks (below): a selected `.env` / blocked file, an unsafe name, a folder link or an outside file link is excluded and counted, not refused. A selected blocked folder is excluded the same way, not a 403. A selected `IGNORED_SEGMENTS` folder is walked, since it was picked explicitly. The 413 limits count the whole selection. The server sets no URL-length cap of its own: the admin dialog refuses a selection whose URL passes 8,000 chars without sending a request. **Excluded by default**: any *folder* whose name is in `IGNORED_SEGMENTS` (`ws/file-watcher.ts`); `include=excluded` brings them back. **Always excluded**: `isHiddenHostPath` / `isHiddenWorkspacePath` (also against a nested `.halo/`), plus common credential stores (~/.kube, cloud CLI configs, shell histories — a zip-only list in `folder-zip.ts`, matched relative to the home dir), `.halo/settings.yaml`, `.env` / `.env.*`. Names a zip can't hold — any containing `\`, or a top-level `X:` prefix — are skipped as `unreadable`. Directory symlinks never followed; file symlinks only when the realpath stays inside the workspace and passes those checks. `estimate=1` → `{files, bytes, excluded: {<reason>: n}, ignoredNames: {<folder>: n}}` with reasons `ignored` / `blocked` / `symlink` / `unreadable`, same rules, no content read. More than 200,000 files or a walk over 10 s → **413** `{error}` (both modes, before any header). A blocked target → 403. Anything left out → `_halo-excluded.txt` at the zip root (grouped by reason, a pruned dir listed once, 1000 paths per reason + a count). Mid-stream (status already sent): a file that fails to open is skipped, a read error partway through truncates that entry — both are listed there. A client disconnect stops the walk and destroys the zip stream |
|
|
40
40
|
| PUT | `/api/files` | Save file (body: `{path, content, projectId}`) |
|
|
41
41
|
| PUT | `/api/files/raw?path=&projectId=[&expectMtime=][&create=1][&append=1][&root=]` | Replace an **existing** file's bytes with the raw request body (binary-safe; `PUT /api/files` is utf-8 text only). Used by canvas preview extensions' save path. 404 if the file doesn't exist (save never creates) unless `create=1`; 400 for a directory. `expectMtime` is the `modifiedAt` the client loaded from `/api/files/stat` (float ms — compared after `Math.round`): when the on-disk mtime differs → **409** `{error:'conflict', mtime, size}` so the client can offer overwrite (retry without / with the new `expectMtime`). `create=1` creates a missing file (parent dirs `mkdir -p`); `append=1` appends instead of overwriting (`expectMtime` ignored; with `create=1` a missing file is created first) — the bundle extensions' `fs` write / append. `root=<dir>` (workspace-relative; the bundle host always sends its bundle path): `<dir>` must be an existing directory and `path` strictly inside it, else **404** `{error:'Bundle root not found'}` with nothing created. Success → `{ok, path, mtime, size}` |
|
|
42
42
|
| POST | `/api/files/new` | Create empty file |
|
|
@@ -121,6 +121,12 @@ Install / update / switch / uninstall don't broadcast themselves — the resulti
|
|
|
121
121
|
|
|
122
122
|
Types: `packages/core/src/protocol/update-types.ts`.
|
|
123
123
|
|
|
124
|
+
**Model sync** (`routes/updates.ts` + `models/hub-update.ts`; cookie; not mounted in AgentCore mode). Backs Settings → Model providers → Sync latest models. Plans and writes with the same `planModelsInstall` / `applyModelsInstall` as `halo models install` — rules: [design/storage.md → Model registry format](../design/storage.md#model-registry-format). No frame of its own: a write trips the `models.d/` watcher, which pushes `models:changed`.
|
|
125
|
+
|
|
126
|
+
| Method | Path | Auth | Purpose |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| POST | `/api/models/update` | cookie | Body `{ confirm?: boolean }` — only `true` counts as consent. The hub is `extension.params.hub_repo` (empty = turmind/halo-hub; `owner/repo` or `https://github.com/<owner>/<repo>` only). Lists its releases (`api.github.com/repos/<owner>/<repo>/releases?per_page=100`, anonymous: **60 requests/hour per IP**, no token is sent), takes the newest non-draft, non-prerelease `models-v<digit>…` release with a `.zip` asset, unpacks its `*.yaml` (basename only, flat) into a temp dir and plans it against the copies in effect. 200 `{hub, release, needsConfirm, written: string[], summary: string \| null, entries}`, one entry per yaml `{file, id?, action: 'install' \| 'up-to-date' \| 'skip' \| 'refuse', reason?, revision?, current?: {source: 'bundled' \| 'hub', revision}, endpointChanges: string[], needsConfirm?}`. `needsConfirm: true` = an install adds an endpoint and `confirm` wasn't `true`: **nothing is written**, `written: []`, `summary: null`; re-POST `{ confirm: true }` fetches and plans again (nothing cached between calls), then writes. Otherwise `written` = the ids written to `models.d/` and `summary` = the CLI's summary line. 400 `{error}` `hub_repo` isn't GitHub (use `/extension models`); 409 `{error: 'A model sync is already running'}` (one at a time, in-process); 502 `{error}` the hub / network is at fault — release API non-200 (rate-limited, private or wrong repo), invalid JSON, no matching release, download failure, not a zip, two yamls with one basename, no yaml in it, over the size cap (list 2 MB, zip 10 MB), timeout / network error; 500 otherwise (e.g. `models.d/` not writable) |
|
|
129
|
+
|
|
124
130
|
## Session Logs (unified)
|
|
125
131
|
|
|
126
132
|
File: `packages/server/src/routes/sessions.ts`
|
|
@@ -53,6 +53,31 @@ Nginx is **not required**. Use it only for domain routing, SSL termination, or w
|
|
|
53
53
|
- pnpm 11 — source builds only (pinned by the root `packageManager`; `corepack enable` picks it up)
|
|
54
54
|
- AWS credentials — optional, only for the Bedrock providers (`~/.aws/credentials`, env vars, or an instance role)
|
|
55
55
|
- bubblewrap (Linux) — needed for the `workspace` / `readonly` access levels: `sudo apt install bubblewrap`; on Ubuntu 23.10+ / 24.04 also the [AppArmor profile](#bubblewrap-on-ubuntu-2310-and-2404-apparmor)
|
|
56
|
+
- RAM — 1 GB recommended; a 0.5 GB box only with a 1 GB swap file persisted in `/etc/fstab` ([Memory and swap](#memory-and-swap)). Disk: about 290 MB for the install, plus about 450 MB of npm cache
|
|
57
|
+
|
|
58
|
+
### Memory and swap
|
|
59
|
+
|
|
60
|
+
Measured on Halo 2.2.5, fresh Ubuntu 24.04 arm64 EC2 (t4g.nano 0.5 GB, t4g.micro 1 GB), 2026-11:
|
|
61
|
+
|
|
62
|
+
| What | RAM |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `npm install -g @turmind/halo` | peak ≈ 540 MB (dependency resolution) |
|
|
65
|
+
| Server, idle | ≈ 160 MB (≈ 250 MB for a few seconds at startup) |
|
|
66
|
+
| Server, typical use (admin, editor, terminal, two parallel sub-sessions) | ≈ 210 MB |
|
|
67
|
+
| Each `halo` CLI call (`--version`, `server status`, the `server start -d` launcher) | ≈ 170 MB more while it runs |
|
|
68
|
+
| Local speech-to-text (voice input, meeting recorder), once used | ≈ 400 MB more, resident until restart; ≈ 100 MB more with speaker labels ([stt-local.md](../design/stt-local.md#engine)) |
|
|
69
|
+
|
|
70
|
+
Agent `shell_exec` work (builds, scripts) comes on top of these.
|
|
71
|
+
|
|
72
|
+
- **1 GB (t4g.micro)**: the recommended minimum. Install and typical use passed with no swap and at least 390 MB still available. With local speech-to-text, add swap or use 2 GB.
|
|
73
|
+
- **0.5 GB (t4g.nano)**: only with a 1 GB swap file. Without it, `apt-get install nodejs` and `npm install -g` are OOM-killed, and an installed server is killed by any ~60 MB transient (a `halo` CLI call, the SSM agent's document worker). With swap everything ran at micro's speed, except the first page load / editor open after idle (+0.7–1 s). Fit for a single-user trial, not a deployment.
|
|
74
|
+
- CPU is not the limit: idle is ≈ 0.03 % of one vCPU; startup costs 3–4 s of CPU once.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
sudo fallocate -l 1G /swapfile && sudo chmod 600 /swapfile
|
|
78
|
+
sudo mkswap /swapfile && sudo swapon /swapfile
|
|
79
|
+
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab # without this line swap is gone after a reboot
|
|
80
|
+
```
|
|
56
81
|
|
|
57
82
|
### bubblewrap on Ubuntu 23.10 and 24.04 (AppArmor)
|
|
58
83
|
|
|
@@ -327,7 +352,7 @@ Use only for a new public release; the retained details here are not extra prepa
|
|
|
327
352
|
|
|
328
353
|
**npm token gotcha**: `npm publish` on this package needs a granular access token created with **"Bypass 2FA"** checked — scope / permission alone yields `403 Two-factor authentication or granular access token with bypass 2fa enabled is required`. `npm whoami` and `npm token list` succeed with a non-bypass token, so neither is a valid pre-flight; check `GET https://registry.npmjs.org/-/npm/v1/tokens` (with the token as bearer) and look for `"bypass_2fa": true` on the token in use before starting a release.
|
|
329
354
|
|
|
330
|
-
The published package contains a single bundled JS entry (`dist/index.js`, ~1.7 MB) plus the evo wrapper the server spawns as its own process (`dist/evo-wrapper.js`, ~130 KB; `build-bundle.mjs` fails without it), all built-in templates (agents / skills / prompts / models), bundled platform docs, and the admin Web UI static export. Total install footprint is about
|
|
355
|
+
The published package contains a single bundled JS entry (`dist/index.js`, ~1.7 MB) plus the evo wrapper the server spawns as its own process (`dist/evo-wrapper.js`, ~130 KB; `build-bundle.mjs` fails without it), the Feishu SDK bundled with its deps (`dist/feishu-sdk.cjs`, ~0.7 MB, loaded on the first Feishu bot connect, so it is not an npm dependency; see [feishu.md](../design/feishu.md#sdk-loading)), all built-in templates (agents / skills / prompts / models), bundled platform docs, and the admin Web UI static export. Total install footprint is about 290 MB on disk (`du`, x86_64, after 2.2.5; mostly `node_modules`; 2.2.5 itself is about 320 MB); npm's own cache adds about 450 MB.
|
|
331
356
|
|
|
332
357
|
Two log files: `~/.halo/logs/server.log` is the stdout/stderr capture of `halo server start -d`, whether the user unit or the off-systemd supervisor runs it (what `halo server logs` tails; the supervisor's `[respawn]` lines appear only off systemd; a system unit logs to the journal instead); `~/.halo/global/logs/server.log` is the server's structured, rotated log (level `general.logging.level`, default `warn`).
|
|
333
358
|
|
|
@@ -159,8 +159,9 @@ dependency work. Three levers, in order of how often they help:
|
|
|
159
159
|
generation, and Gatekeeper scanning the ~350 MB of `extraResources`. Neither
|
|
160
160
|
fast-staging nor `npmRebuild:false` touches that; it runs on every build.
|
|
161
161
|
A further win would be de-duplicating `cli-runtime` (~137 MB) against
|
|
162
|
-
`server-runtime` (~92 MB) — they share most deps (
|
|
163
|
-
|
|
162
|
+
`server-runtime` (~92 MB) — they share most deps (drizzle, better-sqlite3,
|
|
163
|
+
aws-sdk; the Feishu SDK only sits in server-runtime's `node_modules`, the cli
|
|
164
|
+
bundle carries it as `dist/feishu-sdk.cjs`) — but that needs the two trees to physically share
|
|
164
165
|
files without breaking cli's standalone require paths; deferred.
|
|
165
166
|
|
|
166
167
|
## Why `CI=true` is mandatory
|
package/bundled-docs/dev/env.md
CHANGED
|
@@ -106,7 +106,6 @@ Source: `packages/server/src/config.ts`
|
|
|
106
106
|
| `HALO_WEB_FETCH_TIMEOUT` | `10000` | `config.yaml timeout.web_fetch` | web_fetch timeout |
|
|
107
107
|
| `HALO_MODEL_TIMEOUT` | `600000` | `config.yaml timeout.model_request` | Per model-call idle timeout (ms, 10 min): every provider streams (Bedrock, the Anthropic-Messages providers anthropic / mimo / minimax / qwen, the OpenAI-family openai / deepseek / kimi / zhipu / doubao / hunyuan, and Mantle aws-bedrock-mantle / aws-bedrock-openai) and re-arms it on any data the stream delivers — text / thinking deltas, pings, tool-argument fragments, empty reasoning chunks, SSE comment frames — so it fires only when the model goes silent for this long, not on a long but live answer. It is also the only bound on a compact's LLM summary (no separate compact timeout). A bare fetch has no default timeout, so a half-open connection would hang the request forever; on expiry the call aborts and the agent loop retries with backoff. |
|
|
108
108
|
| `HALO_SESSION_GRACE` | `300000` | `config.yaml timeout.session_grace` | WS-disconnect session grace |
|
|
109
|
-
| `HALO_MAX_CACHED_SESSIONS` | `50` | — | In-memory session cache |
|
|
110
109
|
| `HALO_LOG_MAX_SIZE` | `10485760` | `config.yaml logging.max_file_size` | Log file size cap (10 MB) |
|
|
111
110
|
| `HALO_LOG_MAX_FILES` | `5` | `config.yaml logging.max_files` | Retained rotated logs |
|
|
112
111
|
| `HALO_LOG_LEVEL` | `warn` | `settings.yaml general.logging.level` | Log level: debug \| info \| warn \| error |
|
|
@@ -156,7 +155,7 @@ When a provider manifest declares a `default: <<NAME>>` for a secret, `halo setu
|
|
|
156
155
|
- `tavily-web-search.params.api_key` — example only; declared by whichever skill needs it
|
|
157
156
|
- `web-search.params.fast_region` / `web-search.params.deep_region` — AWS regions for web-search's fast gear (Nova grounding, US only; empty = us-east-1) and deep gear (Bedrock Mantle; empty = us-west-2 for luna, us-east-1 for sol, and sol set to a region it isn't offered in falls back to us-east-1)
|
|
158
157
|
- `extension.params.hub_base` — https base URL holding `<id>/update.json` for every hub extension, read by `/extension install <id>` and the admin's update check (extensions without their own `source`); empty = the GitHub raw `extensions/` directory on `main` of `hub_repo`, and a non-GitHub `hub_repo` needs it set. See [design/canvas-extensions.md → Updates](../design/canvas-extensions.md#updates-updatejson)
|
|
159
|
-
- `extension.params.hub_repo` — hub `/extension models`
|
|
158
|
+
- `extension.params.hub_repo` — hub `/extension models` and Settings → Sync latest models (GitHub hubs only, no token) pull the newest `models-v*` release from, and the source of the default `hub_base` (empty = `https://github.com/turmind/halo-hub`); for `models` the skill's `ext.sh` also reads the optional `GITHUB_TOKEN` / `GITLAB_TOKEN` / `GITEA_TOKEN` from the server environment and sends them to that hub's release API only. See [design/canvas-extensions.md](../design/canvas-extensions.md#extension-skill-and-halo-hub)
|
|
160
159
|
|
|
161
160
|
Hardcoded (config.ts, no override):
|
|
162
161
|
- `auth.tokenMaxAge` — 14 days
|
|
@@ -42,7 +42,7 @@ Click an agent in the sidebar to open the right-hand editor. Two views:
|
|
|
42
42
|
Data-driven form for name / description / model / tools / skills / thinking. Changes auto-save about half a second after you stop editing — there's no Save button. The agent's AGENT.md is shown below the form as a read-only preview.
|
|
43
43
|
|
|
44
44
|
**Model section** has four fields:
|
|
45
|
-
- `provider` — dropdown populated from `~/.halo/global/models/*.yaml` plus hub-installed `~/.halo/global/models.d/*.yaml` (`/extension models`; updates the list live)
|
|
45
|
+
- `provider` — dropdown populated from `~/.halo/global/models/*.yaml` plus hub-installed `~/.halo/global/models.d/*.yaml` (`/extension models`; updates the list live — Settings → Model providers → Sync latest models does the same in one click, GitHub hubs only)
|
|
46
46
|
- `id` — combobox (input + datalist); preset options filtered by current provider, also accepts manual input of any model ID
|
|
47
47
|
- `endpoint` — combobox (input + datalist); full endpoint URL (e.g. `https://bedrock-runtime.ap-northeast-1.amazonaws.com`), supports custom proxy URLs. When a model is only served in some regions the list narrows to those, switching to it moves the endpoint there, and an endpoint off its list shows a warning
|
|
48
48
|
- `maxTokens` — optional; defaults to `maxOutputTokens` in the provider yaml
|
|
@@ -227,6 +227,6 @@ With `export NANO_BANANA_KEY=sk-xxx` in the env, the agent's system prompt gets
|
|
|
227
227
|
|
|
228
228
|
## Model registry — adding a new provider
|
|
229
229
|
|
|
230
|
-
Halo ships 13 providers (`aws-bedrock-claude-invoke`, `aws-bedrock-mantle`, `aws-bedrock-openai`, `anthropic`, `openai`, `kimi`, `deepseek`, `minimax`, `qwen`, `hunyuan`, `doubao`, `zhipu`, `mimo-token-plan-china`); each one is a yaml under `~/.halo/global/models/` (refreshed between releases by `/extension models`, which installs newer copies into `~/.halo/global/models.d
|
|
230
|
+
Halo ships 13 providers (`aws-bedrock-claude-invoke`, `aws-bedrock-mantle`, `aws-bedrock-openai`, `anthropic`, `openai`, `kimi`, `deepseek`, `minimax`, `qwen`, `hunyuan`, `doubao`, `zhipu`, `mimo-token-plan-china`); each one is a yaml under `~/.halo/global/models/` (refreshed between releases by `/extension models`, which installs newer copies into `~/.halo/global/models.d/`; Settings → Model providers → Sync latest models does it in one click, GitHub hubs only) whose `runtime:` field names the implementation in [packages/server/src/agents/model-runtime.ts](../../../packages/server/src/agents/model-runtime.ts) (e.g. `anthropic-messages`, `openai-chat`, `bedrock-invoke`). For any OpenAI- or Anthropic-compatible endpoint you don't need new code: pick `openai` or `anthropic` as the provider and fill in the endpoint and model id in Form view. To see what a provider offers, open Settings → Model providers → that provider: below its API key fields it lists every model with its context / output size, capabilities and the regions (AWS) or hosts it can be called on — read-only.
|
|
231
231
|
|
|
232
232
|
A provider that speaks an existing wire protocol needs only a yaml manifest with that `runtime:`; a new protocol also needs a runtime class. The step-by-step is in [dev/add-model-provider.md](../dev/add-model-provider.md). A provider yaml without `runtime:` (or with a name Halo doesn't know) makes session spawn fail with an error naming the yaml; for a bundled provider that means the seed is stale — run `halo setup` or restart the Halo server.
|
|
@@ -77,7 +77,7 @@ To list agents or sessions without running a prompt, use the dedicated subcomman
|
|
|
77
77
|
|
|
78
78
|
- **text format**: stdout carries the agent's **final answer** — the closing text of the last turn, not the live stream. Text the agent emits before a tool call ("let me check…") and the wrap-ups of earlier turns in the same run (e.g. a director absorbing several sub-agent reports) are omitted; if the last turn had no closing text, its full text is printed instead. When stdout is a terminal the answer is rendered as styled markdown (headings, bold, code blocks, tables); when piped or redirected it is written as raw markdown so downstream tools get the text unchanged. Tool calls, thinking, usage — and, with `-v`, the agent's text as it streams — go to stderr.
|
|
79
79
|
|
|
80
|
-
System notices from the root session (retries, compaction, truncated output, a model refusal, `@scope` warnings) are printed on stdout before the answer, one `ℹ️ ` line each and a blank line between them and the answer. Notices from sub-agents and the sibling-status line added when sub-agent reports arrive are left out. Notices from earlier turns in the same run are kept, so a
|
|
80
|
+
System notices from the root session (retries, compaction, truncated output, a model refusal, `@scope` warnings) are printed on stdout before the answer, one `ℹ️ ` line each and a blank line between them and the answer. Notices from sub-agents and the sibling-status line added when sub-agent reports arrive are left out. Notices from earlier turns in the same run are kept, so a run that hit a local-fallback compact still says so in its output. (Scheduled runs record the same text in their run history but push only the answer to chat.)
|
|
81
81
|
|
|
82
82
|
Usage line mirrors the admin chat-panel badge format (timestamp / in / out / ctx / read / write / cache% / ttft / e2e / think / model). `ctx` is the rolled-up context size — `inputTokens + cacheRead + cacheWrite + outputTokens` — so it doesn't look misleading next to a large cache hit:
|
|
83
83
|
|
|
@@ -232,7 +232,7 @@ halo models install <dir> [--yes] # install <dir>/*.yaml into ~/.halo/global/m
|
|
|
232
232
|
halo models list # id · copy in effect (bundled | hub) · revision · runtime
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
Normally run for you by `/extension models`, which downloads the hub's newest `models-v*` release (https GitHub / Gitea / Forgejo / GitLab only) and passes the unpacked directory to `halo models install`. Per provider id the copy with the higher `revision` is in effect (tie → the `models.d` copy); an older one is skipped, an identical one reported up to date. Refused per provider: a `runtime:` this halo doesn't know (needs a newer halo), no `revision`, a `secrets[].default` that isn't empty or `<<ENV_NAME>>`. Endpoint changes against the copy in effect are always listed. Consent is needed for any URL the server would now send requests (and credentials) to that it didn't before: a new provider, a changed `defaultEndpoint` / `endpointPresets`, or a URL added to a model's `endpoints:` list that the provider doesn't already use anywhere. A model list that only loses URLs, or gains one already used elsewhere in the provider, is listed but needs no consent; a reorder alone is not a change. When consent is needed, **nothing is written** and the exit code is `3` — re-run with `--yes` to apply. Writes are atomic (tmp + rename); a running server reloads its registry from `models.d/` without a restart and pushes the change to open admin pages. Existing agents keep the model / endpoint in their own `agent.yaml`. Exit codes: `0` ok (refusals listed in the summary line), `1` error, `3` needs `--yes`. To undo, delete the file from `~/.halo/global/models.d/`.
|
|
235
|
+
Normally run for you by `/extension models`, which downloads the hub's newest `models-v*` release (https GitHub / Gitea / Forgejo / GitLab only) and passes the unpacked directory to `halo models install`. Settings → Model providers → **Sync latest models** does the same in one click (GitHub hubs only; consent is a confirm dialog instead of `--yes`). Per provider id the copy with the higher `revision` is in effect (tie → the `models.d` copy); an older one is skipped, an identical one reported up to date. Refused per provider: a `runtime:` this halo doesn't know (needs a newer halo), no `revision`, a `secrets[].default` that isn't empty or `<<ENV_NAME>>`. Endpoint changes against the copy in effect are always listed. Consent is needed for any URL the server would now send requests (and credentials) to that it didn't before: a new provider, a changed `defaultEndpoint` / `endpointPresets`, or a URL added to a model's `endpoints:` list that the provider doesn't already use anywhere. A model list that only loses URLs, or gains one already used elsewhere in the provider, is listed but needs no consent; a reorder alone is not a change. When consent is needed, **nothing is written** and the exit code is `3` — re-run with `--yes` to apply. Writes are atomic (tmp + rename); a running server reloads its registry from `models.d/` without a restart and pushes the change to open admin pages. Existing agents keep the model / endpoint in their own `agent.yaml`. Exit codes: `0` ok (refusals listed in the summary line), `1` error, `3` needs `--yes`. To undo, delete the file from `~/.halo/global/models.d/`.
|
|
236
236
|
|
|
237
237
|
## Session store migration (`halo sessions migrate v1 v2`)
|
|
238
238
|
|
|
@@ -165,7 +165,7 @@ Halo seeds these skills on every startup (the ids in `BUILTIN_SKILL_IDS`, `packa
|
|
|
165
165
|
| aws-knowledge | Query the official AWS Knowledge MCP server for up-to-date AWS docs (no command, `user-invocable: false`; model auto-activates) |
|
|
166
166
|
| web-search | Real-time web search on AWS Bedrock (needs the machine's AWS credentials with Bedrock access) in two gears: fast (Amazon Nova grounding, default; US regions only) and deep (`--deep`, GPT-5.6 `web_search` via Bedrock Mantle, slower and token-expensive); regions set via its `fast_region` / `deep_region` params (Settings → Skills) (no command, `user-invocable: false`; model auto-activates) |
|
|
167
167
|
| halo | Platform self-knowledge — config directories, MD scopes, which bundled doc answers what (no command, `user-invocable: false`; model auto-activates) |
|
|
168
|
-
| extension | Install / list / remove canvas preview extensions — backs `/extension` (full access); its `hub_base` param (Settings → Skills) is where `install <id>` and the admin's update check read `<id>/update.json` (empty = derived from `hub_repo`, default turmind/halo-hub); `hub_repo` is also the hub `/extension models` pulls from |
|
|
168
|
+
| extension | Install / list / remove canvas preview extensions — backs `/extension` (full access); its `hub_base` param (Settings → Skills) is where `install <id>` and the admin's update check read `<id>/update.json` (empty = derived from `hub_repo`, default turmind/halo-hub); `hub_repo` is also the hub `/extension models` pulls from (and, for a GitHub hub, Settings → Model providers → Sync latest models, one click) |
|
|
169
169
|
|
|
170
170
|
All live under `~/.halo/global/skills/` and are force-overwritten on startup. To customize one, copy it into `<project>/.halo/skills/<id>/` and edit there (workspace > global > extension). `tavily-web-search` remains an optional (non-seeded) skill.
|
|
171
171
|
|
|
@@ -71,7 +71,7 @@ VS Code-style highlight selection (no checkboxes):
|
|
|
71
71
|
|
|
72
72
|
### Right-click menu
|
|
73
73
|
|
|
74
|
-
Items shown depend on the click target.
|
|
74
|
+
Items shown depend on the click target. Right-clicking inside a multi-selection shows "N items selected" over two items, Download N items and Delete N items. These three labels follow the UI language (zh: 已选 / 下载 / 删除 N 项); the single-item menu is English only. On the workspace root row only New File / New Folder / Open in Integrated Terminal / Reveal in File Manager / Download apply:
|
|
75
75
|
|
|
76
76
|
| Action | Shown when | API / behavior |
|
|
77
77
|
|---|---|---|
|
|
@@ -79,7 +79,7 @@ Items shown depend on the click target. With several items selected the menu off
|
|
|
79
79
|
| Open in Integrated Terminal | Always | Spawns a terminal at the target dir (or file's parent) |
|
|
80
80
|
| Reveal in File Manager | Desktop app only | Opens the OS file manager (Finder / Explorer / Linux FM) at the target — a folder opens itself, a file is highlighted in its parent dir. Hidden in a plain browser (gated on the `window.haloReveal` IPC bridge exposed by the desktop preload) |
|
|
81
81
|
| Open to the Side | File or bundle directory | Splits the editor and opens the file (or bundle) in the right pane |
|
|
82
|
-
| Download | File, folder, workspace root | File: `GET /api/files/download?path=...`. Folder / root: estimate + confirm, then one streamed zip — see [Folder download](#folder-download) |
|
|
82
|
+
| Download | File, folder, workspace root, multi-select | File: `GET /api/files/download?path=...`. Folder / root: estimate + confirm, then one streamed zip. Multi-select (files and folders mixed): the same confirm, one zip of the whole selection — see [Folder download](#folder-download) |
|
|
83
83
|
| Rename | Single file/folder | `POST /api/files/rename` |
|
|
84
84
|
| Delete | Single or multi-select | `DELETE /api/files?path=...` (with confirm) |
|
|
85
85
|
| 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 |
|
|
@@ -87,7 +87,7 @@ Items shown depend on the click target. With several items selected the menu off
|
|
|
87
87
|
The menu auto-clamps to the viewport, so right-clicking near the window edge does not clip the bottom items.
|
|
88
88
|
|
|
89
89
|
### Folder download
|
|
90
|
-
Download on a folder (or the workspace root) zips it on the server while it streams — nothing is staged on disk or in memory (`GET /api/files/download-folder`, see [dev/api.md](../dev/api.md#files)).
|
|
90
|
+
Download on a folder (or the workspace root), or Download N items on a multi-selection, zips it on the server while it streams — nothing is staged on disk or in memory (`GET /api/files/download-folder`, see [dev/api.md](../dev/api.md#files)).
|
|
91
91
|
- Clicking Download first fetches an estimate (`estimate=1`, a walk without reading contents) and opens a confirm: `≈ <size> · <n> files`, what is excluded by default with counts (node_modules / .git by name, the rest as "other build/cache folders"), how many items are always excluded or skipped
|
|
92
92
|
- Checkbox "Include node_modules, .git and other build/cache folders" (`include=excluded`) re-fetches the estimate when toggled
|
|
93
93
|
- More than 200,000 files, or a scan longer than 10 s → the dialog shows the server's 413 message ("download a subfolder instead") and Download stays disabled
|
|
@@ -97,6 +97,13 @@ Download on a folder (or the workspace root) zips it on the server while it stre
|
|
|
97
97
|
- **Names a zip can't hold** (containing `\`, or a top-level `C:`-style prefix) are skipped and listed as unreadable
|
|
98
98
|
- **Symlinks**: folder links are never followed; a file link is included only when its target stays inside the workspace and is not excluded
|
|
99
99
|
- When anything was left out, the zip carries `_halo-excluded.txt` at its root: excluded paths grouped by reason (a pruned folder is listed once, not its contents; capped at 1000 per reason plus a count), including unreadable files and skipped links
|
|
100
|
+
- **Multi-selection** (Download N items): the dialog is titled "Download N items as zip"; the estimate, checkbox, 413 handling and every rule above apply. On top of that:
|
|
101
|
+
- Zip entries are relative to the selected items' nearest common folder. Items picked in one folder appear by name (`a.txt`, `sub/…`); `a/b/x.txt` + `a/c` share `a/` and appear as `b/x.txt`, `c/…`
|
|
102
|
+
- The zip is named `<common folder>-selection.zip`; when the common folder is the workspace root, the workspace dir name is used (`<workspace dir>-selection.zip`)
|
|
103
|
+
- An item inside another selected folder (or picked twice) is counted once
|
|
104
|
+
- The 200,000-file and 10 s limits count the whole selection
|
|
105
|
+
- A selected file or link is checked as it would be inside a folder: a selected `.env`, secret, Halo runtime path or unsafe name is left out, and so is a selected folder link (never followed, unlike a single-folder download through a link) or a file link leaving the workspace. All of these are listed in `_halo-excluded.txt` and counted in the estimate. A selected always-excluded folder (e.g. `.halo/sessions`) is left out the same way rather than failing the download. A selected default-excluded folder (e.g. `node_modules`) is included, since it was picked explicitly
|
|
106
|
+
- A selection whose download link would pass 8,000 characters sends no request: the dialog shows "Too many items selected — download the parent folder instead" and Download stays disabled
|
|
100
107
|
|
|
101
108
|
### Modification indicator
|
|
102
109
|
Files with unsaved editor edits show a coloured dot in the tree (synced from `editorStore.modifiedPaths`).
|
|
@@ -92,7 +92,7 @@ params:
|
|
|
92
92
|
secrets: []
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
Built-in skills declare theirs the same way — today `extension`, with plain `hub_base` (the https base URL holding each hub extension's `<id>/update.json`, read by `/extension install <id>` and the admin's update check; point it at a mirror; empty = the GitHub raw `extensions/` directory of `hub_repo`) and `hub_repo` (the hub `/extension models`
|
|
95
|
+
Built-in skills declare theirs the same way — today `extension`, with plain `hub_base` (the https base URL holding each hub extension's `<id>/update.json`, read by `/extension install <id>` and the admin's update check; point it at a mirror; empty = the GitHub raw `extensions/` directory of `hub_repo`) and `hub_repo` (the hub `/extension models` and the Sync latest models button pull release zips from, and where the default `hub_base` comes from; empty = turmind/halo-hub) params, and `web-search`, with plain `fast_region` / `deep_region` params (AWS regions for its two Bedrock gears; empty = us-east-1 fast, us-west-2 deep / us-east-1 for sol). `halo setup` asks only for built-in skills' `secret: true` params; plain params are configured in Settings → Skills only.
|
|
96
96
|
|
|
97
97
|
Global agents declare theirs the same way in `agents/<agent-id>/agent-config.yaml` (same `params:` / `secrets:` lists; shown under Settings → Agents).
|
|
98
98
|
|
|
@@ -235,6 +235,29 @@ A malicious skill that tries `curl -H "Bearer {{aws-bedrock-claude-invoke.secret
|
|
|
235
235
|
|
|
236
236
|
Groups top to bottom: **System** (General · A2A remotes — only while a2a-remotes.yaml has a bearer remote · Security · Extensions, each with a leading icon), **Extension settings**, **Model providers**, **Agents**, **Skills**, and **Orphans** when there are any. Each group header is a quiet toggle (chevron + smaller muted label) that collapses its items; all groups start expanded, and the collapsed ones are remembered per browser (`localStorage` `halo_settings_navCollapsed`, collapsed ids only — so a group that appears later starts expanded).
|
|
237
237
|
|
|
238
|
+
## Sync latest models
|
|
239
|
+
|
|
240
|
+
The **Model providers** group ends with a **Sync latest models** row (refresh icon, styled like the nav items, opens no page). One click has the server fetch the hub's newest `models-v*` release and install the provider yamls in it that are newer than the copy in effect — the same rules as `/extension models` ([design/storage.md → Model registry format](../design/storage.md#model-registry-format), API [dev/api.md → Model sync](../dev/api.md#canvas-preview-extensions)).
|
|
241
|
+
|
|
242
|
+
- **Running**: the row reads "Syncing…" with a spinning icon and is disabled until the request returns.
|
|
243
|
+
- **Confirm**: when an install would send requests to a URL the provider didn't use before (a new provider, a changed default endpoint / presets, an added model endpoint), nothing is written yet. A "Confirm endpoint changes" dialog lists each such provider with its endpoint change lines; **Cancel** (or a click outside) sends nothing more, **Install** re-checks the hub and writes.
|
|
244
|
+
- **Result**: a "Models synced (<release>)" dialog with the summary line and one row per provider yaml — installed / up to date / skipped / refused, plus the reason (e.g. "needs a newer halo").
|
|
245
|
+
- **Failure**: a "Model sync failed" dialog with the server's error text (rate-limited, no release, network…), not the raw HTTP status.
|
|
246
|
+
- **GitHub hubs only**: the hub is the extension skill's `hub_repo` (Settings → Skills → extension; empty = turmind/halo-hub). Requests are anonymous, 60 per hour per IP. For a Gitea / Forgejo / GitLab hub the server refuses with a pointer to `/extension models` in a chat, which handles those.
|
|
247
|
+
- The provider list and model pickers refresh from the `models:changed` push — no reload, no polling. Existing agents keep the model / endpoint in their own `agent.yaml`.
|
|
248
|
+
|
|
249
|
+
## Models browse (provider pages)
|
|
250
|
+
|
|
251
|
+
A model provider's page shows a **Models** section below its credential fields: a read-only view of the provider config in effect (the bundled or hub copy that wins by `revision`), so you can see which models are wired up and where each one can be called without opening the yaml. Nothing here is editable — an agent's model and endpoint are picked in the agent editor. The section header (chevron + "Models" + model count) starts expanded and collapses on click; the state isn't persisted. Shown at both global and workspace scope (the provider config is global). A provider whose yaml declares no `secrets:` has no Settings page, so no browse either (every bundled provider declares some).
|
|
252
|
+
|
|
253
|
+
- **Provider line**: runtime · revision · default model (`defaultModelId`, else the first model; shown by its display name), then a one-line read-only hint.
|
|
254
|
+
- **One card per model**, in yaml order:
|
|
255
|
+
- display name (else the id), with a "Default" tag on the default model; under it the model id in mono, click to copy (a check mark confirms);
|
|
256
|
+
- right side: "Context 272K (max 1M)" — `contextWindow`, plus `maxContextWindow` when it differs — and "Output 128K" (`maxOutputTokens`); hovering the context shows the compaction ratio ("Compacts at 90% of the context"). Sizes read as K / M; a power-of-two size reads binary (262144 → 256K, 1048576 → 1M);
|
|
257
|
+
- capability chips: Image / Video / Audio input (none of the three → "Text only"); Thinking with its effort values (`low / medium / high / xhigh / max`), or "Thinking budget 4K" for a budget-mode model, plus "· on by default" when thinking starts on; Cache with its TTL values (`5m / 1h`); Verbosity with its values. In a list with more than one value the default is bold in the accent color, and the chip's tooltip names it;
|
|
258
|
+
- endpoint chips (map-pin icon): where the model can be called — its own `endpoints:` list, else the provider's `endpointPresets`, with the provider's `defaultEndpoint` listed first when the presets leave it out. An AWS host carrying a region (`bedrock-runtime.<region>.amazonaws.com[.cn]`, `bedrock-mantle.<region>.api.aws`) shows as the region (`us-east-1`); any other URL as its host in mono (`api.deepseek.com`). URLs with the same label share one chip. The default endpoint's chip is highlighted; the tooltip lists the full URL(s).
|
|
259
|
+
- **Data**: `GET /api/agent-configs/models` (the merged registry the agent form also reads), fetched when Settings opens and again on every `models:changed` push (a hub sync or `/extension models`) or WS reconnect — no polling. The yaml is never shown raw.
|
|
260
|
+
|
|
238
261
|
## Extensions view and update notices
|
|
239
262
|
|
|
240
263
|
**Settings → Extensions** (System group) lists the installed canvas preview extensions — on and off in one list sorted by id, plus refused directories as red rows — with **Install from zip** in the header. Each valid row offers:
|