@xingjisoft/neutrino 0.3.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.
Files changed (56) hide show
  1. package/README.md +33 -549
  2. package/assets/neutrino.ico +0 -0
  3. package/assets/neutrino.svg +7 -0
  4. package/assets/pro/backend_output_model.txt +5 -0
  5. package/assets/pro/claude-module-prompt.txt +65 -0
  6. package/assets/pro/common.txt +9 -0
  7. package/assets/pro/confirm_model.txt +5 -0
  8. package/assets/pro/front_output_model.txt +9 -0
  9. package/assets/pro/genimage_model.txt +1 -0
  10. package/assets/pro/main_model.txt +5 -0
  11. package/assets/pro/model-template.json +376 -0
  12. package/assets/pro/modeling_model.txt +5 -0
  13. package/assets/pro/plan_model.txt +8 -0
  14. package/assets/pro/thinking_model.txt +3 -0
  15. package/dist/agents.js +22 -3
  16. package/dist/app.js +199 -26
  17. package/dist/background.js +358 -0
  18. package/dist/bin.js +184 -44
  19. package/dist/browser.js +57 -0
  20. package/dist/config.js +174 -26
  21. package/dist/header.js +2 -1
  22. package/dist/index.js +1 -1
  23. package/dist/llm/approval.js +6 -2
  24. package/dist/llm/client.js +197 -28
  25. package/dist/llm/subagent.js +37 -1
  26. package/dist/llm/tools.js +3 -1
  27. package/dist/migration.js +33 -0
  28. package/dist/pro/agents.js +104 -0
  29. package/dist/pro/config.js +88 -0
  30. package/dist/pro/preflight.js +172 -0
  31. package/dist/screens/agentConversation.js +48 -5
  32. package/dist/screens/agentHistory.js +4 -2
  33. package/dist/screens/chat.js +14 -8
  34. package/dist/screens/modelSelect.js +15 -4
  35. package/dist/screens/settings.js +52 -9
  36. package/dist/screens/start.js +48 -15
  37. package/dist/state.js +1 -0
  38. package/dist/terminal/restore.js +3 -12
  39. package/dist/tray.js +138 -0
  40. package/dist/ui.js +58 -0
  41. package/dist/web/hub.js +26 -4
  42. package/dist/web/server.js +495 -74
  43. package/dist/web/service.js +135 -0
  44. package/dist/web/session.js +278 -25
  45. package/dist/web/settings.js +97 -24
  46. package/dist/web/terminalBridge.js +250 -0
  47. package/package.json +37 -37
  48. package/web/README.md +150 -16
  49. package/web/app.js +490 -25
  50. package/web/i18n.js +396 -345
  51. package/web/index.html +120 -15
  52. package/web/neutrino-pro.gif +0 -0
  53. package/web/neutrino.png +0 -0
  54. package/web/pro.js +184 -0
  55. package/web/settings.js +373 -247
  56. package/web/style.css +398 -0
package/web/style.css CHANGED
@@ -137,6 +137,10 @@ svg {
137
137
  display: none !important;
138
138
  }
139
139
 
140
+ .pro-only { display: none; }
141
+ .pro-mode .pro-only { display: block; }
142
+ .pro-mode .standard-only { display: none !important; }
143
+
140
144
  #app {
141
145
  display: flex;
142
146
  width: 100%;
@@ -240,6 +244,120 @@ svg {
240
244
  flex: none;
241
245
  }
242
246
 
247
+ /* The orbit badge doubles as the switch to the Pro workspace. */
248
+ .brand-logo {
249
+ appearance: none;
250
+ padding: 0;
251
+ cursor: pointer;
252
+ transition: background var(--fast), border-color var(--fast), transform var(--fast);
253
+ }
254
+
255
+ .brand-logo:hover {
256
+ background: var(--surface-2);
257
+ border-color: var(--border-strong);
258
+ }
259
+
260
+ .brand-logo:active {
261
+ transform: scale(0.96);
262
+ }
263
+
264
+ .brand-logo:focus-visible {
265
+ outline: 2px solid var(--accent);
266
+ outline-offset: 2px;
267
+ }
268
+
269
+ /* Pro mode replaces the orbit with the static Neutrino wordmark and drops the
270
+ button cue. The PNG is transparent, so it sits directly on the sidebar. */
271
+ .brand-logo.pro-logo {
272
+ /* Narrower than the animated mark was: the brand name grows to "Neutrino
273
+ Pro" in this workspace, and the extra 30px keeps the name on one line at
274
+ the default sidebar width without touching the action buttons. */
275
+ width: 86px;
276
+ height: 25.8px;
277
+ cursor: default;
278
+ overflow: hidden;
279
+ background: transparent;
280
+ border-color: transparent;
281
+ border-radius: 0;
282
+ }
283
+
284
+ .brand-logo.pro-logo:hover {
285
+ background: transparent;
286
+ border-color: transparent;
287
+ }
288
+
289
+ .brand-logo.pro-logo:active {
290
+ transform: none;
291
+ }
292
+
293
+ .brand-logo.pro-logo img {
294
+ display: block;
295
+ width: 100%;
296
+ height: 100%;
297
+ object-fit: contain;
298
+ }
299
+
300
+ /* A dismissible hint under the brand; it never renders in Pro mode. It takes
301
+ its own row so it never covers the New conversation button, with the
302
+ triangle pointing back up at the badge. */
303
+ .pro-tip {
304
+ position: relative;
305
+ z-index: 20;
306
+ display: flex;
307
+ align-items: center;
308
+ gap: 6px;
309
+ flex: none;
310
+ margin: 10px 12px 0;
311
+ padding: 8px 8px 8px 12px;
312
+ background: #2563eb;
313
+ color: #ffffff;
314
+ border-radius: 10px;
315
+ box-shadow: 0 10px 26px rgba(0, 0, 0, 0.42);
316
+ font-size: 12px;
317
+ line-height: 1.4;
318
+ }
319
+
320
+ .pro-tip::before {
321
+ content: "";
322
+ position: absolute;
323
+ top: -6px;
324
+ left: 16px;
325
+ width: 0;
326
+ height: 0;
327
+ border-right: 6px solid transparent;
328
+ border-bottom: 6px solid #2563eb;
329
+ border-left: 6px solid transparent;
330
+ }
331
+
332
+ .pro-tip span {
333
+ flex: 1;
334
+ min-width: 0;
335
+ overflow-wrap: anywhere;
336
+ }
337
+
338
+ .pro-tip-close {
339
+ display: grid;
340
+ place-items: center;
341
+ flex: none;
342
+ width: 20px;
343
+ height: 20px;
344
+ padding: 0;
345
+ background: transparent;
346
+ border: 0;
347
+ border-radius: 6px;
348
+ color: rgba(255, 255, 255, 0.82);
349
+ }
350
+
351
+ .pro-tip-close:hover {
352
+ background: rgba(255, 255, 255, 0.16);
353
+ color: #ffffff;
354
+ }
355
+
356
+ .pro-tip-close svg {
357
+ width: 12px;
358
+ height: 12px;
359
+ }
360
+
243
361
  .icon-btn {
244
362
  display: grid;
245
363
  place-items: center;
@@ -479,6 +597,16 @@ svg {
479
597
  border-bottom: 1px solid var(--border-soft);
480
598
  }
481
599
 
600
+ /* Match the CLI's 500ms thinking phases across the full title bar. */
601
+ .main-header.is-thinking {
602
+ background-color: #28393d;
603
+ animation: header-thinking 1s steps(1, end) infinite;
604
+ }
605
+
606
+ .main-header.is-thinking .main-title h1 {
607
+ color: #99d9ea;
608
+ }
609
+
482
610
  .main-title {
483
611
  flex: 1;
484
612
  min-width: 0;
@@ -516,6 +644,29 @@ svg {
516
644
  gap: 6px;
517
645
  }
518
646
 
647
+ /* Pro workspace only: a persistent way back to the regular console, pinned to
648
+ the far right of the main header. */
649
+ .pro-exit {
650
+ flex: none;
651
+ }
652
+
653
+ .pro-exit svg {
654
+ flex: none;
655
+ width: 14px;
656
+ height: 14px;
657
+ }
658
+
659
+ @media (max-width: 560px) {
660
+ .pro-exit {
661
+ width: 30px;
662
+ padding: 0;
663
+ }
664
+
665
+ .pro-exit span {
666
+ display: none;
667
+ }
668
+ }
669
+
519
670
  .badge {
520
671
  display: inline-flex;
521
672
  align-items: center;
@@ -1231,6 +1382,26 @@ svg {
1231
1382
 
1232
1383
  /* --------------------------------------------------------------- composer */
1233
1384
 
1385
+ .agent-actions {
1386
+ display: flex;
1387
+ flex-shrink: 0;
1388
+ align-items: center;
1389
+ justify-content: flex-end;
1390
+ gap: 12px;
1391
+ padding: 12px clamp(16px, 4vw, 40px) max(18px, env(safe-area-inset-bottom));
1392
+ }
1393
+
1394
+ .agent-actions .form-feedback {
1395
+ flex: 1;
1396
+ min-width: 0;
1397
+ margin: 0;
1398
+ overflow-wrap: anywhere;
1399
+ }
1400
+
1401
+ .agent-actions button {
1402
+ flex-shrink: 0;
1403
+ }
1404
+
1234
1405
  .composer {
1235
1406
  flex-shrink: 0;
1236
1407
  padding: 0 clamp(16px, 4vw, 40px) 18px;
@@ -1287,6 +1458,113 @@ svg {
1287
1458
  outline: none;
1288
1459
  }
1289
1460
 
1461
+ .pro-mode .composer-card { flex-wrap: wrap; }
1462
+ .pro-mode .composer textarea { flex: 1 0 100%; }
1463
+ .pro-preset-control { position: relative; margin-left: auto; align-self: center; }
1464
+ .pro-preset-control > summary {
1465
+ display: flex; align-items: center; gap: 10px; padding: 7px 10px;
1466
+ border-radius: var(--radius-sm); color: var(--text-secondary); cursor: pointer;
1467
+ font-size: 12px; list-style: none; user-select: none;
1468
+ }
1469
+ .pro-preset-control > summary::-webkit-details-marker { display: none; }
1470
+ .pro-preset-chevron { width: 14px; height: 14px; flex: none; transition: transform var(--fast); }
1471
+ .pro-preset-control[open] .pro-preset-chevron { transform: rotate(180deg); }
1472
+ .pro-preset-control > summary:hover, .pro-preset-control[open] > summary { background: var(--surface-3); }
1473
+ .pro-preset-card {
1474
+ position: absolute; z-index: 30; right: 0; bottom: calc(100% + 12px);
1475
+ width: min(390px, calc(100vw - 48px)); max-height: min(560px, 65dvh); overflow-y: auto;
1476
+ padding: 18px; border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
1477
+ background: var(--surface-1); box-shadow: var(--shadow-strong);
1478
+ }
1479
+ .pro-preset-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; font-size: 13px; }
1480
+ .pro-preset-heading strong { color: var(--accent); font-size: 14px; font-weight: 500; }
1481
+ .pro-slider-label { display: block; color: var(--text-tertiary); font-size: 11px; margin-bottom: 8px; }
1482
+ .pro-preset-slider { position: relative; height: 40px; --pro-slider-position: 0; }
1483
+ .pro-preset-slider.is-disabled { opacity: .5; }
1484
+ .pro-slider-track {
1485
+ position: absolute; top: 8px; right: 2px; left: 2px; height: 24px;
1486
+ border-radius: 999px; background: #454547; overflow: hidden; pointer-events: none;
1487
+ }
1488
+ .pro-slider-fill {
1489
+ width: calc(12px + (100% - 24px) * var(--pro-slider-position)); height: 100%; background: #347ff6;
1490
+ }
1491
+ .pro-slider-stops {
1492
+ position: absolute; top: 18px; right: 14px; left: 14px; height: 4px; pointer-events: none;
1493
+ }
1494
+ .pro-slider-stop {
1495
+ position: absolute; width: 4px; height: 4px; transform: translateX(-50%);
1496
+ border-radius: 50%; background: rgba(255, 255, 255, .18);
1497
+ }
1498
+ .pro-slider-stop.is-filled { background: rgba(255, 255, 255, .35); }
1499
+ #pro-preset-range {
1500
+ position: relative; display: block; width: 100%; height: 40px; margin: 0; padding: 0;
1501
+ -webkit-appearance: none; appearance: none; border: 0; border-radius: 999px; background: transparent; cursor: pointer;
1502
+ }
1503
+ #pro-preset-range::-webkit-slider-runnable-track { height: 24px; border: 0; background: transparent; }
1504
+ #pro-preset-range::-webkit-slider-thumb {
1505
+ -webkit-appearance: none; appearance: none; width: 28px; height: 28px; margin-top: -2px;
1506
+ border: 0; border-radius: 50%; background: #fff;
1507
+ }
1508
+ #pro-preset-range::-moz-range-track { height: 24px; border: 0; background: transparent; }
1509
+ #pro-preset-range::-moz-range-progress { background: transparent; }
1510
+ #pro-preset-range::-moz-range-thumb { width: 28px; height: 28px; border: 0; border-radius: 50%; background: #fff; }
1511
+ #pro-preset-range:focus-visible { outline-offset: 2px; }
1512
+ #pro-preset-range:disabled { cursor: default; }
1513
+ .pro-preset-ticks { position: relative; height: 24px; margin: 6px 14px 16px; }
1514
+ .pro-preset-ticks button {
1515
+ position: absolute; transform: translateX(-50%); border: 0; background: transparent;
1516
+ color: var(--text-caption); font-size: 11px; line-height: 16px; padding: 4px; white-space: nowrap; cursor: pointer;
1517
+ }
1518
+ .pro-preset-ticks button[aria-pressed="true"] { color: var(--accent); }
1519
+ .pro-preset-ticks button:disabled { cursor: default; }
1520
+ .pro-slider-meteors { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
1521
+ .pro-slider-meteor {
1522
+ position: absolute; top: 4px; left: -52px; width: 46px; height: 1.5px; opacity: 0;
1523
+ border-radius: 999px; background: linear-gradient(90deg, transparent, rgba(224, 222, 255, .7) 75%, #fff);
1524
+ animation: pro-slider-meteor 2.8s linear infinite; animation-play-state: paused;
1525
+ }
1526
+ .pro-slider-meteor::after {
1527
+ content: ""; position: absolute; top: 50%; right: -1px; width: 3px; height: 3px;
1528
+ transform: translateY(-50%); border-radius: 50%; background: #fff; box-shadow: 0 0 5px rgba(242, 232, 255, .8);
1529
+ }
1530
+ .pro-slider-meteor:nth-child(2) { top: 15px; width: 34px; animation-duration: 3.4s; animation-delay: -1.1s; }
1531
+ .pro-slider-meteor:nth-child(3) { top: 22px; width: 26px; animation-duration: 2.5s; animation-delay: -.45s; }
1532
+ .pro-slider-meteor:nth-child(4) { top: 8px; width: 20px; animation-duration: 4.2s; animation-delay: -2.3s; }
1533
+ .pro-preset-control.is-max .pro-slider-fill { background: linear-gradient(100deg, #347ff6 5%, #6075ff 50%, #a45af8); }
1534
+ .pro-preset-control.is-max .pro-slider-track { box-shadow: 0 0 14px rgba(132, 99, 255, .22); }
1535
+ .pro-preset-control.is-max .pro-slider-meteors { opacity: 1; }
1536
+ .pro-preset-control[open].is-max .pro-slider-meteor { animation-play-state: running; }
1537
+ .pro-preset-control.is-max .pro-preset-heading strong,
1538
+ .pro-preset-control.is-max .pro-preset-ticks button[aria-pressed="true"] { color: #bda8ff; }
1539
+ @keyframes pro-slider-meteor {
1540
+ 0% { transform: translate3d(0, 8px, 0) rotate(-14deg); opacity: 0; }
1541
+ 8% { opacity: .9; }
1542
+ 55% { opacity: .65; }
1543
+ 72%, 100% { transform: translate3d(450px, -8px, 0) rotate(-14deg); opacity: 0; }
1544
+ }
1545
+ .pro-model-details { border-top: 1px solid var(--border-soft); }
1546
+ .pro-model-details > summary {
1547
+ display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 0;
1548
+ color: var(--text-tertiary); font-size: 11.5px; list-style: none; cursor: pointer; user-select: none;
1549
+ }
1550
+ .pro-model-details > summary::-webkit-details-marker { display: none; }
1551
+ .pro-model-details > summary:hover { color: var(--text); }
1552
+ .pro-model-details-chevron { width: 14px; height: 14px; flex: none; color: var(--text-caption); transition: transform var(--fast); }
1553
+ .pro-model-details[open] .pro-model-details-chevron { transform: rotate(180deg); }
1554
+ .pro-preset-models { display: grid; gap: 9px; padding: 2px 0 12px; }
1555
+ .pro-model-row { display: grid; grid-template-columns: minmax(90px, .8fr) minmax(0, 1.6fr); gap: 12px; font-size: 11.5px; line-height: 1.5; }
1556
+ .pro-model-row > span:first-child { color: var(--text-tertiary); }
1557
+ .pro-model-row > span:last-child { overflow-wrap: anywhere; text-align: right; }
1558
+ .pro-model-row .pro-model-fallback { color: var(--amber); }
1559
+ .pro-preset-card .settings-note { margin-top: 15px; font-size: 11px; }
1560
+ .pro-preset-card .form-feedback:empty { display: none; }
1561
+ .pro-fallback-rows { display: grid; gap: 14px; margin-top: 18px; }
1562
+ .pro-fallback-row { display: grid; gap: 5px; padding-bottom: 14px; border-bottom: 1px solid var(--border-soft); overflow-wrap: anywhere; }
1563
+ .pro-fallback-row strong { font-size: 13px; font-weight: 500; }
1564
+ .pro-fallback-row > span { color: var(--amber); font-size: 12px; }
1565
+ .pro-fallback-row small { color: var(--text-tertiary); font-size: 11px; }
1566
+ .pro-fallback-dialog { max-width: 540px; }
1567
+
1290
1568
  #send {
1291
1569
  display: grid;
1292
1570
  place-items: center;
@@ -1306,6 +1584,15 @@ svg {
1306
1584
  height: 18px;
1307
1585
  }
1308
1586
 
1587
+ #send .pause-icon,
1588
+ #send.is-pause .send-icon {
1589
+ display: none;
1590
+ }
1591
+
1592
+ #send.is-pause .pause-icon {
1593
+ display: block;
1594
+ }
1595
+
1309
1596
  #send:hover {
1310
1597
  background: var(--primary-hover);
1311
1598
  }
@@ -2411,6 +2698,56 @@ select option {
2411
2698
  flex: 1;
2412
2699
  }
2413
2700
 
2701
+ /* One-time background-residency invitation. The orbit badge is the regular
2702
+ console's own mark, so the offer reads as part of the same product. */
2703
+ .background-dialog {
2704
+ width: min(460px, calc(100vw - 24px));
2705
+ }
2706
+
2707
+ .background-header {
2708
+ align-items: center;
2709
+ justify-content: flex-start;
2710
+ gap: 14px;
2711
+ border-bottom: 0;
2712
+ padding-bottom: 18px;
2713
+ }
2714
+
2715
+ .background-mark {
2716
+ display: flex;
2717
+ flex: none;
2718
+ align-items: center;
2719
+ justify-content: center;
2720
+ width: 46px;
2721
+ height: 46px;
2722
+ color: var(--accent);
2723
+ background: var(--surface-1);
2724
+ border: 1px solid var(--border-soft);
2725
+ border-radius: 13px;
2726
+ }
2727
+
2728
+ .background-mark svg {
2729
+ width: 24px;
2730
+ height: 24px;
2731
+ }
2732
+
2733
+ .background-copy {
2734
+ min-width: 0;
2735
+ }
2736
+
2737
+ .background-copy h2 {
2738
+ font-size: 15px;
2739
+ line-height: 1.55;
2740
+ font-weight: 600;
2741
+ }
2742
+
2743
+ .background-feedback:empty {
2744
+ display: none;
2745
+ }
2746
+
2747
+ .background-actions {
2748
+ border-top: 1px solid var(--border-soft);
2749
+ }
2750
+
2414
2751
  /* Folder picker */
2415
2752
  .folder-sheet {
2416
2753
  max-height: calc(100vh - 32px);
@@ -2743,8 +3080,59 @@ select option {
2743
3080
  background: transparent;
2744
3081
  }
2745
3082
 
3083
+ /* --------------------------------------------------------------- pro curtain */
3084
+
3085
+ /* Set by the inline head bootstrap when a just-started switch is recorded. It
3086
+ covers /pro before the module runs, so there is no flash of the new page
3087
+ while the curtain is being rebuilt. The module removes it immediately. */
3088
+ html.pro-transitioning body::before {
3089
+ content: "";
3090
+ position: fixed;
3091
+ inset: 0;
3092
+ z-index: 299;
3093
+ background: #151515;
3094
+ }
3095
+
3096
+ /* Full-screen cover used while switching to /pro. #151515 matches the GIF's
3097
+ own background, so the wordmark blends into the cover on first paint. */
3098
+ .pro-curtain {
3099
+ position: fixed;
3100
+ inset: 0;
3101
+ z-index: 300;
3102
+ display: flex;
3103
+ align-items: center;
3104
+ justify-content: center;
3105
+ background: #151515;
3106
+ opacity: 0;
3107
+ transition: opacity 0.35s ease;
3108
+ }
3109
+
3110
+ .pro-curtain.pro-curtain-visible {
3111
+ opacity: 1;
3112
+ }
3113
+
3114
+ .pro-curtain img {
3115
+ display: block;
3116
+ width: min(82vw, 900px);
3117
+ max-height: 60vh;
3118
+ height: auto;
3119
+ object-fit: contain;
3120
+ }
3121
+
2746
3122
  /* -------------------------------------------------------------- animations */
2747
3123
 
3124
+ @keyframes header-thinking {
3125
+ 0%,
3126
+ 100% {
3127
+ background-color: #28393d;
3128
+ border-bottom-color: rgba(153, 217, 234, 0.45);
3129
+ }
3130
+ 50% {
3131
+ background-color: var(--bg);
3132
+ border-bottom-color: var(--border-soft);
3133
+ }
3134
+ }
3135
+
2748
3136
  @keyframes blink {
2749
3137
  0%,
2750
3138
  49% {
@@ -2781,6 +3169,7 @@ select option {
2781
3169
  }
2782
3170
 
2783
3171
  @media (prefers-reduced-motion: reduce) {
3172
+ .pro-slider-meteors { display: none; }
2784
3173
  *,
2785
3174
  *::before,
2786
3175
  *::after {
@@ -2804,6 +3193,15 @@ select option {
2804
3193
  }
2805
3194
  }
2806
3195
 
3196
+ /* Pro's brand name is longer ("Neutrino Pro"). Keep the regular 288px sidebar
3197
+ down to phone width so the name stays on one line next to the narrower logo;
3198
+ the regular workspace keeps its more compact 264px pane. */
3199
+ @media (min-width: 761px) and (max-width: 1080px) {
3200
+ body.pro-mode #sidebar {
3201
+ width: 288px;
3202
+ }
3203
+ }
3204
+
2807
3205
  /* Phones use one full-width pane: the conversation list opens first. */
2808
3206
  @media (max-width: 760px) {
2809
3207
  #sidebar {