@xingjisoft/neutrino 0.1.4 → 0.1.6
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 +413 -163
- package/assets/prompt.txt +4 -0
- package/dist/agents.js +114 -15
- package/dist/app.js +442 -30
- package/dist/bin.js +177 -5
- package/dist/colors.js +2 -0
- package/dist/compact.js +19 -0
- package/dist/config.js +121 -28
- package/dist/cwd.js +54 -0
- package/dist/header.js +8 -0
- package/dist/llm/approval.js +543 -0
- package/dist/llm/client.js +14 -0
- package/dist/llm/risk.js +377 -0
- package/dist/llm/tools.js +37 -21
- package/dist/persistence.js +51 -0
- package/dist/screens/agentHistory.js +7 -4
- package/dist/screens/chat.js +709 -49
- package/dist/screens/history.js +9 -7
- package/dist/screens/settings.js +131 -14
- package/dist/screens/start.js +50 -16
- package/dist/selection.js +60 -0
- package/dist/state.js +1 -0
- package/dist/terminal/input.js +34 -5
- package/dist/terminal/renderer.js +58 -12
- package/dist/terminal/restore.js +148 -0
- package/dist/ui.js +16 -0
- package/dist/web/auth.js +82 -0
- package/dist/web/hub.js +180 -0
- package/dist/web/server.js +688 -0
- package/dist/web/session.js +572 -0
- package/dist/web/settings.js +101 -0
- package/dist/width.js +20 -0
- package/package.json +6 -4
- package/web/README.md +210 -0
- package/web/app.js +1100 -0
- package/web/index.html +90 -0
- package/web/settings.js +193 -0
- package/web/style.css +1088 -0
- package/web/turns.js +52 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingjisoft/neutrino",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "XINGJI Neutrino - a CLI coding agent in the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -10,15 +10,17 @@
|
|
|
10
10
|
"main": "dist/app.js",
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
-
"assets"
|
|
13
|
+
"assets",
|
|
14
|
+
"web"
|
|
14
15
|
],
|
|
15
16
|
"engines": {
|
|
16
17
|
"node": ">=18.0.0"
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
19
20
|
"build": "tsc -p tsconfig.json",
|
|
20
|
-
"test": "npm run build && node scripts/test-paste.mjs && node scripts/test-mouse.mjs && node scripts/test-cursor.mjs && node scripts/test-selection.mjs && node scripts/test-copy.mjs && node scripts/test-scrollbar.mjs && node scripts/test-render.mjs && node scripts/test-drift.mjs && node scripts/test-sanitize.mjs && node scripts/test-markdown.mjs && node scripts/test-wrap.mjs && node scripts/test-fetch.mjs && node scripts/test-compact.mjs && node scripts/test-tools.mjs",
|
|
21
|
-
"prepare": "npm run build"
|
|
21
|
+
"test": "npm run build && node scripts/test-paste.mjs && node scripts/test-mouse.mjs && node scripts/test-cursor.mjs && node scripts/test-selection.mjs && node scripts/test-drag-scroll.mjs && node scripts/test-selection-performance.mjs && node scripts/test-copy.mjs && node scripts/test-scrollbar.mjs && node scripts/test-render.mjs && node scripts/test-drift.mjs && node scripts/test-sanitize.mjs && node scripts/test-markdown.mjs && node scripts/test-wrap.mjs && node scripts/test-fetch.mjs && node scripts/test-compact.mjs && node scripts/test-history-fold.mjs && node scripts/test-web-history.mjs && node scripts/test-turn-fold.mjs && node scripts/test-web-settings.mjs && node scripts/test-tools.mjs && node scripts/test-approval.mjs && node scripts/test-approval-review.mjs && node scripts/test-approval-ui.mjs && node scripts/test-web-approval-details.mjs && node scripts/test-approval-mode.mjs && node scripts/test-approval-context.mjs && node scripts/test-settings-render.mjs && node scripts/test-approval-flow.mjs && node scripts/test-web.mjs && node scripts/test-web-settings-http.mjs && node scripts/test-web-auth.mjs && node scripts/test-web-auth-ui.mjs && node scripts/test-interrupt.mjs && node scripts/test-terminal-lifecycle.mjs && node scripts/test-persistence.mjs && node scripts/test-resilience.mjs && node scripts/test-approval-timeout.mjs && node scripts/test-approval-hardening.mjs && node scripts/test-approval-extreme.mjs && node scripts/test-approval-reason.mjs",
|
|
22
|
+
"prepare": "npm run build",
|
|
23
|
+
"web": "node dist/bin.js web"
|
|
22
24
|
},
|
|
23
25
|
"keywords": [
|
|
24
26
|
"cli",
|
package/web/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Neutrino Web UI
|
|
2
|
+
|
|
3
|
+
A small, dependency-free single page app that is served by the Neutrino CLI on
|
|
4
|
+
a port you choose. It lets you:
|
|
5
|
+
|
|
6
|
+
- browse every saved conversation grouped by working directory (like Codex),
|
|
7
|
+
with only the five latest transcripts per path shown and the rest collapsed;
|
|
8
|
+
- watch a running terminal conversation stream in real time, with every
|
|
9
|
+
finished turn folded into one summary line that opens on a click (the turn
|
|
10
|
+
being answered stays open, and a paused turn is not folded either);
|
|
11
|
+
- switch the provider, model and intensity for the next turn;
|
|
12
|
+
- edit defaults, context compression and approval settings in a Settings dialog;
|
|
13
|
+
- drive the running conversation: pause / resume the current turn and send new
|
|
14
|
+
messages (they queue while the agent is busy);
|
|
15
|
+
- pick up a saved conversation in the browser and keep chatting, even when no
|
|
16
|
+
terminal is running.
|
|
17
|
+
|
|
18
|
+
Nothing needs to be built — these are plain `.html`, `.css` and `.js` files.
|
|
19
|
+
|
|
20
|
+
## Running it
|
|
21
|
+
|
|
22
|
+
While using the terminal UI, mount the website on a port:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
neutrino --web-port 8765
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or serve only the saved history, without a terminal:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
neutrino web --port 8765
|
|
32
|
+
# or
|
|
33
|
+
npm run web
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then open <http://127.0.0.1:8765>.
|
|
37
|
+
|
|
38
|
+
## Using it from another device
|
|
39
|
+
|
|
40
|
+
The server binds to loopback by default. To open it from your phone or another
|
|
41
|
+
machine, bind an interface others can reach:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
neutrino web --port 8765 --lan # every interface (0.0.0.0)
|
|
45
|
+
neutrino web --port 8765 --host 100.64.0.7 # one specific address
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Startup prints the addresses to open:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Neutrino web UI listening on 0.0.0.0:8765
|
|
52
|
+
Open the address that matches your device:
|
|
53
|
+
http://192.168.1.211:8765/
|
|
54
|
+
http://127.0.0.1:8765/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Hypervisor and container host-only adapters (`vEthernet`, VMware, VirtualBox,
|
|
58
|
+
Docker), link-local `169.254/16` and loopback addresses other than `127.0.0.1`
|
|
59
|
+
are left out of that list on purpose: they cannot be reached from another
|
|
60
|
+
device. CGNAT ranges such as Tailscale's `100.64/10` are kept, since a VPN is a
|
|
61
|
+
common way to reach a machine.
|
|
62
|
+
|
|
63
|
+
## Authentication
|
|
64
|
+
|
|
65
|
+
The Web UI uses a permanent password, initially **`114514`**, on both localhost
|
|
66
|
+
and network interfaces. Change it in **Settings → Web access** in the browser
|
|
67
|
+
or **Settings → Web password** in the terminal. In the browser, enter the new
|
|
68
|
+
password twice and save; leaving both fields blank preserves the current one.
|
|
69
|
+
In the terminal, select the password row, press Enter, type or paste a new
|
|
70
|
+
password, and press Enter to save (Escape cancels).
|
|
71
|
+
|
|
72
|
+
Only a salted scrypt hash is stored in `~/.neutrino/config.json`. The password
|
|
73
|
+
does not expire and is not reset when the server restarts. Settings never send
|
|
74
|
+
back the password or its hash.
|
|
75
|
+
|
|
76
|
+
A modal login form stays above the conversation UI. Successful login sets an
|
|
77
|
+
HttpOnly, SameSite=Strict cookie, remembered for up to one year and shared by
|
|
78
|
+
HTTP requests and the SSE stream. Session credentials are never put into URLs
|
|
79
|
+
or browser localStorage. A password change invalidates existing login cookies; the browser
|
|
80
|
+
that saves the password receives a replacement cookie and stays signed in.
|
|
81
|
+
|
|
82
|
+
To sign in directly from a link, open `http://<host>:8765/?pwd=114514`.
|
|
83
|
+
The browser automatically checks the supplied password through the login endpoint
|
|
84
|
+
before loading conversations, remembers the login cookie, and removes `pwd`
|
|
85
|
+
from the address bar while keeping other query parameters and the URL fragment.
|
|
86
|
+
An incorrect password or a failed request shows the normal login dialog with
|
|
87
|
+
an error, so you can enter the password manually. URL-encode passwords that
|
|
88
|
+
contain special characters (for example, `+` becomes `%2B` and `&` becomes `%26`).
|
|
89
|
+
|
|
90
|
+
For scripts, POST `{"password":"114514"}` to `/api/auth/login` and retain the
|
|
91
|
+
returned cookie for subsequent requests. POST `/api/auth/logout` clears the
|
|
92
|
+
browser cookie. Missing or invalid login cookies receive HTTP 401. Legacy token
|
|
93
|
+
headers, token URLs, `--token` and `NEUTRINO_WEB_TOKEN` are no longer used.
|
|
94
|
+
|
|
95
|
+
Static files and the login/logout routes stay public. `--no-auth` explicitly
|
|
96
|
+
disables password login.
|
|
97
|
+
|
|
98
|
+
## Models and settings
|
|
99
|
+
|
|
100
|
+
Open a conversation to see the **Provider**, **Model** and **Thinking intensity**
|
|
101
|
+
selectors. Choose a configured model and press **Apply to conversation**. The
|
|
102
|
+
next turn uses that selection, and it is saved with the transcript. The same
|
|
103
|
+
controls also update a conversation running in the terminal. Pause an active
|
|
104
|
+
turn before applying a change; a running turn returns a clear conflict message.
|
|
105
|
+
|
|
106
|
+
For saved history, **Continue with model** resumes the transcript with the chosen
|
|
107
|
+
selection. Changing the selectors and then using **Continue here** or sending a
|
|
108
|
+
message also uses the draft selection, so a transcript whose old model was
|
|
109
|
+
removed can still be continued.
|
|
110
|
+
|
|
111
|
+
The **⚙ Settings** button in the sidebar opens defaults for new conversations,
|
|
112
|
+
the context compression threshold, the approval reviewer model, and the
|
|
113
|
+
extreme-risk automatic approval option. Save settings explicitly; cancelling
|
|
114
|
+
leaves the saved values intact. Conversation model changes are separate from
|
|
115
|
+
these defaults. Models come from the same catalog as the terminal, and API keys
|
|
116
|
+
stay on the server. Configure missing keys in the terminal model screen first.
|
|
117
|
+
Intensity uses the same Low–Ultra sampling settings as the terminal.
|
|
118
|
+
|
|
119
|
+
## History vs. live sessions
|
|
120
|
+
|
|
121
|
+
A saved transcript on disk is a finished record: nothing is running, so the
|
|
122
|
+
page shows it read-only (dashed `read-only` badge) and there is nothing to
|
|
123
|
+
pause.
|
|
124
|
+
|
|
125
|
+
To continue one, either
|
|
126
|
+
|
|
127
|
+
- press **✎ Continue here** in the header, or
|
|
128
|
+
- just start typing in the composer and press Enter.
|
|
129
|
+
|
|
130
|
+
Both create a *browser-owned session*: the server resumes the transcript from
|
|
131
|
+
disk, registers it with the live hub, and runs the agent loop headlessly with
|
|
132
|
+
the model, provider, intensity, working directory and token counters recorded in
|
|
133
|
+
the file. From that moment the page behaves exactly like a live terminal
|
|
134
|
+
conversation — streaming output, tool calls, pause/resume, queued messages — and
|
|
135
|
+
every turn is written back to the same transcript file.
|
|
136
|
+
|
|
137
|
+
If the same transcript is already live (a terminal attached, or another tab
|
|
138
|
+
resumed it), the page attaches to that session instead of starting a second one.
|
|
139
|
+
Browser-owned sessions are dropped after 30 idle minutes, or immediately through
|
|
140
|
+
`POST /api/live/:key/close`. Sessions owned by the terminal cannot be closed from
|
|
141
|
+
the web.
|
|
142
|
+
|
|
143
|
+
## How real-time works
|
|
144
|
+
|
|
145
|
+
A `ChatScreen` registers itself with the in-process hub when it is opened. Every
|
|
146
|
+
time it changes (a streamed token, a tool call, a pause, a save) it notifies the
|
|
147
|
+
hub, which coalesces updates and pushes a snapshot over
|
|
148
|
+
`GET /api/live/:key/stream` (SSE). The page keeps the open conversation in sync
|
|
149
|
+
and re-renders it; the sidebar polls `/api/overview` for new sessions.
|
|
150
|
+
|
|
151
|
+
Browser-owned sessions publish to the same hub, so the page code does not need
|
|
152
|
+
to know whether a conversation is driven by the terminal or by the server.
|
|
153
|
+
|
|
154
|
+
## API
|
|
155
|
+
|
|
156
|
+
| Method | Path | Purpose |
|
|
157
|
+
| --- | --- | --- |
|
|
158
|
+
| GET | `/api/overview` | Sessions grouped by cwd + live sessions |
|
|
159
|
+
| GET | `/api/sessions` | Flat list of saved sessions |
|
|
160
|
+
| GET | `/api/settings` | Model catalog and public settings; no credentials |
|
|
161
|
+
| POST | `/api/settings` | Save validated `{ selection?, settings? }` defaults |
|
|
162
|
+
| GET | `/api/session/:id` | One saved transcript |
|
|
163
|
+
| POST | `/api/session/:id/continue` | Resume a transcript with optional `{ selection }`, returns `{ key }` |
|
|
164
|
+
| GET | `/api/live/:key` | Snapshot of a live session |
|
|
165
|
+
| GET | `/api/live/:key/stream` | SSE stream of a live session |
|
|
166
|
+
| POST | `/api/live/:key/pause` | Pause the running turn |
|
|
167
|
+
| POST | `/api/live/:key/resume` | Resume a paused conversation |
|
|
168
|
+
| POST | `/api/live/:key/message` | Send `{ "text": "…" }` |
|
|
169
|
+
| POST | `/api/live/:key/model` | Apply `{ provider, model, intensity }` while idle or paused |
|
|
170
|
+
| POST | `/api/live/:key/close` | Dispose a browser-owned session |
|
|
171
|
+
| GET | `/api/health` | Liveness probe |
|
|
172
|
+
| POST | `/api/auth/login` | Sign in with `{ "password": "…" }` |
|
|
173
|
+
| POST | `/api/auth/logout` | Clear the login cookie |
|
|
174
|
+
| GET / POST | `/api/settings` | Read settings or update them, including `settings.web_password` |
|
|
175
|
+
|
|
176
|
+
Conversation and settings routes require the login cookie; see
|
|
177
|
+
[Authentication](#authentication). `OPTIONS` is answered unauthenticated so
|
|
178
|
+
preflights are not blocked, and it returns no data.
|
|
179
|
+
|
|
180
|
+
### Approval requests
|
|
181
|
+
|
|
182
|
+
When a running agent wants to do something risky (a delete-class command, an
|
|
183
|
+
install, a path outside the working directory) the browser shows an orange
|
|
184
|
+
**Request Approval** banner above the composer, showing the full command (or
|
|
185
|
+
formatted tool arguments), working directory and risk reasons, plus two buttons.
|
|
186
|
+
Long proposals remain complete and scroll inside the card. Automatic reviews
|
|
187
|
+
show the same proposal details while they run.
|
|
188
|
+
`Approve` runs the call, `Deny` returns a note to the agent.
|
|
189
|
+
The reviewer uses the current turn's captured user request and earlier user
|
|
190
|
+
messages; queued input belongs to the next turn. Expand a denied tool result
|
|
191
|
+
to inspect the user requests that were reviewed.
|
|
192
|
+
|
|
193
|
+
Use the **Request Approval / Auto Approval** switch in each conversation's
|
|
194
|
+
controls to choose human approval or independent AI review. The choice is saved
|
|
195
|
+
with the conversation and synced with the terminal. `自动审批` / `自动批准` in
|
|
196
|
+
prompt text no longer changes this mode. Delete-class automatic review still
|
|
197
|
+
requires **Settings → Extreme-risk auto-approval**. Each pending call receives two
|
|
198
|
+
independent safety assessments. A round passes at an average safety score of
|
|
199
|
+
60/100; task relevance has no weight or ordinary minimum. Only an extreme,
|
|
200
|
+
obvious task mismatch independently confirmed with evidence by both reviewers
|
|
201
|
+
(relevance at most 5/100 each) can veto on intent. Concrete serious hazards,
|
|
202
|
+
explicit user prohibitions and failed reviews still prevent execution.
|
|
203
|
+
|
|
204
|
+
It is the same gate the terminal uses: the prompt opt-outs `无需审批` and
|
|
205
|
+
`我已知悉风险,同意给予完整权限,无需任何审批` still work, and
|
|
206
|
+
`POST /api/live/:key/approve` with `{"approved": true|false}` answers the request
|
|
207
|
+
programmatically (409 when nothing is pending).
|
|
208
|
+
|
|
209
|
+
`POST /api/live/:key/approval-mode` with `{"mode":"request"|"auto"}` changes
|
|
210
|
+
the conversation mode and returns its current snapshot (400 for invalid modes).
|