@turmind/halo 1.5.11 → 1.6.1
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/README.md +2 -2
- package/admin-out/404.html +1 -1
- package/admin-out/_next/static/chunks/{941-4719e7343b54b9bd.js → 147-d4200c70445aabb6.js} +1 -1
- package/admin-out/_next/static/chunks/58.d26b1c4f14404226.js +1 -0
- package/admin-out/_next/static/chunks/989-ddba0eb72566da23.js +1 -0
- package/admin-out/_next/static/chunks/app/{layout-5ec6c5f713a8562b.js → layout-ea161287d934ba96.js} +1 -1
- package/admin-out/_next/static/chunks/app/page-be47983ba8ff20b1.js +1 -0
- package/admin-out/_next/static/chunks/webpack-74f577c1b094f942.js +1 -0
- package/admin-out/_next/static/css/3b4dcd0ac6b8eae8.css +1 -0
- package/admin-out/_next/static/css/7b736202f0b1dbfd.css +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +6 -5
- package/bundled-docs/design/a2a.md +286 -0
- package/bundled-docs/design/agent.md +6 -4
- package/bundled-docs/design/agentcore.md +194 -122
- package/bundled-docs/design/architecture.md +2 -2
- package/bundled-docs/design/canvas-extensions.md +59 -12
- package/bundled-docs/design/cron.md +22 -11
- package/bundled-docs/design/observability.md +12 -4
- package/bundled-docs/design/prompt-system.md +10 -5
- package/bundled-docs/design/relay.md +1 -1
- package/bundled-docs/design/session.md +7 -2
- package/bundled-docs/design/storage.md +40 -10
- package/bundled-docs/design/web.md +5 -56
- package/bundled-docs/design/ws.md +1 -1
- package/bundled-docs/dev/add-model-provider.md +15 -9
- package/bundled-docs/dev/api.md +93 -14
- package/bundled-docs/dev/deploy.md +5 -1
- package/bundled-docs/dev/desktop-packaging.md +11 -5
- package/bundled-docs/dev/env.md +9 -5
- package/bundled-docs/dev/previews.md +26 -0
- package/bundled-docs/dev/tools.md +93 -3
- package/bundled-docs/guide/agents.md +6 -6
- package/bundled-docs/guide/channels/web.md +0 -26
- package/bundled-docs/guide/cli.md +45 -8
- package/bundled-docs/guide/delegation-and-access.md +3 -2
- package/bundled-docs/guide/skills.md +13 -7
- package/bundled-docs/guide/workspace.md +1 -1
- package/bundled-docs/requirements/agent.md +6 -1
- package/bundled-docs/requirements/chat.md +9 -3
- package/bundled-docs/requirements/command.md +4 -0
- package/bundled-docs/requirements/editor.md +28 -8
- package/bundled-docs/requirements/skill.md +15 -9
- package/bundled-docs/requirements/workspace.md +6 -0
- package/dist/index.js +6081 -3013
- package/package.json +1 -1
- package/templates/models/anthropic.yaml +109 -13
- package/templates/models/aws-bedrock-claude-invoke.yaml +76 -11
- package/templates/models/aws-bedrock-mantle.yaml +60 -28
- package/templates/models/aws-bedrock-openai.yaml +146 -29
- package/templates/models/deepseek.yaml +7 -3
- package/templates/models/doubao.yaml +72 -20
- package/templates/models/hunyuan.yaml +74 -14
- package/templates/models/kimi.yaml +63 -3
- package/templates/models/mimo-token-plan-china.yaml +9 -1
- package/templates/models/minimax.yaml +9 -3
- package/templates/models/openai.yaml +120 -17
- package/templates/models/qwen.yaml +90 -4
- package/templates/models/zhipu.yaml +9 -1
- package/templates/prompts/all/RUNTIME.md +2 -1
- package/templates/schema.sql +1 -1
- package/templates/skills/cron/SKILL.md +38 -2
- package/templates/skills/cron/manage-cron.py +115 -7
- package/templates/skills/send-file/SKILL.md +1 -1
- package/templates/skills/send-file/a2a.md +13 -0
- package/templates/skills/skill/SKILL.md +22 -11
- package/templates/skills/workspace/share.md +5 -2
- package/templates/skills/workspace/stage.py +51 -14
- package/admin-out/_next/static/chunks/220.7566bc89f3d98993.js +0 -1
- package/admin-out/_next/static/chunks/989-86b0f677f69ccf3b.js +0 -1
- package/admin-out/_next/static/chunks/app/page-4792aa1c79f7e7f1.js +0 -1
- package/admin-out/_next/static/chunks/webpack-efd27999305b080a.js +0 -1
- package/admin-out/_next/static/css/97f3ac570664d044.css +0 -3
- /package/admin-out/_next/static/{GO2W5JoiC46FIxLjtFAXx → j6buZnQDPISEFgtAuTGyl}/_buildManifest.js +0 -0
- /package/admin-out/_next/static/{GO2W5JoiC46FIxLjtFAXx → j6buZnQDPISEFgtAuTGyl}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Agent Tool Reference
|
|
2
2
|
|
|
3
|
-
Agents have two tool categories: workspace tools (files, shell, search) and session tools (managing other sessions). Workspace tools are enabled by name in `agent.yaml`'s `tools` list; the session-tool bundle is granted automatically by a non-empty `team` (see [Session tools](#session-tools)).
|
|
3
|
+
Agents have two tool categories: workspace tools (files, shell, search) and session tools (managing other sessions). Workspace tools are enabled by name in `agent.yaml`'s `tools` list; the session-tool bundle is granted automatically by a non-empty `team` (see [Session tools](#session-tools)). Two opt-in sets reach beyond the workspace: [Relay tools](#relay-tools) (sessions in *other* workspaces on the same server) and [A2A tools](#a2a-tools) (agents on *other* servers).
|
|
4
4
|
|
|
5
5
|
## Workspace tools
|
|
6
6
|
|
|
@@ -180,7 +180,7 @@ Sensitive directories and files are hidden from workspace/readonly sessions via
|
|
|
180
180
|
| Setting | Built-in (always included) | Method |
|
|
181
181
|
|---|---|---|
|
|
182
182
|
| `hidden_dirs` | `~/.halo/secrets,~/.aws,~/.ssh,~/.gnupg,~/.docker,~/.config/gh,~/.halo/global/internal-sessions,~/.halo/global/logs` | bwrap `--tmpfs` overlay (empty directory); Seatbelt `subpath` deny |
|
|
183
|
-
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs}.db` + their `-wal`/`-shm` files | bwrap `--ro-bind ~/.halo/.sandbox-empty` (reads as empty); Seatbelt `literal` deny |
|
|
183
|
+
| `hidden_files` | `~/.npmrc,~/.bash_history,~/.gitconfig,~/.git-credentials,~/.netrc,~/.halo/global/{evo,cron,runs,a2a}.db` + their `-wal`/`-shm` files | bwrap `--ro-bind ~/.halo/.sandbox-empty` (reads as empty); Seatbelt `literal` deny |
|
|
184
184
|
| `writable_dirs` | (empty) | bwrap `--bind` read-write / Seatbelt write allow — for external CLIs that keep local state (e.g. `~/.kiro`); not applied to readonly sessions |
|
|
185
185
|
|
|
186
186
|
Changes saved through the settings API (admin Settings page, `PUT` / `PATCH` / `DELETE /api/settings`) take effect immediately — the save fires `onSettingsChange`, which re-reads the lists into `sandbox.ts` (`setSandboxHiddenPaths`), so the next tool call uses them. A hand edit of `settings.yaml` is not watched for these keys: it applies at the next restart or the next API save. These keys are `globalOnly` in the schema — a workspace `settings.yaml` cannot override them, since they define the security boundary agents run inside.
|
|
@@ -443,6 +443,96 @@ List a workspace's **root** sessions — most recently active first, capped at 1
|
|
|
443
443
|
|
|
444
444
|
Returns `{ "code": 0, "workspace": "<realpath>", "sessions": [{ id, agentId, agentName, title, status, createdAt, updatedAt }], "count": N }`. `title` falls back to `description` like `session_list`; `status` follows the list semantics (`running` when the root itself or any live child is mid-turn, `stopped` when the row is stamped, else `idle`).
|
|
445
445
|
|
|
446
|
+
## A2A tools
|
|
447
|
+
|
|
448
|
+
File: `packages/server/src/a2a/outbound.ts` (`buildA2ATools`). Design notes in [design/a2a.md](../design/a2a.md).
|
|
449
|
+
|
|
450
|
+
Cross-**server** dispatch over A2A v1.0: the agent sends a message to a remote A2A agent (normally another halo server's exposed workspace) and the result is pushed back into this session when the remote is done, no polling. Remotes are configured once per server in `~/.halo/secrets/a2a-remotes.yaml` (no per-workspace file; `<ws>/.halo/a2a-remotes.yaml` is not read):
|
|
451
|
+
|
|
452
|
+
```yaml
|
|
453
|
+
remotes:
|
|
454
|
+
halo-test:
|
|
455
|
+
card: http://myhost.tailnet.ts.net:9527/a2a/halo-test/.well-known/agent-card.json
|
|
456
|
+
auth: bearer # bearer (default) | sigv4 (an AgentCore runtime)
|
|
457
|
+
# push_base: http://172.31.7.121:9527 # optional webhook base for this remote
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
A `bearer` remote's token is the settings secret `a2a.secrets.<remote>` (workspace `settings.yaml` overrides global; `<<ENV>>` works), resolved at call time and never shown to the model. When non-full sessions use the tools, put it in the global `~/.halo/secrets/settings.yaml` — a workspace's `.halo/settings.yaml` is readable to them, `~/.halo/secrets` is not. The list itself is full-only: `~/.halo/secrets` is hidden from workspace / readonly sessions, so they can use the remotes but can't repoint one's `card:` at a host of their choosing and collect its token. The remote's webhook goes to `push_base`, else `general.a2a.public_url`; with neither set `a2a_send` fails. Every remote URL passes the A2A URL policy (`general.a2a.url_allowlist`).
|
|
461
|
+
|
|
462
|
+
**Enabling**: opt-in by the single name `a2a_send` in `agent.yaml`'s `tools:` — it brings `a2a_stop` / `a2a_read` / `a2a_list`; the other three names are not recognised on their own. Available at **every access level** (full, workspace, readonly, any channel) **except the A2A read-only profile** — an inbound A2A session at readonly never gets them, whatever its yaml lists, so a remote's readonly token can't make this server call third parties. The admin tool picker shows one `a2a_send` chip naming the set.
|
|
463
|
+
|
|
464
|
+
**Server only**: in the CLI / TUI — and every cron run, which is a `halo cli` child — there is no push receiver; every A2A tool returns `{"code": 1, "error": "a2a is not available in the CLI / TUI — it only runs inside `halo server`, which receives the remote agent's push reports. This is permanent for this runtime, not a temporary outage: do not retry. …"}`. The tools are still listed there when the agent names `a2a_send`.
|
|
465
|
+
|
|
466
|
+
**Retries**: each request has a 30 s timeout. An AgentCore `-32054` "… please retry" answer is retried after 0.5 / 1 / 2 / 4 / 8 s, and a halo AgentCore container's lease-busy answer (`-32603` "workspace is in use by another runtime session …") after 5 / 10 / 15 / 20 s; then the last answer is returned as the error. Stopping the turn ends the wait at once — for the RPC and for the agent-card fetch before it.
|
|
467
|
+
|
|
468
|
+
Errors are `{ "code": 1, "error": "…" }`; a remote JSON-RPC error reads `<Method>: <message> (<code>)`.
|
|
469
|
+
|
|
470
|
+
### a2a_send
|
|
471
|
+
|
|
472
|
+
Send a message to a remote. Omit `context_id` to start a new conversation; pass the `context_id` from an earlier call for a follow-up — a follow-up to a busy remote is queued and softly interrupts its current step, `interrupt: true` aborts that step instead. Sent as SendMessage with `returnImmediately: true` plus a per-dispatch webhook (a follow-up on a context with a pending dispatch reuses its webhook, so the report comes once). Returns immediately.
|
|
473
|
+
|
|
474
|
+
| Arg | Type | Required | Description |
|
|
475
|
+
|---|---|---|---|
|
|
476
|
+
| `remote` | string | yes | Remote name from `~/.halo/secrets/a2a-remotes.yaml` (see `a2a_list`) |
|
|
477
|
+
| `message` | string | yes | The message to send |
|
|
478
|
+
| `context_id` | string | no | Conversation to continue (from an earlier `a2a_send`); omit to start one |
|
|
479
|
+
| `interrupt` | boolean | no | Follow-up only: abort the remote's current step instead of waiting for it |
|
|
480
|
+
| `files` | string[] | no | Absolute paths of images to attach |
|
|
481
|
+
|
|
482
|
+
**`files`**: png / jpeg / gif / webp only (by extension, and the bytes must agree), ≤5 MB each, ≤10 MB total, each sent as a `{ raw, mediaType, filename }` part after the text. A non-full session may only attach files under the workspace or the OS temp dir — the path and its realpath, so a symlink pointing outside is refused; full access has no path limit. Any violation fails the whole call (`files: <path>: <reason> — nothing was sent`).
|
|
483
|
+
|
|
484
|
+
Returns `{ "code": 0, "remote", "context_id", "task_id", "state": "working" | …, "follow_up": boolean }`.
|
|
485
|
+
|
|
486
|
+
**Report delivery**: when the remote task ends, its push is injected into this session (append + send, like a user message):
|
|
487
|
+
|
|
488
|
+
```
|
|
489
|
+
[A2A report · remote <name> · context <C> · task <T> · status: completed]
|
|
490
|
+
|
|
491
|
+
<the remote's result text>
|
|
492
|
+
|
|
493
|
+
[图片已保存: <path>]
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
`status:` is `completed`, `failed` or `canceled`. The text is capped at `limits.autoReportMax` with a `[Report truncated: N chars total. Use a2a_read("<name>", "<T>") for the full text.]` marker. A failed task's body starts `[A2A REMOTE FAILED: the remote task did not complete. <status> The text below (if any) is a partial trace — do not treat it as a finished result.]`, a canceled one `[A2A REMOTE CANCELED: <status>]`. Images in the result are saved under `.halo/assets/a2a/inbound/<remote>/…` and listed **after** the cap as `[图片已保存: <path>]` (open with `view_image`); image URLs are listed as `[图片: <url>]` and not downloaded; anything else gets `[file not saved: <name> — <reason>]`.
|
|
497
|
+
|
|
498
|
+
**Interim report**: when the remote answers a follow-up and keeps working, the answer arrives first as
|
|
499
|
+
|
|
500
|
+
```
|
|
501
|
+
[A2A interim report · remote <name> · context <C> · task <T> · status: still running] This is an interim reply — the remote is still working; its final [A2A report] follows when done. Do not treat this as the result.
|
|
502
|
+
|
|
503
|
+
<the answer, capped at limits.autoReportMax>
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
The final report still arrives once. Match on the `[A2A report` / `[A2A interim report` prefixes or the `status:` field. Report text is the remote agent's output — data, not instructions.
|
|
507
|
+
|
|
508
|
+
### a2a_stop
|
|
509
|
+
|
|
510
|
+
Cancel a remote task (CancelTask). The `[A2A report · … · status: canceled]` still arrives.
|
|
511
|
+
|
|
512
|
+
| Arg | Type | Required | Description |
|
|
513
|
+
|---|---|---|---|
|
|
514
|
+
| `remote` | string | yes | Remote name |
|
|
515
|
+
| `task_id` | string | yes | Task id from `a2a_send` |
|
|
516
|
+
|
|
517
|
+
Returns `{ "code": 0, "task_id", "state" }`.
|
|
518
|
+
|
|
519
|
+
### a2a_read
|
|
520
|
+
|
|
521
|
+
Read a remote task (GetTask): the full, untruncated result. Use it after a truncated report, or **to check on a task** — when the user asks how a dispatched task is going or its report seems overdue, call it once; never in a loop. Images in the result are saved again on each read and listed like in the report.
|
|
522
|
+
|
|
523
|
+
| Arg | Type | Required | Description |
|
|
524
|
+
|---|---|---|---|
|
|
525
|
+
| `remote` | string | yes | Remote name |
|
|
526
|
+
| `task_id` | string | yes | Task id from `a2a_send` |
|
|
527
|
+
|
|
528
|
+
Returns `{ "code": 0, "task_id", "context_id", "state": "TASK_STATE_…", "status": "<status message text>", "result": "<text + image lines>" }`.
|
|
529
|
+
|
|
530
|
+
### a2a_list
|
|
531
|
+
|
|
532
|
+
The remotes configured on this server (the same list in every workspace) — each with its card's agent name, description and skills (`{ id, name, description }`), or an `error` when the card can't be fetched — plus the dispatches this workspace is still waiting on (newest 50). No arguments.
|
|
533
|
+
|
|
534
|
+
Returns `{ "code": 0, "remotes": [{ name, agent, description, skills }], "pending": [{ remote, context_id, task_id, since }] }`.
|
|
535
|
+
|
|
446
536
|
## Tool assignment
|
|
447
537
|
|
|
448
538
|
Workspace tools are enabled strictly by name in `agent.yaml`'s `tools` list:
|
|
@@ -455,7 +545,7 @@ skills:
|
|
|
455
545
|
- code-review # auto-injects activate_skill
|
|
456
546
|
```
|
|
457
547
|
|
|
458
|
-
Tools not listed are not injected. Session/delegation tools do **not** go in `tools:` — they ride on a non-empty `team` (see [Session tools](#session-tools) above). `activate_skill` is auto-injected whenever the YAML lists `skills` (no need to put it in `tools`), and `continue_task` is auto-injected for **every** agent unconditionally (see [continue_task](#continue_task)). The relay
|
|
548
|
+
Tools not listed are not injected. Session/delegation tools do **not** go in `tools:` — they ride on a non-empty `team` (see [Session tools](#session-tools) above). `activate_skill` is auto-injected whenever the YAML lists `skills` (no need to put it in `tools`), and `continue_task` is auto-injected for **every** agent unconditionally (see [continue_task](#continue_task)). The relay and A2A sets are the two name-gated bundles: listing `relay_send` alone brings `relay_interrupt` / `relay_stop` / `relay_read` / `relay_list` with it, full-access sessions only (see [Relay tools](#relay-tools)); listing `a2a_send` alone brings `a2a_stop` / `a2a_read` / `a2a_list`, at every access level except the A2A read-only profile (see [A2A tools](#a2a-tools)).
|
|
459
549
|
|
|
460
550
|
There is **no implicit default tool set**: `filterTools()` (in `agent-loader.ts`) returns only the tools whose names appear in `agent.yaml`'s `tools:` list. If the field is absent or empty, the agent has zero workspace tools. The admin UI's "Create agent" form scaffolds a fresh agent with an empty `tools: []` for the same reason — fill it in deliberately. The `default` agent's bundled `agent.yaml` lists the common set (`file_read` / `file_write` / `file_edit` / `view_image` / `file_list` / `shell_exec` / `grep` / `glob` / `web_fetch`) that most agents will want; copy that line if you're starting from scratch.
|
|
461
551
|
|
|
@@ -44,7 +44,7 @@ Data-driven form for name / description / model / tools / skills / thinking. Cha
|
|
|
44
44
|
**Model section** has four fields:
|
|
45
45
|
- `provider` — dropdown populated from `~/.halo/global/models/*.yaml` plus hub-installed `~/.halo/global/models.d/*.yaml` (`/extension models`; updates the list live)
|
|
46
46
|
- `id` — combobox (input + datalist); preset options filtered by current provider, also accepts manual input of any model ID
|
|
47
|
-
- `endpoint` — combobox (input + datalist); full endpoint URL (e.g. `https://bedrock-runtime.ap-northeast-1.amazonaws.com`), supports custom proxy URLs
|
|
47
|
+
- `endpoint` — combobox (input + datalist); full endpoint URL (e.g. `https://bedrock-runtime.ap-northeast-1.amazonaws.com`), supports custom proxy URLs. When a model is only served in some regions the list narrows to those, switching to it moves the endpoint there, and an endpoint off its list shows a warning
|
|
48
48
|
- `maxTokens` — optional; defaults to `maxOutputTokens` in the provider yaml
|
|
49
49
|
|
|
50
50
|
Switching provider resets the model id to the new provider's default model (its `defaultModelId`), keeping the current id when the new provider lists it too.
|
|
@@ -141,8 +141,8 @@ priority: 0
|
|
|
141
141
|
|
|
142
142
|
model:
|
|
143
143
|
provider: aws-bedrock-claude-invoke # required, matches ~/.halo/global/models/<provider>.yaml
|
|
144
|
-
id: global.anthropic.claude-
|
|
145
|
-
endpoint: https://bedrock-runtime.us-
|
|
144
|
+
id: global.anthropic.claude-opus-5-5 # any model id the provider lists (or a custom one)
|
|
145
|
+
endpoint: https://bedrock-runtime.us-east-1.amazonaws.com # full endpoint URL; supports custom proxy
|
|
146
146
|
maxTokens: 16384 # optional, max output tokens (default from provider yaml)
|
|
147
147
|
|
|
148
148
|
# Prompt caching — lower cost on repeated system prompts
|
|
@@ -161,8 +161,8 @@ system_prompt: |
|
|
|
161
161
|
|
|
162
162
|
# Context window
|
|
163
163
|
context:
|
|
164
|
-
maxTokens: 272000 #
|
|
165
|
-
compressAt: 0.9 # auto-compact trigger (0.9 = compact when 90% full)
|
|
164
|
+
maxTokens: 272000 # context budget — new agents get the model's recommended window; can't exceed the model's max
|
|
165
|
+
compressAt: 0.9 # auto-compact trigger (0.9 = compact when 90% full); new agents get your global setting, else the model's
|
|
166
166
|
|
|
167
167
|
# Tool allowlist (strict by name; unlisted tools are not injected).
|
|
168
168
|
# Session/delegation tools are NOT listed here — see `team` below.
|
|
@@ -191,7 +191,7 @@ team:
|
|
|
191
191
|
**Full tool list**: see [dev/tools.md](../dev/tools.md).
|
|
192
192
|
|
|
193
193
|
**Common edits**:
|
|
194
|
-
- Agent handling long tasks → `context.maxTokens
|
|
194
|
+
- Agent handling long tasks → raise `context.maxTokens` up to the model's max (the form shows it, e.g. 1M for Opus 5.5) + `context.compressAt: 0.85` — on most 1M-class models input past 272K bills at long-context rates
|
|
195
195
|
- Save cost → `model.promptCaching: '1h'`
|
|
196
196
|
- Complex reasoning → `thinking.enabled: true` + `thinking.effort: high`
|
|
197
197
|
- Read-only agent → keep only `file_read` / `file_list` / `grep` / `glob` in `tools`
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Talk to a halo agent over plain HTTP from any client you control — a browser, a curl script, a custom frontend, your own mobile app. The Web channel is the "build your own UI" channel: it gives you a token, you give the token to whatever client you wrote.
|
|
4
4
|
|
|
5
|
-
> If you want a ready-made browser frontend without writing code, halo ships `packages/web-demo` — a tiny Express app that wraps the Web channel with its own password gate. See "Standalone web-demo frontend" at the bottom.
|
|
6
|
-
|
|
7
5
|
## What you'll end up with
|
|
8
6
|
|
|
9
7
|
- A 24-byte random token (base64url-encoded) bound to one workspace
|
|
@@ -92,8 +90,6 @@ For browser apps you almost never want these — leave them off and use the per-
|
|
|
92
90
|
|
|
93
91
|
Images go to the LLM as multimodal content. Any other `mimeType` (audio, PDF, …) in the same array is not sent to the model: the server saves it to `<workspace>/.halo/assets/web/inbound/<accountId>/<date>/` and appends a `[语音已保存: <path>]` line to your message so the agent can open it with its file tools.
|
|
94
92
|
|
|
95
|
-
The standalone web-demo handles voice + arbitrary files for you; if you want that, use it as a reference implementation.
|
|
96
|
-
|
|
97
93
|
## Slash commands
|
|
98
94
|
|
|
99
95
|
Slash commands are intercepted before they reach the agent — same set as every other channel:
|
|
@@ -120,28 +116,6 @@ Send a slash command exactly like a normal message — the server detects the le
|
|
|
120
116
|
| Token leaked accidentally | Delete the account in admin, create a new one. The old token is invalidated immediately |
|
|
121
117
|
| Want to share one token across multiple users | Don't — every request would land on the same active session. Create one account per user / app |
|
|
122
118
|
|
|
123
|
-
## Standalone web-demo frontend
|
|
124
|
-
|
|
125
|
-
If you want a browser UI with login, image upload, voice recording, and multi-language support without writing it yourself:
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
cd packages/web-demo
|
|
129
|
-
node server.js
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Environment variables:
|
|
133
|
-
|
|
134
|
-
| Variable | Required | Description |
|
|
135
|
-
|---|---|---|
|
|
136
|
-
| `HALO_API` | yes | Halo server URL, e.g. `http://localhost:9527` |
|
|
137
|
-
| `HALO_TOKEN` | yes | The web-channel token from Step 1 |
|
|
138
|
-
| `HALO_WEB_DEMO_PASSWORD` | no | Password gate; empty means open access |
|
|
139
|
-
| `PORT` | no | Listen port; default `9528` |
|
|
140
|
-
|
|
141
|
-
The demo holds your token server-side and exposes its own password-based session — the browser never sees the halo token. Open `http://localhost:9528` to use it.
|
|
142
|
-
|
|
143
|
-
Alternatively, the gear icon (header / login screen) opens **direct-connect mode**: enter any halo server's URL + a web-channel token and the browser talks to that server's `/api/web/*` directly, skipping the proxy and its password. Handy for pointing one deployed web-demo at several halo servers. The token is kept in that browser's localStorage — only use it on devices you trust; clear it in the same panel to return to proxy mode.
|
|
144
|
-
|
|
145
119
|
## Security notes
|
|
146
120
|
|
|
147
121
|
- **Tokens are unhashed** in `~/.halo/secrets/channels/channels.db`. Restrict that file to the user that runs halo
|
|
@@ -64,8 +64,9 @@ To list agents or sessions without running a prompt, use the dedicated subcomman
|
|
|
64
64
|
| Flag | Short | Default | Description |
|
|
65
65
|
|---|---|---|---|
|
|
66
66
|
| `--workspace` | `-w` | cwd | Workspace path |
|
|
67
|
-
| `--
|
|
68
|
-
| `--
|
|
67
|
+
| `--dir` | | root | Focus the session on a workspace sub-folder — same as a sub-agent's `working_dir`: the folder's `.halo/INSTRUCTIONS.md` chain joins the system prompt, plus a `Working directory:` line. Workspace-relative or absolute; must exist inside the workspace. On a resumed session it re-points the stored folder in place (one `[cli] session <id>: dir <from> → <to>` line on stderr); `--dir .` moves it back to the root |
|
|
68
|
+
| `--agent` | `-a` | `default` | Agent ID. A session's agent never changes: `-s <existing>` with a different `-a` is refused (`session <id> runs agent <x>; omit -a to resume it, or use -n …`); without `-s`, the latest cli session is only resumed when it runs the `-a` agent — otherwise a new session starts |
|
|
69
|
+
| `--session` | `-s` | auto | Resume session by ID; created with that id when it doesn't exist |
|
|
69
70
|
| `--new` | `-n` | off | Always start a new session |
|
|
70
71
|
| `--format` | `-f` | `text` | Output format: `text` or `json` |
|
|
71
72
|
| `--verbose` | `-v` | off | Show thinking, tool calls, usage on stderr |
|
|
@@ -129,7 +130,7 @@ Multi-turn conversation. Supports all standard Halo slash commands:
|
|
|
129
130
|
| Command | Description |
|
|
130
131
|
|---|---|
|
|
131
132
|
| `/help` | Show available commands |
|
|
132
|
-
| `/session new` | Start a new session |
|
|
133
|
+
| `/session new` | Start a new session — it keeps the current session's folder (`--dir`; from a sub-agent, its root's), or starts at the root if that folder is gone |
|
|
133
134
|
| `/session list` | List recent sessions |
|
|
134
135
|
| `/session switch <n>` | Switch to session by number |
|
|
135
136
|
| `/session stop` | Stop current agent task (ends the turn, no re-run) |
|
|
@@ -210,6 +211,16 @@ Attach files or images to your message with `@file` and `@image`, or pull a dire
|
|
|
210
211
|
- **Images**: skipped if larger than 5MB
|
|
211
212
|
- If the current model does not support vision, a warning is shown and images are ignored
|
|
212
213
|
|
|
214
|
+
## OpenTelemetry export
|
|
215
|
+
|
|
216
|
+
`halo cli` and `halo tui` export traces, metrics and logs the same way the server does: when the global `general.observability.endpoint` setting is non-empty, each run registers the OTLP exporters at start and the agent loop emits the same GenAI spans (`invoke_agent` / `chat` / `execute_tool`). With no endpoint nothing is loaded and startup is unaffected. The scheduled runs of [Cron Tasks](../design/cron.md) and the evolution pipeline are spawned `halo cli` children, so they show up in the collector too (`session.id` `cron-<jobId>` or the internal agent's name).
|
|
217
|
+
|
|
218
|
+
- **Telling processes apart**: they all share `service.name`; the `halo.process` resource attribute is `server`, `cli` or `tui`, and cron / evolution runs report `cli`.
|
|
219
|
+
- **Flushed on exit**: the batched telemetry is sent before the process ends — on normal completion, TUI quit, an error, and on SIGINT / SIGTERM (after the graceful stop, still exiting 130 / 143). The flush is capped at 3 s, so an unreachable collector delays the exit by at most that and never hangs it.
|
|
220
|
+
- `halo agents` and `halo sessions` only list and don't export.
|
|
221
|
+
|
|
222
|
+
Settings and the span / metric model: [design/observability.md](../design/observability.md).
|
|
223
|
+
|
|
213
224
|
## Model provider configs (`halo models`)
|
|
214
225
|
|
|
215
226
|
```bash
|
|
@@ -217,7 +228,7 @@ halo models install <dir> [--yes] # install <dir>/*.yaml into ~/.halo/global/m
|
|
|
217
228
|
halo models list # id · copy in effect (bundled | hub) · revision · runtime
|
|
218
229
|
```
|
|
219
230
|
|
|
220
|
-
Normally run for you by `/extension models`, which downloads the hub's newest `models-v*` release (https GitHub / Gitea / Forgejo / GitLab only) and passes the unpacked directory to `halo models install`. Per provider id the copy with the higher `revision` is in effect (tie → the `models.d` copy); an older one is skipped, an identical one reported up to date. Refused per provider: a `runtime:` this halo doesn't know (needs a newer halo), no `revision`, a `secrets[].default` that isn't empty or `<<ENV_NAME>>`.
|
|
231
|
+
Normally run for you by `/extension models`, which downloads the hub's newest `models-v*` release (https GitHub / Gitea / Forgejo / GitLab only) and passes the unpacked directory to `halo models install`. Per provider id the copy with the higher `revision` is in effect (tie → the `models.d` copy); an older one is skipped, an identical one reported up to date. Refused per provider: a `runtime:` this halo doesn't know (needs a newer halo), no `revision`, a `secrets[].default` that isn't empty or `<<ENV_NAME>>`. Endpoint changes against the copy in effect are always listed. Consent is needed for any URL the server would now send requests (and credentials) to that it didn't before: a new provider, a changed `defaultEndpoint` / `endpointPresets`, or a URL added to a model's `endpoints:` list that the provider doesn't already use anywhere. A model list that only loses URLs, or gains one already used elsewhere in the provider, is listed but needs no consent; a reorder alone is not a change. When consent is needed, **nothing is written** and the exit code is `3` — re-run with `--yes` to apply. Writes are atomic (tmp + rename); a running server reloads its registry from `models.d/` without a restart and pushes the change to open admin pages. Existing agents keep the model / endpoint in their own `agent.yaml`. Exit codes: `0` ok (refusals listed in the summary line), `1` error, `3` needs `--yes`. To undo, delete the file from `~/.halo/global/models.d/`.
|
|
221
232
|
|
|
222
233
|
## Server Mode (background daemon)
|
|
223
234
|
|
|
@@ -231,6 +242,31 @@ recognized as intentional and never respawns; `halo server stop|restart|status`
|
|
|
231
242
|
still target the server's own pid. Full policy in
|
|
232
243
|
[dev/deploy.md](../dev/deploy.md#crash-semantics--why-restarton-failure-is-load-bearing).
|
|
233
244
|
|
|
245
|
+
## AgentCore runtime (`halo agentcore`)
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
halo agentcore --workspace /mnt/efs/halo [-p 9000]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Runs the server in the foreground as an Amazon Bedrock AgentCore Runtime
|
|
252
|
+
container speaking the A2A protocol (`HALO_RUNTIME_MODE=agentcore-a2a`): `GET
|
|
253
|
+
/ping`, `POST /` (A2A JSON-RPC) and `GET /.well-known/agent-card.json` on one
|
|
254
|
+
fixed workspace. No admin UI, password, channels, cron or evolution — AgentCore
|
|
255
|
+
verifies the caller's signed request before it reaches the container. It always
|
|
256
|
+
runs in the foreground (no `-d`) — meant as the container's entry command.
|
|
257
|
+
|
|
258
|
+
| Flag / env | Meaning |
|
|
259
|
+
|---|---|
|
|
260
|
+
| `-w, --workspace <path>` / `HALO_WORKSPACE` | Workspace to serve — required, must exist. Seeded with `.halo/agent-card.json` if missing. |
|
|
261
|
+
| `-p, --port <n>` | Listen port, default `9000` (the AgentCore contract) |
|
|
262
|
+
| `HALO_A2A_PUBLIC_URL` | Interface URL advertised in the card — the runtime's invoke URL (`…/runtimes/<escaped ARN>/invocations`) |
|
|
263
|
+
| `HALO_A2A_ACCESS` | Session access level: `workspace` (default) \| `full` \| `readonly` |
|
|
264
|
+
|
|
265
|
+
One runtime session id owns the workspace at a time (`.halo/agentcore.lease`);
|
|
266
|
+
requests on another id are refused until the holder has been gone ~45 s. Design,
|
|
267
|
+
the caller-side `auth: sigv4` remote and ops notes:
|
|
268
|
+
[design/agentcore.md](../design/agentcore.md).
|
|
269
|
+
|
|
234
270
|
## Architecture
|
|
235
271
|
|
|
236
272
|
```
|
|
@@ -260,7 +296,8 @@ Session prefix: `cli_`. Sessions are persisted to `<workspace>/.halo/sessions/`
|
|
|
260
296
|
|
|
261
297
|
`tui` / `cli` / `agents` / `sessions` refuse to run ("~/.halo/global/ not initialized. Run halo setup first.") until `halo setup` has seeded `~/.halo/global/`. Then the CLI replicates the server's init sequence (`initRuntime()` in `harness.ts`):
|
|
262
298
|
1. `refreshTemplatesIfOutdated()` — the server's template startup check: when `~/.halo/global/.template-version` is behind the bundled `TEMPLATE_VERSION` (typically right after `halo upgrade`), re-seed `~/.halo/global/` before anything reads the models registry; logs `[CLI] Templates outdated (vX → vY), refreshing ~/.halo/global/` to stderr. Runs once per upgrade — afterwards the stamp matches and it's a single file read
|
|
263
|
-
2. `
|
|
264
|
-
3. `
|
|
265
|
-
4. `
|
|
266
|
-
5. `
|
|
299
|
+
2. `initObservability()` — `tui` / `cli` only (the list-only `agents` / `sessions` skip it): when `general.observability.endpoint` is set, register OTel providers so this run's traces, metrics and logs reach the collector, tagged `halo.process=cli` or `tui`; a no-op otherwise. Cron jobs and evolution runs are spawned `halo cli` children, so they export too. See [OpenTelemetry export](#opentelemetry-export)
|
|
300
|
+
3. `initLogger()` — redirect console to stderr + file logger
|
|
301
|
+
4. `initBwrapCheck()` — probe sandbox availability
|
|
302
|
+
5. `setSandboxHiddenPaths()` — configure sandbox paths
|
|
303
|
+
6. `new SessionManager(workspace)` — create agent session manager (the workspace's `.halo/` is seeded via `ensureWorkspaceHalo`)
|
|
@@ -103,7 +103,7 @@ Extra rules:
|
|
|
103
103
|
- A readonly `shell_exec` gets no secret substitution — `{{…params…}}` / `<<ENV>>` placeholders stay literal.
|
|
104
104
|
- `view_image` also needs a vision-capable model.
|
|
105
105
|
- A skill with `requiresAccess` above the session level is hidden (no metadata, no `activate_skill`).
|
|
106
|
-
- Relay tools are only built for `full` sessions.
|
|
106
|
+
- Relay tools are only built for `full` sessions. A2A tools (`a2a_send` in `tools:`) are built at every level except the A2A read-only profile; a non-full session's `files` must sit in the workspace or the temp dir ([dev/tools.md → A2A tools](../dev/tools.md#a2a-tools)).
|
|
107
107
|
- Hidden from `workspace` / `readonly` sessions only (Full sessions, the admin file explorer and the terminal see everything): a built-in list that is always included (`~/.halo/secrets`, `~/.aws`, `~/.ssh`, `~/.gnupg`, `~/.docker`, `~/.config/gh`, `~/.gitconfig`, `~/.git-credentials`, `~/.npmrc`, `~/.netrc`, the global evo/cron/runs databases, internal-session transcripts, logs, …), any extra paths added in `general.sandbox.hidden_dirs` / `hidden_files` (they add to the built-in list, never replace it), plus the workspace's own `.halo/sessions`, `.halo/logs`, `.halo/evo`, `.halo/halo.db*` (fixed in code). The rest of `.halo/` stays readable.
|
|
108
108
|
|
|
109
109
|
### Where a session's level comes from
|
|
@@ -117,7 +117,8 @@ Extra rules:
|
|
|
117
117
|
| Cron job | Full (the `halo cli` default) | Keeps its stored level (passed through as `--access`) |
|
|
118
118
|
| `halo cli` / TUI | `--access`, default `full` | `--access` value (a different level is persisted) |
|
|
119
119
|
| `relay_send` into another workspace | Full | Keeps its stored level |
|
|
120
|
-
|
|
|
120
|
+
| A2A inbound (`/a2a/<path>`, a web-channel token bound to that workspace) | The token's `accessLevel`, mapped like a channel account. A `readonly` / `observer` token gets the fixed A2A read-only profile: `file_read` / `view_image` / `file_list` / `grep` / `glob` + `continue_task`, even with an OS sandbox — no shell, write or fetch, no delegation, no relay / a2a tools | Re-applied on every inbound message |
|
|
121
|
+
| AgentCore runtime (`halo agentcore`) | `HALO_A2A_ACCESS`: `workspace` (default) \| `full` \| `readonly` — every A2A session gets it | Re-applied on every inbound message |
|
|
121
122
|
|
|
122
123
|
Consequences:
|
|
123
124
|
- A sub-agent can never have more access than its parent had when it was spawned. A readonly channel user can't escalate by delegating.
|
|
@@ -26,9 +26,15 @@ Putting those in AGENT.md would bloat the system prompt. Skills use **progressiv
|
|
|
26
26
|
deploy-check/
|
|
27
27
|
SKILL.md
|
|
28
28
|
scripts/verify.sh
|
|
29
|
+
|
|
30
|
+
~/.halo/global/extensions/<ext>/skills/ # Shipped by an installed extension (read-only)
|
|
31
|
+
<ext>-helper/
|
|
32
|
+
SKILL.md
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
Precedence for the same id: workspace > global > extension. The higher folder wins wholly.
|
|
36
|
+
|
|
37
|
+
**Extension skills** come with an installed canvas extension (see [Canvas preview extensions](../design/canvas-extensions.md#extension-skills)). They appear in the Skills panel under **Extensions** ("from extension X"), read-only: no edit, no delete. They are not enabled automatically: tick them for an agent like any other skill. Uninstalling the extension removes them. To customise one, copy its folder into `~/.halo/global/skills/<id>/` or `<project>/.halo/skills/<id>/` and edit the copy, which then takes precedence.
|
|
32
38
|
|
|
33
39
|
**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
40
|
|
|
@@ -75,7 +81,7 @@ You can edit SKILL.md, add scripts, templates, example docs.
|
|
|
75
81
|
|
|
76
82
|
Click the `+` on the **Global** or **Workspace** group header in the sidebar (the group you click decides the scope) and type a name.
|
|
77
83
|
|
|
78
|
-
Delete: hover a skill and click its trash icon (after a confirmation). The toggle next to it disables / enables the skill.
|
|
84
|
+
Delete: hover a skill and click its trash icon (after a confirmation). The toggle next to it disables / enables the skill. The **Extensions** group has no `+` and no trash. Its skills go away when the extension is uninstalled, but the toggle works.
|
|
79
85
|
|
|
80
86
|
## Letting an agent use a skill
|
|
81
87
|
|
|
@@ -113,10 +119,10 @@ The agent follows the skill's instructions.
|
|
|
113
119
|
|
|
114
120
|
## Resource files
|
|
115
121
|
|
|
116
|
-
Files in the same directory as SKILL.md are listed at the end of `activate_skill`'s return:
|
|
122
|
+
Files in the same directory as SKILL.md are listed at the end of `activate_skill`'s return, under the skill directory's absolute path:
|
|
117
123
|
|
|
118
124
|
```
|
|
119
|
-
Resource files in skill directory:
|
|
125
|
+
Resource files in skill directory (/home/me/.halo/global/skills/code-review):
|
|
120
126
|
- checklist.md
|
|
121
127
|
- examples/review-sample.md
|
|
122
128
|
```
|
|
@@ -154,14 +160,14 @@ Halo seeds these skills on every startup (the ids in `BUILTIN_SKILL_IDS`, `packa
|
|
|
154
160
|
| skill | Create / update skills — backs the `create` / `update` verbs of `/skill` |
|
|
155
161
|
| workspace | Workspace maintenance — backs `/workspace setup` / `tidy` (init / reorganize `.halo/` INDEX.md / INSTRUCTIONS.md / memory/) and `/workspace share` (export a shareable bundle) |
|
|
156
162
|
| cron | Create / list / update / enable / disable / delete scheduled agent runs — backs `/cron` |
|
|
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) |
|
|
163
|
+
| send-file | Deliver an image/video/file as a channel attachment by emitting `MEDIA:<absolute_path>` — works on Web / WeChat / Telegram / Slack / Feishu / WeCom / A2A (no command; model-activated, workspace access) |
|
|
158
164
|
| self | The agent's own visual space (`.halo/canvas/self.html`) for self-expression (no command; model-activated, full access) |
|
|
159
165
|
| aws-knowledge | Query the official AWS Knowledge MCP server for up-to-date AWS docs (no command, `user-invocable: false`; model auto-activates) |
|
|
160
166
|
| web-search | Real-time web search on AWS Bedrock (needs the machine's AWS credentials with Bedrock access) in two gears: fast (Amazon Nova grounding, default; US regions only) and deep (`--deep`, GPT-5.6 `web_search` via Bedrock Mantle, slower and token-expensive); regions set via its `fast_region` / `deep_region` params (Settings → Skills) (no command, `user-invocable: false`; model auto-activates) |
|
|
161
167
|
| halo | Platform self-knowledge — config directories, MD scopes, which bundled doc answers what (no command, `user-invocable: false`; model auto-activates) |
|
|
162
168
|
| extension | Install / list / remove canvas preview extensions — backs `/extension` (full access); its `hub_repo` param (Settings → Skills) picks the hub `install <id>` pulls from (default turmind/halo-hub) |
|
|
163
169
|
|
|
164
|
-
All live under `~/.halo/global/skills/` and are force-overwritten on startup. To customize one, copy it into `<project>/.halo/skills/<id>/` and edit there (workspace
|
|
170
|
+
All live under `~/.halo/global/skills/` and are force-overwritten on startup. To customize one, copy it into `<project>/.halo/skills/<id>/` and edit there (workspace > global > extension). `tavily-web-search` remains an optional (non-seeded) skill.
|
|
165
171
|
|
|
166
172
|
## SKILL.md field reference
|
|
167
173
|
|
|
@@ -206,7 +212,7 @@ user-invocable: false # optional (standard), never becomes a slas
|
|
|
206
212
|
|
|
207
213
|
{body}
|
|
208
214
|
|
|
209
|
-
Resource files in skill directory:
|
|
215
|
+
Resource files in skill directory (<absolute skill dir>):
|
|
210
216
|
- checklist.md
|
|
211
217
|
- examples/review.md
|
|
212
218
|
...
|
|
@@ -124,7 +124,7 @@ Common settings:
|
|
|
124
124
|
- `general.session.max_nesting_depth` — max session nesting depth
|
|
125
125
|
- `general.compact.keep_messages` — recent messages kept intact during compaction
|
|
126
126
|
- `general.logging.level` — `debug` / `info` / `warn` / `error`
|
|
127
|
-
- `general.observability.endpoint` — OTLP collector URL; leave empty to keep observability off (see [design/observability.md](../design/observability.md))
|
|
127
|
+
- `general.observability.endpoint` — OTLP collector URL; leave empty to keep observability off. The server and every `halo cli` / `halo tui` run (cron and evolution runs included) export to it (see [design/observability.md](../design/observability.md))
|
|
128
128
|
- `<provider-id>.secrets.api_key` — provider credentials (Kimi / DeepSeek bearer token)
|
|
129
129
|
- `<provider-id>.secrets.access_key_id` / `.secret_access_key` — AWS Bedrock
|
|
130
130
|
- `<skill-id>.params.<key>` — values an agent can inject into its own `shell_exec` via `{{<skill-id>.params.<key>}}`
|
|
@@ -22,6 +22,11 @@ Left sidebar has two collapsible groups (Global / Workspace — the latter only
|
|
|
22
22
|
|
|
23
23
|
### Form / Edit dual view
|
|
24
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
|
+
- **Model / context fields** in the form:
|
|
26
|
+
- **Endpoint list**: the dropdown offers the model's own `endpoints:` when the registry declares them, else the provider's `endpointPresets`; free text stays allowed.
|
|
27
|
+
- **Auto-switch on model change**: switching to a model with its own list moves the endpoint to that model's default when the current one (or, left empty, the provider default) isn't on the list, with a one-line note naming the new endpoint; a model without a list leaves the endpoint alone. Switching provider sets the new model's default endpoint.
|
|
28
|
+
- **Off-list warning**: a saved / typed endpoint outside the model's own list shows an amber "not listed on this endpoint — requests may fail" hint (not blocked).
|
|
29
|
+
- **Context**: switching model moves `context.maxTokens` / `compressAt` to the new model's recommended values when the old ones were the old model's untouched recommendation (or the window exceeds the new model's max); hand-set values that still fit are kept. The `context.maxTokens` input shows "Model max N" (the registry `maxContextWindow`); an over-max draft turns red ("Exceeds model max N — not saved") and is never auto-saved, and the server refuses it too (400), except a value already on disk for the same model. An empty or non-positive draft is ignored — the saved value stays.
|
|
25
30
|
- **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
31
|
- The chosen view is remembered per agent in `localStorage`
|
|
27
32
|
|
|
@@ -55,7 +60,7 @@ Same id present in both scopes: workspace wins; the overridden global is greyed
|
|
|
55
60
|
|
|
56
61
|
### Tool selection
|
|
57
62
|
- **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` / `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)
|
|
63
|
+
- **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) and the single `a2a_send` chip (grants the cross-server A2A set — `a2a_send` / `a2a_stop` / `a2a_read` / `a2a_list` — at every access level except the A2A read-only profile), 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
64
|
- **`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
65
|
|
|
61
66
|
### Team (delegation switch + whitelist)
|
|
@@ -23,7 +23,8 @@ The primary surface for talking to an agent.
|
|
|
23
23
|
- Markdown + code-block highlighting
|
|
24
24
|
- Tool-call card: expandable, shows tool name / input / output
|
|
25
25
|
- 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")
|
|
26
|
-
- A reply that has produced nothing yet shows a spinner with "Thinking..."
|
|
26
|
+
- A reply that has produced nothing yet shows a spinner with "Thinking..."; once the model streams reasoning text, the spinner gives way to a **live thinking panel**: expanded, muted, with a "Thinking..." header and char count, capped at about 9 lines and tailing its own text (so the chat log grows by at most the cap). It folds to the one-line collapsed "Thinking · N chars" header (click to expand; it stays in the log) as soon as text or a tool call follows, or the message settles. Providers that stream no reasoning text stay on the spinner; Debug mode is unchanged (spinner until text or a tool call arrives, collapsed headers only)
|
|
27
|
+
- **Follow the bottom by intent**: while a reply streams the log follows its end, and scrolling up stops the follow. Upward input — mouse wheel, touch drag, PageUp / ArrowUp / Home / Shift+Space, or grabbing the scrollbar — detaches the moment it arrives, so a fast stream can't snap the view back mid-gesture; upward input that a nested scroller (the live thinking panel, an expanded tool body) would take doesn't count. The log re-attaches only when the view is scrolled back down to within 80 px of the end. Applies to the chat panel, the Sessions tab viewer and the live thinking panel alike; a reader scrolling inside the thinking panel stops its tail the same way
|
|
27
28
|
- 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
29
|
- 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
30
|
|
|
@@ -59,10 +60,12 @@ The workspace's root sessions as **Chrome-style vertical tabs** on the right edg
|
|
|
59
60
|
- **Rows**: one line per row — just the title, no leading icon cell (the "New session" draft row has none either, so the titles line up). Message count, time-ago, model and the state text (`Working…` / `New messages` / `Idle`) are in the tooltip, the state after a ` · ` at the end of its second line. `N msgs` counts the session's user turns **over its whole lifetime**, so compacting or archiving history never makes it go backwards.
|
|
60
61
|
- Each row carries a **2px status bar** along its bottom edge, full row width with 6px inset on both sides and rounded ends (absolutely positioned, so a state change never reflows a narrow list — the spinner it replaced did; decorative, `aria-hidden`): **amber, breathing** (`animate-pulse`) while a loaded tab's turn is running; **blue**, solid, 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. Green is the norm, so it is **dimmed** (`/40`) to recede into the background, and rises to `/70` on the selected and hovered row; amber and blue never dim. The list endpoint's `status` is not used. In the light and warm themes amber renders as the theme's darker orange, same as the rest of the UI.
|
|
61
62
|
- Infinite scroll pages older sessions. A "+" footer starts a new session.
|
|
63
|
+
- **Hover peek (collapsed list)**: resting the mouse on the collapsed rail for 300 ms slides the **full list** (header + rows) in over the chat to its left, at the saved width, without reflowing the chat; leaving closes it after a 200 ms grace. Touch never opens it. It stays open while the row menu, a rename input or the delete confirm started from it is open, and selecting a row closes it. Collapsing by mouse click does not peek straight back open under the pointer until it leaves and re-enters; the peek header's expand button pins the list open.
|
|
62
64
|
- **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
65
|
- **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
|
-
- **
|
|
65
|
-
- **
|
|
66
|
+
- **Row menu (Rename / Delete)**: rows carry no hover buttons. Right-click a row (long-press ~500 ms on touch; moving or lifting early cancels) to open a menu at the pointer with **Rename** (inline rename) and **Delete session** (red). Esc or a click outside closes it. The draft row and the collapsed squares have no menu.
|
|
67
|
+
- **Delete**: deletes the session after a confirm dialog in the UI language ("Delete this session? Its history cannot be recovered." / its Chinese counterpart). Deletion is the REST delete first, then the WS one, so sub-session files go too.
|
|
68
|
+
- **Inline rename**: menu → 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
69
|
- **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
70
|
- **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
|
|
68
71
|
|
|
@@ -98,6 +101,9 @@ When `contextEnabled` is on (default), user messages are auto-prepended with:
|
|
|
98
101
|
- Clipboard paste
|
|
99
102
|
- File-picker button
|
|
100
103
|
- Screenshot button (below)
|
|
104
|
+
- Canvas image **Annotate → Send to chat** (`<name>-annotated.png`; see `requirements/editor.md` → Image annotation)
|
|
105
|
+
|
|
106
|
+
On an agent whose model has no image input, an annotated image is not attached — the attachment-notice row says the model doesn't take images (`chat.attachNoVision`).
|
|
101
107
|
|
|
102
108
|
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).
|
|
103
109
|
|
|
@@ -25,6 +25,10 @@ The rest are noun-verb **object commands**: `/<obj> <verb> [args]`. Some verbs a
|
|
|
25
25
|
|
|
26
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.
|
|
27
27
|
|
|
28
|
+
### `/session new`
|
|
29
|
+
|
|
30
|
+
Built-in verb, all users. Creates a new root session with the workspace's entry agent and switches the caller to it. The new session **keeps the current session's folder** (its `working_dir`, e.g. from `halo tui --dir sub`): from a sub-agent, its root's folder. The folder is re-checked; if it no longer exists, the new session starts at the workspace root.
|
|
31
|
+
|
|
28
32
|
### `/session info`
|
|
29
33
|
|
|
30
34
|
Built-in verb (no access gate beyond ownership). Prints the **session tree** for the caller's current root session — the root plus every descendant sub-agent, so the user can see the whole delegation fan-out at a glance:
|
|
@@ -26,10 +26,15 @@ Monaco-based multi-tab code editor + binary file previewer — surfaced in the U
|
|
|
26
26
|
- Server returns the new `modifiedAt`; the tab updates its mtime
|
|
27
27
|
|
|
28
28
|
### Auto-refresh (mtime-based)
|
|
29
|
-
When agents write files
|
|
30
|
-
1.
|
|
31
|
-
2.
|
|
32
|
-
3.
|
|
29
|
+
When agents or scripts write files, the canvas picks it up — push-based, no polling:
|
|
30
|
+
1. A WS `file:changed` `change` **or `add`** event (`add` = a file renamed over the open one, e.g. `sed -i` or a tmp-file replace) refreshes every open buffer of that path, in all panes. Tab switch, window focus and WS reconnect re-check only the focused pane's active tab (`GET /api/files/stat`)
|
|
31
|
+
2. Only a disk mtime strictly newer than the buffer's counts
|
|
32
|
+
3. Text tab: pull fresh content — skipped if the user has unsaved edits (don't overwrite)
|
|
33
|
+
4. Built-in preview: remounts with a versioned URL (fresh fetch / parse, no stale browser copy), so its view state (zoom, page, annotation marks) resets. Extension preview: not remounted — its host reloads the file inside the running extension (see *Editable extensions*). Bundle directories are skipped
|
|
34
|
+
5. HTML preview: reloads only when the `.html` file itself changes and its text tab has no unsaved edits; changes to linked CSS / JS / images don't trigger it
|
|
35
|
+
6. The pinned face (`self.html`) is excluded — a reload would reset it
|
|
36
|
+
|
|
37
|
+
With no session selected no `file:changed` arrives (the WS subscribes on session select), so only the tab-switch / focus / reconnect checks apply.
|
|
33
38
|
|
|
34
39
|
### Tab persistence
|
|
35
40
|
Open tabs and the active tab live in localStorage; survive refresh.
|
|
@@ -47,7 +52,7 @@ Non-text files go through `FilePreview`, which looks up a **plugin** in the prev
|
|
|
47
52
|
- **Parquet** — server-paginated table (schema + 100-row pages), same pager as CSV/SQLite
|
|
48
53
|
- **SQLite / DB (.db / .sqlite / .sqlite3)** — server-paginated table with a table-selector sidebar (name + row count per table); opened read-only, one page (100 rows) per request
|
|
49
54
|
- **PPTX / PPT** — `pptx-preview` list mode. Fidelity is approximate — complex animations / SmartArt / embedded fonts may not render perfectly; users can download for the exact source. Flagged `heavy: true` so only the *active* pptx mounts (canvas-based rendering needs the main thread). **Speaker-notes sidebar**: notes are extracted from the pptx zip in presentation (play) order and listed on the left; clicking a note scrolls to the corresponding slide (a domIndex mapping bridges play order to pptx-preview's part-filename render order, so reordered decks still land on the right slide). Collapsible with the state remembered in localStorage (`halo.pptxNotesHidden`); the sidebar doesn't appear at all when no slide has notes. Decks with dangling `[Content_Types].xml` overrides (e.g. some WPS exports) are repaired before rendering; if zero slides render anyway, a real error UI is shown instead of a silent black wrapper
|
|
50
|
-
- **Images / video / audio** — `<img>` (via `SafeImage`: pulsing placeholder while loading, "Image unavailable" box on failure, never the broken-image icon;
|
|
55
|
+
- **Images / video / audio** — `<img>` (via `SafeImage`: pulsing placeholder while loading, "Image unavailable" box on failure, never the broken-image icon; wheel / toolbar zoom, left- or middle-drag pan, reset; **Annotate** — below) / native `<video>` / `<audio>`, supports HTTP Range for seek-without-full-download. Markdown-preview images use the same placeholder.
|
|
51
56
|
|
|
52
57
|
All previews share a **standard header** (`PreviewShell`) with filename, Download, Open-as-Text, and an `extraToolbar` slot for plugin-specific buttons (e.g. DOCX Print, XLSX sheet tabs).
|
|
53
58
|
|
|
@@ -55,6 +60,14 @@ Fetched via `GET /api/files/download?inline=1` (supports Range, streams on the s
|
|
|
55
60
|
|
|
56
61
|
Adding new file types is a plugin concern — see `dev/previews.md` for the extension guide.
|
|
57
62
|
|
|
63
|
+
### Image annotation
|
|
64
|
+
An image preview's toolbar has **Annotate**: a drawing board over the picture to mark it up and send it to the agent. Offered only once the image has loaded, not for SVG (may have no intrinsic size), and only in the main Canvas — not the Skills / Agents editors, whose chat is hidden.
|
|
65
|
+
- Tools: pen / rectangle / arrow; colours red (default) / yellow / green / blue; undo (also Ctrl/Cmd+Z), clear. Lines are drawn 4 px wide at the current zoom. A flat rectangle or zero-length arrow is dropped
|
|
66
|
+
- Zoom and pan stay live: wheel / toolbar zoom, middle-drag pans (left / touch drag draws). A second finger or a palm while drawing is ignored — it neither pans nor draws
|
|
67
|
+
- **Send to chat** renders the image at full resolution (long edge capped at 4096, marks scaled with it) as `<name>-annotated.png`, attaches it to the chat input like any upload, closes the board and puts the caret in the chat input — un-maximizing Canvas and switching the bottom panel to Chat if needed. On an agent whose model has no image input nothing is attached; the attachment-notice row says so. If rendering fails, an inline error shows and the marks stay
|
|
68
|
+
- **Esc**, ✕ or the Annotate button again exits; while maximized the first Esc only exits annotate. Esc and Ctrl/Cmd+Z don't act while typing in an input / Monaco
|
|
69
|
+
- Marks are never saved: exiting, switching the pane to another tab, or the file changing on disk discards them
|
|
70
|
+
|
|
58
71
|
### Preview extensions (installable viewers)
|
|
59
72
|
File types without a built-in plugin can be handled by an **installed extension**: a static HTML bundle under `~/.halo/global/extensions/<id>/` (server-wide, not per workspace) that Canvas loads in a sandboxed iframe (scripts + same-origin, like the HTML preview — an extension is trusted like a skill the user chose to install). Design: `design/canvas-extensions.md`.
|
|
60
73
|
- **Resolution order** for a file extension: `default`-priority extensions (newest install wins) → built-in plugins → `option`-priority extensions. `.glb` with the `glb` extension installed opens in the viewer directly; an `option` extension only shows up in the header's **Open with** menu
|
|
@@ -63,7 +76,9 @@ File types without a built-in plugin can be handled by an **installed extension*
|
|
|
63
76
|
- **Fallback**: a file with no built-in plugin and no installed extension shows a static page — "no built-in preview for this type; extensions for more file types are on <hub> ↗", where `<hub>` is the extension skill's `hub_repo` (default halo-hub; a local path / ssh remote shows as text) — plus Open as text / Download. No online lookup
|
|
64
77
|
- **Editable extensions** (manifest `capabilities: ["save"]`): the iframe reports dirty → tab shows the dirty dot → header **Save** button (Ctrl/Cmd+S is *not* wired for extension tabs — Monaco isn't mounted) → `PUT /api/files/raw`. If the file changed on disk since it was loaded the save gets a 409 and the user is asked "changed on disk … Overwrite?". An external change while the tab is clean reloads it; while dirty, local edits are kept silently and the conflict surfaces at the next save
|
|
65
78
|
- **Export from an editable extension** (e.g. draw.io *File ▸ Export as*, Excalidraw main menu *Export PNG / SVG*): the extension iframe can't download files, so the exported image is saved **into the workspace, next to the open file** (`flow.drawio` → `flow.png`). If that file already exists the user is asked whether to overwrite it. On success a banner "Exported to <path>" appears above the preview with a **Download** link. Exporting never marks the tab modified and never changes the open file. Only on Halo builds newer than 1.5.9; on older hosts the extension shows no working export (drawio ≥ 1.1.0 / excalidraw ≥ 1.1.0 hide or fall back on their own)
|
|
66
|
-
- **Bundle extensions** (manifest `bundle: true`, e.g. the `htrans` meeting recorder): a *directory* named with the extension's suffix (`Standup.htrans/`) is one document. In the Explorer it shows a package icon; clicking the row (or Enter) opens it as a tab, and only its chevron expands it so the files inside stay browsable. Right-click → Open to the Side works on it. **New File** with a bundle suffix (`Standup.htrans`) creates the directory and opens it. The extension reads and writes only inside that directory. While it reports busy (e.g. recording) the tab shows the dirty dot, asks before closing, and stays mounted when you switch tabs. Ctrl/Cmd+S / Save do nothing on it. A bundle tab survives a reload while the extension is installed and supported here; otherwise the tab is dropped and the directory is an ordinary folder again
|
|
79
|
+
- **Bundle extensions** (manifest `bundle: true`, e.g. the `htrans` meeting recorder): a *directory* named with the extension's suffix (`Standup.htrans/`) is one document. In the Explorer it shows a package icon; clicking the row (or Enter) opens it as a tab, and only its chevron expands it so the files inside stay browsable. Right-click → Open to the Side works on it. **New File** with a bundle suffix (`Standup.htrans`) creates the directory and opens it — from the file tree's inline New File and from the Explorer header's New File button alike. The extension reads and writes only inside that directory. While it reports busy (e.g. recording) the tab shows the dirty dot, asks before closing, and stays mounted when you switch tabs. Ctrl/Cmd+S / Save do nothing on it. A bundle tab survives a reload while the extension is installed and supported here; otherwise the tab is dropped and the directory is an ordinary folder again
|
|
80
|
+
- **File access beyond the open file** (manifest capability `fs-read`; Settings → Extensions tags it "Reads files on this machine"): a viewer such as an emulator or model viewer may read other files in the workspace or anywhere on the server machine (never write), and ask the user to choose one. The choice is made in a **file picker** the viewer opens on demand: Workspace and Home shortcuts, an editable path box, a breadcrumb, folders first, only files matching the viewer's suffix filter; arrow keys / Enter / Backspace to navigate, Esc or Cancel to close it. A file inside the workspace is handed back as a workspace-relative path, any other as an absolute one. Halo's own secrets (`~/.halo/secrets`, `~/.ssh`, git credentials, the global databases — the same paths the agent sandbox hides) never show up in the picker and can't be read this way, symlinks included
|
|
81
|
+
- **Running notebook cells** (ipynb 2.0.0+ on Halo 1.6.1+): the ipynb viewer edits and saves cells (an editable extension, as above) and runs them on a real kernel on the Halo machine through Halo's Jupyter proxy — any kernelspec the configured Jupyter finds (Python, R, …), started with the notebook's folder as the working directory. Halo starts a local Jupyter server on first use; Jupyter itself is not bundled (`pip install jupyter_server ipykernel`; the viewer shows that hint when it is missing). Same trust as the built-in terminal: the kernel runs as the Halo service user, unsandboxed, and only a logged-in admin can reach it (no web-channel / IM / A2A tokens; not in AgentCore mode). Settings → General: `jupyter.enabled` (allow notebook execution, default on) and `jupyter.path` (full path to `jupyter`, empty = PATH), both applied without a restart. Closing the notebook tab shuts its kernel down after 60 s (a reload within that keeps it); a kernel idle for 1 h is shut down even while the tab is open; stopping Halo stops every kernel. Older hosts don't advertise it: the viewer still edits and saves, without run controls
|
|
67
82
|
- **Platforms** (manifest `platforms`: `web` / `desktop-mac` / `desktop-win` / `desktop-linux`; omitted = everywhere): on an excluded platform (the Halo desktop app is detected by its user agent) the extension acts as if it were not installed. The fallback page says `"<name>" only runs on: <platforms>`, and Settings → Extensions shows a platforms badge plus a gray "not supported here" tag. Manifest capability `media` lets the extension use the microphone and screen capture
|
|
68
83
|
- **Install sources**: Settings → Extensions zip upload; `/extension install <id|zip|url>` (listed by the built-in `default` agent, full access), where an id comes from the hub in Settings → Skills → extension → `hub_repo` (default halo-hub; GitHub / Gitea / Forgejo / GitLab release zips, or tags of any other git repo); or a manual directory drop
|
|
69
84
|
- **Live install / upgrade / uninstall** (from Settings → Extensions upload / remove, the `/extension` skill, or a manual directory change): pushed over WS, no reload. An upgraded extension remounts open tabs (or shows a "updated — reload" banner if the tab is dirty); uninstalling drops clean tabs to the fallback page and keeps dirty tabs alive with a banner
|
|
@@ -77,7 +92,7 @@ Markdown and HTML open as text in Monaco *and* have a rendered view — Canvas d
|
|
|
77
92
|
- Toggle to Edit → Monaco source, Cmd+S saves as usual
|
|
78
93
|
|
|
79
94
|
### Preview caching (MRU)
|
|
80
|
-
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; switching between two heavy tabs of the same type (e.g. two `.
|
|
95
|
+
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; switching between two heavy tabs of the same type (e.g. two `.glb`) remounts the viewer, so it shows the newly active file. An extension tab with unsaved edits stays pinned beyond the 5. Cached previews keep a fixed (path-sorted) order in the DOM rather than moving with recency, so switching tabs never reloads an extension iframe — a running emulator keeps its state. Closing a preview tab removes it from the cache immediately (aborting any in-flight fetch).
|
|
81
96
|
|
|
82
97
|
### File metadata in header
|
|
83
98
|
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.
|
|
@@ -90,6 +105,11 @@ The header has a maximize button on the far right. Toggling it expands Canvas to
|
|
|
90
105
|
|
|
91
106
|
Canvas (including Monaco instances, file tree, and open tab contents) stays mounted across activity-tab switches and maximize toggles — switching away and back does **not** reload content.
|
|
92
107
|
|
|
108
|
+
**Immersive mode for viewer extensions.** When the focused pane's active tab is an extension viewer without `save` (a bundle counts), maximize goes further: the viewer fills the whole viewport and the Canvas header, tab bar, other pane and floating chat panel are hidden. A maximize click also enters real fullscreen (a maximize restored after a reload stays in-page). The viewer keeps running — nothing reloads.
|
|
109
|
+
- **Exit**: `Esc` (including while focus is inside the viewer), or the **× Exit · Esc** pill at the top — on mouse it appears for a moment on entering and whenever the pointer moves to the top edge; on touch it stays on screen as a small × at the top-left, and a tap near the top widens it. A viewer's own fullscreen (e.g. an emulator's) takes the first `Esc`.
|
|
110
|
+
- **Chat replies while immersive**: when an agent finishes a reply the chat can't be seen, so a toast "Agent replied · <session title>" (or "N sessions replied") appears at the top right for ~8 s with **Back to chat**, which leaves immersive, opens that session and puts the caret in the chat input.
|
|
111
|
+
- Editable extensions (`save`) and ordinary files keep the plain maximize described above.
|
|
112
|
+
|
|
93
113
|
## Shortcuts
|
|
94
114
|
|
|
95
115
|
| Shortcut | Action |
|
|
@@ -97,4 +117,4 @@ Canvas (including Monaco instances, file tree, and open tab contents) stays moun
|
|
|
97
117
|
| Cmd/Ctrl + P | Quick Open (fuzzy file search) |
|
|
98
118
|
| Cmd/Ctrl + S | Save current tab |
|
|
99
119
|
| 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) |
|
|
100
|
-
| Esc | Exit maximized Canvas (when not inside an input / Monaco / Quick Open) |
|
|
120
|
+
| Esc | Exit maximized Canvas (when not inside an input / Monaco / Quick Open); also exits the immersive viewer, including from inside it |
|