@turmind/halo 1.5.10 → 1.5.11
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/{388-d5950f264d91625a.js → 941-4719e7343b54b9bd.js} +1 -1
- package/admin-out/_next/static/chunks/989-86b0f677f69ccf3b.js +1 -0
- package/admin-out/_next/static/chunks/app/page-4792aa1c79f7e7f1.js +1 -0
- package/admin-out/_next/static/css/97f3ac570664d044.css +3 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/agent.md +7 -5
- package/bundled-docs/design/halo-city.md +6 -0
- package/bundled-docs/design/prompt-system.md +1 -1
- package/bundled-docs/design/relay.md +6 -6
- package/bundled-docs/design/session.md +12 -11
- package/bundled-docs/design/web.md +33 -5
- package/bundled-docs/dev/acp-adapter.md +59 -175
- package/bundled-docs/dev/api.md +5 -2
- package/bundled-docs/dev/deploy.md +1 -1
- package/bundled-docs/dev/desktop-packaging.md +14 -0
- package/bundled-docs/dev/env.md +2 -2
- package/bundled-docs/dev/tools.md +8 -8
- package/bundled-docs/guide/README.md +1 -1
- package/bundled-docs/guide/channels/README.md +3 -3
- package/bundled-docs/guide/channels/acp.md +67 -42
- package/bundled-docs/guide/channels/feishu.md +1 -1
- package/bundled-docs/guide/channels/slack.md +1 -1
- package/bundled-docs/guide/channels/telegram.md +1 -1
- package/bundled-docs/guide/channels/web.md +10 -8
- package/bundled-docs/guide/channels/wechat.md +1 -1
- package/bundled-docs/guide/channels/wecom.md +1 -1
- package/bundled-docs/guide/chat.md +0 -1
- package/bundled-docs/guide/cli.md +1 -2
- package/bundled-docs/guide/getting-started.md +1 -1
- package/bundled-docs/guide/skills.md +1 -2
- package/bundled-docs/requirements/chat.md +14 -0
- package/bundled-docs/requirements/command.md +1 -2
- package/bundled-docs/requirements/explorer.md +1 -0
- package/bundled-docs/requirements/terminal.md +15 -0
- package/dist/index.js +810 -333
- package/package.json +1 -1
- package/templates/agents/__score__/AGENT.md +1 -1
- package/templates/agents/default/agent.yaml +0 -1
- package/templates/prompts/all/TOOL_SHELL.windows.md +2 -1
- package/templates/skills/skill/SKILL.md +1 -1
- package/templates/skills/workspace/stage.py +1 -1
- package/admin-out/_next/static/chunks/989-1797287c46139412.js +0 -1
- package/admin-out/_next/static/chunks/app/page-44ab337b2f071e4d.js +0 -1
- package/admin-out/_next/static/css/d53aa54212002364.css +0 -3
- package/templates/skills/acp/SKILL.md +0 -243
- package/templates/skills/acp/templates/SKILL.md.tmpl +0 -75
- package/templates/skills/acp/templates/ask.py +0 -177
- package/templates/skills/acp/templates/config.yaml.tmpl +0 -38
- /package/admin-out/_next/static/{A-Rjx2yltZDKaSO1qVv7K → GO2W5JoiC46FIxLjtFAXx}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{A-Rjx2yltZDKaSO1qVv7K → GO2W5JoiC46FIxLjtFAXx}/_ssgManifest.js +0 -0
|
@@ -202,7 +202,7 @@ Per-channel defaults: every channel session (Web, Telegram, Slack, Feishu, WeCom
|
|
|
202
202
|
`grep` reads the first 512 bytes of each file looking for a null byte and skips binaries (`glob` matches paths only and never opens files).
|
|
203
203
|
|
|
204
204
|
### Tool result budget
|
|
205
|
-
The orchestrator truncates tool results over 8000 chars and appends a `[Content truncated]` hint telling the agent to use `grep` for a targeted search. `activate_skill` results are exempt — a SKILL.md body is instructions, not data, and the built-in
|
|
205
|
+
The orchestrator truncates tool results over 8000 chars and appends a `[Content truncated]` hint telling the agent to use `grep` for a targeted search. `activate_skill` results are exempt — a SKILL.md body is instructions, not data, and the built-in cron / self skills exceed 8K.
|
|
206
206
|
|
|
207
207
|
## Session tools
|
|
208
208
|
|
|
@@ -284,13 +284,13 @@ Send a message to another session. Idle = immediate run, busy = queued + soft in
|
|
|
284
284
|
| `target_session_id` | string | yes | Target session ID |
|
|
285
285
|
| `message` | string | yes | Message content |
|
|
286
286
|
|
|
287
|
-
**Busy = soft interrupt (merge-answer parity)**: when the target is busy, `query_session` enqueues the message **and** requests a soft interrupt (same as a user message arriving mid-turn) — the in-flight turn unwinds
|
|
287
|
+
**Busy = soft interrupt (merge-answer parity)**: when the target is busy, `query_session` enqueues the message **and** requests a soft interrupt (same as a user message arriving mid-turn) — the in-flight turn finishes its current batch of tool calls and unwinds before its next model call, then every message that landed alongside it drains as **one merged turn**. So a sub-agent asked two questions while busy answers them **together**, not one-by-one — matching how root folds two user messages. No message is dropped. (Before 0.1.4 a busy `query_session` was pure no-interrupt enqueue, which made sub-agents reply one question at a time.)
|
|
288
288
|
|
|
289
289
|
**Queue cap (backpressure)**: when the target is busy, `query_session` is rejected with `{"code": 1, "error": "...message queue is full (N/max)..."}` once the target's queued **agent-sourced** messages reach `session.maxQueueSize` (default 256, settings `general.session.max_queue_size`). The cap counts **only agent→agent entries** — user messages share the same queue but are immune to backpressure (a human can't hand-type up to the cap, and counting them would let user chatter consume the agents' budget). `interrupt_session` is a deliberate action and bypasses the cap entirely.
|
|
290
290
|
|
|
291
291
|
### interrupt_session
|
|
292
292
|
|
|
293
|
-
Equivalent to `query_session` **plus an immediate abort** of the in-flight turn. The message is enqueued and traced right away (exactly like `query_session`); aborting then makes the queue drain **now** rather than after the current turn finishes, so the enqueued message is folded into the very next merged turn. The abort is hard — it propagates to `shell_exec` and SIGTERMs a command mid-execution (contrast the soft interrupt `query_session` and a busy user message trigger, which wait for the current `
|
|
293
|
+
Equivalent to `query_session` **plus an immediate abort** of the in-flight turn. The message is enqueued and traced right away (exactly like `query_session`); aborting then makes the queue drain **now** rather than after the current turn finishes, so the enqueued message is folded into the very next merged turn. The abort is hard — it propagates to `shell_exec` and SIGTERMs a command mid-execution (contrast the soft interrupt `query_session` and a busy user message trigger, which wait for the current tool batch to finish). Of a cut parallel batch, the call that was running gets the do-not-retry `[tool execution interrupted …]` result and the calls that never started get a "was not run … safe to re-issue" result. For a **compound** command (`sleep 60 && …`) under `full` access, the kill reaches the real worker because the command runs as a **process-group leader** and the whole group is signalled — before 0.1.4 the abort only hit the wrapping `/bin/sh`, leaving the worker to orphan and run to completion (which made `interrupt_session` look like it didn't interrupt). See [session.md → Process-group kill on abort](../design/session.md#process-group-kill-on-abort). Conversation history is preserved (repaired, not discarded), and `interrupt_session` bypasses the `query_session` queue cap.
|
|
294
294
|
|
|
295
295
|
| Arg | Type | Required | Description |
|
|
296
296
|
|---|---|---|---|
|
|
@@ -299,7 +299,7 @@ Equivalent to `query_session` **plus an immediate abort** of the in-flight turn.
|
|
|
299
299
|
|
|
300
300
|
### stop_session
|
|
301
301
|
|
|
302
|
-
Abort the current task of a running session. Any queued messages are **not** dropped — they are folded into the conversation history before the abort, so nothing said while the session was busy is lost. The session stays usable — later `query_session` calls continue the conversation.
|
|
302
|
+
Abort the current task of a running session. Any queued messages are **not** dropped — they are folded into the conversation history before the abort, so nothing said while the session was busy is lost. The session stays usable — later `query_session` calls continue the conversation. Refused with `{"code": 1}` (no stop) when `session_id` is the caller's own session or one of its ancestors — the stop would wait on the very turn making the call; to end its own work an agent just finishes its turn.
|
|
303
303
|
|
|
304
304
|
| Arg | Type | Required | Description |
|
|
305
305
|
|---|---|---|---|
|
|
@@ -307,7 +307,7 @@ Abort the current task of a running session. Any queued messages are **not** dro
|
|
|
307
307
|
|
|
308
308
|
### archive_session
|
|
309
309
|
|
|
310
|
-
**Cascade** archive a session and every descendant. Aborts running work, clears queued messages. Archived sessions disappear from `session_list` and cannot be reached by `query_session`. Only use it when the whole subtree is done.
|
|
310
|
+
**Cascade** archive a session and every descendant. Aborts running work, clears queued messages. Archived sessions disappear from `session_list` and cannot be reached by `query_session`. Only use it when the whole subtree is done. Refused with `{"code": 1}` (nothing archived) when `session_id` is the caller's own session or one of its ancestors, for the same reason as `stop_session`.
|
|
311
311
|
|
|
312
312
|
| Arg | Type | Required | Description |
|
|
313
313
|
|---|---|---|---|
|
|
@@ -372,7 +372,7 @@ Cross-**workspace** dispatch on the same server: a "secretary" agent in workspac
|
|
|
372
372
|
|
|
373
373
|
### relay_send
|
|
374
374
|
|
|
375
|
-
Dispatch a message to a session in another workspace. Creates the session if `session_id` does not exist there (root session, agent `agent_id` or the target workspace's default agent, description `Relay: <first 60 chars>`), stamps the target row's `reply_to` with `{ workspace: <caller ws>, sessionId: <caller session> }`, appends the raw message to the target's UI transcript and sends it prefixed `[channel: relay | from: <caller ws>]\n\n<message>` — so the target agent knows the message came from another workspace's agent, not the admin UI, and doesn't echo the tag. Busy target → the message is **queued + soft interrupt** (same as `query_session` / a user message mid-turn: the current tool finishes, then the queue drains as one merged turn), so follow-ups and corrections ride the same tool. Returns immediately.
|
|
375
|
+
Dispatch a message to a session in another workspace. Creates the session if `session_id` does not exist there (root session, agent `agent_id` or the target workspace's default agent, description `Relay: <first 60 chars>`), stamps the target row's `reply_to` with `{ workspace: <caller ws>, sessionId: <caller session> }`, appends the raw message to the target's UI transcript and sends it prefixed `[channel: relay | from: <caller ws>]\n\n<message>` — so the target agent knows the message came from another workspace's agent, not the admin UI, and doesn't echo the tag. Busy target → the message is **queued + soft interrupt** (same as `query_session` / a user message mid-turn: the current batch of tool calls finishes, then the queue drains as one merged turn), so follow-ups and corrections ride the same tool. Returns immediately. When `workspace` is the caller's own workspace, a `session_id` naming the caller itself or one of its ancestors is refused (`{"code": 1}`, nothing sent, `reply_to` untouched) — its report would come back into the caller's own tree; other sessions in the same workspace are fine.
|
|
376
376
|
|
|
377
377
|
| Arg | Type | Required | Description |
|
|
378
378
|
|---|---|---|---|
|
|
@@ -405,7 +405,7 @@ Body capped at `limits.autoReportMax` (head-kept) with a `[Report truncated: N c
|
|
|
405
405
|
|
|
406
406
|
### relay_interrupt
|
|
407
407
|
|
|
408
|
-
`relay_send` **plus a hard abort** of the target's in-flight turn — `interrupt_session` semantics across workspaces: the message is enqueued first, and if the target was busy (`state === 'queued'`) its turn is aborted immediately (propagates to `shell_exec`, SIGTERMs the process group) so the drain picks the message up now instead of after the current step. Enqueue-then-abort order matters: the finally never sees an empty queue and so never fires a spurious relay report. Idle target → behaves exactly like `relay_send` (`interrupted: false`). Unlike `relay_send` it does **not** create a missing session (`{"code": 1, "error": "session not found"}`) — there is nothing to interrupt. Use it when the target is heading the wrong way and waiting for its current step is not acceptable; for ordinary follow-ups prefer `relay_send`.
|
|
408
|
+
`relay_send` **plus a hard abort** of the target's in-flight turn — `interrupt_session` semantics across workspaces: the message is enqueued first, and if the target was busy (`state === 'queued'`) its turn is aborted immediately (propagates to `shell_exec`, SIGTERMs the process group) so the drain picks the message up now instead of after the current step. Enqueue-then-abort order matters: the finally never sees an empty queue and so never fires a spurious relay report. Idle target → behaves exactly like `relay_send` (`interrupted: false`). Unlike `relay_send` it does **not** create a missing session (`{"code": 1, "error": "session not found"}`) — there is nothing to interrupt. Use it when the target is heading the wrong way and waiting for its current step is not acceptable; for ordinary follow-ups prefer `relay_send`. Same own-workspace self / ancestor refusal as `relay_send`.
|
|
409
409
|
|
|
410
410
|
| Arg | Type | Required | Description |
|
|
411
411
|
|---|---|---|---|
|
|
@@ -417,7 +417,7 @@ Returns `{ "code": 0, "workspace", "session_id", "state": "running" | "queued",
|
|
|
417
417
|
|
|
418
418
|
### relay_stop
|
|
419
419
|
|
|
420
|
-
Cascades `stopSession` on the target session and its sub-agents. If the target was mid-turn the caller still receives a relay report describing where it was cut off (the stop ends the turn → finally → `deliverRelayReport`, with the partial trace as body).
|
|
420
|
+
Cascades `stopSession` on the target session and its sub-agents. If the target was mid-turn the caller still receives a relay report describing where it was cut off (the stop ends the turn → finally → `deliverRelayReport`, with the partial trace as body). When `workspace` is the caller's own workspace, a `session_id` naming the caller itself or one of its ancestors is refused (`{"code": 1}`, no stop) — same rule as `stop_session`; the same id in another workspace is a different session and stops normally.
|
|
421
421
|
|
|
422
422
|
| Arg | Type | Required | Description |
|
|
423
423
|
|---|---|---|---|
|
|
@@ -22,7 +22,7 @@ Aimed at Halo end-users. Pick the doc that matches your situation.
|
|
|
22
22
|
- [Feishu / Lark](channels/feishu.md) — long-connect with appId + appSecret
|
|
23
23
|
- [WeCom](channels/wecom.md) — 企业微信 intelligent bot, long-connect with Bot ID + Secret
|
|
24
24
|
- [WeChat](channels/wechat.md) — QR-scan iLink bot
|
|
25
|
-
- [ACP](channels/acp.md) —
|
|
25
|
+
- [ACP](channels/acp.md) — Zed / JetBrains or other ACP clients driving a remote halo
|
|
26
26
|
|
|
27
27
|
## Related docs
|
|
28
28
|
|
|
@@ -12,7 +12,7 @@ This folder has one onboarding guide per channel. Pick the one you want and foll
|
|
|
12
12
|
| [Feishu / Lark](feishu.md) | Chinese enterprise teams | Feishu open-platform App (`appId` + `appSecret`) + long connection | WebSocket (long-connect) |
|
|
13
13
|
| [WeCom](wecom.md) | Chinese enterprise teams on 企业微信 (single chat + groups) | 智能机器人 (Bot ID + Secret) + long connection | WebSocket (long-connect) |
|
|
14
14
|
| [WeChat](wechat.md) | China-side personal use; mobile-first | iLink-style bot, login by scanning a QR | Long-poll |
|
|
15
|
-
| [ACP](acp.md) |
|
|
15
|
+
| [ACP](acp.md) | An ACP-speaking IDE (Zed / JetBrains / …) driving a remote halo | Web token + adapter CLI | JSON-RPC over stdio |
|
|
16
16
|
|
|
17
17
|
## How channels are wired
|
|
18
18
|
|
|
@@ -28,7 +28,7 @@ This folder has one onboarding guide per channel. Pick the one you want and foll
|
|
|
28
28
|
SessionManager (per workspace)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Each channel handler is a thin adapter between its native protocol and halo's `SessionManager`. Slash commands (`/session` `/agent` `/skill` `/workspace` `/cron` `/
|
|
31
|
+
Each channel handler is a thin adapter between its native protocol and halo's `SessionManager`. Slash commands (`/session` `/agent` `/skill` `/workspace` `/cron` `/evo` `/extension` `/help`) are shared across all of them — see `channels/shared/commands.ts`.
|
|
32
32
|
|
|
33
33
|
The ACP adapter is **not** a channel — it's a stdio bridge that translates ACP JSON-RPC into the web channel's HTTP+SSE. Counted here only because users go through the same "set up an account, get a token" flow.
|
|
34
34
|
|
|
@@ -62,7 +62,7 @@ When in doubt start at `readonly` and raise it later. The setting is on the **ac
|
|
|
62
62
|
|
|
63
63
|
- Already know which channel you want → click the row above
|
|
64
64
|
- New to halo and just want to chat in a browser → [Web](web.md)
|
|
65
|
-
- Want to run halo from your IDE (
|
|
65
|
+
- Want to run halo from your IDE (Zed / JetBrains) → [ACP](acp.md)
|
|
66
66
|
- Setting up a team workspace → [Slack](slack.md), [Feishu](feishu.md) or [WeCom](wecom.md)
|
|
67
67
|
|
|
68
68
|
For the design rationale and protocol details (not user-facing), see [docs/design/](../../design/) — one file per channel: `web.md`, `telegram.md`, `slack.md`, `feishu.md`, `wecom.md`, `wechat.md`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ACP (Agent Client Protocol)
|
|
2
2
|
|
|
3
|
-
Drive a halo server from any [ACP](https://github.com/zed-industries/agent-client-protocol)-speaking client. The most common use is **
|
|
3
|
+
Drive a halo server from any [ACP](https://github.com/zed-industries/agent-client-protocol)-speaking client. The most common use is **an ACP-capable editor (Zed, a JetBrains IDE) on a developer's laptop, talking to a halo agent on EC2** — but any ACP client works.
|
|
4
4
|
|
|
5
5
|
ACP isn't a channel of its own. The `halo acp` command is a **stdio bridge** that translates ACP JSON-RPC into the Web channel's HTTP+SSE. So setup = "create a Web account, then point an adapter at it."
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ ACP isn't a channel of its own. The `halo acp` command is a **stdio bridge** tha
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
[Mac] [EC2]
|
|
11
|
-
|
|
11
|
+
Zed / JetBrains IDE halo server (port 9527)
|
|
12
12
|
│ ACP / JSON-RPC over stdio │
|
|
13
13
|
▼ │
|
|
14
14
|
halo acp adapter ────HTTP/SSE────────────────▶│
|
|
@@ -19,8 +19,7 @@ halo acp adapter ────HTTP/SSE──────────────
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Use ACP when:
|
|
22
|
-
- You want to use
|
|
23
|
-
- You want one halo server to delegate to **another** halo server on a different host (the `/acp add` flow below)
|
|
22
|
+
- You want to use your editor (Zed, a JetBrains IDE) locally as the chat UI but the agent's tools / workspace live on a remote machine
|
|
24
23
|
- You're integrating with any tool that already speaks ACP
|
|
25
24
|
|
|
26
25
|
If you just want a browser UI, use the [Web](web.md) channel directly — ACP is overkill.
|
|
@@ -61,58 +60,82 @@ Each adapter process binds to **one** workspace. To drive multiple workspaces co
|
|
|
61
60
|
|
|
62
61
|
The adapter reads/writes JSON-RPC frames on **stdin / stdout** (one message per line, no LSP-style framing). **Stderr** is reserved for human-readable diagnostics — never parse it.
|
|
63
62
|
|
|
64
|
-
### Wiring into
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
This is the **only** supported way to set up a halo-to-halo binding — there's no generic single-target `ask-acp-agent` skill, because per-binding namespaces (one token-host-workspace triple per skill id) are required for multi-remote use.
|
|
87
|
-
|
|
88
|
-
### Direct asks — `/acp kiro` / `/acp claude`
|
|
63
|
+
### Wiring into Zed
|
|
64
|
+
|
|
65
|
+
Register the adapter as a custom agent in Zed's `settings.json` (Agent Settings → External Agents → Add Agent → Add Custom Agent opens it for you). See [Zed's External Agents docs](https://zed.dev/docs/ai/external-agents):
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"agent_servers": {
|
|
70
|
+
"halo": {
|
|
71
|
+
"type": "custom",
|
|
72
|
+
"command": "halo",
|
|
73
|
+
"args": [
|
|
74
|
+
"acp",
|
|
75
|
+
"--host", "my-ec2-or-localhost",
|
|
76
|
+
"--port", "9527",
|
|
77
|
+
"--token", "<web-token-from-step-1>",
|
|
78
|
+
"--workspace", "/abs/path/on/server"
|
|
79
|
+
],
|
|
80
|
+
"env": {}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
89
85
|
|
|
90
|
-
|
|
86
|
+
### Wiring into JetBrains IDEs
|
|
87
|
+
|
|
88
|
+
In the AI Chat tool window click the menu button in the upper-right corner and choose **Add Custom Agent**; the IDE creates and opens `~/.jetbrains/acp.json`. See [JetBrains' ACP docs](https://www.jetbrains.com/help/ai-assistant/acp.html):
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"agent_servers": {
|
|
93
|
+
"halo": {
|
|
94
|
+
"command": "halo",
|
|
95
|
+
"args": [
|
|
96
|
+
"acp",
|
|
97
|
+
"--host", "my-ec2-or-localhost",
|
|
98
|
+
"--port", "9527",
|
|
99
|
+
"--token", "<web-token-from-step-1>",
|
|
100
|
+
"--workspace", "/abs/path/on/server"
|
|
101
|
+
],
|
|
102
|
+
"env": {}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
91
107
|
|
|
92
|
-
|
|
93
|
-
- `/acp kiro <question>` — local Kiro (via `kiro-cli acp`)
|
|
108
|
+
If the IDE can't find `halo` on its `PATH`, use the absolute path to the binary as `command`.
|
|
94
109
|
|
|
95
|
-
|
|
110
|
+
After registration, pick the agent in the editor's agent panel and chat — the editor thinks it's talking to a local agent, halo thinks it's talking to a Web-channel client.
|
|
96
111
|
|
|
97
112
|
## ACP method coverage
|
|
98
113
|
|
|
114
|
+
The adapter speaks ACP protocol v1 and passes the [ACP TCK](https://github.com/agentclientprotocol/acp-tck) v1 suite (verdict CONFORMANT).
|
|
115
|
+
|
|
99
116
|
| Method | Implemented | Notes |
|
|
100
117
|
|---|---|---|
|
|
101
|
-
| `initialize` | ✅ | Declares `protocolVersion: 1`, `loadSession: true`, no auth methods |
|
|
118
|
+
| `initialize` | ✅ | Declares `protocolVersion: 1`, `loadSession: true`, `sessionCapabilities.list`, image + embedded-context prompts, no auth methods, and `agentInfo` (`halo` + adapter version) |
|
|
102
119
|
| `authenticate` | ✅ (no-op) | Token is passed via launch flags; ACP-side auth has nothing to do |
|
|
103
120
|
| `session/new` | ✅ | Calls `POST /api/web/sessions`; halo mints `web_<accountId>_<ts>_<rand>` and creates the row immediately |
|
|
104
|
-
| `session/load` | ✅ |
|
|
105
|
-
| `session/
|
|
106
|
-
| `session/
|
|
121
|
+
| `session/load` | ✅ | Replays the session's active log into the editor (your messages, the agent's thinking, tool calls with their output, replies), then resumes it. That's the whole conversation until the session is archived; after archiving only the newest part |
|
|
122
|
+
| `session/list` | ✅ | Lists this token's own conversations, newest first — what an editor's session history / resume picker shows |
|
|
123
|
+
| `session/prompt` | ✅ | Text and images go through as-is. A file the editor attaches with its content (an @-mention) is inlined into the message as `[resource: <uri>]` plus the content in a code block; a binary attachment becomes `[binary resource omitted: <uri>]`; a file *link* becomes `[resource link: <name> <uri>]` — only a pointer, since the server can't open a path on your laptop. Audio is not supported (dropped with a stderr warning) |
|
|
124
|
+
| `session/cancel` | ✅ | Stops the turn server-side; the interrupted tool calls still show up before the prompt ends as `cancelled` |
|
|
125
|
+
| `session/resume`, modes, config options, `session/delete` / `close`, `logout` | ❌ | Not advertised in `initialize`; calling them anyway gets "method not found" |
|
|
107
126
|
| Reverse `fs/*` | ❌ | See "Reverse fs" below |
|
|
108
127
|
| Reverse `terminal/*` | ❌ | Same reasoning |
|
|
109
128
|
| `requestPermission` | ❌ | Halo has its own access-level system at the channel-account level |
|
|
110
129
|
|
|
130
|
+
Tool calls show in the editor with a kind (read / edit / execute / search / fetch / other) and a one-line title such as `shell_exec: ls -la`. They carry no file locations — the paths are on the server, the editor couldn't open them. Your editor's own MCP servers are not connected to the halo agent.
|
|
131
|
+
|
|
132
|
+
If the connection drops mid-turn (proxy timeout, network blip, server restart), the adapter re-attaches on its own and fills in whatever text it missed; after 5 failed attempts it ends the turn with `[adapter error] connection lost`. Sending a prompt while the session is already busy (another client is mid-turn on it) queues it on the server; the adapter waits for that run and returns the reply to your message when it finishes.
|
|
133
|
+
|
|
111
134
|
### Session id model
|
|
112
135
|
|
|
113
|
-
ACP `sessionId` **is** the halo session id — there's no extra mapping layer. `session/new` asks the server to mint one (`POST /api/web/sessions`); it lands in the token's own `web_<accountId>_` namespace and the `agent_sessions` row exists immediately, so readonly / workspace tokens can drive it too. The
|
|
136
|
+
ACP `sessionId` **is** the halo session id — there's no extra mapping layer. `session/new` asks the server to mint one (`POST /api/web/sessions`); it lands in the token's own `web_<accountId>_` namespace and the `agent_sessions` row exists immediately, so readonly / workspace tokens can drive it too. The adapter holds no on-disk state; losing its in-memory map on restart is harmless because the conversation lives on the halo server.
|
|
114
137
|
|
|
115
|
-
|
|
138
|
+
`session/list` asks the server for the token's own root conversations (every session under its `web_<accountId>_` prefix — including ones a browser client made with the same token; sub-agent sessions excluded). It's scoped to the token even for a `full` token. Each entry's `cwd` is the server-side workspace path, so if your editor filters the list by its local project directory it gets an empty list — that's expected.
|
|
116
139
|
|
|
117
140
|
## Multi-workspace
|
|
118
141
|
|
|
@@ -131,7 +154,7 @@ ACP optionally lets the agent (running on the server) request files from the cli
|
|
|
131
154
|
1. The Web channel is HTTP + SSE — a one-way stream. Reverse fs would need WebSocket or long-poll
|
|
132
155
|
2. Halo agents currently use `file_read` / `file_write` against the **server's** workspace; supporting reverse fs would need a parallel toolset
|
|
133
156
|
|
|
134
|
-
|
|
157
|
+
What works instead: attach the file with its content (e.g. an @-mention in your editor) — the adapter inlines it into the prompt, so the agent reads it. A plain file link only tells the agent the name and path; it can't open it. Or paste the content into the prompt.
|
|
135
158
|
|
|
136
159
|
## Common problems
|
|
137
160
|
|
|
@@ -140,8 +163,10 @@ For now: if you want the agent to see a Mac-side file, paste it into the prompt.
|
|
|
140
163
|
| Adapter exits immediately on launch | Missing required flag, or the token / host are wrong. Check stderr |
|
|
141
164
|
| `401` on first prompt | Token typo, or token was deleted from admin |
|
|
142
165
|
| `403` when launching with `--workspace /some/other/path` | Token is `readonly` / `workspace` access — use a `full` token or omit `--workspace` |
|
|
143
|
-
| Tool
|
|
144
|
-
|
|
|
166
|
+
| Tool call shows no file link / can't jump to the file | Expected — tool paths are on the server, so the adapter sends no `locations`. See [docs/dev/acp-adapter.md](../../dev/acp-adapter.md) for the full mapping |
|
|
167
|
+
| Second prompt rejected while the first is still running | ACP allows one prompt per session at a time — cancel the first, or wait for it |
|
|
168
|
+
| Reply text appears again behind `[reconnected — full reply]` | The stream dropped mid-reply and the missed part couldn't be appended cleanly, so the adapter resent the whole reply. A proxy cutting long streams is the usual cause |
|
|
169
|
+
| Session history picker is empty | If the editor filters by its local project directory, the list comes back empty: halo sessions live under the **server** workspace path |
|
|
145
170
|
| `/workspace switch <path>` worked but other tokens broke | You changed the db-level default. Switch back with another `/workspace switch`, or stop using slash commands from the adapter |
|
|
146
171
|
|
|
147
172
|
## Reference
|
|
@@ -130,7 +130,7 @@ Same set as the other channels — type as plain text, **in a 1:1 DM only** (in
|
|
|
130
130
|
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
131
131
|
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
132
132
|
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
133
|
-
| `/cron` `/
|
|
133
|
+
| `/cron` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
|
|
134
134
|
| `/help` | List commands — object commands show only the verbs you can run |
|
|
135
135
|
|
|
136
136
|
## Cron jobs targeting Feishu
|
|
@@ -132,7 +132,7 @@ Same set as the other channels, but **only in a DM with the bot** — in a chann
|
|
|
132
132
|
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
133
133
|
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
134
134
|
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
135
|
-
| `/cron` `/
|
|
135
|
+
| `/cron` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
|
|
136
136
|
| `/help` | List commands — object commands show only the verbs you can run |
|
|
137
137
|
|
|
138
138
|
## Cron jobs targeting Slack
|
|
@@ -78,7 +78,7 @@ If nothing happens, check halo server logs for `[Telegram]` lines.
|
|
|
78
78
|
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
79
79
|
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
80
80
|
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
81
|
-
| `/cron` `/
|
|
81
|
+
| `/cron` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
|
|
82
82
|
| `/help` | List commands — object commands show only the verbs you can run |
|
|
83
83
|
|
|
84
84
|
## Cron jobs targeting Telegram
|
|
@@ -36,19 +36,20 @@ curl -N -H "x-token: $TOKEN" -H "Content-Type: application/json" \
|
|
|
36
36
|
http://localhost:9527/api/web/chat
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Response is a stream
|
|
39
|
+
Response is a standard SSE stream: `data: {json}` event lines, plus a `: keepalive` comment line every 15 s so an idle proxy doesn't cut the connection during a long tool call:
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
data: {"type":"session","sessionId":"web_abc123_m1xyz"}
|
|
43
43
|
data: {"type":"thinking","text":"..."}
|
|
44
|
-
data: {"type":"tool_call","toolName":"file_read","toolInput":{...}}
|
|
45
|
-
|
|
44
|
+
data: {"type":"tool_call","toolName":"file_read","toolUseId":"toolu_01…","toolInput":{...}}
|
|
45
|
+
: keepalive
|
|
46
|
+
data: {"type":"tool_result","toolName":"file_read","toolUseId":"toolu_01…","result":"..."}
|
|
46
47
|
data: {"type":"stream","text":"Hello! "}
|
|
47
48
|
data: {"type":"stream","text":"How can I help?"}
|
|
48
49
|
data: {"type":"complete"}
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
Parse the `type` field on each event to render text vs tool calls vs completion.
|
|
52
|
+
Parse the `type` field on each event to render text vs tool calls vs completion. **Only lines starting with `data:` are events** — skip everything else (the keepalive comment, blank separators). `EventSource` does this for you; a hand-rolled parser that splits on `\n\n` and slices off `data: ` will break on the keepalive. `toolUseId` pairs a `tool_result` with its `tool_call` when several run at once (it can be empty for some model providers — then pair with the most recent call).
|
|
52
53
|
|
|
53
54
|
### Other endpoints
|
|
54
55
|
|
|
@@ -56,12 +57,13 @@ Parse the `type` field on each event to render text vs tool calls vs completion.
|
|
|
56
57
|
|---|---|---|
|
|
57
58
|
| `POST /api/web/chat` | Send a message; SSE response |
|
|
58
59
|
| `POST /api/web/sessions` | Mint a new root session in the token's namespace; returns `{sessionId}` |
|
|
60
|
+
| `GET /api/web/sessions?cursor=…` | List the token's own conversations, newest first, 50 per page → `{workspace, sessions: [{sessionId, title, updatedAt}], nextCursor}`; pass `nextCursor` back as `cursor` for the next page (`null` = last page) |
|
|
59
61
|
| `POST /api/web/stop` | Cancel the running task |
|
|
60
|
-
| `GET /api/web/history` | Fetch session message history |
|
|
61
|
-
| `GET /api/web/subscribe` | Reconnect to a running session's SSE stream |
|
|
62
|
+
| `GET /api/web/history` | Fetch session message history (optional `since=<epoch ms>`: only rows from then on) |
|
|
63
|
+
| `GET /api/web/subscribe` | Reconnect to a running session's SSE stream (an idle session answers with one `complete` straight away) |
|
|
62
64
|
| `GET /api/web/file?path=…` | Fetch a file from the bound workspace (path relative to it; `.halo` runtime state is refused) |
|
|
63
65
|
|
|
64
|
-
All
|
|
66
|
+
All seven accept the same auth header.
|
|
65
67
|
|
|
66
68
|
### Per-request overrides
|
|
67
69
|
|
|
@@ -102,7 +104,7 @@ Slash commands are intercepted before they reach the agent — same set as every
|
|
|
102
104
|
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
103
105
|
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
104
106
|
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
105
|
-
| `/cron` `/
|
|
107
|
+
| `/cron` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
|
|
106
108
|
| `/help` | List commands — object commands show only the verbs you can run |
|
|
107
109
|
|
|
108
110
|
Send a slash command exactly like a normal message — the server detects the leading `/`.
|
|
@@ -77,7 +77,7 @@ Same set as the other channels — type as plain text in your WeChat DM with the
|
|
|
77
77
|
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
78
78
|
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
79
79
|
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
80
|
-
| `/cron` `/
|
|
80
|
+
| `/cron` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
|
|
81
81
|
| `/help` | List commands — object commands show only the verbs you can run |
|
|
82
82
|
| `/qr [level]` | Generate an invite QR (`full`-access bot only; `level` = `readonly` (default) / `workspace` / `full`) |
|
|
83
83
|
|
|
@@ -105,7 +105,7 @@ Same set as the other channels — type as plain text in a **single chat** (comm
|
|
|
105
105
|
| `/agent <verb>` | Manage agents (`list` / `switch` / `desc` open to all; `delete` full; `create` / `update` via skill, full) |
|
|
106
106
|
| `/skill <verb>` | Manage skills (`list` / `desc` open; `disable` / `enable` workspace; `delete` full; `create` / `update` via skill, full) |
|
|
107
107
|
| `/workspace <verb>` | Workspace: `info` (all) / `switch <path>` (full) / `setup` / `tidy` (workspace) / `share` (full) |
|
|
108
|
-
| `/cron` `/
|
|
108
|
+
| `/cron` `/extension` | Skill-backed object commands (full access); `/evo [hint]` queues a self-evolution run |
|
|
109
109
|
| `/help` | List commands — object commands show only the verbs you can run |
|
|
110
110
|
|
|
111
111
|
## Cron jobs targeting WeCom
|
|
@@ -68,7 +68,6 @@ Most commands are noun-verb **object commands**: `/<obj> <verb> [args]`. Bare `/
|
|
|
68
68
|
| `/skill <verb>` | Manage skills — `list` / `desc` (built-in, open to all) · `disable` / `enable` (built-in, workspace access) · `delete` (built-in, full access) · `create` / `update` (handled by the `skill` skill, full access) |
|
|
69
69
|
| `/workspace <verb>` | Manage the workspace — `info` (built-in, open to all) · `switch <path>` (built-in, full access) · `setup` / `tidy` (workspace skill, workspace access; init / reorganize `.halo/` INDEX.md / INSTRUCTIONS.md / memory/) · `share` (workspace skill, full access; export a shareable bundle) |
|
|
70
70
|
| `/cron <verb>` | Scheduled agent runs — `create` / `list` / `update` / `enable` / `disable` / `delete` (cron skill, full access) |
|
|
71
|
-
| `/acp <verb>` | Talk to other agents over ACP — `kiro <q>` / `claude <q>` ask a local agent directly; `add` / `list` / `remove` manage generated `ask-<label>` bindings (acp skill, full access) |
|
|
72
71
|
| `/extension <verb>` | Canvas preview extensions — `install <id\|zip\|url>` / `list` / `remove <id>` (extension skill, full access) |
|
|
73
72
|
| `/evo [hint]` | Queue a self-evolution run on this session (full access only) |
|
|
74
73
|
| `/help` | List every command — object commands only show the verbs you can run |
|
|
@@ -143,7 +143,6 @@ Multi-turn conversation. Supports all standard Halo slash commands:
|
|
|
143
143
|
| `/workspace switch <path>` | Switch workspace (full access only) |
|
|
144
144
|
| `/workspace setup` / `/workspace tidy` / `/workspace share` | Set up / tidy the `.halo/` knowledge files, or export a shareable bundle (workspace skill) |
|
|
145
145
|
| `/cron <verb>` | Manage scheduled agent runs: `create` / `list` / `update` / `enable` / `disable` / `delete` |
|
|
146
|
-
| `/acp <verb>` | Ask other agents over ACP (`kiro <q>` / `claude <q>`) and manage `ask-*` bindings (`add` / `list` / `remove`) |
|
|
147
146
|
| `/extension <verb>` | Canvas preview extensions: `install` / `list` / `remove`; `models` updates the model provider list from the hub (full access only) |
|
|
148
147
|
| `/evo [hint]` | Queue a self-evolution run on this session (full access only) |
|
|
149
148
|
| `/quit` | Exit |
|
|
@@ -181,7 +180,7 @@ The line under the input box:
|
|
|
181
180
|
| `Ctrl+W` / `Alt+Backspace` | Delete the word before the cursor |
|
|
182
181
|
| `Ctrl+U` / `Ctrl+K` | Delete to start / to end of input |
|
|
183
182
|
|
|
184
|
-
On resume (`-s <id>`, or the default latest session), the TUI replays the session's prior conversation on screen so you see where you left off
|
|
183
|
+
On resume (`-s <id>`, or the default latest session), the TUI replays the session's prior conversation on screen so you see where you left off — only the last 100 messages; anything older collapses into a first line `── N earlier messages hidden · ctrl+o for the full log ──`. `shell_exec` output is shown inline by default (other tools' output needs `-v`). Tool lines show a short argument summary and duration, e.g. `⚙ file_read hello.txt 4ms`.
|
|
185
184
|
|
|
186
185
|
**Multi-line paste**: pasting text with newlines (or >800 chars) collapses into a compact placeholder like `[#1 pasted 3 lines]` in the input; you can keep typing around it, and the original text is expanded back in full on submit. Pasted content is inserted at the cursor position.
|
|
187
186
|
|
|
@@ -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 — and the same check at the start of `halo cli` / `halo tui` — 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` — and a CLI-only user just runs `halo cli` / `halo tui` after `halo upgrade`.
|
|
81
81
|
|
|
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`, `
|
|
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`, `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
|
|
|
@@ -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`, `
|
|
33
|
+
**Built-in skills** (`agent`, `skill`, `workspace`, `cron`, `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
|
|
|
@@ -154,7 +154,6 @@ Halo seeds these skills on every startup (the ids in `BUILTIN_SKILL_IDS`, `packa
|
|
|
154
154
|
| skill | Create / update skills — backs the `create` / `update` verbs of `/skill` |
|
|
155
155
|
| workspace | Workspace maintenance — backs `/workspace setup` / `tidy` (init / reorganize `.halo/` INDEX.md / INSTRUCTIONS.md / memory/) and `/workspace share` (export a shareable bundle) |
|
|
156
156
|
| cron | Create / list / update / enable / disable / delete scheduled agent runs — backs `/cron` |
|
|
157
|
-
| acp | Talk to other agents over ACP (`/acp kiro\|claude <q>`) and manage `ask-<label>` bindings for halo-to-halo delegation (`/acp add\|list\|remove`) |
|
|
158
157
|
| 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
158
|
| self | The agent's own visual space (`.halo/canvas/self.html`) for self-expression (no command; model-activated, full access) |
|
|
160
159
|
| aws-knowledge | Query the official AWS Knowledge MCP server for up-to-date AWS docs (no command, `user-invocable: false`; model auto-activates) |
|
|
@@ -8,6 +8,7 @@ The primary surface for talking to an agent.
|
|
|
8
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
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
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)
|
|
11
|
+
- While it can be changed, the Bot icon is primary-coloured (same "active" colour as the other toolbar toggles); locked, it stays muted grey and dimmed, and hovering it explains that the agent can't change once the conversation has started — start a new session to use another (`chat.agent.locked`)
|
|
11
12
|
- The Agents panel's "Test" button can also preselect an agent
|
|
12
13
|
|
|
13
14
|
### Access level
|
|
@@ -96,9 +97,22 @@ When `contextEnabled` is on (default), user messages are auto-prepended with:
|
|
|
96
97
|
- Drag to chat input
|
|
97
98
|
- Clipboard paste
|
|
98
99
|
- File-picker button
|
|
100
|
+
- Screenshot button (below)
|
|
99
101
|
|
|
100
102
|
Images ride along as base64; multimodal supported. Before sending, every attachment is downscaled (long edge ≤ 1568) and re-encoded as JPEG; an SVG is rasterized the same way (long edge 1024, on white), since vision input only takes jpeg / png / gif / webp. An image the browser can't decode (e.g. HEIC / TIFF in Chrome) and that isn't one of those four is refused when attached, with an inline notice naming the file — it is never sent only to be dropped server-side. 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).
|
|
101
103
|
|
|
104
|
+
### Screenshot (drag-select a region)
|
|
105
|
+
A Scissors button in the toolbar's input group (upload · **screenshot** · screen · camera) takes one still of the screen and lets the user crop it into an image attachment:
|
|
106
|
+
- Click → a frozen frame opens in a full-window crop layer over the admin (dark backdrop, area outside the selection dimmed). Drag a box (dragging again redraws it); **Enter**, double-click or ✓ confirms, **Esc** or ✕ cancels. Confirming with no box (or one under 8 px) attaches the whole frame. Enter here never sends the message.
|
|
107
|
+
- The crop is cut from the full-resolution frame and attached as `screenshot-YYYYMMDD-HHMMSS.png` — from then on it's an ordinary attachment (chip, X to remove, downscaled to JPEG on send like any upload).
|
|
108
|
+
- Grey at rest, primary colour while the picker / crop layer is open; clicking it then cancels.
|
|
109
|
+
- Frame source differs by client:
|
|
110
|
+
- **Desktop client** (mac / win): no picker — grabs the display the Halo window is on (only that one on multi-monitor), Halo itself visible in the shot. Needs macOS **Screen Recording**; without it the permission hint shows in the attachment-notice row.
|
|
111
|
+
- **Browser**: the browser's own `getDisplayMedia` picker every time (a page can't read the screen otherwise), one frame, stream stopped right away. Picking "Entire screen" includes the browser's own "sharing" bar — crop it out. Cancelling the picker does nothing.
|
|
112
|
+
- An older desktop client without the screenshot bridge falls back to the browser path (Halo's own source picker).
|
|
113
|
+
- Independent of live capture: never reads or changes a bound screen share.
|
|
114
|
+
- Shown only when a frame source exists and the selected agent's model accepts images — same gate as live capture (no button on mobile browsers).
|
|
115
|
+
|
|
102
116
|
### Inline media chips
|
|
103
117
|
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.
|
|
104
118
|
|
|
@@ -6,7 +6,7 @@ Unified command processing — all slash commands (built-in + skill) are dispatc
|
|
|
6
6
|
|
|
7
7
|
Source: [commands/index.ts](../../../packages/server/src/commands/index.ts) (descriptors) + [channels/shared/commands.ts](../../../packages/server/src/channels/shared/commands.ts) (execution)
|
|
8
8
|
|
|
9
|
-
Top-level built-in slash commands: `/help` `/evo` `/session` `/agent` `/skill` `/workspace`. (`/cron`
|
|
9
|
+
Top-level built-in slash commands: `/help` `/evo` `/session` `/agent` `/skill` `/workspace`. (`/cron` looks like a top-level command too but is provided by a same-name skill — see the verb table below; it isn't registered in `commands/index.ts` or `DISPATCH_COMMANDS`.)
|
|
10
10
|
|
|
11
11
|
| Name | Slash | Type | Purpose |
|
|
12
12
|
|---|---|---|---|
|
|
@@ -22,7 +22,6 @@ The rest are noun-verb **object commands**: `/<obj> <verb> [args]`. Some verbs a
|
|
|
22
22
|
| `/skill` | list / desc (built-in, no gate) · disable / enable (built-in, workspace) · delete (built-in, full) · create / update (skill verb, full) |
|
|
23
23
|
| `/workspace` | info (built-in, no gate) · switch (built-in, full) · setup / tidy (skill verb, workspace) · share (skill verb, full) — `/w` is a built-in alias (see [Command aliases](#command-aliases)) |
|
|
24
24
|
| `/cron` | create / list / update / enable / disable / delete — all skill verbs, full |
|
|
25
|
-
| `/acp` | kiro / claude (ask a local agent directly; question = rest of line) · add / list / remove (manage generated ask-* bindings) — all full |
|
|
26
25
|
|
|
27
26
|
Session lifecycle actions (`subscribe` / `unsubscribe`, `session:delete`) are handled inline by the WS handler, not as slash commands. In the admin, a bare `/session new` (and `/clear`) is handled client-side: it opens a new draft chat tab and sends nothing; `/session new <args>` still goes to the server. The WS `session:clear` frame was removed in 1.5.3-alpha.
|
|
28
27
|
|
|
@@ -21,6 +21,7 @@ Desktop app only — the browser has one tab per window natively.
|
|
|
21
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.
|
|
22
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
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`).
|
|
24
|
+
- **Right-click menu for page text**: Electron shows no menu by default, so the shell adds one where a browser would show its native menu — in an editable field (chat input, settings fields): Cut / Copy / Paste / Select all; on selected non-editable text: Copy; elsewhere: none. Menus the admin draws itself (file tree, editor tabs, terminal, Monaco) suppress it, so there's never a double menu. Labels follow the OS locale (zh → Chinese). Lives in `packages/desktop/src/main.cjs` (`buildContextMenuTemplate`), so it ships with the next desktop build.
|
|
24
25
|
- **Pin (always-on-top)** acts per-window — each window pins independently.
|
|
25
26
|
- **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.
|
|
26
27
|
- **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.
|
|
@@ -47,6 +47,19 @@ When the WS drops, the bottom panel remounts `TerminalPanel` (fresh key on `_dis
|
|
|
47
47
|
- Terminal type: `xterm-256color`
|
|
48
48
|
- Default size: 80 × 24 (resize requests override)
|
|
49
49
|
- Strips `npm_config_prefix` env (avoids nvm warnings when starting node)
|
|
50
|
+
- Font: JetBrains Mono 13px, line height 1 (no extra leading — box-drawing / TUI rows join up)
|
|
51
|
+
|
|
52
|
+
### Copy & paste
|
|
53
|
+
Keys depend on the client's OS (the keyboard in front of the user — the PTY is always the server's):
|
|
54
|
+
|
|
55
|
+
| Client | Copy | Paste |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| macOS | `Cmd+C` | `Cmd+V` |
|
|
58
|
+
| Windows / Linux | `Ctrl+C` **with a selection** (copies and clears the selection; no selection → `^C` / SIGINT as usual) · `Ctrl+Shift+C` | `Ctrl+V` · `Ctrl+Shift+V` (literal `^V` is given up) |
|
|
59
|
+
|
|
60
|
+
- Keyboard copy / paste ride the browser's native copy / paste events (paste stays bracketed), so they work on a plain-http origin too
|
|
61
|
+
- **Right-click menu** (inside the terminal): Copy (disabled with no selection) · Paste · Select all · Clear. Menu copy / paste use the async Clipboard API; when paste is refused (plain-http origin or no clipboard-read permission) the menu says to use the keyboard shortcut instead
|
|
62
|
+
- Key mapping: `terminalClipboardKey` in [packages/admin/src/features/terminal/terminal-clipboard.ts](../../../packages/admin/src/features/terminal/terminal-clipboard.ts); menu in `terminal-context-menu.tsx`
|
|
50
63
|
|
|
51
64
|
### Lifecycle
|
|
52
65
|
|
|
@@ -111,6 +124,8 @@ Defined in [packages/server/src/config.ts](../../../packages/server/src/config.t
|
|
|
111
124
|
| T6 | Open 3 tabs, close 1 explicitly | Other 2 keep their PTYs; closed one gets `terminal:exit` |
|
|
112
125
|
| T7 | `exit` from within shell | `terminal:exit` with the shell's exit code; the terminal prints `[Process exited]` and its tab stays until closed |
|
|
113
126
|
| T8 | Paste a 10 KB block | Sent as `terminal:input` without choking; shell echoes in chunks |
|
|
127
|
+
| T9 | Windows/Linux: select text → `Ctrl+C`; then `Ctrl+C` again with nothing selected | First copies and clears the selection; second sends `^C` (interrupts a running `sleep 30`) |
|
|
128
|
+
| T10 | Right-click in the terminal → Paste | Clipboard text inserted at the prompt (or the "use the shortcut" hint on a plain-http origin) |
|
|
114
129
|
|
|
115
130
|
Follows the pattern of [test/session.md](../test/session.md).
|
|
116
131
|
|