@xcanwin/manyoyo 5.7.4 → 5.7.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/lib/web/frontend/app.css +98 -5
- package/lib/web/frontend/app.html +1 -0
- package/lib/web/frontend/app.js +427 -22
- package/lib/web/server.js +55 -0
- package/package.json +1 -1
package/lib/web/frontend/app.css
CHANGED
|
@@ -536,6 +536,60 @@ textarea:focus-visible {
|
|
|
536
536
|
gap: 8px;
|
|
537
537
|
}
|
|
538
538
|
|
|
539
|
+
button.tree-toggle {
|
|
540
|
+
width: 100%;
|
|
541
|
+
text-align: left;
|
|
542
|
+
color: var(--text);
|
|
543
|
+
background: rgba(255, 250, 242, 0.92);
|
|
544
|
+
border-color: rgba(181, 146, 99, 0.38);
|
|
545
|
+
padding: 10px 12px;
|
|
546
|
+
display: flex;
|
|
547
|
+
align-items: flex-start;
|
|
548
|
+
justify-content: space-between;
|
|
549
|
+
gap: 12px;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
button.tree-toggle:hover {
|
|
553
|
+
transform: none;
|
|
554
|
+
background: #fff6eb;
|
|
555
|
+
border-color: #d1aa7f;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
button.tree-toggle:active {
|
|
559
|
+
transform: none;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
button.tree-toggle:focus-visible {
|
|
563
|
+
outline: none;
|
|
564
|
+
box-shadow: 0 0 0 3px rgba(196, 85, 31, 0.14);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.tree-toggle-main {
|
|
568
|
+
min-width: 0;
|
|
569
|
+
display: flex;
|
|
570
|
+
flex-direction: column;
|
|
571
|
+
gap: 4px;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.tree-toggle-meta {
|
|
575
|
+
color: var(--muted);
|
|
576
|
+
font-size: 11px;
|
|
577
|
+
line-height: 1.45;
|
|
578
|
+
word-break: break-word;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.tree-toggle-caret {
|
|
582
|
+
color: #8c7257;
|
|
583
|
+
font-size: 18px;
|
|
584
|
+
line-height: 1;
|
|
585
|
+
flex-shrink: 0;
|
|
586
|
+
transition: transform 140ms ease;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.tree-toggle[aria-expanded="true"] .tree-toggle-caret {
|
|
590
|
+
transform: rotate(90deg);
|
|
591
|
+
}
|
|
592
|
+
|
|
539
593
|
.workbench-group-head {
|
|
540
594
|
padding: 10px 12px;
|
|
541
595
|
border: 1px solid rgba(181, 146, 99, 0.38);
|
|
@@ -543,6 +597,22 @@ textarea:focus-visible {
|
|
|
543
597
|
background: rgba(255, 250, 242, 0.92);
|
|
544
598
|
}
|
|
545
599
|
|
|
600
|
+
.workbench-group.has-active .workbench-group-head,
|
|
601
|
+
.container-card.has-active {
|
|
602
|
+
border-color: #c68d5a;
|
|
603
|
+
box-shadow: 0 0 0 2px rgba(196, 85, 31, 0.08);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.workbench-group-body {
|
|
607
|
+
display: flex;
|
|
608
|
+
flex-direction: column;
|
|
609
|
+
gap: 8px;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.workbench-group-body[hidden] {
|
|
613
|
+
display: none;
|
|
614
|
+
}
|
|
615
|
+
|
|
546
616
|
.workbench-group-kicker,
|
|
547
617
|
.container-card-kicker {
|
|
548
618
|
color: var(--muted);
|
|
@@ -584,11 +654,8 @@ textarea:focus-visible {
|
|
|
584
654
|
gap: 10px;
|
|
585
655
|
}
|
|
586
656
|
|
|
587
|
-
.container-
|
|
588
|
-
|
|
589
|
-
display: flex;
|
|
590
|
-
flex-direction: column;
|
|
591
|
-
gap: 4px;
|
|
657
|
+
.container-toggle {
|
|
658
|
+
flex: 1;
|
|
592
659
|
}
|
|
593
660
|
|
|
594
661
|
.container-card-meta {
|
|
@@ -611,6 +678,10 @@ textarea:focus-visible {
|
|
|
611
678
|
gap: 6px;
|
|
612
679
|
}
|
|
613
680
|
|
|
681
|
+
.agent-list[hidden] {
|
|
682
|
+
display: none;
|
|
683
|
+
}
|
|
684
|
+
|
|
614
685
|
.agent-item {
|
|
615
686
|
text-align: left;
|
|
616
687
|
width: 100%;
|
|
@@ -1477,6 +1548,28 @@ details.trace-card > .trace-card-summary {
|
|
|
1477
1548
|
}
|
|
1478
1549
|
}
|
|
1479
1550
|
|
|
1551
|
+
.streaming-reply {
|
|
1552
|
+
border-color: var(--subaccent);
|
|
1553
|
+
box-shadow: 0 8px 16px rgba(15, 124, 114, 0.10);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
.streaming-cursor {
|
|
1557
|
+
display: inline-block;
|
|
1558
|
+
width: 7px;
|
|
1559
|
+
height: 16px;
|
|
1560
|
+
margin-left: 2px;
|
|
1561
|
+
vertical-align: text-bottom;
|
|
1562
|
+
background: var(--subaccent);
|
|
1563
|
+
border-radius: 2px;
|
|
1564
|
+
animation: blink-cursor 640ms steps(2, start) infinite;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
@keyframes blink-cursor {
|
|
1568
|
+
to {
|
|
1569
|
+
visibility: hidden;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1480
1573
|
@keyframes shimmer {
|
|
1481
1574
|
100% {
|
|
1482
1575
|
transform: translateX(100%);
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
>更多</button>
|
|
59
59
|
<div class="header-actions" id="headerActions">
|
|
60
60
|
<button type="button" id="refreshBtn" class="secondary">刷新</button>
|
|
61
|
+
<button type="button" id="addAgentBtn" class="secondary">新建AGENT</button>
|
|
61
62
|
<button type="button" id="removeBtn" class="danger-outline">删除容器</button>
|
|
62
63
|
<button type="button" id="removeAllBtn" class="danger">删除对话</button>
|
|
63
64
|
</div>
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -63,6 +63,12 @@
|
|
|
63
63
|
createRuns: {},
|
|
64
64
|
sessionNodeMap: new Map(),
|
|
65
65
|
sessionRenderMode: 'empty',
|
|
66
|
+
sidebarTreeLoaded: false,
|
|
67
|
+
sidebarTree: {
|
|
68
|
+
directories: {},
|
|
69
|
+
containers: {}
|
|
70
|
+
},
|
|
71
|
+
pendingActiveSessionScroll: false,
|
|
66
72
|
directoryPicker: {
|
|
67
73
|
open: false,
|
|
68
74
|
loading: false,
|
|
@@ -110,6 +116,7 @@
|
|
|
110
116
|
const viewDetailBtn = document.getElementById('viewDetailBtn');
|
|
111
117
|
const viewConfigBtn = document.getElementById('viewConfigBtn');
|
|
112
118
|
const viewCheckBtn = document.getElementById('viewCheckBtn');
|
|
119
|
+
const addAgentBtn = document.getElementById('addAgentBtn');
|
|
113
120
|
const mobileSidebarClose = document.getElementById('mobileSidebarClose');
|
|
114
121
|
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
|
115
122
|
const openConfigBtn = document.getElementById('openConfigBtn');
|
|
@@ -205,12 +212,63 @@
|
|
|
205
212
|
const GEMINI_YOLO_FLAG = '--yolo';
|
|
206
213
|
const CODEX_DANGEROUS_FLAG = '--dangerously-bypass-approvals-and-sandbox';
|
|
207
214
|
const OPENCODE_PERMISSION_KEY = 'OPENCODE_PERMISSION=';
|
|
215
|
+
const SIDEBAR_TREE_STORAGE_KEY = 'manyoyo.web.sidebarTree.v1';
|
|
208
216
|
const markdownRenderer = window.ManyoyoMarkdown
|
|
209
217
|
&& typeof window.ManyoyoMarkdown.shouldRenderMessage === 'function'
|
|
210
218
|
&& typeof window.ManyoyoMarkdown.render === 'function'
|
|
211
219
|
? window.ManyoyoMarkdown
|
|
212
220
|
: null;
|
|
213
221
|
|
|
222
|
+
function normalizeBooleanMap(source) {
|
|
223
|
+
const result = {};
|
|
224
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
Object.keys(source).forEach(function (key) {
|
|
228
|
+
if (typeof source[key] === 'boolean') {
|
|
229
|
+
result[String(key)] = source[key];
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function loadSidebarTreeState() {
|
|
236
|
+
if (state.sidebarTreeLoaded) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
state.sidebarTreeLoaded = true;
|
|
240
|
+
try {
|
|
241
|
+
if (!window.localStorage) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const raw = window.localStorage.getItem(SIDEBAR_TREE_STORAGE_KEY);
|
|
245
|
+
if (!raw) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const parsed = JSON.parse(raw);
|
|
249
|
+
state.sidebarTree = {
|
|
250
|
+
directories: normalizeBooleanMap(parsed && parsed.directories),
|
|
251
|
+
containers: normalizeBooleanMap(parsed && parsed.containers)
|
|
252
|
+
};
|
|
253
|
+
} catch (e) {
|
|
254
|
+
state.sidebarTree = {
|
|
255
|
+
directories: {},
|
|
256
|
+
containers: {}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function persistSidebarTreeState() {
|
|
262
|
+
try {
|
|
263
|
+
if (!window.localStorage) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
window.localStorage.setItem(SIDEBAR_TREE_STORAGE_KEY, JSON.stringify(state.sidebarTree));
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// 忽略浏览器存储异常,避免影响主流程
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
214
272
|
function appendPlainMessageContent(bubble, content) {
|
|
215
273
|
const pre = document.createElement('pre');
|
|
216
274
|
pre.textContent = content == null ? '' : String(content);
|
|
@@ -412,6 +470,9 @@
|
|
|
412
470
|
function roleName(role, message) {
|
|
413
471
|
if (role === 'user') return '我';
|
|
414
472
|
if (role === 'assistant') {
|
|
473
|
+
if (message && message.streamingReply) {
|
|
474
|
+
return 'AGENT 实时回复';
|
|
475
|
+
}
|
|
415
476
|
if (message && message.streamTrace) {
|
|
416
477
|
return 'AGENT 过程';
|
|
417
478
|
}
|
|
@@ -459,6 +520,132 @@
|
|
|
459
520
|
});
|
|
460
521
|
}
|
|
461
522
|
|
|
523
|
+
function getSessionDirectoryPath(session) {
|
|
524
|
+
return String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function getSessionContainerName(session) {
|
|
528
|
+
return String(session && session.containerName ? session.containerName : '').trim();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function findSessionByName(sessionName) {
|
|
532
|
+
const target = String(sessionName || '').trim();
|
|
533
|
+
if (!target) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
return state.sessions.find(function (session) {
|
|
537
|
+
return session && session.name === target;
|
|
538
|
+
}) || null;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function pruneSidebarTreeState() {
|
|
542
|
+
loadSidebarTreeState();
|
|
543
|
+
|
|
544
|
+
const validDirectories = new Set(state.sessions.map(getSessionDirectoryPath));
|
|
545
|
+
const validContainers = new Set(state.sessions.map(getSessionContainerName).filter(Boolean));
|
|
546
|
+
let changed = false;
|
|
547
|
+
|
|
548
|
+
Object.keys(state.sidebarTree.directories).forEach(function (key) {
|
|
549
|
+
if (!validDirectories.has(key)) {
|
|
550
|
+
delete state.sidebarTree.directories[key];
|
|
551
|
+
changed = true;
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
Object.keys(state.sidebarTree.containers).forEach(function (key) {
|
|
556
|
+
if (!validContainers.has(key)) {
|
|
557
|
+
delete state.sidebarTree.containers[key];
|
|
558
|
+
changed = true;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
if (changed) {
|
|
563
|
+
persistSidebarTreeState();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function setSidebarDirectoryExpanded(directoryPath, expanded, options) {
|
|
568
|
+
const path = String(directoryPath || '').trim();
|
|
569
|
+
if (!path) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
loadSidebarTreeState();
|
|
573
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
574
|
+
if (state.sidebarTree.directories[path] === expanded) {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
state.sidebarTree.directories[path] = expanded;
|
|
578
|
+
if (opts.persist !== false) {
|
|
579
|
+
persistSidebarTreeState();
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function setSidebarContainerExpanded(containerName, expanded, options) {
|
|
585
|
+
const name = String(containerName || '').trim();
|
|
586
|
+
if (!name) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
loadSidebarTreeState();
|
|
590
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
591
|
+
if (state.sidebarTree.containers[name] === expanded) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
state.sidebarTree.containers[name] = expanded;
|
|
595
|
+
if (opts.persist !== false) {
|
|
596
|
+
persistSidebarTreeState();
|
|
597
|
+
}
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function ensureSessionPathExpanded(sessionName, options) {
|
|
602
|
+
const session = findSessionByName(sessionName);
|
|
603
|
+
if (!session) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
607
|
+
const directoryPath = getSessionDirectoryPath(session);
|
|
608
|
+
const containerName = getSessionContainerName(session);
|
|
609
|
+
const changedDirectory = setSidebarDirectoryExpanded(directoryPath, true, { persist: false });
|
|
610
|
+
const changedContainer = setSidebarContainerExpanded(containerName, true, { persist: false });
|
|
611
|
+
if ((changedDirectory || changedContainer) && opts.persist !== false) {
|
|
612
|
+
persistSidebarTreeState();
|
|
613
|
+
}
|
|
614
|
+
return changedDirectory || changedContainer;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function directoryContainsActiveSession(directoryGroup) {
|
|
618
|
+
const groups = directoryGroup && Array.isArray(directoryGroup.containers) ? directoryGroup.containers : [];
|
|
619
|
+
return groups.some(function (containerGroup) {
|
|
620
|
+
return containerContainsActiveSession(containerGroup);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function containerContainsActiveSession(containerGroup) {
|
|
625
|
+
const sessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
|
|
626
|
+
return sessions.some(function (session) {
|
|
627
|
+
return session && session.name === state.active;
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function isDirectoryExpanded(directoryGroup) {
|
|
632
|
+
loadSidebarTreeState();
|
|
633
|
+
const key = String(directoryGroup && directoryGroup.path ? directoryGroup.path : '').trim();
|
|
634
|
+
if (key && typeof state.sidebarTree.directories[key] === 'boolean') {
|
|
635
|
+
return state.sidebarTree.directories[key];
|
|
636
|
+
}
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function isContainerExpanded(containerGroup) {
|
|
641
|
+
loadSidebarTreeState();
|
|
642
|
+
const key = String(containerGroup && containerGroup.containerName ? containerGroup.containerName : '').trim();
|
|
643
|
+
if (key && typeof state.sidebarTree.containers[key] === 'boolean') {
|
|
644
|
+
return state.sidebarTree.containers[key];
|
|
645
|
+
}
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
|
|
462
649
|
function normalizeSlashPath(value) {
|
|
463
650
|
return String(value || '').replace(/\\/g, '/');
|
|
464
651
|
}
|
|
@@ -1524,6 +1711,9 @@
|
|
|
1524
1711
|
const activeAgentRunning = isAgentRunActiveForSession(state.active);
|
|
1525
1712
|
const busy = state.loadingSessions || state.loadingMessages || state.sending;
|
|
1526
1713
|
refreshBtn.disabled = busy;
|
|
1714
|
+
if (addAgentBtn) {
|
|
1715
|
+
addAgentBtn.disabled = !state.active || busy;
|
|
1716
|
+
}
|
|
1527
1717
|
removeBtn.disabled = !state.active || busy;
|
|
1528
1718
|
removeAllBtn.disabled = !state.active || busy;
|
|
1529
1719
|
sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && !agentEnabled);
|
|
@@ -1966,6 +2156,7 @@
|
|
|
1966
2156
|
disconnectTerminal('会话切换,终端已断开', true);
|
|
1967
2157
|
}
|
|
1968
2158
|
state.active = sessionName;
|
|
2159
|
+
ensureSessionPathExpanded(sessionName);
|
|
1969
2160
|
state.sessionDetail = null;
|
|
1970
2161
|
state.sessionDetailError = '';
|
|
1971
2162
|
if (isMobileLayout()) {
|
|
@@ -2044,13 +2235,74 @@
|
|
|
2044
2235
|
containerGroup.updatedAt = session.updatedAt;
|
|
2045
2236
|
}
|
|
2046
2237
|
});
|
|
2047
|
-
return Array.from(groups.values()).
|
|
2238
|
+
return Array.from(groups.values()).map(function (group) {
|
|
2239
|
+
group.containers = Array.from(group.containers.values()).sort(function (a, b) {
|
|
2240
|
+
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2241
|
+
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2242
|
+
return timeB - timeA;
|
|
2243
|
+
});
|
|
2244
|
+
group.containers.forEach(function (containerGroup) {
|
|
2245
|
+
containerGroup.sessions.sort(function (a, b) {
|
|
2246
|
+
const timeA = a && a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2247
|
+
const timeB = b && b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2248
|
+
return timeB - timeA;
|
|
2249
|
+
});
|
|
2250
|
+
});
|
|
2251
|
+
return group;
|
|
2252
|
+
}).sort(function (a, b) {
|
|
2048
2253
|
const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
2049
2254
|
const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
2050
2255
|
return timeB - timeA;
|
|
2051
2256
|
});
|
|
2052
2257
|
}
|
|
2053
2258
|
|
|
2259
|
+
function createTreeMetaText(parts) {
|
|
2260
|
+
return (parts || []).filter(Boolean).join(' · ');
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
function createTreeToggle(options) {
|
|
2264
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
2265
|
+
const button = document.createElement('button');
|
|
2266
|
+
button.type = 'button';
|
|
2267
|
+
button.className = `tree-toggle ${opts.className || ''}`.trim();
|
|
2268
|
+
button.setAttribute('aria-expanded', opts.expanded ? 'true' : 'false');
|
|
2269
|
+
|
|
2270
|
+
const main = document.createElement('div');
|
|
2271
|
+
main.className = 'tree-toggle-main';
|
|
2272
|
+
|
|
2273
|
+
const kicker = document.createElement('div');
|
|
2274
|
+
kicker.className = opts.kickerClassName || 'workbench-group-kicker';
|
|
2275
|
+
kicker.textContent = opts.kicker || '';
|
|
2276
|
+
main.appendChild(kicker);
|
|
2277
|
+
|
|
2278
|
+
const title = document.createElement('div');
|
|
2279
|
+
title.className = opts.titleClassName || 'workbench-group-title';
|
|
2280
|
+
title.textContent = opts.title || '';
|
|
2281
|
+
main.appendChild(title);
|
|
2282
|
+
|
|
2283
|
+
const metaText = createTreeMetaText(opts.metaParts);
|
|
2284
|
+
if (metaText) {
|
|
2285
|
+
const meta = document.createElement('div');
|
|
2286
|
+
meta.className = 'tree-toggle-meta';
|
|
2287
|
+
meta.textContent = metaText;
|
|
2288
|
+
main.appendChild(meta);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
const caret = document.createElement('span');
|
|
2292
|
+
caret.className = 'tree-toggle-caret';
|
|
2293
|
+
caret.setAttribute('aria-hidden', 'true');
|
|
2294
|
+
caret.textContent = '›';
|
|
2295
|
+
|
|
2296
|
+
button.appendChild(main);
|
|
2297
|
+
button.appendChild(caret);
|
|
2298
|
+
|
|
2299
|
+
if (typeof opts.onClick === 'function') {
|
|
2300
|
+
button.addEventListener('click', opts.onClick);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
return button;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2054
2306
|
function createAgentRow(session, index) {
|
|
2055
2307
|
const status = sessionStatusInfo(session.status);
|
|
2056
2308
|
const btn = document.createElement('button');
|
|
@@ -2091,6 +2343,19 @@
|
|
|
2091
2343
|
return btn;
|
|
2092
2344
|
}
|
|
2093
2345
|
|
|
2346
|
+
function scrollActiveSessionIntoView() {
|
|
2347
|
+
if (!state.pendingActiveSessionScroll || !state.active) {
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
state.pendingActiveSessionScroll = false;
|
|
2351
|
+
const targetNode = state.sessionNodeMap.get(state.active);
|
|
2352
|
+
if (targetNode && typeof targetNode.scrollIntoView === 'function') {
|
|
2353
|
+
targetNode.scrollIntoView({
|
|
2354
|
+
block: 'nearest'
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2094
2359
|
function renderSessions() {
|
|
2095
2360
|
const containerCount = new Set(state.sessions.map(function (session) {
|
|
2096
2361
|
return session && session.containerName ? session.containerName : '';
|
|
@@ -2123,38 +2388,65 @@
|
|
|
2123
2388
|
let itemIndex = 0;
|
|
2124
2389
|
|
|
2125
2390
|
grouped.forEach(function (directoryGroup) {
|
|
2391
|
+
const directoryExpanded = isDirectoryExpanded(directoryGroup);
|
|
2392
|
+
const directoryHasActive = directoryContainsActiveSession(directoryGroup);
|
|
2126
2393
|
const group = document.createElement('section');
|
|
2127
2394
|
group.className = 'workbench-group';
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
groupHead
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2395
|
+
group.classList.toggle('has-active', directoryHasActive);
|
|
2396
|
+
|
|
2397
|
+
const groupHead = createTreeToggle({
|
|
2398
|
+
className: 'workbench-group-head',
|
|
2399
|
+
kickerClassName: 'workbench-group-kicker',
|
|
2400
|
+
titleClassName: 'workbench-group-title',
|
|
2401
|
+
kicker: '目录',
|
|
2402
|
+
title: directoryGroup.path,
|
|
2403
|
+
expanded: directoryExpanded,
|
|
2404
|
+
metaParts: [
|
|
2405
|
+
`${directoryGroup.containers.length} 个容器`,
|
|
2406
|
+
`${directoryGroup.containers.reduce(function (count, containerGroup) {
|
|
2407
|
+
return count + containerGroup.sessions.length;
|
|
2408
|
+
}, 0)} 个AGENT`,
|
|
2409
|
+
formatDateTime(directoryGroup.updatedAt) || '暂无更新'
|
|
2410
|
+
],
|
|
2411
|
+
onClick: function () {
|
|
2412
|
+
setSidebarDirectoryExpanded(directoryGroup.path, !directoryExpanded);
|
|
2413
|
+
renderSessions();
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2135
2416
|
group.appendChild(groupHead);
|
|
2136
2417
|
|
|
2137
2418
|
const containerStack = document.createElement('div');
|
|
2138
|
-
containerStack.className = 'container-stack';
|
|
2419
|
+
containerStack.className = 'container-stack workbench-group-body';
|
|
2420
|
+
containerStack.hidden = !directoryExpanded;
|
|
2139
2421
|
|
|
2140
|
-
|
|
2422
|
+
directoryGroup.containers.forEach(function (containerGroup) {
|
|
2423
|
+
const containerExpanded = isContainerExpanded(containerGroup);
|
|
2424
|
+
const containerHasActive = containerContainsActiveSession(containerGroup);
|
|
2141
2425
|
const status = sessionStatusInfo(containerGroup.status);
|
|
2142
2426
|
const containerCard = document.createElement('section');
|
|
2143
2427
|
containerCard.className = 'container-card';
|
|
2428
|
+
containerCard.classList.toggle('has-active', containerHasActive);
|
|
2144
2429
|
|
|
2145
2430
|
const containerHead = document.createElement('div');
|
|
2146
2431
|
containerHead.className = 'container-card-head';
|
|
2147
2432
|
|
|
2148
|
-
const containerInfo =
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2433
|
+
const containerInfo = createTreeToggle({
|
|
2434
|
+
className: 'container-toggle',
|
|
2435
|
+
kickerClassName: 'container-card-kicker',
|
|
2436
|
+
titleClassName: 'container-card-title',
|
|
2437
|
+
kicker: '容器',
|
|
2438
|
+
title: containerGroup.containerName,
|
|
2439
|
+
expanded: containerExpanded,
|
|
2440
|
+
metaParts: [
|
|
2441
|
+
status.label,
|
|
2442
|
+
`${containerGroup.sessions.length} 个AGENT`,
|
|
2443
|
+
formatDateTime(containerGroup.updatedAt) || '暂无更新'
|
|
2444
|
+
],
|
|
2445
|
+
onClick: function () {
|
|
2446
|
+
setSidebarContainerExpanded(containerGroup.containerName, !containerExpanded);
|
|
2447
|
+
renderSessions();
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
2158
2450
|
|
|
2159
2451
|
const addAgentBtn = document.createElement('button');
|
|
2160
2452
|
addAgentBtn.type = 'button';
|
|
@@ -2170,6 +2462,7 @@
|
|
|
2170
2462
|
|
|
2171
2463
|
const agentList = document.createElement('div');
|
|
2172
2464
|
agentList.className = 'agent-list';
|
|
2465
|
+
agentList.hidden = !containerExpanded;
|
|
2173
2466
|
containerGroup.sessions.forEach(function (session) {
|
|
2174
2467
|
const row = createAgentRow(session, itemIndex);
|
|
2175
2468
|
state.sessionNodeMap.set(session.name, row);
|
|
@@ -2183,6 +2476,8 @@
|
|
|
2183
2476
|
group.appendChild(containerStack);
|
|
2184
2477
|
sessionList.appendChild(group);
|
|
2185
2478
|
});
|
|
2479
|
+
|
|
2480
|
+
scrollActiveSessionIntoView();
|
|
2186
2481
|
}
|
|
2187
2482
|
|
|
2188
2483
|
function renderMessagesLoading() {
|
|
@@ -2216,6 +2511,11 @@
|
|
|
2216
2511
|
}
|
|
2217
2512
|
|
|
2218
2513
|
function getMessageRenderKey(msg, index) {
|
|
2514
|
+
if (msg && msg.id && msg.streamingReply) {
|
|
2515
|
+
const content = msg.content ? String(msg.content) : '';
|
|
2516
|
+
const timestamp = msg.timestamp ? String(msg.timestamp) : '';
|
|
2517
|
+
return `id:${msg.id}|streaming|${timestamp}|${content}`;
|
|
2518
|
+
}
|
|
2219
2519
|
if (msg && msg.id && msg.streamTrace) {
|
|
2220
2520
|
const content = msg.content ? String(msg.content) : '';
|
|
2221
2521
|
const timestamp = msg.timestamp ? String(msg.timestamp) : '';
|
|
@@ -2267,14 +2567,39 @@
|
|
|
2267
2567
|
const bubble = document.createElement('div');
|
|
2268
2568
|
bubble.className = 'bubble';
|
|
2269
2569
|
|
|
2570
|
+
const isStreamingReply = Boolean(msg && msg.streamingReply);
|
|
2270
2571
|
const shouldRenderStructuredTrace = Boolean(
|
|
2271
2572
|
msg
|
|
2272
2573
|
&& msg.streamTrace
|
|
2273
2574
|
&& Array.isArray(msg.traceEvents)
|
|
2274
2575
|
&& msg.traceEvents.length
|
|
2275
2576
|
);
|
|
2276
|
-
const shouldRenderMarkdown = Boolean(!msg.streamTrace && markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
|
|
2277
|
-
if (
|
|
2577
|
+
const shouldRenderMarkdown = Boolean(!msg.streamTrace && !msg.streamingReply && markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
|
|
2578
|
+
if (isStreamingReply) {
|
|
2579
|
+
bubble.classList.add('streaming-reply');
|
|
2580
|
+
var replyContent = String(msg.content || '');
|
|
2581
|
+
if (replyContent && markdownRenderer && typeof markdownRenderer.render === 'function') {
|
|
2582
|
+
var mdNode = document.createElement('div');
|
|
2583
|
+
mdNode.className = 'md-content';
|
|
2584
|
+
var rendered = '';
|
|
2585
|
+
try {
|
|
2586
|
+
rendered = String(markdownRenderer.render(replyContent) || '');
|
|
2587
|
+
} catch (e) {
|
|
2588
|
+
rendered = '';
|
|
2589
|
+
}
|
|
2590
|
+
if (rendered) {
|
|
2591
|
+
mdNode.innerHTML = rendered;
|
|
2592
|
+
bubble.appendChild(mdNode);
|
|
2593
|
+
} else {
|
|
2594
|
+
appendPlainMessageContent(bubble, replyContent);
|
|
2595
|
+
}
|
|
2596
|
+
} else {
|
|
2597
|
+
appendPlainMessageContent(bubble, replyContent);
|
|
2598
|
+
}
|
|
2599
|
+
var cursor = document.createElement('span');
|
|
2600
|
+
cursor.className = 'streaming-cursor';
|
|
2601
|
+
bubble.appendChild(cursor);
|
|
2602
|
+
} else if (shouldRenderStructuredTrace) {
|
|
2278
2603
|
appendStructuredTraceContent(bubble, msg);
|
|
2279
2604
|
} else if (shouldRenderMarkdown) {
|
|
2280
2605
|
const markdownNode = document.createElement('div');
|
|
@@ -2365,7 +2690,9 @@
|
|
|
2365
2690
|
}
|
|
2366
2691
|
|
|
2367
2692
|
function applySessionsSnapshot(rawSessions, preferredName) {
|
|
2693
|
+
const previousActive = state.active;
|
|
2368
2694
|
state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
|
|
2695
|
+
pruneSidebarTreeState();
|
|
2369
2696
|
|
|
2370
2697
|
if (typeof preferredName === 'string' && preferredName.trim()) {
|
|
2371
2698
|
state.active = preferredName.trim();
|
|
@@ -2379,6 +2706,10 @@
|
|
|
2379
2706
|
if (!state.active && state.sessions.length) {
|
|
2380
2707
|
state.active = state.sessions[0].name;
|
|
2381
2708
|
}
|
|
2709
|
+
if (state.active && state.active !== previousActive) {
|
|
2710
|
+
ensureSessionPathExpanded(state.active);
|
|
2711
|
+
state.pendingActiveSessionScroll = true;
|
|
2712
|
+
}
|
|
2382
2713
|
if (state.terminal.sessionName && state.terminal.sessionName !== state.active) {
|
|
2383
2714
|
disconnectTerminal('会话已变化,终端已断开', true);
|
|
2384
2715
|
}
|
|
@@ -2638,11 +2969,55 @@
|
|
|
2638
2969
|
});
|
|
2639
2970
|
}
|
|
2640
2971
|
|
|
2972
|
+
function appendStreamingReplyLocal(sessionName) {
|
|
2973
|
+
var replyMessage = {
|
|
2974
|
+
id: createLocalMessageId('local-streaming-reply'),
|
|
2975
|
+
role: 'assistant',
|
|
2976
|
+
content: '',
|
|
2977
|
+
timestamp: new Date().toISOString(),
|
|
2978
|
+
mode: 'agent',
|
|
2979
|
+
streamingReply: true
|
|
2980
|
+
};
|
|
2981
|
+
if (state.active === sessionName) {
|
|
2982
|
+
state.messages.push(replyMessage);
|
|
2983
|
+
}
|
|
2984
|
+
return replyMessage.id;
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
function updateStreamingReplyLocal(sessionName, replyMessageId, content) {
|
|
2988
|
+
if (state.active !== sessionName) {
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
for (var i = state.messages.length - 1; i >= 0; i -= 1) {
|
|
2992
|
+
var message = state.messages[i];
|
|
2993
|
+
if (!message || String(message.id || '') !== String(replyMessageId || '')) {
|
|
2994
|
+
continue;
|
|
2995
|
+
}
|
|
2996
|
+
message.content = String(content || '');
|
|
2997
|
+
message.timestamp = new Date().toISOString();
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
function removeStreamingReplyLocal(sessionName, replyMessageId) {
|
|
3003
|
+
if (state.active !== sessionName) {
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
for (var i = state.messages.length - 1; i >= 0; i -= 1) {
|
|
3007
|
+
var message = state.messages[i];
|
|
3008
|
+
if (message && String(message.id || '') === String(replyMessageId || '') && message.streamingReply) {
|
|
3009
|
+
state.messages.splice(i, 1);
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
|
|
2641
3015
|
async function sendAgentPromptStream(sessionName, inputText, pendingMessage) {
|
|
2642
3016
|
const traceMessageId = appendAgentTraceMessageLocal(sessionName);
|
|
2643
3017
|
const traceLines = ['[执行过程]', '等待 Agent 启动…'];
|
|
2644
3018
|
let finalResult = null;
|
|
2645
3019
|
let streamError = null;
|
|
3020
|
+
let streamingReplyId = null;
|
|
2646
3021
|
|
|
2647
3022
|
state.agentRun.active = true;
|
|
2648
3023
|
state.agentRun.sessionName = sessionName;
|
|
@@ -2692,6 +3067,20 @@
|
|
|
2692
3067
|
pushTraceLine(event.text || '', event.traceEvent || null);
|
|
2693
3068
|
return;
|
|
2694
3069
|
}
|
|
3070
|
+
if (event.type === 'content_delta') {
|
|
3071
|
+
var content = String(event.content || '').trim();
|
|
3072
|
+
if (!content) {
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
if (!streamingReplyId) {
|
|
3076
|
+
streamingReplyId = appendStreamingReplyLocal(sessionName);
|
|
3077
|
+
}
|
|
3078
|
+
updateStreamingReplyLocal(sessionName, streamingReplyId, content);
|
|
3079
|
+
if (state.active === sessionName) {
|
|
3080
|
+
renderMessages(state.messages, { stickToBottom: true });
|
|
3081
|
+
}
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
2695
3084
|
if (event.type === 'result') {
|
|
2696
3085
|
finalResult = event;
|
|
2697
3086
|
if (event.interrupted) {
|
|
@@ -2711,6 +3100,9 @@
|
|
|
2711
3100
|
streamError = e;
|
|
2712
3101
|
}
|
|
2713
3102
|
} finally {
|
|
3103
|
+
if (streamingReplyId) {
|
|
3104
|
+
removeStreamingReplyLocal(sessionName, streamingReplyId);
|
|
3105
|
+
}
|
|
2714
3106
|
const pendingIndex = confirmPendingUserMessage(sessionName, pendingMessage.id);
|
|
2715
3107
|
if (pendingIndex >= 0 && pendingIndex < state.messageRenderKeys.length) {
|
|
2716
3108
|
if (pendingIndex < messagesNode.children.length) {
|
|
@@ -3099,6 +3491,18 @@
|
|
|
3099
3491
|
});
|
|
3100
3492
|
}
|
|
3101
3493
|
|
|
3494
|
+
if (addAgentBtn) {
|
|
3495
|
+
addAgentBtn.addEventListener('click', function () {
|
|
3496
|
+
const activeSession = getActiveSession();
|
|
3497
|
+
const targetContainer = activeSession ? getSessionContainerName(activeSession) : '';
|
|
3498
|
+
if (!targetContainer) {
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
closeMobileActionsMenu();
|
|
3502
|
+
createAgentSession(targetContainer);
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3102
3506
|
if (configModal) {
|
|
3103
3507
|
configModal.addEventListener('click', function (event) {
|
|
3104
3508
|
if (event.target === configModal && !state.configSaving) {
|
|
@@ -3223,6 +3627,7 @@
|
|
|
3223
3627
|
disconnectTerminal('', true);
|
|
3224
3628
|
});
|
|
3225
3629
|
|
|
3630
|
+
loadSidebarTreeState();
|
|
3226
3631
|
renderSessions();
|
|
3227
3632
|
renderMessages(state.messages);
|
|
3228
3633
|
setMobileSessionPanel(false);
|
package/lib/web/server.js
CHANGED
|
@@ -1053,6 +1053,48 @@ function prepareStructuredTraceEvents(agentProgram, payload, state) {
|
|
|
1053
1053
|
return [];
|
|
1054
1054
|
}
|
|
1055
1055
|
|
|
1056
|
+
function extractContentDeltaFromPayload(agentProgram, payload) {
|
|
1057
|
+
if (!payload || typeof payload !== 'object') {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
if (agentProgram === 'claude') {
|
|
1061
|
+
if (pickFirstString(payload.type) !== 'assistant') {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
const message = toPlainObject(payload.message);
|
|
1065
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
1066
|
+
const text = content
|
|
1067
|
+
.filter(item => item && typeof item === 'object' && item.type === 'text')
|
|
1068
|
+
.map(item => collectStructuredText(item))
|
|
1069
|
+
.filter(Boolean)
|
|
1070
|
+
.join('\n')
|
|
1071
|
+
.trim();
|
|
1072
|
+
if (!text) {
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
return { text, reset: true };
|
|
1076
|
+
}
|
|
1077
|
+
if (agentProgram === 'gemini' || agentProgram === 'opencode') {
|
|
1078
|
+
const eventType = pickFirstString(payload.type);
|
|
1079
|
+
if (eventType !== 'message') {
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
const role = pickFirstString(payload.role);
|
|
1083
|
+
if (role !== 'assistant') {
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
const text = collectStructuredText(payload.content);
|
|
1087
|
+
if (!text) {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
if (payload.delta === true) {
|
|
1091
|
+
return { text, reset: false };
|
|
1092
|
+
}
|
|
1093
|
+
return { text, reset: true };
|
|
1094
|
+
}
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1056
1098
|
function prepareCodexTraceEvent(payload) {
|
|
1057
1099
|
if (!payload || typeof payload !== 'object') {
|
|
1058
1100
|
return null;
|
|
@@ -2281,6 +2323,7 @@ async function execAgentInWebContainerStream(ctx, state, sessionRefOrContainerNa
|
|
|
2281
2323
|
const structuredTraceState = {
|
|
2282
2324
|
toolNamesById: new Map()
|
|
2283
2325
|
};
|
|
2326
|
+
let contentDeltaAccumulator = '';
|
|
2284
2327
|
function appendChunk(chunk, target) {
|
|
2285
2328
|
if (!chunk) return;
|
|
2286
2329
|
const text = chunk.toString('utf-8');
|
|
@@ -2318,6 +2361,18 @@ async function execAgentInWebContainerStream(ctx, state, sessionRefOrContainerNa
|
|
|
2318
2361
|
traceEvent
|
|
2319
2362
|
});
|
|
2320
2363
|
});
|
|
2364
|
+
const deltaContent = extractContentDeltaFromPayload(agentProgram, payload, structuredTraceState);
|
|
2365
|
+
if (deltaContent !== null) {
|
|
2366
|
+
if (deltaContent.reset) {
|
|
2367
|
+
contentDeltaAccumulator = deltaContent.text;
|
|
2368
|
+
} else {
|
|
2369
|
+
contentDeltaAccumulator += deltaContent.text;
|
|
2370
|
+
}
|
|
2371
|
+
onEvent({
|
|
2372
|
+
type: 'content_delta',
|
|
2373
|
+
content: contentDeltaAccumulator
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2321
2376
|
return;
|
|
2322
2377
|
}
|
|
2323
2378
|
if (agentProgram === 'codex' && (/^OpenAI Codex\b/.test(rawLine) || /^tokens used\b/i.test(rawLine))) {
|