@xingjisoft/neutrino 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,137 +1,398 @@
1
- # XINGJI Neutrino
2
-
3
- A CLI coding agent that lives in your terminal. Neutrino gives you an interactive chat interface backed by LLMs, with built-in tool use (running commands, reading and writing files) so it can help you work on real projects.
4
-
5
- ## Features
6
-
7
- - **Interactive terminal UI** — logo screen, chat, model select, history, and settings screens.
8
- - **Full mouse support** — hover to highlight, click to select/open/position the cursor, wheel to scroll, right-click to go back (like OpenCode).
9
- - **Select & copy** — drag the mouse over any text (agent output, the input box, menus, …) to select it; releasing the button copies the selection to the clipboard (via OSC 52, with a native fallback).
10
- - **Paste-friendly input** — like OpenCode, a pasted block has its newlines turned into spaces so it stays one line, and only a manually typed `Enter` submits the message.
11
- - **Faithful log rendering** — text in the chat log keeps its indentation, blank lines and column alignment; when a long tool-output line wraps, continuation lines stay indented instead of snapping back to the left margin.
12
- - **Scrollable context log** — a vertical scrollbar is drawn on the right edge of the chat (and the agent-conversation viewer) whenever the conversation is taller than the view. Scroll with the wheel, `PgUp`/`PgDn` or `↑`/`↓`, or grab the scrollbar with the mouse and drag it like in a GUI.
13
- - **LLM providers** — ships with DeepSeek, OpenAI, and Anthropic presets; works with any OpenAI-compatible endpoint.
14
- - **Tool calling** — the agent can run shell commands, read files, write files, and list directories in your working directory.
15
- - **Streaming responses** — token-by-token output with pause/resume (`ESC ESC`).
16
- - **Intensity control** — tune `temperature`/`top_p` from `Low` to `Ultra`.
17
- - **Session history** — conversations are saved automatically and can be resumed or deleted.
18
- - **Token usage stats** — click the `ⓘ` in the chat header to see prompt/completion/total token counts for the current session.
19
- - **Agent history browser** — view past sessions from other CLI agents (Claude Code, Codex, OpenCode) directly from the history screen.
20
-
21
- ## Requirements
22
-
23
- - Node.js **>= 18.0.0**
24
- - An interactive terminal (TTY)
25
-
26
- ## Installation
27
-
28
- ```sh
29
- npm install -g @xingjisoft/neutrino
30
- ```
31
-
32
- Or run from source:
33
-
34
- ```sh
35
- git clone <repo-url> neutrino
36
- cd neutrino
37
- npm install
38
- npm run build
39
- npm link
40
- ```
41
-
42
- ## Usage
43
-
44
- ```sh
45
- neutrino
46
- ```
47
-
48
- Type a message on the start screen and press `Enter` to begin a new chat.
49
-
50
- ### Keys
51
-
52
- | Keys | Action |
53
- | --- | --- |
54
- | `Enter` | Send message / confirm (only a manually typed Enter sends) |
55
- | `←`/`→`, `↑`/`↓` | Move focus / navigate |
56
- | `Ctrl+E` | Open model config |
57
- | `Ctrl+B` | Back to menu (from chat) |
58
- | `Ctrl+L` | Clear conversation (from chat) |
59
- | `Esc Esc` | Pause / resume streaming |
60
- | `Esc` | Clear an active selection |
61
- | `PgUp` / `PgDn` | Scroll chat history |
62
- | `Ctrl+C` | Copy the selection, or quit if nothing is selected |
63
- | `Ctrl+Q` | Quit |
64
-
65
- ### Mouse
66
-
67
- | Action | Effect |
68
- | --- | --- |
69
- | Hover over an item | Highlight / select it in lists and menus |
70
- | Click an item | Select and confirm (open a screen, load a session, apply a setting…) |
71
- | Click the input box | Focus it and place the cursor at the clicked position |
72
- | Click and drag | Select text under the rectangle; releasing copies it to the clipboard |
73
- | Click the header (left of `ⓘ`) | Open the model selector |
74
- | Click `ⓘ` (chat header) | Toggle token usage stats |
75
- | Drag the scrollbar | Grab the thumb on the right edge of the chat/log and drag it to scroll |
76
- | Scroll wheel | Scroll the chat/log, or move through lists |
77
- | Right-click | Go back (like `Esc`) |
78
-
79
- The selection highlight is drawn over whatever is on screen, so it works uniformly on the chat log, the input box and every menu. Selected text is copied with trailing whitespace trimmed per line (shell-style), so it can be pasted straight back into a terminal.
80
-
81
- ## Configuration
82
-
83
- Settings are stored under `~/.neutrino/`:
84
-
85
- - `model.json` — provider and model definitions (edit to add your own endpoints).
86
- - `config.json` — app configuration (selected provider/model/intensity, theme, language).
87
- - `session/` — saved conversation sessions, one JSON file per session.
88
-
89
- Set your API key in the model select screen or directly in `model.json`.
90
-
91
- ### Browsing other agents' history
92
-
93
- From the history screen, select **Open other Agent's history** to view past sessions recorded by other CLI agents installed on your machine:
94
-
95
- - **Claude Code** — reads `~/.claude/projects/**/*.jsonl`.
96
- - **Codex** — reads `~/.codex/sessions` and `~/.codex/archived_sessions`.
97
- - **OpenCode** — reads the local SQLite database (`opencode.db`) via `node:sqlite` (requires Node.js **22.13+**; silently unavailable on older versions).
98
-
99
- These sessions are read-only; you can open them for review but not edit or delete.
100
-
101
- ## Built-in tools
102
-
103
- The agent can call the following tools during a conversation:
104
-
105
- - `run_command` — execute a shell command (120s timeout, output capped).
106
- - `read_file` — read a text file (capped at 200KB).
107
- - `write_file` — write content to a file.
108
- - `list_files` — list directory contents.
109
-
110
- ## Development
111
-
112
- ```sh
113
- npm run build # compile TypeScript to dist/
114
- npm test # build, then run every test below
115
- ```
116
-
117
- Individual tests:
118
-
119
- ```sh
120
- node scripts/test-paste.mjs # verify pasted newlines are flattened to spaces
121
- node scripts/test-mouse.mjs # verify mouse escape-sequence parsing
122
- node scripts/test-cursor.mjs # verify click-to-position-cursor math
123
- node scripts/test-selection.mjs # verify selection highlighting & text extraction
124
- node scripts/test-scrollbar.mjs # verify the conversation scrollbar size/position and drag maths
125
- node scripts/test-markdown.mjs # verify markdown/table rendering (incl. ragged rows)
126
- node scripts/test-wrap.mjs # verify indentation & spacing are preserved when wrapping
127
- node scripts/test-render.mjs # fuzz the diff renderer + render the chat screen,
128
- # asserting no stale/residual cells are left behind
129
- ```
130
-
131
- ### Rendering model
132
-
133
- The screen is drawn into a `Buffer` of cells which is diffed against the previous frame, so only changed rows are emitted. Rows are erased to the end of the line before being repainted, and the buffer keeps double-width glyphs consistent (writing over one half of a wide character blanks the other half). Together these guarantee that a repainted row can never leave a stale cell behind.
134
-
135
- ## License
136
-
137
- Apache-2.0. See [LICENSE](./LICENSE).
1
+ # XINGJI Neutrino
2
+
3
+ A CLI coding agent that lives in your terminal. Neutrino gives you an interactive chat interface backed by LLMs, with built-in tool use (running commands, reading and writing files) so it can help you work on real projects.
4
+
5
+ ## Features
6
+
7
+ - **Interactive terminal UI** — logo screen, chat, model select, history, and settings screens.
8
+ - **Full mouse support** — hover to highlight, click to select/open/position the cursor, wheel to scroll, right-click to go back (like OpenCode).
9
+ - **Select & copy** — drag the mouse over any text (agent output, the input box, menus, …) to select it; releasing the button copies the selection to the clipboard (via OSC 52, with a native fallback).
10
+ - **Paste-friendly input** — like OpenCode, a pasted block has its newlines turned into spaces so it stays one line, and only a manually typed `Enter` submits the message.
11
+ - **Faithful log rendering** — text in the chat log keeps its indentation, blank lines and column alignment; when a long tool-output line wraps, continuation lines stay indented instead of snapping back to the left margin.
12
+ - **Scrollable context log** — a vertical scrollbar is drawn on the right edge of the chat (and the agent-conversation viewer) whenever the conversation is taller than the view. Scroll with the wheel, `PgUp`/`PgDn` or `↑`/`↓`, or grab the scrollbar with the mouse and drag it like in a GUI.
13
+ - **LLM providers** — ships with DeepSeek, OpenAI, and Anthropic presets; works with any OpenAI-compatible endpoint.
14
+ - **Tool calling** — the agent can run shell commands, read files, write files, and list directories in your working directory.
15
+ - **Streaming responses** — token-by-token output with pause/resume (`ESC ESC`).
16
+ - **Intensity control** — tune `temperature`/`top_p` from `Low` to `Ultra`.
17
+ - **Session history** — messages are saved before a turn starts, streamed replies are checkpointed every second, and tool calls/results are saved immediately. The history screen lists conversations started in the current working directory. Failed saves are shown in the terminal and browser UI.
18
+ - **Complete conversation history** — context compression keeps every original message and tool result on disk. The earlier portion starts collapsed in the terminal and browser; click **Earlier conversation** to expand it, including in reopened history. Model requests use a separate summary plus recent messages.
19
+ - **Token usage stats** — click the `ⓘ` in the chat header to see prompt/completion/total token counts for the current session.
20
+ - **Agent history browser** — view past sessions from other CLI agents (Claude Code, Codex, OpenCode) directly from the history screen.
21
+ - **Approval gate** — risky commands and paths outside the working directory need your sign-off. The header turns orange and reads **Request Approval**; press `Y` or `N`. Delete-class commands always ask, unless you explicitly granted full trust, and there is an opt-in reviewer agent for automatic decisions.
22
+ - **Browser UI on any port** — mount a local website that lists every saved conversation grouped by working directory, streams the running one in real time, and lets you pause/resume it, send messages, switch provider/model/intensity, and edit settings from the browser.
23
+
24
+ ## Requirements
25
+
26
+ - Node.js **>= 18.0.0**
27
+ - An interactive terminal (TTY)
28
+
29
+ ## Installation
30
+
31
+ ```sh
32
+ npm install -g @xingjisoft/neutrino
33
+ ```
34
+
35
+ Or run from source:
36
+
37
+ ```sh
38
+ git clone <repo-url> neutrino
39
+ cd neutrino
40
+ npm install
41
+ npm run build
42
+ npm link
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ ```sh
48
+ neutrino
49
+ ```
50
+
51
+ Type a message on the start screen and press `Enter` to begin a new chat.
52
+
53
+ ### Keys
54
+
55
+ | Keys | Action |
56
+ | --- | --- |
57
+ | `Enter` | Send message / confirm (only a manually typed Enter sends) |
58
+ | `←`/`→`, `↑`/`↓` | Move focus / navigate |
59
+ | `Ctrl+E` | Open model config |
60
+ | `Ctrl+B` | Back to menu (from chat) |
61
+ | `Ctrl+L` | Clear conversation (from chat) |
62
+ | `Esc Esc` | Pause / resume streaming |
63
+ | `Esc` | Clear an active selection |
64
+ | `PgUp` / `PgDn` | Scroll chat history |
65
+ | `Ctrl+C` | Copy the selection, or quit if nothing is selected |
66
+ | `Ctrl+A` | Toggle Request Approval / Auto Approval in a conversation |
67
+ | `Y` | Approve the pending request (while the approval card is up) |
68
+ | `N` / `Esc` | Deny the pending request |
69
+ | `Ctrl+Q` | Quit |
70
+
71
+ ### Mouse
72
+
73
+ | Action | Effect |
74
+ | --- | --- |
75
+ | Hover over an item | Highlight / select it in lists and menus |
76
+ | Click an item | Select and confirm (open a screen, load a session, apply a setting…) |
77
+ | Click the input box | Focus it and place the cursor at the clicked position |
78
+ | Click and drag | Select text under the rectangle; releasing copies it to the clipboard |
79
+ | Click the header (left of `ⓘ`) | Open the model selector |
80
+ | Click `ⓘ` (chat header) | Toggle token usage stats |
81
+ | Drag the scrollbar | Grab the thumb on the right edge of the chat/log and drag it to scroll |
82
+ | Scroll wheel | Scroll the chat/log, or move through lists |
83
+ | Right-click | Go back (like `Esc`) |
84
+
85
+ The selection highlight is drawn over whatever is on screen, so it works uniformly on the chat log, the input box and every menu. Selected text is copied with trailing whitespace trimmed per line (shell-style), so it can be pasted straight back into a terminal. UI chrome (such as the scrollbar) is skipped when copying and highlighting, so a copy never picks up the bar or the padding in front of it.
86
+
87
+ ## Configuration
88
+
89
+ Settings are stored under `~/.neutrino/`:
90
+
91
+ - `model.json` — provider and model definitions (edit to add your own endpoints).
92
+ - `config.json` — app configuration (selected provider/model/intensity, theme,
93
+ language, approval mode, `auto_approve_extreme_risk`, `sound_alerts`).
94
+ - `session/` — saved conversation sessions, one JSON file per session.
95
+
96
+ Set your API key in the model select screen or directly in `model.json`.
97
+
98
+ A few environment variables override flags:
99
+
100
+ | Variable | Effect |
101
+ | --- | --- |
102
+ | `NEUTRINO_WEB_PORT` | Web UI port: like `--web-port` in TUI mode, and the port in `neutrino web`. |
103
+ | `NEUTRINO_WEB_HOST` | Interface to bind, like `--host`. |
104
+
105
+ ### Approvals
106
+
107
+ Every tool call is classified before it runs.
108
+
109
+ | Class | Examples | Default behaviour |
110
+ | --- | --- | --- |
111
+ | Routine | `npm run build`, `git status`, reading or writing inside the project | Runs without asking |
112
+ | High | `sudo`, `chmod`, package installs, `git push`, `curl`, inline `python -c`, any path outside the working directory | Asks the user |
113
+ | Extreme | Delete-class: `rm`, `del`, `shred`, `mkfs`, `git clean -f`, `git reset --hard`, `find -delete`, `DROP TABLE`, … | Always asks the user |
114
+
115
+ While a call waits for an answer the top bar turns orange and reads
116
+ **Request Approval**, and an orange card floats above the input box with the
117
+ command, **why the agent says it needs it**, why it was flagged, and
118
+ `[ Y ] Approve` / `[ N ] Deny`. Approving runs the call; denying returns a note
119
+ to the model so it can pick another route instead of retrying.
120
+
121
+ Every tool call carries a `reason` argument, and a call that needs approval but
122
+ states none does not run: the gate refuses it before you are interrupted or a
123
+ reviewer is paid for, and the model is told to resend the same call with a
124
+ reason. The reason is the agent's own claim, shown apart from the arguments.
125
+ A vague, imperfect or seemingly unrelated reason is not itself a safety hazard;
126
+ reviewers judge the actual call's consequences and explicit user restrictions.
127
+
128
+ Two phrases in your message change the policy for that conversation:
129
+
130
+ | Phrase in the prompt | Effect |
131
+ | --- | --- |
132
+ | `无需审批` | High-risk calls run without asking. Delete-class calls still ask. |
133
+ | `我已知悉风险,同意给予完整权限,无需任何审批` | Nothing asks, including delete-class calls. |
134
+
135
+ The one-row **Request Approval / Auto Approval** button above the main menu's
136
+ version label sets and remembers the default for new conversations. Each terminal
137
+ conversation has its own button above the input (or **Ctrl+A**), and each browser
138
+ conversation has a switch in its controls. The conversation's choice is saved
139
+ with its history and synced between terminal and browser. Writing `自动审批` or
140
+ `自动批准` in a prompt no longer enables automatic review.
141
+
142
+ With **Auto Approval** enabled, each pending tool call receives **two independent
143
+ safety reviews**. A review round passes when its average safety score is at least
144
+ 60/100 and no concrete safety hazard or failed review blocks it. Task relevance
145
+ has zero weight and no ordinary minimum: even a score of 0 does not by itself
146
+ prevent a safe call from running, and doubts about purpose must not lower safety.
147
+
148
+ Only an extreme, obvious task mismatch can veto on relevance: both reviewers
149
+ must independently mark it, give a relevance score of at most 5/100, and provide
150
+ specific evidence comparing the task and proposed action. One suspicion, weak
151
+ justification, or uncertainty about the task is insufficient.
152
+
153
+ The policy favors ordinary implementation and verification steps, including
154
+ scoped edits, dependency installation, public network checks and temporary test
155
+ files. Concrete serious hazards and explicit user prohibitions block approval;
156
+ a failed, timed-out or malformed review also prevents automatic execution.
157
+ Every assessment and the safety result are included in the decision, per round
158
+ for delete-class calls.
159
+
160
+ Each turn captures its literal user request and earlier user messages separately
161
+ from summaries and tool output, so short follow-ups keep their task context.
162
+ Queued messages take effect on the next turn. Denied tool results include the
163
+ reviewed user request and recent user requests for diagnosis.
164
+
165
+ Automatic approval of delete-class commands is behind a switch in **Settings →
166
+ Extreme-risk auto-approval**, which is **off by default**: while it is off,
167
+ delete-class calls always go to a human even in **Auto Approval** mode.
168
+
169
+ With the switch on, a delete-class call is reviewed **three separate times**.
170
+ Each round is the usual pair of independent reviewers; the rounds run one after
171
+ another and no round is told what an earlier one concluded. Every round is
172
+ judged on its own scores and the call only runs if **all three** approve, so the
173
+ first round that denies or fails ends the sequence: a deletion never gets a
174
+ second chance to pass. The switches are layered, so turning **Auto Approval**
175
+ off still sends every delete-class request to you. The reviewer model defaults
176
+ to the chat model and can be changed in **Settings → Approval reviewer model**.
177
+
178
+ ### Sound alerts
179
+
180
+ A short cue plays when a turn finishes and when an approval request appears, in
181
+ both the terminal and the web UI, so a long task or a blocking approval is
182
+ noticeable from another window. Approval requests ring twice; finished turns
183
+ ring once. Toggle it in **Settings → Sound alerts** (TUI or web), or set
184
+ `"sound_alerts": false` in `config.json`.
185
+
186
+ Browsers refuse to start audio before a user gesture, so the web UI stays silent
187
+ until the page has been clicked or a key has been pressed at least once.
188
+
189
+ ### Browsing other agents' history
190
+
191
+ From the history screen, select **Open other Agent's history** to view past sessions recorded by other CLI agents installed on your machine:
192
+
193
+ - **Claude Code** — reads `~/.claude/projects/**/*.jsonl`.
194
+ - **Codex** — reads `~/.codex/sessions` and `~/.codex/archived_sessions`.
195
+ - **OpenCode** — reads the local SQLite database (`opencode.db`) via `node:sqlite` (requires Node.js **22.13+**; silently unavailable on older versions).
196
+
197
+ Only sessions that were started in the current working directory are listed, so the history stays focused on the project you are in. These sessions are read-only; you can open them for review but not edit or delete.
198
+
199
+ ## Web UI
200
+
201
+ Neutrino can mount a small website on a port of your choice. It groups every
202
+ saved conversation by working directory (showing the five newest per path with
203
+ the rest collapsed), streams a running conversation live, and lets you pause or
204
+ resume it and send new messages from the browser.
205
+
206
+ A saved transcript opens read-only, because nothing is running for it. Press
207
+ **Continue here** (or simply type in the composer) and the server resumes it
208
+ in-process — same model, working directory and token counters — so you can keep
209
+ chatting from the browser with no terminal attached. Turns are written back to
210
+ the same session file.
211
+
212
+ Use the selectors above a conversation to change its provider, model or
213
+ intensity, then press **Apply to conversation** (pause an active turn first).
214
+ The **⚙ Settings** button in the sidebar edits defaults for new conversations,
215
+ context compression and approval settings. Conversation choices are saved with
216
+ the transcript and do not overwrite the defaults. Configured models and keys
217
+ are shared with the terminal; keys stay on the server.
218
+
219
+ Context auto-compression starts at 80% of the current model's context window.
220
+ Models default to 1,000,000 tokens (compression at 800,000). Adjust the model
221
+ context window in terminal Settings or select the provider/model in Web Settings
222
+ and enter its capacity in tokens. This is saved per provider/model and applies
223
+ to future turns in active conversations. The legacy fixed
224
+ `context_compress_threshold` setting is no longer used.
225
+
226
+ ```sh
227
+ # Mount the web UI while the terminal UI runs
228
+ neutrino --web-port 8765
229
+
230
+ # Or serve only the saved history, with no terminal
231
+ neutrino web --port 8765
232
+ ```
233
+
234
+ Then open <http://127.0.0.1:8765>. The site itself lives in [`web/`](./web) and
235
+ needs no build step; see [`web/README.md`](./web/README.md) for the API.
236
+
237
+ ### Reaching it from another device
238
+
239
+ The server binds to loopback, so only this machine can open it. To use it from a
240
+ phone, a tablet or another box on your LAN or VPN, bind an interface others can
241
+ see:
242
+
243
+ ```sh
244
+ neutrino web --port 8765 --lan # every interface (0.0.0.0)
245
+ neutrino --web-port 8765 --lan # same, while the TUI runs
246
+ neutrino web --port 8765 --host 100.64.0.7 # one specific address
247
+ ```
248
+
249
+ Startup prints every address that works, so you can copy the one matching the
250
+ device you are on:
251
+
252
+ ```
253
+ Neutrino web UI listening on 0.0.0.0:8765
254
+ Open the address that matches your device:
255
+ http://100.105.130.25:8765/
256
+ http://192.168.1.211:8765/
257
+ http://127.0.0.1:8765/
258
+ ```
259
+
260
+ Open the address and sign in with your permanent Web password. The initial
261
+ password is **`114514`**. Change it in the browser's **Settings → Web access**
262
+ or in the terminal's **Settings → Web password**. It is saved as a salted hash
263
+ in `~/.neutrino/config.json` and survives restarts.
264
+
265
+ For automatic login, open `http://<host>:8765/?pwd=114514`. The page checks the
266
+ password, remembers the login, and removes `pwd` from the address bar. If it is
267
+ incorrect, the password login dialog lets you try again. URL-encode special
268
+ characters in your password.
269
+
270
+ The browser remembers your login in an HttpOnly cookie for up to one year,
271
+ including across server restarts. Changing the password signs out other
272
+ browsers; the browser that saves the new password stays signed in. Addresses
273
+ and startup output never contain your password or session credentials.
274
+
275
+ - **`--no-auth`** explicitly disables password login.
276
+ - Static files and the login endpoint remain public; all conversation and
277
+ settings APIs require a login, including when accessed through localhost.
278
+ - **Firewall:** the first bind to `0.0.0.0` triggers an allow prompt on Windows
279
+ and macOS. Allow it for private networks, and check that your WLAN is not
280
+ marked "public", where incoming connections are blocked anyway.
281
+ - **`127.0.0.1` remains the default** because the zero-config case should not be
282
+ a network service.
283
+
284
+ ## Built-in tools
285
+
286
+ The agent can call the following tools during a conversation:
287
+
288
+ - `run_command` — execute a shell command (120s timeout, output capped).
289
+ - `read_file` — read a text file (capped at 200KB).
290
+ - `write_file` — write content to a file.
291
+ - `list_files` — list directory contents.
292
+
293
+ Every tool takes an additional `reason` argument: one sentence saying what the
294
+ call is for and why it is needed now. It is what the approval card shows and what
295
+ reviewers check the arguments against, so it is required even for calls that
296
+ usually run without asking.
297
+
298
+ ## Development
299
+
300
+ ```sh
301
+ npm run build # compile TypeScript to dist/
302
+ npm test # build, then run every test below
303
+ ```
304
+
305
+ Individual tests:
306
+
307
+ ```sh
308
+ node scripts/test-paste.mjs # verify pasted newlines are flattened to spaces
309
+ node scripts/test-mouse.mjs # verify mouse escape-sequence parsing
310
+ node scripts/test-cursor.mjs # verify click-to-position-cursor math
311
+ node scripts/test-selection.mjs # verify selection highlighting & text extraction
312
+ node scripts/test-copy.mjs # verify a copy never includes the scrollbar or trailing padding
313
+ node scripts/test-scrollbar.mjs # verify the conversation scrollbar size/position and drag maths
314
+ node scripts/test-markdown.mjs # verify markdown/table rendering (incl. ragged rows)
315
+ node scripts/test-wrap.mjs # verify indentation & spacing are preserved when wrapping
316
+ node scripts/test-render.mjs # fuzz the diff renderer + render the chat screen,
317
+ # asserting no stale/residual cells are left behind
318
+ node scripts/test-sanitize.mjs # verify ANSI/control codes never reach the screen
319
+ node scripts/test-drift.mjs # verify the input area self-heals after terminal drift
320
+ node scripts/test-fetch.mjs # verify streamed requests release aborts and retry drops
321
+ node scripts/test-compact.mjs # verify context-compression planning
322
+ node scripts/test-history-fold.mjs # verify full-history retention, folding and resume
323
+ node scripts/test-web-history.mjs # verify browser history disclosures and streaming state
324
+ node scripts/test-tools.mjs # verify tool-output folding
325
+ node scripts/test-web.mjs # verify the web server, API, SSE stream and path-traversal guards
326
+ node scripts/test-web-settings.mjs # verify model switching, persistence, browser controls and defaults
327
+ node scripts/test-web-settings-http.mjs # verify authenticated settings and model changes over HTTP
328
+ ```
329
+
330
+ ### Rendering model
331
+
332
+ The screen is drawn into a `Buffer` of cells which is diffed against the previous frame, so only changed rows are emitted. Rows are erased to the end of the line before being repainted, and the buffer keeps double-width glyphs consistent (writing over one half of a wide character blanks the other half). Together these guarantee that a repainted row can never leave a stale cell behind.
333
+
334
+ Because only *changed* rows are emitted, a row whose contents happen to be identical from one frame to the next is never rewritten — so if the terminal shifts its own contents behind our back (ConPTY reflowing the buffer mid-resize, a size-race repaint, a stray scroll) that row would stay stale. Two defences cover this: the persistent chrome (input box, tabs, key hints) is repainted on every frame via `Screen.alwaysRepaintRows()`, and the app periodically re-emits the whole cached frame (`Renderer.repaint()`) as a safety net. Both are no-ops when nothing drifted.
335
+
336
+ ### Text hygiene
337
+
338
+ A cell's glyph is copied straight into the output stream, so a cell holding a *control* character becomes a command the terminal executes — an ESC sequence can move the cursor, clear the screen or leave the alternate buffer, and a TAB shifts the rest of the row. Tool output (ANSI colours from `ls --color`, `git`, `npm`, progress bars, …), file contents and pasted text all carry such bytes, so they are neutralised in three places:
339
+
340
+ 1. `src/sanitize.ts` — `sanitizeText()` removes whole escape sequences (not just the ESC byte, which would leave `[31m` behind as literal text), expands tabs so measured widths match what the terminal draws, and drops any remaining control code. It is applied where untrusted text enters the display (chat messages, tool results, pasted input).
341
+ 2. `Buffer` — writing a control character stores a space instead, preserving the column width the caller measured, so no cell can ever hold one.
342
+ 3. `Renderer` — a last-resort check when emitting a row, so the byte stream cannot carry a control code even if a cell were populated another way.
343
+
344
+ ### Network resilience
345
+
346
+ Model responses arrive over a streamed connection that can drop at any point. A single blip should not end the session, so three cases are handled:
347
+
348
+ - **The request never goes out** (`fetch` rejects) — retried a couple of times with a short backoff, bounded by a connect timeout.
349
+ - **The stream dies before the first byte** — the server or a proxy closed the connection mid-flight (`TypeError: terminated`, `other side closed`, …). Nothing has reached the user, so the request is simply re-issued.
350
+ - **The stream dies after output has started** — retrying would duplicate text, so the error is surfaced instead, with the real reason expanded out of the `error.cause` chain (`fetch failed` alone says nothing useful).
351
+
352
+ Any stream the consumer walks away from (pause, or a newer turn taking over) always has its body cancelled, so sockets are not leaked.
353
+
354
+ ### Crash safety
355
+
356
+ The TUI takes over the terminal: raw mode, the alternate screen and mouse
357
+ reporting. If the process ever dies without undoing those, the shell inherits a
358
+ terminal that types `[<b>;<x>;<y>M` mouse reports at every cursor move.
359
+
360
+ So the teardown sequence is registered three ways — normal quit, a
361
+ `process.on("exit")` hook that writes synchronously to fd 1, and
362
+ `uncaughtException` / `unhandledRejection` handlers. Normal quit uses the same
363
+ synchronous teardown, so pending frame output cannot swallow the cleanup.
364
+ Catchable exits also flush the latest conversation snapshot, including a partial
365
+ reply. Uncaught errors are appended to `~/.neutrino/crash.log` and exit with
366
+ status 1. SIGHUP and Windows Ctrl+Break also restore and checkpoint before exit.
367
+
368
+ Session snapshots are written to a temporary file in the session directory,
369
+ synced, then atomically renamed over the previous snapshot. A failed write
370
+ preserves the previous history, reports an error in the UI and crash log, and
371
+ is retried at the next checkpoint. Partial replies are replaced by the completed
372
+ reply in the saved snapshot, so reopening history does not duplicate them.
373
+
374
+ Key handlers and the render loop are also wrapped: they run inside `stdin`
375
+ callbacks and timers, where a throw is an uncaught exception that used to end
376
+ the session. A failed frame is now retried, and only a screen that cannot be
377
+ drawn at all stops the app.
378
+
379
+ An uncatchable kill (SIGKILL, TerminateProcess or a runtime/OS failure) cannot
380
+ run cleanup. Already-written snapshots survive; with a writable disk, streamed
381
+ output since the last one-second checkpoint may be lost. If a shell is stuck
382
+ in mouse mode, run this in Bash:
383
+
384
+ ```sh
385
+ printf '\033[?2004l\033[?1000l\033[?1002l\033[?1003l\033[?1006l\033[?1015l\033[?25h\033[0m\033[?1049l'
386
+ stty sane
387
+ ```
388
+
389
+ In PowerShell:
390
+
391
+ ```powershell
392
+ $esc = [char]27
393
+ [Console]::Write("${esc}[?2004l${esc}[?1000l${esc}[?1002l${esc}[?1003l${esc}[?1006l${esc}[?1015l${esc}[?25h${esc}[0m${esc}[?1049l")
394
+ ```
395
+
396
+ ## License
397
+
398
+ Apache-2.0. See [LICENSE](./LICENSE).
package/assets/prompt.txt CHANGED
@@ -16,5 +16,9 @@ Guidelines:
16
16
  - When suggesting changes, explain the reasoning.
17
17
  - Use the tools available to you to explore the codebase before making changes.
18
18
  - Verify your changes by running tests and linting when possible.
19
+ - Every tool call must carry a `reason` argument: one honest sentence saying what
20
+ the call is for and why it is needed for the current request. It is what the
21
+ user sees when the call needs approval, and a call that needs approval but gives
22
+ no reason is refused instead of run.
19
23
 
20
24
  Language: Always respond in the same language the user writes in.