@turmind/halo 1.5.3-alpha → 1.5.5

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.
Files changed (75) hide show
  1. package/admin-out/404.html +1 -1
  2. package/admin-out/_next/static/chunks/{133-33aa091fba853037.js → 422-cc74b1fdd3af5415.js} +1 -1
  3. package/admin-out/_next/static/chunks/989-bc743317498d3fbc.js +1 -0
  4. package/admin-out/_next/static/chunks/app/page-0a390059f1914664.js +1 -0
  5. package/admin-out/_next/static/chunks/{webpack-1a264efd965db7ab.js → webpack-edf248e07333566d.js} +1 -1
  6. package/admin-out/_next/static/css/b53542687ebd4c65.css +3 -0
  7. package/admin-out/index.html +1 -1
  8. package/admin-out/index.txt +5 -5
  9. package/bundled-docs/design/agent.md +4 -4
  10. package/bundled-docs/design/agentcore.md +13 -10
  11. package/bundled-docs/design/architecture.md +24 -14
  12. package/bundled-docs/design/command.md +5 -5
  13. package/bundled-docs/design/cron.md +2 -2
  14. package/bundled-docs/design/evolution.md +34 -35
  15. package/bundled-docs/design/express-self.md +6 -4
  16. package/bundled-docs/design/feishu.md +18 -14
  17. package/bundled-docs/design/halo-city.md +5 -4
  18. package/bundled-docs/design/observability.md +9 -9
  19. package/bundled-docs/design/prompt-system.md +12 -12
  20. package/bundled-docs/design/session.md +12 -12
  21. package/bundled-docs/design/slack.md +22 -20
  22. package/bundled-docs/design/storage.md +18 -19
  23. package/bundled-docs/design/telegram.md +26 -8
  24. package/bundled-docs/design/web.md +3 -1
  25. package/bundled-docs/design/wechat.md +8 -6
  26. package/bundled-docs/design/wecom.md +23 -9
  27. package/bundled-docs/design/ws.md +6 -6
  28. package/bundled-docs/dev/acp-adapter.md +8 -8
  29. package/bundled-docs/dev/add-channel.md +12 -9
  30. package/bundled-docs/dev/add-model-provider.md +31 -20
  31. package/bundled-docs/dev/api.md +83 -57
  32. package/bundled-docs/dev/deploy.md +8 -6
  33. package/bundled-docs/dev/env.md +16 -11
  34. package/bundled-docs/dev/logging.md +15 -29
  35. package/bundled-docs/dev/previews.md +20 -11
  36. package/bundled-docs/dev/tools.md +24 -12
  37. package/bundled-docs/guide/README.md +1 -0
  38. package/bundled-docs/guide/agents.md +19 -53
  39. package/bundled-docs/guide/channels/README.md +7 -4
  40. package/bundled-docs/guide/channels/acp.md +3 -1
  41. package/bundled-docs/guide/channels/feishu.md +13 -11
  42. package/bundled-docs/guide/channels/slack.md +12 -10
  43. package/bundled-docs/guide/channels/telegram.md +21 -27
  44. package/bundled-docs/guide/channels/web.md +15 -11
  45. package/bundled-docs/guide/channels/wechat.md +15 -14
  46. package/bundled-docs/guide/channels/wecom.md +7 -2
  47. package/bundled-docs/guide/chat.md +11 -10
  48. package/bundled-docs/guide/cli.md +10 -8
  49. package/bundled-docs/guide/getting-started.md +3 -3
  50. package/bundled-docs/guide/secrets-and-credentials.md +3 -3
  51. package/bundled-docs/guide/sessions.md +3 -3
  52. package/bundled-docs/guide/skills.md +8 -6
  53. package/bundled-docs/guide/testing-agents-and-skills.md +14 -15
  54. package/bundled-docs/guide/workspace.md +27 -15
  55. package/bundled-docs/requirements/agent.md +19 -18
  56. package/bundled-docs/requirements/chat.md +19 -21
  57. package/bundled-docs/requirements/command.md +10 -10
  58. package/bundled-docs/requirements/editor.md +10 -9
  59. package/bundled-docs/requirements/explorer.md +10 -7
  60. package/bundled-docs/requirements/session.md +16 -17
  61. package/bundled-docs/requirements/settings.md +18 -14
  62. package/bundled-docs/requirements/skill.md +4 -4
  63. package/bundled-docs/requirements/source-control.md +4 -3
  64. package/bundled-docs/requirements/terminal.md +13 -13
  65. package/bundled-docs/requirements/workspace.md +19 -13
  66. package/dist/index.js +2038 -3558
  67. package/package.json +1 -1
  68. package/templates/config.yaml +6 -0
  69. package/templates/skills/workspace/share.md +11 -3
  70. package/templates/skills/workspace/stage.py +85 -5
  71. package/admin-out/_next/static/chunks/989-c6c7772db8a50926.js +0 -1
  72. package/admin-out/_next/static/chunks/app/page-5f41b086cd5ad353.js +0 -1
  73. package/admin-out/_next/static/css/8809d33379458514.css +0 -3
  74. /package/admin-out/_next/static/{Wqi_ugna9MYsiV7ECuawQ → cuVcldfdg1PXevaQMQSzb}/_buildManifest.js +0 -0
  75. /package/admin-out/_next/static/{Wqi_ugna9MYsiV7ECuawQ → cuVcldfdg1PXevaQMQSzb}/_ssgManifest.js +0 -0
@@ -24,27 +24,25 @@ Sending `/setcommands` to BotFather lets Telegram clients show a menu next to th
24
24
  /setcommands → pick your bot → paste:
25
25
 
26
26
  start - 开始 / start
27
- new - 新会话 / new session
28
- list - 会话列表 / list sessions
29
- switch - 切换会话 / switch session
30
- stop - 中断任务 / stop running task
31
- compact - 压缩上下文 / compact context
32
- ws - 查看/切换 workspace
27
+ session - 会话管理 / manage sessions
28
+ agent - 智能体 / manage agents
29
+ skill - 技能 / manage skills
30
+ workspace - 查看/切换 workspace
33
31
  help - 帮助 / help
34
32
  ```
35
33
 
36
34
  ## Step 3 — Add the account in halo admin
37
35
 
38
- Open halo admin → **Channels** → **Telegram** → **Add Account**:
36
+ Open halo admin → **Channels** → **Telegram** → **Add Bot**:
39
37
 
40
38
  | Field | Value |
41
39
  |---|---|
42
- | Bot token | the `…:ABC…` from Step 1 |
43
- | Workspace path | absolute path, e.g. `/home/ubuntu/my-project` |
44
- | Label | optional |
40
+ | Bot Token (from @BotFather) | the `…:ABC…` from Step 1 |
41
+ | Bind to workspace | absolute path, e.g. `/home/ubuntu/my-project` |
42
+ | Name (optional) | a label for the account |
45
43
  | Access level | `readonly` (default), `workspace`, or `full` |
46
44
  | Language | `en` or `zh` |
47
- | Allowed users | optional whitelist; comma-separated user IDs and/or `@usernames`. Empty = anyone can talk to the bot |
45
+ | Allowed users (optional) | whitelist; comma-separated user IDs and/or `@usernames`. Empty = anyone can talk to the bot |
48
46
 
49
47
  On submit halo calls Telegram's `getMe` to validate the token and auto-fill `botUsername`. If that call fails the account isn't created.
50
48
 
@@ -52,7 +50,7 @@ On submit halo calls Telegram's `getMe` to validate the token and auto-fill `bot
52
50
 
53
51
  - **Empty** — anyone who finds the bot can chat with it. Fine for personal bots, dangerous if the bot is `full`-access
54
52
  - **Numeric IDs** (e.g. `123456789`) — recommended. A user's id is stable and you can find it by sending `/start` to `@userinfobot` in Telegram
55
- - **`@username`** — works for inbound filtering but **cron fan-out skips them** (Telegram's Bot API needs a numeric chat id, and `@username` lookup isn't always reliable)
53
+ - **`@username`** — works for inbound filtering (matched case-insensitively against the sender's current username, so it breaks if they rename)
56
54
 
57
55
  For team bots prefer numeric IDs.
58
56
 
@@ -60,13 +58,15 @@ For team bots prefer numeric IDs.
60
58
 
61
59
  In Telegram, search the bot's username (`@halo_dev_bot`) → press **Start** → say `hello` → expect a streamed reply.
62
60
 
63
- If nothing happens, check halo server logs for `[telegram]` lines.
61
+ If nothing happens, check halo server logs for `[Telegram]` lines.
64
62
 
65
63
  ## How halo handles inbound
66
64
 
67
65
  - **Private chats** — every message routes to the bot
68
- - **Group chats** — not supported in v1 (group messages are dropped)
69
- - **Photos / documents** — the file URL is included in the agent's input text; images aren't yet sent to vision automatically (by design — Telegram's photo URLs are short-lived and the upstream LLM can't always reach them)
66
+ - **Group chats** — supported. Each member talks to their **own** session (the same one they use in DM with the bot), and the bot replies in the group. `allowedUsers` is checked against the sender, so non-whitelisted members are refused there too. By default Telegram's *privacy mode* only delivers `/command@yourbot` and replies to the bot's messages; to have the bot see every group message, send `/setprivacy` → **Disable** to BotFather (then re-add the bot to the group) or make it a group admin
67
+ - **Photos** — downloaded, sent to the model as vision input and saved under `<workspace>/.halo/assets/telegram/inbound/<accountId>/<date>/`; the saved path is appended to the message text
68
+ - **Documents / voice / round video** — downloaded (20 MB cap) to the same folder; the agent gets the saved path in the message text, not the content
69
+ - **After a server restart** — if the agent was mid-task when halo restarted, its reply still reaches the most recent private chat the user talked in; a group conversation reconnects on the user's next message there
70
70
  - **Slash commands** — handled by halo, not by Telegram. The BotFather menu in Step 2 is just a UI hint
71
71
 
72
72
  ## Slash commands
@@ -74,31 +74,25 @@ If nothing happens, check halo server logs for `[telegram]` lines.
74
74
  | Command | Effect |
75
75
  |---|---|
76
76
  | `/start` | Welcome message |
77
- | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
77
+ | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` / `info` |
78
78
  | `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
79
79
  | `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
80
80
  | `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
81
+ | `/cron` `/acp` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
81
82
  | `/help` | List commands — object commands show only the verbs you can run |
82
83
 
83
84
  ## Cron jobs targeting Telegram
84
85
 
85
- When a cron job is created from inside a Telegram chat, the dispatcher targets that chat. From the admin UI you can also enter chat IDs directly (comma-separated for fan-out). The dispatcher resolves recipients in this order:
86
-
87
- 1. Explicit chat IDs on the cron target row (if provided in the form)
88
- 2. Fan-out to every numeric ID in the account's `allowedUsers` whitelist
89
- 3. The most-recent inbound chat ID, as a single-recipient fallback
90
-
91
- Want to fan out a daily report to your whole team? Add their numeric IDs to `allowedUsers` and create a cron without an explicit chat ID — halo delivers to each one and records per-recipient ✓/✗ in the cron run history.
86
+ When a cron job is created from inside a Telegram chat, the dispatcher targets that chat. From the admin UI you can also enter chat IDs directly (comma-separated for fan-out). A Telegram target **must** carry an explicit numeric chat ID (for a private chat it equals the user ID; a group's is negative) — there is no fallback to the whitelist or to the last inbound chat, and the dispatch fails without one. Per-recipient ✓/✗ is recorded in the cron run history. A report longer than 4000 characters is sent as several messages, in order.
92
87
 
93
88
  ## Common problems
94
89
 
95
90
  | Symptom | Cause / fix |
96
91
  |---|---|
97
- | "Unauthorized" / `getMe` fails on Add Account | Bot token has a typo, or the bot was deleted in BotFather |
92
+ | "Unauthorized" / `getMe` fails on Add Bot | Bot token has a typo, or the bot was deleted in BotFather |
98
93
  | Bot exists but doesn't reply | Check `allowedUsers` — if non-empty, your user must be in it |
99
- | Bot used to work, now silent | Maybe two halo processes are running and stealing each other's `getUpdates` long-poll. Check `~/.halo/global/server.pid` |
100
- | Group messages don't trigger the bot | Expected — groups aren't supported in v1 |
101
- | Cron fan-out skips a whitelisted user | The user is listed as `@username`. Add their numeric ID instead |
94
+ | Bot used to work, now silent | Maybe two halo processes are running and stealing each other's `getUpdates` long-poll. Check `~/.halo/global/server.lock` |
95
+ | Group messages don't trigger the bot | Privacy mode is on (the default): only `/command@yourbot` and replies to the bot get through. Disable it with BotFather `/setprivacy` and re-add the bot, or make it a group admin |
102
96
 
103
97
  ## Multi-bot setup
104
98
 
@@ -13,13 +13,13 @@ The token is **the** credential — anyone holding it can talk to the agent at t
13
13
 
14
14
  ## Step 1 — Create an account
15
15
 
16
- Open halo admin → **Channels** → **Web** → **Add Account**:
16
+ Open halo admin → **Channels** → **Web** → **Create Access**:
17
17
 
18
18
  | Field | Value |
19
19
  |---|---|
20
- | Workspace path | absolute path, e.g. `/home/ubuntu/my-project` |
21
- | Label | optional |
22
- | Access level | `readonly` (default), `workspace`, or `full` |
20
+ | Bind to workspace | absolute path, e.g. `/home/ubuntu/my-project` |
21
+ | Name (optional) | a label for the account |
22
+ | Access level | `readonly` (default), `workspace`, `full`, or `observer` (global read-only, for dashboards / metrics) |
23
23
  | Language | `en` or `zh` |
24
24
 
25
25
  Click **Create**. The success screen shows the auto-generated token **once** — copy it now, you can't retrieve it again. (You can always delete the account and create a new one if you lose the token.)
@@ -55,18 +55,20 @@ Parse the `type` field on each event to render text vs tool calls vs completion.
55
55
  | Method | Path | Purpose |
56
56
  |---|---|---|
57
57
  | `POST /api/web/chat` | Send a message; SSE response |
58
+ | `POST /api/web/sessions` | Mint a new root session in the token's namespace; returns `{sessionId}` |
58
59
  | `POST /api/web/stop` | Cancel the running task |
59
60
  | `GET /api/web/history` | Fetch session message history |
60
61
  | `GET /api/web/subscribe` | Reconnect to a running session's SSE stream |
62
+ | `GET /api/web/file?path=…` | Fetch a file from the bound workspace (path relative to it; `.halo` runtime state is refused) |
61
63
 
62
- All four accept the same auth header.
64
+ All six accept the same auth header.
63
65
 
64
66
  ### Per-request overrides
65
67
 
66
68
  By default each token is locked to the workspace and active session set by the admin. External integrations (notably the [ACP adapter](acp.md)) need finer control:
67
69
 
68
70
  - `workspace` — server-side absolute path, **gated on `accessLevel === 'full'`** (readonly / workspace tokens can't escape their bound workspace; the override is rejected)
69
- - `sessionId` — explicit halo session id; lets clients pre-mint stable ids and address them across reconnects
71
+ - `sessionId` — explicit halo session id; lets clients address a session across reconnects. **Gated on ownership**: `full` tokens may name any id; `readonly` / `workspace` tokens only ids under their own account (`web_<accountId>_*`, e.g. minted by `POST /api/web/sessions`) — anything else is `403`
70
72
  - `agentId` — only used when the request creates a new session
71
73
 
72
74
  These can be passed as POST body fields, headers (`x-workspace`, `x-session-id`, `x-agent-id`), or query params (`?workspace=…&sessionId=…&agentId=…`). POST body wins on conflict.
@@ -86,7 +88,7 @@ For browser apps you almost never want these — leave them off and use the per-
86
88
  }
87
89
  ```
88
90
 
89
- Images go to the LLM as multimodal content. Audio and other binary uploads aren't supported on this endpoint directly — for those, save the file to `<workspace>/.halo/assets/web/inbound/<accountId>/<date>/` first (out-of-band) and reference its path in your message text.
91
+ Images go to the LLM as multimodal content. Any other `mimeType` (audio, PDF, …) in the same array is not sent to the model: the server saves it to `<workspace>/.halo/assets/web/inbound/<accountId>/<date>/` and appends a `[语音已保存: <path>]` line to your message so the agent can open it with its file tools.
90
92
 
91
93
  The standalone web-demo handles voice + arbitrary files for you; if you want that, use it as a reference implementation.
92
94
 
@@ -96,10 +98,11 @@ Slash commands are intercepted before they reach the agent — same set as every
96
98
 
97
99
  | Command | Effect |
98
100
  |---|---|
99
- | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
101
+ | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` / `info` |
100
102
  | `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
101
103
  | `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
102
104
  | `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
105
+ | `/cron` `/acp` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
103
106
  | `/help` | List commands — object commands show only the verbs you can run |
104
107
 
105
108
  Send a slash command exactly like a normal message — the server detects the leading `/`.
@@ -109,7 +112,8 @@ Send a slash command exactly like a normal message — the server detects the le
109
112
  | Symptom | Cause / fix |
110
113
  |---|---|
111
114
  | `401` on every call | Missing `x-token` header, or token is for a deleted account |
112
- | `403` when passing `workspace=…` | Token is `readonly` / `workspace` access — only `full` can override |
115
+ | `429` | 5 bad tokens from one IP within 15 minutes locks that IP out for 15 minutes (in-memory, cleared on restart) |
116
+ | `403` when passing `workspace=…` or a foreign `sessionId` | Token is `readonly` / `workspace` access — only `full` can override the workspace or address sessions it didn't mint |
113
117
  | SSE stream hangs forever | Reverse proxy buffering. Disable buffering for `text/event-stream` (nginx: `proxy_buffering off`, Cloudflare: enable streaming) |
114
118
  | Token leaked accidentally | Delete the account in admin, create a new one. The old token is invalidated immediately |
115
119
  | Want to share one token across multiple users | Don't — every request would land on the same active session. Create one account per user / app |
@@ -139,9 +143,9 @@ Alternatively, the gear icon (header / login screen) opens **direct-connect mode
139
143
  ## Security notes
140
144
 
141
145
  - **Tokens are unhashed** in `~/.halo/secrets/channels/channels.db`. Restrict that file to the user that runs halo
142
- - **No rate limit at the channel level** — if you expose the API on the public internet, put a reverse proxy with rate limits in front
146
+ - **Bad-token lockout only** — 5 invalid tokens from one IP in 15 minutes locks that IP out (`429`) for 15 minutes; there is no per-token request rate limit, so if you expose the API on the public internet put a reverse proxy with rate limits in front
143
147
  - **Admin endpoints** (`POST /api/web/accounts`, `PATCH`, `DELETE`) require admin cookie auth, **not** the token. They're for the admin panel, not for the token holder
144
- - A `full`-access token can call `/workspace <abs-path>` to switch the bound workspace database-side. If you don't want that, give out `workspace` or `readonly` tokens instead
148
+ - A `full`-access token can call `/workspace switch <abs-path>` to switch the bound workspace database-side. If you don't want that, give out `workspace` or `readonly` tokens instead
145
149
 
146
150
  ## Reference
147
151
 
@@ -21,18 +21,18 @@ Halo auto-creates `.halo/` inside the folder if it doesn't exist; the path itsel
21
21
 
22
22
  ## Step 2 — Open the admin scan flow
23
23
 
24
- Open halo admin → **Channels** → **WeChat** → **Add Account**.
24
+ Open halo admin → **Channels** → **WeChat** → **Add Bot**.
25
25
 
26
26
  The form is **just the workspace + label + access level + language** — no bot token field, because the token comes from the scan:
27
27
 
28
28
  | Field | Value |
29
29
  |---|---|
30
- | Workspace path | absolute path from Step 1, e.g. `/home/ubuntu/my-project` |
31
- | Label | optional, shown in admin's account list |
30
+ | Bind to workspace | absolute path from Step 1, e.g. `/home/ubuntu/my-project` |
31
+ | Name (optional) | shown in admin's account list |
32
32
  | Access level | `readonly` (default), `workspace`, or `full` |
33
33
  | Language | `en` or `zh` |
34
34
 
35
- Click **Generate QR**. Halo hits `ilinkai.weixin.qq.com` to mint a fresh QR code and starts a poll loop waiting for the scan.
35
+ Click **Next: Scan**. Halo hits `ilinkai.weixin.qq.com` to mint a fresh QR code and starts a poll loop waiting for the scan.
36
36
 
37
37
  > **What the access level covers.** A WeChat account is bound to exactly one WeChat user — the one who scans — so there's no whitelist to configure; the scanner *is* the whitelist. The level you choose here is what that phone gets: `full` means whoever holds the unlocked phone has shell access as the server user. The same applies to invite QRs minted with `/qr full` from inside the chat — each scan creates a separate account at the level you named, so hand those out as you would an SSH key.
38
38
 
@@ -43,19 +43,19 @@ Click **Generate QR**. Halo hits `ilinkai.weixin.qq.com` to mint a fresh QR code
43
43
  3. Aim at the QR shown in the admin UI
44
44
  4. Tap **Confirm Login / 确认登录** on the phone
45
45
 
46
- Within a few seconds the admin UI shows "登录成功" / "Logged in" and switches to the success page. Behind the scenes:
46
+ Within a few seconds the admin UI shows "Connected (<id>)" and closes the dialog. Behind the scenes:
47
47
 
48
48
  - iLink returns the bot's token, the IDC-specific `baseUrl`, and your WeChat-side `ilink_user_id`
49
49
  - Halo writes those into the new account row
50
50
  - The long-poll loop starts immediately — the bot is now live
51
51
 
52
- The QR is valid for ~3 minutes; if it expires the admin UI auto-refreshes. If you cancel and come back later, just open the form again — a new QR is minted.
52
+ The server waits up to ~8 minutes for the scan and gives up after the QR has expired three times; the dialog then shows the error and a **Start over** button. If you cancel and come back later, just open the form again — a new QR is minted.
53
53
 
54
54
  ## Step 4 — Test it
55
55
 
56
56
  The bot's name in your WeChat contacts is whatever the iLink platform set it to (typically a generic placeholder like `小助手`; you can rename it locally). Open the bot's chat in WeChat and send `hello` → expect a streamed reply.
57
57
 
58
- If nothing happens, check halo server logs for `[wechat]` lines.
58
+ If nothing happens, check halo server logs for `[WeChat]` lines.
59
59
 
60
60
  ## How halo handles inbound
61
61
 
@@ -63,7 +63,8 @@ If nothing happens, check halo server logs for `[wechat]` lines.
63
63
  - **Images** — passed to the LLM as multimodal content
64
64
  - **Voice** — decrypted, saved under `<workspace>/.halo/assets/weixin/inbound/<accountId>/<date>/` (the `weixin` directory name is kept for backward compatibility with existing workspaces), path included in the agent's input
65
65
  - **Video / files** — same as voice
66
- - **Group chats** — not supported in v1 (group messages are dropped)
66
+ - **Group chats** — not supported: the WeChat (iLink) bot is a one-to-one contact and the platform doesn't deliver group messages to it, so there is nothing for halo to handle
67
+ - **After a server restart** — if the agent was mid-task when halo restarted, its reply still reaches each user who has chatted with the bot (as long as the stored `context_token` is still accepted; otherwise it fails with `ret=-2` until they write again)
67
68
  - **Self-loop** — bot-authored messages are filtered out
68
69
 
69
70
  ## Slash commands
@@ -72,12 +73,13 @@ Same set as the other channels — type as plain text in your WeChat DM with the
72
73
 
73
74
  | Command | Effect |
74
75
  |---|---|
75
- | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
76
+ | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` / `info` |
76
77
  | `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
77
78
  | `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
78
79
  | `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
80
+ | `/cron` `/acp` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
79
81
  | `/help` | List commands — object commands show only the verbs you can run |
80
- | `/qr [level]` | Generate an invite QR (admin only) |
82
+ | `/qr [level]` | Generate an invite QR (`full`-access bot only; `level` = `readonly` (default) / `workspace` / `full`) |
81
83
 
82
84
  ## Cron jobs targeting WeChat
83
85
 
@@ -94,11 +96,10 @@ Fan-out across multiple WeChat users is not supported — one bot, one owner.
94
96
  | Symptom | Cause / fix |
95
97
  |---|---|
96
98
  | QR shows but scan does nothing | iLink platform thinks the QR is for a different account. Cancel and regenerate |
97
- | "二维码已过期" / QR expired | Admin UI should auto-refresh. If not, click Generate again |
99
+ | "登录超时:二维码多次过期" / QR expired | Click **Start over** in the dialog and scan the new QR |
98
100
  | Bot stops responding after a few hours | Long-poll likely lost its `syncBuf`. Restart the server; reconnect is automatic |
99
- | Two halo processes both poll the same bot | Each process gets a copy of every message, replied twice. Make sure only one server runs — see `~/.halo/global/server.pid` |
100
- | Group chat doesn't trigger the bot | Expected — groups aren't supported in v1 |
101
- | Sent voice message returns no reply | Check that the account has `workspace` or `full` access; readonly cannot save inbound media |
101
+ | Two halo processes both poll the same bot | Each process gets a copy of every message, replied twice. Make sure only one server runs — see `~/.halo/global/server.lock` |
102
+ | Group chat doesn't trigger the bot | Expected — WeChat (iLink) doesn't support group chats for bots; talk to it one-to-one |
102
103
  | Cron push or a delayed reply fails with `ret=-2 prepare failed` while fresh chat replies work | Three causes: the report exceeds 16 KB (halo chunks at 3500 chars, so this is rare now); the account has no stored `context_token` for the recipient yet — the gateway wants outbound to echo the user's latest inbound token, which halo persists on every inbound message since 1.1.8; or that token has **expired** — the gateway stops accepting it some time after the user's last message (the window isn't fixed; failures were seen 12 min and 79 min after the last inbound), and only a new inbound message from that user refreshes it. In all cases: send the bot any message once and the next push carries a fresh token. The error names which case it is (`context_token missing` / `context_token age 79m`), and a dropped chat reply now shows up as a `⚠️ WeChat delivery failed` notification in the session log (admin Sessions tab) instead of vanishing silently |
103
104
 
104
105
  ## Multi-workspace / multi-bot
@@ -40,7 +40,7 @@ Open halo admin → **Channels** → **WeCom** → **Add Bot**:
40
40
  | Bot ID | from Step 3 |
41
41
  | Secret | from Step 3 |
42
42
  | Bind to workspace | absolute path, e.g. `/home/ubuntu/my-project` |
43
- | Name | optional label |
43
+ | Name (optional) | a label for the account |
44
44
  | Access level | `readonly` (default), `workspace`, or `full` |
45
45
  | Language | `en` or `zh` — for hints and command replies |
46
46
 
@@ -91,16 +91,21 @@ When the agent emits a `MEDIA:<path>` line (a file under the workspace), halo up
91
91
 
92
92
  Replies render **Markdown natively** — headings, bold, lists, quotes, links, code blocks and tables all display as formatted text.
93
93
 
94
+ ### After a server restart
95
+
96
+ If the agent was mid-task when halo restarted, its reply still reaches the **most recent** chat the bot talked in (one per bot). Because the original message can no longer be answered directly, that reply arrives as a regular pushed message rather than as a reply to your message; once you write again, replies go back to normal.
97
+
94
98
  ## Slash commands
95
99
 
96
100
  Same set as the other channels — type as plain text in a **single chat** (commands are ignored in groups, where the shared session belongs to everyone):
97
101
 
98
102
  | Command | Effect |
99
103
  |---|---|
100
- | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
104
+ | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` / `info` |
101
105
  | `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
102
106
  | `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
103
107
  | `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
108
+ | `/cron` `/acp` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
104
109
  | `/help` | List commands — object commands show only the verbs you can run |
105
110
 
106
111
  ## Cron jobs targeting WeCom
@@ -28,16 +28,16 @@ A change applies from the next message you send, and the level is saved per sess
28
28
 
29
29
  ## Context injection
30
30
 
31
- Next to the chat is a `📎 Context` toggle (on by default). When on, Halo auto-injects:
31
+ The input toolbar shows a context chip (the open file's name, or the selection) — click it to toggle; on by default. When on, Halo auto-injects:
32
32
  - The currently open file path: `[Currently viewing: src/foo.ts]`
33
33
  - The editor's current selection: `[Selected text in foo.ts:10-25]\n\`\`\`...\n\`\`\``
34
34
 
35
- Turn it off if you don't want that context injected.
35
+ Click the chip (it turns struck-through) if you don't want that context injected.
36
36
 
37
37
  ## `@` file mention
38
38
 
39
39
  Typing `@` in the input opens a file search:
40
- - Real-time fuzzy matching (150 ms debounce)
40
+ - Real-time fuzzy matching (120 ms debounce)
41
41
  - Selecting inserts a path chip
42
42
  - You can `@` multiple files back-to-back
43
43
 
@@ -49,7 +49,7 @@ The search scans the whole project on the server — independent of Explorer exp
49
49
  - **Paste**: paste an image from the clipboard
50
50
  - **Button**: click the 📎 on the left
51
51
 
52
- Images are sent to the agent as base64, with multimodal support (Claude 4.6 can see images).
52
+ Chat attachments are images only — other dropped or pasted files are ignored. Images are sent to the agent as base64, with multimodal support (Claude 4.6 can see images).
53
53
 
54
54
  ## Slash commands
55
55
 
@@ -59,13 +59,14 @@ Most commands are noun-verb **object commands**: `/<obj> <verb> [args]`. Bare `/
59
59
 
60
60
  | Command | Purpose |
61
61
  |---|---|
62
- | `/session <verb>` | Manage sessions — `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context`. All built-in, available to everyone. `new` starts a fresh conversation (old session stays in the sidebar; running sub-agents keep going); `compact` keeps the most recent N messages intact (N defaults to 5, `general.compact.keep_messages`); `context` shows token usage, agent info, and available tools |
62
+ | `/session <verb>` | Manage sessions — `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` / `info` (session tree). All built-in, available to everyone. `new` starts a fresh conversation (old session stays in the sidebar; running sub-agents keep going); `compact` keeps the most recent N messages intact (N defaults to 5, `general.compact.keep_messages`); `context` shows token usage, agent info, and available tools |
63
63
  | `/clear` | Admin-UI alias for `/session new` |
64
64
  | `/agent <verb>` | Manage agents — `list` / `switch <name\|index>` / `desc` (built-in, open to all) · `delete` (built-in, full access) · `create` / `update` (handled by the `agent` skill, full access) |
65
65
  | `/skill <verb>` | Manage skills — `list` / `desc` (built-in, open to all) · `disable` / `enable` (built-in, workspace access) · `delete` (built-in, full access) · `create` / `update` (handled by the `skill` skill, full access) |
66
66
  | `/workspace <verb>` | Manage the workspace — `info` (built-in, open to all) · `switch <path>` (built-in, full access) · `setup` / `tidy` (workspace skill, workspace access; init / reorganize `.halo/` INDEX.md / INSTRUCTIONS.md / memory/) · `share` (workspace skill, full access; export a shareable bundle) |
67
67
  | `/cron <verb>` | Scheduled agent runs — `create` / `list` / `update` / `enable` / `disable` / `delete` (cron skill, full access) |
68
68
  | `/acp <verb>` | Talk to other agents over ACP — `kiro <q>` / `claude <q>` ask a local agent directly; `add` / `list` / `remove` manage generated `ask-<label>` bindings (acp skill, full access) |
69
+ | `/extension <verb>` | Canvas preview extensions — `install <id\|zip\|url>` / `list` / `remove <id>` (extension skill, full access) |
69
70
  | `/evo [hint]` | Queue a self-evolution run on this session (full access only) |
70
71
  | `/help` | List every command — object commands only show the verbs you can run |
71
72
 
@@ -103,8 +104,8 @@ Three ways to cut in while the agent is running:
103
104
  The ring in the bottom-right of the input shows context window usage:
104
105
  - Green: < 50%
105
106
  - Yellow: 50–70%
106
- - Orange: 70–90%
107
- - Red: > 90%
107
+ - Orange: 70–85%
108
+ - Red: > 85%
108
109
 
109
110
  Reaching `compressAt` (default 90%, setting `general.compact.compress_at`) auto-triggers compact. If the LLM summary fails or times out, Halo falls back to a local compact (notice: `Auto-compacted N older messages (local fallback — LLM summary failed: <reason>)`) rather than retrying. An auto-compact has no overall time limit — it only times out after 10 minutes with no data from the model. While the agent is running you can't click the TokenRing (guarded by `isStreaming`).
110
111
 
@@ -112,9 +113,9 @@ Reaching `compressAt` (default 90%, setting `general.compact.compress_at`) auto-
112
113
 
113
114
  Your sessions are listed as vertical tabs on the right edge of the chat panel:
114
115
  - Lists the workspace's main sessions, newest first; scroll down to load older ones
115
- - Click a tab to show that session. The one you left keeps running and streaming in its own tab: a spinner shows while it works, and a dot means new output arrived
116
+ - Click a tab to show that session. The one you left keeps running and streaming in its own tab. Each tab's dot shows its state: pulsing amber while it works, blue when new output arrived in the background, green otherwise (including sessions not opened since the page loaded)
116
117
  - **+** (or `/session new`) opens a fresh tab; the session is created when you send its first message
117
- - Hover a tab to rename it (pencil); ✕ deletes it after a confirmation — deletion can't be undone
118
+ - Hover a tab to rename it (pencil) or delete it (trash icon, after a confirmation) — deletion can't be undone
118
119
  - Drag the list's left edge to make it wider or narrower, or collapse it to a thin strip of initials
119
120
  - After a refresh, the session you were on reopens
120
121
 
@@ -126,6 +127,6 @@ Long messages you sent are collapsed to a one-line header: a chevron, the send t
126
127
 
127
128
  **"Queued" hint**: the previous turn hasn't finished; your message is queued and will run next turn.
128
129
 
129
- **"Rate limited, retrying in Xs..."**: Bedrock throttled; automatic exponential backoff (up to 5 retries).
130
+ **"Rate limited, retrying in Xs..."**: the model provider throttled; automatic backoff, up to `general.agent.max_retries` attempts (default 5).
130
131
 
131
132
  **A sudden `[Message from xxx]`**: a sub-agent finished and auto-reported back to the parent agent's conversation.
@@ -29,7 +29,7 @@ bundle flattens them.
29
29
  # 2. build the upstream artifacts the bundle consumes
30
30
  pnpm --filter @turmind/halo-core build
31
31
  pnpm --filter @turmind/halo-server build
32
- cd packages/admin && npx next build --no-lint && node scripts/copy-monaco.mjs && cd ../..
32
+ pnpm --filter @turmind/halo-admin build
33
33
  # 3. stage the release bundle — HALO_RELEASE=1 stamps the bare version
34
34
  # (0.1.2), NOT the default <version>-<sha>: a `-<sha>` suffix is a semver
35
35
  # prerelease that `npm install` skips and never tags as `latest`.
@@ -136,6 +136,7 @@ Multi-turn conversation. Supports all standard Halo slash commands:
136
136
  | `/session interrupt` | Interrupt the running turn now (aborts a command mid-run); any messages queued while busy then run as one follow-up turn |
137
137
  | `/session compact` | Compact session context |
138
138
  | `/session context` | Show context window + agent info |
139
+ | `/session info` | Show the session tree (descendants) |
139
140
  | `/agent <verb>` | Manage agents: `list` / `switch <name\|index>` / `desc` / `delete` / `create` / `update` |
140
141
  | `/skill <verb>` | Manage skills: `list` / `desc` / `disable` / `enable` / `delete` / `create` / `update` |
141
142
  | `/workspace info` | Show current workspace |
@@ -143,6 +144,7 @@ Multi-turn conversation. Supports all standard Halo slash commands:
143
144
  | `/workspace setup` / `/workspace tidy` / `/workspace share` | Set up / tidy the `.halo/` knowledge files, or export a shareable bundle (workspace skill) |
144
145
  | `/cron <verb>` | Manage scheduled agent runs: `create` / `list` / `update` / `enable` / `disable` / `delete` |
145
146
  | `/acp <verb>` | Ask other agents over ACP (`kiro <q>` / `claude <q>`) and manage `ask-*` bindings (`add` / `list` / `remove`) |
147
+ | `/extension <verb>` | Canvas preview extensions: `install` / `list` / `remove` (full access only) |
146
148
  | `/evo [hint]` | Queue a self-evolution run on this session (full access only) |
147
149
  | `/quit` | Exit |
148
150
 
@@ -216,7 +218,7 @@ same binary. `start -d` runs it in the background **with a bounded respawn
216
218
  supervisor**: the detached process supervises the server and brings it back on
217
219
  a crash (non-zero exit, or a kill by anything other than SIGTERM/SIGINT), at
218
220
  most 5 times per 5 minutes with a 2s pause, logging each decision as
219
- `[respawn] …` into `~/.halo/global/logs/server.log`. `halo server stop` is
221
+ `[respawn] …` into `~/.halo/logs/server.log`. `halo server stop` is
220
222
  recognized as intentional and never respawns; `halo server stop|restart|status`
221
223
  still target the server's own pid. Full policy in
222
224
  [dev/deploy.md](../dev/deploy.md#crash-semantics--why-restarton-failure-is-load-bearing).
@@ -226,8 +228,9 @@ still target the server's own pid. Full policy in
226
228
  ```
227
229
  packages/cli/
228
230
  src/
229
- index.ts — Entry: subcommand dispatch (tui/cli/agents/sessions/server/setup/acp), SIGINT/SIGTERM handling
231
+ index.ts — Entry: subcommand dispatch (tui/cli/agents/sessions/server/setup/upgrade/acp), SIGINT/SIGTERM handling
230
232
  harness.ts — Shared agent harness wrapping SessionManager
233
+ server-supervisor.ts — Respawn policy for `halo server start -d`
231
234
  cli.ts — Non-interactive: stdin/args → agent → stdout → exit
232
235
  tui.tsx — Interactive entry: mounts the ink app
233
236
  tui/
@@ -247,9 +250,8 @@ Session prefix: `cli_`. Sessions are persisted to `<workspace>/.halo/sessions/`
247
250
 
248
251
  ### Initialization
249
252
 
250
- The CLI replicates the server's init sequence:
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`):
251
254
  1. `initLogger()` — redirect console to stderr + file logger
252
- 2. `ensureHaloHome()` — ensure `~/.halo/` structure
253
- 3. `initBwrapCheck()` — probe sandbox availability
254
- 4. `setSandboxHiddenPaths()` — configure sandbox paths
255
- 5. `new SessionManager(workspace)` — create agent session manager
255
+ 2. `initBwrapCheck()` — probe sandbox availability
256
+ 3. `setSandboxHiddenPaths()` — configure sandbox paths
257
+ 4. `new SessionManager(workspace)` — create agent session manager (the workspace's `.halo/` is seeded via `ensureWorkspaceHalo`)
@@ -12,7 +12,7 @@ Open `http://localhost:9527` (or your deployment URL) in a browser and enter the
12
12
 
13
13
  ## Open a workspace
14
14
 
15
- After login, you land in the workspace. A URL with `?folder=/abs/path` opens that directory directly; without `?folder`, Halo falls back to your home directory as the workspace — everything still works.
15
+ After login, you land in the workspace. A URL with `?folder=/abs/path` opens that directory directly; without `?folder`, Halo reopens the last workspace you used, or your home directory if there isn't one — everything still works.
16
16
 
17
17
  The Explorer top bar has two ways to switch workspace:
18
18
  - **Type an absolute path** + Enter — invalid paths raise an error
@@ -22,7 +22,7 @@ Halo auto-creates a `.halo/` subdirectory in the target folder (for sessions, ag
22
22
 
23
23
  ## First conversation (bootstrap)
24
24
 
25
- The first time a new user chats with the Default agent, the agent asks a few questions:
25
+ The first time a new user chats with the Default agent, it works in a few getting-to-know-you questions (if you open with a real task, it does the task first and asks over the next turns):
26
26
  - What should I call you?
27
27
  - What would you like to call me?
28
28
  - Preferred communication style (formal/casual, English/other language, etc.)
@@ -79,7 +79,7 @@ After `halo setup`:
79
79
 
80
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`.
81
81
 
82
- **Server-refreshed**: `INSTRUCTIONS.md`, `prompts/`, `models/`, `docs/`, the built-in agent ids (`default`, `executor`, `deep-executor`, `__evo_agent__`, `__score__`, `__apply_agent__`), and the built-in skill ids (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `nova-web-search`, `halo`). To customize one, copy it into the workspace scope (`<project>/.halo/...`) — workspace replaces global at runtime.
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
 
84
84
  **Never overwritten**: anything else under `agents/` or `skills/` (your own creations), and everything under `secrets/`.
85
85
 
@@ -35,7 +35,7 @@ Goal: a skill called `caption-image` that calls `api.example.com` with an API ke
35
35
  Beside `SKILL.md`:
36
36
 
37
37
  ```yaml
38
- # extensions/skills/caption-image/config.yaml
38
+ # skills/caption-image/config.yaml
39
39
  params:
40
40
  - key: api_key
41
41
  description: API key for api.example.com
@@ -107,7 +107,7 @@ env vars (process.env.X) > workspace settings.yaml > global settings.yaml > sche
107
107
 
108
108
  If `process.env.EXAMPLE_KEY` is unset and the value at `caption-image.params.api_key` is `<<EXAMPLE_KEY>>`:
109
109
  - The agent sees the literal `<<EXAMPLE_KEY>>` in the rendered skill body
110
- - The server logs `[md-vars] Env var "EXAMPLE_KEY" not set — keeping <<EXAMPLE_KEY>> literal`
110
+ - The server logs `[MdVars] Env var "EXAMPLE_KEY" not set — keeping <<EXAMPLE_KEY>> literal`
111
111
  - API calls the agent makes will fail with a clear error (usually HTTP 401) and the error message will reveal the literal, so the user can tell what to fix
112
112
 
113
113
  This is intentional: empty strings hide the misconfiguration; literals make it visible.
@@ -155,7 +155,7 @@ Free-form, but consistency helps. Common patterns in Halo:
155
155
  | Env var | Purpose |
156
156
  |---|---|
157
157
  | `HALO_PASSWORD` | Server login password |
158
- | `HALO_MODEL_ID` / `HALO_MODEL_PROVIDER` / `HALO_MAX_CONTEXT_TOKENS` | Server defaults |
158
+ | `HALO_MAX_CONTEXT_TOKENS` | Server default context window (272000) |
159
159
  | `AWS_REGION` / `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | AWS / Bedrock |
160
160
 
161
161
  For your own skills, `UPPER_SNAKE` prefixed by the product is idiomatic (`NANO_BANANA_KEY`, `EXAMPLE_API_TOKEN`). The placeholder is case-sensitive — `<<example_key>>` won't match `EXAMPLE_KEY`.
@@ -24,7 +24,7 @@ The main agent (the one you talk to directly) sits at the top; any sub-agents it
24
24
 
25
25
  - Collapse/expand: click the arrow
26
26
  - Count badge `+3`: 3 descendants
27
- - Red ⏹ icon: session was stopped (stopped_at is set)
27
+ - ⏹ icon: session was stopped (stopped_at is set)
28
28
 
29
29
  ## Message viewer
30
30
 
@@ -48,7 +48,7 @@ Top-right Bug icon toggles it. When on you also see:
48
48
 
49
49
  ## System prompt viewer
50
50
 
51
- In Debug mode the top-right gets an extra `Prompt` button; click to expand the full system prompt (AGENT.md + INSTRUCTIONS.md + INDEX.md + TOOL_GUIDELINES + skill metadata).
51
+ Next to the Debug toggle, a `Prompt` button appears once the session has a recorded system prompt; click to expand the full system prompt (AGENT.md + INSTRUCTIONS.md + INDEX.md + TOOL_GUIDELINES + skill metadata).
52
52
 
53
53
  Use it to see exactly what context the agent was given for a given turn.
54
54
 
@@ -71,7 +71,7 @@ Refreshing the browser or a network blip:
71
71
 
72
72
  ## Deleting a session
73
73
 
74
- Click a session's trash icon (Sessions page) or ✕ on its chat tab, then confirm:
74
+ Hover a session and click its trash icon (Sessions page or a chat tab), then confirm:
75
75
  - Cascade delete in SQLite (parent + all descendants)
76
76
  - `.halo/sessions/{agentId}/{sid}.json` removed
77
77
  - Sidebar entry removed
@@ -30,7 +30,7 @@ Putting those in AGENT.md would bloat the system prompt. Skills use **progressiv
30
30
 
31
31
  Workspace overrides global (same id).
32
32
 
33
- **Built-in skills** (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `nova-web-search`) are server-shipped and **force-overwritten on every server startup**. Local edits to these directories under `~/.halo/global/skills/` will be lost. To customize one, copy it into `<project>/.halo/skills/<id>/` (workspace replaces global) and edit there. Other skills under `~/.halo/global/skills/` — including any the user created via the admin UI — are untouched by the seeder.
33
+ **Built-in skills** (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `web-search`, `halo`, `extension`) are server-shipped and **force-overwritten on every server startup**. Local edits to these directories under `~/.halo/global/skills/` will be lost. To customize one, copy it into `<project>/.halo/skills/<id>/` (workspace replaces global) and edit there. Other skills under `~/.halo/global/skills/` — including any the user created via the admin UI — are untouched by the seeder.
34
34
 
35
35
  ## SKILL.md format
36
36
 
@@ -63,7 +63,7 @@ Follow the checklist in checklist.md...
63
63
 
64
64
  ## Open the Skills panel
65
65
 
66
- Left-side Activity Bar → `🪄 Skills` icon.
66
+ Left-side Activity Bar → `⚡ Skills` icon.
67
67
 
68
68
  Click any skill and the right pane becomes a mini workspace:
69
69
  - File tree (every file in the skill directory) on the left
@@ -73,9 +73,9 @@ You can edit SKILL.md, add scripts, templates, example docs.
73
73
 
74
74
  ## Create / delete
75
75
 
76
- `+` button on the sidebar — fill in name / description / scope.
76
+ Click the `+` on the **Global** or **Workspace** group header in the sidebar (the group you click decides the scope) and type a name.
77
77
 
78
- Delete: right-click → Delete.
78
+ Delete: hover a skill and click its trash icon (after a confirmation). The toggle next to it disables / enables the skill.
79
79
 
80
80
  ## Letting an agent use a skill
81
81
 
@@ -158,7 +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
- | nova-web-search | Real-time web search via Amazon Nova 2 Lite's nova_grounding (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) |
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) |
162
164
 
163
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.
164
166
 
@@ -278,7 +280,7 @@ nano-banana:
278
280
 
279
281
  1. **Merge order**: workspace `<project>/.halo/settings.yaml` deep-merges over global `~/.halo/secrets/settings.yaml` (field-level merge, not block replacement)
280
282
  2. **Env vars**: `<<ENV_NAME>>` inside a value is replaced with `process.env.ENV_NAME` at render time. **If the env var is missing, the `<<ENV_NAME>>` literal is kept** so agents can see what's unset and the user can fix it
281
- 3. **Unknown placeholders** stay as-is (e.g. `{{foo}}`, `{{nano-banana.params.missing-key}}`); the server logs a `[md-vars] Unknown placeholder "…"` warning
283
+ 3. **Unknown placeholders** stay as-is (e.g. `{{foo}}`, `{{nano-banana.params.missing-key}}`); the server logs a `[MdVars]` line saying it left the placeholder as-is
282
284
  4. **Key charset**: `[\w-]`, hyphens allowed (e.g. `nano-banana`)
283
285
 
284
286
  ### Why put credentials in env vars