@xingjisoft/neutrino 0.5.2 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/assets/fast.txt +18 -0
- package/assets/pro/common.txt +4 -0
- package/assets/pro/main_model.txt +2 -0
- package/assets/pro/plan_model.txt +3 -1
- package/assets/prompt.txt +9 -0
- package/assets/ptc.txt +27 -0
- package/assets/subagent.txt +22 -1
- package/dist/app.js +2 -0
- package/dist/config.js +33 -1
- package/dist/llm/client.js +61 -8
- package/dist/llm/codeExecutor.js +239 -0
- package/dist/llm/codemode.js +164 -0
- package/dist/llm/context.js +110 -0
- package/dist/llm/plan.js +97 -9
- package/dist/llm/risk.js +10 -0
- package/dist/llm/runtime.js +382 -0
- package/dist/llm/search.js +426 -0
- package/dist/llm/searchNetwork.js +129 -0
- package/dist/llm/subagent.js +247 -112
- package/dist/llm/tools.js +101 -4
- package/dist/pro/agents.js +49 -19
- package/dist/pro/preflight.js +24 -2
- package/dist/screens/chat.js +337 -69
- package/dist/web/hub.js +9 -0
- package/dist/web/server.js +57 -8
- package/dist/web/session.js +342 -62
- package/dist/web/terminalBridge.js +10 -1
- package/package.json +10 -5
- package/web/README.md +3 -2
- package/web/app.js +276 -51
- package/web/i18n.js +20 -0
- package/web/index.html +10 -0
- package/web/style.css +57 -11
- package/web/turns.js +7 -8
package/web/i18n.js
CHANGED
|
@@ -79,6 +79,21 @@ export function locale() {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
const ZH = {
|
|
82
|
+
"Agent mode": "Agent 模式",
|
|
83
|
+
"Standard mode": "标准模式",
|
|
84
|
+
"PTC mode": "PTC 模式",
|
|
85
|
+
"Fast mode": "快速模式",
|
|
86
|
+
"Professional mode": "专业模式",
|
|
87
|
+
"Full-featured Agent for every scenario": "功能完整的 Agent ,适用于所有场景",
|
|
88
|
+
"All standard capabilities, with code-based planning to save tokens": "拥有标准模式所有功能,通过代码实现规划,节省 Token ",
|
|
89
|
+
"Less thinking and fewer calls for quick answers": "减少思考和调用,迅速回答问题",
|
|
90
|
+
"Assign tasks to the best-suited models so multiple models complement each other": "适合需要多个模型取长补短的任务,将任务分给最适合他的模型",
|
|
91
|
+
"Applies to the next turn": "从下一轮开始生效",
|
|
92
|
+
"Changing mode…": "正在切换模式…",
|
|
93
|
+
"Could not change mode: {error}": "无法切换模式:{error}",
|
|
94
|
+
"Select a working directory first.": "请先选择工作目录。",
|
|
95
|
+
"Program": "程序",
|
|
96
|
+
|
|
82
97
|
"Pro preset": "Pro 预设",
|
|
83
98
|
"Preset": "预设",
|
|
84
99
|
"Neutrino Pro": "Neutrino Pro",
|
|
@@ -218,6 +233,7 @@ const ZH = {
|
|
|
218
233
|
"Choose a conversation from the sidebar to view it.": "从侧边栏选择一个对话查看。",
|
|
219
234
|
"No conversations yet.": "暂无对话。",
|
|
220
235
|
"No conversations in this folder yet.": "此文件夹中暂无对话。",
|
|
236
|
+
"New conversation in this folder": "在此文件夹中新建对话",
|
|
221
237
|
"Type a message below to start a conversation in this folder.": "在下方输入消息,即可在此文件夹中开始对话。",
|
|
222
238
|
"Send a message to get started.": "发送一条消息开始对话。",
|
|
223
239
|
"This conversation is empty.": "此对话为空。",
|
|
@@ -268,6 +284,10 @@ const ZH = {
|
|
|
268
284
|
|
|
269
285
|
// -- live status --------------------------------------------------------
|
|
270
286
|
"paused": "已暂停",
|
|
287
|
+
"Stopping tasks…": "正在停止任务…",
|
|
288
|
+
"Blocked": "受阻",
|
|
289
|
+
"Cancelled": "已取消",
|
|
290
|
+
"Skipped": "已跳过",
|
|
271
291
|
"working": "处理中",
|
|
272
292
|
"idle": "空闲",
|
|
273
293
|
"compressing": "正在压缩上下文",
|
package/web/index.html
CHANGED
|
@@ -129,6 +129,16 @@
|
|
|
129
129
|
<div class="composer-inner">
|
|
130
130
|
<div class="composer-card">
|
|
131
131
|
<textarea id="input" rows="1" placeholder="Message Neutrino…" aria-label="Message" data-i18n-placeholder="Message Neutrino…"></textarea>
|
|
132
|
+
<details id="agent-mode-picker" class="agent-mode-picker">
|
|
133
|
+
<summary aria-label="Agent mode" data-i18n-aria-label="Agent mode"><strong id="agent-mode-label" data-i18n="Standard mode">Standard mode</strong><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="m7 10 5 5 5-5" /></svg></summary>
|
|
134
|
+
<div class="agent-mode-menu" role="menu" aria-label="Agent mode" data-i18n-aria-label="Agent mode">
|
|
135
|
+
<button type="button" role="menuitemradio" aria-checked="true" data-agent-mode="standard"><span><strong data-i18n="Standard mode">Standard mode</strong><small data-i18n="Full-featured Agent for every scenario">Full-featured Agent for every scenario</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
136
|
+
<button type="button" role="menuitemradio" aria-checked="false" data-agent-mode="ptc"><span><strong data-i18n="PTC mode">PTC mode</strong><small data-i18n="All standard capabilities, with code-based planning to save tokens">All standard capabilities, with code-based planning to save tokens</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
137
|
+
<button type="button" role="menuitemradio" aria-checked="false" data-agent-mode="fast"><span><strong data-i18n="Fast mode">Fast mode</strong><small data-i18n="Less thinking and fewer calls for quick answers">Less thinking and fewer calls for quick answers</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
138
|
+
<button type="button" role="menuitemradio" aria-checked="false" data-agent-mode="pro"><span><strong data-i18n="Professional mode">Professional mode</strong><small data-i18n="Assign tasks to the best-suited models so multiple models complement each other">Assign tasks to the best-suited models so multiple models complement each other</small></span><svg class="agent-mode-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg></button>
|
|
139
|
+
</div>
|
|
140
|
+
<p id="agent-mode-feedback" class="agent-mode-feedback" role="status" aria-live="polite"></p>
|
|
141
|
+
</details>
|
|
132
142
|
<details id="model-picker" class="model-picker pro-preset-control hidden">
|
|
133
143
|
<summary id="model-picker-summary" aria-label="Conversation model" data-i18n-aria-label="Conversation model"><span id="model-picker-label"></span><svg class="pro-preset-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 10 5 5 5-5" /></svg></summary>
|
|
134
144
|
<div class="pro-preset-card model-picker-card">
|
package/web/style.css
CHANGED
|
@@ -413,11 +413,20 @@ svg {
|
|
|
413
413
|
margin-bottom: 4px;
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
/* A folder row is two controls: the name folds the list, the plus beside it
|
|
417
|
+
opens a new conversation in that folder. */
|
|
418
|
+
.group-head {
|
|
419
|
+
display: flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
gap: 2px;
|
|
422
|
+
}
|
|
423
|
+
|
|
416
424
|
.group-header {
|
|
417
425
|
display: flex;
|
|
418
426
|
align-items: center;
|
|
419
427
|
gap: 7px;
|
|
420
|
-
|
|
428
|
+
flex: 1;
|
|
429
|
+
min-width: 0;
|
|
421
430
|
height: 30px;
|
|
422
431
|
padding: 0 8px;
|
|
423
432
|
background: transparent;
|
|
@@ -461,16 +470,22 @@ svg {
|
|
|
461
470
|
font-size: 12px;
|
|
462
471
|
}
|
|
463
472
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
background: var(--surface-1);
|
|
473
|
+
/* Small enough to sit on the folder row without competing with the name, and
|
|
474
|
+
dimmed so a long list of folders never reads as a wall of plus signs. */
|
|
475
|
+
.group-new {
|
|
476
|
+
width: 22px;
|
|
477
|
+
height: 22px;
|
|
470
478
|
color: var(--text-caption);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.group-new svg {
|
|
482
|
+
width: 13px;
|
|
483
|
+
height: 13px;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.group-new:hover {
|
|
487
|
+
background: var(--surface-1);
|
|
488
|
+
color: var(--text-secondary);
|
|
474
489
|
}
|
|
475
490
|
|
|
476
491
|
.group-items {
|
|
@@ -1355,7 +1370,7 @@ svg {
|
|
|
1355
1370
|
transform: rotate(90deg);
|
|
1356
1371
|
}
|
|
1357
1372
|
|
|
1358
|
-
.turn-
|
|
1373
|
+
.turn-label {
|
|
1359
1374
|
flex: 1 1 auto;
|
|
1360
1375
|
min-width: 0;
|
|
1361
1376
|
overflow: hidden;
|
|
@@ -1474,6 +1489,26 @@ svg {
|
|
|
1474
1489
|
outline: none;
|
|
1475
1490
|
}
|
|
1476
1491
|
|
|
1492
|
+
.agent-mode-picker { position: relative; align-self: center; margin-right: auto; min-width: 0; }
|
|
1493
|
+
.agent-mode-picker > summary { display: flex; align-items: center; gap: 7px; padding: 7px 9px; border-radius: var(--radius-sm); cursor: pointer; list-style: none; color: var(--text-secondary); font-size: 12px; }
|
|
1494
|
+
.agent-mode-picker > summary::-webkit-details-marker { display: none; }
|
|
1495
|
+
.agent-mode-picker > summary strong { font-weight: 600; }
|
|
1496
|
+
.agent-mode-picker > summary svg { width: 14px; height: 14px; }
|
|
1497
|
+
.agent-mode-picker > summary:hover, .agent-mode-picker[open] > summary { background: var(--surface-3); }
|
|
1498
|
+
.agent-mode-picker.is-pending > summary { color: var(--accent); }
|
|
1499
|
+
.agent-mode-menu { position: absolute; z-index: 35; left: 0; bottom: calc(100% + 10px); width: min(370px, calc(100vw - 48px)); max-height: min(520px, 60dvh); overflow-y: auto; padding: 6px; border: 1px solid var(--border-strong); border-radius: var(--radius-lg); background: var(--surface-1); box-shadow: var(--shadow-strong); }
|
|
1500
|
+
.agent-mode-menu > button { display: flex; align-items: center; gap: 12px; width: 100%; padding: 11px 12px; text-align: left; color: var(--text); background: transparent; border: 0; border-radius: var(--radius-sm); cursor: pointer; font-family: var(--font); }
|
|
1501
|
+
.agent-mode-menu > button:hover, .agent-mode-menu > button:focus-visible, .agent-mode-menu > button[aria-checked="true"] { background: var(--surface-3); }
|
|
1502
|
+
.agent-mode-menu > button > span { flex: 1; min-width: 0; }
|
|
1503
|
+
.agent-mode-menu strong { display: block; margin-bottom: 4px; font-size: 13px; font-weight: 700; }
|
|
1504
|
+
.agent-mode-menu small { display: block; color: var(--text-tertiary); font-size: 12px; font-weight: 400; line-height: 1.55; }
|
|
1505
|
+
.agent-mode-check { flex: none; width: 16px; height: 16px; color: var(--accent); visibility: hidden; }
|
|
1506
|
+
.agent-mode-menu [aria-checked="true"] .agent-mode-check { visibility: visible; }
|
|
1507
|
+
.agent-mode-menu > button:disabled { opacity: .55; cursor: wait; }
|
|
1508
|
+
.agent-mode-feedback { position: absolute; bottom: 100%; left: 0; width: min(370px, calc(100vw - 48px)); margin: 0 0 5px; font-size: 11px; color: var(--text-secondary); }
|
|
1509
|
+
.agent-mode-feedback:empty, .agent-mode-picker[open] .agent-mode-feedback { display: none; }
|
|
1510
|
+
.agent-mode-feedback.error { color: var(--error, #ef8181); }
|
|
1511
|
+
.program-source { border-bottom: 1px solid var(--border-soft); }
|
|
1477
1512
|
.pro-preset-control { position: relative; margin-left: auto; align-self: center; }
|
|
1478
1513
|
.pro-preset-control > summary {
|
|
1479
1514
|
display: flex; align-items: center; gap: 10px; padding: 7px 10px;
|
|
@@ -3308,6 +3343,13 @@ html.pro-transitioning body::before {
|
|
|
3308
3343
|
min-height: 40px;
|
|
3309
3344
|
}
|
|
3310
3345
|
|
|
3346
|
+
/* The plus takes the same 40px target as the other icon buttons here, so
|
|
3347
|
+
its icon grows with it instead of staying at the desktop size. */
|
|
3348
|
+
.group-new svg {
|
|
3349
|
+
width: 16px;
|
|
3350
|
+
height: 16px;
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3311
3353
|
.main-header {
|
|
3312
3354
|
display: grid;
|
|
3313
3355
|
grid-template-columns: 40px minmax(0, 1fr);
|
|
@@ -3533,3 +3575,7 @@ html.pro-transitioning body::before {
|
|
|
3533
3575
|
min-height: 64px;
|
|
3534
3576
|
}
|
|
3535
3577
|
}
|
|
3578
|
+
|
|
3579
|
+
.plan-step.blocked .plan-node,
|
|
3580
|
+
.plan-step.blocked .plan-step-title strong { color: var(--orange, #dca548); }
|
|
3581
|
+
.plan-step.cancelled, .plan-step.skipped { opacity: .65; }
|
package/web/turns.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// A conversation is a sequence of turns: each one starts at a user message and
|
|
4
4
|
// runs until the next one. Once the agent has finished a turn, the browser folds
|
|
5
|
-
// away the work it took — the
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// away the work it took — the thinking and the tool calls — and keeps the full
|
|
6
|
+
// user prompt and final reply on screen. The complete round is still one click
|
|
7
|
+
// away.
|
|
8
8
|
//
|
|
9
9
|
// The thresholds mirror TURN_FOLD_MIN_BLOCKS / TURN_FOLD_MIN_CHARS in
|
|
10
10
|
// src/screens/chat.ts, so the terminal and the browser fold the same turns.
|
|
@@ -33,8 +33,8 @@ export function turnAnswerIndex(messages) {
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* True when folding the work behind this turn saves more room than it costs.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* User input and the reply are never folded away, so neither counts toward the
|
|
37
|
+
* folding threshold, however long either one is.
|
|
38
38
|
*/
|
|
39
39
|
export function turnFolds(messages) {
|
|
40
40
|
const reply = turnAnswerIndex(messages);
|
|
@@ -42,7 +42,7 @@ export function turnFolds(messages) {
|
|
|
42
42
|
let chars = 0;
|
|
43
43
|
messages.forEach((message, index) => {
|
|
44
44
|
if (index === reply) return;
|
|
45
|
-
if (message.role !== "
|
|
45
|
+
if (message.role !== "assistant") return;
|
|
46
46
|
blocks++;
|
|
47
47
|
chars += String(message.content ?? "").length;
|
|
48
48
|
});
|
|
@@ -50,8 +50,7 @@ export function turnFolds(messages) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* The first line of the first message in `role
|
|
54
|
-
* signpost, and the full text is one click away.
|
|
53
|
+
* The first nonempty line of the first message in `role` that contains text.
|
|
55
54
|
*/
|
|
56
55
|
export function turnPreview(messages, role) {
|
|
57
56
|
for (const message of messages) {
|