@xcanwin/manyoyo 6.2.0 → 6.2.4
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/bin/manyoyo.js +8 -0
- package/lib/runtime-resolver.js +10 -0
- package/lib/web/frontend/app.css +37 -194
- package/lib/web/frontend/app.html +6 -4
- package/lib/web/frontend/app.js +13 -23
- package/lib/web/frontend/login.html +1 -2
- package/lib/web/server.js +44 -5
- package/manyoyo.example.json +7 -0
- package/package.json +1 -1
package/bin/manyoyo.js
CHANGED
|
@@ -90,6 +90,7 @@ let SERVER_PORT = 3000;
|
|
|
90
90
|
let SERVER_AUTH_USER = "";
|
|
91
91
|
let SERVER_AUTH_PASS = "";
|
|
92
92
|
let SERVER_AUTH_PASS_AUTO = false;
|
|
93
|
+
let SERVER_TITLE = null;
|
|
93
94
|
const SAFE_CONTAINER_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
|
|
94
95
|
|
|
95
96
|
// Color definitions using ANSI codes
|
|
@@ -306,6 +307,7 @@ function installServeProcessDiagnostics(logger) {
|
|
|
306
307
|
* @property {{shellPrefix?:string,shell?:string,shellSuffix?:string,env?:Object.<string,string|number|boolean>,envFile?:string[]}} [first] - 仅首次创建容器执行的一次性命令配置
|
|
307
308
|
* @property {string[]} [volumes] - 挂载卷数组
|
|
308
309
|
* @property {Object.<string, Object>} [plugins] - 可选插件配置映射(如 plugins.playwright)
|
|
310
|
+
* @property {{title?:string}} [serve] - serve 命令专属配置(如自定义网页标题,留空字符串则显示为空)
|
|
309
311
|
* @property {Object.<string, Object>} [runs] - 运行配置映射(-r <name>)
|
|
310
312
|
* @property {string} [yolo] - YOLO 模式
|
|
311
313
|
* @property {string} [containerMode] - 容器模式
|
|
@@ -1461,6 +1463,7 @@ Notes:
|
|
|
1461
1463
|
SERVER_AUTH_USER = resolvedRuntime.serverUser || '';
|
|
1462
1464
|
SERVER_AUTH_PASS = resolvedRuntime.serverPass || '';
|
|
1463
1465
|
SERVER_AUTH_PASS_AUTO = Boolean(resolvedRuntime.serverPassAuto);
|
|
1466
|
+
SERVER_TITLE = resolvedRuntime.serveTitle;
|
|
1464
1467
|
|
|
1465
1468
|
if (isShowConfigMode) {
|
|
1466
1469
|
const finalConfig = {
|
|
@@ -1489,6 +1492,9 @@ Notes:
|
|
|
1489
1492
|
serverPort: isServerMode ? SERVER_PORT : null,
|
|
1490
1493
|
serverUser: SERVER_AUTH_USER || "",
|
|
1491
1494
|
serverPass: SERVER_AUTH_PASS || "",
|
|
1495
|
+
serve: {
|
|
1496
|
+
title: SERVER_TITLE
|
|
1497
|
+
},
|
|
1492
1498
|
exec: {
|
|
1493
1499
|
prefix: EXEC_COMMAND_PREFIX,
|
|
1494
1500
|
shell: EXEC_COMMAND,
|
|
@@ -1590,6 +1596,7 @@ function createRuntimeContext(modeState = {}) {
|
|
|
1590
1596
|
serverAuthUser: SERVER_AUTH_USER,
|
|
1591
1597
|
serverAuthPass: SERVER_AUTH_PASS,
|
|
1592
1598
|
serverAuthPassAuto: SERVER_AUTH_PASS_AUTO,
|
|
1599
|
+
serveTitle: SERVER_TITLE,
|
|
1593
1600
|
logger: null
|
|
1594
1601
|
};
|
|
1595
1602
|
}
|
|
@@ -2086,6 +2093,7 @@ async function runWebServerMode(runtime) {
|
|
|
2086
2093
|
authUser: runtime.serverAuthUser,
|
|
2087
2094
|
authPass: runtime.serverAuthPass,
|
|
2088
2095
|
authPassAuto: runtime.serverAuthPassAuto,
|
|
2096
|
+
serveTitle: runtime.serveTitle,
|
|
2089
2097
|
dockerCmd: DOCKER_CMD,
|
|
2090
2098
|
hostPath: runtime.hostPath,
|
|
2091
2099
|
containerPath: runtime.containerPath,
|
package/lib/runtime-resolver.js
CHANGED
|
@@ -148,6 +148,15 @@ function resolveRuntimeConfig(options = {}) {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
let serveTitle = null;
|
|
152
|
+
const globalServeConfig = globalConfig.serve && typeof globalConfig.serve === 'object' ? globalConfig.serve : null;
|
|
153
|
+
const runServeConfig = runConfig.serve && typeof runConfig.serve === 'object' ? runConfig.serve : null;
|
|
154
|
+
if (runServeConfig && Object.prototype.hasOwnProperty.call(runServeConfig, 'title')) {
|
|
155
|
+
serveTitle = String(runServeConfig.title);
|
|
156
|
+
} else if (globalServeConfig && Object.prototype.hasOwnProperty.call(globalServeConfig, 'title')) {
|
|
157
|
+
serveTitle = String(globalServeConfig.title);
|
|
158
|
+
}
|
|
159
|
+
|
|
151
160
|
if (!hostPath) {
|
|
152
161
|
hostPath = defaults.hostPath;
|
|
153
162
|
}
|
|
@@ -197,6 +206,7 @@ function resolveRuntimeConfig(options = {}) {
|
|
|
197
206
|
serverUser,
|
|
198
207
|
serverPass,
|
|
199
208
|
serverPassAuto,
|
|
209
|
+
serveTitle,
|
|
200
210
|
exec: {
|
|
201
211
|
prefix: execPrefix,
|
|
202
212
|
shell: execShell,
|
package/lib/web/frontend/app.css
CHANGED
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
|
|
14
14
|
--line: #d9c7ad;
|
|
15
15
|
--line-strong: #b59263;
|
|
16
|
-
--line-soft: rgba(181, 146, 99, 0.26);
|
|
17
|
-
--line-medium: rgba(181, 146, 99, 0.4);
|
|
18
16
|
--tree-guide: rgba(181, 146, 99, 0.44);
|
|
19
17
|
--tree-guide-soft: rgba(181, 146, 99, 0.44);
|
|
20
18
|
--tree-active: #ecdfc8;
|
|
@@ -26,7 +24,6 @@
|
|
|
26
24
|
|
|
27
25
|
--accent: #c4551f;
|
|
28
26
|
--accent-strong: #9d4418;
|
|
29
|
-
--accent-soft: #fee4d6;
|
|
30
27
|
|
|
31
28
|
--subaccent: #0f7c72;
|
|
32
29
|
--subaccent-strong: #0a655e;
|
|
@@ -36,14 +33,9 @@
|
|
|
36
33
|
--danger-strong: #962824;
|
|
37
34
|
--danger-soft: #ffe8e5;
|
|
38
35
|
|
|
39
|
-
--status-running-bg: #e0f5ef;
|
|
40
36
|
--status-running-text: #0c695f;
|
|
41
|
-
--status-stopped-bg: #fff0dd;
|
|
42
37
|
--status-stopped-text: #9a5a09;
|
|
43
|
-
--status-history-bg: #ece7df;
|
|
44
|
-
--status-history-text: #645647;
|
|
45
38
|
--tree-history-text: #b7b0a7;
|
|
46
|
-
--status-unknown-bg: #ece9ff;
|
|
47
39
|
--status-unknown-text: #5a4bb0;
|
|
48
40
|
|
|
49
41
|
--user-bubble: #fee9dd;
|
|
@@ -54,7 +46,6 @@
|
|
|
54
46
|
--terminal-fg: #e8edf5;
|
|
55
47
|
--terminal-cursor: #ffd166;
|
|
56
48
|
|
|
57
|
-
--shadow-soft: 0 14px 34px rgba(44, 31, 15, 0.16);
|
|
58
49
|
--shadow-strong: 0 24px 54px rgba(44, 31, 15, 0.24);
|
|
59
50
|
}
|
|
60
51
|
|
|
@@ -126,7 +117,7 @@ body::after {
|
|
|
126
117
|
|
|
127
118
|
.sidebar {
|
|
128
119
|
min-height: 0;
|
|
129
|
-
padding: 16px;
|
|
120
|
+
padding: 16px 12px 8px;
|
|
130
121
|
display: flex;
|
|
131
122
|
flex-direction: column;
|
|
132
123
|
gap: 12px;
|
|
@@ -159,6 +150,7 @@ body::after {
|
|
|
159
150
|
font-size: 22px;
|
|
160
151
|
line-height: 1;
|
|
161
152
|
text-transform: uppercase;
|
|
153
|
+
white-space: nowrap;
|
|
162
154
|
}
|
|
163
155
|
|
|
164
156
|
.sidebar-actions {
|
|
@@ -174,6 +166,16 @@ body::after {
|
|
|
174
166
|
min-width: 62px;
|
|
175
167
|
}
|
|
176
168
|
|
|
169
|
+
.sidebar-create button,
|
|
170
|
+
.sidebar-bottom button {
|
|
171
|
+
width: 100%;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.sidebar-bottom {
|
|
175
|
+
display: flex;
|
|
176
|
+
margin-top: -4px;
|
|
177
|
+
}
|
|
178
|
+
|
|
177
179
|
input:focus,
|
|
178
180
|
textarea:focus {
|
|
179
181
|
outline: none;
|
|
@@ -526,136 +528,6 @@ textarea:focus-visible {
|
|
|
526
528
|
border-radius: 8px;
|
|
527
529
|
}
|
|
528
530
|
|
|
529
|
-
.session-item {
|
|
530
|
-
text-align: left;
|
|
531
|
-
width: 100%;
|
|
532
|
-
border: 1px solid var(--line);
|
|
533
|
-
background: var(--panel-strong);
|
|
534
|
-
color: var(--text);
|
|
535
|
-
border-radius: 10px;
|
|
536
|
-
padding: 11px 11px 11px 10px;
|
|
537
|
-
animation: itemIn 220ms ease both;
|
|
538
|
-
animation-delay: calc(var(--item-index, 0) * 30ms);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
.session-item:hover {
|
|
542
|
-
border-color: #d1aa7f;
|
|
543
|
-
background: #fff8ef;
|
|
544
|
-
box-shadow: inset 3px 0 0 #d39c6d;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
.session-item.active {
|
|
548
|
-
border-color: #c68d5a;
|
|
549
|
-
background: #fff3e8;
|
|
550
|
-
box-shadow: inset 3px 0 0 var(--accent), 0 0 0 2px rgba(196, 85, 31, 0.14);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
.session-item.status-history:not(.active),
|
|
554
|
-
.session-item.status-stopped:not(.active),
|
|
555
|
-
.session-item.status-unknown:not(.active) {
|
|
556
|
-
border-color: rgba(181, 146, 99, 0.22);
|
|
557
|
-
background: rgba(255, 252, 247, 0.72);
|
|
558
|
-
box-shadow: none;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
.session-item.status-history:not(.active) .session-name,
|
|
562
|
-
.session-item.status-history:not(.active) .session-meta,
|
|
563
|
-
.session-item.status-history:not(.active) .session-time,
|
|
564
|
-
.session-item.status-stopped:not(.active) .session-name,
|
|
565
|
-
.session-item.status-stopped:not(.active) .session-meta,
|
|
566
|
-
.session-item.status-stopped:not(.active) .session-time,
|
|
567
|
-
.session-item.status-unknown:not(.active) .session-name,
|
|
568
|
-
.session-item.status-unknown:not(.active) .session-meta,
|
|
569
|
-
.session-item.status-unknown:not(.active) .session-time {
|
|
570
|
-
opacity: 0.25;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
.session-name {
|
|
574
|
-
font-size: 13px;
|
|
575
|
-
font-weight: 700;
|
|
576
|
-
margin-bottom: 6px;
|
|
577
|
-
word-break: break-all;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
.session-meta {
|
|
581
|
-
display: flex;
|
|
582
|
-
align-items: center;
|
|
583
|
-
gap: 7px;
|
|
584
|
-
font-size: 12px;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
.session-status {
|
|
588
|
-
border-radius: 999px;
|
|
589
|
-
padding: 2px 8px;
|
|
590
|
-
font-weight: 700;
|
|
591
|
-
font-size: 11px;
|
|
592
|
-
line-height: 1.4;
|
|
593
|
-
border: 1px solid transparent;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
.session-status.running {
|
|
597
|
-
background: var(--status-running-bg);
|
|
598
|
-
color: var(--status-running-text);
|
|
599
|
-
border-color: #a9dacf;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
.session-status.stopped {
|
|
603
|
-
background: var(--status-stopped-bg);
|
|
604
|
-
color: var(--status-stopped-text);
|
|
605
|
-
border-color: #edc98e;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
.session-status.history {
|
|
609
|
-
background: var(--status-history-bg);
|
|
610
|
-
color: var(--status-history-text);
|
|
611
|
-
border-color: #d8cebe;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
.session-status.unknown {
|
|
615
|
-
background: var(--status-unknown-bg);
|
|
616
|
-
color: var(--status-unknown-text);
|
|
617
|
-
border-color: #cac1fb;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
.session-count {
|
|
621
|
-
color: var(--muted);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
.session-time {
|
|
625
|
-
margin-top: 6px;
|
|
626
|
-
font-size: 11px;
|
|
627
|
-
color: #7b6d5d;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
.workbench-group {
|
|
631
|
-
display: flex;
|
|
632
|
-
flex-direction: column;
|
|
633
|
-
gap: 10px;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
button.tree-toggle {
|
|
637
|
-
width: 100%;
|
|
638
|
-
text-align: left;
|
|
639
|
-
color: var(--text);
|
|
640
|
-
background: rgba(255, 250, 242, 0.92);
|
|
641
|
-
border-color: rgba(181, 146, 99, 0.38);
|
|
642
|
-
padding: 10px 12px;
|
|
643
|
-
display: flex;
|
|
644
|
-
align-items: flex-start;
|
|
645
|
-
justify-content: flex-start;
|
|
646
|
-
gap: 0;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
button.tree-toggle:hover {
|
|
650
|
-
transform: none;
|
|
651
|
-
background: #fff6eb;
|
|
652
|
-
border-color: #d1aa7f;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
button.tree-toggle:active {
|
|
656
|
-
transform: none;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
531
|
.tree-node-block,
|
|
660
532
|
.tree-node-children {
|
|
661
533
|
display: flex;
|
|
@@ -1036,7 +908,7 @@ button.tree-toggle:active {
|
|
|
1036
908
|
|
|
1037
909
|
.main {
|
|
1038
910
|
min-height: 0;
|
|
1039
|
-
padding: 14px;
|
|
911
|
+
padding: 8px 14px;
|
|
1040
912
|
display: grid;
|
|
1041
913
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
1042
914
|
gap: 0;
|
|
@@ -1052,8 +924,7 @@ button.tree-toggle:active {
|
|
|
1052
924
|
justify-content: space-between;
|
|
1053
925
|
align-items: flex-start;
|
|
1054
926
|
gap: 12px;
|
|
1055
|
-
padding:
|
|
1056
|
-
border-bottom: 1px solid var(--line-medium);
|
|
927
|
+
padding: 0 8px 4px;
|
|
1057
928
|
}
|
|
1058
929
|
|
|
1059
930
|
.header-main {
|
|
@@ -1133,7 +1004,7 @@ body.workspace-switcher-open .workspace-switcher-panel {
|
|
|
1133
1004
|
.workspace-shell {
|
|
1134
1005
|
min-height: 0;
|
|
1135
1006
|
height: 100%;
|
|
1136
|
-
padding-top:
|
|
1007
|
+
padding-top: 6px;
|
|
1137
1008
|
display: block;
|
|
1138
1009
|
overflow: hidden;
|
|
1139
1010
|
}
|
|
@@ -1657,16 +1528,18 @@ body.terminal-mode .composer {
|
|
|
1657
1528
|
gap: 6px;
|
|
1658
1529
|
animation: msgIn 210ms ease both;
|
|
1659
1530
|
animation-delay: 0ms;
|
|
1660
|
-
/* animation 会让每条消息各自建立独立的层叠上下文(transform/opacity 动画的副作用),
|
|
1661
|
-
悬浮显示的 .msg-actions 浮层因此只能在本条消息内部比较 z-index,
|
|
1662
|
-
会被后面消息按 DOM 序整体盖住;悬浮时显式提升本条消息的 z-index 才能穿透 */
|
|
1663
|
-
z-index: 0;
|
|
1664
1531
|
}
|
|
1665
1532
|
|
|
1666
|
-
.msg
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1533
|
+
/* .msg-actions 悬浮浮层会伸到气泡下方,给带浮层的消息多留一点底部间距,
|
|
1534
|
+
让浮层完整落在消息之间的空白里,不需要靠 z-index 去抢下一条消息的地盘 */
|
|
1535
|
+
.msg:has(.msg-actions) {
|
|
1536
|
+
margin-bottom: 20px;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
.msg:last-child {
|
|
1540
|
+
/* animation 会让每条消息各自建立独立的层叠上下文(transform/opacity 动画的副作用);
|
|
1541
|
+
只有最后一条消息的浮层可能伸到吸底 composer(移动端 position: sticky, z-index: 3)
|
|
1542
|
+
的区域里,需要显式盖过它 */
|
|
1670
1543
|
z-index: 4;
|
|
1671
1544
|
}
|
|
1672
1545
|
|
|
@@ -1747,12 +1620,6 @@ body.terminal-mode .composer {
|
|
|
1747
1620
|
opacity: 1;
|
|
1748
1621
|
pointer-events: auto;
|
|
1749
1622
|
}
|
|
1750
|
-
|
|
1751
|
-
/* 触屏设备没有真正的 :hover,上面 .msg:hover 提升 z-index 的规则永远不会触发;
|
|
1752
|
-
这里常驻按住基础 z-index 之上,浮层才不会被吸底输入框(sticky, z-index: 3)盖住 */
|
|
1753
|
-
.msg {
|
|
1754
|
-
z-index: 4;
|
|
1755
|
-
}
|
|
1756
1623
|
}
|
|
1757
1624
|
|
|
1758
1625
|
.msg-copy-btn {
|
|
@@ -1781,24 +1648,6 @@ body.terminal-mode .composer {
|
|
|
1781
1648
|
background: var(--subaccent-soft);
|
|
1782
1649
|
}
|
|
1783
1650
|
|
|
1784
|
-
.msg-exit {
|
|
1785
|
-
font-family: var(--font-mono);
|
|
1786
|
-
font-size: 11px;
|
|
1787
|
-
letter-spacing: 0.1px;
|
|
1788
|
-
color: var(--muted);
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
.msg.user .msg-exit {
|
|
1792
|
-
align-self: flex-end;
|
|
1793
|
-
text-align: right;
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
.msg.assistant .msg-exit,
|
|
1797
|
-
.msg.system .msg-exit {
|
|
1798
|
-
align-self: flex-start;
|
|
1799
|
-
text-align: left;
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
1651
|
.bubble {
|
|
1803
1652
|
position: relative;
|
|
1804
1653
|
border: 1px solid var(--line);
|
|
@@ -1823,14 +1672,12 @@ body.terminal-mode .composer {
|
|
|
1823
1672
|
}
|
|
1824
1673
|
|
|
1825
1674
|
body.agent-mode .msg.origin-command .bubble,
|
|
1826
|
-
body.agent-mode .msg.origin-command .msg-meta
|
|
1827
|
-
body.agent-mode .msg.origin-command .msg-exit {
|
|
1675
|
+
body.agent-mode .msg.origin-command .msg-meta {
|
|
1828
1676
|
opacity: 0.25;
|
|
1829
1677
|
}
|
|
1830
1678
|
|
|
1831
1679
|
body.command-mode .msg.origin-agent .bubble,
|
|
1832
|
-
body.command-mode .msg.origin-agent .msg-meta
|
|
1833
|
-
body.command-mode .msg.origin-agent .msg-exit {
|
|
1680
|
+
body.command-mode .msg.origin-agent .msg-meta {
|
|
1834
1681
|
opacity: 0.25;
|
|
1835
1682
|
}
|
|
1836
1683
|
|
|
@@ -2033,12 +1880,20 @@ details.trace-card > .trace-card-summary {
|
|
|
2033
1880
|
}
|
|
2034
1881
|
|
|
2035
1882
|
.composer {
|
|
2036
|
-
margin-top:
|
|
2037
|
-
padding:
|
|
1883
|
+
margin-top: 4px;
|
|
1884
|
+
padding: 4px 8px 0;
|
|
2038
1885
|
border-radius: 14px;
|
|
2039
1886
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.32) 0%, rgba(255, 249, 240, 0.78) 100%);
|
|
2040
1887
|
}
|
|
2041
1888
|
|
|
1889
|
+
body.composer-options-open .composer {
|
|
1890
|
+
/* 移动端 .composer 是 position: sticky + z-index: 3 自成一个层叠上下文,
|
|
1891
|
+
"选项"面板的 z-index: 8 只在这个上下文内部有效,会被消息区里
|
|
1892
|
+
z-index 更高的内容(如 .msg:last-child)整体盖住;下拉展开时
|
|
1893
|
+
把 composer 本身的层级也一起抬高,才能让面板真正浮在最上层 */
|
|
1894
|
+
z-index: 20;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
2042
1897
|
.composer-inner {
|
|
2043
1898
|
display: grid;
|
|
2044
1899
|
grid-template-columns: 1fr auto;
|
|
@@ -2131,17 +1986,6 @@ body.composer-options-open .composer-options-panel {
|
|
|
2131
1986
|
}
|
|
2132
1987
|
}
|
|
2133
1988
|
|
|
2134
|
-
@keyframes itemIn {
|
|
2135
|
-
from {
|
|
2136
|
-
opacity: 0;
|
|
2137
|
-
transform: translateY(6px);
|
|
2138
|
-
}
|
|
2139
|
-
to {
|
|
2140
|
-
opacity: 1;
|
|
2141
|
-
transform: translateY(0);
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
1989
|
@keyframes msgIn {
|
|
2146
1990
|
from {
|
|
2147
1991
|
opacity: 0;
|
|
@@ -2281,7 +2125,6 @@ body.composer-options-open .composer-options-panel {
|
|
|
2281
2125
|
|
|
2282
2126
|
.header {
|
|
2283
2127
|
padding: 4px 12px;
|
|
2284
|
-
border-bottom: none;
|
|
2285
2128
|
}
|
|
2286
2129
|
|
|
2287
2130
|
.workbench-tabs {
|
|
@@ -18,10 +18,6 @@
|
|
|
18
18
|
<div class="brand-wrap">
|
|
19
19
|
<div class="brand">MANYOYO Web</div>
|
|
20
20
|
</div>
|
|
21
|
-
<div class="sidebar-actions">
|
|
22
|
-
<button type="button" id="openConfigBtn" class="secondary">配置</button>
|
|
23
|
-
<button type="button" id="openCreateBtn">新建</button>
|
|
24
|
-
</div>
|
|
25
21
|
<button
|
|
26
22
|
type="button"
|
|
27
23
|
id="mobileSidebarClose"
|
|
@@ -29,11 +25,17 @@
|
|
|
29
25
|
aria-label="关闭会话列表"
|
|
30
26
|
>关闭</button>
|
|
31
27
|
</div>
|
|
28
|
+
<div class="sidebar-create">
|
|
29
|
+
<button type="button" id="openCreateBtn">新建容器</button>
|
|
30
|
+
</div>
|
|
32
31
|
<div class="session-head">
|
|
33
32
|
<span>工作台</span>
|
|
34
33
|
<span id="sessionCount">0 个</span>
|
|
35
34
|
</div>
|
|
36
35
|
<div id="sessionList" role="tree" aria-label="会话树"></div>
|
|
36
|
+
<div class="sidebar-bottom">
|
|
37
|
+
<button type="button" id="openConfigBtn" class="secondary">系统设置</button>
|
|
38
|
+
</div>
|
|
37
39
|
</aside>
|
|
38
40
|
<main class="main">
|
|
39
41
|
<header class="header">
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -951,17 +951,6 @@
|
|
|
951
951
|
return base.replace(/\/+$/, '') + '/' + child.replace(/^\/+/, '');
|
|
952
952
|
}
|
|
953
953
|
|
|
954
|
-
function envMapToText(envMap) {
|
|
955
|
-
if (!envMap || typeof envMap !== 'object') {
|
|
956
|
-
return '';
|
|
957
|
-
}
|
|
958
|
-
return Object.entries(envMap)
|
|
959
|
-
.map(function (entry) {
|
|
960
|
-
return entry[0] + '=' + String(entry[1] == null ? '' : entry[1]);
|
|
961
|
-
})
|
|
962
|
-
.join('\n');
|
|
963
|
-
}
|
|
964
|
-
|
|
965
954
|
function textToLineArray(text) {
|
|
966
955
|
return String(text || '')
|
|
967
956
|
.split('\n')
|
|
@@ -2257,12 +2246,15 @@
|
|
|
2257
2246
|
}
|
|
2258
2247
|
|
|
2259
2248
|
function syncUi() {
|
|
2249
|
+
const useStaticTitle = typeof window.__MANYOYO_SERVE_TITLE__ === 'string';
|
|
2260
2250
|
if (!state.active) {
|
|
2261
|
-
|
|
2251
|
+
if (!useStaticTitle) {
|
|
2252
|
+
document.title = window.ManyoyoChatBehavior.buildDocumentTitle('');
|
|
2253
|
+
}
|
|
2262
2254
|
if (isComposerMode()) {
|
|
2263
2255
|
commandInput.value = '';
|
|
2264
2256
|
}
|
|
2265
|
-
} else {
|
|
2257
|
+
} else if (!useStaticTitle) {
|
|
2266
2258
|
const activeSession = getActiveSession();
|
|
2267
2259
|
document.title = window.ManyoyoChatBehavior.buildDocumentTitle(
|
|
2268
2260
|
activeSession && activeSession.agentName ? activeSession.agentName : state.active
|
|
@@ -2953,10 +2945,6 @@
|
|
|
2953
2945
|
});
|
|
2954
2946
|
}
|
|
2955
2947
|
|
|
2956
|
-
function createTreeMetaText(parts) {
|
|
2957
|
-
return (parts || []).filter(Boolean).join(' · ');
|
|
2958
|
-
}
|
|
2959
|
-
|
|
2960
2948
|
function getSessionCreatedTime(session) {
|
|
2961
2949
|
if (session && session.createdAt) {
|
|
2962
2950
|
const time = new Date(session.createdAt).getTime();
|
|
@@ -3368,7 +3356,7 @@
|
|
|
3368
3356
|
});
|
|
3369
3357
|
}
|
|
3370
3358
|
|
|
3371
|
-
function renderSessionTreeNodes(nodes, parentNode, ancestorHasNext
|
|
3359
|
+
function renderSessionTreeNodes(nodes, parentNode, ancestorHasNext) {
|
|
3372
3360
|
(Array.isArray(nodes) ? nodes : []).forEach(function (node, index) {
|
|
3373
3361
|
const isLastSibling = index === nodes.length - 1;
|
|
3374
3362
|
const item = createTreeItem({
|
|
@@ -3390,8 +3378,6 @@
|
|
|
3390
3378
|
|
|
3391
3379
|
if (node.kind === 'agent') {
|
|
3392
3380
|
item.button.dataset.sessionName = node.sessionName || '';
|
|
3393
|
-
item.button.style.setProperty('--item-index', String(itemCounter.value));
|
|
3394
|
-
itemCounter.value += 1;
|
|
3395
3381
|
state.sessionNodeMap.set(node.sessionName, item.button);
|
|
3396
3382
|
item.button.addEventListener('click', function () {
|
|
3397
3383
|
handleSessionItemClick(node.sessionName || '');
|
|
@@ -3409,7 +3395,7 @@
|
|
|
3409
3395
|
childrenNode.className = `tree-node-children tree-node-children-${node.kind}`;
|
|
3410
3396
|
childrenNode.setAttribute('role', 'group');
|
|
3411
3397
|
childrenNode.hidden = !node.expanded;
|
|
3412
|
-
renderSessionTreeNodes(node.children || [], childrenNode, ancestorHasNext.concat(!isLastSibling)
|
|
3398
|
+
renderSessionTreeNodes(node.children || [], childrenNode, ancestorHasNext.concat(!isLastSibling));
|
|
3413
3399
|
block.appendChild(childrenNode);
|
|
3414
3400
|
parentNode.appendChild(block);
|
|
3415
3401
|
|
|
@@ -3505,8 +3491,7 @@
|
|
|
3505
3491
|
|
|
3506
3492
|
const grouped = groupSessionsByDirectory(state.sessions);
|
|
3507
3493
|
const treeNodes = buildSessionTreeNodes(grouped);
|
|
3508
|
-
|
|
3509
|
-
renderSessionTreeNodes(treeNodes, sessionList, [], itemCounter);
|
|
3494
|
+
renderSessionTreeNodes(treeNodes, sessionList, []);
|
|
3510
3495
|
updateSidebarActiveSelection();
|
|
3511
3496
|
|
|
3512
3497
|
scrollActiveSessionIntoView();
|
|
@@ -4497,6 +4482,11 @@
|
|
|
4497
4482
|
return;
|
|
4498
4483
|
}
|
|
4499
4484
|
|
|
4485
|
+
// 窄屏(手机端):Enter 始终换行,发送需点击发送按钮
|
|
4486
|
+
if (isMobileLayout()) {
|
|
4487
|
+
return;
|
|
4488
|
+
}
|
|
4489
|
+
|
|
4500
4490
|
// Shift+Enter / Option(Alt)+Enter: 换行
|
|
4501
4491
|
if (event.shiftKey || event.altKey) {
|
|
4502
4492
|
return;
|
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
name="viewport"
|
|
7
7
|
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
|
8
8
|
/>
|
|
9
|
-
<title>MANYOYO Web
|
|
9
|
+
<title>MANYOYO Web</title>
|
|
10
10
|
<link rel="stylesheet" href="/auth/frontend/login.css" />
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<form class="card" id="loginForm">
|
|
14
14
|
<div class="badge">MANYOYO</div>
|
|
15
15
|
<h1>Web 登录</h1>
|
|
16
|
-
<p>登录后可访问容器会话与对话管理。</p>
|
|
17
16
|
<label for="username">用户名</label>
|
|
18
17
|
<input id="username" name="username" autocomplete="username" required />
|
|
19
18
|
<label for="password">密码</label>
|
package/lib/web/server.js
CHANGED
|
@@ -3458,6 +3458,44 @@ function loadTemplate(name) {
|
|
|
3458
3458
|
return fs.readFileSync(filePath, 'utf-8');
|
|
3459
3459
|
}
|
|
3460
3460
|
|
|
3461
|
+
function escapeHtmlText(value) {
|
|
3462
|
+
return String(value == null ? '' : value)
|
|
3463
|
+
.replace(/&/g, '&')
|
|
3464
|
+
.replace(/</g, '<')
|
|
3465
|
+
.replace(/>/g, '>');
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
// JSON.stringify 不转义 "</script>",且作为 String.replace 的第二个参数时 $&/$'/$` 等会被
|
|
3469
|
+
// 当成特殊替换模式重新解释;用替换函数 + 转义 "<" 双重防护,避免 serveTitle 里的内容
|
|
3470
|
+
// 提前闭合 <script> 标签或拼接进原始 HTML。
|
|
3471
|
+
function jsonForInlineScript(value) {
|
|
3472
|
+
return JSON.stringify(value).replace(/</g, '\\u003c');
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
function applyServeTitle(html, ctx) {
|
|
3476
|
+
if (typeof ctx.serveTitle !== 'string') {
|
|
3477
|
+
return html;
|
|
3478
|
+
}
|
|
3479
|
+
// serveTitle 已配置(含空字符串):固定 <title>,覆盖模板默认的 MANYOYO Web
|
|
3480
|
+
return html.replace('<title>MANYOYO Web</title>', () => `<title>${escapeHtmlText(ctx.serveTitle)}</title>`);
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
function renderIndexHtml(ctx) {
|
|
3484
|
+
let html = applyServeTitle(loadTemplate('app.html'), ctx);
|
|
3485
|
+
if (typeof ctx.serveTitle === 'string') {
|
|
3486
|
+
// 告知前端跳过按会话动态改写 document.title
|
|
3487
|
+
html = html.replace(
|
|
3488
|
+
'</head>',
|
|
3489
|
+
() => ` <script>window.__MANYOYO_SERVE_TITLE__ = ${jsonForInlineScript(ctx.serveTitle)};</script>\n</head>`
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
return html;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
function renderLoginHtml(ctx) {
|
|
3496
|
+
return applyServeTitle(loadTemplate('login.html'), ctx);
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3461
3499
|
function toPositiveInt(value, fallback) {
|
|
3462
3500
|
const parsed = Number.parseInt(value, 10);
|
|
3463
3501
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
@@ -3656,7 +3694,7 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
|
|
|
3656
3694
|
}
|
|
3657
3695
|
|
|
3658
3696
|
if (req.method === 'GET' && pathname === '/auth/login') {
|
|
3659
|
-
sendHtml(res, 200,
|
|
3697
|
+
sendHtml(res, 200, renderLoginHtml(ctx));
|
|
3660
3698
|
return true;
|
|
3661
3699
|
}
|
|
3662
3700
|
|
|
@@ -3712,7 +3750,7 @@ async function handleWebAuthRoutes(req, res, pathname, ctx, state) {
|
|
|
3712
3750
|
return false;
|
|
3713
3751
|
}
|
|
3714
3752
|
|
|
3715
|
-
function sendWebUnauthorized(res, pathname) {
|
|
3753
|
+
function sendWebUnauthorized(res, pathname, ctx) {
|
|
3716
3754
|
if (pathname.startsWith('/api/') || pathname.startsWith('/auth/')) {
|
|
3717
3755
|
sendJson(res, 401, { error: 'UNAUTHORIZED' });
|
|
3718
3756
|
return;
|
|
@@ -3724,7 +3762,7 @@ function sendWebUnauthorized(res, pathname) {
|
|
|
3724
3762
|
sendHtml(
|
|
3725
3763
|
res,
|
|
3726
3764
|
401,
|
|
3727
|
-
|
|
3765
|
+
renderLoginHtml(ctx),
|
|
3728
3766
|
{ 'Set-Cookie': getWebAuthClearCookie() }
|
|
3729
3767
|
);
|
|
3730
3768
|
}
|
|
@@ -4498,6 +4536,7 @@ async function startWebServer(options) {
|
|
|
4498
4536
|
authUser: options.authUser,
|
|
4499
4537
|
authPass: options.authPass,
|
|
4500
4538
|
authPassAuto: options.authPassAuto,
|
|
4539
|
+
serveTitle: typeof options.serveTitle === 'string' ? options.serveTitle : null,
|
|
4501
4540
|
dockerCmd: options.dockerCmd,
|
|
4502
4541
|
hostPath: options.hostPath,
|
|
4503
4542
|
containerPath: options.containerPath,
|
|
@@ -4574,12 +4613,12 @@ async function startWebServer(options) {
|
|
|
4574
4613
|
|
|
4575
4614
|
const authSession = getWebAuthSession(state, req);
|
|
4576
4615
|
if (!authSession) {
|
|
4577
|
-
sendWebUnauthorized(res, pathname);
|
|
4616
|
+
sendWebUnauthorized(res, pathname, ctx);
|
|
4578
4617
|
return;
|
|
4579
4618
|
}
|
|
4580
4619
|
|
|
4581
4620
|
if (req.method === 'GET' && pathname === '/') {
|
|
4582
|
-
sendHtml(res, 200,
|
|
4621
|
+
sendHtml(res, 200, renderIndexHtml(ctx));
|
|
4583
4622
|
return;
|
|
4584
4623
|
}
|
|
4585
4624
|
|
package/manyoyo.example.json
CHANGED
|
@@ -44,6 +44,13 @@
|
|
|
44
44
|
"serverUser": "admin",
|
|
45
45
|
"serverPass": "change-this-password",
|
|
46
46
|
|
|
47
|
+
// serve 命令专属配置(覆盖型:runs.<name>.serve > 全局配置)
|
|
48
|
+
// 不设置 serve 字段(或不设置 title 字段)时,网页 <title> 按当前会话 Agent 名动态显示
|
|
49
|
+
// 一旦设置 title,则固定显示该值(不再随会话变化),留空字符串 "" 即固定显示为空
|
|
50
|
+
// "serve": {
|
|
51
|
+
// "title": "My MANYOYO"
|
|
52
|
+
// },
|
|
53
|
+
|
|
47
54
|
// 可选插件(manyoyo playwright / manyoyo plugin playwright)
|
|
48
55
|
"plugins": {
|
|
49
56
|
"playwright": {
|