@turmind/halo 2.1.0 → 2.1.2
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/{a5z_n1eIm6T8T3Ty_MMHF → 1yjkA6O2OJ9CQ1XyyoazM}/_buildManifest.js +1 -1
- package/admin-out/_next/static/chunks/0.700b96fc96b80936.js +1 -0
- package/admin-out/_next/static/chunks/1160.e199e2decb964814.js +1 -0
- package/admin-out/_next/static/chunks/1208.0d86b06cbdf8a97e.js +38 -0
- package/admin-out/_next/static/chunks/1299.9ae1e73c8ca9cc34.js +23 -0
- package/admin-out/_next/static/chunks/1336.3eb7654fcfae2a58.js +1 -0
- package/admin-out/_next/static/chunks/1494.56493f36b01d2bda.js +1 -0
- package/admin-out/_next/static/chunks/1522.7d80e505c25fc865.js +1 -0
- package/admin-out/_next/static/chunks/1650.78e7ab21cb8bbb48.js +62 -0
- package/admin-out/_next/static/chunks/1690.20f8661b1877aa3d.js +1 -0
- package/admin-out/_next/static/chunks/1807.764b0338b13846a5.js +1 -0
- package/admin-out/_next/static/chunks/1989-669da2a252898797.js +1 -0
- package/admin-out/_next/static/chunks/199.fca450ee70f089fb.js +1 -0
- package/admin-out/_next/static/chunks/1b86ba38-4193816aebc732b9.js +1 -0
- package/admin-out/_next/static/chunks/{1d2d5650-d8d130c85727fdbb.js → 1d2d5650-c677408087984c7a.js} +1 -1
- package/admin-out/_next/static/chunks/2202.59a95bacad0d8c25.js +1 -0
- package/admin-out/_next/static/chunks/2204.93c869dacb19e384.js +1 -0
- package/admin-out/_next/static/chunks/2329.2f32de914b104261.js +148 -0
- package/admin-out/_next/static/chunks/2449.53d632fa8e412643.js +1 -0
- package/admin-out/_next/static/chunks/2477.79f2f92780311a37.js +94 -0
- package/admin-out/_next/static/chunks/2555.279979b7abe9d67f.js +104 -0
- package/admin-out/_next/static/chunks/2612.427656205dd7d7cc.js +1 -0
- package/admin-out/_next/static/chunks/2938.1555d68665415ace.js +1 -0
- package/admin-out/_next/static/chunks/295.0ef9b18be46f06e5.js +1 -0
- package/admin-out/_next/static/chunks/2979.d5a2293e776ec5de.js +1 -0
- package/admin-out/_next/static/chunks/3023.8f2b6311aae80029.js +36 -0
- package/admin-out/_next/static/chunks/3047.a49ae982be1f9fd4.js +226 -0
- package/admin-out/_next/static/chunks/304eda7d.2c6ab1276b03c10a.js +1 -0
- package/admin-out/_next/static/chunks/3483.cb8f5ba28c565e7f.js +1 -0
- package/admin-out/_next/static/chunks/391.71124432b84c926d.js +24 -0
- package/admin-out/_next/static/chunks/3986.a6181916d2c44515.js +1 -0
- package/admin-out/_next/static/chunks/4120.c271dfe3e1bc082e.js +1 -0
- package/admin-out/_next/static/chunks/4267.030877aaeac1bd71.js +1 -0
- package/admin-out/_next/static/chunks/4362.9d2ee4f9b6facc49.js +1 -0
- package/admin-out/_next/static/chunks/43ad6dab.4911ea318c123311.js +62 -0
- package/admin-out/_next/static/chunks/4480-6ceca99203a95c0e.js +1 -0
- package/admin-out/_next/static/chunks/4613.0985db499409cf22.js +1 -0
- package/admin-out/_next/static/chunks/4794.ba746110cfcbf536.js +1 -0
- package/admin-out/_next/static/chunks/4808.1cbe5b52fa01fc3d.js +1 -0
- package/admin-out/_next/static/chunks/4ec056c8.fe288ac8bd38a73b.js +160 -0
- package/admin-out/_next/static/chunks/5056.a87b1e056ba592b1.js +32 -0
- package/admin-out/_next/static/chunks/5058.8514aa6d6a947cce.js +1 -0
- package/admin-out/_next/static/chunks/545.e760052f10d36a9b.js +1 -0
- package/admin-out/_next/static/chunks/{510.e6c6faeb60ef3c33.js → 5510.f3889206b5768b33.js} +1 -1
- package/admin-out/_next/static/chunks/5593.d4c5f6a0862b4c6c.js +34 -0
- package/admin-out/_next/static/chunks/5700.4189184227fcf178.js +1 -0
- package/admin-out/_next/static/chunks/5741.dd7360163a2374cf.js +8 -0
- package/admin-out/_next/static/chunks/5850.b58c01f1e835d335.js +1 -0
- package/admin-out/_next/static/chunks/6066.1571d105977d528c.js +86 -0
- package/admin-out/_next/static/chunks/61527465.ae14a459dbfc381c.js +1 -0
- package/admin-out/_next/static/chunks/6203.672c38fcc2748c83.js +1 -0
- package/admin-out/_next/static/chunks/6390.4de9361c493b8d29.js +27 -0
- package/admin-out/_next/static/chunks/63c91ae9.ed896f9d6d01dfd4.js +1 -0
- package/admin-out/_next/static/chunks/6476.c7ec7d60350855c8.js +1 -0
- package/admin-out/_next/static/chunks/6574.30661d21e54bb004.js +1 -0
- package/admin-out/_next/static/chunks/6694.91c424de10ee2a24.js +79 -0
- package/admin-out/_next/static/chunks/6927.7f60d259473bb56a.js +193 -0
- package/admin-out/_next/static/chunks/7073.9d5f9dd7a77827b7.js +1 -0
- package/admin-out/_next/static/chunks/7250.a005173b9af23f14.js +1 -0
- package/admin-out/_next/static/chunks/7267.09e9b83beadc4847.js +1 -0
- package/admin-out/_next/static/chunks/72d2e465.c6751b76f1fa1cb3.js +1 -0
- package/admin-out/_next/static/chunks/7423.ce958b2d6a455a2f.js +1 -0
- package/admin-out/_next/static/chunks/7443.ee6684ba397e49da.js +41 -0
- package/admin-out/_next/static/chunks/7482.7f6f11637c008b77.js +1 -0
- package/admin-out/_next/static/chunks/7498.621ff3bc139607ad.js +1 -0
- package/admin-out/_next/static/chunks/7573.7f7f2a6fe7891e68.js +1 -0
- package/admin-out/_next/static/chunks/7613.fe24922a6a839656.js +1 -0
- package/admin-out/_next/static/chunks/7796.052a2a69f9bc0471.js +287 -0
- package/admin-out/_next/static/chunks/8148.156aea8f2a9ce398.js +115 -0
- package/admin-out/_next/static/chunks/8300.6c86a2236d42618f.js +64 -0
- package/admin-out/_next/static/chunks/8555.2e8d51736bfefd63.js +1 -0
- package/admin-out/_next/static/chunks/8558.baa0cf80a6964d20.js +286 -0
- package/admin-out/_next/static/chunks/8608.dba2a172fa47a740.js +77 -0
- package/admin-out/_next/static/chunks/8680.ed502a4a22357230.js +92 -0
- package/admin-out/_next/static/chunks/8698.28ca7821a30dba63.js +1 -0
- package/admin-out/_next/static/chunks/8712.e587efd8a6256aa9.js +1 -0
- package/admin-out/_next/static/chunks/8771.53592f31c08edafa.js +309 -0
- package/admin-out/_next/static/chunks/8878.330238d3d9f33faf.js +1 -0
- package/admin-out/_next/static/chunks/8892.9df3095f6c52e734.js +1 -0
- package/admin-out/_next/static/chunks/9104.6d57df418831714a.js +74 -0
- package/admin-out/_next/static/chunks/9121.574e833259fe9085.js +1 -0
- package/admin-out/_next/static/chunks/9142.0aac6a6c8e06bcaa.js +149 -0
- package/admin-out/_next/static/chunks/9180.3c809798364a0c62.js +1 -0
- package/admin-out/_next/static/chunks/92.788f49cdb265a668.js +217 -0
- package/admin-out/_next/static/chunks/9590.3e1a4754a7e66e84.js +93 -0
- package/admin-out/_next/static/chunks/9599.b2fd519c8a2951c9.js +1 -0
- package/admin-out/_next/static/chunks/9698-0157e7ac70c3f9fc.js +1 -0
- package/admin-out/_next/static/chunks/9837.92abff094b289f98.js +1 -0
- package/admin-out/_next/static/chunks/9839.73433bbeec6e1a51.js +1 -0
- package/admin-out/_next/static/chunks/9868.dcc9fd5f0041ffa8.js +1 -0
- package/admin-out/_next/static/chunks/9937.cff3c28655b07c61.js +24 -0
- package/admin-out/_next/static/chunks/a89fe872.304b3efb308b10a1.js +122 -0
- package/admin-out/_next/static/chunks/app/_not-found/page-e21cd35ca818a886.js +1 -0
- package/admin-out/_next/static/chunks/app/layout-0ed29966e0abc26b.js +1 -0
- package/admin-out/_next/static/chunks/app/page-30279944f9000c87.js +1 -0
- package/admin-out/_next/static/chunks/bf5d185f.735c9f3d05199f97.js +1 -0
- package/admin-out/_next/static/chunks/db534642.8e8a9023cd90863d.js +1 -0
- package/admin-out/_next/static/chunks/fcb912be.a7d93c5f2dd72e30.js +66 -0
- package/admin-out/_next/static/chunks/framework-98918e2142cd6d06.js +1 -0
- package/admin-out/_next/static/chunks/main-93c216652d4113b1.js +1 -0
- package/admin-out/_next/static/chunks/main-app-5e981adb363c52e5.js +1 -0
- package/admin-out/_next/static/chunks/pages/{_app-a3b7e86f97343e7a.js → _app-6fab9bb03f9f4fdf.js} +1 -1
- package/admin-out/_next/static/chunks/pages/_error-ada6d7affab6ebcc.js +1 -0
- package/admin-out/_next/static/chunks/webpack-7f8156dbbdb54868.js +1 -0
- package/admin-out/_next/static/css/46c83727621f8763.css +1 -0
- package/admin-out/index.html +1 -1
- package/admin-out/index.txt +5 -5
- package/bundled-docs/design/agentcore.md +1 -1
- package/bundled-docs/design/architecture.md +3 -3
- package/bundled-docs/design/storage.md +3 -3
- package/bundled-docs/design/ws.md +7 -7
- package/bundled-docs/dev/env.md +1 -2
- package/bundled-docs/guide/chat.md +1 -0
- package/bundled-docs/guide/workspace.md +7 -4
- package/bundled-docs/requirements/chat.md +1 -0
- package/bundled-docs/requirements/editor.md +1 -1
- package/bundled-docs/requirements/explorer.md +2 -0
- package/bundled-docs/requirements/source-control.md +2 -0
- package/bundled-docs/requirements/terminal.md +76 -44
- package/bundled-docs/requirements/workspace.md +10 -1
- package/dist/evo-wrapper.js +4 -5
- package/dist/index.js +176 -118
- package/package.json +3 -1
- package/templates/config.yaml +0 -6
- package/templates/models/aws-bedrock-claude-invoke.yaml +3 -3
- package/admin-out/_next/static/chunks/160.c52d3d6cf7bc8286.js +0 -1
- package/admin-out/_next/static/chunks/1b86ba38-c29611e086b2ce25.js +0 -1
- package/admin-out/_next/static/chunks/250.9094fea2325b09a3.js +0 -1
- package/admin-out/_next/static/chunks/304eda7d.f525962766560ba5.js +0 -1
- package/admin-out/_next/static/chunks/336.3171154ecc29c80a.js +0 -1
- package/admin-out/_next/static/chunks/362.a0c58d3bf9b1300b.js +0 -1
- package/admin-out/_next/static/chunks/398-9a8be186767b4504.js +0 -1
- package/admin-out/_next/static/chunks/449.c4b881b9ebdc556e.js +0 -1
- package/admin-out/_next/static/chunks/522.9e25c63e3082cb1c.js +0 -1
- package/admin-out/_next/static/chunks/58.d26b1c4f14404226.js +0 -1
- package/admin-out/_next/static/chunks/613.fb09e55e0a61d3fe.js +0 -1
- package/admin-out/_next/static/chunks/690.6a83d18b488a3e2e.js +0 -1
- package/admin-out/_next/static/chunks/698-19872069fed27164.js +0 -1
- package/admin-out/_next/static/chunks/73.05abce37d312e880.js +0 -1
- package/admin-out/_next/static/chunks/839.a2729fd33013d763.js +0 -1
- package/admin-out/_next/static/chunks/868.bbb91afd48c9eb58.js +0 -1
- package/admin-out/_next/static/chunks/892.a35118e09d8d8efd.js +0 -1
- package/admin-out/_next/static/chunks/989-04766caa08c5f506.js +0 -1
- package/admin-out/_next/static/chunks/app/_not-found/page-4271518f2746cf73.js +0 -1
- package/admin-out/_next/static/chunks/app/layout-ea161287d934ba96.js +0 -1
- package/admin-out/_next/static/chunks/app/page-ee1d0dae346ba9d2.js +0 -1
- package/admin-out/_next/static/chunks/db534642.657a5bd93dc1e8c7.js +0 -1
- package/admin-out/_next/static/chunks/framework-c48c994d2190a38e.js +0 -1
- package/admin-out/_next/static/chunks/main-app-340a5b58ed229c4d.js +0 -1
- package/admin-out/_next/static/chunks/main-f1f665fbc24e1033.js +0 -1
- package/admin-out/_next/static/chunks/pages/_error-751fca0c729a7e01.js +0 -1
- package/admin-out/_next/static/chunks/webpack-662d2cae1104f635.js +0 -1
- package/admin-out/_next/static/css/e395d2ae2e0636a0.css +0 -1
- /package/admin-out/_next/static/{a5z_n1eIm6T8T3Ty_MMHF → 1yjkA6O2OJ9CQ1XyyoazM}/_ssgManifest.js +0 -0
|
@@ -22,25 +22,32 @@ xterm.js terminal with a node-pty backend, multi-tab, reconnect-resilient.
|
|
|
22
22
|
|
|
23
23
|
Derivation in `createTerminal` in [packages/admin/src/features/terminal/terminal-panel.tsx](../../../packages/admin/src/features/terminal/terminal-panel.tsx); backend resolution in `TerminalManager.start` in [packages/server/src/ws/terminal-manager.ts](../../../packages/server/src/ws/terminal-manager.ts).
|
|
24
24
|
|
|
25
|
-
Workspace switch reloads the page and does **not** migrate existing terminals — they keep their original cwd and are only
|
|
25
|
+
Workspace switch reloads the page and does **not** migrate existing terminals — they keep their original cwd and are reattached only in their own workspace; the new workspace shows its own terminals, or starts a fresh one at its root if it has none.
|
|
26
26
|
|
|
27
|
-
###
|
|
27
|
+
### Shared, persistent terminals
|
|
28
|
+
|
|
29
|
+
A workspace's terminals belong to the workspace, not to a browser: the admin has one login, so every page that opens the workspace — another tab, another browser, another device — sees and drives the same terminals.
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
- **Persistence**: a PTY ends only when it is closed with ✕, when its shell exits, or when the server restarts. Closing the browser, losing the connection, or leaving for days doesn't end it — there is no grace timer.
|
|
32
|
+
- **Full restore**: the server mirrors each PTY into a headless xterm (`@xterm/headless`). A page that attaches gets a serialized snapshot of it (`@xterm/addon-serialize`): the scrollback (`general.limits.terminal_scrollback_lines`, default 5000 lines), the screen, a full-screen program's alternate screen (vim, htop), and the terminal modes — bracketed paste, application cursor keys, mouse tracking and SGR mouse encoding, hidden cursor. Output made while nobody was attached is part of the snapshot.
|
|
33
|
+
- **Live sync**: every attached page receives the output and can type; all input goes to the one PTY.
|
|
34
|
+
- **Close everywhere**: ✕ on any page sends `terminal:close` and the PTY ends; every attached page gets `terminal:exit` and prints `[Process exited]`.
|
|
35
|
+
- **Size**: the PTY follows its *driver*, the page that last typed into it. Each page reports its size (`terminal:resize`); when a page types and its size differs from the PTY's, the PTY (and the mirror) switch to that size. A resize from a page that isn't the driver is only recorded, unless no driver is attached any more — then it applies at once. Pages with a different size than the driver see the driver's layout reflowed into their own width.
|
|
36
|
+
|
|
37
|
+
### Reconnect resilience
|
|
30
38
|
|
|
31
|
-
1.
|
|
32
|
-
2.
|
|
33
|
-
3.
|
|
34
|
-
4.
|
|
35
|
-
5. Server replays the entire output buffer, reattaches live I/O, and responds with `terminal:reattached { terminalIds: [...] }`
|
|
36
|
-
6. If the grace timer expires first, the PTY is killed and the detach entry removed
|
|
39
|
+
1. On mount (once the workspace is known — with split view on, the panel mounts before the page has resolved its workspace) **and** on every `_connected`, the panel sends `terminal:reattach { workspacePath }`. The server attaches the connection to every PTY of that workspace and subscribes it to the workspace
|
|
40
|
+
2. The server answers `terminal:reattached { terminals: [{ id, name }] }`, then one `terminal:snapshot` per PTY, then live `terminal:output`. Snapshot and live stream join exactly: output that arrives while a snapshot is being taken is queued for that page and sent right after it — nothing lost, nothing repeated
|
|
41
|
+
3. A PTY another page starts later is pushed to every subscribed page as `terminal:opened { terminalId, name }` + its snapshot — no polling
|
|
42
|
+
4. When a connection closes, the server only removes it from the PTYs' attached sets and from the workspace subscription; no timer is started
|
|
37
43
|
|
|
38
44
|
Connection-level liveness and reconnect (server keepalive tolerance, client self-check timer, auth-expiry handling) are owned by the shared WS client — see [design/ws.md](../design/ws.md#client-side-liveness--reconnect).
|
|
39
45
|
|
|
40
|
-
When the WS drops, the bottom panel remounts `TerminalPanel` (fresh key on `_disconnected`): local xterm instances are disposed without sending `terminal:close`, and the new panel reattaches like a page load
|
|
46
|
+
When the WS drops, the bottom panel remounts `TerminalPanel` (fresh key on `_disconnected`): local xterm instances are disposed without sending `terminal:close`, and the new panel reattaches like a page load — its screen comes back from the snapshots. In the panel ([packages/admin/src/features/terminal/terminal-panel.tsx](../../../packages/admin/src/features/terminal/terminal-panel.tsx)):
|
|
41
47
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
48
|
+
- `terminal:reattached` / `terminal:opened`: a tab is added for each id not shown yet, named as the server says (the name the creating page gave in `terminal:start`).
|
|
49
|
+
- `terminal:snapshot`: the local xterm (created if missing) is `reset()`, sized to the snapshot's `cols × rows`, given the snapshot, then fit to its container; the fitted size is reported with `terminal:resize`.
|
|
50
|
+
- If 2 s after the connection is up the panel still has no terminal, it starts one (the timer is armed only while connected, so an outage doesn't add a PTY per remount).
|
|
44
51
|
|
|
45
52
|
### Environment
|
|
46
53
|
- Shell: `$SHELL` or `/bin/bash` (`ComSpec` / `powershell.exe` on Windows); bash / zsh / sh / fish start as login shells (`-l`)
|
|
@@ -61,45 +68,59 @@ Keys depend on the client's OS (the keyboard in front of the user — the PTY is
|
|
|
61
68
|
- **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
69
|
- 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`
|
|
63
70
|
|
|
71
|
+
### Local echo (predictive)
|
|
72
|
+
Keystrokes show up before the server's echo comes back, so typing over a slow link doesn't lag. This is a port of VS Code's terminal type-ahead ([packages/admin/src/features/terminal/type-ahead/](../../../packages/admin/src/features/terminal/type-ahead/)), and it is always on. No setting turns it off.
|
|
73
|
+
|
|
74
|
+
- A predicted character is drawn **dim**. When the PTY echoes the same character, it is redrawn normally. When the PTY sends something else, the prediction is rolled back: the original cell and cursor come back, then the real output is written. This happens, for example, when another page typed into the same terminal first (see Shared, persistent terminals). No residue is left.
|
|
75
|
+
- Predicted keys: printable characters, Backspace, ←/→ (also `Esc b` / `Esc f`), and Enter when the cursor isn't on the last row. Any other key ends prediction until the PTY answers: Tab, ↑/↓, Ctrl-keys and the like. This is why **Tab completion and history paging are not predicted**.
|
|
76
|
+
- The first keystroke on a line is drawn only after the PTY has echoed one. A prompt that doesn't echo starts nothing.
|
|
77
|
+
- Not predicted:
|
|
78
|
+
- while the title names `vim`, `vi`, `nano` or `tmux`;
|
|
79
|
+
- in any full-screen program on the alternate screen (htop, less in full-screen mode…);
|
|
80
|
+
- at a `less`-style `:` prompt on the bottom row.
|
|
81
|
+
- A prediction the PTY never echoes is withdrawn after a timeout: at least 500 ms, or 1.5 × the slowest confirmed echo, whichever is longer. **Password prompts**: if the prompt is on a line where echo was already confirmed, a typed character may flash dim and then be withdrawn. At a fresh prompt nothing is drawn.
|
|
82
|
+
- A `terminal:snapshot` drops all pending predictions without rolling them back, because the snapshot replaces the whole screen.
|
|
83
|
+
|
|
64
84
|
### Lifecycle
|
|
65
85
|
|
|
66
86
|
```
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
│ live IO │
|
|
82
|
-
└─────────┘
|
|
87
|
+
terminal:start
|
|
88
|
+
│
|
|
89
|
+
▼
|
|
90
|
+
┌──────────────────────────────────┐ page attaches
|
|
91
|
+
│ PTY + headless mirror │◀── (reattach / opened):
|
|
92
|
+
│ attached pages: 0..n │ snapshot, then live
|
|
93
|
+
│ (output + exit go to all) │
|
|
94
|
+
└──┬──────────────┬─────────────┬──┘
|
|
95
|
+
│ page leaves │ ✕ / shell │ server
|
|
96
|
+
│ (WS drops) │ exits │ restart
|
|
97
|
+
▼ ▼ ▼
|
|
98
|
+
still running exit to all gone
|
|
99
|
+
(0 pages ok, pages, PTY
|
|
100
|
+
no timer) removed
|
|
83
101
|
```
|
|
84
102
|
|
|
85
103
|
Close semantics:
|
|
86
|
-
- Explicit `terminal:close` — PTY killed immediately
|
|
87
|
-
-
|
|
104
|
+
- Explicit `terminal:close` (✕ on any page) — PTY killed immediately; every attached page gets `terminal:exit`
|
|
105
|
+
- The shell exits by itself — every attached page gets `terminal:exit` with its exit code
|
|
88
106
|
- Server shutdown — all PTYs die with the process
|
|
107
|
+
- WS drop / browser closed — **not** an end: the PTY keeps running with no page attached, for as long as it takes
|
|
89
108
|
|
|
90
109
|
## WebSocket protocol
|
|
91
110
|
|
|
92
111
|
| Direction | Type | Fields | Purpose |
|
|
93
112
|
|---|---|---|---|
|
|
94
|
-
| C→S | `terminal:start` | `terminalId?`, `cwd?`, `cols?`, `rows?` | Spawn a new PTY |
|
|
95
|
-
| C→S | `terminal:input` | `terminalId`, `data` | Send user keystrokes |
|
|
96
|
-
| C→S | `terminal:resize` | `terminalId`, `cols`, `rows` |
|
|
97
|
-
| C→S | `terminal:close` | `terminalId` | Explicit close |
|
|
98
|
-
| C→S | `terminal:reattach` |
|
|
99
|
-
| S→C | `terminal:ready` | `terminalId` | PTY spawned and ready |
|
|
100
|
-
| S→C | `terminal:output` | `terminalId`, `data` | PTY stdout/stderr |
|
|
101
|
-
| S→C | `terminal:exit` | `terminalId`, `exitCode` | PTY
|
|
102
|
-
| S→C | `terminal:reattached` | `
|
|
113
|
+
| C→S | `terminal:start` | `terminalId?`, `cwd?`, `cols?`, `rows?`, `workspacePath?`, `name?` | Spawn a new PTY in the workspace; `name` is the tab name every page shows. Pushed to the workspace's other subscribers |
|
|
114
|
+
| C→S | `terminal:input` | `terminalId`, `data` | Send user keystrokes; makes the sender the PTY's size driver. Rejected without `terminalId` |
|
|
115
|
+
| C→S | `terminal:resize` | `terminalId`, `cols`, `rows` | Report this page's size; applied if it is the driver or no driver is attached. Rejected without `terminalId` |
|
|
116
|
+
| C→S | `terminal:close` | `terminalId` | Explicit close — ends the PTY for every page |
|
|
117
|
+
| C→S | `terminal:reattach` | `workspacePath` | Attach to every PTY of the workspace and subscribe to it |
|
|
118
|
+
| S→C | `terminal:ready` | `terminalId` | PTY spawned and ready (to its creator) |
|
|
119
|
+
| S→C | `terminal:output` | `terminalId`, `data` | PTY stdout/stderr, to every attached page |
|
|
120
|
+
| S→C | `terminal:exit` | `terminalId`, `exitCode` | PTY ended (close or shell exit), to every attached page |
|
|
121
|
+
| S→C | `terminal:reattached` | `terminals: [{ id, name }]` | The workspace's PTYs, all now attached; a snapshot follows for each |
|
|
122
|
+
| S→C | `terminal:opened` | `terminalId`, `name` | Another page started a PTY in a subscribed workspace; a snapshot follows |
|
|
123
|
+
| S→C | `terminal:snapshot` | `terminalId`, `data`, `cols`, `rows` | Serialized scrollback + screen + modes at attach time, taken at `cols × rows`; live output continues right after it |
|
|
103
124
|
|
|
104
125
|
Source: [packages/server/src/ws/terminal-manager.ts](../../../packages/server/src/ws/terminal-manager.ts).
|
|
105
126
|
|
|
@@ -107,8 +128,9 @@ Source: [packages/server/src/ws/terminal-manager.ts](../../../packages/server/sr
|
|
|
107
128
|
|
|
108
129
|
| Config key | Default | Purpose |
|
|
109
130
|
|---|---|---|
|
|
110
|
-
| `config.
|
|
111
|
-
|
|
131
|
+
| `config.limits.terminalScrollbackLines` (setting `general.limits.terminal_scrollback_lines`) | 5000 lines | Scrollback the server-side mirror keeps per terminal (restored on attach); applies to terminals opened after a change |
|
|
132
|
+
|
|
133
|
+
`timeout.terminal_grace` / `HALO_TERMINAL_GRACE` and `general.limits.terminal_scrollback_bytes` are gone; a value left in an existing `config.yaml` / `settings.yaml` is ignored.
|
|
112
134
|
|
|
113
135
|
Defined in [packages/server/src/config.ts](../../../packages/server/src/config.ts).
|
|
114
136
|
|
|
@@ -119,17 +141,27 @@ Defined in [packages/server/src/config.ts](../../../packages/server/src/config.t
|
|
|
119
141
|
| T1 | Start, run `ls` | Output arrives; prompt returns |
|
|
120
142
|
| T2 | Explorer → right-click a folder → Open in Integrated Terminal | A new terminal whose cwd is that folder (`pwd` confirms) |
|
|
121
143
|
| T3 | Resize window | PTY cols/rows update; long-running process (e.g. `watch ls`) reflows |
|
|
122
|
-
| T4 | Disconnect mid-command (`sleep 5 && echo done`) → reconnect
|
|
123
|
-
| T5 | Disconnect
|
|
144
|
+
| T4 | Disconnect mid-command (`sleep 5 && echo done`) → reconnect | Screen restored from the snapshot: scrollback from before the drop plus `done` |
|
|
145
|
+
| T5 | Disconnect (or close the browser) → come back hours later | The terminal is still there, scrollback and running programs intact |
|
|
146
|
+
| T5b | Run `vim` (or `htop`) → refresh the page | vim's screen is back as it was; keys and mouse work in it; after `:q` the shell's scrollback is back |
|
|
147
|
+
| T5c | Open the same workspace in two browsers | Both show the same terminals; typing in either shows on both; ✕ in one prints `[Process exited]` in both |
|
|
148
|
+
| T5d | Open a terminal in browser A while B has the workspace open | B gets the new tab with A's name, live |
|
|
149
|
+
| T5e | Open the workspace on another device | Same terminals with their full scrollback; another workspace's terminals don't appear |
|
|
150
|
+
| T5f | Two browsers with different window sizes, type in the narrow one, then in the wide one | `stty size` follows whoever typed last |
|
|
124
151
|
| T6 | Open 3 tabs, close 1 explicitly | Other 2 keep their PTYs; closed one gets `terminal:exit` |
|
|
125
152
|
| T7 | `exit` from within shell | `terminal:exit` with the shell's exit code; the terminal prints `[Process exited]` and its tab stays until closed |
|
|
126
153
|
| T8 | Paste a 10 KB block | Sent as `terminal:input` without choking; shell echoes in chunks |
|
|
127
154
|
| 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
155
|
| T10 | Right-click in the terminal → Paste | Clipboard text inserted at the prompt (or the "use the shortcut" hint on a plain-http origin) |
|
|
156
|
+
| T11 | Over a slow link (e.g. 300 ms RTT), type `ls -la` after a first confirmed character | Characters appear at once, dim. They turn normal as the echo arrives. Backspace and ←/→ are instant too |
|
|
157
|
+
| T12 | Two browsers on one terminal, type different characters in both at the same moment | Each page ends up showing exactly what the shell echoed. A dim prediction from the losing page is rolled back with no stray character left |
|
|
158
|
+
| T13 | Type in `vim`, `nano`, `tmux`, `htop`, `less` | Nothing is drawn ahead of the program's own output |
|
|
159
|
+
| T14 | `sudo -k; sudo true`, then type the password | Nothing stays on screen. At most a dim character flashes and is withdrawn within about 0.5 s |
|
|
160
|
+
| T15 | Type, then Tab to complete or ↑ for history | The completion or history entry appears only when the shell sends it. The characters typed before it are kept |
|
|
129
161
|
|
|
130
162
|
Follows the pattern of [test/session.md](../test/session.md).
|
|
131
163
|
|
|
132
164
|
## Related design
|
|
133
165
|
|
|
134
|
-
-
|
|
166
|
+
- Attach / snapshot plumbing: [design/architecture.md#terminalmanager](../design/architecture.md#terminalmanager--pty-management)
|
|
135
167
|
- WS envelope shape: [design/ws.md](../design/ws.md)
|
|
@@ -69,6 +69,15 @@ Current tab stored in `editorStore.bottomTab`.
|
|
|
69
69
|
|
|
70
70
|
The middle of the tab bar shows the current tab's title — the session title on Chat ("New session" for a draft, "Untitled session" until it has one), the active terminal's name on Terminal. It is truncated with an ellipsis when it doesn't fit (full text on hover) and hidden when the bar is too narrow.
|
|
71
71
|
|
|
72
|
+
### Split view
|
|
73
|
+
A split button (split-square icon, left of maximize) shows Chat and Terminal at once; pressing it again returns to the single view:
|
|
74
|
+
- **Layout**: side by side (Chat left, Terminal right) when the panel's content is at least 720px wide; stacked (Chat on top) when it's narrower — e.g. the 480-wide floating window. The direction follows the width live
|
|
75
|
+
- **Divider**: drag to resize; each pane keeps at least 20%. The ratio is remembered separately for side-by-side and stacked (`localStorage` `halo_bottom_split_ratio_row` / `_column`)
|
|
76
|
+
- **Tab bar**: both tabs show as selected; clicking either one leaves the split and shows just that tab. The centred title reads `<chat title> · <terminal name>`, dropping an empty half
|
|
77
|
+
- **Terminal** mounts as soon as the split opens (it's otherwise mounted on first visit to the Terminal tab)
|
|
78
|
+
- **Persisted** in `localStorage` (`editorStore.bottomSplit`, key `halo_bottom_split`) — survives a refresh. Works docked, maximized and floating
|
|
79
|
+
- Switching single ↔ split or flipping the direction never remounts Chat or Terminal — the chat keeps its state and terminals their scroll buffers
|
|
80
|
+
|
|
72
81
|
### Floating mode
|
|
73
82
|
The tab bar has a maximize button and, at the far right, a float button. Toggling the float button detaches the panel into a draggable window:
|
|
74
83
|
- **Default position/size**: bottom-right corner, 480×640 with a 24px margin
|
|
@@ -86,7 +95,7 @@ The tab bar has a maximize button and, at the far right, a float button. Togglin
|
|
|
86
95
|
|---|---|
|
|
87
96
|
| Cmd/Ctrl + P | Quick Open (fuzzy file search) |
|
|
88
97
|
| Cmd/Ctrl + S | Save current file |
|
|
89
|
-
| Cmd/Ctrl + ` | Toggle the bottom panel between Chat and Terminal |
|
|
98
|
+
| Cmd/Ctrl + ` | Toggle the bottom panel between Chat and Terminal (does nothing in split view) |
|
|
90
99
|
| Alt + W | Close current tab (Cmd+W cannot be overridden in browsers; the desktop app uses Cmd/Ctrl + W) |
|
|
91
100
|
|
|
92
101
|
## Auth
|
package/dist/evo-wrapper.js
CHANGED
|
@@ -888,7 +888,7 @@ function generalSection() {
|
|
|
888
888
|
{ key: "limits.tool_result_render_chars", type: "int", globalOnly: true, description: 'Per-tool-result cap on the content fed back to the LLM. Larger results are truncated (with a "[Content truncated]" marker telling the model to re-run with narrower scope) to protect the context window and prompt cache. The UI/admin chat panel shows a far larger slice \u2014 see tool_result_ui_chars.', description_zh: "\u6BCF\u6761 tool result \u56DE\u4F20\u7ED9 LLM \u7684\u5185\u5BB9\u957F\u5EA6\u4E0A\u9650\u3002\u8D85\u51FA\u90E8\u5206\u4F1A\u88AB\u622A\u65AD\uFF08\u5E76\u9644 [Content truncated] \u6807\u8BB0\u63D0\u793A\u6A21\u578B\u7F29\u5C0F\u8303\u56F4\u91CD\u67E5\uFF09\uFF0C\u4EE5\u4FDD\u62A4\u4E0A\u4E0B\u6587\u7A97\u53E3\u548C\u63D0\u793A\u8BCD\u7F13\u5B58\u3002UI/admin \u804A\u5929\u9762\u677F\u5C55\u793A\u7684\u5185\u5BB9\u4E0A\u9650\u8981\u5927\u5F97\u591A\uFF0C\u89C1 tool_result_ui_chars\u3002", default: "8000" },
|
|
889
889
|
{ key: "limits.tool_result_ui_chars", type: "int", globalOnly: true, description: "Per-tool-result cap on the content stored for UI display (admin/web chat panel). Far larger than the LLM cap so the full output of a normal command stays visible, but bounded so a multi-MB `cat` does not bloat the session file, the WS payload, or the browser render. Output past this is truncated with a marker pointing at file_read for the complete content.", description_zh: "\u6BCF\u6761 tool result \u5B58\u7ED9 UI \u5C55\u793A\uFF08admin/web \u804A\u5929\u9762\u677F\uFF09\u7684\u5185\u5BB9\u957F\u5EA6\u4E0A\u9650\u3002\u8FDC\u5927\u4E8E LLM \u4E0A\u9650\uFF0C\u4FDD\u8BC1\u666E\u901A\u547D\u4EE4\u7684\u5B8C\u6574\u8F93\u51FA\u90FD\u80FD\u770B\u5230\uFF1B\u4F46\u4ECD\u6709\u754C\uFF0C\u907F\u514D cat \u4E2A\u51E0 MB \u7684\u6587\u4EF6\u6491\u7206\u4F1A\u8BDD\u6587\u4EF6\u3001WS \u4F20\u8F93\u548C\u6D4F\u89C8\u5668\u6E32\u67D3\u3002\u8D85\u51FA\u90E8\u5206\u622A\u65AD\u5E76\u9644\u6807\u8BB0\uFF0C\u63D0\u793A\u7528 file_read \u770B\u5B8C\u6574\u5185\u5BB9\u3002", default: String(64 * 1024) },
|
|
890
890
|
{ key: "limits.auto_report_chars", type: "int", globalOnly: true, description: "Cap on the auto-report a finished sub-agent delivers to its parent. Longer reports are truncated with a pointer to get_session_output for the full text.", description_zh: "\u5B50\u4F1A\u8BDD\u5B8C\u6210\u540E\u81EA\u52A8\u4E0A\u62A5\u7ED9\u7236\u4F1A\u8BDD\u7684\u5185\u5BB9\u957F\u5EA6\u4E0A\u9650\u3002\u8D85\u51FA\u622A\u65AD\uFF0C\u5E76\u63D0\u793A\u7528 get_session_output \u53D6\u5168\u6587\u3002", default: "8192" },
|
|
891
|
-
{ key: "limits.
|
|
891
|
+
{ key: "limits.terminal_scrollback_lines", type: "int", globalOnly: true, description: "Scrollback lines the server keeps per terminal, restored to every page that attaches (reconnect, another browser or device). Applies to terminals opened after the change.", description_zh: "\u670D\u52A1\u7AEF\u4E3A\u6BCF\u4E2A\u7EC8\u7AEF\u4FDD\u7559\u7684\u56DE\u6EDA\u884C\u6570\uFF0C\u4EFB\u4F55\u9875\u9762\u63A5\u5165\uFF08\u91CD\u8FDE\u3001\u6362\u6D4F\u89C8\u5668\u6216\u8BBE\u5907\uFF09\u65F6\u6062\u590D\u3002\u4FEE\u6539\u540E\u5BF9\u65B0\u5F00\u7684\u7EC8\u7AEF\u751F\u6548\u3002", default: "5000" }
|
|
892
892
|
].map((f) => ({
|
|
893
893
|
...f,
|
|
894
894
|
kind: "param",
|
|
@@ -1355,8 +1355,7 @@ var config = {
|
|
|
1355
1355
|
// fragments, SSE comments included), so for them it is an idle timeout — a
|
|
1356
1356
|
// long but live stream is never cut off.
|
|
1357
1357
|
modelRequest: systemInt("HALO_MODEL_TIMEOUT", "timeout.model_request", 10 * 6e4),
|
|
1358
|
-
sessionGrace: systemInt("HALO_SESSION_GRACE", "timeout.session_grace", 5 * 6e4)
|
|
1359
|
-
terminalGrace: systemInt("HALO_TERMINAL_GRACE", "timeout.terminal_grace", 5 * 6e4)
|
|
1358
|
+
sessionGrace: systemInt("HALO_SESSION_GRACE", "timeout.session_grace", 5 * 6e4)
|
|
1360
1359
|
},
|
|
1361
1360
|
// Runtime caps — read live from settings.yaml on every access. Getters
|
|
1362
1361
|
// (not literals) so a settings reload takes effect on the next tool
|
|
@@ -1381,8 +1380,8 @@ var config = {
|
|
|
1381
1380
|
get autoReportMax() {
|
|
1382
1381
|
return settingsInt("general.limits.auto_report_chars", 8192);
|
|
1383
1382
|
},
|
|
1384
|
-
get
|
|
1385
|
-
return settingsInt("general.limits.
|
|
1383
|
+
get terminalScrollbackLines() {
|
|
1384
|
+
return settingsInt("general.limits.terminal_scrollback_lines", 5e3);
|
|
1386
1385
|
}
|
|
1387
1386
|
},
|
|
1388
1387
|
compact: {
|