@turmind/halo 1.5.4 → 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 +10 -9
- 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 +16 -17
- 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-92e9683ae8d39750.js +0 -1
- package/admin-out/_next/static/chunks/app/page-7a635969198fc62b.js +0 -1
- package/admin-out/_next/static/css/4ea5f674146e54d8.css +0 -3
- /package/admin-out/_next/static/{lm8Bz3ugTSORyuSXfyx56 → cuVcldfdg1PXevaQMQSzb}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{lm8Bz3ugTSORyuSXfyx56 → cuVcldfdg1PXevaQMQSzb}/_ssgManifest.js +0 -0
|
@@ -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 →
|
|
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 →
|
|
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
|
|
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
|
|
45
|
-
| `
|
|
46
|
-
|
|
|
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
|
|
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
|
|
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 →
|
|
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 **+**
|
|
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
|
|
133
|
-
- Skill metadata → system prompt: [packages/server/src/agents/agent-loader.ts
|
|
134
|
-
- Chat session creation on first message: [packages/server/src/ws/handler.ts
|
|
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
|
|
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
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
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
|
|
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
|
|
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 /
|
|
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__
|
|
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
|
|
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
|
|
@@ -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
|
|
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:
|
|
103
|
+
- Reconnect grace: 5 min (`timeout.terminal_grace`)
|
|
97
104
|
|
|
98
105
|
## Settings
|
|
99
106
|
|
|
100
|
-
`⚙️ Settings` tab. Form
|
|
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
|
-
| `
|
|
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
|
|
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
|
|
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
|
-
###
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
9
|
-
- Lists every available agent (from `GET /api/agent-configs`; overridden and
|
|
10
|
-
-
|
|
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
|
|
26
|
-
-
|
|
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
|
|
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
|
|
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 (`
|
|
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,
|
|
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 (
|
|
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` |
|
|
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** —
|
|
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.
|
|
@@ -10,8 +10,8 @@ Top-level built-in slash commands: `/help` `/evo` `/session` `/agent` `/skill` `
|
|
|
10
10
|
|
|
11
11
|
| Name | Slash | Type | Purpose |
|
|
12
12
|
|---|---|---|---|
|
|
13
|
-
| `help` | `/help` |
|
|
14
|
-
| `evo` | `/evo [text]` | server | Flat command (no verbs), **full-only**. Trigger self-evolution on the current root session: snapshot the conversation + queue an evo run. Optional text becomes a hint for what to focus on.
|
|
13
|
+
| `help` | `/help` | server | List commands — object commands show only the verbs the user can run; a command is hidden entirely if no verb is runnable |
|
|
14
|
+
| `evo` | `/evo [text]` | server | Flat command (no verbs), **full-only**. Trigger self-evolution on the current root session: snapshot the conversation + queue an evo run. Optional text becomes a hint for what to focus on. Works at every `general.evolution.level` (L0 = manual only; L1 also drafts automatically on pre-compact). Hidden from `/help` for non-full users. See [design/evolution.md](../design/evolution.md). |
|
|
15
15
|
|
|
16
16
|
The rest are noun-verb **object commands**: `/<obj> <verb> [args]`. Some verbs are built-in deterministic code (`SUBCOMMAND_ROUTES`); the others fall through to the same-name skill (LLM, dispatched via `$1`). Bare `/<obj>` or `/<obj> help` lists the verbs available to the user (filtered by access level).
|
|
17
17
|
|
|
@@ -38,7 +38,7 @@ Resolution starts from the caller's active session, walks up to its root (`id.sp
|
|
|
38
38
|
|
|
39
39
|
## Command aliases
|
|
40
40
|
|
|
41
|
-
`dispatchCommand` expands shorthand aliases before routing, so every channel (WS,
|
|
41
|
+
`dispatchCommand` expands shorthand aliases before routing, so every channel (WS, Web, the IM channels, CLI/TUI) gets the same shortcuts uniformly. Aliases are read from a single seed file:
|
|
42
42
|
|
|
43
43
|
- **`~/.halo/global/aliases.yaml`** — seeded once on first run (`init.ts`, via `writeIfMissing`), **never overwritten**, so the user can freely edit it. Deleting the file disables all aliases. Changes take effect immediately: the loader caches the parsed YAML keyed by file **mtime**, re-reading only when the file changes (no restart, no per-command disk parse on the hot path).
|
|
44
44
|
|
|
@@ -58,7 +58,7 @@ The canonical command is `/workspace`; `/w` is its bare alias. `/ws` and `/wi` a
|
|
|
58
58
|
|
|
59
59
|
## Cross-channel commands
|
|
60
60
|
|
|
61
|
-
All channels (WS,
|
|
61
|
+
All channels (WS, Web, Telegram, Slack, Feishu, WeCom, WeChat, CLI/TUI) share `dispatchCommand` for common commands. Channel-specific commands (e.g. WeChat `/qr`) are handled in the channel handler.
|
|
62
62
|
|
|
63
63
|
## Skill-as-command
|
|
64
64
|
|
|
@@ -88,30 +88,30 @@ When triggered, the skill body is read from disk and sent to the agent session a
|
|
|
88
88
|
|
|
89
89
|
User args are filled via `$ARGUMENTS` / `$1`–`$9` placeholders (args are no longer appended verbatim to the body end); a verb reaches the body as `$1` for dispatch.
|
|
90
90
|
|
|
91
|
-
Every `GET /api/commands
|
|
91
|
+
Every `GET /api/commands` call that carries `sessionId` / `agentId` rescans skill directories.
|
|
92
92
|
|
|
93
93
|
### Conflict detection
|
|
94
94
|
|
|
95
95
|
A skill's `command` must not collide with a built-in slash command or with another skill's. Collisions are resolved at scan time in `scanSkillDescriptors`, the single source feeding both dispatch and the discovery API:
|
|
96
96
|
|
|
97
|
-
- **Built-ins always win** — a skill command colliding with a built-in is not registered as a command. This is by design for object commands: their same-name skill (e.g. `agent`, `skill`, `workspace`) is shadowed as a command, but its body still serves the skill verbs (create / update etc.) via the verb fallback.
|
|
97
|
+
- **Built-ins always win** (hidden ones too) — a skill command colliding with a built-in is not registered as a command. This is by design for object commands: their same-name skill (e.g. `agent`, `skill`, `workspace`) is shadowed as a command, but its body still serves the skill verbs (create / update etc.) via the verb fallback.
|
|
98
98
|
- **Among skills, first-come wins** (workspace scope is merged over global before this check); the later one is dropped with a warning.
|
|
99
|
-
- Colliding entries are **dropped** from the command list
|
|
99
|
+
- Colliding entries are **dropped** from the command list; a skill-vs-skill clash also logs a warning (`[CommandRegistry] skill "<id>" command "/x" shadowed by …`), a built-in owning the name doesn't. Dropping at the shared source guarantees a command can never appear in the palette that dispatch is unable to route ("visible but unreachable").
|
|
100
100
|
|
|
101
101
|
Note: a skill's `command` collision only removes the *slash command*; the skill itself remains usable via the agent's normal skill activation.
|
|
102
102
|
|
|
103
103
|
## Command discovery API
|
|
104
104
|
|
|
105
105
|
```
|
|
106
|
-
GET /api/commands?projectId=xxx
|
|
106
|
+
GET /api/commands?projectId=xxx[&sessionId=][&agentId=]
|
|
107
107
|
→ { commands: CommandDescriptor[] }
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
Returns every non-hidden command (built-
|
|
110
|
+
Returns every non-hidden built-in command, plus the skill commands the session's / agent's skill whitelist and access level allow when `sessionId` or `agentId` is given (with neither, built-ins only). The frontend calls this on project switch to populate the command palette.
|
|
111
111
|
|
|
112
112
|
## Adding a new command
|
|
113
113
|
|
|
114
|
-
A server-handled command lives in **
|
|
114
|
+
A server-handled command lives in **four** places that must stay in sync —
|
|
115
115
|
miss any one and the server either throws at startup or silently no-ops:
|
|
116
116
|
|
|
117
117
|
1. Add a `exec<Name>` function in `channels/shared/commands.ts`
|
|
@@ -15,18 +15,19 @@ Monaco-based multi-tab code editor + binary file previewer — surfaced in the U
|
|
|
15
15
|
## Core behaviour
|
|
16
16
|
|
|
17
17
|
### Multi-tab editing
|
|
18
|
-
- Click a file in Explorer (single click = select, double click
|
|
18
|
+
- Click a file in Explorer (single click = select, then opens after a 300 ms delay; double click opens immediately)
|
|
19
19
|
- Each tab independently tracks content, original content, language, mtime
|
|
20
|
-
- Unsaved tabs show a small
|
|
20
|
+
- Unsaved tabs show a small amber dot (the ✕ appears on hover)
|
|
21
21
|
- Closing an unsaved tab prompts for confirmation
|
|
22
|
+
- The tab bar's **Split right** button opens a second pane (two at most); each pane has its own tabs, Edit/Preview and Diff state
|
|
22
23
|
|
|
23
24
|
### Save
|
|
24
25
|
- `Cmd+S` / `Ctrl+S` calls `PUT /api/files`
|
|
25
26
|
- Server returns the new `modifiedAt`; the tab updates its mtime
|
|
26
27
|
|
|
27
28
|
### Auto-refresh (mtime-based)
|
|
28
|
-
When agents write files through tools, the canvas detects it:
|
|
29
|
-
1.
|
|
29
|
+
When agents write files through tools, the canvas detects it — push-based, no polling:
|
|
30
|
+
1. The server's WS `file:changed` (`change`) event re-reads the matching open tab; the active tab is also re-checked with `GET /api/files/stat` on tab switch, window focus and WS reconnect
|
|
30
31
|
2. If `diskMtime > tab.mtime`, pull fresh content
|
|
31
32
|
3. Skip if the user has unsaved edits (don't overwrite)
|
|
32
33
|
|
|
@@ -34,7 +35,7 @@ When agents write files through tools, the canvas detects it:
|
|
|
34
35
|
Open tabs and the active tab live in localStorage; survive refresh.
|
|
35
36
|
|
|
36
37
|
### Diff view
|
|
37
|
-
-
|
|
38
|
+
- A tab with unsaved edits shows a **Diff** button in its pane's tab bar: last saved content vs. the current buffer, computed in the client (no git involved; `GET /api/git/diff` is the Source Control panel's)
|
|
38
39
|
- Monaco left/right compare view
|
|
39
40
|
|
|
40
41
|
### Binary previews
|
|
@@ -66,13 +67,13 @@ File types without a built-in plugin can be handled by an **installed extension*
|
|
|
66
67
|
|
|
67
68
|
### Renderable text formats (Markdown / HTML)
|
|
68
69
|
Markdown and HTML open as text in Monaco *and* have a rendered view — Canvas defaults to the **rendered** view since the primary audience is an AI generating reports / pages for humans to read.
|
|
69
|
-
-
|
|
70
|
+
- Each pane's tab bar shows an **Edit / Preview** toggle for the active Markdown / HTML tab; the state is per pane, shared by both formats
|
|
70
71
|
- **Markdown** → `MarkdownPreview` (react-markdown + GFM; relative image `src` rewritten to the download endpoint so local images work)
|
|
71
|
-
- **HTML** → sandboxed iframe (`sandbox="allow-same-origin"
|
|
72
|
+
- **HTML** → sandboxed iframe (`sandbox="allow-scripts allow-same-origin"`: the page's own JS runs, so a self-contained page renders live; top-navigation, forms and pop-ups stay blocked. Trusted like opening the file in Monaco — it comes from the user's own workspace)
|
|
72
73
|
- Toggle to Edit → Monaco source, Cmd+S saves as usual
|
|
73
74
|
|
|
74
75
|
### Preview caching (MRU)
|
|
75
|
-
Recently opened preview tabs stay mounted (up to 5, MRU) so switching between them doesn't re-fetch or re-parse. Plugins flagged `heavy: true` bypass the cache — only the active instance mounts, others unmount. Closing a preview tab removes it from the cache immediately (aborting any in-flight fetch).
|
|
76
|
+
Recently opened preview tabs stay mounted (up to 5, MRU) so switching between them doesn't re-fetch or re-parse. Plugins flagged `heavy: true` bypass the cache — only the active instance mounts, others unmount. An extension tab with unsaved edits stays pinned beyond the 5. Closing a preview tab removes it from the cache immediately (aborting any in-flight fetch).
|
|
76
77
|
|
|
77
78
|
### File metadata in header
|
|
78
79
|
The Canvas header shows `(size · Created … · Modified …)` for the active tab. Both text tabs and preview tabs populate this — preview tabs fetch `GET /api/files/stat` on open (and on tab restore from localStorage) since they never read content.
|
|
@@ -91,5 +92,5 @@ Canvas (including Monaco instances, file tree, and open tab contents) stays moun
|
|
|
91
92
|
|---|---|
|
|
92
93
|
| Cmd/Ctrl + P | Quick Open (fuzzy file search) |
|
|
93
94
|
| Cmd/Ctrl + S | Save current tab |
|
|
94
|
-
| Alt + W | Close current tab (Cmd+W cannot be overridden in browsers) |
|
|
95
|
+
| Alt + W | Close current tab (Cmd+W cannot be overridden in browsers; the desktop app uses Cmd/Ctrl + W, and closes the window when no tab is open) |
|
|
95
96
|
| Esc | Exit maximized Canvas (when not inside an input / Monaco / Quick Open) |
|
|
@@ -6,22 +6,25 @@ VS Code-style file tree sidebar with multi-select, drag-drop, right-click menu.
|
|
|
6
6
|
|
|
7
7
|
### Initialization
|
|
8
8
|
- URL `?folder=/abs/path` → use that directly as workspace
|
|
9
|
-
- No `?folder` →
|
|
9
|
+
- No `?folder` → reopen the last workspace (`halo_last_folder` in localStorage); if none is stored or it no longer resolves, fall back to `GET /api/fs/home`. Either way the path is written back into the URL; UI behaves identically
|
|
10
|
+
- The path is resolved through `POST /api/fs/workspace/resolve`, which rejects non-directories and filesystem roots and seeds the workspace's `.halo/` scaffold
|
|
10
11
|
- There's always a valid workspace; every frontend feature (file tree / sessions / agents / settings) depends on it
|
|
11
12
|
|
|
12
13
|
### Path input
|
|
13
14
|
- Explorer top bar has a path input + 📁🔍 picker button
|
|
14
|
-
- Pressing Enter calls `GET /api/fs/exists?path=...` first — a miss shows an alert, no switch
|
|
15
|
-
- **Recent-workspaces dropdown**: focusing the input shows the most-recently-used list; typing filters it by substring. Each row shows the folder name + full path; clicking one switches to it; a hover "×" removes a single entry. Entries are recorded only after a switch validates the path (so invalid paths never land), in canonical resolved form, MRU-ordered and deduped, capped at 8. Persisted in localStorage (`halo_recent_workspaces`)
|
|
15
|
+
- Pressing Enter calls `GET /api/fs/exists?path=...` first — a miss (or a non-directory) shows an alert, no switch
|
|
16
|
+
- **Recent-workspaces dropdown**: focusing the input shows the most-recently-used list; typing filters it by substring. The current workspace is left out. Each row shows the folder name + full path; clicking one switches to it; a hover "×" removes a single entry. Entries are recorded only after a switch validates the path (so invalid paths never land), in canonical resolved form, MRU-ordered and deduped, capped at 8. Persisted in localStorage (`halo_recent_workspaces`)
|
|
16
17
|
|
|
17
18
|
### Desktop windows (multi-window)
|
|
18
19
|
Desktop app only — the browser has one tab per window natively.
|
|
19
20
|
- **New window**: `Cmd+N` (macOS, via the File menu) / `Ctrl+N` (Windows/Linux, bound at the webContents level since those platforms have no app menu). Each window is a fresh view onto the **one shared local server** — no second server process spawns.
|
|
20
21
|
- **Per-window workspace**: windows share the server origin, hence one localStorage, so a window tracks its own workspace via its URL `?folder=` (not localStorage, which would clobber across windows). Different windows can sit on different workspaces; the same workspace can be open in two windows.
|
|
21
22
|
- **Quit semantics** (platform split): on macOS, closing every window keeps the app in the Dock (only `Cmd+Q` truly exits) and clicking the Dock icon reopens a window; on Windows/Linux there's no Dock to resummon from, so closing the last window quits.
|
|
23
|
+
- **Quick toggles entry** (bottom of the activity bar, below Settings; browser and desktop): a gear with a thin segmented status bar under it — one segment per available item, in fixed order network → notify → pin → keep-awake (browser: network + notify, or network alone without the Web Notification API; desktop: all four). Network segment: emerald = fresh, pulsing amber = stale, red = down (the gear itself also turns red when down); notify / pin segments: primary when on; keep-awake: amber when on (it stops the screen locking, so it gets its own colour); off = dimmed muted grey. The native hover title lists every item's state. Clicking opens a panel (titled "Status & window") to the right of the entry: a read-only network row, then one switch row per toggle; switches apply immediately and the panel stays open; outside click, Esc or a second entry click closes it. Segments, rows and the hover summary all come from one item list (`useQuickToggleItems` in `features/workspace/quick-toggles.tsx`).
|
|
22
24
|
- **Pin (always-on-top)** acts per-window — each window pins independently.
|
|
25
|
+
- **Keep screen awake** (desktop only) acts per-window and isn't persisted — a restart or a new window starts off, a reload keeps it. While any opted-in window is neither minimized nor hidden (merely covered by other windows still counts), the app holds one display-sleep blocker, so the screen won't dim or lock when you step away.
|
|
23
26
|
- **Activity awareness**: while the agent streams a reply the sidebar's workspace name carries an amber pulsing dot (emerald when idle) and the window title gains a `●` prefix (`● Halo — <name>` busy vs `Halo — <name>` idle) — both are plain web behaviour that Electron mirrors to the native title bar.
|
|
24
|
-
- **Finish notification** (works in both the desktop app and a plain browser): an opt-in
|
|
27
|
+
- **Finish notification** (works in both the desktop app and a plain browser): an opt-in toggle in the quick-toggles panel, **off by default**, persisted per-machine in localStorage (`halo_notify_on_finish`). It's shown whenever a notification can be raised — the desktop shell (`window.haloNotify` bridge) or a browser with the Web Notification API; turning it on in a browser requests Notification permission from within the toggle click (the required user gesture). When on, a turn finishing in **any loaded chat tab** — on screen or in the background — triggers two decoupled cues (sessions no tab has loaded, or released by a reconnect, never ring; tab switches and snapshot replays aren't completions; queued messages drained back to back ring once, after the last one): a **self-synthesized WebAudio chime** that always plays (regardless of focus — an audible "it's done" even while you're watching the tab, and not subject to the OS per-site notification-sound setting), plus — **only when the window is unfocused** — a banner (desktop: native notification + Dock bounce / taskbar flash; browser: a Web Notification whose click refocuses the tab) whose body names the session by its title ("“<title>” finished responding."; generic text if the title isn't known). Focused → chime only, no banner.
|
|
25
28
|
|
|
26
29
|
### Directory picker (FolderPicker modal)
|
|
27
30
|
Visual directory browser opened by the 📁🔍 button:
|
|
@@ -47,8 +50,8 @@ Visual directory browser opened by the 📁🔍 button:
|
|
|
47
50
|
|
|
48
51
|
### Selection model
|
|
49
52
|
VS Code-style highlight selection (no checkboxes):
|
|
50
|
-
- **Click**: select and highlight
|
|
51
|
-
- **Double click**: open in editor tab
|
|
53
|
+
- **Click**: select and highlight; a file also opens in the editor after a short (300 ms) delay, a folder toggles open/closed
|
|
54
|
+
- **Double click**: open the file in an editor tab right away (cancels the delayed open)
|
|
52
55
|
- **Ctrl/Cmd + click**: toggle multi-select
|
|
53
56
|
- **Shift + click**: range select
|
|
54
57
|
|
|
@@ -60,7 +63,7 @@ VS Code-style highlight selection (no checkboxes):
|
|
|
60
63
|
|
|
61
64
|
### Right-click menu
|
|
62
65
|
|
|
63
|
-
Items shown depend on the click target:
|
|
66
|
+
Items shown depend on the click target. With several items selected the menu offers only Delete; on the workspace root row only New File / New Folder / Open in Integrated Terminal / Reveal in File Manager apply:
|
|
64
67
|
|
|
65
68
|
| Action | Shown when | API / behavior |
|
|
66
69
|
|---|---|---|
|