@xingjisoft/neutrino 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,416 +1,52 @@
1
1
  # XINGJI Neutrino
2
2
 
3
- [English](./README-en.md) | **简体中文**
3
+ ↓ 详细内容点这里 Learn More ↓
4
4
 
5
- 一个运行在终端中的 CLI 编程代理。Neutrino 提供由大语言模型驱动的交互式聊天界面,并内置工具调用能力(执行命令、读写文件),因此可以帮你处理真实项目。
5
+ [English](./README-en.md) | [简体中文](./README-en.md)
6
6
 
7
- ## 功能特性
7
+ ## 什么是 Neutrino ?
8
8
 
9
- - **交互式终端界面**——包含 Logo 页、聊天、模型选择、历史记录和设置页面。
10
- - **完整鼠标支持**——悬停高亮,点击选择/打开/定位光标,滚轮滚动,右键返回(与 OpenCode 类似)。
11
- - **选择与复制**——在任意文本(代理输出、输入框、菜单……)上拖动鼠标即可选中;松开按键后,选区会复制到剪贴板(通过 OSC 52,并提供原生回退方案)。将拖动保持在日志顶部或底部边缘可滚动日志,因此一次选择可以贯穿整段对话。
12
- - **粘贴友好的输入**——与 OpenCode 一样,粘贴的文本块会将换行符转换为空格以保持单行,只有手动键入的 `Enter` 才会发送消息。
13
- - **忠实的日志渲染**——聊天日志中的文本保留缩进、空行和列对齐;长工具输出换行时,续行会保持缩进,而不会退回到左侧页边距。
14
- - **可滚动的上下文日志**——当对话高度超过视图时,聊天区域(以及代理对话查看器)右边缘会绘制垂直滚动条。可用滚轮、`PgUp`/`PgDn` 或 `↑`/`↓` 滚动,也可以像 GUI 一样用鼠标抓住滚动条拖动。
15
- - **LLM 服务商**——内置 DeepSeek、OpenAI 和 Anthropic 预设;兼容任何 OpenAI 兼容端点。
16
- - **工具调用**——代理可以在你的工作目录中执行 shell 命令、读取文件、写入文件和列出目录。
17
- - **流式响应**——逐 token 输出,支持暂停/恢复(`ESC ESC`)。
18
- - **强度控制**——可在 `Low` 到 `Ultra` 之间调节 `temperature`/`top_p`,范围限于每个模型在 `model.json` 中声明的级别。
19
- - **会话历史**——消息会在回合开始前保存,流式回复每秒建立检查点,工具调用/结果会立即保存。历史记录页面列出在当前工作目录中开始的对话。保存失败会在终端和浏览器界面中显示。
20
- - **自动折叠回合**——回合结束后,回复背后的工作(思考和工具调用)会在终端和浏览器中折叠成一行摘要,而答案本身始终留在屏幕上。点击该行可重新展开整轮对话;没有多少内容可隐藏的回合,或本身很长的答案,会保持展开。用 `ESC ESC` 停止的回合处于暂停而非完成状态,因此保持展开。不会丢弃任何内容:对话记录、已保存会话和模型上下文都保留每条消息。
21
- - **完整对话历史**——上下文压缩会将每条原始消息和工具结果保留在磁盘上。较早部分在终端和浏览器中默认折叠;点击**更早的对话**可展开它,重新打开的历史记录中也是如此。模型请求则使用单独的摘要加上近期消息。
22
- - **先规划后执行的回合**——在回答请求之前,规划代理会先把它拆成有序、独立的步骤(“用 box.png 建模该案例”、“为相机制作动画:推近、环绕、拉远”),然后工作代理自上而下逐项执行这些步骤。每个步骤都是一项具体交付物,因此后续工作变得明确,而不是一条含糊的指令。参见[先规划后执行](#先规划后执行)。
23
- - **步骤卡片**——点击聊天标题栏中的 `ⓘ`(或按 `Ctrl+P`)可在右上角打开信息卡片。它把计划显示为纵向的节点时间线:正在处理的步骤高亮,尚未进行的步骤点亮,已完成的步骤以深色加对勾绘制。没有计划运行时,同一卡片会显示提示/补全/总 token 数。
24
- - **子代理**——独立工作可以交给子代理:一个拥有独立上下文、独立工具调用和相同审批关卡的第二个代理循环。只返回其报告,因此读取三十个文件的调研不会让对话里塞满三十个文件的内容。参见[子代理](#子代理)。
25
- - **代理历史记录浏览器**——可直接在历史记录页面查看其他 CLI 代理(Claude Code、Codex、OpenCode)的过往会话。
26
- - **审批关卡**——高风险命令和工作目录之外的路径需要你签字确认。标题栏会变成橙色并显示 **请求审批(Request Approval)**;按 `Y` 或 `N`。删除类命令始终会询问,除非你明确授予了完全信任;此外还有一个可选的审查代理用于自动决策。
27
- - **默认开启的浏览器界面**——每次启动 `neutrino` 时,也会在 <http://127.0.0.1:8765> 挂载一个本地网站,按工作目录列出所有已保存对话,实时流式显示正在运行的对话,并允许你暂停/恢复它、发送消息、开始新对话、打开或创建工作目录、切换服务商/模型/思考强度、编辑所有终端设置,以及在 English 和简体中文之间切换界面语言。用 `--port`/`--host` 修改它,用 `--lan` 共享它,或用 `--no-web` 只保留终端。
28
- - **Pro 工作区**——点击 Web 界面中的轨道徽标(或终端状态栏下方的 **启用Pro模式** 框)可切换到 `/pro` 处的隔离工作区,进入时会播放三秒的动态字标动画。Pro 通过预设滑块协调八个模型角色,强制规划,按角色临时降档,并可配置递归;只有机器级设置(Web 密码、审批策略和局域网绑定)是共享的。参见 [Pro 工作区](#pro-工作区)。
9
+ Neutrino 是由 XINGJI 工作室开发的一款跨生态跨平台的全新架构 Agent。
29
10
 
30
- ## 环境要求
11
+ ## Neutrino 有什么特点?
31
12
 
32
- - Node.js **>= 18.0.0**
33
- - 一个交互式终端(TTY)
13
+ - 提供 Web 控制台,对手机端进行特别优化,提供与 CLI 界面**一样甚至更好**的操作体验。
14
+ - Pro 模式 使用全新自研 “站中间串两边” 架构,把任务分割给各个最适合的模型,降低成本并提升一次通过率,免去繁杂的描述和拆分挑选,**真正做到一句话一次性做到最好**!
15
+ - OpenCode 的思考强度切换简直是一坨史,但Neutrino不存在这个问题。**你想要什么强度就用什么强度。**
16
+ - 配置文件条目清晰,想怎么用就怎么用,相比于 OpenCode 更加方便。
17
+ - 更严谨更方便的自动审批系统,**一键切换**,多智能体评审,保证数据安全。
18
+ - 超级轻量,总代码量仅 3 万行。
19
+ - 兼容 ClaudeCode、OpenCode、Codex 等多种 Agent 的对话格式,无缝迁移。
20
+ - 1000 万以内最好的 Agent!(绝对没有嘲讽某公司的意思)
34
21
 
35
- ## 安装
22
+ ## 相比于 OpenCode 的优势
36
23
 
37
- ```sh
38
- npm install -g @xingjisoft/neutrino
39
- ```
40
-
41
- 或从源码运行:
42
-
43
- ```sh
44
- git clone <repo-url> neutrino
45
- cd neutrino
46
- npm install
47
- npm run build
48
- npm link
49
- ```
50
-
51
- ## 使用
52
-
53
- ```sh
54
- neutrino
55
- ```
56
-
57
- 在开始界面输入消息并按 `Enter` 开始新对话。浏览器界面会同时挂载在 <http://127.0.0.1:8765>;关于 `--port`、`--lan` 和 `--no-web`,请参阅下方的 [Web 界面](#web-界面)。
58
-
59
- ### 快捷键
60
-
61
- | 按键 | 操作 |
62
- | --- | --- |
63
- | `Enter` | 发送消息 / 确认(只有手动键入的 Enter 才会发送) |
64
- | `←`/`→`、`↑`/`↓` | 移动焦点 / 导航 |
65
- | `Ctrl+E` | 打开模型配置 |
66
- | `Ctrl+B` | 返回菜单(在聊天中) |
67
- | `Ctrl+L` | 清空对话(在聊天中) |
68
- | `Esc Esc` | 暂停 / 恢复流式输出(暂停的回合保持展开:只有已完成的回复才会折叠) |
69
- | `Esc` | 清除当前选区 |
70
- | `PgUp` / `PgDn` | 滚动聊天历史 |
71
- | `Ctrl+C` | 有文本被选中时不做任何事;否则暂停正在运行的对话,或在顶部显示退出提示 |
72
- | `Ctrl+C` × 3 | 连续按三次退出,每次间隔不超过 1.5 秒(按其他键、粘贴、点击或滚动会重置计数) |
73
- | `Ctrl+Shift+C` | 立即退出(需要终端转发该快捷键) |
74
- | `Ctrl+A` | 在对话中切换请求审批 / 自动审批 |
75
- | `Ctrl+P` | 切换右上角的计划 / token 信息卡片 |
76
- | `Y` | 批准待处理请求(审批卡片出现时) |
77
- | `N` / `Esc` | 拒绝待处理请求 |
78
- | `Ctrl+Q` | 退出 |
79
-
80
- ### 鼠标
81
-
82
- | 操作 | 效果 |
83
- | --- | --- |
84
- | 悬停条目 | 在列表和菜单中高亮 / 选中该项 |
85
- | 点击条目 | 选择并确认(打开页面、加载会话、应用设置……) |
86
- | 点击输入框 | 聚焦输入框并将光标放到点击位置 |
87
- | 点击折叠的回合 | 展开完整的一轮,或再次折叠 |
88
- | 点击 `⚙ tool` | 展开或折叠工具调用的输出 |
89
- | 点击并拖动 | 选中矩形范围内的文本;松开后复制到剪贴板 |
90
- | 拖到日志首行/末行 | 逐行滚动日志(按住越久越快),并让选区继续延伸到屏幕之外 |
91
- | 点击标题栏(`ⓘ` 左侧) | 打开模型选择器 |
92
- | 点击 `ⓘ`(聊天标题栏) | 切换计划 / token 信息卡片 |
93
- | 拖动滚动条 | 抓住聊天/日志右边缘的滑块并拖动以滚动 |
94
- | 滚轮 | 滚动聊天/日志,或在列表中移动 |
95
- | 右键 | 返回(等同于 `Esc`) |
96
-
97
- 选择高亮绘制在屏幕内容之上,因此在聊天日志、输入框和每个菜单上表现一致。复制选中文本时,会按行去掉行尾空白(shell 风格),因此可以直接粘贴回终端。复制和高亮时会跳过 UI 装饰(例如滚动条),所以复制内容永远不会带上滚动条或它前面的填充。
98
-
99
- 拖动滚动时,选区起点会固定在其开始时所在的那一行内容上,而不是当时所在的屏幕行:标记会留在正在滚动的一侧边缘,高亮停在日志边缘而不会溢出到标题栏或输入框,复制内容也包含已经滚出可视区域的行的内容。往回滚动时选区会缩小;按 `Esc` 取消选区。
100
-
101
- ## 先规划后执行
102
-
103
- 一个请求分两遍回答。**规划代理**首先把消息转换为一个有序的独立步骤列表;**工作代理**随后自上而下、每遍只执行一个步骤。每个步骤都对应一项交付物,因此长请求不再是一条含糊的指令,而是一系列具体、可验证的任务。计划会作为一条消息写入对话并随会话保存,因此重新打开对话时步骤仍然存在。
104
-
105
- 这些步骤也会列在聊天右上角的 `ⓘ` 卡片中——点击它,或按 `Ctrl+P`:
106
-
107
- - 节点按执行顺序自上而下排列,并由连接线相连,
108
- - 正在处理的步骤是当前高亮行,
109
- - 尚未进行的步骤会被点亮,
110
- - 已完成的步骤以深色加对勾绘制;失败的步骤标为红色。
111
-
112
- 计划一写入,卡片就会自动打开;关闭它也不会丢失任何内容:按 `Ctrl+P` 即可重新打开。在较矮的终端中,卡片会显示当前步骤附近的窗口,并统计其上方和下方的步骤数。
113
-
114
- 某个步骤失败会终止计划:其余步骤保持待处理,下一条消息可以重新规划它们。用 `Esc Esc` 暂停的回合会停止计划并标记为 **stopped**,因此不会有步骤继续显示为运行中。当没有返回可用的计划时——单动作请求、服务商错误、返回内容不是计划——该回合会像以前一样一次性执行,因此规划绝不会成为新的失败来源。
115
-
116
- **简单请求完全不会进入规划。** 规划会在任何工作开始前消耗一次模型往返,而对于“把这个变量改名”这类请求,这次往返纯属摩擦。只有当请求显示出需要拆分的理由时才会进行规划:
117
-
118
- | 信号 | 示例 |
119
- | --- | --- |
120
- | 不止一件事要做 | `先改配置,再更新文档`, `运行测试,并且把报错修掉` |
121
- | 本身的结构 | 编号列表或项目符号列表 |
122
- | 读起来像任务简报 | 超过约 180 列 |
123
- | 要求给出计划 | `列个计划`, `plan`, `steps` |
124
-
125
- 其他所有请求都一次性执行:不调用规划代理,不显示计划卡片,代理仍会在自己的回答中说明它正在采取的步骤。该判断位于 `src/llm/plan.ts` 中的 `needsPlan()`。
126
-
127
- 提示词是文件而不是代码:[`assets/planner.txt`](./assets/planner.txt) 规定步骤如何编写,[`assets/step.txt`](./assets/step.txt) 是每个步骤的指令,[`assets/prompt.txt`](./assets/prompt.txt) 是工作代理的基础系统提示词。单次运行可通过 `NEUTRINO_PLAN=off` 关闭规划,或在 `config.json` 中设置 `"plan_first": false` 永久关闭。
128
-
129
- ## 子代理
130
-
131
- 代理可以把一个自包含的子任务交给**子代理**:这是一个拥有自己的对话记录、自己的工具调用和相同工作目录的第二个代理循环。只返回最终报告——它读取的文件和运行的命令永远不会进入主对话。这正是关键所在:调研大型代码库或试探命令行为会消耗大量消息,而父代理需要的是结论,不是过程记录。子代理的 token 仍计入对话用量。
132
-
133
- - 父代理调用 `delegate` 工具,传入完整任务以及(可选的)少量上下文。子代理看不到对话,也不能向用户提问,因此引用“上面的文件”的任务会失败;系统提示词会要求模型编写一份自包含的简报。
134
- - 它的工具调用经过**相同的审批关卡**:风险调用会停住回合并显示通常的卡片(标题栏变橙,浏览器显示横幅)。拒绝会告诉子代理不要原样重试,父代理会在报告中收到拒绝信息。
135
- - 委派只有一层——`delegate` 不在子代理的工具集中——因此工作始终只有一个负责人和一个暂停按钮。
136
- - 运行期间,终端状态行和浏览器徽标会显示当前阶段(`Sub-agent — read_file`),`delegate` 工具块会显示交给它的任务。报告到达后会替换该标记,作为工具调用的结果。
137
- - 轮次上限(12)用于约束永远无法完成的子代理;父代理随后会收到部分报告,并清楚标记为部分完成。暂停的回合会中止子代理,并在工具结果中说明。
138
-
139
- 子代理的提示词同样是一个文件:[`assets/subagent.txt`](./assets/subagent.txt)。
140
-
141
- ## 配置
142
-
143
- 设置保存在 `~/.neutrino/` 下:
144
-
145
- - `model.json` — 服务商和模型定义(可编辑以添加自己的端点)。模型可以列出它接受的思考强度:`"intensity": ["low", "high", "max"]`。名称匹配不区分大小写,终端、Web 界面和 API 只会提供该列表中的级别,因此不受支持的级别永远不会发送到端点。省略该字段(或留空)则继续允许全部五档(`Low`、`Medium`、`High`、`Max`、`Ultra`)。DeepSeek 风格的思考端点会自动在 assistant 消息中回传 `reasoning_content`;名称不同的端点可以通过 `"reasoning": true` 选择启用(或用 `false` 选择禁用)。
146
- - `config.json` — 应用配置(选中的服务商/模型/思考强度、主题、语言、审批模式、`auto_approve_extreme_risk`、`sound_alerts`、`web_console`、`web_workspaces`)。`language`(`en` 或 `zh`)是浏览器控制台的界面语言;终端界面本身保持英文。
147
- - `session/` — 保存的对话会话,每个会话一个 JSON 文件。
148
- - `pro/config.json` 和 `pro/session/` — Pro 工作区自己的设置和对话。Pro 不读写常规工作区的默认值、语言、规划选择、审查模型、压缩阈值或最近文件夹。机器级设置——`web_password_hash`、`approval_mode`、`auto_approve_extreme_risk` 和 `web_lan`——会从 `config.json` 分层读取,因此 Web 密码、审批策略和局域网绑定由两个工作区共享。
149
-
150
- 在模型选择页面或直接在 `model.json` 中设置 API 密钥。
151
-
152
- 一些环境变量可以覆盖启动参数:
153
-
154
- | 变量 | 作用 |
155
- | --- | --- |
156
- | `NEUTRINO_WEB_PORT` | Web 界面端口:等同于任一模式下的 `--port`,并且即使 `web_console` 关闭也会挂载界面。 |
157
- | `NEUTRINO_WEB_HOST` | 要绑定的接口,等同于 `--host`;当 `web_console` 关闭时也会挂载界面。 |
158
- | `NEUTRINO_NETWORK_ATTEMPTS` | 对反复断线的模型请求的总尝试次数(默认 7)。 |
159
- | `NEUTRINO_RETRY_DELAY_MS` | 首次重试前的等待时间(毫秒);每次重试翻倍(默认 5000)。 |
160
- | `NEUTRINO_PLAN` | `off` 会一次性回答请求而不进行规划(覆盖 `plan_first`);`on` 强制规划。 |
161
-
162
- ### 审批
163
-
164
- 每次工具调用在执行前都会被分类。
165
-
166
- | 类别 | 示例 | 默认行为 |
167
- | --- | --- | --- |
168
- | 常规 | `npm run build`、`git status`、在项目内读取或写入 | 直接运行,不询问 |
169
- | 高风险 | `sudo`、`chmod`、安装软件包、`git push`、`curl`、内联 `python -c`、工作目录之外的任意路径 | 询问用户 |
170
- | 极高风险 | 删除类:`rm`、`del`、`shred`、`mkfs`、`git clean -f`、`git reset --hard`、`find -delete`、`DROP TABLE` 等 | 始终询问用户 |
171
-
172
- 当调用等待回答时,顶部栏会变成橙色并显示 **请求审批(Request Approval)**,输入框上方会浮出一张橙色卡片,展示命令、**代理说明为何需要它**、它被标记的原因,以及 `[ Y ] Approve` / `[ N ] Deny`(批准 / 拒绝)。批准即运行该调用;拒绝会给模型返回一条说明,让它选择其他路径而不是重试。
173
-
174
- 每次工具调用都带有一个 `reason` 参数;需要审批但未说明理由的调用不会运行:审批关卡会在打扰你或付费请审查者之前拒绝它,并告知模型带上理由重新发送同一调用。理由是代理自己的说法,会与参数分开显示。含糊、不完美或看似无关的理由本身并不构成安全风险;审查者判断的是实际调用的后果以及用户明确提出的限制。
175
-
176
- 消息中的两个短语会改变该对话的策略:
177
-
178
- | 提示词中的短语 | 作用 |
179
- | --- | --- |
180
- | `无需审批` | 高风险调用直接运行,不再询问。删除类调用仍会询问。 |
181
- | `我已知悉风险,同意给予完整权限,无需任何审批` | 任何调用都不再询问,包括删除类调用。 |
182
-
183
- 主菜单版本标签上方的一行**请求审批 / 自动审批**按钮用于设置并记住新对话的默认值。每个终端对话在输入框上方都有自己的按钮(或使用 **Ctrl+A**),每个浏览器对话在其控制区中都有一个开关。对话的选择会随其历史记录保存,并在终端和浏览器之间同步。在提示词中写入 `自动审批` 或 `自动批准` 不再会启用自动审查。
184
-
185
- 启用**自动审批**后,每个待处理的工具调用会接受**两次独立的安全审查**。当一轮审查的平均安全评分至少为 60/100,且没有具体的安全隐患或审查失败时,该轮才通过。任务相关性权重为零,也没有普通的最低分要求:即使评分为 0,其本身也不会阻止安全调用运行;对目的的疑虑不得降低安全性。
186
-
187
- 只有极端、明显的任务不匹配才能以相关性为由否决:两位审查者必须各自独立标记,给出至多 5/100 的相关性评分,并提供比较任务与拟议操作的具体证据。单一怀疑、薄弱理由或对任务的不确定都不足以构成否决。
188
-
189
- 该策略倾向于普通的实现与验证步骤,包括限定范围的编辑、依赖安装、公开网络检查和临时测试文件。具体而严重的危险以及用户明确的禁止会阻止批准;审查失败、超时或格式错误也会阻止自动执行。每项评估和安全结果都会纳入决策;对于删除类调用,则按轮次纳入。
190
-
191
- 每个回合都会把用户的字面请求和更早的用户消息单独捕获,与摘要及工具输出分开,因此简短的后续消息仍保留任务上下文。排队的消息在下一回合生效。被拒绝的工具结果会包含本次审查的用户请求和近期用户请求,以便诊断。
192
-
193
- 删除类命令的自动审批由**设置 → 极高风险自动审批**中的开关控制,该开关**默认关闭**:关闭期间,即使在**自动审批**模式下,删除类调用也始终交由人工处理。
194
-
195
- 开启开关后,删除类调用会被**独立审查三次**。每轮都是通常的一对独立审查者;各轮依次运行,且不会告知某一轮之前各轮的结论。每轮按自身评分判定,只有**三轮全部**批准时调用才会运行,因此第一个拒绝或失败的轮次就会结束整个流程:删除操作永远没有第二次通过的机会。开关是分层生效的,因此关闭**自动审批**后,每个删除类请求仍会发送给你。审查模型默认使用聊天模型,可在**设置 → 审批审查模型**中更改。
196
-
197
- ### 声音提醒
198
-
199
- 回合结束和出现审批请求时,终端和 Web 界面都会播放一段短提示音,这样即使你身处另一个窗口,也能注意到耗时较长的任务或阻塞中的审批。审批请求响两声,回合结束响一声。可在**设置 → 声音提醒**(TUI 或 Web)中切换,或在 `config.json` 中设置 `"sound_alerts": false`。
200
-
201
- 浏览器在用户手势之前会拒绝启动音频,因此在你点击页面或按过至少一次键之前,Web 界面会保持静音。
202
-
203
- ### 浏览其他代理的历史记录
204
-
205
- 在历史记录页面选择 **Open other Agent's history(打开其他 Agent 的历史记录)**,可查看本机安装的其他 CLI 代理记录的过往会话:
206
-
207
- - **Claude Code** — 读取 `~/.claude/projects/**/*.jsonl`。
208
- - **Codex** — 读取 `~/.codex/sessions` 和 `~/.codex/archived_sessions`。
209
- - **OpenCode** — 通过 `node:sqlite` 读取本地 SQLite 数据库(`opencode.db`)(需要 Node.js **22.13+**;在旧版本上会静默不可用)。
210
-
211
- 只列出在当前工作目录中开始的会话,因此历史记录始终聚焦于你所在的项目。这些会话是只读的;可以打开查看,但不能编辑或删除。
212
-
213
- ## Web 界面
214
-
215
- 每当终端界面启动时,Neutrino 都会在 <http://127.0.0.1:8765> 挂载一个小型网站。它按工作目录对所有已保存对话进行分组(每个路径显示最新的五个,其余折叠),将每个已完成回合的工作折叠为一行摘要,点击即可展开,而回复始终留在屏幕上;实时流式呈现正在运行的对话,并允许你从浏览器暂停或恢复它、发送新消息。对话文本以 Markdown 渲染——包括标题、列表(含任务列表和嵌套)、表格、引用块、围栏代码、强调、链接和图片——同时原始 HTML 保持惰性,只有安全的链接/图片协议才会变为可点击。
216
-
217
- 侧边栏中的**新建对话**会开始一段尚无对话记录的聊天。其对话框用于选择工作目录,以及首回合的服务商、模型和思考强度。**浏览…**会打开服务器的文件夹选择器,你可以在其中浏览文件系统、创建文件夹,并将其用作对话的工作目录。打开过的文件夹会记录在**工作目录**下(列表上方的文件夹按钮),从而将侧边栏限定到该项目,并允许你在那里开始新聊天;你随时可以返回**所有目录**。
218
-
219
- 已保存的对话记录以只读方式打开,因为此时没有正在运行的进程。点击**在此继续**(或直接在输入框中输入),服务器便会在进程内恢复它——模型、工作目录和 token 计数器保持不变——因此即使没有连接终端,你也可以继续从浏览器聊天。回合会写回同一个会话文件。**清空**会在同一文件夹中开始一段新对话(之前的对话记录仍会保存),**删除**会移除已保存的对话记录,**其他 Agent** 会列出 Claude Code、Codex 或 OpenCode 在该文件夹中记录的只读会话。
24
+ - 可以方便自由地随意调整思考强度。
25
+ - 提供更好的 Web 控制台,为手机端特别优化,不管什么手机什么设备,无需下载随时随地只要有网就能远控!
26
+ - 进行特殊优化,**更省钱!**(OpenCode 用啥都贵)
27
+ - 更简单严谨的自动审批系统,解放双手的同时保证你的数据安全,全自动审批一切命令(可以在设置中调整,要求高危命令人工审批),无需人工干涉。
28
+ - 更好的自动重试系统。
29
+ - 我们是小团队,你的建议我们全部都能收到!
30
+ - Pro 模式真正做到一句话一次性做到最好!目前主流 Agent **全部** 没有这个功能!(因为主流 Agent 基本都是 AI 厂商的,总不能自家 Agent 用自己竞争对手的东西吧)
31
+ - 配置文件简洁易懂,想咋改就咋改。
220
32
 
221
- 使用对话上方的选择器可更改其服务商、模型或思考强度,然后点击**应用到对话**(请先暂停活动回合)。侧边栏中的**设置**(齿轮)按钮用于编辑新对话的默认值、界面语言、先规划后执行、默认审批模式、上下文压缩和审批设置。它还包含**Web 访问**开关,可将控制台绑定到 `0.0.0.0`(应用内等同于 `--lan`),立即生效并被记住。对话中的选择会随对话记录保存,不会覆盖默认值。已配置的模型和密钥与终端共享;密钥只保留在服务器上。
33
+ ## 安装方式
222
34
 
223
- 界面语言可在侧边栏底部或**设置 → 界面**中切换:支持 English 和简体中文。更改会立即生效,无需刷新,并会保存在 `config.json` 中,重启后仍然有效,且所有浏览器共享。终端界面本身不会被翻译。
35
+ npm 一键安装:
224
36
 
225
- 对话标题栏中的 `ⓘ` 按钮会打开与终端中 `Ctrl+P` 相同的计划/token 卡片:已规划回合的各步骤及其实时状态,以及没有计划运行时的提示/补全/总计 token 数。
226
-
227
- 上下文自动压缩会在当前模型上下文窗口的 80% 处开始。模型默认为 1,000,000 token(在 800,000 时压缩)。可在终端设置中调整模型上下文窗口,或在 Web 设置中选择服务商/模型并输入其容量(以 token 计)。该值按服务商/模型保存(Pro 工作区单独保存),并应用于活动对话的未来回合。旧的固定 `context_compress_threshold` 设置不再使用。
228
-
229
- ### Pro 工作区
230
-
231
- Pro 使用八个角色:主执行、推理、前端补全、后端补全、独立审批、图像生成(预留)、规划和建模。每个角色在 `assets/pro/` 下都有一个提示词;前端提示词参考了提供的组件设计指南。
232
-
233
- 首次使用时,`~/.neutrino/pro/model.json` 会从随附示例(`assets/pro/model-template.json`,源自 `tmp-proproject/pro-model-example.json`)原样复制。服务商端点和凭据仍来自普通的 `~/.neutrino/model.json`;Pro 文件只引用服务商/模型对,并记录其思考强度和上下文窗口。
234
-
235
- 使用输入框右下角的卡片可在 Pro 文件中的预设之间滑动切换。每个任务开始前,Pro 会为每个活动角色发起一次小型的带认证补全请求。如果某个角色的请求失败,只有该角色会按文件顺序依次尝试更低的预设。Custom 是单独的用户自定义团队,而不是降级层级。弹窗会列出不可用的模型及其临时替代项;预设文件保持不变。如果没有任何层级能提供某个角色,执行会停止并列出缺失的角色。图像生成不进行检查。
236
-
237
- 规划代理首先接收每个请求,包括单步任务;随后主代理接收请求和计划。所有活动角色都可以通过 `call_pro_agent` 咨询其他角色,并共享相同的审批策略。自动审查使用选定的审批角色,并保留通常的判定格式和审查轮次。初始规划和审查者咨询都是只读的,包括嵌套调用。设置控制默认预设和最大递归深度(`pro_max_recursion_depth`,默认 10,范围 1–100)。暂停会取消活动的模型请求,包括可用性检查。上下文压缩在 80% 阈值处使用每个角色配置的 `context_window`。
238
-
239
- Web 控制台有两个界面相同的工作区。常规工作区位于 `/`;**Pro** 位于 `/pro`。在常规控制台中,点击 **Neutrino** 标题左侧的轨道徽标:一层 `#151515` 幕布会在页面上淡入,并在中央播放动态 `web/neutrino-pro.gif` 字标,三秒后淡出并进入 `/pro`。指向徽标的蓝色提示会说明这一切换;点击其中的 X 可在该浏览器中永久关闭提示。Pro 模式使用静态 `web/neutrino.png` 字标作为左上角 Logo,将侧边栏品牌重命名为 **Neutrino Pro**,页面标题设为 **Neutrino Pro**,并在工作区右上角添加 **Exit Pro mode / 返回普通模式** 按钮。其输入框还会添加一张专业团队预设卡片。该按钮返回位于 `/` 的常规工作区。
240
-
241
- 终端在开始界面的状态栏下方提供了一个 **启用Pro模式** 框,作为相同的入口。点击它会在 Web 控制台未运行时启动控制台,并在默认浏览器中打开 `/pro`。
242
-
243
- 两个工作区绝不会混用对话或活动会话:Pro 将会话记录保存在 `~/.neutrino/pro/session/` 下,将其设置保存在 `~/.neutrino/pro/config.json` 下。Pro 有自己的界面语言、声音提醒、默认预设、最大递归深度和最近工作目录。角色模型、思考强度和上下文容量保存在 `~/.neutrino/pro/model.json` 中。只有描述整台机器的设置会被共享:Web 密码、审批策略(默认审批模式和极高风险自动审批)以及控制台是否同时监听局域网。它们守护整台机器,因此两个工作区、终端和每个浏览器读写的都是相同的值。登录也因此共享:在一个页面登录后,另一个页面也会登录;在任一设置对话框中更改密码,两边都会生效。Pro 模式支持 English 和简体中文,并按工作区分别记住。
244
-
245
- ```sh
246
- # 终端界面现在会同时启动 Web 界面
247
- neutrino
248
-
249
- # 选择其他端口或接口
250
- neutrino --port 9000
251
- neutrino --host 100.64.0.7
252
-
253
- # 或者仅提供已保存历史记录,不启动终端
254
- neutrino web --port 8765
255
- ```
256
-
257
- 然后打开 <http://127.0.0.1:8765>。网站本身位于 [`web/`](./web),无需构建步骤;API 参见 [`web/README.md`](./web/README.md)。
258
-
259
- 控制台默认开启;单次运行可用 `--no-web` 关闭,或在终端的**设置 → Web 控制台**中永久关闭。`--web-port` 标志仍作为 `--port` 的别名被接受。如果默认端口已被占用(通常是另一个 Neutrino 实例),终端界面仍会启动,并打印如何选择其他端口。
260
-
261
- ### 从其他设备访问
262
-
263
- 服务器绑定到回环地址,因此只有本机可以打开它。要从手机、平板或局域网/VPN 中的另一台机器访问,请绑定一个其他设备可见的接口:
264
-
265
- ```sh
266
- neutrino --lan # 在所有接口上启用 TUI + Web 界面(0.0.0.0)
267
- neutrino --host 100.64.0.7 # 在指定地址上启用 TUI + Web 界面
268
- neutrino web --port 8765 --lan # 在所有接口上启用无头 Web 界面
269
- ```
270
-
271
- `--lan` 是 `--host 0.0.0.0` 的简写;两者不能同时使用。
272
-
273
- 同一个开关无需重启即可使用:**设置 → Web 访问 → 允许其他设备访问(局域网,0.0.0.0)**会将正在运行的控制台重新绑定到同一端口的 `0.0.0.0`,并为后续启动保存该选择。关闭后会将控制台恢复为 `127.0.0.1`。显式的 `--host`/`--lan` 在启动时选择接口;此后,页面内开关会接管正在运行的进程。
274
-
275
- 启动时会打印所有可用地址,你可以复制与当前设备匹配的那个:
276
-
277
- ```
278
- Neutrino web UI listening on 0.0.0.0:8765
279
- Open the address that matches your device:
280
- http://100.105.130.25:8765/
281
- http://192.168.1.211:8765/
282
- http://127.0.0.1:8765/
283
- ```
284
-
285
- 打开该地址,并使用你设置的永久 Web 密码登录。初始密码是 **`114514`**。可在浏览器的**设置 → Web 访问**或终端的**设置 → Web 密码**中更改。密码会以加盐哈希的形式保存在 `~/.neutrino/config.json` 中,重启后仍然有效。网站运行时,其地址也会显示在终端标题栏和终端的**设置 → Web 控制台**行中,你可以在那里无需重启地启动或停止它。
286
-
287
- 要自动登录,请打开 `http://<host>:8765/?pwd=114514`。页面会校验密码、记住登录状态,并从地址栏中移除 `pwd`。如果密码不正确,密码登录对话框会让你重试。请对密码中的特殊字符进行 URL 编码。
288
-
289
- 浏览器会将你的登录状态保存在有效期最长一年的 HttpOnly Cookie 中,包括跨服务器重启。更改密码会使其他浏览器退出登录;保存新密码的浏览器会保持登录。地址和启动输出绝不会包含你的密码或会话凭据。
290
-
291
- - **`--no-auth`** 显式禁用密码登录。
292
- - 静态文件和登录端点仍然公开;所有对话和设置 API 都需要登录,包括通过 localhost 访问时。
293
- - **防火墙:** 首次绑定到 `0.0.0.0` 时,Windows 和 macOS 会弹出允许提示。请为专用网络允许它,并检查你的 WLAN 是否被标记为“公用”,在公用网络中传入连接本来就会被阻止。
294
- - **`127.0.0.1` 仍是默认接口。** 控制台会随终端界面启动,但在你传入 `--host`/`--lan` 或开启**设置 → Web 访问 → 允许其他设备访问**之前,它并不是网络服务。
295
-
296
- ## 内置工具
297
-
298
- 代理可以在对话过程中调用以下工具:
299
-
300
- - `run_command` — 执行 shell 命令(120 秒超时,输出有上限)。
301
- - `read_file` — 读取文本文件(上限 200KB)。
302
- - `write_file` — 将内容写入文件。
303
- - `list_files` — 列出目录内容。
304
- - `delegate` — 将一个自包含的子任务交给子代理并返回其报告。
305
-
306
- 每个工具都接受一个额外的 `reason` 参数:用一句话说明该调用的目的以及为什么现在需要它。审批卡片会显示它,审查者也会对照它检查参数,因此即使是通常无需询问的调用也必须提供。
307
-
308
- ## 开发
309
-
310
- ```sh
311
- npm run build # 将 TypeScript 编译到 dist/
312
- npm test # 先构建,再运行下面的所有测试
313
- ```
314
-
315
- 单项测试:
316
-
317
- ```sh
318
- node scripts/test-paste.mjs # 验证粘贴的换行符会被压平为空格
319
- node scripts/test-mouse.mjs # 验证鼠标转义序列解析
320
- node scripts/test-cursor.mjs # 验证点击定位光标的计算
321
- node scripts/test-selection.mjs # 验证选区高亮和文本提取
322
- node scripts/test-drag-scroll.mjs # 验证拖动选择自动滚动:边缘滚动、跨屏复制
323
- node scripts/test-copy.mjs # 验证复制内容永远不会包含滚动条或尾部填充
324
- node scripts/test-scrollbar.mjs # 验证对话滚动条的大小/位置和拖动计算
325
- node scripts/test-markdown.mjs # 验证 markdown/表格渲染(包括参差不齐的行)
326
- node scripts/test-wrap.mjs # 验证换行时保留缩进和间距
327
- node scripts/test-render.mjs # 对 diff 渲染器进行模糊测试并渲染聊天界面,
328
- # 断言没有留下过时/残留的单元格
329
- node scripts/test-sanitize.mjs # 验证 ANSI/控制码永远不会到达屏幕
330
- node scripts/test-drift.mjs # 验证输入区域在终端漂移后能自愈
331
- node scripts/test-fetch.mjs # 验证流式请求会释放中止、断线时重试,以及 7 次尝试的退避
332
- node scripts/test-compact.mjs # 验证上下文压缩规划
333
- node scripts/test-history-fold.mjs # 验证完整历史保留、折叠和恢复
334
- node scripts/test-web-history.mjs # 验证浏览器历史记录披露和流式状态
335
- node scripts/test-web-default.mjs # 验证 Web 控制台默认启动和切换
336
- node scripts/test-turn-fold.mjs # 验证折叠回合保留回复和每条消息
337
- node scripts/test-plan.mjs # 验证先规划后执行、步骤顺序、步骤卡片和回退
338
- node scripts/test-subagent.mjs # 验证委派:隔离、审批、中止、轮次限制
339
- node scripts/test-tools.mjs # 验证工具输出折叠
340
- node scripts/test-web.mjs # 验证 Web 服务器、API、SSE 流和路径遍历防护
341
- node scripts/test-web-settings.mjs # 验证模型切换、持久化、浏览器控件和默认值
342
- node scripts/test-web-settings-http.mjs # 验证通过 HTTP 进行认证的设置和模型更改
343
- node scripts/test-web-markdown.mjs # 验证浏览器 markdown 渲染器及其 XSS 边界
344
- node scripts/test-web-i18n.mjs # 验证每个控制台标签都有中文翻译且占位符得以保留
345
- node scripts/test-web-workspaces.mjs # 验证文件夹浏览/创建、新对话、清空/删除和其他代理历史记录
346
- node scripts/test-web-pro.mjs # 验证 /pro 路由、隔离的历史记录/活动会话/设置、共享密码和审批策略
37
+ ``` bash
38
+ npm install -g @xingjisoft/neutrino
347
39
  ```
348
40
 
349
- ### 渲染模型
350
-
351
- 屏幕被绘制到一个由单元格组成的 `Buffer` 中,并与上一帧进行差异比较,因此只会输出发生变化的行。重绘前会将行清除到行尾,缓冲区还会保持双宽字形的一致性(覆盖宽字符的一半会清空另一半)。这些机制共同保证:重绘的行绝不会留下过时的单元格。
352
-
353
- 由于只输出*发生变化*的行,如果某一行的内容在相邻两帧中恰好完全相同,它就永远不会被重写——因此,如果终端在我们背后移动了自身内容(ConPTY 在调整大小期间重排缓冲区、大小竞争导致的重绘、意外的滚动),该行就会保持过时状态。为此有两道防线:持久界面元素(输入框、标签页、按键提示)通过 `Screen.alwaysRepaintRows()` 在每一帧重绘;应用还会定期重新输出整个缓存帧(`Renderer.repaint()`)作为安全网。当没有任何漂移时,两者都是空操作。
354
-
355
- ### 文本卫生
356
-
357
- 单元格的字形会被直接复制到输出流中,因此保存*控制*字符的单元格会变成终端执行的命令——ESC 序列可以移动光标、清屏或退出备用缓冲区,TAB 会移动该行其余内容。工具输出(来自 `ls --color`、`git`、`npm` 的 ANSI 颜色,进度条……)、文件内容和粘贴文本都可能携带这类字节,因此会在三个位置进行中和:
358
-
359
- 1. `src/sanitize.ts` — `sanitizeText()` 会移除完整转义序列(而不仅是 ESC 字节,否则会留下 `[31m` 作为字面文本),展开制表符以使测量宽度与终端绘制一致,并丢弃任何剩余控制码。它应用于不受信任文本进入显示的位置(聊天消息、工具结果、粘贴输入)。
360
- 2. `Buffer` — 写入控制字符时会改为存储一个空格,保留调用方测量的列宽,因此任何单元格都不可能保存控制字符。
361
- 3. `Renderer` — 输出行时的最后一道检查,因此即使单元格通过其他方式被填充,字节流也不可能携带控制码。
362
-
363
- ### 网络韧性
41
+ ## 使用小技巧
364
42
 
365
- 模型响应通过流式连接到达,连接可能在任何时刻断开。一次短暂抖动绝不能结束会话——一个回合可能是数分钟的工作,而代理重置空闲连接并不能说明用户任务有任何问题——因此失败请求会在放弃之前重试:
43
+ - Web 控制台配合 Tailscale 等软件,可以直接实现外网安全远控!而且无需下载,打开即用!
44
+ - 支持后台常驻(默认关闭):在 Web 控制台或终端设置中切换后立即生效,开启时关闭终端也不会中断已开始的浏览器任务。也可以用 `neutrino background start|stop|status|open` 管理后台控制台。
366
45
 
367
- | 尝试 | 之前的等待 |
368
- | --- | --- |
369
- | 1 | — |
370
- | 2 | 5 秒 |
371
- | 3 | 10 秒 |
372
- | 4 | 20 秒 |
373
- | 5 | 40 秒 |
374
- | 6 | 80 秒 |
375
- | 7 | 2 分 40 秒 |
46
+ ---
376
47
 
377
- 也就是七次尝试,总计约五分钟,每次等待翻倍:只是偶尔抖动的链路会在预算耗尽前很久就恢复,而真正断开的链路也不会被反复冲击。`NEUTRINO_NETWORK_ATTEMPTS` 和 `NEUTRINO_RETRY_DELAY_MS` 可调整这两个数值。该策略位于 `src/llm/retry.ts`。
378
-
379
- 哪些情况值得重试:
380
-
381
- - **连接失败**——重置(`ECONNRESET`)、连接被拒绝或不可达、`EPIPE`、DNS(`EAI_AGAIN`)、连接超时,以及 undici 的 `UND_ERR_*` 错误码。
382
- - **在收到第一个字节前就中断的流**——服务器或代理在传输中途关闭连接(`TypeError: terminated`、`other side closed`、`socket hang up` 等)。用户还没有收到任何内容,因此直接重新发起请求。
383
- - **暂时性的 HTTP 状态**——408/425、429 和 5xx(负载均衡器返回的 502/503/504,529“过载”)。错误请求则不同:400/401/403/404/422 会在第一次回答时直接暴露,因为重发只会延迟真正的错误信息。
384
- - **已经开始输出后中断的流** *不会*重试:重放会导致回复重复,因此会展示错误,并从 `error.cause` 链中展开真实原因(单纯显示 `fetch failed` 没有任何有用信息),同时保留已输出的部分文本。
385
-
386
- 重试可能让流静默长达 160 秒,因此它绝不会对静默本身保持沉默:终端状态行和 Web 徽标会说明尝试次数和延迟(例如 `Network hiccup — retrying in 40s (attempt 4/7)`)。等待过程也会被轮询,因此暂停回合、发送更新的消息或退出会立即放弃等待,而不会一直等到退避结束。
387
-
388
- 任何被放弃的流(暂停,或被更新的回合接管)都会取消其响应体,因此套接字不会泄漏。
389
-
390
- ### 崩溃安全
391
-
392
- TUI 会接管终端:原始模式、备用屏幕和鼠标报告。如果进程在未恢复这些设置的情况下退出,shell 会继承一个在每次光标移动时都会输入 `[<b>;<x>;<y>M` 鼠标报告的终端。
393
-
394
- 因此清理序列通过三种方式注册——正常退出、向 fd 1 同步写入的 `process.on("exit")` 钩子,以及 `uncaughtException` / `unhandledRejection` 处理器。正常退出使用相同的同步清理,因此待处理的帧输出不会吞掉清理逻辑。可捕获的退出还会刷新最新的对话快照,包括部分回复。未捕获的错误会追加到 `~/.neutrino/crash.log` 并以状态码 1 退出。SIGHUP 和 Windows Ctrl+Break 也会在退出前恢复终端并建立检查点。
395
-
396
- 会话快照会写入会话目录中的临时文件,同步后再原子重命名覆盖之前的快照。写入失败会保留之前的历史记录,在界面和崩溃日志中报告错误,并在下一个检查点重试。保存的快照中,部分回复会被完整回复替换,因此重新打开历史记录时不会出现重复。
397
-
398
- 按键处理器和渲染循环也被包裹起来:它们运行在 `stdin` 回调和定时器中,在这些地方抛出异常会成为未捕获异常,过去会结束会话。现在失败的帧会重试,只有完全无法绘制的屏幕才会停止应用。
399
-
400
- 无法捕获的终止(SIGKILL、TerminateProcess 或运行时/操作系统故障)无法执行清理。已写入的快照会保留;如果磁盘可写,自上一个一秒检查点以来的流式输出可能会丢失。如果 shell 卡在鼠标模式,请在 Bash 中运行:
401
-
402
- ```sh
403
- printf '\033[?2004l\033[?1000l\033[?1002l\033[?1003l\033[?1006l\033[?1015l\033[?25h\033[0m\033[?1049l'
404
- stty sane
405
- ```
406
-
407
- 在 PowerShell 中:
408
-
409
- ```powershell
410
- $esc = [char]27
411
- [Console]::Write("${esc}[?2004l${esc}[?1000l${esc}[?1002l${esc}[?1003l${esc}[?1006l${esc}[?1015l${esc}[?25h${esc}[0m${esc}[?1049l")
412
- ```
48
+ 欢迎各位大佬提供建议!
413
49
 
414
- ## 许可证
50
+ ---
415
51
 
416
- Apache-2.0。参见 [LICENSE](./LICENSE)。
52
+ Copymiddle(O) XINGJI Interactive Software 2017-2026 Some rights reserved.
Binary file
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Neutrino">
2
+ <rect width="32" height="32" rx="7" fill="#151517"/>
3
+ <g fill="none" stroke="#f9fafb" stroke-width="1.9" stroke-linecap="round">
4
+ <ellipse cx="16" cy="16" rx="12.5" ry="5.1" transform="rotate(-30 16 16)"/>
5
+ <circle cx="16" cy="16" r="4.1"/>
6
+ </g>
7
+ </svg>
package/dist/agents.js CHANGED
@@ -167,11 +167,16 @@ function listClaude(cwd) {
167
167
  metas.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
168
168
  return metas;
169
169
  }
170
- function loadClaude(id) {
170
+ function loadClaude(id, cwd) {
171
171
  for (const dir of claudeProjectDirs()) {
172
172
  const fp = path.join(dir, `${id}.jsonl`);
173
173
  if (!fs.existsSync(fp))
174
174
  continue;
175
+ if (cwd) {
176
+ const recorded = claudeSessionCwd(fp);
177
+ if (recorded ? !sameCwd(recorded, cwd) : path.basename(dir).toLowerCase() !== claudeProjectKey(cwd))
178
+ continue;
179
+ }
175
180
  const out = [];
176
181
  for (const o of parseJsonlLines(fp)) {
177
182
  if (o.type === "user") {
@@ -486,10 +491,24 @@ export function listAgentSessions(agent, cwd = process.cwd()) {
486
491
  return listCodex(cwd);
487
492
  return listOpencode(cwd);
488
493
  }
489
- export function loadAgentMessages(agent, id) {
494
+ export function loadAgentMessages(agent, id, cwd) {
495
+ if (!safeAgentSessionId(id))
496
+ return [];
490
497
  if (agent === "claude")
491
- return loadClaude(id);
498
+ return loadClaude(id, cwd);
492
499
  if (agent === "codex")
493
500
  return loadCodex(id);
494
501
  return loadOpencode(id);
495
502
  }
503
+ export function safeAgentSessionId(id) {
504
+ return /^[A-Za-z0-9._-]{1,200}$/.test(id) && id !== "." && !id.includes("..");
505
+ }
506
+ /** Resolve the source in its project, including older Claude files without cwd. */
507
+ export function loadAgentSession(agent, id, cwd) {
508
+ if (!safeAgentSessionId(id))
509
+ return null;
510
+ const meta = listAgentSessions(agent, cwd).find((session) => session.id === id);
511
+ if (!meta)
512
+ return null;
513
+ return { ...meta, cwd, messages: loadAgentMessages(agent, id, cwd) };
514
+ }