@turmind/halo 1.5.4 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) 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-e0faeff1c4a0b0b9.js +1 -0
  4. package/admin-out/_next/static/chunks/app/page-44f707ea8dad57da.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/b8c87b919758c52b.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 +10 -8
  10. package/bundled-docs/design/agentcore.md +13 -10
  11. package/bundled-docs/design/architecture.md +24 -14
  12. package/bundled-docs/design/canvas-extensions.md +19 -4
  13. package/bundled-docs/design/command.md +5 -5
  14. package/bundled-docs/design/cron.md +2 -2
  15. package/bundled-docs/design/evolution.md +34 -35
  16. package/bundled-docs/design/express-self.md +6 -4
  17. package/bundled-docs/design/feishu.md +18 -14
  18. package/bundled-docs/design/halo-city.md +5 -4
  19. package/bundled-docs/design/observability.md +9 -9
  20. package/bundled-docs/design/prompt-system.md +12 -12
  21. package/bundled-docs/design/session.md +14 -14
  22. package/bundled-docs/design/slack.md +22 -20
  23. package/bundled-docs/design/storage.md +30 -29
  24. package/bundled-docs/design/telegram.md +26 -8
  25. package/bundled-docs/design/web.md +3 -1
  26. package/bundled-docs/design/wechat.md +8 -6
  27. package/bundled-docs/design/wecom.md +23 -9
  28. package/bundled-docs/design/ws.md +6 -6
  29. package/bundled-docs/dev/acp-adapter.md +8 -8
  30. package/bundled-docs/dev/add-channel.md +12 -9
  31. package/bundled-docs/dev/add-model-provider.md +31 -20
  32. package/bundled-docs/dev/api.md +86 -57
  33. package/bundled-docs/dev/deploy.md +8 -6
  34. package/bundled-docs/dev/env.md +29 -21
  35. package/bundled-docs/dev/logging.md +15 -29
  36. package/bundled-docs/dev/previews.md +20 -11
  37. package/bundled-docs/dev/tools.md +26 -14
  38. package/bundled-docs/guide/README.md +1 -0
  39. package/bundled-docs/guide/agents.md +19 -53
  40. package/bundled-docs/guide/channels/README.md +7 -4
  41. package/bundled-docs/guide/channels/acp.md +3 -1
  42. package/bundled-docs/guide/channels/feishu.md +13 -11
  43. package/bundled-docs/guide/channels/slack.md +12 -10
  44. package/bundled-docs/guide/channels/telegram.md +21 -27
  45. package/bundled-docs/guide/channels/web.md +15 -11
  46. package/bundled-docs/guide/channels/wechat.md +15 -14
  47. package/bundled-docs/guide/channels/wecom.md +7 -2
  48. package/bundled-docs/guide/chat.md +10 -9
  49. package/bundled-docs/guide/cli.md +10 -8
  50. package/bundled-docs/guide/delegation-and-access.md +1 -1
  51. package/bundled-docs/guide/getting-started.md +3 -3
  52. package/bundled-docs/guide/secrets-and-credentials.md +3 -3
  53. package/bundled-docs/guide/sessions.md +3 -3
  54. package/bundled-docs/guide/skills.md +9 -7
  55. package/bundled-docs/guide/testing-agents-and-skills.md +14 -15
  56. package/bundled-docs/guide/workspace.md +29 -17
  57. package/bundled-docs/requirements/agent.md +19 -18
  58. package/bundled-docs/requirements/chat.md +16 -17
  59. package/bundled-docs/requirements/command.md +10 -10
  60. package/bundled-docs/requirements/editor.md +12 -10
  61. package/bundled-docs/requirements/explorer.md +10 -7
  62. package/bundled-docs/requirements/session.md +16 -17
  63. package/bundled-docs/requirements/settings.md +30 -18
  64. package/bundled-docs/requirements/skill.md +4 -4
  65. package/bundled-docs/requirements/source-control.md +4 -3
  66. package/bundled-docs/requirements/terminal.md +13 -13
  67. package/bundled-docs/requirements/workspace.md +19 -13
  68. package/dist/index.js +10587 -12088
  69. package/package.json +1 -1
  70. package/templates/agents/default/agent.yaml +1 -0
  71. package/templates/config.yaml +6 -0
  72. package/templates/skills/extension/SKILL.md +25 -14
  73. package/templates/skills/extension/config.yaml +9 -0
  74. package/templates/skills/extension/templates/ext.sh +166 -34
  75. package/templates/skills/web-search/SKILL.md +20 -6
  76. package/templates/skills/web-search/config.yaml +13 -0
  77. package/templates/skills/web-search/search.py +27 -6
  78. package/templates/skills/workspace/share.md +11 -3
  79. package/templates/skills/workspace/stage.py +85 -5
  80. package/admin-out/_next/static/chunks/989-92e9683ae8d39750.js +0 -1
  81. package/admin-out/_next/static/chunks/app/page-7a635969198fc62b.js +0 -1
  82. package/admin-out/_next/static/css/4ea5f674146e54d8.css +0 -3
  83. /package/admin-out/_next/static/{lm8Bz3ugTSORyuSXfyx56 → visjvN510yFK-Ff5zwx-0}/_buildManifest.js +0 -0
  84. /package/admin-out/_next/static/{lm8Bz3ugTSORyuSXfyx56 → visjvN510yFK-Ff5zwx-0}/_ssgManifest.js +0 -0
@@ -54,6 +54,8 @@ halo acp \
54
54
  | `--token` | yes | Web-channel token. `full` access required for multi-workspace use |
55
55
  | `--workspace` | yes | Absolute server-side path for this adapter |
56
56
  | `--agent-id` | no | Halo agent profile to use when ACP `session/new` creates a fresh session. Default `default` (alias `--agent`) |
57
+ | `--scheme` | no | `http` (default) or `https` — use `https` for a server behind a TLS reverse proxy |
58
+ | `--header "Name: value"` | no | Repeatable. Extra HTTP header sent on every request (like `curl -H`), for auth in front of the server — proxy cookie, `CF-Access-*`, basic auth |
57
59
 
58
60
  Each adapter process binds to **one** workspace. To drive multiple workspaces concurrently from one token, run multiple adapter processes — one per workspace.
59
61
 
@@ -139,7 +141,7 @@ For now: if you want the agent to see a Mac-side file, paste it into the prompt.
139
141
  | `401` on first prompt | Token typo, or token was deleted from admin |
140
142
  | `403` when launching with `--workspace /some/other/path` | Token is `readonly` / `workspace` access — use a `full` token or omit `--workspace` |
141
143
  | Tool calls don't appear in Claude Code | Expected — halo doesn't translate every event back as ACP `tool_call`. See [docs/dev/acp-adapter.md](../../dev/acp-adapter.md) for the full mapping |
142
- | Two `session/prompt` calls on the same id, second hangs | Halo queues messages when a session is busy; ACP adapter ends the response with `[queued]`. Wait for the first to finish |
144
+ | Two `session/prompt` calls on the same id, second hangs | Halo queues messages when a session is busy; ACP adapter ends the response with `[queued — session busy]`. Wait for the first to finish |
143
145
  | `/workspace switch <path>` worked but other tokens broke | You changed the db-level default. Switch back with another `/workspace switch`, or stop using slash commands from the adapter |
144
146
 
145
147
  ## Reference
@@ -77,16 +77,16 @@ After approval the bot is visible in your Feishu tenant. Repeat this step **ever
77
77
 
78
78
  ## Step 7 — Add the account in halo admin
79
79
 
80
- Open halo admin → **Channels** → **Feishu** → **Add Account**:
80
+ Open halo admin → **Channels** → **Feishu** → **Add Bot**:
81
81
 
82
82
  | Field | Value |
83
83
  |---|---|
84
84
  | App ID | from Step 2 |
85
85
  | App Secret | from Step 2 |
86
- | Verification token | leave blank (long-connect doesn't use it) |
87
- | Encrypt key | leave blank (long-connect doesn't encrypt the wire) |
88
- | Workspace path | absolute path, e.g. `/home/ubuntu/my-project` |
89
- | Label | optional |
86
+ | Verification Token (optional) | leave blank (long-connect doesn't use it) |
87
+ | Encrypt Key (optional) | leave blank (long-connect doesn't encrypt the wire) |
88
+ | Bind to workspace | absolute path, e.g. `/home/ubuntu/my-project` |
89
+ | Name (optional) | a label for the account |
90
90
  | Access level | `readonly` (default), `workspace`, or `full` |
91
91
  | Language | `en` or `zh` |
92
92
 
@@ -109,7 +109,7 @@ In Feishu desktop / mobile:
109
109
  1. Top-bar search → type the bot's name → click into the bot's profile → start a DM → send `hello` → expect a streamed reply
110
110
  2. Add the bot to a group: open a group → 设置 → 群机器人 → add → `@halo hi` → expect a reply
111
111
 
112
- If nothing happens, check halo server logs (`/tmp/halo-server.log`) for `[feishu]` lines.
112
+ If nothing happens, check halo server logs (`~/.halo/global/logs/server.log`) for `[Feishu]` lines.
113
113
 
114
114
  ## How halo handles inbound
115
115
 
@@ -117,18 +117,20 @@ If nothing happens, check halo server logs (`/tmp/halo-server.log`) for `[feishu
117
117
  - **Groups** — only `@`-mentions wake the bot up
118
118
  - **Threads** — Feishu's `root_id` is used as the session boundary; replies stay in-thread
119
119
  - **Files** — images go to the LLM as multimodal content; other files are saved under `<workspace>/.halo/assets/feishu/inbound/<accountId>/<date>/`
120
+ - **After a server restart** — if the agent was mid-task when halo restarted, its reply still reaches the most recent 1:1 DM the bot talked in; group threads reconnect on their next `@`-mention
120
121
  - **Self-loop** — bot-authored events are dropped to prevent the bot replying to itself
121
122
 
122
123
  ## Slash commands
123
124
 
124
- Same set as the other channels — type as plain text in a DM or thread:
125
+ Same set as the other channels — type as plain text, **in a 1:1 DM only** (in a group the text goes to the agent as a normal message):
125
126
 
126
127
  | Command | Effect |
127
128
  |---|---|
128
- | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
129
+ | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` / `info` |
129
130
  | `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
130
131
  | `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
131
132
  | `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
133
+ | `/cron` `/acp` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
132
134
  | `/help` | List commands — object commands show only the verbs you can run |
133
135
 
134
136
  ## Cron jobs targeting Feishu
@@ -145,12 +147,12 @@ Search depends on the contact and `im:chat:readonly` scopes from Step 4. If auto
145
147
 
146
148
  | Symptom | Cause / fix |
147
149
  |---|---|
148
- | `99991663` on Add Account | App Secret wrong — copy it again from Step 2 |
149
- | `99991668` on Add Account | Bot capability not enabled — go back to Step 3 |
150
+ | `99991663` on Add Bot | App Secret wrong — copy it again from Step 2 |
151
+ | `99991668` on Add Bot | Bot capability not enabled — go back to Step 3 |
150
152
  | Bot exists but receives no events | Step 5 mode is set to webhook (not long-connect), or Step 6 wasn't done |
151
153
  | Permission errors when sending images / reading group info | New scope was approved but app version wasn't republished — go to Step 6 |
152
154
  | Can't find the bot in Feishu search | Step 6 wasn't done. Search results are tenant-wide and only show **published** apps |
153
- | Multiple halo instances reply to the same message | Only one process should hold the long-connect. Check `~/.halo/global/server.pid` for the lock |
155
+ | Multiple halo instances reply to the same message | Only one process should hold the long-connect. Check `~/.halo/global/server.lock` for the lock |
154
156
 
155
157
  ## Multi-tenant setup
156
158
 
@@ -83,14 +83,14 @@ In the Slack desktop client, fully quit and reopen (or `Cmd+R` to reload) — ot
83
83
 
84
84
  ## Step 7 — Add the account in halo admin
85
85
 
86
- Open halo admin → **Channels** → **Slack** → **Add Account**:
86
+ Open halo admin → **Channels** → **Slack** → **Add Bot**:
87
87
 
88
88
  | Field | Value |
89
89
  |---|---|
90
- | Bot token | the `xoxb-…` from Step 5 |
91
- | App token | the `xapp-…` from Step 2 |
92
- | Workspace path | absolute path to the workspace this bot drives, e.g. `/home/ubuntu/my-project` |
93
- | Label | optional, e.g. "Engineering bot" |
90
+ | Bot Token (xoxb-…) | the `xoxb-…` from Step 5 |
91
+ | App Token (xapp-…) | the `xapp-…` from Step 2 |
92
+ | Bind to workspace | absolute path to the workspace this bot drives, e.g. `/home/ubuntu/my-project` |
93
+ | Name (optional) | e.g. "Engineering bot" |
94
94
  | Access level | `readonly` (default), `workspace`, or `full` |
95
95
  | Language | `en` or `zh` |
96
96
 
@@ -111,7 +111,7 @@ Pick the level with that in mind:
111
111
  1. In Slack, click the bot's name in the sidebar to start a DM → type `hello` → expect a streamed reply
112
112
  2. Invite the bot into a channel: `/invite @halo` → mention it: `@halo what files are in this workspace?` → expect a reply in the same thread
113
113
 
114
- If nothing happens, check the halo server logs (`/tmp/halo-server.log`) for `[slack]` lines.
114
+ If nothing happens, check the halo server logs (`~/.halo/global/logs/server.log`) for `[Slack]` lines.
115
115
 
116
116
  ## How halo handles inbound
117
117
 
@@ -119,18 +119,20 @@ If nothing happens, check the halo server logs (`/tmp/halo-server.log`) for `[sl
119
119
  - **Channels / groups** — only `@`-mentions wake the bot up, plain messages are ignored
120
120
  - **Threads** — the bot replies in-thread; one halo session per thread, so a long thread stays in one conversation
121
121
  - **Files** — images go to the LLM as multimodal content; other files are saved under `<workspace>/.halo/assets/slack/inbound/<accountId>/<date>/`
122
+ - **After a server restart** — if the agent was mid-task when halo restarted, its reply still reaches the most recent conversation (DM or thread) the bot talked in (one per bot); other threads reconnect on their next message
122
123
  - **`bot_id` self-loop** — messages posted by any bot (including this one) are dropped, so the agent can't reply to itself
123
124
 
124
125
  ## Slash commands
125
126
 
126
- Same set as the other channels — type these as plain text in a DM or thread:
127
+ Same set as the other channels, but **only in a DM with the bot** — in a channel or thread the text goes to the agent as a normal message. Slack's own client intercepts a leading `/`, so type `!` instead (e.g. `!session list`); halo treats `!cmd` as `/cmd` and shows `!…` in its replies. The table below uses `/`:
127
128
 
128
129
  | Command | Effect |
129
130
  |---|---|
130
- | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` |
131
+ | `/session <verb>` | Session lifecycle: `new` / `list` / `switch <n>` / `stop` / `interrupt` / `compact` / `context` / `info` |
131
132
  | `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
132
133
  | `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
133
134
  | `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
135
+ | `/cron` `/acp` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
134
136
  | `/help` | List commands — object commands show only the verbs you can run |
135
137
 
136
138
  ## Cron jobs targeting Slack
@@ -149,10 +151,10 @@ The search depends on the `users:read` / `channels:read` scopes added in Step 4.
149
151
  |---|---|
150
152
  | **"向此应用发送消息的功能已关闭"** banner on the DM page | Step 6 wasn't done — open App Home, enable Messages Tab, reload Slack |
151
153
  | Account created but no events arrive | Socket Mode token (`xapp-`) is wrong, or `connections:write` scope missing on it |
152
- | `not_authed` / `invalid_auth` error on Add Account | Bot token (`xoxb-`) typo, or you pasted the App token in the Bot field by mistake |
154
+ | `not_authed` / `invalid_auth` error on Add Bot | Bot token (`xoxb-`) typo, or you pasted the App token in the Bot field by mistake |
153
155
  | Bot replies to itself in a loop | Should never happen — halo drops all `bot_id`-tagged messages. If it does, file a bug |
154
156
  | Search dropdown empty in cron form | Missing `users:read` / `channels:read` scopes — add them, then Reinstall to Workspace |
155
- | Multiple halo instances reply to the same message | Only one process should hold the Socket Mode connection. Check `~/.halo/global/server.pid` for the lock |
157
+ | Multiple halo instances reply to the same message | Only one process should hold the Socket Mode connection. Check `~/.halo/global/server.lock` for the lock |
156
158
 
157
159
  ## Multi-workspace setup
158
160
 
@@ -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
 
@@ -114,7 +115,7 @@ 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
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`)