@turmind/halo 0.2.1 → 0.2.2
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 +109 -46
- package/admin-out/404.html +1 -1
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +1 -1
- package/bundled-docs/design/agentcore.md +97 -0
- package/bundled-docs/design/evolution.md +2 -2
- package/bundled-docs/design/halo-city.md +18 -5
- package/bundled-docs/design/storage.md +4 -4
- package/bundled-docs/design/web.md +10 -8
- package/bundled-docs/dev/api.md +3 -1
- package/bundled-docs/dev/deploy.md +10 -0
- package/bundled-docs/dev/env.md +3 -2
- package/bundled-docs/dev/tools.md +4 -3
- package/bundled-docs/guide/channels/web.md +2 -0
- package/bundled-docs/requirements/settings.md +10 -3
- package/dist/index.js +986 -341
- package/package.json +1 -1
- /package/admin-out/_next/static/{iSrc6429pyTB0lK-EE5oH → vHdygDsxwoyxkXg5umpZ1}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{iSrc6429pyTB0lK-EE5oH → vHdygDsxwoyxkXg5umpZ1}/_ssgManifest.js +0 -0
|
@@ -170,10 +170,8 @@ node server.js
|
|
|
170
170
|
|
|
171
171
|
### Security model
|
|
172
172
|
|
|
173
|
-
- `HALO_TOKEN` and `HALO_API` are server-side only, never exposed to the browser
|
|
174
|
-
-
|
|
175
|
-
- web-demo server proxies all requests to Halo, injecting the token in `x-token` header
|
|
176
|
-
- Session auth: random hex token stored in `x-session` header + localStorage
|
|
173
|
+
- **Proxy mode (default)**: `HALO_TOKEN` and `HALO_API` are server-side only, never exposed to the browser; frontend uses same-origin relative paths (`/chat`, `/history`, etc.); web-demo server proxies all requests to Halo, injecting the token in `x-token` header. Session auth: HMAC token in `x-session` header + localStorage. All proxy routes including `GET /file` are gated by the auth middleware.
|
|
174
|
+
- **Direct-connect mode (opt-in)**: the gear panel takes a halo server URL + web-channel token; the browser then calls `<server>/api/web/*` directly with `x-token`, bypassing the proxy (no password step). The token is stored in that browser's localStorage by explicit user choice — the UI says so next to the field. One slot; clearing it returns to proxy mode. Works because the server CORS reflects any origin and allowlists `x-token`.
|
|
177
175
|
|
|
178
176
|
### Proxy routes
|
|
179
177
|
|
|
@@ -183,17 +181,21 @@ node server.js
|
|
|
183
181
|
| `POST /stop` | `POST /api/web/stop` |
|
|
184
182
|
| `GET /history` | `GET /api/web/history` |
|
|
185
183
|
| `GET /subscribe` | `GET /api/web/subscribe` |
|
|
184
|
+
| `GET /file` | `GET /api/web/file` |
|
|
186
185
|
|
|
187
186
|
### Features
|
|
188
187
|
|
|
189
|
-
-
|
|
190
|
-
-
|
|
188
|
+
- Markdown rendering for assistant text (marked@12, gfm + breaks) with typewriter streaming; user text stays escaped plain text
|
|
189
|
+
- SSE streaming with collapsible thinking / tool-call step blocks
|
|
190
|
+
- Auto-reconnect: if session is running on page load, subscribes to live stream; history renders rebuild-from-scratch (never append)
|
|
191
191
|
- Message queue: can send while agent is running, messages queue and send sequentially
|
|
192
|
-
- Slash commands (`/help`, `/session`, `/agent`, `/skill`, `/workspace`)
|
|
193
|
-
- Image upload
|
|
192
|
+
- Slash commands (`/help`, `/session`, `/agent`, `/skill`, `/workspace`), session/agent switcher menus
|
|
193
|
+
- Image upload + camera capture (camera button is mobile-only — the `capture` attribute is a no-op on desktop, where it would duplicate the file picker)
|
|
194
194
|
- Voice recording (webm audio saved to workspace, path given to agent)
|
|
195
195
|
- Stop/interrupt button
|
|
196
196
|
- i18n: auto-detects browser language (`navigator.language`), toggle button in header, persists to `localStorage`
|
|
197
|
+
- Mobile-first layout: 100dvh-safe, safe-area insets, ≥44px touch targets, bottom-sheet menus/settings on small widths, visual language shared with `packages/agentcore-demo`
|
|
198
|
+
- Page load needs internet for two CDN deps (tailwindcss, marked) — no longer fully self-contained
|
|
197
199
|
|
|
198
200
|
## Integration guide (custom frontend)
|
|
199
201
|
|
package/bundled-docs/dev/api.md
CHANGED
|
@@ -222,7 +222,7 @@ File: `packages/server/src/routes/web.ts`
|
|
|
222
222
|
| POST | `/api/web/stop` | Stop running task → `{stopped: boolean}` |
|
|
223
223
|
| GET | `/api/web/history` | Active session history → `{sessionId, messages[], running}` |
|
|
224
224
|
| GET | `/api/web/subscribe` | Reconnect SSE to running session |
|
|
225
|
-
| GET | `/api/web/file?path=` | Inline-serve a workspace-relative file (image / video / pdf etc.). Path-traversal-checked against the token's bound workspace. |
|
|
225
|
+
| GET | `/api/web/file?path=` | Inline-serve a workspace-relative file (image / video / pdf etc.). Path-traversal-checked against the token's bound workspace: the lexical check is re-verified against the realpath'd root, so a symlink inside the workspace pointing outside it returns 403; a dangling symlink (target doesn't exist) returns 404. |
|
|
226
226
|
|
|
227
227
|
See [design/web.md](../design/web.md).
|
|
228
228
|
|
|
@@ -238,6 +238,8 @@ runtime so the frontend can render rooms (workspaces) + characters (sessions).
|
|
|
238
238
|
| GET | `/api/show/state` | `full` or `observer` token → every known workspace; otherwise the account's own. Returns `{ serverTime, uptime, accessLevel, skills[], workspaces[] }` |
|
|
239
239
|
| GET | `/api/show/session?ws=&id=` | Inspector-panel detail for a single session. Trimmed message log (last 40, content/tool I/O capped) plus `contextTokens` / `outputTokens` / `maxContextTokens` / `isRunning`. Non-`full`/`observer` tokens may only read their own workspace. |
|
|
240
240
|
|
|
241
|
+
`observer` is more than an aggregate-counts role: past `/show/state`, it can also call `/show/session` to read **any workspace's** session transcript (last 40 messages, content truncated to 600 chars, tool input to 200 chars). Mint it knowing it grants cross-workspace transcript read access, not just dashboard counters.
|
|
242
|
+
|
|
241
243
|
Each `workspace` = `{ path, key, label, counts{running,idle,stopped}, totalSessions, skills[], sessions[] }`; each `session` = `{ id, parentId, depth, agentName, description, status, lastTool, activeSkill, contextTokens, outputTokens, messageCount, updatedAt }`. `lastTool` / `activeSkill` come from the live in-memory UI log (empty when the session isn't loaded). Sessions per room are capped (`totalSessions` reports the true total). Frontend: `halo-city/` at repo root.
|
|
242
244
|
|
|
243
245
|
**Read-only by construction — degraded snapshots.** Both endpoints resolve a workspace's runtime via `registry.peek()` — in-memory lookup only, **never** `getOrCreate`: constructing a SessionManager has write side effects (boot orphan-reconciliation batch-stops live sub-session rows; `.halo/` scaffolding), so a pure visualization poll must not trigger it. When no live SessionManager exists in this process, the handler opens that workspace's `.halo/halo.db` **read-only** (cached connection; missing db/tables silently degrade to an empty room) and projects rows / the persisted session file into the same wire shape. Sessions not in memory therefore return a **persisted snapshot**: `status` / token counts / `messageCount` may lag behind the last persistence point, and the live-only signals (`lastTool` / `activeSkill`) are empty by definition. The degraded `/api/show/session` path reports `maxContextTokens: 0` (the real cap needs a live agent config; the city inspector hides the meter when the cap is unknown).
|
|
@@ -152,6 +152,14 @@ This installs the `halo` binary on `$PATH`. Subcommands available:
|
|
|
152
152
|
1. `halo upgrade` — bumps the on-disk npm package
|
|
153
153
|
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.
|
|
154
154
|
|
|
155
|
+
### Release checklist (before `npm publish`)
|
|
156
|
+
|
|
157
|
+
1. **Bump version** in root `package.json` (and any workspace `package.json` that mirrors it).
|
|
158
|
+
2. **Update `CHANGELOG.md`**: rename `[Unreleased]` → `[x.y.z] - YYYY-MM-DD`, add a fresh empty `[Unreleased]` section above it.
|
|
159
|
+
3. **Bump `TEMPLATE_VERSION`** in `packages/server/src/init.ts` if any file under `templates/` was touched.
|
|
160
|
+
4. **Build admin**: `pnpm --filter @turmind/halo-admin build` — verify `admin/out/monaco/vs/loader.js` exists.
|
|
161
|
+
5. Commit, tag `vx.y.z`, push, then publish.
|
|
162
|
+
|
|
155
163
|
The published package contains a single bundled JS entry (~620 KB), all built-in templates (agents / skills / prompts / models), bundled platform docs, and the admin Web UI static export. Total install footprint ≈ 120 MB after npm dedupes shared deps.
|
|
156
164
|
|
|
157
165
|
### Non-interactive (Docker / CI) details
|
|
@@ -196,3 +204,5 @@ server {
|
|
|
196
204
|
}
|
|
197
205
|
}
|
|
198
206
|
```
|
|
207
|
+
|
|
208
|
+
Behind a reverse proxy, set `general.server.trust_proxy: true` in `settings.yaml` (global scope only). Without it, brute-force rate-limiting / lockout resolves the client IP from the direct socket address — which behind a proxy is the proxy's own IP, so every client collapses into one bucket and can't be told apart. Only enable this when the proxy in front is one you control and it rewrites `x-forwarded-for` itself; otherwise a client can forge the header and bypass lockouts. Default is `false` (direct-connect deployments).
|
package/bundled-docs/dev/env.md
CHANGED
|
@@ -108,8 +108,9 @@ settings.yaml only (no env override):
|
|
|
108
108
|
- `general.compact.max_summary_input` (default 15000) — local compaction fallback input cap
|
|
109
109
|
- `general.compact.max_message_slice` (default 800) — local compaction per-message cap
|
|
110
110
|
- `general.compact.summarize_timeout_sec` (default 300) — self-compact timeout
|
|
111
|
-
- `general.
|
|
112
|
-
- `general.sandbox.
|
|
111
|
+
- `general.server.trust_proxy` (default `false`) — trust `x-forwarded-for` for client IP resolution, scope: global (enable only behind a reverse proxy you control)
|
|
112
|
+
- `general.sandbox.hidden_dirs` (default `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh`) — bwrap tmpfs overlays, scope: global
|
|
113
|
+
- `general.sandbox.hidden_files` (default `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc`) — bwrap /dev/null binds, scope: global
|
|
113
114
|
- `general.logging.level` (default `warn`) — log level: debug | info | warn | error
|
|
114
115
|
|
|
115
116
|
`<provider-id>.secrets.*` (server-only, hard-rejected by `{{}}` substitution; declared in `models/<provider-id>.yaml` `secrets:`):
|
|
@@ -137,10 +137,10 @@ Configured in `settings.yaml` under `general.sandbox`:
|
|
|
137
137
|
|
|
138
138
|
| Setting | Default | Method |
|
|
139
139
|
|---|---|---|
|
|
140
|
-
| `hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker` | `--tmpfs` overlay (empty directory) |
|
|
141
|
-
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig` | `--ro-bind /dev/null` (empty file) |
|
|
140
|
+
| `hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh` | `--tmpfs` overlay (empty directory) |
|
|
141
|
+
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc` | `--ro-bind /dev/null` (empty file) |
|
|
142
142
|
|
|
143
|
-
Changes take effect immediately — `config.ts` reads settings.yaml via an mtime-watched lazy cache, so the next `shell_exec` reads the latest values.
|
|
143
|
+
Changes take effect immediately — `config.ts` reads settings.yaml via an mtime-watched lazy cache, so the next `shell_exec` reads the latest values. These keys are `globalOnly` in the schema — a workspace `settings.yaml` cannot override them, since they define the security boundary agents run inside.
|
|
144
144
|
|
|
145
145
|
`/tmp` is not in the hidden list — it receives a standalone `--tmpfs` mount for process isolation (each bwrap invocation gets its own empty `/tmp`), not for hiding secrets.
|
|
146
146
|
|
|
@@ -172,6 +172,7 @@ Start a sub-agent session asynchronously. When the sub-agent finishes, its **wra
|
|
|
172
172
|
| `agent_id` | string | yes | Agent ID (e.g. `"coder"`) |
|
|
173
173
|
| `message` | string | yes | Task description |
|
|
174
174
|
| `system_prompt_context` | string | no | Extra context prepended to the initial message |
|
|
175
|
+
| `title` | string | no | Session title shown in the admin sidebar. When omitted, auto-generated from the task message (first 60 chars of `description`). |
|
|
175
176
|
| `working_dir` | string | no | Sub-agent's focus directory (absolute or workspace-relative; default: project root). It's persistent session identity (stored in the DB, restored on resume), so the directory-chain `.halo/INSTRUCTIONS.md` along the path root→dir is baked into the sub-agent's **system prompt every turn** (it never forgets the directory's rules), and the prompt is tagged with this focus. Does **not** change where tools run. |
|
|
176
177
|
|
|
177
178
|
**Output (JSON string)**
|
|
@@ -134,6 +134,8 @@ Environment variables:
|
|
|
134
134
|
|
|
135
135
|
The demo holds your token server-side and exposes its own password-based session — the browser never sees the halo token. Open `http://localhost:9528` to use it.
|
|
136
136
|
|
|
137
|
+
Alternatively, the gear icon (header / login screen) opens **direct-connect mode**: enter any halo server's URL + a web-channel token and the browser talks to that server's `/api/web/*` directly, skipping the proxy and its password. Handy for pointing one deployed web-demo at several halo servers. The token is kept in that browser's localStorage — only use it on devices you trust; clear it in the same panel to return to proxy mode.
|
|
138
|
+
|
|
137
139
|
## Security notes
|
|
138
140
|
|
|
139
141
|
- **Tokens are unhashed** in `~/.halo/secrets/channels/channels.db`. Restrict that file to the user that runs halo
|
|
@@ -7,6 +7,8 @@ Global + workspace configuration. The shape mirrors VSCode's `contributes.config
|
|
|
7
7
|
```yaml
|
|
8
8
|
# ~/.halo/secrets/settings.yaml — values only
|
|
9
9
|
general: # built-in declarer (server itself)
|
|
10
|
+
server:
|
|
11
|
+
trust_proxy: false # true only behind a reverse proxy you control — see below
|
|
10
12
|
session:
|
|
11
13
|
max_queue_size: 256
|
|
12
14
|
max_nesting_depth: 16
|
|
@@ -15,8 +17,8 @@ general: # built-in declarer (server itself)
|
|
|
15
17
|
max_summary_input: 15000
|
|
16
18
|
...
|
|
17
19
|
sandbox:
|
|
18
|
-
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker"
|
|
19
|
-
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig"
|
|
20
|
+
hidden_dirs: "~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh"
|
|
21
|
+
hidden_files: "~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc"
|
|
20
22
|
writable_dirs: "" # e.g. ~/.kiro,~/.local/share/kiro-cli — rw bind-mounts in bwrap; ignored for readonly sessions
|
|
21
23
|
logging:
|
|
22
24
|
level: warn
|
|
@@ -91,7 +93,11 @@ secrets: []
|
|
|
91
93
|
|
|
92
94
|
### General — built-in
|
|
93
95
|
|
|
94
|
-
Declared in [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`. The server itself is the implicit declarer. Keys: `language`, `agent.*`, `session.*`, `compact.*`, `sandbox.*`, `logging.*`, `evolution.*`, `limits.*`.
|
|
96
|
+
Declared in [packages/server/src/settings-schema.ts](../../../packages/server/src/settings-schema.ts) `generalSection()`. The server itself is the implicit declarer. Keys: `language`, `agent.*`, `server.*`, `session.*`, `compact.*`, `sandbox.*`, `logging.*`, `evolution.*`, `limits.*`.
|
|
97
|
+
|
|
98
|
+
`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.
|
|
99
|
+
|
|
100
|
+
`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).
|
|
95
101
|
|
|
96
102
|
`evolution.*` controls the self-evolution subsystem (see [plans/self-evolution.md](../plans/self-evolution.md)). All `evolution.*` keys are `globalOnly` — they live in `~/.halo/secrets/settings.yaml` only, not workspace settings. Notable knobs: `evolution.level` (`L0` = off, `L1` = human + LLM assist), `evolution.max_concurrent_run` / `max_concurrent_apply` (wrapper concurrency caps), `evolution.run_timeout_minutes` / `apply_timeout_minutes` (heartbeat timeouts), `evolution.max_attempts` (per-row retry cap), `evolution.triggers.pre_compact` (snapshot session before compaction).
|
|
97
103
|
|
|
@@ -106,6 +112,7 @@ Declared in [packages/server/src/settings-schema.ts](../../../packages/server/sr
|
|
|
106
112
|
| `description_zh` | no | Chinese description (UI picks based on lang) |
|
|
107
113
|
| `default` | no | Placeholder shown when the value is unset; supports `<<ENV>>` |
|
|
108
114
|
| `secret` | no | `true` → masked in API responses + password input in UI |
|
|
115
|
+
| `globalOnly` | no | `true` → read from global settings only; workspace overrides are ignored at runtime. UI disables the workspace input and shows a "global only" hint |
|
|
109
116
|
|
|
110
117
|
## Scope: global vs. workspace
|
|
111
118
|
|