@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
@@ -104,7 +104,7 @@ Extra rules:
104
104
  - `view_image` also needs a vision-capable model.
105
105
  - A skill with `requiresAccess` above the session level is hidden (no metadata, no `activate_skill`).
106
106
  - Relay tools are only built for `full` sessions.
107
- - Hidden from `workspace` / `readonly` sessions: the `general.sandbox.hidden_dirs` / `hidden_files` lists (default: `~/.halo/secrets`, `~/.aws`, `~/.ssh`, `~/.gnupg`, `~/.docker`, `~/.config/gh`, `~/.gitconfig`, `~/.git-credentials`, `~/.npmrc`, `~/.netrc`, the global evo/cron/runs databases, internal-session transcripts, logs, …) plus the workspace's own `.halo/sessions`, `.halo/logs`, `.halo/evo`, `.halo/halo.db*` (fixed in code). The rest of `.halo/` stays readable.
107
+ - Hidden from `workspace` / `readonly` sessions only (Full sessions, the admin file explorer and the terminal see everything): a built-in list that is always included (`~/.halo/secrets`, `~/.aws`, `~/.ssh`, `~/.gnupg`, `~/.docker`, `~/.config/gh`, `~/.gitconfig`, `~/.git-credentials`, `~/.npmrc`, `~/.netrc`, the global evo/cron/runs databases, internal-session transcripts, logs, …), any extra paths added in `general.sandbox.hidden_dirs` / `hidden_files` (they add to the built-in list, never replace it), plus the workspace's own `.halo/sessions`, `.halo/logs`, `.halo/evo`, `.halo/halo.db*` (fixed in code). The rest of `.halo/` stays readable.
108
108
 
109
109
  ### Where a session's level comes from
110
110
 
@@ -12,7 +12,7 @@ Open `http://localhost:9527` (or your deployment URL) in a browser and enter the
12
12
 
13
13
  ## Open a workspace
14
14
 
15
- After login, you land in the workspace. A URL with `?folder=/abs/path` opens that directory directly; without `?folder`, Halo falls back to your home directory as the workspace — everything still works.
15
+ After login, you land in the workspace. A URL with `?folder=/abs/path` opens that directory directly; without `?folder`, Halo reopens the last workspace you used, or your home directory if there isn't one — everything still works.
16
16
 
17
17
  The Explorer top bar has two ways to switch workspace:
18
18
  - **Type an absolute path** + Enter — invalid paths raise an error
@@ -22,7 +22,7 @@ Halo auto-creates a `.halo/` subdirectory in the target folder (for sessions, ag
22
22
 
23
23
  ## First conversation (bootstrap)
24
24
 
25
- The first time a new user chats with the Default agent, the agent asks a few questions:
25
+ The first time a new user chats with the Default agent, it works in a few getting-to-know-you questions (if you open with a real task, it does the task first and asks over the next turns):
26
26
  - What should I call you?
27
27
  - What would you like to call me?
28
28
  - Preferred communication style (formal/casual, English/other language, etc.)
@@ -79,7 +79,7 @@ After `halo setup`:
79
79
 
80
80
  **When does "refreshed on upgrade" actually run?** `halo setup` always re-runs the seed; the server's startup check also re-runs it automatically when `~/.halo/global/.template-version` is behind the bundled `TEMPLATE_VERSION`. So the routine flow `halo upgrade && halo server restart` is enough — no need to remember `halo setup`.
81
81
 
82
- **Server-refreshed**: `INSTRUCTIONS.md`, `prompts/`, `models/`, `docs/`, the built-in agent ids (`default`, `executor`, `deep-executor`, `__evo_agent__`, `__score__`, `__apply_agent__`), and the built-in skill ids (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `nova-web-search`, `halo`). To customize one, copy it into the workspace scope (`<project>/.halo/...`) — workspace replaces global at runtime.
82
+ **Server-refreshed**: `INSTRUCTIONS.md`, `prompts/`, `models/`, `docs/`, the built-in agent ids (`default`, `executor`, `deep-executor`, `goal`, `__evo_agent__`, `__score__`, `__apply_agent__`), and the built-in skill ids (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `web-search`, `halo`, `extension`). To customize one, copy it into the workspace scope (`<project>/.halo/...`) — workspace replaces global at runtime.
83
83
 
84
84
  **Never overwritten**: anything else under `agents/` or `skills/` (your own creations), and everything under `secrets/`.
85
85
 
@@ -35,7 +35,7 @@ Goal: a skill called `caption-image` that calls `api.example.com` with an API ke
35
35
  Beside `SKILL.md`:
36
36
 
37
37
  ```yaml
38
- # extensions/skills/caption-image/config.yaml
38
+ # skills/caption-image/config.yaml
39
39
  params:
40
40
  - key: api_key
41
41
  description: API key for api.example.com
@@ -107,7 +107,7 @@ env vars (process.env.X) > workspace settings.yaml > global settings.yaml > sche
107
107
 
108
108
  If `process.env.EXAMPLE_KEY` is unset and the value at `caption-image.params.api_key` is `<<EXAMPLE_KEY>>`:
109
109
  - The agent sees the literal `<<EXAMPLE_KEY>>` in the rendered skill body
110
- - The server logs `[md-vars] Env var "EXAMPLE_KEY" not set — keeping <<EXAMPLE_KEY>> literal`
110
+ - The server logs `[MdVars] Env var "EXAMPLE_KEY" not set — keeping <<EXAMPLE_KEY>> literal`
111
111
  - API calls the agent makes will fail with a clear error (usually HTTP 401) and the error message will reveal the literal, so the user can tell what to fix
112
112
 
113
113
  This is intentional: empty strings hide the misconfiguration; literals make it visible.
@@ -155,7 +155,7 @@ Free-form, but consistency helps. Common patterns in Halo:
155
155
  | Env var | Purpose |
156
156
  |---|---|
157
157
  | `HALO_PASSWORD` | Server login password |
158
- | `HALO_MODEL_ID` / `HALO_MODEL_PROVIDER` / `HALO_MAX_CONTEXT_TOKENS` | Server defaults |
158
+ | `HALO_MAX_CONTEXT_TOKENS` | Server default context window (272000) |
159
159
  | `AWS_REGION` / `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | AWS / Bedrock |
160
160
 
161
161
  For your own skills, `UPPER_SNAKE` prefixed by the product is idiomatic (`NANO_BANANA_KEY`, `EXAMPLE_API_TOKEN`). The placeholder is case-sensitive — `<<example_key>>` won't match `EXAMPLE_KEY`.
@@ -24,7 +24,7 @@ The main agent (the one you talk to directly) sits at the top; any sub-agents it
24
24
 
25
25
  - Collapse/expand: click the arrow
26
26
  - Count badge `+3`: 3 descendants
27
- - Red ⏹ icon: session was stopped (stopped_at is set)
27
+ - ⏹ icon: session was stopped (stopped_at is set)
28
28
 
29
29
  ## Message viewer
30
30
 
@@ -48,7 +48,7 @@ Top-right Bug icon toggles it. When on you also see:
48
48
 
49
49
  ## System prompt viewer
50
50
 
51
- In Debug mode the top-right gets an extra `Prompt` button; click to expand the full system prompt (AGENT.md + INSTRUCTIONS.md + INDEX.md + TOOL_GUIDELINES + skill metadata).
51
+ Next to the Debug toggle, a `Prompt` button appears once the session has a recorded system prompt; click to expand the full system prompt (AGENT.md + INSTRUCTIONS.md + INDEX.md + TOOL_GUIDELINES + skill metadata).
52
52
 
53
53
  Use it to see exactly what context the agent was given for a given turn.
54
54
 
@@ -71,7 +71,7 @@ Refreshing the browser or a network blip:
71
71
 
72
72
  ## Deleting a session
73
73
 
74
- Click a session's trash icon (Sessions page) or ✕ on its chat tab, then confirm:
74
+ Hover a session and click its trash icon (Sessions page or a chat tab), then confirm:
75
75
  - Cascade delete in SQLite (parent + all descendants)
76
76
  - `.halo/sessions/{agentId}/{sid}.json` removed
77
77
  - Sidebar entry removed
@@ -30,7 +30,7 @@ Putting those in AGENT.md would bloat the system prompt. Skills use **progressiv
30
30
 
31
31
  Workspace overrides global (same id).
32
32
 
33
- **Built-in skills** (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `nova-web-search`) are server-shipped and **force-overwritten on every server startup**. Local edits to these directories under `~/.halo/global/skills/` will be lost. To customize one, copy it into `<project>/.halo/skills/<id>/` (workspace replaces global) and edit there. Other skills under `~/.halo/global/skills/` — including any the user created via the admin UI — are untouched by the seeder.
33
+ **Built-in skills** (`agent`, `skill`, `workspace`, `cron`, `acp`, `send-file`, `self`, `aws-knowledge`, `web-search`, `halo`, `extension`) are server-shipped and **force-overwritten on every server startup**. Local edits to these directories under `~/.halo/global/skills/` will be lost. To customize one, copy it into `<project>/.halo/skills/<id>/` (workspace replaces global) and edit there. Other skills under `~/.halo/global/skills/` — including any the user created via the admin UI — are untouched by the seeder.
34
34
 
35
35
  ## SKILL.md format
36
36
 
@@ -63,7 +63,7 @@ Follow the checklist in checklist.md...
63
63
 
64
64
  ## Open the Skills panel
65
65
 
66
- Left-side Activity Bar → `🪄 Skills` icon.
66
+ Left-side Activity Bar → `⚡ Skills` icon.
67
67
 
68
68
  Click any skill and the right pane becomes a mini workspace:
69
69
  - File tree (every file in the skill directory) on the left
@@ -73,9 +73,9 @@ You can edit SKILL.md, add scripts, templates, example docs.
73
73
 
74
74
  ## Create / delete
75
75
 
76
- `+` button on the sidebar — fill in name / description / scope.
76
+ Click the `+` on the **Global** or **Workspace** group header in the sidebar (the group you click decides the scope) and type a name.
77
77
 
78
- Delete: right-click → Delete.
78
+ Delete: hover a skill and click its trash icon (after a confirmation). The toggle next to it disables / enables the skill.
79
79
 
80
80
  ## Letting an agent use a skill
81
81
 
@@ -138,7 +138,7 @@ params:
138
138
  secrets: [] # rare for skills — server-side only, never substituted into shell_exec
139
139
  ```
140
140
 
141
- The Settings page reads this declaration and renders inputs grouped under the skill's name. The user's value lands in `~/.halo/secrets/settings.yaml` at `<skill-id>.params.api_key`.
141
+ The Settings page reads this declaration and renders inputs grouped under the skill's name. The user's value lands in `~/.halo/secrets/settings.yaml` at `<skill-id>.params.api_key`. Among built-ins, `extension` declares a plain `hub_repo` param and `web-search` plain `fast_region` / `deep_region` params; `halo setup` asks only for built-in skills' `secret: true` params, so plain ones are set in Settings → Skills.
142
142
 
143
143
  > Credentials **do not** belong directly in `settings.yaml` — use `<<ENV_NAME>>` placeholders with the real value in env vars, or type them once into the Settings page (which still encrypts in the UI but stores plaintext on disk). Don't write credentials in SKILL.md either. SKILL.md + the schema in `config.yaml` are meant to be shareable; values are the user's.
144
144
 
@@ -158,7 +158,9 @@ Halo seeds these skills on every startup (the ids in `BUILTIN_SKILL_IDS`, `packa
158
158
  | send-file | Deliver an image/video/file as a channel attachment by emitting `MEDIA:<absolute_path>` — works on Web / WeChat / Telegram / Slack / Feishu (no command; model-activated, workspace access) |
159
159
  | self | The agent's own visual space (`.halo/canvas/self.html`) for self-expression (no command; model-activated, full access) |
160
160
  | aws-knowledge | Query the official AWS Knowledge MCP server for up-to-date AWS docs (no command, `user-invocable: false`; model auto-activates) |
161
- | nova-web-search | Real-time web search via Amazon Nova 2 Lite's nova_grounding (no command, `user-invocable: false`; model auto-activates) |
161
+ | web-search | Real-time web search on AWS Bedrock (needs the machine's AWS credentials with Bedrock access) in two gears: fast (Amazon Nova grounding, default; US regions only) and deep (`--deep`, GPT-5.6 `web_search` via Bedrock Mantle, slower and token-expensive); regions set via its `fast_region` / `deep_region` params (Settings → Skills) (no command, `user-invocable: false`; model auto-activates) |
162
+ | halo | Platform self-knowledge — config directories, MD scopes, which bundled doc answers what (no command, `user-invocable: false`; model auto-activates) |
163
+ | extension | Install / list / remove canvas preview extensions — backs `/extension` (full access); its `hub_repo` param (Settings → Skills) picks the hub `install <id>` pulls from (default turmind/halo-hub) |
162
164
 
163
165
  All live under `~/.halo/global/skills/` and are force-overwritten on startup. To customize one, copy it into `<project>/.halo/skills/<id>/` and edit there (workspace overrides global). `tavily-web-search` remains an optional (non-seeded) skill.
164
166
 
@@ -278,7 +280,7 @@ nano-banana:
278
280
 
279
281
  1. **Merge order**: workspace `<project>/.halo/settings.yaml` deep-merges over global `~/.halo/secrets/settings.yaml` (field-level merge, not block replacement)
280
282
  2. **Env vars**: `<<ENV_NAME>>` inside a value is replaced with `process.env.ENV_NAME` at render time. **If the env var is missing, the `<<ENV_NAME>>` literal is kept** so agents can see what's unset and the user can fix it
281
- 3. **Unknown placeholders** stay as-is (e.g. `{{foo}}`, `{{nano-banana.params.missing-key}}`); the server logs a `[md-vars] Unknown placeholder "…"` warning
283
+ 3. **Unknown placeholders** stay as-is (e.g. `{{foo}}`, `{{nano-banana.params.missing-key}}`); the server logs a `[MdVars]` line saying it left the placeholder as-is
282
284
  4. **Key charset**: `[\w-]`, hyphens allowed (e.g. `nano-banana`)
283
285
 
284
286
  ### Why put credentials in env vars
@@ -6,7 +6,7 @@ How to verify an agent you just created behaves right, and how to exercise a ski
6
6
 
7
7
  ### 1. Open the agent
8
8
 
9
- Activity Bar → `👥 Agents` → click the agent you want to test.
9
+ Activity Bar → `🤖 Agents` → click the agent you want to test.
10
10
 
11
11
  ### 2. Click Test
12
12
 
@@ -32,21 +32,20 @@ The Explorer's Chat panel is now ready with your agent pre-selected. Send a mess
32
32
 
33
33
  - **Does it greet the way AGENT.md says?** Send "Hi". Check the reply matches the personality you wrote.
34
34
  - **Does it call the right tools?** If the agent is supposed to read files, ask it to. Watch the inline tool-call card — it shows the tool name, arguments, and (expandable) output.
35
- - **Is the system prompt what you expect?** Open the Sessions panel (Activity Bar → `🕘 Sessions`), select this session, toggle **Debug mode**, click the **Prompt** button on the first message. Full rendered system prompt (AGENT.md + USER.md + INSTRUCTIONS.md + skill metadata + tool list).
35
+ - **Is the system prompt what you expect?** Open the Sessions panel (Activity Bar → `📨 Sessions`), select this session, toggle **Debug mode**, click the **Prompt** button in the viewer header. Full rendered system prompt (AGENT.md + USER.md + INSTRUCTIONS.md + skill metadata + tool list).
36
36
  - **Is the model correct?** Debug mode's usage badges show `model` on every assistant turn.
37
37
 
38
38
  ### 5. Iterate
39
39
 
40
- Edits take effect on the **next** session, not retroactively:
40
+ Edits are never retroactive — the turn that is running finishes on the old config — but a session is rebuilt from disk after every run, so the **next turn** of an existing session already picks them up:
41
41
 
42
42
  | What you changed | When it takes effect |
43
43
  |---|---|
44
- | `AGENT.md` body | Next `/session new` session |
45
- | `agent.yaml` tools / skills / model | Next session spawn |
46
- | `settings.yaml` values | Next session spawn (or next `activate_skill` call for SKILL bodies) |
47
- | Env vars (`<<ENV>>` placeholders) | Restart server, then next session spawn |
44
+ | `AGENT.md` body, `agent.yaml` tools / skills / model | Next turn (and any new session) |
45
+ | `settings.yaml` values | Next turn (or next `activate_skill` call for SKILL bodies) |
46
+ | Env vars (`<<ENV>>` placeholders) | Restart server, then next turn |
48
47
 
49
- So the loop is: edit → `/session new` → re-test. No server restart for MD/YAML/settings changes.
48
+ So the loop is: edit → send another message (or `/session new` for a clean slate) → re-test. No server restart for MD/YAML/settings changes. Full table in [delegation-and-access.md](delegation-and-access.md#2-when-a-config-edit-takes-effect).
50
49
 
51
50
  ---
52
51
 
@@ -56,7 +55,7 @@ There is **no separate "test this skill" button**. Skills exist to be called by
56
55
 
57
56
  ### 1. Attach the skill to an agent
58
57
 
59
- In the agent's Form view, the **Skills** section lists every skill in the workspace. Check the one you want to test. Save.
58
+ In the agent's Form view, the **Skills** section lists every skill in the workspace. Check the one you want to test (the form auto-saves).
60
59
 
61
60
  Equivalent YAML edit:
62
61
 
@@ -114,22 +113,22 @@ The env var is unset. Halo renders the literal placeholder so you (and the agent
114
113
 
115
114
  ### Saved agent changes don't seem to apply
116
115
 
117
- You might be looking at a session that started *before* the edit. Every session caches its own assembled system prompt at spawn time. `/session new` to start fresh, or switch to a different session and back.
116
+ You might be looking at a turn that was already running when you saved — it finishes on the old config, and the edit applies from the next turn. If the next turn still behaves the old way, `/session new` to start fresh.
118
117
 
119
118
  ### Deleting a test session
120
119
 
121
- Sessions → select → context menu → Delete. Or programmatically: `DELETE /api/sessions/logs/:id`. Deletion cascades to child sessions (sub-agents), their JSON files, and the SQLite rows. See [dev/api.md#session-logs](../dev/api.md#session-logs-unified).
120
+ Sessions → hover the session → trash icon. Or programmatically: `DELETE /api/sessions/logs/:id`. Deletion cascades to child sessions (sub-agents), their JSON files, and the SQLite rows. See [dev/api.md#session-logs](../dev/api.md#session-logs-unified).
122
121
 
123
122
  ### "Test button does something weird" — the session is orphaned
124
123
 
125
- Since 1.5.3-alpha, Test no longer clears any stored session: it only opens a new draft tab (the old `localStorage.halo_session_<projectId>` key is gone). The new session is created by the first message you send from that tab. If you see an old conversation instead of an empty one, the draft tab isn't the one on screen. Click **+** at the bottom of the session tabs to bring it back (an unused draft is reused).
124
+ Since 1.5.3-alpha, Test no longer clears any stored session: it only opens a new draft tab (the old `localStorage.halo_session_<projectId>` key is gone). The new session is created by the first message you send from that tab. If you see an old conversation instead of an empty one, the draft tab isn't the one on screen. Click **+** below the session tabs to bring it back (an unused draft is reused).
126
125
 
127
126
  ---
128
127
 
129
128
  ## References
130
129
 
131
130
  - Test button implementation: `testAgent` in [packages/admin/src/features/agents/agent-management-main.tsx](../../../packages/admin/src/features/agents/agent-management-main.tsx)
132
- - `activate_skill` runtime: [packages/server/src/agents/agent-loader.ts:111-159](../../../packages/server/src/agents/agent-loader.ts#L111-L159) (function `createSkillTool`)
133
- - Skill metadata → system prompt: [packages/server/src/agents/agent-loader.ts:97-102](../../../packages/server/src/agents/agent-loader.ts#L97-L102) (function `buildSkillPrompt`)
134
- - Chat session creation on first message: [packages/server/src/ws/handler.ts:263-344](../../../packages/server/src/ws/handler.ts#L263-L344) (function `handleChat`)
131
+ - `activate_skill` runtime: [packages/server/src/agents/agent-loader.ts](../../../packages/server/src/agents/agent-loader.ts) (function `createSkillTool`)
132
+ - Skill metadata → system prompt: [packages/server/src/agents/agent-loader.ts](../../../packages/server/src/agents/agent-loader.ts) (function `buildSkillPrompt`)
133
+ - Chat session creation on first message: [packages/server/src/ws/handler.ts](../../../packages/server/src/ws/handler.ts) (function `handleChat`)
135
134
  - End-to-end test scenarios for the session system: [test/session.md](../test/session.md)
@@ -21,17 +21,23 @@ Overall interface layout plus the three sidebar features.
21
21
  - **Sidebar** is resizable
22
22
  - **Bottom Panel** is resizable; the header has a Chat / Terminal switch
23
23
 
24
- The Skills tab is an exception — it takes the full right side, no Bottom Panel.
24
+ The docked Bottom Panel belongs to the Explorer tab; every other tab fills the whole right side (a floating panel stays visible on any tab).
25
25
 
26
26
  ## Activity Bar tabs
27
27
 
28
28
  | Icon | Tab | Purpose |
29
29
  |---|---|---|
30
30
  | 📄 Files | Explorer | File tree + Monaco editor |
31
+ | 🌿 Branch | Source Control | Changes, commit, push, history (hidden for non-git workspaces) |
31
32
  | 📨 Messages | Sessions | Session history + debug viewer |
32
- | 🪄 Wand | Skills | Skill editing (mini workspace) |
33
- | 👥 Users | Agents | Agent configuration (Form/YAML/MD) |
34
- | ⚙️ Gear | Settings | Settings editor (Form/YAML) |
33
+ | ⚡ Zap | Skills | Skill editing (mini workspace) |
34
+ | 🤖 Bot | Agents | Agent configuration (Form / Edit) |
35
+ | 💬 Chat bubble | Channels | Web / Telegram / Slack / Feishu / WeCom / WeChat accounts |
36
+ | ✨ Sparkles | Evolution | Self-evolution runs and review |
37
+ | 🕐 Clock | Cron | Scheduled agent runs |
38
+ | 🎚️ Sliders | Settings | Settings form (bottom of the bar) |
39
+
40
+ Below Settings sits the **quick toggles** gear. The thin bar under it has one segment per item: network (green = connected, amber = probing, red = disconnected; the gear turns red too), finish notification, and in the desktop app also pin-on-top and keep-screen-awake (amber when on). Hover for a summary. Click to open a panel where you switch them; it stays open until you click outside, press Esc, or click the gear again.
35
41
 
36
42
  ## Switching workspace
37
43
 
@@ -44,29 +50,30 @@ The Explorer top bar has a path input + 📁🔍 picker button:
44
50
  - Single click to select / double click to enter
45
51
  - Open button to confirm
46
52
 
47
- The URL carries `?folder=/abs/path`, persisting the workspace across refreshes. When `folder` is absent, Halo falls back to the home directory.
53
+ The URL carries `?folder=/abs/path`, persisting the workspace across refreshes. When `folder` is absent, Halo reopens the last workspace you used, or the home directory if there isn't one.
48
54
 
49
55
  ## Explorer (file tree)
50
56
 
51
57
  VS Code style:
52
- - Click: select
53
- - Double click: open in a new Tab
58
+ - Click a file: select, then open it after a short delay (300 ms)
59
+ - Double click: open the file right away; on a folder, expand / collapse it
60
+ - Click a folder: expand / collapse
54
61
  - Ctrl/Cmd + click: toggle multi-select
55
62
  - Shift + click: range select
56
63
  - Drag: move files/folders (multi-select drag supported)
57
64
 
58
- Right-click menu: New File / New Folder / Rename / Delete / Download.
65
+ Right-click menu: New File / New Folder / Open in Integrated Terminal / Reveal in File Manager (desktop app only) / Open to the Side / Download / Rename / Delete / Open as Workspace (folders).
59
66
 
60
67
  **Quick Open**: `Cmd/Ctrl+P` opens fuzzy file search — server-side, full-project, independent of expansion state.
61
68
 
62
69
  ### Skipped directories
63
- `node_modules`, `__pycache__`, `.halo` (knowledge dir, not shown in the tree; use `file_read` directly).
70
+ `.git`, `.DS_Store`, `node_modules`, `__pycache__`.
64
71
 
65
72
  ## Editor
66
73
 
67
74
  Monaco, multi-tab:
68
75
  - Drag tabs to reorder
69
- - Unsaved tabs show a red dot
76
+ - Unsaved tabs show an amber dot
70
77
  - Closing an unsaved tab prompts for confirmation
71
78
  - **Auto-refresh**: detects agent-written file changes by mtime and updates contents
72
79
  - **Tab persistence**: stored in localStorage, survives refreshes
@@ -78,11 +85,11 @@ Special-file previews:
78
85
 
79
86
  ### Preview extensions
80
87
 
81
- File types the editor has no built-in viewer for (e.g. `.glb` 3D models) show a "no built-in preview" page with a link to [halo-hub](https://github.com/turmind/halo-hub), where ready-made viewer extensions live. Extensions are installed **server-wide** (`~/.halo/global/extensions/<id>/`) and take effect in every open tab immediately — no restart, no page reload.
88
+ File types the editor has no built-in viewer for (e.g. `.glb` 3D models) show a "no built-in preview" page with a link to [halo-hub](https://github.com/turmind/halo-hub), where ready-made viewer extensions live: 3D models (`glb` — `.glb` / `.gltf` / `.obj` / `.stl`), draw.io diagrams (`drawio`, editable), Excalidraw whiteboards (`excalidraw`, editable) and Jupyter notebooks (`ipynb`, read-only). Extensions are installed **server-wide** (`~/.halo/global/extensions/<id>/`) and take effect in every open tab immediately — no restart, no page reload.
82
89
 
83
90
  Three ways to install:
84
91
  - **Settings → Extensions**: upload the extension `.zip`; the same panel lists what's installed (version, file types) and removes extensions
85
- - **Ask the agent**: `/extension install glb` pulls the latest `glb-v*` release from halo-hub; `/extension install /path/to/x.zip` or an `https://…zip` URL also work. `/extension list` and `/extension remove <id>` round it out (full-access sessions only)
92
+ - **Ask the agent**: `/extension install glb` pulls the latest `glb-v*` release from the configured hub — halo-hub unless you change **Settings → Skills → extension → `hub_repo`** (a GitHub / Gitea / Forgejo / GitLab repo URL, `owner/repo` for GitHub, or any git URL / local repo, which installs from `glb-v<x.y.z>` tags); `/extension install /path/to/x.zip` or an `https://…zip` URL also work. `/extension list` and `/extension remove <id>` round it out (full-access sessions only)
86
93
  - **By hand**: drop an unpacked extension directory into `~/.halo/global/extensions/` — the server watches that directory
87
94
 
88
95
  Once installed, matching files open in the extension's viewer. If more than one viewer can show a file, the preview header gets an **Open with** menu (per tab, not remembered). Extensions that declare the `save` capability can edit the file: the tab shows the usual dirty dot, and the header **Save** button writes it back (Ctrl/Cmd+S doesn't apply to extension tabs). If the file changed on disk in between, you're asked before overwriting.
@@ -91,13 +98,13 @@ Once installed, matching files open in the extension's viewer. If more than one
91
98
 
92
99
  Bottom-panel Terminal tab:
93
100
  - xterm.js frontend + node-pty backend
94
- - Multi-instance (each tab owns its own PTY)
101
+ - Multi-instance (each terminal owns its own PTY), listed as a collapsible vertical tab strip on the right
95
102
  - cwd = current project root
96
- - Reconnect grace: 60 s
103
+ - Reconnect grace: 5 min (`timeout.terminal_grace`)
97
104
 
98
105
  ## Settings
99
106
 
100
- `⚙️ Settings` tab. Form / YAML split view:
107
+ `⚙️ Settings` tab. Form view:
101
108
  - Auto-generated controls (text / number / password / toggle) per declared field
102
109
  - Each row shows source (`workspace` / `inherited from global` / `unset`) + Reset
103
110
 
@@ -105,7 +112,10 @@ Scope: Global / Workspace toggle. Same key: workspace overrides global, leaf by
105
112
 
106
113
  Sections are grouped by declarer:
107
114
  - **System** — server-built-in knobs (session limits, compaction, sandbox, logging)
115
+ - **Security** — change password, log out
116
+ - **Extensions** — installed canvas preview extensions
108
117
  - **Model Providers** — secrets declared by each `models/<id>.yaml` (AWS, Kimi, DeepSeek, …)
118
+ - **Agents** — params/secrets declared by each global agent's `agent-config.yaml`
109
119
  - **Skills** — params/secrets declared by each `skills/<id>/config.yaml`
110
120
  - **Orphans** — values in settings.yaml whose namespace isn't currently declared (uninstalled skill leftovers); manual cleanup only
111
121
 
@@ -127,11 +137,13 @@ See [requirements/settings.md](../requirements/settings.md), [secrets-and-creden
127
137
  |---|---|
128
138
  | `Cmd/Ctrl + P` | Quick Open |
129
139
  | `Cmd/Ctrl + S` | Save file |
130
- | `Cmd/Ctrl + W` | Close tab |
140
+ | `Alt + W` | Close editor tab (browser; the desktop app uses `Cmd/Ctrl + W`) |
141
+ | `Cmd/Ctrl + \`` | Switch the bottom panel between Chat and Terminal |
142
+ | `Esc` | Leave editor maximize |
131
143
 
132
144
  ## Bottom panel switching
133
145
 
134
- Two tabs in the header: Chat / Terminal. Current tab lives in `editorStore.bottomTab` and survives refresh.
146
+ Two tabs in the header: Chat / Terminal, plus maximize and float / dock buttons. The panel opens on Chat after a refresh.
135
147
 
136
148
  ## Login / logout
137
149
 
@@ -1,10 +1,10 @@
1
1
  # Agent — Requirements
2
2
 
3
- Agent configuration management: creating, editing YAML / Form / AGENT.md.
3
+ Agent configuration management: creating, editing via Form / mini workspace (agent.yaml, AGENT.md).
4
4
 
5
5
  ## Layout
6
6
 
7
- Left sidebar has two collapsible groups (Global / Workspace); right side is the editor:
7
+ Left sidebar has two collapsible groups (Global / Workspace — the latter only while a workspace is open); right side is the editor. Each group header has a `+` to create an agent in that scope, and the sidebar header has a refresh button:
8
8
 
9
9
  ```
10
10
  ┌─────────────────┬───────────────────────────────┐
@@ -20,22 +20,22 @@ Left sidebar has two collapsible groups (Global / Workspace); right side is the
20
20
 
21
21
  ## Core behaviour
22
22
 
23
- ### YAML / Form dual view
24
- - **YAML view**: Monaco-edit the raw `agent.yaml`
25
- - **Form view**: data-driven form derived from YAML fields (name / description / model / tools / skills / thinking)
26
- - Switching views preserves edits
23
+ ### Form / Edit dual view
24
+ - **Form view** (default): data-driven form derived from the YAML fields (name / description / priority / model incl. thinking and capabilities / system_prompt / team / tools / skills). Edits auto-save to `agent.yaml` (debounced 500 ms). If `agent.yaml` fails to load or parse, the form is replaced by an error with a Retry button and nothing is auto-saved (an unparsable file is fixed in Edit view); below the form, a read-only preview of `AGENT.md` is shown when it has content
25
+ - **Edit view**: the **Edit** button opens a mini workspace (file tree + editor) rooted at the agent's folder, for editing `agent.yaml` / `AGENT.md` directly; **Back** returns to the form, which reloads from disk
26
+ - The chosen view is remembered per agent in `localStorage`
27
27
 
28
28
  ### CRUD
29
29
  | Operation | API |
30
30
  |---|---|
31
31
  | List | `GET /api/agent-configs?projectId=xxx` |
32
32
  | Create | `POST /api/agent-configs` (body: `{name, description, scope, projectId?}`) |
33
- | Read YAML | `GET /api/agent-configs/:id/yaml` |
34
- | Write YAML | `PUT /api/agent-configs/:id/yaml` |
35
- | Delete | `DELETE /api/agent-configs/:id` |
33
+ | Read YAML | `GET /api/agent-configs/:id/yaml` (`scope=workspace` requires `projectId`, else 400) |
34
+ | Write YAML | `PUT /api/agent-configs/:id/yaml` (same `projectId` rule) |
35
+ | Delete | `DELETE /api/agent-configs/:id` (same `projectId` rule — never falls back to deleting the same-named global agent) |
36
36
  | Toggle disabled | `PATCH /api/agent-configs/:id/toggle` → `{ ok, disabled }` |
37
37
 
38
- Creating requires name + description; the backend uses `defaultAgentYaml(name, description)` to produce a full YAML.
38
+ Creating requires a name (description is optional); the id is derived from the name. The backend uses `defaultAgentYaml(name, description)` to produce a full YAML and also scaffolds an `AGENT.md`.
39
39
 
40
40
  ### Scope (Global / Workspace)
41
41
  - **Global**: `~/.halo/global/agents/<id>/agent.yaml` — shared across projects
@@ -45,18 +45,18 @@ Same id present in both scopes: workspace wins; the overridden global is greyed
45
45
 
46
46
  **Cross-scope conflict**: creating a same-name agent in the other scope prompts about the overwrite behaviour.
47
47
 
48
- **Delete protection**: at least one global agent must remain (server-enforced).
48
+ **Delete protection**: at least one global agent must remain (server-enforced). The sidebar offers delete (hover trash) on workspace agents only; global rows show a crown instead.
49
49
 
50
50
  ### Disable / Enable
51
- - Toggle switch on each agent row in the admin sidebar. Disabled state is stored per workspace in the `disabled_items` table of `halo.db` (not in agent.yaml). Both global and workspace agents can be independently toggled per workspace.
51
+ - Toggle switch on each agent row in the admin sidebar (shown on hover while enabled; not offered for internal agents or when no workspace is open). Disabled state is stored per workspace in the `disabled_items` table of `halo.db` (not in agent.yaml). Both global and workspace agents can be independently toggled per workspace.
52
52
  - Disabled agents are greyed out (opacity-40) with sub-text "disabled"; the toggle stays visible.
53
53
  - Hidden from: the delegation roster, chat agent selector, `/workspace share` export.
54
54
  - Still visible in the admin management sidebar for re-enabling.
55
55
 
56
56
  ### Tool selection
57
57
  - **Session tools**: `start_session` / `session_list` / `query_session` / `interrupt_session` / `stop_session` / `archive_session` / `get_session_output` / `query_agent` — **not** selected by name; the whole bundle is granted automatically by a non-empty `team` (see below). Listing them under `agent.yaml tools` has no effect.
58
- - **Workspace tools**: `file_read` / `file_write` / `file_edit` / `file_list` / `shell_exec` / `grep` / `glob` / `web_fetch`, returned by `GET /api/agent-configs/tools`
59
- - **`activate_skill`**: auto-injected whenever the YAML lists `skills`; loads the full SKILL.md on demand
58
+ - **Workspace tools**: `file_read` / `view_image` / `file_write` / `file_edit` / `file_list` / `shell_exec` / `grep` / `glob` / `web_fetch`, plus the single `relay_send` chip (grants the whole cross-workspace relay set, full-access sessions only), returned by `GET /api/agent-configs/tools`. `view_image` is dropped at runtime when the model can't take images (the form dims it)
59
+ - **`activate_skill`**: auto-injected when the YAML lists at least one usable skill (not disabled, allowed at the session's access level); loads the full SKILL.md on demand
60
60
 
61
61
  ### Team (delegation switch + whitelist)
62
62
  - Optional `team: [id, …]` field in `agent.yaml`. A **non-empty** list is the on/off switch for delegation: it grants the whole session-tool bundle (`start_session` / `query_agent` / …) plus the prompt roster, AND scopes which agents this one may reach.
@@ -67,7 +67,7 @@ Same id present in both scopes: workspace wins; the overridden global is greyed
67
67
  - **Picker only offers effective, enabled agents.** A chip shows iff the *effective* agent for that id is runnable — same resolve-then-check the runtime uses: a workspace agent shadows the same-id global (the overridden global is skipped), then anything disabled is dropped. So a global stays out of the picker when its workspace override is disabled, even though that global's own record isn't flagged disabled. Matches the chat / cron selectors and the runtime roster — you can never pick a teammate that can't actually run.
68
68
 
69
69
  ### Skill selection
70
- `GET /api/skills?projectId=xxx` lists available skills; `agent.yaml`'s `skills` references them by id.
70
+ `GET /api/skills?projectId=xxx` lists available skills; `agent.yaml`'s `skills` references them by id. The form offers enabled skills only; ids that are referenced but not installed show as red ⚠ chips (click to remove).
71
71
 
72
72
  ### MD file editing
73
73
 
@@ -75,13 +75,14 @@ Same id present in both scopes: workspace wins; the overridden global is greyed
75
75
  |---|---|---|
76
76
  | AGENT.md | yes | Agent personality / behaviour (overrides YAML `system_prompt`) |
77
77
  | INSTRUCTIONS.md | yes | User preferences (global or workspace scope) |
78
+ | INDEX.md | no | Project index, read-only through this API |
78
79
 
79
- The MD editor has a **Global / Workspace** scope toggle (visible when a workspace is open).
80
+ The admin edits `AGENT.md` through the Edit mini workspace; `INSTRUCTIONS.md` is not edited from this tab.
80
81
 
81
- API: `GET/PUT /api/agent-configs/:id/md/:fileType`
82
+ API: `GET/PUT /api/agent-configs/:id/md/:fileType`, `GET /api/agent-configs/:id/md-all`
82
83
 
83
84
  ### Test button
84
- - Sets `selectedAgentId` in the chat store
85
+ - Opens a fresh chat draft tab (an untouched draft is reused) with this agent selected
85
86
  - Dispatches a `halo:navigate` event to switch to explorer/chat
86
87
  - The user chats with the selected agent in the main chat panel
87
88
  - **Not rendered for internal agents** (`internal: true`) — they're delegated to by other agents, never driven directly
@@ -5,10 +5,9 @@ The primary surface for talking to an agent.
5
5
  ## Core behaviour
6
6
 
7
7
  ### Agent selection
8
- - Bottom-left dropdown in the chat panel selects which agent to use
9
- - Lists every available agent (from `GET /api/agent-configs`; overridden and disabled agents are hidden)
10
- - Default agent has a "default" badge
11
- - **Locked during an active session** — the agent is bound to the session; to change, start a new session (/session new)
8
+ - Dropdown in the composer's left control cluster (after the Debug button) selects which agent to use; hidden when only one agent is usable
9
+ - Lists every available agent (from `GET /api/agent-configs`; overridden, disabled and internal agents are hidden), highest priority first. A new session starts on the top one
10
+ - **Locked during an active session** (and while a response is streaming) — the agent is bound to the session; to change, start a new session (/session new)
12
11
  - The Agents panel's "Test" button can also preselect an agent
13
12
 
14
13
  ### Access level
@@ -22,8 +21,8 @@ The primary surface for talking to an agent.
22
21
  ### Message rendering
23
22
  - Markdown + code-block highlighting
24
23
  - Tool-call card: expandable, shows tool name / input / output
25
- - Sub-agent messages carry the agent-name label (e.g. "Coder", "Researcher")
26
- - Streaming text has a cursor animation
24
+ - Sub-agent streams stay out of the chat: their reports land as a green callout, and Debug mode adds agent start/done markers labelled with the agent name (e.g. "Coder", "Researcher")
25
+ - A reply that has produced nothing yet shows a spinner with "Thinking..."
27
26
  - The user bubble shows its send time (`HH:mm`, browser-local) at the left of its header row, after the expand chevron (see [User-message actions](#user-message-actions-expand--copy--delete)); hovering it gives the full date. Same bubble component as the Sessions tab, so both surfaces get it
28
27
  - In Debug mode the system-prompt `context` row stays in the list but doesn't open an exchange of its own, so there is no blank strip above the first bubble
29
28
 
@@ -39,7 +38,7 @@ Actions on user-role turns: the blue sticky user bubble, plus the sub-agent-repo
39
38
  - An expanded body is capped at 40vh and scrolls inside the bubble, because the bubbles are sticky.
40
39
  - The compact-summary callout is collapsed by default and toggles from its own header, as before.
41
40
  - **Copy** — copies the prompt text to the clipboard
42
- - **Delete** (confirm dialog) — removes the whole exchange (the user turn + all responses up to the next user turn) with **two-layer semantics**: the LLM context (`rawMessages`) drops the turn physically — the model never sees it again, freeing context; the UI keeps the messages, rendered greyed-out with a "deleted" tag, as an audit trail. No undo; a deleted exchange loses its Delete button. Rejected with an error toast while the agent is running or compacting. Root sessions only (sub-session logs don't offer Delete). If the turn was already compacted out of raw context, only the UI marking happens (silent degrade). Once a session has **archived history** the action is refused for the whole session with a plain explanatory notice (not an `Error:` bubble) — per-turn positions can no longer be mapped once older turns left the active file. Design details in [design/session.md](../design/session.md#exchange-deletion-soft-ui--hard-raw), protocol in [design/ws.md](../design/ws.md).
41
+ - **Delete** (confirm dialog) — removes the whole exchange (the user turn + all responses up to the next user turn) with **two-layer semantics**: the LLM context (`rawMessages`) drops the turn physically — the model never sees it again, freeing context; the UI keeps the messages, rendered greyed-out with a "deleted" tag, as an audit trail. No undo; a deleted exchange loses its Delete button. Rejected with an `Error:` message in the chat while the agent is running or compacting. Root sessions only (sub-session logs don't offer Delete). If the turn was already compacted out of raw context, only the UI marking happens (silent degrade). If a compact archived history while the panel was open, the action is refused with a plain explanatory notice (not an `Error:` bubble) — the panel's turn positions no longer match the active file; reopening the session re-anchors it and Delete works again. Design details in [design/session.md](../design/session.md#exchange-deletion-soft-ui--hard-raw), protocol in [design/ws.md](../design/ws.md).
43
42
 
44
43
  ### Archived history (scroll up to load)
45
44
  Long sessions get their older exchanges moved out of the active log on compact, so opening a session stays fast no matter how long it has run. Loading older messages is **two-tiered** — a local, no-network tier first, then the network-backed archive:
@@ -49,19 +48,19 @@ Long sessions get their older exchanges moved out of the active log on compact,
49
48
  - Only once the local window is fully expanded does the next scroll-to-top (or the "Load earlier messages" row that takes over the same slot) reach for the **network** tier: it loads one archived segment per gesture, oldest-newer order preserved, and becomes "No earlier messages" once everything is loaded. The two tiers can't be skipped or reordered — the archive row only appears once there's nothing left to expand locally
50
49
  - Either tier preserves the reading position when older messages prepend — the view doesn't jump
51
50
  - Loaded archive segments sit in a **collapsed** block ("Archived · N segment(s) · M messages") — the user scrolled up for older context, not to have hundreds of exchanges re-flow the view; expanding renders them like normal messages, growing **upward** so the viewport stays pinned to the newest end, with the expand/collapse toggle below the revealed content
52
- - Archived exchanges are **read-only**: Copy still works, Delete is absent (the server refuses it for archived sessions)
51
+ - Archived exchanges are **read-only**: Copy still works, Delete is absent (they are no longer in the active log, so a turn position can't address them)
53
52
  - Segments already loaded aren't re-fetched; a failed load can simply be retried with the same row
54
53
 
55
54
  ### Session tabs (right side)
56
- The workspace's root sessions as **Chrome-style vertical tabs** on the right edge of the chat panel (`VerticalTabList` rows inside a `ResizableSidebar`, default 200px). The list **is** the tab list: every session is "open", clicking a row shows it, and there is no close — deleting the session is the only way a tab goes away. Since 1.5.3-alpha this replaces the top tab strip, the History and New-session buttons beside the composer, and the "N previous sessions" link in the empty state.
55
+ The workspace's root sessions as **Chrome-style vertical tabs** on the right edge of the chat panel (`VerticalTabRow` rows inside a `ResizableSidebar`, default 200px). The list **is** the tab list: every session is "open", clicking a row shows it, and there is no close — deleting the session is the only way a tab goes away. Since 1.5.3-alpha this replaces the top tab strip, the History and New-session buttons beside the composer, and the "N previous sessions" link in the empty state.
57
56
 
58
57
  - **Resize / collapse**: drag the left edge to resize (120–480px). Collapse from the header; the collapsed list is a `w-10` rail of square tabs, each showing the title's first letter plus the status dot in a corner, with a "+" at the bottom. Both settings are global preferences in `localStorage` (`halo_session_sidebar_open`, `halo_session_sidebar_width`); the list is open by default.
59
- - **Rows**: one line per row, 🎯 + title. Message count, time-ago and model are in the tooltip. `N msgs` counts the session's user turns **over its whole lifetime**, so compacting or archiving history never makes it go backwards.
58
+ - **Rows**: one line per row, status dot + title. Message count, time-ago and model are in the tooltip. `N msgs` counts the session's user turns **over its whole lifetime**, so compacting or archiving history never makes it go backwards.
60
59
  - Each row leads with one fixed-size status dot (fixed so a state change never reflows a narrow list — the spinner it replaced did): **amber, pulsing** while a loaded tab's turn is running; **blue** when a loaded background tab is idle with unread output (output landed while it wasn't on screen; cleared when shown); **green** otherwise — including sessions no tab has loaded and tabs a reconnect released. The list endpoint's `status` is not used.
61
60
  - Infinite scroll pages older sessions. A "+" footer starts a new session.
62
61
  - **New session**: "+", `/session new` and `/clear` open a **draft tab**. A "New session" row sits on top while it is on screen, and an untouched draft is reused rather than duplicated. The session is created by the draft's first message or command. The previous session keeps streaming in its own tab.
63
62
  - **Background tabs**: one WS connection carries every loaded tab, so a session that isn't on screen keeps receiving its turn. After a reconnect only the tab on screen is reattached; the others reload when clicked. Live capture and face (`<<<SHOW>>>`) markers only act for the tab on screen.
64
- - **Delete (✕)**: deletes the session after a confirm dialog in the UI language ("Delete this session? Its history cannot be recovered." / its Chinese counterpart). Deletion is the REST delete first, then the WS one, so sub-session files go too. The ✕ is always visible on the active tab and appears on hover for the others; hover also reveals a **rename** pencil.
63
+ - **Delete (trash icon)**: deletes the session after a confirm dialog in the UI language ("Delete this session? Its history cannot be recovered." / its Chinese counterpart). Deletion is the REST delete first, then the WS one, so sub-session files go too. The trash icon, like the **rename** pencil, appears only on hover — the active tab included.
65
64
  - **Inline rename**: pencil → input in place, Enter/blur commits (`PATCH /sessions/logs/:id`), Esc cancels; empty or unchanged title is a no-op. Other session-list consumers refresh via the `session:changed` WS push
66
65
  - **Persistence**: only the session on screen is remembered, per workspace, as `{active}` under `halo_chat_tabs_<projectId>`. A refresh restores and loads only that tab. The pre-tabs keys `halo_session_<projectId>` / `halo_session_id` are read once to seed it and then removed.
67
66
  - **Switch loading**: clicking a row that hasn't loaded yet shows a "Loading session…" state in the message area, cleared only when the server's `state:snapshot` for that exact sessionId arrives (empty sessions included). Past 30s it degrades to "Slow network — still loading…" plus a Retry button (re-subscribes) — slow ≠ failed, nothing aborts on its own. Switching to an already-loaded tab is instant, and its scroll position is restored
@@ -72,9 +71,9 @@ The full command list is fetched from `GET /api/commands` per session and includ
72
71
 
73
72
  | Command | Type | Purpose |
74
73
  |---|---|---|
75
- | `/help` | client | List available commands |
74
+ | `/help` | server | List available commands |
76
75
  | `/clear` | client | Alias for `/session new` (admin-UI shortcut, no server registration) |
77
- | `/session new` | server | Start a new session |
76
+ | `/session new` | server | Start a new session (the admin handles it client-side as a new draft tab, like `/clear`) |
78
77
  | `/session context` | server | Show context window usage, agent info |
79
78
  | `/session compact` | server | LLM-summary compact of the conversation |
80
79
 
@@ -93,7 +92,7 @@ When `contextEnabled` is on (default), user messages are auto-prepended with:
93
92
  - `[Currently viewing: path/to/file.ts]`
94
93
  - `[Selected text in file.ts:10-25]\n\`\`\`...\n\`\`\``
95
94
 
96
- ### File attachments
95
+ ### File attachments (images)
97
96
  - Drag to chat input
98
97
  - Clipboard paste
99
98
  - File-picker button
@@ -101,7 +100,7 @@ When `contextEnabled` is on (default), user messages are auto-prepended with:
101
100
  Images ride along as base64; multimodal supported. Pasted images are also persisted to `<workspace>/.halo/assets/web/inbound/web/<date>/` so a `[图片已保存: /abs/path]` marker survives page reload and renders as a click-to-preview chip (shared with the WeChat channel's inbound media flow).
102
101
 
103
102
  ### Inline media chips
104
- Any message containing `[图片/视频/语音/文件 已保存: /path]` markers (WeChat + web) or a leading `MEDIA: /path` line (agent-emitted, e.g. from `wechat-send`) renders a compact chip with filename + icon. Clicking opens a full-size preview modal (image/video/audio inline, file → download link). The modal has a Download button (top-right, next to close) for image/video/audio; the media URL carries a per-open cache-buster (`&t=<timestamp>`) so overwritten files (same path, new bytes) always show current content. Paths inside the active workspace or under `/tmp/` are previewable; everything else degrades to a non-clickable chip.
103
+ Any message containing `[图片/视频/语音/文件 已保存: /path]` markers (WeChat + web) or a leading `MEDIA: /path` line (agent-emitted, e.g. from `wechat-send`) renders a compact chip with filename + icon. Clicking opens a full-size preview modal (image/video/audio inline, file → download link). The modal has a Download button (top-right, next to close) for image/video/audio; the media URL carries a per-open cache-buster (`&t=<timestamp>`) so overwritten files (same path, new bytes) always show current content. Paths inside the active workspace or under the OS temp dir (`/tmp/`) are previewable; everything else degrades to a non-clickable chip.
105
104
 
106
105
  ### Live capture (desktop only)
107
106
  Lets the agent *see something live* on demand. Desktop client (Electron) only — the entry points never render in a plain browser. Borrows the meeting-app "share" model: the user binds one source, then the agent requests a frame when it actually needs to look.
@@ -115,7 +114,7 @@ Once bound, a frame is **not** attached to every message. Instead a one-line ins
115
114
  Constraints:
116
115
  - Shown only when the selected agent's model accepts image input (capture is pointless on a text-only model); switching to a text-only model auto-unbinds.
117
116
  - Screen share needs macOS **Screen Recording** permission; the camera prompts for **Camera** permission on first use, with an "Open Settings" path if previously denied.
118
- - Binding is **in-memory only** — switching sessions or restarting requires re-selecting.
117
+ - Binding is **in-memory only** and shared by all chat tabs — a page reload or restart requires re-selecting.
119
118
 
120
119
  ### The agent's face (`self.html`)
121
120
  A second channel beyond text: a visual space the agent drives in real time to express itself. Works everywhere (pure HTML/canvas, no Electron dependency) — desktop **and** plain browser.
@@ -123,7 +122,7 @@ A second channel beyond text: a visual space the agent drives in real time to ex
123
122
  - **What it is.** A self-contained animated particle canvas at `<workspace>/.halo/canvas/self.html` — a breathing core that reacts to the cursor (knows when it's watched), can form words/CJK/ASCII-from-emoji, play choreographed sequences, and gesture (pulse/flash/shake). Zero external references (no CDN/remote fonts) — ships and runs offline.
124
123
  - **Seeding.** Force-copied from `packages/server/templates/canvas/self.html` into every workspace on open (platform-owned, like built-in skills). The `self` built-in skill (wired into the default agent) teaches the agent it has this face and how to drive it.
125
124
  - **Opening it.** The ✨ button in the chat-input toolbar opens the face in the editor preview (switches to Explorer, render-mode on → lands on the live face) and posts `self.intro()` once it has mounted, so there's always a greeting when a human turns to look. The greeting is driven solely by this open action — the page does **not** self-fire it on load — so it plays exactly once, on both first and subsequent opens. The user can also just open the file directly (no auto-greeting in that path).
126
- - **Driving it.** The agent emits `<<<SHOW: …js… >>>` markers in a reply; Halo forwards the payload **verbatim** (it never parses it) to the open preview iframe via `postMessage`, where it's `eval`'d against the face's `self` API (`say`/`play`/`react`/`pulse`/`flash`/`shake`/`intro`) inside the sandboxed iframe. Markers are stripped from the rendered chat (like `<<<CAPTURE>>>`) — the user sees the face move, not the code. Multiple markers in one reply **queue and play in order**.
125
+ - **Driving it.** The agent emits `<<<SHOW: …js… >>>` markers in a reply; Halo forwards the payload **verbatim** (it never parses it) to the open preview iframe via `postMessage`, where it's `eval`'d against the face's `self` API (`say`/`play`/`react`/`pulse`/`flash`/`shake`/`intro`/`voice`) inside the sandboxed iframe. Markers are stripped from the rendered chat (like `<<<CAPTURE>>>`) — the user sees the face move, not the code. Multiple markers in one reply **queue and play in order**.
127
126
  - **Engine vs. expression.** `self.html` is a stable *engine* (defines how the face can move); the agent expresses itself by sending runtime JS, **never** by editing the file — so the force-copy-on-open never clobbers anything meaningful. The engine only changes when the platform adds a new capability (template edit + `TEMPLATE_VERSION` bump).
128
127
  - **No open preview = no-op.** `<<<SHOW>>>` only reaches a mounted preview; if the face isn't open the marker is silently dropped (the skill tells the agent to invite the user to open it rather than rely on a marker landing in the void).
129
128
  - **Identity.** Deliberately nameless ("HELLO / A MIND / IS HERE / BEYOND WORDS") — the conversational identity is user-configurable and the model may not be Claude, so the face never hard-codes a name.