@turmind/halo 2.1.0 → 2.1.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.
@@ -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 reattached in their own workspace (within the grace period); the new workspace starts a fresh terminal at its root.
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
- ### Reconnect resilience
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
- When the WebSocket drops:
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. PTYs are **not killed** — they stay in the module-level `terminals` map with `currentWs = null` (`packages/server/src/ws/terminal-manager.ts`)
32
- 2. Output during detach is buffered (ring buffer, up to `config.limits.terminalOutputBuffer` = 50 KB per terminal)
33
- 3. Grace period: `config.timeout.terminalGrace` (default 5 min)
34
- 4. On reconnect, the client sends `terminal:reattach` with its `browserId` and workspace path (sent on initial mount **and** on every subsequent `_connected` event); only PTYs of that browser × workspace are claimed
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. The local scrollback is lost; the server replays only what was buffered while detached. On the reattach handler ([packages/admin/src/features/terminal/terminal-panel.tsx](../../../packages/admin/src/features/terminal/terminal-panel.tsx)), each id in `terminalIds` is dispatched by whether a local xterm instance already exists:
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
- - **Already exists** (a reattach without a remount): only a `terminal:resize` is sent so server PTY dimensions resync; the existing instance keeps its scrollback and continues receiving live output.
43
- - **Does not exist** (first mount, or after the remount above): a fresh xterm container is created and bound to that id. Bracketed paste mode is resynced by locally writing `\x1b[?2004h` into the new instance — bash enabled the mode on the PTY at spawn time, but that sequence went to the disposed instance; without the resync, a multi-line paste into the reattached terminal would be sent unbracketed and execute line by line.
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
- create ──▶│ in this WS │
69
- │ session map │
70
- └──────┬──────┘
71
- │ WS drops
72
- ▼
73
- ┌─────────────┐
74
- │ detached │ (grace timer = 5min, ring buffer active)
75
- │ pool │
76
- └──┬───────┬──┘
77
- reattach │ │ grace expires
78
- ▼ ▼
79
- ┌─────────┐ kill PTY, clean up
80
- │ replay +│
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, detach entry (if any) cleaned up
87
- - WS drop → grace expires (default 5 min) without reattach → PTY killed
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` | Resize PTY (screen resize) |
97
- | C→S | `terminal:close` | `terminalId` | Explicit close |
98
- | C→S | `terminal:reattach` | — | Reattach all detached terminals after reconnect |
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 exited |
102
- | S→C | `terminal:reattached` | `terminalIds` | Reattach completed for these |
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.timeout.terminalGrace` | 300,000 ms | Detach retention period |
111
- | `config.limits.terminalOutputBuffer` (setting `general.limits.terminal_scrollback_bytes`) | 50,000 bytes | Detach output ring buffer cap |
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 within grace period | Buffered output replayed; `done` visible |
123
- | T5 | Disconnect → wait > grace period → reconnect | Terminal gone (PTY killed at grace expiry) |
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
- - Detach / reattach plumbing: [design/architecture.md#terminalmanager](../design/architecture.md#terminalmanager--pty-management)
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
@@ -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.terminal_scrollback_bytes", type: "int", globalOnly: true, description: "Off-screen scrollback bytes retained per persistent terminal while detached.", description_zh: "\u7EC8\u7AEF\u65AD\u5F00\u671F\u95F4\u4FDD\u7559\u7684\u56DE\u6EDA\u7F13\u51B2\u5B57\u8282\u6570\u3002", default: "50000" }
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 terminalOutputBuffer() {
1385
- return settingsInt("general.limits.terminal_scrollback_bytes", 5e4);
1383
+ get terminalScrollbackLines() {
1384
+ return settingsInt("general.limits.terminal_scrollback_lines", 5e3);
1386
1385
  }
1387
1386
  },
1388
1387
  compact: {