@xcanwin/manyoyo 5.9.0 → 5.9.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/lib/web/frontend/app.css +5 -46
- package/lib/web/frontend/app.js +204 -57
- package/lib/web/server.js +144 -11
- package/package.json +1 -1
package/lib/web/frontend/app.css
CHANGED
|
@@ -1667,22 +1667,6 @@ body.command-mode .msg.origin-agent .bubble {
|
|
|
1667
1667
|
gap: 10px;
|
|
1668
1668
|
}
|
|
1669
1669
|
|
|
1670
|
-
.trace-summary {
|
|
1671
|
-
display: flex;
|
|
1672
|
-
flex-direction: column;
|
|
1673
|
-
gap: 6px;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
.trace-summary-line {
|
|
1677
|
-
padding: 7px 10px;
|
|
1678
|
-
border-radius: 8px;
|
|
1679
|
-
border: 1px dashed rgba(181, 146, 99, 0.45);
|
|
1680
|
-
background: rgba(255, 250, 242, 0.82);
|
|
1681
|
-
color: var(--muted);
|
|
1682
|
-
font-size: 12px;
|
|
1683
|
-
line-height: 1.45;
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
1670
|
.trace-flow {
|
|
1687
1671
|
display: flex;
|
|
1688
1672
|
flex-direction: column;
|
|
@@ -1722,6 +1706,11 @@ body.command-mode .msg.origin-agent .bubble {
|
|
|
1722
1706
|
border-left-color: var(--line-strong);
|
|
1723
1707
|
}
|
|
1724
1708
|
|
|
1709
|
+
.trace-card.trace-card-residual {
|
|
1710
|
+
background: rgba(255, 250, 242, 0.86);
|
|
1711
|
+
box-shadow: none;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1725
1714
|
.trace-card-summary {
|
|
1726
1715
|
list-style: none;
|
|
1727
1716
|
display: flex;
|
|
@@ -1735,36 +1724,6 @@ details.trace-card > .trace-card-summary {
|
|
|
1735
1724
|
cursor: pointer;
|
|
1736
1725
|
}
|
|
1737
1726
|
|
|
1738
|
-
.trace-card.trace-card-compact {
|
|
1739
|
-
border-left-width: 2px;
|
|
1740
|
-
border-radius: 999px;
|
|
1741
|
-
background: rgba(255, 251, 245, 0.78);
|
|
1742
|
-
box-shadow: none;
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
.trace-card.trace-card-compact .trace-card-summary {
|
|
1746
|
-
padding: 5px 10px;
|
|
1747
|
-
gap: 7px;
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
.trace-card.trace-card-compact .trace-card-badge {
|
|
1751
|
-
min-width: 0;
|
|
1752
|
-
padding: 2px 6px;
|
|
1753
|
-
background: rgba(31, 26, 20, 0.06);
|
|
1754
|
-
font-size: 10px;
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
.trace-card.trace-card-compact .trace-card-title {
|
|
1758
|
-
font-size: 11px;
|
|
1759
|
-
font-weight: 500;
|
|
1760
|
-
color: #6e6256;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
.trace-card.trace-card-compact .trace-card-phase {
|
|
1764
|
-
font-size: 10px;
|
|
1765
|
-
color: #8a7d70;
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
1727
|
.trace-card-summary::-webkit-details-marker {
|
|
1769
1728
|
display: none;
|
|
1770
1729
|
}
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -92,6 +92,10 @@
|
|
|
92
92
|
controller: null,
|
|
93
93
|
traceMessageId: ''
|
|
94
94
|
},
|
|
95
|
+
agentRecovery: {
|
|
96
|
+
sessionName: '',
|
|
97
|
+
timer: null
|
|
98
|
+
},
|
|
95
99
|
terminal: {
|
|
96
100
|
term: null,
|
|
97
101
|
fitAddon: null,
|
|
@@ -381,34 +385,6 @@
|
|
|
381
385
|
return 'neutral';
|
|
382
386
|
}
|
|
383
387
|
|
|
384
|
-
function shouldCompactTraceEvent(traceEvent) {
|
|
385
|
-
const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
|
|
386
|
-
const phase = event.phase ? String(event.phase) : '';
|
|
387
|
-
const kind = event.kind ? String(event.kind) : '';
|
|
388
|
-
if (phase !== 'completed') {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
return kind === 'command' || kind === 'mcp' || kind === 'tool';
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function buildCompactTraceText(traceEvent) {
|
|
395
|
-
const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
|
|
396
|
-
if (event.kind === 'command') {
|
|
397
|
-
const suffix = typeof event.exitCode === 'number'
|
|
398
|
-
? `exit ${event.exitCode}`
|
|
399
|
-
: (event.status ? String(event.status) : 'completed');
|
|
400
|
-
return `${event.command || event.text || '命令'} · ${suffix}`;
|
|
401
|
-
}
|
|
402
|
-
if (event.kind === 'mcp') {
|
|
403
|
-
const toolLabel = [event.server, event.tool].filter(Boolean).join('.') || event.text || 'MCP';
|
|
404
|
-
return event.argumentSummary ? `${toolLabel} · ${event.argumentSummary}` : toolLabel;
|
|
405
|
-
}
|
|
406
|
-
if (event.kind === 'tool') {
|
|
407
|
-
return event.toolName || event.text || '工具';
|
|
408
|
-
}
|
|
409
|
-
return event.text || '事件';
|
|
410
|
-
}
|
|
411
|
-
|
|
412
388
|
function appendTraceCardBody(cardBody, label, value) {
|
|
413
389
|
const text = stringifyPrettyJson(value).trim();
|
|
414
390
|
if (!text) {
|
|
@@ -432,12 +408,19 @@
|
|
|
432
408
|
|
|
433
409
|
function createTraceEventCard(traceEvent) {
|
|
434
410
|
const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
|
|
435
|
-
const compact = shouldCompactTraceEvent(event);
|
|
436
411
|
const bodyParts = [];
|
|
437
|
-
if (
|
|
412
|
+
if (event.kind === 'command' && event.command) {
|
|
438
413
|
bodyParts.push({ label: '命令', value: event.command });
|
|
414
|
+
if (typeof event.exitCode === 'number') {
|
|
415
|
+
bodyParts.push({ label: '退出码', value: String(event.exitCode) });
|
|
416
|
+
} else if (event.status) {
|
|
417
|
+
bodyParts.push({ label: '状态', value: event.status });
|
|
418
|
+
}
|
|
439
419
|
}
|
|
440
|
-
if (
|
|
420
|
+
if (event.kind === 'mcp') {
|
|
421
|
+
if (event.server || event.tool) {
|
|
422
|
+
bodyParts.push({ label: '工具', value: [event.server, event.tool].filter(Boolean).join('.') });
|
|
423
|
+
}
|
|
441
424
|
if (event.argumentSummary) {
|
|
442
425
|
bodyParts.push({ label: '参数摘要', value: event.argumentSummary });
|
|
443
426
|
}
|
|
@@ -451,7 +434,7 @@
|
|
|
451
434
|
bodyParts.push({ label: '错误', value: event.error });
|
|
452
435
|
}
|
|
453
436
|
}
|
|
454
|
-
if (
|
|
437
|
+
if (event.kind === 'tool' && event.toolName) {
|
|
455
438
|
bodyParts.push({ label: '工具', value: event.toolName });
|
|
456
439
|
}
|
|
457
440
|
if ((event.kind === 'agent_message' || event.kind === 'status' || event.kind === 'error') && event.detail) {
|
|
@@ -460,7 +443,7 @@
|
|
|
460
443
|
|
|
461
444
|
const hasBody = bodyParts.length > 0;
|
|
462
445
|
const card = document.createElement(hasBody ? 'details' : 'div');
|
|
463
|
-
card.className = 'trace-card trace-tone-' + resolveTraceTone(event)
|
|
446
|
+
card.className = 'trace-card trace-tone-' + resolveTraceTone(event);
|
|
464
447
|
if (hasBody && event.kind === 'error') {
|
|
465
448
|
card.open = true;
|
|
466
449
|
}
|
|
@@ -475,7 +458,7 @@
|
|
|
475
458
|
|
|
476
459
|
const title = document.createElement('span');
|
|
477
460
|
title.className = 'trace-card-title';
|
|
478
|
-
title.textContent =
|
|
461
|
+
title.textContent = event && event.text ? String(event.text) : '事件';
|
|
479
462
|
header.appendChild(title);
|
|
480
463
|
|
|
481
464
|
const phaseText = humanizeTracePhase(event);
|
|
@@ -494,9 +477,44 @@
|
|
|
494
477
|
bodyParts.forEach(function (part) {
|
|
495
478
|
appendTraceCardBody(body, part.label, part.value);
|
|
496
479
|
});
|
|
497
|
-
|
|
480
|
+
card.appendChild(body);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return card;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function resolveResidualTraceTone(line) {
|
|
487
|
+
const text = String(line || '').trim();
|
|
488
|
+
if (!text) {
|
|
489
|
+
return 'neutral';
|
|
490
|
+
}
|
|
491
|
+
if (text.startsWith('[stderr]') || text.startsWith('[错误]')) {
|
|
492
|
+
return 'error';
|
|
498
493
|
}
|
|
494
|
+
if (text.startsWith('[任务]') || text.includes('上下文模式') || text.includes('会话恢复') || text.includes('等待 Agent 启动')) {
|
|
495
|
+
return 'status';
|
|
496
|
+
}
|
|
497
|
+
return 'neutral';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function createResidualTraceCard(line) {
|
|
501
|
+
const card = document.createElement('div');
|
|
502
|
+
card.className = 'trace-card trace-tone-' + resolveResidualTraceTone(line) + ' trace-card-residual';
|
|
503
|
+
|
|
504
|
+
const header = document.createElement('div');
|
|
505
|
+
header.className = 'trace-card-summary';
|
|
506
|
+
|
|
507
|
+
const badge = document.createElement('span');
|
|
508
|
+
badge.className = 'trace-card-badge';
|
|
509
|
+
badge.textContent = '状态';
|
|
510
|
+
header.appendChild(badge);
|
|
511
|
+
|
|
512
|
+
const title = document.createElement('span');
|
|
513
|
+
title.className = 'trace-card-title';
|
|
514
|
+
title.textContent = String(line || '');
|
|
515
|
+
header.appendChild(title);
|
|
499
516
|
|
|
517
|
+
card.appendChild(header);
|
|
500
518
|
return card;
|
|
501
519
|
}
|
|
502
520
|
|
|
@@ -505,21 +523,11 @@
|
|
|
505
523
|
const container = document.createElement('div');
|
|
506
524
|
container.className = 'trace-structured';
|
|
507
525
|
|
|
508
|
-
const residualLines = buildStructuredTraceResidualLines(message);
|
|
509
|
-
if (residualLines.length) {
|
|
510
|
-
const summary = document.createElement('div');
|
|
511
|
-
summary.className = 'trace-summary';
|
|
512
|
-
residualLines.forEach(function (line) {
|
|
513
|
-
const item = document.createElement('div');
|
|
514
|
-
item.className = 'trace-summary-line';
|
|
515
|
-
item.textContent = line;
|
|
516
|
-
summary.appendChild(item);
|
|
517
|
-
});
|
|
518
|
-
container.appendChild(summary);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
526
|
const flow = document.createElement('div');
|
|
522
527
|
flow.className = 'trace-flow';
|
|
528
|
+
buildStructuredTraceResidualLines(message).forEach(function (line) {
|
|
529
|
+
flow.appendChild(createResidualTraceCard(line));
|
|
530
|
+
});
|
|
523
531
|
(Array.isArray(message && message.traceEvents) ? message.traceEvents : []).forEach(function (traceEvent) {
|
|
524
532
|
flow.appendChild(createTraceEventCard(traceEvent));
|
|
525
533
|
});
|
|
@@ -1334,6 +1342,114 @@
|
|
|
1334
1342
|
);
|
|
1335
1343
|
}
|
|
1336
1344
|
|
|
1345
|
+
function clearAgentRecoveryPoll() {
|
|
1346
|
+
if (state.agentRecovery && state.agentRecovery.timer) {
|
|
1347
|
+
window.clearTimeout(state.agentRecovery.timer);
|
|
1348
|
+
state.agentRecovery.timer = null;
|
|
1349
|
+
}
|
|
1350
|
+
if (state.agentRecovery) {
|
|
1351
|
+
state.agentRecovery.sessionName = '';
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function hasPendingAgentMessagesForSession(sessionName) {
|
|
1356
|
+
const targetSession = String(sessionName || '').trim();
|
|
1357
|
+
if (!targetSession || state.active !== targetSession) {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
return state.messages.some(function (message) {
|
|
1361
|
+
return Boolean(
|
|
1362
|
+
message
|
|
1363
|
+
&& message.mode === 'agent'
|
|
1364
|
+
&& (message.pending === true || message.streamingReply === true)
|
|
1365
|
+
);
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function scheduleAgentRecoveryPoll(sessionName, immediate) {
|
|
1370
|
+
const targetSession = String(sessionName || '').trim();
|
|
1371
|
+
if (!targetSession || state.active !== targetSession) {
|
|
1372
|
+
clearAgentRecoveryPoll();
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (isAgentRunActiveForSession(targetSession)) {
|
|
1376
|
+
clearAgentRecoveryPoll();
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
if (!hasPendingAgentMessagesForSession(targetSession)) {
|
|
1380
|
+
clearAgentRecoveryPoll();
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (state.agentRecovery.sessionName !== targetSession) {
|
|
1384
|
+
clearAgentRecoveryPoll();
|
|
1385
|
+
state.agentRecovery.sessionName = targetSession;
|
|
1386
|
+
}
|
|
1387
|
+
if (state.agentRecovery.timer) {
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
const delay = immediate === true ? 0 : 1500;
|
|
1392
|
+
state.agentRecovery.timer = window.setTimeout(async function () {
|
|
1393
|
+
state.agentRecovery.timer = null;
|
|
1394
|
+
if (state.active !== targetSession || isAgentRunActiveForSession(targetSession)) {
|
|
1395
|
+
clearAgentRecoveryPoll();
|
|
1396
|
+
syncUi();
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
await refreshSessionsSilent({ preferredName: targetSession });
|
|
1401
|
+
await Promise.all([
|
|
1402
|
+
loadMessagesForSession(targetSession, { silent: true }),
|
|
1403
|
+
loadSessionDetailForSession(targetSession)
|
|
1404
|
+
]);
|
|
1405
|
+
} catch (e) {
|
|
1406
|
+
// 静默失败,等待下一轮轮询
|
|
1407
|
+
}
|
|
1408
|
+
if (state.active === targetSession && !isAgentRunActiveForSession(targetSession) && hasPendingAgentMessagesForSession(targetSession)) {
|
|
1409
|
+
scheduleAgentRecoveryPoll(targetSession, false);
|
|
1410
|
+
} else {
|
|
1411
|
+
clearAgentRecoveryPoll();
|
|
1412
|
+
}
|
|
1413
|
+
syncUi();
|
|
1414
|
+
}, delay);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function syncAgentRecoveryForSession(sessionName) {
|
|
1418
|
+
const targetSession = String(sessionName || '').trim();
|
|
1419
|
+
if (!targetSession || state.active !== targetSession) {
|
|
1420
|
+
clearAgentRecoveryPoll();
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
if (isAgentRunActiveForSession(targetSession)) {
|
|
1424
|
+
clearAgentRecoveryPoll();
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
if (hasPendingAgentMessagesForSession(targetSession)) {
|
|
1428
|
+
scheduleAgentRecoveryPoll(targetSession, false);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (state.agentRecovery.sessionName === targetSession) {
|
|
1432
|
+
clearAgentRecoveryPoll();
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
async function recoverAgentRunFromServer(sessionName) {
|
|
1437
|
+
const targetSession = String(sessionName || '').trim();
|
|
1438
|
+
if (!targetSession) {
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
try {
|
|
1442
|
+
await refreshSessionsSilent({ preferredName: targetSession });
|
|
1443
|
+
await Promise.all([
|
|
1444
|
+
loadMessagesForSession(targetSession, { silent: true }),
|
|
1445
|
+
loadSessionDetailForSession(targetSession)
|
|
1446
|
+
]);
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
return false;
|
|
1449
|
+
}
|
|
1450
|
+
return hasPendingAgentMessagesForSession(targetSession);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1337
1453
|
function isActiveSessionHistoryOnly() {
|
|
1338
1454
|
const session = getActiveSession();
|
|
1339
1455
|
return sessionStatusInfo(session && session.status).tone === 'history';
|
|
@@ -2076,7 +2192,7 @@
|
|
|
2076
2192
|
});
|
|
2077
2193
|
}
|
|
2078
2194
|
|
|
2079
|
-
const activeAgentRunning = isAgentRunActiveForSession(state.active);
|
|
2195
|
+
const activeAgentRunning = isAgentRunActiveForSession(state.active) || hasPendingAgentMessagesForSession(state.active);
|
|
2080
2196
|
const busy = state.loadingSessions || state.loadingMessages || state.sending;
|
|
2081
2197
|
refreshBtn.disabled = busy;
|
|
2082
2198
|
if (addAgentBtn) {
|
|
@@ -2084,14 +2200,14 @@
|
|
|
2084
2200
|
}
|
|
2085
2201
|
removeBtn.disabled = !state.active || busy;
|
|
2086
2202
|
removeAllBtn.disabled = !state.active || busy;
|
|
2087
|
-
sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && !agentEnabled);
|
|
2203
|
+
sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && (!agentEnabled || activeAgentRunning));
|
|
2088
2204
|
if (stopBtn) {
|
|
2089
2205
|
stopBtn.disabled = !activityTab || !agentMode || !activeAgentRunning || state.agentRun.stopping;
|
|
2090
2206
|
}
|
|
2091
2207
|
if (agentTemplateBtn) {
|
|
2092
2208
|
agentTemplateBtn.disabled = !state.active || state.agentTemplateSaving;
|
|
2093
2209
|
}
|
|
2094
|
-
commandInput.disabled = !activityTab || !state.active || (agentMode && !agentEnabled);
|
|
2210
|
+
commandInput.disabled = !activityTab || !state.active || (agentMode && (!agentEnabled || activeAgentRunning));
|
|
2095
2211
|
if (commandInput) {
|
|
2096
2212
|
commandInput.placeholder = agentMode
|
|
2097
2213
|
? '输入提示词,例如:请帮我分析当前项目结构并给出重构建议'
|
|
@@ -3282,6 +3398,7 @@
|
|
|
3282
3398
|
state.messageRequestId += 1;
|
|
3283
3399
|
state.messages = [];
|
|
3284
3400
|
state.loadingMessages = false;
|
|
3401
|
+
clearAgentRecoveryPoll();
|
|
3285
3402
|
renderMessages(state.messages);
|
|
3286
3403
|
syncUi();
|
|
3287
3404
|
return;
|
|
@@ -3314,6 +3431,9 @@
|
|
|
3314
3431
|
state.messages = Array.isArray(data && data.messages) ? data.messages : [];
|
|
3315
3432
|
}
|
|
3316
3433
|
if (!requestError || targetSession === state.active) {
|
|
3434
|
+
if (targetSession === state.active) {
|
|
3435
|
+
syncAgentRecoveryForSession(targetSession);
|
|
3436
|
+
}
|
|
3317
3437
|
renderMessages(state.messages);
|
|
3318
3438
|
syncUi();
|
|
3319
3439
|
} else {
|
|
@@ -3451,6 +3571,7 @@
|
|
|
3451
3571
|
state.agentRun.stopping = false;
|
|
3452
3572
|
state.agentRun.sessionName = '';
|
|
3453
3573
|
state.agentRun.traceMessageId = '';
|
|
3574
|
+
clearAgentRecoveryPoll();
|
|
3454
3575
|
}
|
|
3455
3576
|
|
|
3456
3577
|
function appendAssistantMessageLocal(sessionName, result, mode) {
|
|
@@ -3524,6 +3645,7 @@
|
|
|
3524
3645
|
state.agentRun.stopping = false;
|
|
3525
3646
|
state.agentRun.controller = new window.AbortController();
|
|
3526
3647
|
state.agentRun.traceMessageId = traceMessageId;
|
|
3648
|
+
clearAgentRecoveryPoll();
|
|
3527
3649
|
renderMessages(state.messages, { stickToBottom: true });
|
|
3528
3650
|
syncUi();
|
|
3529
3651
|
|
|
@@ -3884,13 +4006,20 @@
|
|
|
3884
4006
|
});
|
|
3885
4007
|
}
|
|
3886
4008
|
} catch (e) {
|
|
3887
|
-
if (
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
4009
|
+
if (mode === 'agent') {
|
|
4010
|
+
const recovered = await recoverAgentRunFromServer(submitSession);
|
|
4011
|
+
if (!recovered && document.visibilityState !== 'hidden') {
|
|
4012
|
+
alert(e.message);
|
|
4013
|
+
}
|
|
4014
|
+
} else {
|
|
4015
|
+
if (state.active === submitSession) {
|
|
4016
|
+
state.messages = state.messages.filter(function (message) {
|
|
4017
|
+
return !(message && message.id === pendingMessage.id);
|
|
4018
|
+
});
|
|
4019
|
+
renderMessages(state.messages, { stickToBottom: true });
|
|
4020
|
+
}
|
|
4021
|
+
alert(e.message);
|
|
3892
4022
|
}
|
|
3893
|
-
alert(e.message);
|
|
3894
4023
|
} finally {
|
|
3895
4024
|
state.sending = false;
|
|
3896
4025
|
syncUi();
|
|
@@ -4153,6 +4282,24 @@
|
|
|
4153
4282
|
}
|
|
4154
4283
|
});
|
|
4155
4284
|
|
|
4285
|
+
document.addEventListener('visibilitychange', function () {
|
|
4286
|
+
if (document.visibilityState !== 'visible' || !state.active) {
|
|
4287
|
+
return;
|
|
4288
|
+
}
|
|
4289
|
+
recoverAgentRunFromServer(state.active).catch(function () {
|
|
4290
|
+
// 静默恢复失败不打断当前交互
|
|
4291
|
+
});
|
|
4292
|
+
});
|
|
4293
|
+
|
|
4294
|
+
window.addEventListener('focus', function () {
|
|
4295
|
+
if (!state.active) {
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
4298
|
+
recoverAgentRunFromServer(state.active).catch(function () {
|
|
4299
|
+
// 静默恢复失败不打断当前交互
|
|
4300
|
+
});
|
|
4301
|
+
});
|
|
4302
|
+
|
|
4156
4303
|
if (typeof MOBILE_LAYOUT_MEDIA.addEventListener === 'function') {
|
|
4157
4304
|
MOBILE_LAYOUT_MEDIA.addEventListener('change', onLayoutMediaChange);
|
|
4158
4305
|
} else if (typeof MOBILE_LAYOUT_MEDIA.addListener === 'function') {
|
package/lib/web/server.js
CHANGED
|
@@ -384,6 +384,10 @@ function listWebAgentSessions(history, options = {}) {
|
|
|
384
384
|
});
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
function createWebSessionMessageId() {
|
|
388
|
+
return `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
|
389
|
+
}
|
|
390
|
+
|
|
387
391
|
function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role, content, extra = {}) {
|
|
388
392
|
const sessionRef = typeof sessionRefOrContainerName === 'string'
|
|
389
393
|
? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
|
|
@@ -391,13 +395,14 @@ function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role,
|
|
|
391
395
|
const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
|
|
392
396
|
const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
|
|
393
397
|
const timestamp = new Date().toISOString();
|
|
394
|
-
|
|
395
|
-
id:
|
|
398
|
+
const message = {
|
|
399
|
+
id: createWebSessionMessageId(),
|
|
396
400
|
role,
|
|
397
401
|
content,
|
|
398
402
|
timestamp,
|
|
399
403
|
...extra
|
|
400
|
-
}
|
|
404
|
+
};
|
|
405
|
+
agentSession.messages.push(message);
|
|
401
406
|
|
|
402
407
|
if (agentSession.messages.length > WEB_HISTORY_MAX_MESSAGES) {
|
|
403
408
|
agentSession.messages = agentSession.messages.slice(-WEB_HISTORY_MAX_MESSAGES);
|
|
@@ -406,6 +411,57 @@ function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role,
|
|
|
406
411
|
agentSession.updatedAt = timestamp;
|
|
407
412
|
history.updatedAt = timestamp;
|
|
408
413
|
saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
|
|
414
|
+
return message;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function patchWebSessionMessage(webHistoryDir, sessionRefOrContainerName, messageId, patch) {
|
|
418
|
+
const sessionRef = typeof sessionRefOrContainerName === 'string'
|
|
419
|
+
? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
|
|
420
|
+
: sessionRefOrContainerName;
|
|
421
|
+
const targetId = String(messageId || '').trim();
|
|
422
|
+
if (!targetId) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
|
|
426
|
+
const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
|
|
427
|
+
const nextPatch = patch && typeof patch === 'object' ? patch : {};
|
|
428
|
+
const message = agentSession.messages.find(item => item && String(item.id || '') === targetId);
|
|
429
|
+
if (!message) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
Object.keys(nextPatch).forEach(key => {
|
|
433
|
+
if (key === 'id') {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
message[key] = nextPatch[key];
|
|
437
|
+
});
|
|
438
|
+
const timestamp = new Date().toISOString();
|
|
439
|
+
agentSession.updatedAt = timestamp;
|
|
440
|
+
history.updatedAt = timestamp;
|
|
441
|
+
saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
|
|
442
|
+
return message;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function removeWebSessionMessage(webHistoryDir, sessionRefOrContainerName, messageId) {
|
|
446
|
+
const sessionRef = typeof sessionRefOrContainerName === 'string'
|
|
447
|
+
? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
|
|
448
|
+
: sessionRefOrContainerName;
|
|
449
|
+
const targetId = String(messageId || '').trim();
|
|
450
|
+
if (!targetId) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
|
|
454
|
+
const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
|
|
455
|
+
const index = agentSession.messages.findIndex(item => item && String(item.id || '') === targetId);
|
|
456
|
+
if (index === -1) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
const removed = agentSession.messages.splice(index, 1)[0] || null;
|
|
460
|
+
const timestamp = new Date().toISOString();
|
|
461
|
+
agentSession.updatedAt = timestamp;
|
|
462
|
+
history.updatedAt = timestamp;
|
|
463
|
+
saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
|
|
464
|
+
return removed;
|
|
409
465
|
}
|
|
410
466
|
|
|
411
467
|
function setWebSessionAgentPromptCommand(webHistoryDir, containerName, agentPromptCommand) {
|
|
@@ -816,6 +872,7 @@ function hasAgentConversationHistory(history) {
|
|
|
816
872
|
for (const message of messages) {
|
|
817
873
|
if (!message || typeof message !== 'object') continue;
|
|
818
874
|
if (message.mode !== 'agent') continue;
|
|
875
|
+
if (message.pending === true) continue;
|
|
819
876
|
if (message.streamTrace === true) continue;
|
|
820
877
|
if (message.role === 'user' || message.role === 'assistant') {
|
|
821
878
|
return true;
|
|
@@ -838,6 +895,7 @@ function buildAgentPromptWithHistory(history, prompt) {
|
|
|
838
895
|
.filter(message => (
|
|
839
896
|
message
|
|
840
897
|
&& message.mode === 'agent'
|
|
898
|
+
&& message.pending !== true
|
|
841
899
|
&& message.streamTrace !== true
|
|
842
900
|
&& (message.role === 'user' || message.role === 'assistant')
|
|
843
901
|
))
|
|
@@ -1547,9 +1605,9 @@ function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, m
|
|
|
1547
1605
|
function appendWebAgentTraceMessage(webHistoryDir, sessionRefOrContainerName, content, extra = {}) {
|
|
1548
1606
|
const text = String(content || '').trim();
|
|
1549
1607
|
if (!text) {
|
|
1550
|
-
return;
|
|
1608
|
+
return null;
|
|
1551
1609
|
}
|
|
1552
|
-
appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, 'assistant', text, {
|
|
1610
|
+
return appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, 'assistant', text, {
|
|
1553
1611
|
mode: 'agent',
|
|
1554
1612
|
streamTrace: true,
|
|
1555
1613
|
...extra
|
|
@@ -3827,10 +3885,26 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
3827
3885
|
return;
|
|
3828
3886
|
}
|
|
3829
3887
|
|
|
3888
|
+
const userMessage = appendWebSessionMessage(state.webHistoryDir, sessionRef, 'user', prompt, {
|
|
3889
|
+
mode: 'agent',
|
|
3890
|
+
pending: true
|
|
3891
|
+
});
|
|
3892
|
+
const traceMessage = appendWebAgentTraceMessage(
|
|
3893
|
+
state.webHistoryDir,
|
|
3894
|
+
sessionRef,
|
|
3895
|
+
'[执行过程]\n等待 Agent 启动…',
|
|
3896
|
+
{
|
|
3897
|
+
traceEvents: [],
|
|
3898
|
+
pending: true
|
|
3899
|
+
}
|
|
3900
|
+
);
|
|
3901
|
+
|
|
3830
3902
|
let prepared = null;
|
|
3831
3903
|
try {
|
|
3832
3904
|
prepared = await prepareWebAgentExecution(ctx, state, sessionRef, prompt);
|
|
3833
3905
|
} catch (e) {
|
|
3906
|
+
removeWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id);
|
|
3907
|
+
removeWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id);
|
|
3834
3908
|
sendJson(res, 400, { error: e && e.message ? e.message : 'Agent 执行准备失败' });
|
|
3835
3909
|
return;
|
|
3836
3910
|
}
|
|
@@ -3838,8 +3912,9 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
3838
3912
|
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError } = prepared;
|
|
3839
3913
|
const traceLines = ['[执行过程]'];
|
|
3840
3914
|
const traceEvents = [];
|
|
3841
|
-
|
|
3842
|
-
|
|
3915
|
+
let streamingReplyMessageId = '';
|
|
3916
|
+
patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
|
|
3917
|
+
pending: true,
|
|
3843
3918
|
contextMode
|
|
3844
3919
|
});
|
|
3845
3920
|
|
|
@@ -3863,6 +3938,14 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
3863
3938
|
if (resumeAttempted) {
|
|
3864
3939
|
traceLines.push(resumeSucceeded ? '会话恢复成功' : '会话恢复失败,已回退到历史注入');
|
|
3865
3940
|
}
|
|
3941
|
+
patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
|
|
3942
|
+
content: traceLines.join('\n'),
|
|
3943
|
+
traceEvents: traceEvents.slice(),
|
|
3944
|
+
contextMode,
|
|
3945
|
+
resumeAttempted,
|
|
3946
|
+
resumeSucceeded,
|
|
3947
|
+
pending: true
|
|
3948
|
+
});
|
|
3866
3949
|
|
|
3867
3950
|
try {
|
|
3868
3951
|
const result = await execAgentInWebContainerStream(ctx, state, sessionRef, command, {
|
|
@@ -3873,18 +3956,57 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
3873
3956
|
if (event.traceEvent && typeof event.traceEvent === 'object') {
|
|
3874
3957
|
traceEvents.push(event.traceEvent);
|
|
3875
3958
|
}
|
|
3959
|
+
patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
|
|
3960
|
+
content: traceLines.join('\n'),
|
|
3961
|
+
traceEvents: traceEvents.slice(),
|
|
3962
|
+
pending: true
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
if (event && event.type === 'content_delta' && typeof event.content === 'string') {
|
|
3966
|
+
if (!streamingReplyMessageId) {
|
|
3967
|
+
const streamingReplyMessage = appendWebSessionMessage(
|
|
3968
|
+
state.webHistoryDir,
|
|
3969
|
+
sessionRef,
|
|
3970
|
+
'assistant',
|
|
3971
|
+
event.content,
|
|
3972
|
+
{
|
|
3973
|
+
mode: 'agent',
|
|
3974
|
+
streamingReply: true,
|
|
3975
|
+
pending: true
|
|
3976
|
+
}
|
|
3977
|
+
);
|
|
3978
|
+
streamingReplyMessageId = streamingReplyMessage && streamingReplyMessage.id
|
|
3979
|
+
? streamingReplyMessage.id
|
|
3980
|
+
: '';
|
|
3981
|
+
} else {
|
|
3982
|
+
patchWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId, {
|
|
3983
|
+
content: event.content,
|
|
3984
|
+
pending: true
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3876
3987
|
}
|
|
3877
3988
|
sendNdjson(res, event);
|
|
3878
3989
|
}
|
|
3879
3990
|
});
|
|
3880
3991
|
traceLines.push(result.interrupted === true ? '[任务] 已停止' : '[任务] 已完成');
|
|
3881
|
-
|
|
3992
|
+
patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
|
|
3993
|
+
pending: false,
|
|
3994
|
+
contextMode,
|
|
3995
|
+
resumeAttempted,
|
|
3996
|
+
resumeSucceeded
|
|
3997
|
+
});
|
|
3998
|
+
patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
|
|
3999
|
+
content: traceLines.join('\n'),
|
|
3882
4000
|
traceEvents,
|
|
3883
4001
|
contextMode,
|
|
3884
4002
|
resumeAttempted,
|
|
3885
4003
|
resumeSucceeded,
|
|
3886
|
-
interrupted: result.interrupted === true
|
|
4004
|
+
interrupted: result.interrupted === true,
|
|
4005
|
+
pending: false
|
|
3887
4006
|
});
|
|
4007
|
+
if (streamingReplyMessageId) {
|
|
4008
|
+
removeWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId);
|
|
4009
|
+
}
|
|
3888
4010
|
finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, {
|
|
3889
4011
|
contextMode,
|
|
3890
4012
|
resumeAttempted,
|
|
@@ -3902,13 +4024,24 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
3902
4024
|
});
|
|
3903
4025
|
} catch (e) {
|
|
3904
4026
|
traceLines.push(`[错误] ${e && e.message ? e.message : 'Agent 执行失败'}`);
|
|
3905
|
-
|
|
4027
|
+
patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
|
|
4028
|
+
pending: false,
|
|
4029
|
+
contextMode,
|
|
4030
|
+
resumeAttempted,
|
|
4031
|
+
resumeSucceeded
|
|
4032
|
+
});
|
|
4033
|
+
patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
|
|
4034
|
+
content: traceLines.join('\n'),
|
|
3906
4035
|
traceEvents,
|
|
3907
4036
|
contextMode,
|
|
3908
4037
|
resumeAttempted,
|
|
3909
4038
|
resumeSucceeded,
|
|
3910
|
-
interrupted: true
|
|
4039
|
+
interrupted: true,
|
|
4040
|
+
pending: false
|
|
3911
4041
|
});
|
|
4042
|
+
if (streamingReplyMessageId) {
|
|
4043
|
+
removeWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId);
|
|
4044
|
+
}
|
|
3912
4045
|
sendNdjson(res, {
|
|
3913
4046
|
type: 'error',
|
|
3914
4047
|
error: e && e.message ? e.message : 'Agent 执行失败'
|