@turmind/halo 2.2.2 → 2.2.4
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-9d30cdb1a86e4beb.js +1 -0
- package/admin-out/_next/static/chunks/4362.115405260299210d.js +1 -0
- package/admin-out/_next/static/chunks/{7613.fe24922a6a839656.js → 7073.4081ed209e6748bd.js} +1 -1
- package/admin-out/_next/static/chunks/{7073.9d5f9dd7a77827b7.js → 7613.9c1657bb3b52d1b5.js} +1 -1
- package/admin-out/_next/static/chunks/app/{page-10a4afac9cbc27a2.js → page-c9d174a5432b0007.js} +1 -1
- package/admin-out/_next/static/chunks/{webpack-9532ec7f1fa9a757.js → webpack-1970c5140e4019b7.js} +1 -1
- package/admin-out/_next/static/css/87d4fc049f865389.css +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/a2a.md +3 -1
- package/bundled-docs/design/architecture.md +1 -1
- package/bundled-docs/design/cron.md +1 -1
- package/bundled-docs/design/evolution.md +6 -6
- package/bundled-docs/design/node.md +18 -39
- package/bundled-docs/design/observability.md +2 -2
- package/bundled-docs/design/relay.md +1 -1
- package/bundled-docs/design/session.md +3 -3
- package/bundled-docs/design/storage.md +5 -2
- package/bundled-docs/design/stt-local.md +2 -2
- package/bundled-docs/design/ws.md +3 -3
- package/bundled-docs/dev/api.md +10 -9
- package/bundled-docs/dev/deploy.md +1 -1
- package/bundled-docs/dev/env.md +2 -1
- package/bundled-docs/dev/tools.md +10 -6
- package/bundled-docs/guide/README.md +1 -1
- package/bundled-docs/guide/cli.md +4 -31
- package/bundled-docs/guide/node.md +57 -36
- package/bundled-docs/requirements/command.md +1 -1
- package/bundled-docs/requirements/settings.md +39 -2
- package/dist/evo-wrapper.js +368 -268
- package/dist/index.js +2330 -2531
- package/package.json +2 -1
- package/templates/agents/default/agent.yaml +1 -1
- package/templates/models/aws-bedrock-openai.yaml +81 -7
- package/templates/skills/cron/SKILL.md +1 -3
- package/admin-out/_next/static/chunks/1989-3b2faa2731d973c4.js +0 -1
- package/admin-out/_next/static/chunks/4362.9d2ee4f9b6facc49.js +0 -1
- package/admin-out/_next/static/css/8fc43fce83e5ce5d.css +0 -1
- /package/admin-out/_next/static/{u9pg60rITCj73MBYeXsW1 → vf5W8US5y8B2f1E6zJJw3}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{u9pg60rITCj73MBYeXsW1 → vf5W8US5y8B2f1E6zJJw3}/_ssgManifest.js +0 -0
|
@@ -84,7 +84,7 @@ To list agents or sessions without running a prompt, use the dedicated subcomman
|
|
|
84
84
|
```
|
|
85
85
|
[ 17:23:02 in 0.0K out 0.1K ctx 5.8K read 5.3K write 0.3K cache 94% e2e 3.3s think medium claude-sonnet-4-6 ]
|
|
86
86
|
```
|
|
87
|
-
- **json format**: Single JSON object on stdout after completion (`text` has the same final-answer semantics as text format; `notices` holds the same root system notices as an array of strings, without the `ℹ️ ` prefix):
|
|
87
|
+
- **json format**: Single JSON object on stdout after completion (`text` has the same final-answer semantics as text format; `notices` holds the same root system notices as an array of strings, without the `ℹ️ ` prefix; `error` is `null` on success, else the reason — an error event's text, or the root's last-turn error such as a model refusal):
|
|
88
88
|
|
|
89
89
|
```json
|
|
90
90
|
{
|
|
@@ -106,10 +106,12 @@ halo cli -w /path/to/review-ws "review the diff" --format json | jq '.text'
|
|
|
106
106
|
# Pipe content in
|
|
107
107
|
git diff HEAD~1 | halo cli -w /path/to/review-ws "review this diff"
|
|
108
108
|
|
|
109
|
-
# Exit code: 0 = success, 1 = error, 130 = SIGINT, 143 = SIGTERM (graceful: session repaired + flushed before exit)
|
|
109
|
+
# Exit code: 0 = success, 1 = error (including a model refusal on the root's last turn — Anthropic's refusal or an OpenAI-format provider's content-filter block), 130 = SIGINT, 143 = SIGTERM (graceful: session repaired + flushed before exit)
|
|
110
110
|
halo cli "check for issues" && echo "OK" || echo "FAILED"
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
A refusal on a sub-agent's turn does not change the exit code — it reaches the root as an aborted report, and the run exits 0 if the root then wraps up normally. A later root turn that ends normally clears an earlier refusal.
|
|
114
|
+
|
|
113
115
|
When stdin is not a TTY, piped input is appended after the positional prompt. With a positional prompt, stdin is only read if its first data arrives within 0.5 s — then to EOF, however long it takes. Otherwise it's ignored, so an open-but-idle stdin (scripts, CI, cron, `ssh host 'halo cli …'` without `-t`) no longer hangs the run. A producer that takes longer than that to print anything gets ignored too; capture it first (`out=$(slow-cmd); halo cli "summarize" <<<"$out"`). Without a positional prompt, stdin *is* the prompt: `halo cli` waits for EOF, and after 1 s of silence prints one `waiting for the prompt on stdin` hint to stderr.
|
|
114
116
|
|
|
115
117
|
## TUI Mode (interactive)
|
|
@@ -308,35 +310,6 @@ the caller-side `auth: sigv4` remote, session dirs and ops notes:
|
|
|
308
310
|
[design/agentcore.md](../design/agentcore.md). The hub skill `agentcore-deploy`
|
|
309
311
|
builds the image and creates the runtime for you.
|
|
310
312
|
|
|
311
|
-
## Linked machine (`halo node`)
|
|
312
|
-
|
|
313
|
-
```bash
|
|
314
|
-
HALO_NODE_TOKEN=<token> halo node --server https://halo.example.com --root ~/proj [--allow-shell]
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
Links this machine to a workspace on a halo server, so that workspace's
|
|
318
|
-
full-access agents can work on files here with the `node_*` tools. Runs in the
|
|
319
|
-
foreground, reconnects on its own, Ctrl-C to stop. Needs no `halo setup` and
|
|
320
|
-
no model keys — nothing is written under `~/.halo/`. Create the token in
|
|
321
|
-
Admin → Channels → Linked machines.
|
|
322
|
-
|
|
323
|
-
On Linux (x86_64 / arm64 / armv7) the standalone `halo-node` binary from the
|
|
324
|
-
GitHub release does the same job with the same flags and doesn't need Node.js
|
|
325
|
-
(`curl -fsSL …/install.sh | sh` installs it); `halo node` there prints a
|
|
326
|
-
one-line `[Node] tip:` with that command. See
|
|
327
|
-
[node.md](node.md#2-run-it-on-the-machine).
|
|
328
|
-
|
|
329
|
-
| Flag / env | Meaning |
|
|
330
|
-
|---|---|
|
|
331
|
-
| `--server <url>` | Server address, `http(s)://host:port` (`ws(s)://` also accepted) — required |
|
|
332
|
-
| `--token <t>` / `HALO_NODE_TOKEN` | Pairing token — required; prefer the env var (flags show up in `ps`) |
|
|
333
|
-
| `--root <dir>` | Folder to share; every path must stay inside it. Omit (or `/`) for whole-machine mode: any path this user can reach, relative paths from the current dir |
|
|
334
|
-
| `--allow-shell` | Let agents run commands (`node_run`) as this user, not sandboxed. Off by default |
|
|
335
|
-
| `--header "Name: value"` | Repeatable. Extra header on the connection, for an auth proxy in front of the server |
|
|
336
|
-
|
|
337
|
-
Exits 1 on a rejected / revoked token, a deleted workspace or a protocol
|
|
338
|
-
mismatch; retries everything else. Full guide: [node.md](node.md).
|
|
339
|
-
|
|
340
313
|
## Architecture
|
|
341
314
|
|
|
342
315
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Linked machines (`halo
|
|
1
|
+
# Linked machines (`halo-node`)
|
|
2
2
|
|
|
3
|
-
Let a workspace's agents work on files on **your own computer** — a laptop, a desktop, a box behind NAT — while the agents themselves keep running on the halo server. You run `halo
|
|
3
|
+
Let a workspace's agents work on files on **your own computer** — a laptop, a desktop, a box behind NAT — while the agents themselves keep running on the halo server. You run `halo-node` on that machine; it dials out to the server (no port to open), and the workspace's agents get `node_*` tools that read, write, search and (if you allow it) run commands there.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
[your laptop] [server]
|
|
@@ -8,24 +8,21 @@ halo-node --root ~/proj ──WebSocket──────▶ halo server ──
|
|
|
8
8
|
(files, optional shell) node_read / node_write / … / node_run
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The machine runs `halo-node`, a single binary attached to every GitHub release for **Linux (x86_64 / arm64 / armv7)**, **macOS (Apple silicon / Intel)** and **Windows (amd64)**. It needs no Node.js, no `halo setup` and no model keys, and writes nothing under `~/.halo/`. The Linux build is ~7 MB and idles at about 9 MB of memory.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- **macOS** (or a Linux / Windows machine that already has the npm package): `halo node` from `npm install -g @turmind/halo`.
|
|
13
|
+
Supported: Linux and Windows run vet and the tests in CI (Linux arm64 / armv7 under qemu). The macOS builds are only cross-compiled — not tested on a Mac; on macOS, file errors from the agent's tools can read `Unknown system error -<n>` instead of the usual `ENOENT: no such file or directory` text. Windows `node_run` commands go to `cmd.exe`, with UTF-8 output and a GBK fallback.
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Supported: **Linux, macOS and Windows amd64**. The Go client is tested natively on Linux and Windows CI; macOS uses npm. Windows `node_run` commands go to `cmd.exe`, with UTF-8 output and a GBK fallback.
|
|
15
|
+
> `halo node` (the npm client from `npm install -g @turmind/halo`) was removed in 2.2.4; use `halo-node`. Same flags, same token.
|
|
19
16
|
|
|
20
17
|
## 1. Create a token
|
|
21
18
|
|
|
22
|
-
Admin → **Channels → Linked machines** → **Add machine**. Pick the workspace and a machine name (`a-z 0-9 -`, e.g. `mbp`; unique within the workspace — the agent uses it to address the machine). The dialog shows two commands to copy:
|
|
19
|
+
Admin → **Channels → Linked machines** → **Add machine**. Pick the workspace and a machine name (`a-z 0-9 -`, e.g. `mbp`; unique within the workspace — the agent uses it to address the machine). The dialog shows two commands to copy: **Linux / macOS** installs `halo-node` with the install script and runs it with `~/.local/bin` prepended to `PATH` (where a non-root install lands); **Windows** downloads `halo-node-windows-amd64.exe` in PowerShell and runs it. The download link points at the server's own release, so the machine and the server come from the same version.
|
|
23
20
|
|
|
24
21
|
The token stays on the list row (copy button). Deleting the row revokes it at once and disconnects the machine.
|
|
25
22
|
|
|
26
23
|
## 2. Run it on the machine
|
|
27
24
|
|
|
28
|
-
**Linux** — install once with the release's install script, then run:
|
|
25
|
+
**Linux / macOS** — install once with the release's install script, then run:
|
|
29
26
|
|
|
30
27
|
```bash
|
|
31
28
|
curl -fsSL https://github.com/turmind/halo-agent/releases/latest/download/install.sh | sh
|
|
@@ -36,7 +33,7 @@ halo-node --server https://halo.example.com --root ~/proj
|
|
|
36
33
|
|
|
37
34
|
What the script does (`sh`, busybox included; curl or wget):
|
|
38
35
|
|
|
39
|
-
- picks `halo-node-linux-{amd64,arm64,armv7}`
|
|
36
|
+
- picks the binary from `uname -s` and `uname -m`: on Linux `halo-node-linux-{amd64,arm64,armv7}` (`armv8l`, a 64-bit kernel with 32-bit userland, gets armv7); on macOS (`Darwin`) `halo-node-darwin-arm64` for `arm64` (Apple silicon) and `halo-node-darwin-amd64` for `x86_64` (Intel). Any other architecture or OS stops with a message.
|
|
40
37
|
- downloads that release's binary and checks it against the release's `SHA256SUMS` (`sha256sum` or `shasum`). A mismatch stops with an error and leaves nothing behind. With neither tool installed it warns and skips the check.
|
|
41
38
|
- installs to `/usr/local/bin/halo-node` when run as root, otherwise to `~/.local/bin/halo-node`. If that directory isn't in `PATH`, it says so. Debian, Ubuntu and Raspberry Pi OS add `~/.local/bin` to `PATH` only at a login after it exists, which is why the commands above (and the admin's) prepend it.
|
|
42
39
|
- runs the downloaded binary's `--version` before replacing anything; a download that doesn't run stops with an error.
|
|
@@ -48,16 +45,19 @@ Each release's `install.sh` installs that release's binary. The admin's command
|
|
|
48
45
|
**Manual download** — if you'd rather not pipe a script into `sh`, have only wget (`wget -qO- <url>/install.sh | sh` also works), or the machine can't reach GitHub (download on another machine, copy it over):
|
|
49
46
|
|
|
50
47
|
```bash
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
O=$(uname -s | tr A-Z a-z) # linux | darwin
|
|
49
|
+
A=$(uname -m); case "$A" in x86_64) A=amd64;; aarch64|arm64) A=arm64;; armv7l|armv8l) A=armv7;; *) echo "no halo-node build for $A" >&2;; esac
|
|
50
|
+
curl -fLo halo-node "https://github.com/turmind/halo-agent/releases/latest/download/halo-node-$O-$A" && chmod +x halo-node
|
|
51
|
+
# optional: verify (macOS: shasum -a 256 -c instead of sha256sum -c)
|
|
52
|
+
curl -fsL https://github.com/turmind/halo-agent/releases/latest/download/SHA256SUMS | grep "halo-node-$O-$A\$" | sed "s/halo-node-$O-$A/halo-node/" | sha256sum -c
|
|
55
53
|
./halo-node --server https://halo.example.com --root ~/proj
|
|
56
54
|
```
|
|
57
55
|
|
|
56
|
+
On macOS, a binary downloaded in a browser is quarantined and Gatekeeper blocks it (the builds aren't notarized); clear that once before running it: `xattr -d com.apple.quarantine ./halo-node-darwin-*` (or whatever you renamed it to). `curl` / `wget` downloads — including `install.sh` — aren't quarantined.
|
|
57
|
+
|
|
58
58
|
### Windows (amd64)
|
|
59
59
|
|
|
60
|
-
Download `halo-node-windows-amd64.exe` from the [GitHub release](https://github.com/turmind/halo-agent/releases/latest) matching your server (no
|
|
60
|
+
Download `halo-node-windows-amd64.exe` from the [GitHub release](https://github.com/turmind/halo-agent/releases/latest) matching your server (no Node.js needed; the admin's command does it with `Invoke-WebRequest` into the current folder). Put it in e.g. `C:\halo-node`. In PowerShell:
|
|
61
61
|
|
|
62
62
|
```powershell
|
|
63
63
|
$env:HALO_NODE_TOKEN = '<token>'
|
|
@@ -66,30 +66,35 @@ $env:HALO_NODE_TOKEN = '<token>'
|
|
|
66
66
|
|
|
67
67
|
The release's `SHA256SUMS` includes the exe; compare its entry with `Get-FileHash C:\halo-node\halo-node-windows-amd64.exe -Algorithm SHA256`. Use either `/` or `\` in paths; `~/` expands from `USERPROFILE`. Folder mode resolves symlinks and junctions before checking the root. Add `--allow-shell` only if you want commands as this Windows user. Stop the process before replacing the exe during an upgrade.
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
It runs in the foreground and logs each call (`[Node] read src/app.ts`, `[Node] refused write ../x: outside root`). Ctrl-C stops it. It reconnects by itself after network drops or a server restart; it exits (code 1) on a bad / revoked token or when the workspace is gone.
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
npm install -g @turmind/halo # once
|
|
73
|
-
export HALO_NODE_TOKEN=<token>
|
|
74
|
-
halo node --server https://halo.example.com --root ~/proj
|
|
75
|
-
```
|
|
71
|
+
The admin row turns green (live, no refresh) and shows `folder · shell on/off · os`.
|
|
76
72
|
|
|
77
|
-
|
|
73
|
+
### Flags
|
|
78
74
|
|
|
79
|
-
|
|
75
|
+
| Flag / env | Meaning |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `--server <url>` | Server address, `http(s)://host:port` (`ws(s)://` also accepted) — required |
|
|
78
|
+
| `--token <t>` / `HALO_NODE_TOKEN` | Pairing token — required; prefer the env var (flags show up in `ps`) |
|
|
79
|
+
| `--root <dir>` | Folder to share; every path must stay inside it. Omit (or `/`) for whole-machine mode: any path this user can reach, relative paths from the current dir |
|
|
80
|
+
| `--allow-shell` | Let agents run commands (`node_run`) as this user, not sandboxed. Off by default |
|
|
81
|
+
| `--proxy <url>` | `http(s)://` forward proxy for the connection, e.g. `--proxy http://127.0.0.1:12334`; when given, `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` are ignored ([below](#through-a-forward-proxy)) |
|
|
82
|
+
| `--header "Name: value"` | Repeatable. Extra header on the connection, for an auth proxy in front of the server |
|
|
83
|
+
| `HALO_SHELL_TIMEOUT` | `node_run` timeout in ms (default 120000) |
|
|
84
|
+
| `--version` / `--help` | Print the version / usage |
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
Exits 1 on a rejected / revoked token, a deleted workspace, a protocol mismatch or a bad flag; retries everything else.
|
|
82
87
|
|
|
83
88
|
### Folder mode vs whole-machine mode
|
|
84
89
|
|
|
85
90
|
- **`--root <folder>`** — share one folder. Every path the agent passes must stay inside it (symlinks pointing out are refused too); relative paths start from it.
|
|
86
|
-
- **No `--root`** (or `--root /`) — **whole-machine mode**: the agent can reach every path your user can, like a full-access local session. Relative paths start from the directory you ran `halo
|
|
91
|
+
- **No `--root`** (or `--root /`) — **whole-machine mode**: the agent can reach every path your user can, like a full-access local session. Relative paths start from the directory you ran `halo-node` in. A startup line warns about it, and the admin shows "Whole machine".
|
|
87
92
|
|
|
88
93
|
Prefer `--root` unless you really mean the whole machine.
|
|
89
94
|
|
|
90
95
|
### `--allow-shell`
|
|
91
96
|
|
|
92
|
-
Off by default — `node_run` then refuses with "restart `halo
|
|
97
|
+
Off by default — `node_run` then refuses with "restart `halo-node` with --allow-shell". With it, agents can run **any command as your user, not sandboxed**. In folder mode only the command's working directory is held inside the folder; the command itself (`cat ~/.ssh/...`, `curl ...`) can reach anything your user can. Turn it on only for agents and workspaces you trust. Commands are killed after the shell timeout (120 s, or `HALO_SHELL_TIMEOUT` in ms on the machine).
|
|
93
98
|
|
|
94
99
|
### What the agent can see
|
|
95
100
|
|
|
@@ -97,25 +102,41 @@ Everything inside the shared folder — including `.env` files, keys, credential
|
|
|
97
102
|
|
|
98
103
|
### Behind a login proxy
|
|
99
104
|
|
|
100
|
-
`halo
|
|
105
|
+
`halo-node` sends a bearer token, nothing else. If the server sits behind an auth proxy (Cloudflare Access, an SSO gateway, basic auth), either point `--server` at an address the machine can reach directly, or pass the proxy's credentials with `--header` (repeatable, `Name: value`, split on the first colon):
|
|
101
106
|
|
|
102
107
|
```bash
|
|
103
108
|
halo-node --server https://halo.example.com --root ~/proj \
|
|
104
109
|
--header "CF-Access-Client-Id: <id>" --header "CF-Access-Client-Secret: <secret>"
|
|
105
110
|
```
|
|
106
111
|
|
|
107
|
-
|
|
112
|
+
### Through a forward proxy
|
|
113
|
+
|
|
114
|
+
To send the connection out through a local or corporate proxy (Hiddify / Clash system proxy, a company HTTP proxy), pass `--proxy`:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
halo-node --server https://halo.example.com --root ~/proj --proxy http://127.0.0.1:12334
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`http://` and `https://` proxies only, `user:pass@` allowed (`--proxy http://alice:pw@proxy.corp:3128` — sent as `Proxy-Authorization`, never printed; the startup line shows only `scheme://host:port`). Percent-encode a `/`, `?`, `#` or `@` in the user or password (`%2F`, `%3F`, `%23`, `%40`); a URL with anything after the host is refused. Without `--proxy`, halo-node reads the usual environment variables — `HTTPS_PROXY` for an `https://` server, `HTTP_PROXY` for `http://`, `NO_PROXY` for exceptions:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
HTTPS_PROXY=http://127.0.0.1:12334 halo-node --server https://halo.example.com --root ~/proj
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`--proxy` wins: when it's given, all three variables are ignored. A server on `localhost` / `127.0.0.1` is never proxied through the variables. Most system-proxy apps don't set these variables for the terminal, so pass one of the two explicitly. Conversely, a machine whose shell sets `HTTPS_PROXY` / `HTTP_PROXY` for other tools but reaches the server directly needs the server's host in `NO_PROXY` (halo-node before 2.2.4 ignored the variables).
|
|
127
|
+
|
|
128
|
+
Use a `https://` server through a proxy. An `https://` (wss) connection is tunnelled with `CONNECT`, so the proxy sees only the host and the token stays inside TLS. An `http://` (ws) server is requested from the proxy as a plain absolute-form `GET` — the token passes through the proxy in clear text, and a proxy that doesn't forward the WebSocket `Upgrade` can't connect at all.
|
|
108
129
|
|
|
109
130
|
### TLS certificates (`https://` servers)
|
|
110
131
|
|
|
111
|
-
`halo-node` trusts the system's CA store (the Windows certificate store on Windows; `/etc/ssl/certs/…` on Linux, where `SSL_CERT_FILE` / `SSL_CERT_DIR` can point it elsewhere). It falls back to a built-in Mozilla root set only on a system with no CA bundle at all. For a private or corporate CA, install it into the system store, or on Linux set `SSL_CERT_FILE`.
|
|
132
|
+
`halo-node` trusts the system's CA store (the Windows certificate store on Windows; `/etc/ssl/certs/…` on Linux, where `SSL_CERT_FILE` / `SSL_CERT_DIR` can point it elsewhere). It falls back to a built-in Mozilla root set only on a system with no CA bundle at all. For a private or corporate CA, install it into the system store, or on Linux set `SSL_CERT_FILE`.
|
|
112
133
|
|
|
113
134
|
### Run it as a service (systemd)
|
|
114
135
|
|
|
115
136
|
```ini
|
|
116
137
|
# /etc/systemd/system/halo-node.service
|
|
117
138
|
[Unit]
|
|
118
|
-
Description=halo
|
|
139
|
+
Description=halo-node
|
|
119
140
|
Wants=network-online.target
|
|
120
141
|
After=network-online.target
|
|
121
142
|
|
|
@@ -138,7 +159,7 @@ RestartPreventExitStatus=1
|
|
|
138
159
|
WantedBy=multi-user.target
|
|
139
160
|
```
|
|
140
161
|
|
|
141
|
-
Network drops, server restarts, a busy token (409) and a lockout (429) are handled inside the process, which keeps running, so `on-failure` is enough. Don't use `Restart=always`: it keeps restarting after a revoked token, and each restart is a failed login. After 5 failures in 15 minutes the server locks out that IP address, which blocks every machine connecting from it. ExecStart takes the path the install script printed.
|
|
162
|
+
Network drops, server restarts, a busy token (409) and a lockout (429) are handled inside the process, which keeps running, so `on-failure` is enough. Don't use `Restart=always`: it keeps restarting after a revoked token, and each restart is a failed login. After 5 failures in 15 minutes the server locks out that IP address, which blocks every machine connecting from it. ExecStart takes the path the install script printed. After upgrading with the install script, `systemctl restart halo-node` to run the new binary.
|
|
142
163
|
|
|
143
164
|
### Start at boot on Windows (Task Scheduler)
|
|
144
165
|
|
|
@@ -170,7 +191,7 @@ If your server is reached through **Tailscale**, enable Tailscale's **Run unatte
|
|
|
170
191
|
|
|
171
192
|
### Settings on the machine
|
|
172
193
|
|
|
173
|
-
`halo-node` takes its shell timeout only from `HALO_SHELL_TIMEOUT` (ms, default 120 s); grep's default of 50 matches and the 5 MiB command-output cap are fixed.
|
|
194
|
+
`halo-node` takes its shell timeout only from `HALO_SHELL_TIMEOUT` (ms, default 120 s); grep's default of 50 matches and the 5 MiB command-output cap are fixed. It doesn't read a halo server's `~/.halo/secrets/` settings on the same machine.
|
|
174
195
|
|
|
175
196
|
## 3. Let the agent use it
|
|
176
197
|
|
|
@@ -193,8 +214,8 @@ Then just ask: "list my machines", "read `notes/todo.md` on mbp", "run the tests
|
|
|
193
214
|
|---|---|
|
|
194
215
|
| `token rejected by the server (401)` | Wrong / deleted token — create a new one. 5 bad tries in 15 min lock the address out for a while (429). |
|
|
195
216
|
| `the workspace this token is bound to no longer exists (404)` | The workspace folder was removed on the server. |
|
|
196
|
-
| `protocol mismatch (426)` |
|
|
197
|
-
| `this token is already connected from another process` | Another `halo
|
|
198
|
-
| agent: `machine "mbp" is offline` | `halo
|
|
217
|
+
| `protocol mismatch (426)` | `halo-node` and the server speak different protocol versions — upgrade the older one: re-run `install.sh` (or download the server's release) here, or `halo upgrade` on the server. |
|
|
218
|
+
| `this token is already connected from another process` | Another `halo-node` with the same token is running; this one keeps retrying until that one stops. Also normal right after the machine rebooted or woke from sleep: the server needs 40–60 s to notice the old connection is dead, so the new process backs off and can take up to ~90 s to come online. |
|
|
219
|
+
| agent: `machine "mbp" is offline` | `halo-node` isn't running / connected. |
|
|
199
220
|
| agent: `went offline during the call` | The connection dropped mid-call; the operation may or may not have happened. Running commands on the machine were stopped. |
|
|
200
221
|
| agent: `not available in this runtime` | You're in the standalone CLI / TUI — linked machines only work through `halo server` (admin UI or a channel). |
|
|
@@ -11,7 +11,7 @@ Top-level built-in slash commands: `/help` `/evo` `/session` `/agent` `/skill` `
|
|
|
11
11
|
| Name | Slash | Type | Purpose |
|
|
12
12
|
|---|---|---|---|
|
|
13
13
|
| `help` | `/help` | server | List commands — object commands show only the verbs the user can run; a command is hidden entirely if no verb is runnable |
|
|
14
|
-
| `evo` | `/evo [text]` | server | Flat command (no verbs), **full-only**. Trigger self-evolution on the current root session: snapshot the conversation + queue an evo run. Optional text becomes a hint for what to focus on. Works at every `general.evolution.level` (L0 = manual only; L1 also drafts automatically on pre-compact; L2 / L3 may also auto-apply the result — prompt-only / capability changes that pass the gates — then post one `chat:system` line in this session). Hidden from `/help` for non-full users. See [design/evolution.md](../design/evolution.md). |
|
|
14
|
+
| `evo` | `/evo [text]` | server | Flat command (no verbs), **full-only**. Trigger self-evolution on the current root session: snapshot the conversation + queue an evo run. Optional text becomes a hint for what to focus on. Works at every `general.evolution.level` (L0 = manual only; L1 also drafts automatically on pre-compact; L2 / L3 may also auto-apply the result — prompt-only / capability changes that pass the gates — then post one `chat:system` line in this session). The level is the workspace's own: `evolution.level` can be set per workspace in `<ws>/.halo/settings.yaml`, falling back to the global one. Hidden from `/help` for non-full users. See [design/evolution.md](../design/evolution.md). |
|
|
15
15
|
|
|
16
16
|
The rest are noun-verb **object commands**: `/<obj> <verb> [args]`. Some verbs are built-in deterministic code (`SUBCOMMAND_ROUTES`); the others fall through to the same-name skill (LLM, dispatched via `$1`). Bare `/<obj>` or `/<obj> help` lists the verbs available to the user (filtered by access level).
|
|
17
17
|
|
|
@@ -102,13 +102,49 @@ An installed canvas extension declares its own config in its manifest: `"setting
|
|
|
102
102
|
|
|
103
103
|
### General — built-in
|
|
104
104
|
|
|
105
|
-
Declared in [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`. The server itself is the implicit declarer. Keys: `language`, `theme`, `agent.max_retries`, `server.*`, `session.*`, `compact.*`, `sandbox.*`, `logging.*`, `observability.*` (read once at boot — restart the server to apply; cron / evolution runs pick a change up on their next run), `evolution.*`, `limits.*`. All `general.*` keys are `globalOnly`: `config.ts` resolves
|
|
105
|
+
Declared in [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`. The server itself is the implicit declarer. Keys: `language`, `theme`, `agent.max_retries`, `server.*`, `session.*`, `compact.*`, `sandbox.*`, `logging.*`, `observability.*` (read once at boot — restart the server to apply; cron / evolution runs pick a change up on their next run), `outbound_proxy.*`, `evolution.*`, `limits.*`. All `general.*` keys are `globalOnly` except `evolution.level` and `evolution.auto_apply_threshold`: `config.ts` resolves the `globalOnly` ones through `settingsValue()` against `~/.halo/secrets/settings.yaml` only, so a workspace `settings.yaml` cannot override them. Per-workspace layering applies to namespaced `params` / `secrets` (`getServerSecret(ns, key, workspaceRoot)`, `substituteSecrets`).
|
|
106
106
|
|
|
107
107
|
`server.trust_proxy` (boolean, default `false`, `globalOnly`): whether the brute-force rate limiter (`middleware/brute-force.ts` `getClientIp`) trusts the `x-forwarded-for` header for client IP resolution. Direct-connect deployments leave it `false` and get the socket address. Behind a reverse proxy (nginx / Cloudflare / etc.), set it to `true` so the real client IP is honored instead of the proxy's — but only when that proxy is one you control and rewrites the header itself, otherwise a client can forge XFF to dodge lockouts.
|
|
108
108
|
|
|
109
109
|
`sandbox.hidden_dirs` / `sandbox.hidden_files` / `sandbox.writable_dirs` are `globalOnly` — they define the security boundary agents run inside, so a workspace `settings.yaml` cannot override them (a workspace overriding them could lift its own sandbox constraints). `hidden_dirs` / `hidden_files` hold **extra** entries: the effective list is the built-in default (`DEFAULT_HIDDEN_DIRS` / `DEFAULT_HIDDEN_FILES` in `tools/sandbox.ts`, the single source — `~/.halo/secrets`, `~/.aws`, `~/.ssh`, `~/.gnupg`, `~/.docker`, `~/.config/gh`, `~/.halo/global/internal-sessions`, `~/.halo/global/logs`; `~/.npmrc`, `~/.bash_history`, `~/.gitconfig`, `~/.git-credentials`, `~/.netrc`, the global `evo` / `cron` / `runs` databases + `-wal` / `-shm`) plus the user's entries, deduped (`config.ts` `resolveSandboxPaths()`, used by both the server and the CLI). A default entry cannot be removed from the list in settings. The lists apply only to workspace / readonly sessions (bwrap / Seatbelt, or `assertPathAllowed` without an OS sandbox); Full sessions, the admin file explorer (`validatePath`, workspace-bound only) and the terminal never consult them. The schema `default` is empty so the placeholder doesn't suggest copying the built-ins; the field description names them. `writable_dirs` is a plain list (no built-ins).
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
#### Outbound proxy
|
|
112
|
+
|
|
113
|
+
For a machine whose way out is a local proxy (e.g. Hiddify / Clash in system-proxy mode on `127.0.0.1:12334`). Command-line tools ignore the OS proxy setting, so without this they connect directly. Three `globalOnly` fields, all read live (the mtime-cached settings, one `stat` per spawn / request) — no restart:
|
|
114
|
+
|
|
115
|
+
| Key | Type | Default | Meaning |
|
|
116
|
+
|---|---|---|---|
|
|
117
|
+
| `outbound_proxy.enabled` | boolean | `false` | Master switch. On with an empty `url` = off, no error |
|
|
118
|
+
| `outbound_proxy.url` | string | `''` (placeholder `http://127.0.0.1:12334`) | Proxy address. Must parse with scheme `http:` or `https:` and carry **no user-info** — `PUT` / `PATCH` reject `user:pass@…` with 400 (`… put them in Token …`) so a credential never lands in a plain-text field. A hand-edited bad value is ignored at read time (proxy off, one `[OutboundProxy]` warning that never echoes the URL) |
|
|
119
|
+
| `outbound_proxy.token` | string, **secret** | `''` | Optional credential: `user:password`, or a bare token (= user name, empty password). Masked in `GET /api/settings/schema` like any secret |
|
|
120
|
+
|
|
121
|
+
**What goes through it** (when on):
|
|
122
|
+
- **Spawned children** — `shell_exec` at every access level, Terminals, notebook kernels, evolution runs. `cleanChildEnv()` (`child-env.ts`) adds `http_proxy`, `https_proxy`, `HTTP_PROXY`, `HTTPS_PROXY`, `no_proxy`, `NO_PROXY`. The bwrap (`--clearenv` + `--setenv`) and macOS Seatbelt (allowlisted env) paths add the same vars from the same function. Neither isolates the network, so a proxy on the host's loopback is reachable from inside.
|
|
123
|
+
- **web_fetch** and **A2A requests to remotes** (agent card + JSON-RPC, `a2a/outbound.ts`) — an undici `ProxyAgent` passed per request. It is never the global dispatcher: model provider calls share the global `fetch` and keep their own route. A2A's `general.a2a.url_allowlist` check still runs on the target before the request goes to the proxy. A2A push webhooks to callers and inbound image URL fetches stay direct.
|
|
124
|
+
|
|
125
|
+
**Not covered**: model provider calls, channel connections (Telegram / Slack / Feishu …), the hub / update checks.
|
|
126
|
+
|
|
127
|
+
**no_proxy**: `localhost,127.0.0.1,::1`, plus whatever `NO_PROXY` / `no_proxy` the server process itself inherited, deduped (an inherited `*` makes the list just `*`). A child gets that list. web_fetch / A2A match it the way curl 8.x does, so in-process requests and a child's curl route alike:
|
|
128
|
+
- a host name matches an entry equal to it or to a parent domain, case-insensitive, a leading dot ignored — `corp.example` and `.corp.example` both match `corp.example` and `a.corp.example`, not `notcorp.example`;
|
|
129
|
+
- an IP address (v4, or v6 as in `http://[fd00::1]/`) matches an entry that is that address or a CIDR containing it — `10.0.0.0/8`, `fd00::/8`; an IP entry never matches a host name;
|
|
130
|
+
- `*` alone matches everything;
|
|
131
|
+
- nothing else is a pattern: `*.corp.example` is compared literally (matches no real host), and an entry with a port (`host:8080`) never matches — ports play no part.
|
|
132
|
+
|
|
133
|
+
web_fetch applies this per redirect hop: a redirect into a no_proxy host connects directly, one out of it goes through the proxy.
|
|
134
|
+
|
|
135
|
+
**Off** = Halo sets no proxy variables at all. Proxy variables the server process inherited from its own environment are not removed — they are the operator's.
|
|
136
|
+
|
|
137
|
+
**Token transport** — Basic auth both ways, `base64(user:password)`:
|
|
138
|
+
- children: in the proxy URL's user-info, percent-encoded (`http://<user>:<pass>@host:port`) — the one form curl, git and pip all read;
|
|
139
|
+
- web_fetch / A2A: the `Proxy-Authorization` header (on the CONNECT for https and http alike). While the proxy is on, a web_fetch call carrying its own `Proxy-Authorization` header (any case) is refused with a tool error before any request — no_proxy targets included.
|
|
140
|
+
|
|
141
|
+
`<<ENV_NAME>>` in the token expands from the server's environment, as in any secret; an unset variable stays literal.
|
|
142
|
+
|
|
143
|
+
The credential never appears in logs (only the user-info-free origin is printed: `[OutboundProxy] on: http://host:port (with token)`). In `shell_exec` results (e.g. `env` output) its URL form `<user:pass>@` (percent-encoded or not) and its base64 are masked at any length; the bare token / encoded user-info on their own only from 4 characters (shorter strings would mangle ordinary output). In bwrap sessions only the command itself gets the proxy variables, not the file tools' helper processes.
|
|
144
|
+
|
|
145
|
+
**Terminals** get their env when they are opened: a Terminal already open keeps the old setting, a newly opened one picks up the change.
|
|
146
|
+
|
|
147
|
+
`evolution.*` controls the self-evolution subsystem (see [design/evolution.md](../design/evolution.md)). `evolution.level` and `evolution.auto_apply_threshold` can be set **per workspace**: `<workspace>/.halo/settings.yaml` overrides `~/.halo/secrets/settings.yaml`, which overrides the schema default (an open workspace can sit at L0 while a private one auto-applies), and the workspace Settings page edits them like any non-`globalOnly` field. The rest of `evolution.*` is `globalOnly` — it lives in `~/.halo/secrets/settings.yaml` only, not workspace settings. Notable knobs: `evolution.level` (`L0` = manual only: `/evo` drafts, a reviewer approves; `L1` = L0 plus automatic drafting on pre-compact; `L2` = L1 plus auto-applying prompt-only changes that pass the gates; `L3` = L2 plus auto-applying capability changes — auto-applied changes still pass the regression check and can be rolled back from the Evolution tab; only changes learned from a `full`-access session auto-apply; a cron run's own turn never drafts on pre-compact), `evolution.auto_apply_threshold` (int 0–100, default 80 — minimum average score at L2/L3, on top of the fixed floors confidence high / behavior ≥ 70 / lint 100 / all sandbox checks; a save outside 0–100 is rejected with 400), `evolution.max_concurrent_run` / `max_concurrent_apply` (wrapper concurrency caps), `evolution.run_timeout_minutes` / `apply_timeout_minutes` (heartbeat timeouts), `evolution.max_attempts` (per-row retry cap), `evolution.triggers.pre_compact` (snapshot session before compaction).
|
|
112
148
|
|
|
113
149
|
Goal mode's entry points are offline (the `/goal` command, admin banner and creation of new goal sessions are hidden). To reopen them, set `general.goal_mode_enabled` — a global-only boolean, default `false`, read from `~/.halo/secrets/settings.yaml` only (a workspace `settings.yaml` has no effect) and not shown in the Settings UI — then restart the Halo server and refresh the browser (existing goal bindings and history are not touched either way):
|
|
114
150
|
|
|
@@ -134,6 +170,7 @@ There is no "default provider" setting: a newly created agent's `model:` is a co
|
|
|
134
170
|
| `options` / `optionLabels` | no | For `type: enum`: the allowed values, and optional display labels parallel to them |
|
|
135
171
|
| `description` | no | English description rendered as help text |
|
|
136
172
|
| `description_zh` | no | Chinese description (UI picks based on lang) |
|
|
173
|
+
| `placeholder` | no | Example shown in an empty text input, display only — unlike `default` it is not a value anything falls back to (e.g. `outbound_proxy.url`). Built-in `general` section only |
|
|
137
174
|
| `default` | no | Placeholder shown when the value is unset; supports `<<ENV>>`. For provider / skill / agent fields it is display-only: an unset param's `{{<id>.params.<key>}}` stays literal at substitution, so the consumer applies its own fallback (the `extension` skill's `ext.sh` treats `{{…}}` as "use the default hub"; `web-search`'s `search.py` as "use the default region") |
|
|
138
175
|
| `secret` | no | `true` → masked in API responses + password input in UI |
|
|
139
176
|
| `restartRequired` | no | `true` → read once at server start; the Settings page shows a restart notice after saving it. Built-in `general` section only |
|