@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.
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/{133-33aa091fba853037.js → 422-cc74b1fdd3af5415.js} +1 -1
- package/admin-out/_next/static/chunks/989-bc743317498d3fbc.js +1 -0
- package/admin-out/_next/static/chunks/app/page-0a390059f1914664.js +1 -0
- package/admin-out/_next/static/chunks/{webpack-1a264efd965db7ab.js → webpack-edf248e07333566d.js} +1 -1
- package/admin-out/_next/static/css/b53542687ebd4c65.css +3 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/agent.md +4 -4
- package/bundled-docs/design/agentcore.md +13 -10
- package/bundled-docs/design/architecture.md +24 -14
- package/bundled-docs/design/command.md +5 -5
- package/bundled-docs/design/cron.md +2 -2
- package/bundled-docs/design/evolution.md +34 -35
- package/bundled-docs/design/express-self.md +6 -4
- package/bundled-docs/design/feishu.md +18 -14
- package/bundled-docs/design/halo-city.md +5 -4
- package/bundled-docs/design/observability.md +9 -9
- package/bundled-docs/design/prompt-system.md +12 -12
- package/bundled-docs/design/session.md +12 -12
- package/bundled-docs/design/slack.md +22 -20
- package/bundled-docs/design/storage.md +18 -19
- package/bundled-docs/design/telegram.md +26 -8
- package/bundled-docs/design/web.md +3 -1
- package/bundled-docs/design/wechat.md +8 -6
- package/bundled-docs/design/wecom.md +23 -9
- package/bundled-docs/design/ws.md +6 -6
- package/bundled-docs/dev/acp-adapter.md +8 -8
- package/bundled-docs/dev/add-channel.md +12 -9
- package/bundled-docs/dev/add-model-provider.md +31 -20
- package/bundled-docs/dev/api.md +83 -57
- package/bundled-docs/dev/deploy.md +8 -6
- package/bundled-docs/dev/env.md +16 -11
- package/bundled-docs/dev/logging.md +15 -29
- package/bundled-docs/dev/previews.md +20 -11
- package/bundled-docs/dev/tools.md +24 -12
- package/bundled-docs/guide/README.md +1 -0
- package/bundled-docs/guide/agents.md +19 -53
- package/bundled-docs/guide/channels/README.md +7 -4
- package/bundled-docs/guide/channels/acp.md +3 -1
- package/bundled-docs/guide/channels/feishu.md +13 -11
- package/bundled-docs/guide/channels/slack.md +12 -10
- package/bundled-docs/guide/channels/telegram.md +21 -27
- package/bundled-docs/guide/channels/web.md +15 -11
- package/bundled-docs/guide/channels/wechat.md +15 -14
- package/bundled-docs/guide/channels/wecom.md +7 -2
- package/bundled-docs/guide/chat.md +11 -10
- package/bundled-docs/guide/cli.md +10 -8
- package/bundled-docs/guide/getting-started.md +3 -3
- package/bundled-docs/guide/secrets-and-credentials.md +3 -3
- package/bundled-docs/guide/sessions.md +3 -3
- package/bundled-docs/guide/skills.md +8 -6
- package/bundled-docs/guide/testing-agents-and-skills.md +14 -15
- package/bundled-docs/guide/workspace.md +27 -15
- package/bundled-docs/requirements/agent.md +19 -18
- package/bundled-docs/requirements/chat.md +19 -21
- package/bundled-docs/requirements/command.md +10 -10
- package/bundled-docs/requirements/editor.md +10 -9
- package/bundled-docs/requirements/explorer.md +10 -7
- package/bundled-docs/requirements/session.md +16 -17
- package/bundled-docs/requirements/settings.md +18 -14
- package/bundled-docs/requirements/skill.md +4 -4
- package/bundled-docs/requirements/source-control.md +4 -3
- package/bundled-docs/requirements/terminal.md +13 -13
- package/bundled-docs/requirements/workspace.md +19 -13
- package/dist/index.js +2038 -3558
- package/package.json +1 -1
- package/templates/config.yaml +6 -0
- package/templates/skills/workspace/share.md +11 -3
- package/templates/skills/workspace/stage.py +85 -5
- package/admin-out/_next/static/chunks/989-c6c7772db8a50926.js +0 -1
- package/admin-out/_next/static/chunks/app/page-5f41b086cd5ad353.js +0 -1
- package/admin-out/_next/static/css/8809d33379458514.css +0 -3
- /package/admin-out/_next/static/{Wqi_ugna9MYsiV7ECuawQ → cuVcldfdg1PXevaQMQSzb}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{Wqi_ugna9MYsiV7ECuawQ → cuVcldfdg1PXevaQMQSzb}/_ssgManifest.js +0 -0
|
@@ -8,12 +8,18 @@ Location: [`packages/admin/src/features/editor/previews/`](../../../packages/adm
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
previews/
|
|
11
|
-
├── FilePreview.tsx Public entry.
|
|
12
|
-
│ renders <Suspense><Component/></Suspense
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
├── FilePreview.tsx Public entry. Asks the registry for the candidates for the
|
|
12
|
+
│ extension, renders the chosen one (<Suspense><Component/></Suspense>
|
|
13
|
+
│ for a built-in, the iframe host for an extension); too-large and
|
|
14
|
+
│ no-viewer placeholders.
|
|
15
|
+
├── types.ts PreviewPlugin, PreviewProps, Resolved
|
|
16
|
+
├── registry.ts register() / setExtensions() / loadExtensions() / resolve() / canPreview() / isHeavyPreview()
|
|
17
|
+
│ + subscribe() / useRegistryVersion() so open tabs re-resolve on change
|
|
18
|
+
├── extension-host.tsx Iframe host for installed preview extensions (+ extension-host-logic.ts,
|
|
19
|
+
│ extension-token.ts) — see design/canvas-extensions.md
|
|
15
20
|
├── ui/
|
|
16
|
-
│ ├── preview-shell.tsx Standard header (filename + Open-as-Text + Download
|
|
21
|
+
│ ├── preview-shell.tsx Standard header (filename + extraToolbar + Open with + Open-as-Text + Download)
|
|
22
|
+
│ ├── open-with-menu.tsx "Open with" picker, shown only when a file has more than one viewer
|
|
17
23
|
│ ├── use-preview-fetch.ts Hook: fetch + AbortController + parse, returns {data, error, loading}
|
|
18
24
|
│ ├── use-data-fetch.ts JSON-endpoint sibling: abortable fetcher → {data, error, loading},
|
|
19
25
|
│ │ for previews that parse server-side instead of raw bytes
|
|
@@ -60,6 +66,8 @@ interface PreviewProps {
|
|
|
60
66
|
viewUrl: string // for inline viewing, supports HTTP Range
|
|
61
67
|
downloadUrl: string // for forced download (used by the shell's Download button)
|
|
62
68
|
onOpenAsText?: () => void // set when the file can also be force-opened as text
|
|
69
|
+
tooLarge?: boolean // file over the editor's 10MB read cap; FilePreview shows a placeholder instead of the plugin
|
|
70
|
+
size?: number // on-disk size in bytes, shown by that placeholder
|
|
63
71
|
}
|
|
64
72
|
```
|
|
65
73
|
|
|
@@ -121,7 +129,7 @@ register(fooPlugin)
|
|
|
121
129
|
|
|
122
130
|
Done. The Canvas panel will:
|
|
123
131
|
- Treat `.foo` / `.foobar` as non-text (routes to preview instead of Monaco)
|
|
124
|
-
- Mount `FooPreview` inside an MRU cache (up to 5 concurrent
|
|
132
|
+
- Mount `FooPreview` inside an MRU cache (up to 5 concurrent previews cached)
|
|
125
133
|
|
|
126
134
|
### Routing API: `canPreview()` vs `loadExtensions()`
|
|
127
135
|
|
|
@@ -129,6 +137,7 @@ Done. The Canvas panel will:
|
|
|
129
137
|
|
|
130
138
|
- **One-shot routing decisions** (`isBinaryExtension` in `editor-panel.tsx`: click-open, tab restore after reload, open-to-side) must `await loadExtensions()` before concluding "text", because an extension may claim an extension-only type (`.glb`) or a text suffix (`.json`). A preview verdict needs no wait — extensions only add viewers ahead of text, and `FilePreview` re-picks the viewer on a registry version bump.
|
|
131
139
|
- The wait is capped at `INITIAL_LOAD_WAIT_MS` (3 s, `registry.ts`), counted from the first call; on timeout callers proceed as if nothing were installed. The request isn't cancelled — a late result still lands via `setExtensions`.
|
|
140
|
+
- Candidates come from `resolve(ext)`, best first: `default` extensions → the built-in plugin → `option` extensions → Open as Text (always last). More than one candidate puts an **Open with** menu in the header.
|
|
132
141
|
- It never rejects and is never re-issued: a failed fetch leaves the extension layer empty without delaying later opens. The WS-reconnect re-fetch and `extension:changed` frames fill it in.
|
|
133
142
|
|
|
134
143
|
## PreviewShell — the standard header
|
|
@@ -211,21 +220,21 @@ const { data } = usePreviewFetch(viewUrl, (buf, signal) =>
|
|
|
211
220
|
|
|
212
221
|
## `heavy: true` — when to use it
|
|
213
222
|
|
|
214
|
-
Set `heavy: true` when the preview:
|
|
223
|
+
Set `heavy: true` on a built-in when the preview:
|
|
215
224
|
- Needs DOM access (can't run in a Worker — e.g. `pptx-preview` draws to canvas)
|
|
216
225
|
- Is the dominant memory cost of the page (large canvas, many cached elements)
|
|
217
226
|
|
|
218
227
|
Effect: the MRU cache skips this plugin. Only the **active** instance mounts; switching away unmounts the component (releases memory and stops any in-flight work). Switching back re-fetches/re-renders.
|
|
219
228
|
|
|
220
|
-
Only pptx currently uses this. Don't set it by default — the MRU cache gives much faster switches.
|
|
229
|
+
Only pptx currently uses this among built-ins (read-only preview extensions are also treated as heavy, judged by `isHeavyPreview()`). Don't set it by default — the MRU cache gives much faster switches.
|
|
221
230
|
|
|
222
231
|
## `onOpenAsText`
|
|
223
232
|
|
|
224
|
-
|
|
233
|
+
The editor panel passes `props.onOpenAsText` to every preview tab. Calling it closes the preview tab and opens the raw file in Monaco. Pass it straight through to `PreviewShell` and the button appears automatically (the **Open with** menu also lists Open as text).
|
|
225
234
|
|
|
226
235
|
## Two plugin patterns: raw-bytes vs. server-parsed
|
|
227
236
|
|
|
228
|
-
Most built-in plugins (pdf, docx, xlsx, pptx, media) are **raw-bytes**:
|
|
237
|
+
Most built-in plugins (pdf, docx, xlsx, pptx, media) are **raw-bytes**: they load the file from `viewUrl` (`GET /api/files/download?inline=1`). docx and xlsx use `usePreviewFetch` to download it as an ArrayBuffer and parse client-side in a Worker; pptx fetches it itself and renders on the main thread; pdf and media just hand `viewUrl` to the browser (`<iframe>` / `<img>` / `<video>` / `<audio>`). This is the pattern documented above.
|
|
229
238
|
|
|
230
239
|
Parquet, SQLite and CSV/TSV are **server-parsed** instead: the server does the parsing and hands back one page of JSON rows at a time via `GET /api/data-preview/*` (route details in [`dev/api.md`](api.md#data-preview)), so a multi-GB file never has to reach the browser.
|
|
231
240
|
|
|
@@ -288,4 +297,4 @@ See [`dev/api.md`](api.md) for the route details.
|
|
|
288
297
|
- Open-as-Text button works (closes preview, opens in Monaco)
|
|
289
298
|
- Closing the preview tab cleanly aborts any in-flight fetch/parse
|
|
290
299
|
- Opening a second file of the same type reuses the worker (no second worker instance — check DevTools → Application → Service Workers / the process graph)
|
|
291
|
-
- Switching between up to 5 preview tabs is instant (MRU cache); the 6th oldest unmounts
|
|
300
|
+
- Switching between up to 5 preview tabs is instant (MRU cache); the 6th oldest unmounts (a dirty extension tab is never evicted)
|
|
@@ -31,13 +31,14 @@ Write a file (creates parent dirs on demand).
|
|
|
31
31
|
|
|
32
32
|
### file_edit
|
|
33
33
|
|
|
34
|
-
Replace a string in a file (exact match).
|
|
34
|
+
Replace a string in a file (exact match). Fails when `old_string` is empty, identical to `new_string`, not found, or (without `replace_all`) found more than once.
|
|
35
35
|
|
|
36
36
|
| Arg | Type | Required | Description |
|
|
37
37
|
|---|---|---|---|
|
|
38
38
|
| path | string | yes | File path |
|
|
39
39
|
| old_string | string | yes | The exact text to find |
|
|
40
40
|
| new_string | string | yes | The replacement |
|
|
41
|
+
| replace_all | boolean | no | Replace every occurrence (default: false) |
|
|
41
42
|
|
|
42
43
|
### view_image
|
|
43
44
|
|
|
@@ -58,7 +59,7 @@ Processing before the bytes go out — only the payload changes, the file on dis
|
|
|
58
59
|
|
|
59
60
|
How many images history can hold in total is capped separately at the session level — see [design/session.md → History image budget](../design/session.md#history-image-budget).
|
|
60
61
|
|
|
61
|
-
**Vision gating**: this tool is only injected into the agent's tool list when the underlying model declares `capabilities.image: true` in its provider manifest. For text-only models (DeepSeek and others), `view_image` is silently dropped at `createWorkspaceTools()` time so the model never sees it — calling it would otherwise produce a 400 from the provider.
|
|
62
|
+
**Vision gating**: this tool is only injected into the agent's tool list when the underlying model declares `capabilities.image: true` in its provider manifest (or the agent's `model.image` in `agent.yaml` overrides it). For text-only models (DeepSeek and others), `view_image` is silently dropped at `createWorkspaceTools()` time so the model never sees it — calling it would otherwise produce a 400 from the provider.
|
|
62
63
|
|
|
63
64
|
### file_list
|
|
64
65
|
|
|
@@ -103,7 +104,7 @@ Find files by glob.
|
|
|
103
104
|
| pattern | string | yes | Glob, e.g. `**/*.ts`, `src/**/*.tsx` |
|
|
104
105
|
| path | string | no | Starting dir (default: workspace root) |
|
|
105
106
|
|
|
106
|
-
Returns paths relative to workspace root, alphabetically. Same skip list as grep.
|
|
107
|
+
Returns paths relative to workspace root, alphabetically; stops collecting at 5,000 matches. Same skip list as grep.
|
|
107
108
|
|
|
108
109
|
### web_fetch
|
|
109
110
|
|
|
@@ -125,7 +126,7 @@ Each session carries `accessLevel: 'readonly' | 'workspace' | null` (persisted i
|
|
|
125
126
|
|
|
126
127
|
| Level | DB value | Tools (with OS sandbox) | Tools (without) | Sandbox |
|
|
127
128
|
|---|---|---|---|---|
|
|
128
|
-
| `null` (full) | `
|
|
129
|
+
| `null` (full) | `NULL` | All 9 tools | All 9 tools | None (rm guard still applies) |
|
|
129
130
|
| `workspace` | `workspace` | All 9 tools | All 9 tools | workspace rw, sensitive paths + workspace runtime state hidden |
|
|
130
131
|
| `readonly` | `readonly` | All 9 tools | file_read, view_image, file_list, grep, glob (5 tools) | workspace ro, sensitive paths + workspace runtime state hidden |
|
|
131
132
|
|
|
@@ -182,7 +183,7 @@ Sensitive directories and files are hidden from workspace/readonly sessions via
|
|
|
182
183
|
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs}.db` + their `-wal`/`-shm` files | bwrap `--ro-bind ~/.halo/.sandbox-empty` (reads as empty); Seatbelt `literal` deny |
|
|
183
184
|
| `writable_dirs` | (empty) | bwrap `--bind` read-write / Seatbelt write allow — for external CLIs that keep local state (e.g. `~/.kiro`); not applied to readonly sessions |
|
|
184
185
|
|
|
185
|
-
Changes saved through the settings API (admin Settings page, `PUT` / `PATCH /api/settings`) take effect immediately — the save fires `onSettingsChange`, which re-reads the lists into `sandbox.ts` (`setSandboxHiddenPaths`), so the next tool call uses them. A hand edit of `settings.yaml` is not watched for these keys: it applies at the next restart or the next API save. These keys are `globalOnly` in the schema — a workspace `settings.yaml` cannot override them, since they define the security boundary agents run inside.
|
|
186
|
+
Changes saved through the settings API (admin Settings page, `PUT` / `PATCH` / `DELETE /api/settings`) take effect immediately — the save fires `onSettingsChange`, which re-reads the lists into `sandbox.ts` (`setSandboxHiddenPaths`), so the next tool call uses them. A hand edit of `settings.yaml` is not watched for these keys: it applies at the next restart or the next API save. These keys are `globalOnly` in the schema — a workspace `settings.yaml` cannot override them, since they define the security boundary agents run inside.
|
|
186
187
|
|
|
187
188
|
**Workspace-relative set (hardcoded)** — the workspace's own runtime state, which holds other channels'/users' conversations on a shared workspace. Code constants in `sandbox.ts` (`WORKSPACE_HIDDEN_DIRS` / `WORKSPACE_HIDDEN_FILES`), deliberately not settings: this is a security boundary (a config edit must not be able to open it), and the entries are workspace-relative while the settings lists are absolute/`~` paths.
|
|
188
189
|
|
|
@@ -198,7 +199,7 @@ The rest of `.halo/` (INSTRUCTIONS.md, INDEX.md, docs/, memory/, skills/, agents
|
|
|
198
199
|
Per-channel defaults: every channel session (Web, Telegram, Slack, Feishu, WeCom, WeChat) inherits its account's `access_level` — `full` → full, `workspace` → workspace, `readonly` / `observer` → readonly — and new accounts default to `readonly` (DB column default, `insertAccount` fallback and the admin create forms). Level sources for every entry point (admin, cron, cli, relay, goal, sub-agents): [guide/delegation-and-access.md](../guide/delegation-and-access.md#where-a-sessions-level-comes-from).
|
|
199
200
|
|
|
200
201
|
### Binary file detection
|
|
201
|
-
`grep`
|
|
202
|
+
`grep` reads the first 512 bytes of each file looking for a null byte and skips binaries (`glob` matches paths only and never opens files).
|
|
202
203
|
|
|
203
204
|
### Tool result budget
|
|
204
205
|
The orchestrator truncates tool results over 8000 chars and appends a `[Content truncated]` hint telling the agent to use `grep` for a targeted search. `activate_skill` results are exempt — a SKILL.md body is instructions, not data, and the built-in acp / cron / self skills exceed 8K.
|
|
@@ -227,6 +228,7 @@ Start a sub-agent session asynchronously. When the sub-agent finishes, its **wra
|
|
|
227
228
|
|
|
228
229
|
- Success: `{"code": 0, "session_id": "<childSessionId>"}`
|
|
229
230
|
- Unknown agent: `{"code": 1, "error": "agent \"<id>\" not found..."}`
|
|
231
|
+
- Agent outside the caller's `team`: `{"code": 1, "error": "agent \"<id>\" is not in your team..."}`
|
|
230
232
|
- Depth exceeded: `{"code": 1, "error": "Maximum nesting depth (N) reached..."}`
|
|
231
233
|
- Working dir invalid: `{"code": 1, "error": "working_dir \"...\" is outside the workspace"}` (or does-not-exist / not-a-directory)
|
|
232
234
|
|
|
@@ -244,12 +246,21 @@ No arguments. Returns JSON:
|
|
|
244
246
|
"sessions": [
|
|
245
247
|
{
|
|
246
248
|
"id": "root>sid_xxx",
|
|
249
|
+
"parentId": "root",
|
|
247
250
|
"agentId": "coder",
|
|
248
251
|
"agentName": "Coder",
|
|
249
|
-
"title": "Implement login page",
|
|
250
252
|
"description": "Implement login page",
|
|
251
253
|
"status": "running",
|
|
252
|
-
"
|
|
254
|
+
"accessLevel": null,
|
|
255
|
+
"goalSessionId": null,
|
|
256
|
+
"createdAt": 1714000000000,
|
|
257
|
+
"updatedAt": 1714000005000,
|
|
258
|
+
"stoppedAt": null,
|
|
259
|
+
"archivedAt": null,
|
|
260
|
+
"title": "Implement login page",
|
|
261
|
+
"exchangeCount": 1,
|
|
262
|
+
"contextTokens": 12345,
|
|
263
|
+
"totalOutputTokens": 678
|
|
253
264
|
}
|
|
254
265
|
],
|
|
255
266
|
"count": 1
|
|
@@ -258,10 +269,11 @@ No arguments. Returns JSON:
|
|
|
258
269
|
|
|
259
270
|
`status`: `running` / `idle` / `stopped`. Archived sessions are excluded.
|
|
260
271
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
272
|
+
Each entry is the session's `SessionInfo` row. `title` is the human-assigned
|
|
273
|
+
label (set by renaming the session in the admin sidebar) mirrored onto the row
|
|
274
|
+
from the session file, so it matches what the UI shows. It falls back to
|
|
275
|
+
`description` (the `start_session` task summary) when no title was set, so the
|
|
276
|
+
field is never empty — lets a caller dispatch work by title.
|
|
265
277
|
|
|
266
278
|
### query_session
|
|
267
279
|
|
|
@@ -20,6 +20,7 @@ Aimed at Halo end-users. Pick the doc that matches your situation.
|
|
|
20
20
|
- [Telegram](channels/telegram.md) — BotFather token, long-poll
|
|
21
21
|
- [Slack](channels/slack.md) — Socket Mode (no public webhook needed)
|
|
22
22
|
- [Feishu / Lark](channels/feishu.md) — long-connect with appId + appSecret
|
|
23
|
+
- [WeCom](channels/wecom.md) — 企业微信 intelligent bot, long-connect with Bot ID + Secret
|
|
23
24
|
- [WeChat](channels/wechat.md) — QR-scan iLink bot
|
|
24
25
|
- [ACP](channels/acp.md) — Claude Code or other ACP clients driving a remote halo
|
|
25
26
|
|
|
@@ -6,17 +6,17 @@ The agent is Halo's core abstraction: personality (AGENT.md) + config (agent.yam
|
|
|
6
6
|
|
|
7
7
|
| Kind | Description |
|
|
8
8
|
|---|---|
|
|
9
|
-
| Built-in | Server-shipped agents (`default`, `executor`, `deep-executor` + the
|
|
9
|
+
| Built-in | Server-shipped agents (`default`, `executor`, `deep-executor` + the internal `goal` / `__evo_agent__` / `__score__` / `__apply_agent__`; `goal` is seeded like the rest but only usable when `general.goal_mode_enabled` is set — off by default, no UI). Live under `~/.halo/global/agents/<id>/`. **Force-overwritten on every server startup** — local edits to these files will be lost on the next start. To customize: copy into the workspace scope (workspace replaces global) and edit there. |
|
|
10
10
|
| Global | Any other agent under `~/.halo/global/agents/<id>/`, e.g. one you created via the admin UI. Shared across projects. Never overwritten by the server. |
|
|
11
11
|
| Workspace | `<project>/.halo/agents/<id>/`, private to the current project; same-id workspace wins over global. Override is **whole-folder**: the workspace agent folder replaces the global one entirely, so copy *both* `agent.yaml` and `AGENT.md` when customizing — a folder with only `AGENT.md` has no model config and won't load. |
|
|
12
12
|
|
|
13
13
|
## Open the Agents panel
|
|
14
14
|
|
|
15
|
-
Click the
|
|
15
|
+
Click the `🤖 Agents` icon in the Activity Bar.
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
┌─────────────────┬──────────────────────────┐
|
|
19
|
-
│ Agents │ Form /
|
|
19
|
+
│ Agents │ Form / Edit │
|
|
20
20
|
│─────────────────│ │
|
|
21
21
|
│ ▼ Global (2) │ │
|
|
22
22
|
│ 🤖 Default │ │
|
|
@@ -28,38 +28,32 @@ Click the `👥 Agents` icon in the Activity Bar.
|
|
|
28
28
|
|
|
29
29
|
## Create an agent
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Click the `+` on the **Global** or **Workspace** group header in the sidebar (the group you click decides the scope; Workspace needs an open workspace). Two prompts follow:
|
|
32
32
|
- **Name**: display name (e.g. "Coder")
|
|
33
|
-
- **Description**: one-line description
|
|
34
|
-
- **Scope**: Global / Workspace
|
|
33
|
+
- **Description**: one-line description (optional)
|
|
35
34
|
|
|
36
35
|
The backend calls `defaultAgentYaml(name, description)` to generate a minimal `agent.yaml`.
|
|
37
36
|
|
|
38
37
|
## Edit an agent
|
|
39
38
|
|
|
40
|
-
Click an agent in the sidebar to open the right-hand editor.
|
|
39
|
+
Click an agent in the sidebar to open the right-hand editor. Two views:
|
|
41
40
|
|
|
42
41
|
### Form view
|
|
43
|
-
Data-driven form for name / description / model / tools / skills / thinking —
|
|
42
|
+
Data-driven form for name / description / model / tools / skills / thinking. Changes auto-save about half a second after you stop editing — there's no Save button. The agent's AGENT.md is shown below the form as a read-only preview.
|
|
44
43
|
|
|
45
44
|
**Model section** has four fields:
|
|
46
45
|
- `provider` — dropdown populated from `~/.halo/global/models/*.yaml`
|
|
47
|
-
- `id` — combobox (input + datalist); preset options filtered by current provider, also accepts manual input of any
|
|
46
|
+
- `id` — combobox (input + datalist); preset options filtered by current provider, also accepts manual input of any model ID
|
|
48
47
|
- `endpoint` — combobox (input + datalist); full endpoint URL (e.g. `https://bedrock-runtime.ap-northeast-1.amazonaws.com`), supports custom proxy URLs
|
|
49
48
|
- `maxTokens` — optional; defaults to `maxOutputTokens` in the provider yaml
|
|
50
49
|
|
|
51
|
-
Switching provider
|
|
50
|
+
Switching provider resets the model id to the new provider's default model (its `defaultModelId`), keeping the current id when the new provider lists it too.
|
|
52
51
|
|
|
53
52
|
**Capability buttons** (Prompt Caching / Thinking) appear when the selected model matches a registry entry with declared capabilities. For manually entered model IDs not in the registry, default presets are shown (5min/1hour for caching, Low/Medium/High/Max for thinking).
|
|
54
53
|
|
|
55
|
-
###
|
|
56
|
-
Edit
|
|
57
|
-
|
|
58
|
-
### MD view
|
|
59
|
-
Edit `AGENT.md` (personality) and `INSTRUCTIONS.md` (preferences).
|
|
54
|
+
### Edit view
|
|
55
|
+
The **Edit** button (top right) opens the agent's folder in a mini workspace (file tree + Monaco); **Back** returns to the form. Edit `agent.yaml` directly for advanced settings (like `context.maxTokens`, `promptCaching`) and `AGENT.md` for the personality.
|
|
60
56
|
- `AGENT.md` takes precedence over YAML's `system_prompt`
|
|
61
|
-
- `INSTRUCTIONS.md` is injected into every agent, not just this one
|
|
62
|
-
- When a workspace is open, the top-right toggles **Global / Workspace**; both MDs can be edited independently of which scope the current agent lives in. Switching to a non-existent file shows `(new)`; saving auto-creates the dir/file.
|
|
63
57
|
- AGENT.md supports `{{var}}` placeholders (`{{<skill-id>.params.<key>}}` / `<<ENV>>` / built-ins) rendered on agent start — see [skills.md placeholder section](skills.md#placeholders-template-variables)
|
|
64
58
|
|
|
65
59
|
## Tool configuration
|
|
@@ -75,7 +69,7 @@ team: # a non-empty team is what enables delegation
|
|
|
75
69
|
- executor # the agent ids this one may spawn
|
|
76
70
|
```
|
|
77
71
|
|
|
78
|
-
**Workspace tools**: `file_read / file_write / file_edit / file_list / shell_exec / grep / glob / web_fetch` — listed by name under `tools`.
|
|
72
|
+
**Workspace tools**: `file_read / view_image / file_write / file_edit / file_list / shell_exec / grep / glob / web_fetch` — listed by name under `tools`.
|
|
79
73
|
|
|
80
74
|
**Session tools**: `start_session / session_list / query_session / interrupt_session / stop_session / archive_session / get_session_output / query_agent` — **not** listed under `tools`. The whole bundle is granted automatically the moment an agent declares a non-empty `team`; an empty/absent `team` means no delegation (no session tools, no roster). To let a sub-agent delegate further, give it its own `team`. Add the agent's own id to its `team` to enable parallel self-spawn.
|
|
81
75
|
|
|
@@ -123,13 +117,14 @@ Internal agents (`internal: true`, e.g. self-evolution agents) have no Test butt
|
|
|
123
117
|
|
|
124
118
|
## Delete
|
|
125
119
|
|
|
126
|
-
|
|
120
|
+
Hover a **workspace** agent in the sidebar and click its trash icon. Global agents show a crown instead (no delete button there). Constraints:
|
|
127
121
|
- The last global agent cannot be deleted (server-enforced)
|
|
128
122
|
- Workspace agents delete freely
|
|
123
|
+
- `/agent delete <name>` (full access) removes either kind
|
|
129
124
|
|
|
130
125
|
## Common workflows
|
|
131
126
|
|
|
132
|
-
**Copy a global agent into workspace for customisation**:
|
|
127
|
+
**Copy a global agent into workspace for customisation**: copy the whole folder `~/.halo/global/agents/<id>/` to `<project>/.halo/agents/<id>/` and edit there. (Clicking `+` on Workspace with the same name creates a fresh scaffold under the same id, not a copy.)
|
|
133
128
|
|
|
134
129
|
**Multi-agent collaboration**: the Default agent sees its teammates in the prompt roster, starts them with `start_session`, and the sub-agent auto-reports on completion. See [sessions.md](sessions.md).
|
|
135
130
|
|
|
@@ -146,7 +141,7 @@ priority: 0
|
|
|
146
141
|
|
|
147
142
|
model:
|
|
148
143
|
provider: aws-bedrock-claude-invoke # required, matches ~/.halo/global/models/<provider>.yaml
|
|
149
|
-
id: global.anthropic.claude-sonnet-4-6 #
|
|
144
|
+
id: global.anthropic.claude-sonnet-4-6 # the provider yaml's defaultModelId
|
|
150
145
|
endpoint: https://bedrock-runtime.us-west-2.amazonaws.com # full endpoint URL; supports custom proxy
|
|
151
146
|
maxTokens: 16384 # optional, max output tokens (default from provider yaml)
|
|
152
147
|
|
|
@@ -226,41 +221,12 @@ nano-banana:
|
|
|
226
221
|
api_key: <<NANO_BANANA_KEY>>
|
|
227
222
|
```
|
|
228
223
|
|
|
229
|
-
…where the schema lives
|
|
224
|
+
…where the schema lives in the skill's own `config.yaml` (`skills/nano-banana/config.yaml`).
|
|
230
225
|
|
|
231
226
|
With `export NANO_BANANA_KEY=sk-xxx` in the env, the agent's system prompt gets the real key on start.
|
|
232
227
|
|
|
233
228
|
## Model registry — adding a new provider
|
|
234
229
|
|
|
235
|
-
Halo ships
|
|
236
|
-
|
|
237
|
-
### 1. Declare the provider
|
|
238
|
-
|
|
239
|
-
Create `~/.halo/global/models/<providerId>.yaml`:
|
|
240
|
-
|
|
241
|
-
```yaml
|
|
242
|
-
id: openai # required; matches the filename
|
|
243
|
-
displayName: OpenAI
|
|
244
|
-
description: OpenAI Chat Completions
|
|
245
|
-
models:
|
|
246
|
-
- id: gpt-4o
|
|
247
|
-
displayName: GPT-4o
|
|
248
|
-
maxOutputTokens: 16384
|
|
249
|
-
capabilities:
|
|
250
|
-
promptCaching:
|
|
251
|
-
ttlPresets:
|
|
252
|
-
- { value: 5m, label: 5min }
|
|
253
|
-
# No thinking block → UI automatically hides the Thinking button
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### 2. Implement the runtime
|
|
257
|
-
|
|
258
|
-
Add a case in the switch inside [packages/server/src/agents/model-runtime.ts](../../../packages/server/src/agents/model-runtime.ts) returning a class that implements `ModelRuntime` (`messages` + `run()`). Interface detail in [design/agent.md](../design/agent.md#agent-instance).
|
|
259
|
-
|
|
260
|
-
### Usage
|
|
261
|
-
|
|
262
|
-
Set `model.provider: openai` in your agent.yaml; the Provider dropdown in Form view lists it automatically.
|
|
263
|
-
|
|
264
|
-
### Current state
|
|
230
|
+
Halo ships 13 providers (`aws-bedrock-claude-invoke`, `aws-bedrock-mantle`, `aws-bedrock-openai`, `anthropic`, `openai`, `kimi`, `deepseek`, `minimax`, `qwen`, `hunyuan`, `doubao`, `zhipu`, `mimo-token-plan-china`); each one is a yaml under `~/.halo/global/models/` plus a matching case in [packages/server/src/agents/model-runtime.ts](../../../packages/server/src/agents/model-runtime.ts). For any OpenAI- or Anthropic-compatible endpoint you don't need new code: pick `openai` or `anthropic` as the provider and fill in the endpoint and model id in Form view.
|
|
265
231
|
|
|
266
|
-
|
|
232
|
+
A truly new provider takes a yaml manifest plus a runtime case; the step-by-step is in [dev/add-model-provider.md](../dev/add-model-provider.md). If you drop a provider yaml without a runtime case, session spawn fails with `Unknown provider "..."`.
|
|
@@ -28,7 +28,7 @@ This folder has one onboarding guide per channel. Pick the one you want and foll
|
|
|
28
28
|
SessionManager (per workspace)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Each channel handler is a thin adapter between its native protocol and halo's `SessionManager`. Slash commands (`/session` `/agent` `/skill` `/workspace` `/cron` `/acp` `/evo` `/help`) are shared across all of them — see `channels/shared/commands.ts`.
|
|
31
|
+
Each channel handler is a thin adapter between its native protocol and halo's `SessionManager`. Slash commands (`/session` `/agent` `/skill` `/workspace` `/cron` `/acp` `/evo` `/extension` `/help`) are shared across all of them — see `channels/shared/commands.ts`.
|
|
32
32
|
|
|
33
33
|
The ACP adapter is **not** a channel — it's a stdio bridge that translates ACP JSON-RPC into the web channel's HTTP+SSE. Counted here only because users go through the same "set up an account, get a token" flow.
|
|
34
34
|
|
|
@@ -42,16 +42,19 @@ What the admin UI actually asks you for. Auto-filled fields (botUsername, botUse
|
|
|
42
42
|
| Telegram | Bot token, workspace path | Label, access level, language, allowed users | BotFather `/newbot` |
|
|
43
43
|
| Slack | Bot token (`xoxb-`), App token (`xapp-`), workspace path | Label, access level, language | api.slack.com → Create App → install + Socket Mode |
|
|
44
44
|
| Feishu | App ID, App Secret, workspace path | Verification token, encrypt key, label, access level, language | open.feishu.cn → Create App + add bot capability |
|
|
45
|
-
| WeCom | Bot ID, Secret, workspace path | Label, access level, language
|
|
45
|
+
| WeCom | Bot ID, Secret, workspace path | Label, access level, language | work.weixin.qq.com → 智能机器人 → create bot, copy Bot ID + Secret |
|
|
46
46
|
| WeChat | Workspace path (set before scanning) | Label, access level, language | QR scan in admin UI; bot token comes back from the scan |
|
|
47
47
|
|
|
48
|
+
In the admin account list every row shows an access-level badge next to its name, and a red **(path does not exist)** after the workspace path when the bound folder is missing on disk (the account is skipped at startup until you re-bind it). Secret fields in the Add forms (Telegram bot token, Slack bot / app tokens, Feishu App Secret and Encrypt Key, WeCom Secret) are password inputs — pasted values are masked.
|
|
49
|
+
|
|
48
50
|
## Access level
|
|
49
51
|
|
|
50
52
|
Every channel account carries an `accessLevel`:
|
|
51
53
|
|
|
52
|
-
- `readonly` (default) — sandboxed, workspace mounted read-only, secrets dir not mounted
|
|
54
|
+
- `readonly` (default) — sandboxed, workspace mounted read-only, secrets dir not mounted. With an OS sandbox the agent gets all its tools (writes fail); without one only the read-only tools (`file_read`, `view_image`, `file_list`, `grep`, `glob`)
|
|
53
55
|
- `workspace` — sandboxed, workspace mounted read-write, secrets dir not mounted
|
|
54
|
-
- `full` — no sandbox, all tools, can `/workspace <path>` to another workspace
|
|
56
|
+
- `full` — no sandbox, all tools, can `/workspace switch <path>` to another workspace
|
|
57
|
+
- `observer` — Web accounts only: global read-only, meant for dashboards / metrics (Halo City, `/api/metrics`); chat runs at `readonly`. A chat-channel account that still holds `observer` from older data shows an Observer badge, and its edit form keeps that level unless you pick another one
|
|
55
58
|
|
|
56
59
|
When in doubt start at `readonly` and raise it later. The setting is on the **account row**, so a single Slack workspace can have one `readonly` bot account in #general and a separate `full` account in #ops.
|
|
57
60
|
|
|
@@ -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
|
|
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
|
|
87
|
-
| Encrypt
|
|
88
|
-
|
|
|
89
|
-
|
|
|
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 (
|
|
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
|
|
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
|
|
149
|
-
| `99991668` on Add
|
|
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.
|
|
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
|
|
86
|
+
Open halo admin → **Channels** → **Slack** → **Add Bot**:
|
|
87
87
|
|
|
88
88
|
| Field | Value |
|
|
89
89
|
|---|---|
|
|
90
|
-
| Bot
|
|
91
|
-
| App
|
|
92
|
-
|
|
|
93
|
-
|
|
|
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 (
|
|
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
|
|
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
|
|
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.
|
|
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
|
|