@turmind/halo 1.5.5 → 1.5.7
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/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/989-e0faeff1c4a0b0b9.js +1 -0
- package/admin-out/_next/static/chunks/app/page-44f707ea8dad57da.js +1 -0
- package/admin-out/_next/static/css/b8c87b919758c52b.css +3 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/agent.md +14 -12
- package/bundled-docs/design/architecture.md +12 -11
- package/bundled-docs/design/canvas-extensions.md +19 -4
- package/bundled-docs/design/session.md +2 -2
- package/bundled-docs/design/storage.md +15 -12
- package/bundled-docs/dev/add-model-provider.md +33 -24
- package/bundled-docs/dev/api.md +4 -1
- package/bundled-docs/dev/deploy.md +1 -1
- package/bundled-docs/dev/desktop-packaging.md +4 -3
- package/bundled-docs/dev/env.md +14 -11
- package/bundled-docs/dev/tools.md +2 -2
- package/bundled-docs/guide/agents.md +2 -2
- package/bundled-docs/guide/cli.md +5 -4
- package/bundled-docs/guide/delegation-and-access.md +1 -1
- package/bundled-docs/guide/getting-started.md +1 -1
- package/bundled-docs/guide/skills.md +3 -3
- package/bundled-docs/guide/workspace.md +2 -2
- package/bundled-docs/requirements/editor.md +3 -2
- package/bundled-docs/requirements/settings.md +17 -8
- package/dist/index.js +9671 -9631
- package/package.json +1 -1
- package/templates/agents/default/agent.yaml +1 -0
- package/templates/models/anthropic.yaml +1 -0
- package/templates/models/aws-bedrock-claude-invoke.yaml +1 -0
- package/templates/models/aws-bedrock-mantle.yaml +1 -0
- package/templates/models/aws-bedrock-openai.yaml +1 -0
- package/templates/models/deepseek.yaml +1 -0
- package/templates/models/doubao.yaml +1 -0
- package/templates/models/hunyuan.yaml +1 -0
- package/templates/models/kimi.yaml +1 -0
- package/templates/models/mimo-token-plan-china.yaml +1 -0
- package/templates/models/minimax.yaml +1 -0
- package/templates/models/openai.yaml +1 -0
- package/templates/models/qwen.yaml +1 -0
- package/templates/models/zhipu.yaml +1 -0
- package/templates/skills/extension/SKILL.md +25 -14
- package/templates/skills/extension/config.yaml +9 -0
- package/templates/skills/extension/templates/ext.sh +166 -34
- package/templates/skills/web-search/SKILL.md +20 -6
- package/templates/skills/web-search/config.yaml +13 -0
- package/templates/skills/web-search/search.py +27 -6
- package/admin-out/_next/static/chunks/989-bc743317498d3fbc.js +0 -1
- package/admin-out/_next/static/chunks/app/page-0a390059f1914664.js +0 -1
- package/admin-out/_next/static/css/b53542687ebd4c65.css +0 -3
- /package/admin-out/_next/static/{cuVcldfdg1PXevaQMQSzb → rTD3zkMvzwtqUwzhz6QJH}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{cuVcldfdg1PXevaQMQSzb → rTD3zkMvzwtqUwzhz6QJH}/_ssgManifest.js +0 -0
|
@@ -97,6 +97,8 @@ Protocol (single-machine scope; NFS / cross-host explicitly out):
|
|
|
97
97
|
|
|
98
98
|
Bias is deliberate: pid reuse can read a stale pid as "alive," which only costs one missed cleanup round; a false-dead could stop another process's live sessions. Full race analysis in the file's header comment.
|
|
99
99
|
|
|
100
|
+
**`HALO_BADGE=DEV` servers never claim.** dev and prod serve the same workspaces, so prod (any other badge, or none) owns the lock outright: a DEV server (`config.server.ownsWorkspaceRuntimes === false`) builds its registry with `reconcileOrphansOnBoot: false`, skips the run-ledger eager sweep, and has channel route restore build the SessionManager directly. It never creates, rewrites or takes over `runtime.lock`, and runs no boot cleanup. Two costs: orphans left by a dev crash stay until prod's next restart cleans them, and a prod restart stamps the sub-sessions dev is running in a shared workspace stopped. That is what prod ownership means.
|
|
101
|
+
|
|
100
102
|
## Session file format (v1)
|
|
101
103
|
|
|
102
104
|
Path: `.halo/sessions/{agentId}/{sessionId}.json`
|
|
@@ -342,8 +344,8 @@ general: # built-in declarer (the server itself
|
|
|
342
344
|
max_summary_input: 15000
|
|
343
345
|
max_message_slice: 800
|
|
344
346
|
sandbox:
|
|
345
|
-
hidden_dirs: "~/.
|
|
346
|
-
hidden_files: "~/.
|
|
347
|
+
hidden_dirs: "~/.kube" # extras, appended to the built-in list
|
|
348
|
+
hidden_files: "~/.pgpass" # extras, appended to the built-in list
|
|
347
349
|
logging:
|
|
348
350
|
level: warn
|
|
349
351
|
|
|
@@ -385,20 +387,20 @@ A value of the form `<<ENV_NAME>>` is replaced at read time with `process.env.EN
|
|
|
385
387
|
|
|
386
388
|
| Path | Default | Notes |
|
|
387
389
|
|---|---|---|
|
|
388
|
-
| `general.session.max_queue_size` | 256 | Max queued messages per session |
|
|
389
|
-
| `general.session.max_nesting_depth` | 16 | Sub-session nesting cap |
|
|
390
|
-
| `general.compact.keep_messages` | 5 | Recent messages kept intact during compaction |
|
|
391
|
-
| `general.compact.max_summary_input` | 15000 | Local truncation fallback total char cap |
|
|
392
|
-
| `general.compact.max_message_slice` | 800 | Local truncation per-message char cap |
|
|
390
|
+
| `general.session.max_queue_size` | 256 | Max queued messages per session. Restart required |
|
|
391
|
+
| `general.session.max_nesting_depth` | 16 | Sub-session nesting cap. Restart required |
|
|
392
|
+
| `general.compact.keep_messages` | 5 | Recent messages kept intact during compaction. Restart required |
|
|
393
|
+
| `general.compact.max_summary_input` | 15000 | Local truncation fallback total char cap. Restart required |
|
|
394
|
+
| `general.compact.max_message_slice` | 800 | Local truncation per-message char cap. Restart required |
|
|
393
395
|
| `general.limits.shell_output_bytes` | 5242880 | Max bytes captured from one `shell_exec` (stdout+stderr); excess truncated with a `[truncated]` marker |
|
|
394
396
|
| `general.limits.web_fetch_bytes` | 51200 | Max bytes downloaded by one `web_fetch` |
|
|
395
397
|
| `general.limits.grep_default_matches` | 50 | Default `grep` match cap when no explicit `max` is passed |
|
|
396
398
|
| `general.limits.tool_result_render_chars` | 8000 | Per-tool-result cap on the content **fed to the LLM** (truncated with a re-run marker to protect the context window / prompt cache). The UI gets a much larger slice — see `tool_result_ui_chars` and `agent-loop.ts` (`resultContent` = LLM cap, `resultTextFull` = UI cap) |
|
|
397
399
|
| `general.limits.tool_result_ui_chars` | 65536 | Per-tool-result cap on the content **stored for UI display** (admin/web chat panel). Far larger than the LLM cap so a normal command's full output stays visible, but bounded so a multi-MB `cat` can't bloat the session file / WS payload / browser render; excess truncated with a marker pointing at `file_read` |
|
|
398
400
|
| `general.limits.terminal_scrollback_bytes` | 50000 | Off-screen scrollback bytes retained per detached persistent terminal |
|
|
399
|
-
| `general.sandbox.hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs`
|
|
400
|
-
| `general.sandbox.hidden_files` |
|
|
401
|
-
| `general.logging.level` | warn | `debug` / `info` / `warn` / `error
|
|
401
|
+
| `general.sandbox.hidden_dirs` | `''` | Extra dirs hidden from workspace/readonly sessions — bwrap tmpfs overlay (Linux) / Seatbelt deny (macOS). **Appended** to the built-in `DEFAULT_HIDDEN_DIRS` (`tools/sandbox.ts`: `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs`), which are always included. Full sessions, the admin file explorer and the terminal are not affected |
|
|
402
|
+
| `general.sandbox.hidden_files` | `''` | Extra files hidden from workspace/readonly sessions — bwrap bind of the empty `~/.halo/.sandbox-empty`, reads as empty (Linux) / Seatbelt deny (macOS). **Appended** to the built-in `DEFAULT_HIDDEN_FILES` (`~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs}.db` + `-wal` / `-shm`), which are always included. Full sessions, the admin file explorer and the terminal are not affected |
|
|
403
|
+
| `general.logging.level` | warn | `debug` / `info` / `warn` / `error`. Restart required |
|
|
402
404
|
| `general.observability.endpoint` | `''` | OTLP base URL of an OpenTelemetry collector (e.g. `http://localhost:4318`); empty = off. Restart required. See [observability.md](observability.md) |
|
|
403
405
|
| `general.observability.service_name` | `halo` | OTel resource `service.name`. Restart required |
|
|
404
406
|
| `general.observability.headers` | `''` | **Secret.** Extra OTLP request headers, comma-separated `k=v`. Restart required |
|
|
@@ -408,7 +410,7 @@ Schema source: [packages/server/src/settings-schema.ts](../../../packages/server
|
|
|
408
410
|
|
|
409
411
|
### Caching
|
|
410
412
|
|
|
411
|
-
`config.ts` reads settings.yaml lazily with mtime-watching: every read stats the file and reparses if mtime changed. UI saves bump mtime, so the server picks up new values on the next read **without a restart
|
|
413
|
+
`config.ts` reads settings.yaml lazily with mtime-watching: every read stats the file and reparses if mtime changed. UI saves bump mtime, so the server picks up new values on the next read **without a restart** — except the keys marked "Restart required" above plus `general.agent.max_retries`, which `config.ts` reads once into object literals at boot (observability: `initObservability()`); their schema fields carry `restartRequired: true` and the Settings page shows a restart notice after saving one.
|
|
412
414
|
|
|
413
415
|
### Orphans
|
|
414
416
|
|
|
@@ -423,6 +425,7 @@ Path: `~/.halo/global/models/<providerId>.yaml` — one file per provider. All f
|
|
|
423
425
|
```yaml
|
|
424
426
|
# aws-bedrock-claude-invoke.yaml
|
|
425
427
|
id: aws-bedrock-claude-invoke # required, must match filename and agent.yaml model.provider
|
|
428
|
+
runtime: bedrock-invoke # required, implementation class (wire protocol) — see below
|
|
426
429
|
displayName: AWS Bedrock Claude (Invoke API) # shown in UI
|
|
427
430
|
displayName_zh: AWS Bedrock Claude(Invoke API) # optional zh override
|
|
428
431
|
description: Invokes Bedrock via InvokeModel (non-streaming)
|
|
@@ -496,7 +499,7 @@ Existing agent yaml values always win over these defaults — switching provider
|
|
|
496
499
|
|
|
497
500
|
`agent.yaml`'s `model.provider` must match a provider's `id` — otherwise session spawn fails.
|
|
498
501
|
|
|
499
|
-
Routing: `
|
|
502
|
+
Routing: `id` is the provider identity (model picker, `agent.yaml` `model.provider`, `<id>.secrets.*`); `runtime:` picks the implementation class. `resolveProviderRuntime(providerId)` reads `runtime` from the registry entry, then `createModelRuntime(runtime, cfg)` dispatches on the runtime name only — the provider id is never matched against it. Runtimes: `bedrock-invoke` (BedrockAgent), `bedrock-mantle` (MantleAgent, OpenAI Responses), `anthropic-messages` (AnthropicAgent), `openai-chat` (OpenAIAgent, Chat Completions), and the vendor subclasses under their own names (`kimi`, `deepseek`, `minimax`, `qwen`, `hunyuan`, `doubao`, `zhipu`). Several providers can share one runtime (`anthropic` + `mimo-token-plan-china`; `aws-bedrock-mantle` + `aws-bedrock-openai`). No yaml for the id, no `runtime:` (a seed older than template v82), or an unknown name → session build throws naming the provider and yaml path. See [design/agent.md](../design/agent.md#agent-instance).
|
|
500
503
|
|
|
501
504
|
## Skill schema (`config.yaml`)
|
|
502
505
|
|
|
@@ -4,33 +4,37 @@ How to wire up a new LLM provider (OpenAI-compatible, a local model server — a
|
|
|
4
4
|
|
|
5
5
|
## Reality check
|
|
6
6
|
|
|
7
|
-
Halo is **not a plugin system** for providers today.
|
|
7
|
+
Halo is **not a plugin system** for providers today. A provider yaml's `id` is the provider identity (model picker, `agent.yaml` `model.provider`, settings namespace `<id>.secrets.*`); its `runtime:` field names the implementation class — the wire protocol. `createModelRuntime` (in `model-runtime.ts`) switches on the **runtime name only**; the provider id is never matched against it.
|
|
8
|
+
|
|
9
|
+
A provider whose upstream speaks a protocol Halo already has (Anthropic Messages, OpenAI Chat Completions, …) is **one new YAML manifest** with `runtime:` set to that name — no code. A new wire protocol means:
|
|
8
10
|
- One new runtime class extending `AgentLoop`
|
|
9
11
|
- One new YAML manifest on disk
|
|
10
|
-
- One case added to
|
|
11
|
-
- API key wiring in
|
|
12
|
+
- One name added to `MODEL_RUNTIME_NAMES` + one case added to the switch in `model-runtime.ts`
|
|
13
|
+
- API key wiring is automatic: `secrets:` in the manifest (see step 4)
|
|
12
14
|
|
|
13
|
-
No recompile of the frontend — the Form view's Provider dropdown reads the manifest at startup and picks up the new provider automatically. Server code
|
|
15
|
+
No recompile of the frontend — the Form view's Provider dropdown reads the manifest at startup and picks up the new provider automatically. Server code needs a rebuild only when you added a runtime.
|
|
14
16
|
|
|
15
17
|
Total effort: typically a few hours depending on how weird the upstream API is about tool use and response format.
|
|
16
18
|
|
|
17
19
|
### Existing providers
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
|
22
|
-
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
21
|
+
Runtime names (`runtime:` value → class): `bedrock-invoke` → `BedrockAgent`, `bedrock-mantle` → `MantleAgent` (OpenAI Responses API), `anthropic-messages` → `AnthropicAgent`, `openai-chat` → `OpenAIAgent` (Chat Completions); the vendor subclasses keep their vendor name (`kimi`, `deepseek`, `minimax`, `qwen`, `hunyuan`, `doubao`, `zhipu`). No implementation class reads the provider id — the endpoint (required in `agent.yaml`) carries everything host-specific.
|
|
22
|
+
|
|
23
|
+
| Provider ID | `runtime` | Class | File | Notes |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| `aws-bedrock-claude-invoke` | `bedrock-invoke` | `BedrockAgent` | `bedrock-agent.ts` | Anthropic Messages API via AWS SDK |
|
|
26
|
+
| `kimi` | `kimi` | `KimiAgent` | `kimi-agent.ts` | OpenAI-compatible, streaming via `fetchChatCompletionStream` (usage arrives in its own `choices: []` chunk), thinking (reasoning_content). Cached tokens via `cachedPromptTokens` (every known usage key — which one Kimi reports isn't pinned). Two API generations, branched on modelId: **K3** — vision (base64 data URLs only, no public-URL input), thinking always-on server-side, sole knob is top-level `reasoning_effort` (enum currently just `"max"`); **K2.6** — no vision, no `reasoning_effort` field at all, only the `thinking:{type:'enabled'|'disabled'}` toggle. Never send the K2.x `thinking` parameter to K3 |
|
|
27
|
+
| `deepseek` | `deepseek` | `DeepSeekAgent` | `deepseek-agent.ts` | OpenAI-compatible, streaming via `fetchChatCompletionStream` (first chunk has `content: null` + `reasoning_content: ''`; the usage chunk still carries `choices`), thinking (reasoning_content). Vision per model: `deepseek-flash` (V4.1-Flash) sees `image_url` data URLs; `deepseek-v4-pro` accepts the block but ignores it (live-probed 2026-09-24 with a 64×64 blue PNG) — registry `image` flag gates it |
|
|
28
|
+
| `minimax` | `minimax` | `MiniMaxAgent` | `minimax-agent.ts` | Anthropic Messages API (x-api-key), streaming via `fetchAnthropicStream` — note `message_start.usage` is all zeros and the real input / cache counts arrive in `message_delta.usage` (the accumulator takes them from there). Two thinking schemes, branched on registry `thinking.mode`: **M3** (default) — vision, thinking off by default and enabled via `thinking:{type:'adaptive'}` (no levels, no budget_tokens), no `promptCaching` in the manifest on purpose (official docs scope `cache_control` to M2.x only; M3 has passive automatic caching); **M2.7** — no vision, thinking always-on (manual budget_tokens), prompt caching (5m, M2.7 doesn't report cache_creation_input_tokens) |
|
|
29
|
+
| `qwen` | `qwen` | `QwenAgent` | `qwen-agent.ts` | Aliyun Bailian Anthropic-compatible API, streaming via `fetchAnthropicStream` (SSE frames come as `data:{…}` with no space after the colon — the reader accepts both), thinking enable/disable both honored, prompt caching (5m only), vision on Plus, max no vision |
|
|
30
|
+
| `hunyuan` | `hunyuan` | `HunyuanAgent` | `hunyuan-agent.ts` | Tencent Hy3 preview via OpenAI-compatible (tokenhub.tencentmaas.com), streaming via `fetchChatCompletionStream` (`stream_options.include_usage` accepted; usage in its own `choices: []` chunk), reasoning_effort low/high (no_think = omit), automatic prompt caching (cached_tokens), no vision |
|
|
31
|
+
| `doubao` | `doubao` | `DoubaoAgent` | `doubao-agent.ts` | ByteDance Doubao Seed-2.0 (pro/lite/mini/code) on Volcengine Ark, OpenAI-compatible, streaming via `fetchChatCompletionStream` (usage in its own `choices: []` chunk) + `thinking:{type:'enabled'/'disabled'}` (no `auto`), automatic prompt caching, no vision |
|
|
32
|
+
| `zhipu` | `zhipu` | `ZhipuAgent` | `zhipu-agent.ts` | Zhipu GLM-5.x on open.bigmodel.cn, OpenAI-compatible, streaming via `fetchChatCompletionStream` (tool-call arguments arrive complete in one fragment; usage chunk carries `choices`). Output cap via `max_tokens` only (`max_completion_tokens` is silently ignored). Thinking branched on modelId: **glm-5.3\*** always thinks — always sends `thinking:{type:'enabled',clear_thinking:false}`, `reasoning_effort` only when low/high/max (`disabled` / other efforts 400); other ids (glm-5.2) honour `thinking:{type:'disabled'}`. Vision per model: `glm-5.3-flash` / `glm-5.3-flashx` see `image_url` data URLs; `glm-5.3` / `glm-5.2` are text-only and 400 on any image part (code 1210, classified `multimodal_4xx` so leftover history images degrade). Automatic caching, `prompt_tokens_details.cached_tokens` (included in `prompt_tokens`, subtracted). |
|
|
33
|
+
| `openai` | `openai-chat` | `OpenAIAgent` | `openai-agent.ts` | **Generic OpenAI-compatible**. Use for native OpenAI or any third-party OpenAI gateway (incl. Ollama / llama.cpp). User fills endpoint + model id; capabilities openly editable in the form. Streaming via `fetchChatCompletionStream` — always sends `stream: true` + `stream_options: { include_usage: true }` (OpenAI's own field; Ollama ≥ 0.3 / vLLM / LM Studio accept it too). A gateway that rejects unknown fields would 400 — accepted trade-off, no non-streaming fallback. Forgiving about cache field names (`cachedPromptTokens`: first value > 0 of `prompt_tokens_details.cached_tokens`, top-level `cached_tokens`, `prompt_cache_hit_tokens`, `cache_read_tokens`) and reasoning field names (`reasoning_content` for OpenAI o-series/DeepSeek, `reasoning` for Ollama/llama.cpp). |
|
|
34
|
+
| `anthropic` | `anthropic-messages` | `AnthropicAgent` | `anthropic-agent.ts` | **Generic Anthropic Messages**. Use for api.anthropic.com or any Anthropic-compatible gateway. User fills endpoint + model id. Streaming via `fetchAnthropicStream` (the gateway must support `stream: true` SSE — the reference Anthropic behaviour). Standard `cache_control: ephemeral` with optional `ttl: '1h'`. |
|
|
35
|
+
| `mimo-token-plan-china` | `anthropic-messages` | `AnthropicAgent` | (reuses `anthropic-agent.ts`) | Xiaomi MiMo (UI name "Xiaomi MiMo"; the id keeps its historical name so existing configs don't break) — Anthropic-compatible gateway, streams like `anthropic` (gateway built for Claude Code, which always streams; not live-probed); no dedicated class, just `runtime: anthropic-messages` in its yaml (`defaultEndpoint` `token-plan-cn.xiaomimimo.com/anthropic`). The key type picks the endpoint: Token Plan `tp-`/`ttp-` keys only work on `token-plan-{cn,sgp,ams}.xiaomimimo.com/anthropic`, pay-as-you-go `sk-` keys only on `api.xiaomimimo.com/anthropic` — all four ship as `endpointPresets` (pay-as-you-go live-probed 2026-09-24; sgp / ams from the docs only). V2.6 (`mimo-v2.6-pro` / `-flash`, both vision, 1M / 131072) is current; `mimo-v2.5(-pro)` kept in the template until the official 2026-10-21 deprecation, then drop |
|
|
36
|
+
| `aws-bedrock-mantle` | `bedrock-mantle` | `MantleAgent` | `mantle-agent.ts` | OpenAI GPT-6.1 Sol, GPT-6 (Astra / Sol / Luna) and GPT-5.6 (Sol / Terra / Luna) on Amazon Bedrock via the **OpenAI Responses API** (not Completions/Converse/InvokeModel). Vision, reasoning via `reasoning.effort` (low/med/high/xhigh/max, no budget_tokens; on 5.6 `max` is accepted live but absent from the model's own validation list — measured ~6% more reasoning_tokens than xhigh; on Astra it's on the official list), `text.verbosity` (low/medium/high, same on both generations). **Region split** (live-probed 2026-09-24): us-east-1 serves GPT-6.1 Sol + GPT-6 Sol / Luna + every 5.6 model (404s Astra; GPT-6.1 Sol probed 2026-09-30, us-east-1 only); us-east-2 serves only the 5.6 trio; us-west-2 serves Astra + 5.6 Terra / Luna. Template ships all three as `endpointPresets`, default is now us-east-1 (`openai.gpt-6-sol` default model); Astra agents pick us-west-2 in the form; `extractRegionFromHost` falls back to us-east-1. All GPT-6 variants: 1,050,000 context / 128,000 max output, same effort (none/low/medium/high/xhigh/max) + verbosity surface; input beyond 272K tokens bills at 2x, so cap `context.maxTokens` in agent.yaml unless that's intended. Never surfaces reasoning summaries — `output[].reasoning.summary` stays empty even with `reasoning.summary=detailed` while `reasoning_tokens` counts up (verified live), so no thinking text in the UI is upstream behavior, not a parsing gap. Streams (`stream: true`, Responses API SSE folded by `readResponsesStream` in the same file): `response.output_text.delta` frames go out live, the terminal `response.completed` / `response.incomplete` frame carries the full final response object; this host sends **no** `data: [DONE]` (live-probed 2026-09-29). |
|
|
37
|
+
| `aws-bedrock-openai` | `bedrock-mantle` | `MantleAgent` | (reuses `mantle-agent.ts`) | Frontier models on Bedrock via the **bedrock-runtime** host's OpenAI Responses surface (`https://bedrock-runtime.<region>.amazonaws.com/openai/v1` — AWS's recommended endpoint for new apps; Mantle is the compatibility one). Same wire format, so its yaml just says `runtime: bedrock-mantle` + `extractRegionFromHost` accepts both hostnames (SigV4 signs with the host's region — a mismatched region 401s "Credential should be scoped to a valid region"). Rules that differ from Mantle: model ids must be cross-Region inference profiles (`global.*` / `us.*`; bare ids 400 "on-demand throughput isn't supported"); **no region split** — live-probed 2026-09-24, all nine `global.*` ids answer 200 from us-east-1 / us-west-2 / eu-west-1 / ap-northeast-1 alike; **no server-side tools** — `tools:[{type:"web_search"}]` 400s "web search is not supported for this request", which is why the web-search skill's deep gear stays on Mantle. Ships the same seven **GPT-6.1 / 6 / 5.6** entries as the Mantle template (`global.openai.gpt-6.1-sol`, `global.openai.gpt-6-{astra,sol,luna}`, `global.openai.gpt-5.6-{sol,terra,luna}`; identical effort none/low/medium/high/xhigh/max + verbosity surface per each model's own validation error — except Astra and GPT-6.1 Sol 400 on `none`, which the runtime never sends), plus **Grok 4.6** (500K ctx, 2x input price past 200K, max output undisclosed → 64000 carry-over, effort **only** low/medium/high/xhigh — `none`/`max` 400, so no Max preset; vision OK; $2/$6) and **Kimi K3** (1M ctx / 131072 out, effort none…max all accepted, ladder advisory on short prompts; vision OK; $3/$15). All: implicit prompt caching verified (run 1 `cache_write_tokens`, runs 2+ `cached_tokens` ≈ prefix), no reasoning summaries. Streams like Mantle (same event vocabulary) but this host **does** append a trailing `data: [DONE]`, which `readSseJson` skips; grok-4.6 emits a `reasoning` output item (no summary text deltas) before the `message` item. |
|
|
34
38
|
|
|
35
39
|
---
|
|
36
40
|
|
|
@@ -155,6 +159,7 @@ Create `packages/server/templates/models/<provider>.yaml` (deployed to `~/.halo/
|
|
|
155
159
|
|
|
156
160
|
```yaml
|
|
157
161
|
id: myprovider # required, must match filename and agent.yaml model.provider
|
|
162
|
+
runtime: myprovider # required, implementation class — an existing name (e.g. openai-chat) or your new one
|
|
158
163
|
displayName: My Provider
|
|
159
164
|
description: Short description
|
|
160
165
|
defaultEndpoint: https://api.example.com/v1
|
|
@@ -190,15 +195,17 @@ Shape source: [packages/server/templates/models/aws-bedrock-claude-invoke.yaml](
|
|
|
190
195
|
|
|
191
196
|
### 3. Wire the dispatcher
|
|
192
197
|
|
|
193
|
-
|
|
198
|
+
Skip this step when the manifest's `runtime:` names an existing runtime. For a new one, edit [packages/server/src/agents/model-runtime.ts](../../../packages/server/src/agents/model-runtime.ts) — add the name to `MODEL_RUNTIME_NAMES`, then the import and switch case (keyed by the runtime name, not the provider id):
|
|
194
199
|
|
|
195
200
|
```ts
|
|
196
201
|
import { MyProviderAgent } from './myprovider-agent.js'
|
|
197
202
|
|
|
203
|
+
export const MODEL_RUNTIME_NAMES = [/* … */, 'myprovider'] as const
|
|
204
|
+
|
|
198
205
|
case 'myprovider':
|
|
199
206
|
return new MyProviderAgent({
|
|
200
207
|
modelId: cfg.modelId,
|
|
201
|
-
endpoint: cfg.endpoint
|
|
208
|
+
endpoint: cfg.endpoint, // required — agent.yaml model.endpoint
|
|
202
209
|
apiKey: cfg.apiKey ?? '',
|
|
203
210
|
systemPrompt: cfg.systemPrompt,
|
|
204
211
|
tools: cfg.tools,
|
|
@@ -239,7 +246,7 @@ If a user wants to keep the key in an env var, they leave the `default` (`<<MY_P
|
|
|
239
246
|
|
|
240
247
|
### 5. Bump template version
|
|
241
248
|
|
|
242
|
-
In [packages/server/src/init.ts](../../../packages/server/src/init.ts), increment `TEMPLATE_VERSION` so new manifests and settings deploy on
|
|
249
|
+
In [packages/server/src/init.ts](../../../packages/server/src/init.ts), increment `TEMPLATE_VERSION` so new manifests and settings deploy on the next server start or `halo cli` / `halo tui` run (both call `refreshTemplatesIfOutdated`).
|
|
243
250
|
|
|
244
251
|
### 6. Use it
|
|
245
252
|
|
|
@@ -265,12 +272,14 @@ Or via the Form view: the Provider dropdown will include the new provider once t
|
|
|
265
272
|
5. Check the usage badges: `inputTokens` (non-cached) + `cacheRead` should equal total prompt tokens; token ring should show correct context window percentage
|
|
266
273
|
6. If the provider supports thinking, verify `reasoning_content` appears and tool loop works across multiple turns
|
|
267
274
|
|
|
268
|
-
If something's wrong, server logs under `~/.halo/global/logs/server.log` are the first place to look.
|
|
275
|
+
If something's wrong, server logs under `~/.halo/global/logs/server.log` are the first place to look. Session build throws a `[model-runtime]` error naming the provider and its yaml path when no manifest has that `id` (`Unknown provider "..."`), when the manifest has no `runtime:` (stale seed — run `halo setup` or restart the server), or when `runtime:` names something not in `MODEL_RUNTIME_NAMES` (the error lists the valid names).
|
|
269
276
|
|
|
270
277
|
---
|
|
271
278
|
|
|
272
279
|
## When to use the generic `openai` / `anthropic` provider instead of writing a new class
|
|
273
280
|
|
|
281
|
+
(A curated provider of your own for a stock-protocol vendor is just a manifest with `runtime: openai-chat` or `runtime: anthropic-messages` — `mimo-token-plan-china` is that case.)
|
|
282
|
+
|
|
274
283
|
Two of the providers above are deliberately generic — they take whatever `endpoint` + `model id` the user fills in the form and don't ship a curated model list. Use them when:
|
|
275
284
|
|
|
276
285
|
- The new vendor speaks **stock OpenAI chat completions** or **stock Anthropic Messages** with no quirks worth coding around. Examples: self-hosted vLLM, third-party OpenAI gateway.
|
|
@@ -391,7 +400,7 @@ Keeping it a hard-coded switch is deliberate for now:
|
|
|
391
400
|
- The Agent management UI reads the manifest directly; a runtime-registered plugin would need to notify the frontend dynamically
|
|
392
401
|
- One provider lands every few months, not every week
|
|
393
402
|
|
|
394
|
-
|
|
403
|
+
The switch is keyed by runtime name (one case per wire protocol / vendor class), so providers that share a protocol never add a case. If / when the switch becomes unwieldy, the next step would be a module-load registry — each runtime exports a `register(runtimeName, factory)` call that the dispatcher discovers at boot. Until then, editing the switch is simpler than managing a registry.
|
|
395
404
|
|
|
396
405
|
---
|
|
397
406
|
|
package/bundled-docs/dev/api.md
CHANGED
|
@@ -558,7 +558,7 @@ Source: [packages/server/src/routes/agent-configs.ts](../../../packages/server/s
|
|
|
558
558
|
```
|
|
559
559
|
|
|
560
560
|
Files created:
|
|
561
|
-
- `<agentDir>/agent.yaml` — scaffold whose `model` block
|
|
561
|
+
- `<agentDir>/agent.yaml` — scaffold whose `model` block is a copy of the default agent's `model:` mapping (workspace scope: `<projectId>/.halo/agents/default/agent.yaml` if it exists, else the global default agent; global scope: the global one). Missing file / no `model` mapping → derived from the provider registry (`aws-bedrock-claude-invoke` if installed, else the first installed provider). Empty tools/skills; the response's `agent.model` is that block's `id`
|
|
562
562
|
- `<agentDir>/AGENT.md` — `# <name>\n\n<description>\n`
|
|
563
563
|
|
|
564
564
|
### PUT `/api/agent-configs/:id/yaml?scope=&projectId=`
|
|
@@ -735,6 +735,9 @@ Resolves declared schema (from `models/<id>.yaml` `secrets:`, `skills/<id>/confi
|
|
|
735
735
|
"description_zh": "...",
|
|
736
736
|
"default": "...", // schema-default placeholder; omitted when the schema declares none
|
|
737
737
|
"secret": true, // UI masks input + value
|
|
738
|
+
"globalOnly": false, // general.* only: workspace overrides ignored, workspace writes 400
|
|
739
|
+
"restartRequired": false, // general.* only: read once at boot — UI shows a restart notice after saving
|
|
740
|
+
"advanced": false, // general.* only: rendered in the collapsed "Advanced" area (all but language / theme)
|
|
738
741
|
"value": "AK****ST", // already masked when secret:true; null = unset
|
|
739
742
|
"hasValue": true, // any layer has a non-empty value
|
|
740
743
|
"source": "global", // 'workspace' | 'global' | 'unset'
|
|
@@ -224,7 +224,7 @@ This installs the `halo` binary on `$PATH`. Subcommands available:
|
|
|
224
224
|
### Upgrade flow
|
|
225
225
|
|
|
226
226
|
1. `halo upgrade` — bumps the on-disk npm package
|
|
227
|
-
2. `halo server restart` — server's startup check sees `~/.halo/global/.template-version` is behind the new bundled `TEMPLATE_VERSION`, runs `ensureHaloHome` automatically, then starts. Refreshes `docs/`, built-in agents, built-in skills, system prompts, and the model registry. User-owned files (USER.md, custom agents/skills, INSTRUCTIONS.md overrides) are left alone. See `init.ts` for the per-category overwrite policy.
|
|
227
|
+
2. `halo server restart` — server's startup check sees `~/.halo/global/.template-version` is behind the new bundled `TEMPLATE_VERSION`, runs `ensureHaloHome` automatically, then starts (`halo cli` / `halo tui` run the same check on start, so a CLI-only install refreshes without a server). Refreshes `docs/`, built-in agents, built-in skills, system prompts, and the model registry. User-owned files (USER.md, custom agents/skills, INSTRUCTIONS.md overrides) are left alone. See `init.ts` for the per-category overwrite policy.
|
|
228
228
|
|
|
229
229
|
### Public release command reference
|
|
230
230
|
|
|
@@ -374,9 +374,10 @@ Still good practice: build server + admin **before** `pnpm dist:arm64`.
|
|
|
374
374
|
|
|
375
375
|
- **Changing anything under `templates/` requires bumping `TEMPLATE_VERSION`
|
|
376
376
|
(`packages/server/src/init.ts`) — otherwise the change never reaches existing
|
|
377
|
-
installs.** Server startup
|
|
378
|
-
reseed) when the on-disk
|
|
379
|
-
|
|
377
|
+
installs.** Server startup (and `halo cli` / `halo tui` start) only re-runs
|
|
378
|
+
`ensureHaloHome` (the template reseed) when the on-disk
|
|
379
|
+
`~/.halo/global/.template-version` is *strictly less than* the compiled
|
|
380
|
+
`TEMPLATE_VERSION` (`init.ts` `refreshTemplatesIfOutdated`). Equal → the
|
|
380
381
|
whole reseed is skipped, so a template edit with no version bump is invisible
|
|
381
382
|
to every machine that already has the prior version stamped. This bit the
|
|
382
383
|
`team`-delegation rewrite: `default/AGENT.md` gained the "team whitelist"
|
package/bundled-docs/dev/env.md
CHANGED
|
@@ -82,11 +82,12 @@ Three config file types, precedence **env vars > config.yaml / settings.yaml > c
|
|
|
82
82
|
| `~/.halo/secrets/settings.yaml` | User | Provider secrets, skill params, `general.*` server knobs (session, compaction, limits, sandbox, logging) — "preferences" |
|
|
83
83
|
| `<project>/.halo/settings.yaml` | Project | Overrides of provider / skill `params` and `secrets`; every `general.*` key is global-only (a workspace value is rejected by the API and ignored at runtime) |
|
|
84
84
|
|
|
85
|
-
`init.ts` seeds these on first run with a per-category policy. Refresh trigger: `halo setup` always re-runs the seed, and the
|
|
85
|
+
`init.ts` seeds these on first run with a per-category policy. Refresh trigger: `halo setup` always re-runs the seed, and the startup check re-runs it automatically when `~/.halo/global/.template-version` is behind the bundled `TEMPLATE_VERSION` — on server start and at the start of `halo cli` / `halo tui` (`init.ts` `refreshTemplatesIfOutdated`, called from `index.ts` and the CLI's `harness.ts` `initRuntime`; a never-seeded home is left to `halo setup`).
|
|
86
86
|
|
|
87
87
|
- **Always overwritten** (platform-owned, refreshed when the template version moves): `~/.halo/global/{prompts,models,docs}/`, `INSTRUCTIONS.md`, the built-in agent ids (`default`, `executor`, `deep-executor`, `goal`, `__evo_agent__`, `__score__`, `__apply_agent__`), built-in skill ids (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `aws-knowledge`, `web-search`, `self`, `halo`, `extension`).
|
|
88
88
|
- **Built-in agents** keep the user's `model:` and `context:` blocks on overwrite — the admin UI lets users change which model an agent uses and its context limits, and that choice survives upgrades.
|
|
89
89
|
- **Optional skills** (`tavily-web-search`) install only when picked via `halo setup`; the opt-in list is `~/.halo/global/.installed-optional-skills`. Picked skills are force-overwritten alongside the always-overwritten set.
|
|
90
|
+
- **Skill params in `halo setup`**: a newly picked optional skill walks all its declared params; for built-in skills setup asks only the `secret: true` ones (`listRequiredSkillsWithSecrets`, `setup-providers.ts`) — plain params such as `extension.params.hub_repo` / `web-search.params.fast_region` are set in Settings → Skills only.
|
|
90
91
|
- **`secrets/config.yaml`** is leaf-merged: existing leaf `value`s preserved, new leaves added when a server upgrade introduces them.
|
|
91
92
|
- **`secrets/settings.yaml`** is created empty if missing and never touched again. Defaults live in `settings-schema.ts`.
|
|
92
93
|
|
|
@@ -111,7 +112,7 @@ Source: `packages/server/src/config.ts`
|
|
|
111
112
|
| `HALO_LOG_MAX_FILES` | `5` | `config.yaml logging.max_files` | Retained rotated logs |
|
|
112
113
|
| `HALO_LOG_LEVEL` | `warn` | `settings.yaml general.logging.level` | Log level: debug \| info \| warn \| error |
|
|
113
114
|
| `HALO_JWT_SECRET` | (none) | `config.yaml server.jwt_secret` | JWT signing secret (generated by `halo setup` if missing; stripped from child-process env) |
|
|
114
|
-
| `HALO_BADGE` | (none) | — | Environment badge (e.g. `DEV`) returned by `GET /api/auth/check` on **both** the 200 and 401 branches, so the admin can brand the login screen too. The admin repaints the favicon with a badge band and prefixes the tab title, making parallel dev/prod tabs tellable apart. dev and prod serve the same static build, so this has to be a runtime signal. Unset / whitespace-only → `null` → stock branding. Because the 401 branch carries it, the value is **public to unauthenticated clients** — keep it a label (`DEV`, `staging`), never anything sensitive. |
|
|
115
|
+
| `HALO_BADGE` | (none) | — | Environment badge (e.g. `DEV`) returned by `GET /api/auth/check` on **both** the 200 and 401 branches, so the admin can brand the login screen too. The admin repaints the favicon with a badge band and prefixes the tab title, making parallel dev/prod tabs tellable apart. dev and prod serve the same static build, so this has to be a runtime signal. Unset / whitespace-only → `null` → stock branding. Because the 401 branch carries it, the value is **public to unauthenticated clients** — keep it a label (`DEV`, `staging`), never anything sensitive. `DEV` (trimmed, any case) also makes the server a **non-owner of workspace runtimes**: it never claims `<workspace>/.halo/runtime.lock` and skips boot cleanup (orphan reconcile, goal / run-ledger sweeps), leaving both to the prod server that shares those workspaces. Any other value → normal owner. See [storage.md](../design/storage.md#workspace-runtime-lock). |
|
|
115
116
|
| `HALO_RUNTIME_MODE` | (none) | — | `agentcore` runs the server as an Amazon Bedrock AgentCore Runtime container (see [design/agentcore.md](../design/agentcore.md)) |
|
|
116
117
|
| `HALO_WORKSPACE` | `process.cwd()` | — | Workspace root in agentcore mode |
|
|
117
118
|
| `HALO_DEFAULT_PROVIDER` | (none) | — | Read by `halo setup --non-interactive`: binds the built-in agents to this provider id (and its default model). An unknown id only prints a warning |
|
|
@@ -120,17 +121,17 @@ Source: `packages/server/src/config.ts`
|
|
|
120
121
|
| `HALO_PROMPT` | (none) | — | `plain` makes `halo setup` use numbered-menu prompts instead of the raw-mode TUI |
|
|
121
122
|
|
|
122
123
|
settings.yaml only (no env override):
|
|
123
|
-
- `general.session.max_queue_size` (default 256)
|
|
124
|
-
- `general.session.max_nesting_depth` (default 16)
|
|
125
|
-
- `general.agent.max_retries` (default 5)
|
|
126
|
-
- `general.compact.keep_messages` (default 5) — recent messages to keep uncompacted
|
|
127
|
-
- `general.compact.max_summary_input` (default 15000) — local compaction fallback input cap
|
|
128
|
-
- `general.compact.max_message_slice` (default 800) — local compaction per-message cap
|
|
124
|
+
- `general.session.max_queue_size` (default 256), restart required
|
|
125
|
+
- `general.session.max_nesting_depth` (default 16), restart required
|
|
126
|
+
- `general.agent.max_retries` (default 5), restart required
|
|
127
|
+
- `general.compact.keep_messages` (default 5) — recent messages to keep uncompacted, restart required
|
|
128
|
+
- `general.compact.max_summary_input` (default 15000) — local compaction fallback input cap, restart required
|
|
129
|
+
- `general.compact.max_message_slice` (default 800) — local compaction per-message cap, restart required
|
|
129
130
|
- `general.server.trust_proxy` (default `false`) — trust `x-forwarded-for` for client IP resolution, scope: global (enable only behind a reverse proxy you control)
|
|
130
|
-
- `general.sandbox.hidden_dirs` (default `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs`)
|
|
131
|
+
- `general.sandbox.hidden_dirs` (default empty) — extra dirs hidden from workspace/readonly sessions (bwrap tmpfs overlays on Linux, Seatbelt deny on macOS), **appended** to the built-in list, which is always included (`~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs`); Full sessions, the admin file explorer and the terminal are not affected, scope: global
|
|
131
132
|
- `general.sandbox.writable_dirs` (default empty) — dirs writable inside the sandbox besides the workspace (Linux / macOS), for external CLIs that keep local state (e.g. `~/.kiro`); not applied to readonly sessions, scope: global
|
|
132
|
-
- `general.sandbox.hidden_files` (default `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs}.db` + their `-wal`/`-shm` files)
|
|
133
|
-
- `general.logging.level` (default `warn`) — log level: debug | info | warn | error
|
|
133
|
+
- `general.sandbox.hidden_files` (default empty) — extra files hidden from workspace/readonly sessions, read as empty files (bwrap bind of `~/.halo/.sandbox-empty` on Linux, Seatbelt deny on macOS), **appended** to the built-in list, which is always included (`~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs}.db` + their `-wal`/`-shm` files); Full sessions, the admin file explorer and the terminal are not affected, scope: global
|
|
134
|
+
- `general.logging.level` (default `warn`) — log level: debug | info | warn | error, restart required
|
|
134
135
|
- `general.observability.endpoint` (default `''`) — OTLP collector base URL (e.g. `http://localhost:4318`); empty = off, scope: global, restart required. See [design/observability.md](../design/observability.md)
|
|
135
136
|
- `general.observability.service_name` (default `halo`) — OTel resource `service.name`, scope: global, restart required
|
|
136
137
|
- `general.observability.headers` (default `''`, secret) — extra OTLP request headers, comma-separated `k=v`, scope: global, restart required
|
|
@@ -145,6 +146,8 @@ When a provider manifest declares a `default: <<NAME>>` for a secret, `halo setu
|
|
|
145
146
|
|
|
146
147
|
`<skill-id>.params.*` (referenceable from skills via `{{<skill-id>.params.<key>}}`, or short form `{{params.<key>}}` inside SKILL.md; declared in `skills/<id>/config.yaml`):
|
|
147
148
|
- `tavily-web-search.params.api_key` — example only; declared by whichever skill needs it
|
|
149
|
+
- `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)
|
|
150
|
+
- `extension.params.hub_repo` — hub `/extension install <id>` pulls from (empty = `https://github.com/turmind/halo-hub`); 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)
|
|
148
151
|
|
|
149
152
|
Hardcoded (config.ts, no override):
|
|
150
153
|
- `auth.tokenMaxAge` — 14 days
|
|
@@ -175,9 +175,9 @@ This is a known, unfixed gap (not a bug in a specific route): don't hand out `wo
|
|
|
175
175
|
|
|
176
176
|
Sensitive directories and files are hidden from workspace/readonly sessions via bwrap overlays / Seatbelt deny rules (and the same lists gate `assertPathAllowed` for in-process file tools). Under bwrap a hidden file reads as empty and a hidden dir as an empty directory; under Seatbelt both are denied outright. Paths that don't exist on the filesystem are silently skipped. Two categories coexist:
|
|
177
177
|
|
|
178
|
-
**Global lists (configurable)** — credentials and cross-workspace state
|
|
178
|
+
**Global lists (built-in + configurable extras)** — credentials and cross-workspace state. The built-in lists are code constants in `sandbox.ts` (`DEFAULT_HIDDEN_DIRS` / `DEFAULT_HIDDEN_FILES`, the single source) and are always included in the effective lists (which apply to workspace/readonly sessions only — Full sessions skip the sandbox entirely); `settings.yaml` `general.sandbox.hidden_dirs` / `hidden_files` hold extra entries that are **appended** (deduped) — they never replace the built-ins, so a built-in entry cannot be removed from the list in settings. `config.ts` `resolveSandboxPaths()` builds the effective lists for both the server and the CLI:
|
|
179
179
|
|
|
180
|
-
| Setting |
|
|
180
|
+
| Setting | Built-in (always included) | Method |
|
|
181
181
|
|---|---|---|
|
|
182
182
|
| `hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs` | bwrap `--tmpfs` overlay (empty directory); Seatbelt `subpath` deny |
|
|
183
183
|
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs}.db` + their `-wal`/`-shm` files | bwrap `--ro-bind ~/.halo/.sandbox-empty` (reads as empty); Seatbelt `literal` deny |
|
|
@@ -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/`
|
|
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/` 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.
|
|
231
231
|
|
|
232
|
-
A
|
|
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.
|
|
@@ -251,7 +251,8 @@ Session prefix: `cli_`. Sessions are persisted to `<workspace>/.halo/sessions/`
|
|
|
251
251
|
### Initialization
|
|
252
252
|
|
|
253
253
|
`tui` / `cli` / `agents` / `sessions` refuse to run ("~/.halo/global/ not initialized. Run halo setup first.") until `halo setup` has seeded `~/.halo/global/`. Then the CLI replicates the server's init sequence (`initRuntime()` in `harness.ts`):
|
|
254
|
-
1. `
|
|
255
|
-
2. `
|
|
256
|
-
3. `
|
|
257
|
-
4. `
|
|
254
|
+
1. `refreshTemplatesIfOutdated()` — the server's template startup check: when `~/.halo/global/.template-version` is behind the bundled `TEMPLATE_VERSION` (typically right after `halo upgrade`), re-seed `~/.halo/global/` before anything reads the models registry; logs `[CLI] Templates outdated (vX → vY), refreshing ~/.halo/global/` to stderr. Runs once per upgrade — afterwards the stamp matches and it's a single file read
|
|
255
|
+
2. `initLogger()` — redirect console to stderr + file logger
|
|
256
|
+
3. `initBwrapCheck()` — probe sandbox availability
|
|
257
|
+
4. `setSandboxHiddenPaths()` — configure sandbox paths
|
|
258
|
+
5. `new SessionManager(workspace)` — create agent session manager (the workspace's `.halo/` is seeded via `ensureWorkspaceHalo`)
|
|
@@ -104,7 +104,7 @@ Extra rules:
|
|
|
104
104
|
- `view_image` also needs a vision-capable model.
|
|
105
105
|
- A skill with `requiresAccess` above the session level is hidden (no metadata, no `activate_skill`).
|
|
106
106
|
- Relay tools are only built for `full` sessions.
|
|
107
|
-
- Hidden from `workspace` / `readonly` sessions
|
|
107
|
+
- Hidden from `workspace` / `readonly` sessions only (Full sessions, the admin file explorer and the terminal see everything): a built-in list that is always included (`~/.halo/secrets`, `~/.aws`, `~/.ssh`, `~/.gnupg`, `~/.docker`, `~/.config/gh`, `~/.gitconfig`, `~/.git-credentials`, `~/.npmrc`, `~/.netrc`, the global evo/cron/runs databases, internal-session transcripts, logs, …), any extra paths added in `general.sandbox.hidden_dirs` / `hidden_files` (they add to the built-in list, never replace it), plus the workspace's own `.halo/sessions`, `.halo/logs`, `.halo/evo`, `.halo/halo.db*` (fixed in code). The rest of `.halo/` stays readable.
|
|
108
108
|
|
|
109
109
|
### Where a session's level comes from
|
|
110
110
|
|
|
@@ -77,7 +77,7 @@ After `halo setup`:
|
|
|
77
77
|
└── channels/channels.db ← per-channel account state
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
**When does "refreshed on upgrade" actually run?** `halo setup` always re-runs the seed; the server's startup check also re-runs it automatically when `~/.halo/global/.template-version` is behind the bundled `TEMPLATE_VERSION`. So the routine flow `halo upgrade && halo server restart` is enough — no need to remember `halo setup`.
|
|
80
|
+
**When does "refreshed on upgrade" actually run?** `halo setup` always re-runs the seed; the server's startup check — and the same check at the start of `halo cli` / `halo tui` — also re-runs it automatically when `~/.halo/global/.template-version` is behind the bundled `TEMPLATE_VERSION`. So the routine flow `halo upgrade && halo server restart` is enough — no need to remember `halo setup` — and a CLI-only user just runs `halo cli` / `halo tui` after `halo upgrade`.
|
|
81
81
|
|
|
82
82
|
**Server-refreshed**: `INSTRUCTIONS.md`, `prompts/`, `models/`, `docs/`, the built-in agent ids (`default`, `executor`, `deep-executor`, `goal`, `__evo_agent__`, `__score__`, `__apply_agent__`), and the built-in skill ids (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `web-search`, `halo`, `extension`). To customize one, copy it into the workspace scope (`<project>/.halo/...`) — workspace replaces global at runtime.
|
|
83
83
|
|
|
@@ -138,7 +138,7 @@ params:
|
|
|
138
138
|
secrets: [] # rare for skills — server-side only, never substituted into shell_exec
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
-
The Settings page reads this declaration and renders inputs grouped under the skill's name. The user's value lands in `~/.halo/secrets/settings.yaml` at `<skill-id>.params.api_key`.
|
|
141
|
+
The Settings page reads this declaration and renders inputs grouped under the skill's name. The user's value lands in `~/.halo/secrets/settings.yaml` at `<skill-id>.params.api_key`. Among built-ins, `extension` declares a plain `hub_repo` param and `web-search` plain `fast_region` / `deep_region` params; `halo setup` asks only for built-in skills' `secret: true` params, so plain ones are set in Settings → Skills.
|
|
142
142
|
|
|
143
143
|
> Credentials **do not** belong directly in `settings.yaml` — use `<<ENV_NAME>>` placeholders with the real value in env vars, or type them once into the Settings page (which still encrypts in the UI but stores plaintext on disk). Don't write credentials in SKILL.md either. SKILL.md + the schema in `config.yaml` are meant to be shareable; values are the user's.
|
|
144
144
|
|
|
@@ -158,9 +158,9 @@ Halo seeds these skills on every startup (the ids in `BUILTIN_SKILL_IDS`, `packa
|
|
|
158
158
|
| send-file | Deliver an image/video/file as a channel attachment by emitting `MEDIA:<absolute_path>` — works on Web / WeChat / Telegram / Slack / Feishu (no command; model-activated, workspace access) |
|
|
159
159
|
| self | The agent's own visual space (`.halo/canvas/self.html`) for self-expression (no command; model-activated, full access) |
|
|
160
160
|
| aws-knowledge | Query the official AWS Knowledge MCP server for up-to-date AWS docs (no command, `user-invocable: false`; model auto-activates) |
|
|
161
|
-
| web-search | Real-time web search in two gears: fast (Amazon Nova grounding, default) and deep (`--deep`, GPT-5.6 `web_search` via Bedrock Mantle, slower and token-expensive) (no command, `user-invocable: false`; model auto-activates) |
|
|
161
|
+
| 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) |
|
|
162
162
|
| halo | Platform self-knowledge — config directories, MD scopes, which bundled doc answers what (no command, `user-invocable: false`; model auto-activates) |
|
|
163
|
-
| extension | Install / list / remove canvas preview extensions — backs `/extension` (full access) |
|
|
163
|
+
| extension | Install / list / remove canvas preview extensions — backs `/extension` (full access); its `hub_repo` param (Settings → Skills) picks the hub `install <id>` pulls from (default turmind/halo-hub) |
|
|
164
164
|
|
|
165
165
|
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 overrides global). `tavily-web-search` remains an optional (non-seeded) skill.
|
|
166
166
|
|
|
@@ -85,11 +85,11 @@ Special-file previews:
|
|
|
85
85
|
|
|
86
86
|
### Preview extensions
|
|
87
87
|
|
|
88
|
-
File types the editor has no built-in viewer for (e.g. `.glb` 3D models) show a "no built-in preview" page with a link to [halo-hub](https://github.com/turmind/halo-hub), where ready-made viewer extensions live. Extensions are installed **server-wide** (`~/.halo/global/extensions/<id>/`) and take effect in every open tab immediately — no restart, no page reload.
|
|
88
|
+
File types the editor has no built-in viewer for (e.g. `.glb` 3D models) show a "no built-in preview" page with a link to [halo-hub](https://github.com/turmind/halo-hub), where ready-made viewer extensions live: 3D models (`glb` — `.glb` / `.gltf` / `.obj` / `.stl`), draw.io diagrams (`drawio`, editable), Excalidraw whiteboards (`excalidraw`, editable) and Jupyter notebooks (`ipynb`, read-only). Extensions are installed **server-wide** (`~/.halo/global/extensions/<id>/`) and take effect in every open tab immediately — no restart, no page reload.
|
|
89
89
|
|
|
90
90
|
Three ways to install:
|
|
91
91
|
- **Settings → Extensions**: upload the extension `.zip`; the same panel lists what's installed (version, file types) and removes extensions
|
|
92
|
-
- **Ask the agent**: `/extension install glb` pulls the latest `glb-v*` release from halo-hub; `/extension install /path/to/x.zip` or an `https://…zip` URL also work. `/extension list` and `/extension remove <id>` round it out (full-access sessions only)
|
|
92
|
+
- **Ask the agent**: `/extension install glb` pulls the latest `glb-v*` release from the configured hub — halo-hub unless you change **Settings → Skills → extension → `hub_repo`** (a GitHub / Gitea / Forgejo / GitLab repo URL, `owner/repo` for GitHub, or any git URL / local repo, which installs from `glb-v<x.y.z>` tags); `/extension install /path/to/x.zip` or an `https://…zip` URL also work. `/extension list` and `/extension remove <id>` round it out (full-access sessions only)
|
|
93
93
|
- **By hand**: drop an unpacked extension directory into `~/.halo/global/extensions/` — the server watches that directory
|
|
94
94
|
|
|
95
95
|
Once installed, matching files open in the extension's viewer. If more than one viewer can show a file, the preview header gets an **Open with** menu (per tab, not remembered). Extensions that declare the `save` capability can edit the file: the tab shows the usual dirty dot, and the header **Save** button writes it back (Ctrl/Cmd+S doesn't apply to extension tabs). If the file changed on disk in between, you're asked before overwriting.
|
|
@@ -60,8 +60,9 @@ File types without a built-in plugin can be handled by an **installed extension*
|
|
|
60
60
|
- **Resolution order** for a file extension: `default`-priority extensions (newest install wins) → built-in plugins → `option`-priority extensions. `.glb` with the `glb` extension installed opens in the viewer directly; an `option` extension only shows up in the header's **Open with** menu
|
|
61
61
|
- **Opening and tab restore**: whether a file opens as text or in a viewer is decided when it is clicked, restored after a page reload, or opened to the side. Since an installed extension may claim a type (`.glb`) or even a text suffix (`.json`), a file that would open as text first waits for the page's initial extension list (up to 3 s), so a `.glb` tab survives a reload as a viewer rather than coming back as text. Files that already have a built-in viewer open without waiting. If the list isn't back within 3 s (or failed to load), files open as if no extension were installed — a `.glb` opens as text; a list that arrives later still updates the viewer of tabs that are already previews
|
|
62
62
|
- **Open with** (header menu, shown only when there are >2 candidates counting *Open as text*): switches the current tab's viewer; the choice is per tab and not persisted
|
|
63
|
-
- **Fallback**: a file with no built-in plugin and no installed extension shows a static page — "no built-in preview for this type; extensions for more file types are on
|
|
63
|
+
- **Fallback**: a file with no built-in plugin and no installed extension shows a static page — "no built-in preview for this type; extensions for more file types are on <hub> ↗", where `<hub>` is the extension skill's `hub_repo` (default halo-hub; a local path / ssh remote shows as text) — plus Open as text / Download. No online lookup
|
|
64
64
|
- **Editable extensions** (manifest `capabilities: ["save"]`): the iframe reports dirty → tab shows the dirty dot → header **Save** button (Ctrl/Cmd+S is *not* wired for extension tabs — Monaco isn't mounted) → `PUT /api/files/raw`. If the file changed on disk since it was loaded the save gets a 409 and the user is asked "changed on disk … Overwrite?". An external change while the tab is clean reloads it; while dirty, local edits are kept silently and the conflict surfaces at the next save
|
|
65
|
+
- **Install sources**: Settings → Extensions zip upload; `/extension install <id|zip|url>` (listed by the built-in `default` agent, full access), where an id comes from the hub in Settings → Skills → extension → `hub_repo` (default halo-hub; GitHub / Gitea / Forgejo / GitLab release zips, or tags of any other git repo); or a manual directory drop
|
|
65
66
|
- **Live install / upgrade / uninstall** (from Settings → Extensions upload / remove, the `/extension` skill, or a manual directory change): pushed over WS, no reload. An upgraded extension remounts open tabs (or shows a "updated — reload" banner if the tab is dirty); uninstalling drops clean tabs to the fallback page and keeps dirty tabs alive with a banner
|
|
66
67
|
- **Settings → Extensions**: lists installed extensions (name, version, file types, license/homepage) and manifest errors; upload `.zip` (≤ 100 MB) and remove. Any logged-in admin can do both — the admin cookie has no access level
|
|
67
68
|
|
|
@@ -73,7 +74,7 @@ Markdown and HTML open as text in Monaco *and* have a rendered view — Canvas d
|
|
|
73
74
|
- Toggle to Edit → Monaco source, Cmd+S saves as usual
|
|
74
75
|
|
|
75
76
|
### Preview caching (MRU)
|
|
76
|
-
Recently opened preview tabs stay mounted (up to 5, MRU) so switching between them doesn't re-fetch or re-parse. Plugins flagged `heavy: true` bypass the cache — only the active instance mounts, others unmount. An extension tab with unsaved edits stays pinned beyond the 5. Closing a preview tab removes it from the cache immediately (aborting any in-flight fetch).
|
|
77
|
+
Recently opened preview tabs stay mounted (up to 5, MRU) so switching between them doesn't re-fetch or re-parse. Plugins flagged `heavy: true` bypass the cache — only the active instance mounts, others unmount; switching between two heavy tabs of the same type (e.g. two `.ipynb`) remounts the viewer, so it shows the newly active file. An extension tab with unsaved edits stays pinned beyond the 5. Closing a preview tab removes it from the cache immediately (aborting any in-flight fetch).
|
|
77
78
|
|
|
78
79
|
### File metadata in header
|
|
79
80
|
The Canvas header shows `(size · Created … · Modified …)` for the active tab. Both text tabs and preview tabs populate this — preview tabs fetch `GET /api/files/stat` on open (and on tab restore from localStorage) since they never read content.
|