@yeaft/webchat-agent 0.0.134 → 0.0.136
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/connection.js +27 -12
- package/crew.js +125 -10
- package/package.json +1 -1
package/connection.js
CHANGED
|
@@ -420,7 +420,10 @@ async function handleMessage(msg) {
|
|
|
420
420
|
const pid = process.pid;
|
|
421
421
|
const configDir = getConfigDir();
|
|
422
422
|
mkdirSync(configDir, { recursive: true });
|
|
423
|
+
const logDir = join(configDir, 'logs');
|
|
424
|
+
mkdirSync(logDir, { recursive: true });
|
|
423
425
|
const batPath = join(configDir, 'upgrade.bat');
|
|
426
|
+
const logPath = join(logDir, 'upgrade.log');
|
|
424
427
|
const isPm2 = !!process.env.pm_id;
|
|
425
428
|
const installDirWin = installDir.replace(/\//g, '\\');
|
|
426
429
|
|
|
@@ -430,14 +433,18 @@ async function handleMessage(msg) {
|
|
|
430
433
|
`set PID=${pid}`,
|
|
431
434
|
`set PKG=${pkgName}@latest`,
|
|
432
435
|
`set INSTALL_DIR=${installDirWin}`,
|
|
436
|
+
`set LOGFILE=${logPath}`,
|
|
433
437
|
`set MAX_WAIT=30`,
|
|
434
438
|
`set COUNT=0`,
|
|
439
|
+
'',
|
|
440
|
+
':: Redirect all output to log file',
|
|
441
|
+
'echo [Upgrade] Started at %date% %time% > "%LOGFILE%"',
|
|
435
442
|
':WAIT_LOOP',
|
|
436
443
|
'tasklist /FI "PID eq %PID%" 2>NUL | find /I "%PID%" >NUL',
|
|
437
444
|
'if errorlevel 1 goto PID_EXITED',
|
|
438
445
|
'set /A COUNT+=1',
|
|
439
446
|
'if %COUNT% GEQ %MAX_WAIT% (',
|
|
440
|
-
' echo [Upgrade] Timeout waiting for PID %PID% to exit after 60s',
|
|
447
|
+
' echo [Upgrade] Timeout waiting for PID %PID% to exit after 60s >> "%LOGFILE%"',
|
|
441
448
|
' goto PID_EXITED',
|
|
442
449
|
')',
|
|
443
450
|
'timeout /T 2 /NOBREAK >NUL',
|
|
@@ -446,10 +453,10 @@ async function handleMessage(msg) {
|
|
|
446
453
|
];
|
|
447
454
|
|
|
448
455
|
if (isPm2) {
|
|
449
|
-
// pm2 可能已自动重启旧代码(exit
|
|
456
|
+
// pm2 可能已自动重启旧代码(exit 触发),先 stop 释放文件锁
|
|
450
457
|
batLines.push(
|
|
451
|
-
'echo [Upgrade] Stopping pm2 agent to release file locks...',
|
|
452
|
-
'call pm2 stop yeaft-agent 2
|
|
458
|
+
'echo [Upgrade] Stopping pm2 agent to release file locks... >> "%LOGFILE%"',
|
|
459
|
+
'call pm2 stop yeaft-agent >> "%LOGFILE%" 2>&1',
|
|
453
460
|
'timeout /T 3 /NOBREAK >NUL',
|
|
454
461
|
);
|
|
455
462
|
}
|
|
@@ -459,23 +466,26 @@ async function handleMessage(msg) {
|
|
|
459
466
|
: 'cd /d "%INSTALL_DIR%" && call npm install %PKG%';
|
|
460
467
|
|
|
461
468
|
batLines.push(
|
|
462
|
-
'echo [Upgrade] Installing %PKG%...',
|
|
463
|
-
npmBatCmd
|
|
469
|
+
'echo [Upgrade] Installing %PKG%... >> "%LOGFILE%"',
|
|
470
|
+
`${npmBatCmd} >> "%LOGFILE%" 2>&1`,
|
|
464
471
|
'if errorlevel 1 (',
|
|
465
|
-
' echo [Upgrade] npm install failed with exit code %errorlevel%',
|
|
466
|
-
'
|
|
467
|
-
' echo [Upgrade] Successfully installed %PKG%',
|
|
472
|
+
' echo [Upgrade] npm install failed with exit code %errorlevel% >> "%LOGFILE%"',
|
|
473
|
+
' goto CLEANUP',
|
|
468
474
|
')',
|
|
475
|
+
'echo [Upgrade] Successfully installed %PKG% >> "%LOGFILE%"',
|
|
469
476
|
);
|
|
470
477
|
|
|
471
478
|
if (isPm2) {
|
|
472
479
|
batLines.push(
|
|
473
|
-
'echo [Upgrade] Starting agent via pm2...',
|
|
474
|
-
'call pm2 start yeaft-agent',
|
|
480
|
+
'echo [Upgrade] Starting agent via pm2... >> "%LOGFILE%"',
|
|
481
|
+
'call pm2 start yeaft-agent >> "%LOGFILE%" 2>&1',
|
|
475
482
|
);
|
|
476
483
|
}
|
|
477
484
|
|
|
478
|
-
batLines.push(
|
|
485
|
+
batLines.push(
|
|
486
|
+
':CLEANUP',
|
|
487
|
+
`del /F /Q "${batPath}"`,
|
|
488
|
+
);
|
|
479
489
|
|
|
480
490
|
writeFileSync(batPath, batLines.join('\r\n'));
|
|
481
491
|
const child = spawn('cmd.exe', ['/c', batPath], {
|
|
@@ -486,6 +496,11 @@ async function handleMessage(msg) {
|
|
|
486
496
|
child.unref();
|
|
487
497
|
console.log(`[Agent] Spawned upgrade script (PID wait for ${pid}, pm2=${isPm2}, dir=${installDir}): ${batPath}`);
|
|
488
498
|
sendToServer({ type: 'upgrade_agent_ack', success: true, version: latestVersion, pendingRestart: true });
|
|
499
|
+
|
|
500
|
+
if (isPm2) {
|
|
501
|
+
// PM2 环境:先同步 pm2 stop 防止 autorestart 竞态,再 exit
|
|
502
|
+
try { execSync('pm2 stop yeaft-agent', { timeout: 5000 }); } catch {}
|
|
503
|
+
}
|
|
489
504
|
} else {
|
|
490
505
|
// Linux/macOS: 生成 detached shell 脚本,先停止服务再升级再启动
|
|
491
506
|
// 避免在升级过程中 systemd 不断重启已删除的旧版本
|
package/crew.js
CHANGED
|
@@ -1501,13 +1501,31 @@ async function dispatchToRole(session, roleName, content, fromSource, taskId, ta
|
|
|
1501
1501
|
* 处理人的输入
|
|
1502
1502
|
*/
|
|
1503
1503
|
export async function handleCrewHumanInput(msg) {
|
|
1504
|
-
const { sessionId, content, targetRole } = msg;
|
|
1504
|
+
const { sessionId, content, targetRole, files } = msg;
|
|
1505
1505
|
const session = crewSessions.get(sessionId);
|
|
1506
1506
|
if (!session) {
|
|
1507
1507
|
console.warn(`[Crew] Session not found: ${sessionId}`);
|
|
1508
1508
|
return;
|
|
1509
1509
|
}
|
|
1510
1510
|
|
|
1511
|
+
// Build dispatch content (supports image attachments)
|
|
1512
|
+
function buildHumanContent(prefix, text) {
|
|
1513
|
+
if (files && files.length > 0) {
|
|
1514
|
+
const blocks = [];
|
|
1515
|
+
for (const file of files) {
|
|
1516
|
+
if (file.isImage || file.mimeType?.startsWith('image/')) {
|
|
1517
|
+
blocks.push({
|
|
1518
|
+
type: 'image',
|
|
1519
|
+
source: { type: 'base64', media_type: file.mimeType, data: file.data }
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
blocks.push({ type: 'text', text: `${prefix}\n${text}` });
|
|
1524
|
+
return blocks;
|
|
1525
|
+
}
|
|
1526
|
+
return `${prefix}\n${text}`;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1511
1529
|
// 注意:不在这里发送人的消息到 Web(前端已本地添加,避免重复)
|
|
1512
1530
|
// 但需要记录到 uiMessages 用于恢复时重放
|
|
1513
1531
|
session.uiMessages.push({
|
|
@@ -1525,8 +1543,7 @@ export async function handleCrewHumanInput(msg) {
|
|
|
1525
1543
|
|
|
1526
1544
|
// 发给请求人工介入的角色,或指定的目标角色
|
|
1527
1545
|
const target = targetRole || waitingContext?.fromRole || session.decisionMaker;
|
|
1528
|
-
|
|
1529
|
-
await dispatchToRole(session, target, humanPrompt, 'human');
|
|
1546
|
+
await dispatchToRole(session, target, buildHumanContent('人工回复:', content), 'human');
|
|
1530
1547
|
return;
|
|
1531
1548
|
}
|
|
1532
1549
|
|
|
@@ -1553,13 +1570,23 @@ export async function handleCrewHumanInput(msg) {
|
|
|
1553
1570
|
// 检查目标角色是否正在忙
|
|
1554
1571
|
const targetState = session.roleStates.get(target);
|
|
1555
1572
|
if (targetState?.turnActive) {
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1573
|
+
if (msg.interrupt) {
|
|
1574
|
+
// 中断模式:立即打断角色
|
|
1575
|
+
await interruptRole(session, target, buildHumanContent('人工消息(中断):', message), 'human');
|
|
1576
|
+
} else {
|
|
1577
|
+
// 排队
|
|
1578
|
+
session.humanMessageQueue.push({ target, content: message, timestamp: Date.now() });
|
|
1579
|
+
console.log(`[Crew] Human message queued for ${target} (busy)`);
|
|
1580
|
+
sendCrewMessage({
|
|
1581
|
+
type: 'crew_message_queued',
|
|
1582
|
+
sessionId: session.id,
|
|
1583
|
+
target,
|
|
1584
|
+
queueLength: session.humanMessageQueue.filter(m => m.target === target).length
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1559
1587
|
return;
|
|
1560
1588
|
}
|
|
1561
|
-
|
|
1562
|
-
await dispatchToRole(session, target, humanPrompt, 'human');
|
|
1589
|
+
await dispatchToRole(session, target, buildHumanContent('人工消息:', message), 'human');
|
|
1563
1590
|
return;
|
|
1564
1591
|
}
|
|
1565
1592
|
}
|
|
@@ -1572,11 +1599,16 @@ export async function handleCrewHumanInput(msg) {
|
|
|
1572
1599
|
if (targetState?.turnActive) {
|
|
1573
1600
|
session.humanMessageQueue.push({ target, content, timestamp: Date.now() });
|
|
1574
1601
|
console.log(`[Crew] Human message queued for ${target} (busy)`);
|
|
1602
|
+
sendCrewMessage({
|
|
1603
|
+
type: 'crew_message_queued',
|
|
1604
|
+
sessionId: session.id,
|
|
1605
|
+
target,
|
|
1606
|
+
queueLength: session.humanMessageQueue.filter(m => m.target === target).length
|
|
1607
|
+
});
|
|
1575
1608
|
return;
|
|
1576
1609
|
}
|
|
1577
1610
|
|
|
1578
|
-
|
|
1579
|
-
await dispatchToRole(session, target, humanPrompt, 'human');
|
|
1611
|
+
await dispatchToRole(session, target, buildHumanContent('人工消息:', content), 'human');
|
|
1580
1612
|
}
|
|
1581
1613
|
|
|
1582
1614
|
/**
|
|
@@ -1644,6 +1676,11 @@ export async function handleCrewControl(msg) {
|
|
|
1644
1676
|
case 'stop_role':
|
|
1645
1677
|
if (targetRole) await stopRole(session, targetRole);
|
|
1646
1678
|
break;
|
|
1679
|
+
case 'interrupt_role':
|
|
1680
|
+
if (targetRole && msg.content) {
|
|
1681
|
+
await interruptRole(session, targetRole, msg.content, 'human');
|
|
1682
|
+
}
|
|
1683
|
+
break;
|
|
1647
1684
|
case 'stop_all':
|
|
1648
1685
|
await stopAll(session);
|
|
1649
1686
|
break;
|
|
@@ -1726,6 +1763,58 @@ async function resumeSession(session) {
|
|
|
1726
1763
|
/**
|
|
1727
1764
|
* 停止单个角色
|
|
1728
1765
|
*/
|
|
1766
|
+
/**
|
|
1767
|
+
* 中断角色当前 turn 并发送新消息
|
|
1768
|
+
*/
|
|
1769
|
+
async function interruptRole(session, roleName, newContent, fromSource = 'human') {
|
|
1770
|
+
const roleState = session.roleStates.get(roleName);
|
|
1771
|
+
if (!roleState) {
|
|
1772
|
+
console.warn(`[Crew] Cannot interrupt ${roleName}: no roleState`);
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
console.log(`[Crew] Interrupting ${roleName}`);
|
|
1777
|
+
|
|
1778
|
+
// 结束 streaming 状态
|
|
1779
|
+
endRoleStreaming(session, roleName);
|
|
1780
|
+
|
|
1781
|
+
// 保存 sessionId 用于 resume 上下文连续性
|
|
1782
|
+
if (roleState.claudeSessionId) {
|
|
1783
|
+
await saveRoleSessionId(session.sharedDir, roleName, roleState.claudeSessionId)
|
|
1784
|
+
.catch(e => console.warn(`[Crew] Failed to save sessionId for ${roleName}:`, e.message));
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// Abort 当前 query
|
|
1788
|
+
if (roleState.abortController) {
|
|
1789
|
+
roleState.abortController.abort();
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// 清理旧状态
|
|
1793
|
+
roleState.query = null;
|
|
1794
|
+
roleState.inputStream = null;
|
|
1795
|
+
roleState.turnActive = false;
|
|
1796
|
+
roleState.accumulatedText = '';
|
|
1797
|
+
|
|
1798
|
+
// 通知前端中断
|
|
1799
|
+
sendCrewMessage({
|
|
1800
|
+
type: 'crew_turn_completed',
|
|
1801
|
+
sessionId: session.id,
|
|
1802
|
+
role: roleName,
|
|
1803
|
+
interrupted: true
|
|
1804
|
+
});
|
|
1805
|
+
|
|
1806
|
+
sendStatusUpdate(session);
|
|
1807
|
+
|
|
1808
|
+
// 系统消息记录
|
|
1809
|
+
sendCrewOutput(session, 'system', 'system', {
|
|
1810
|
+
type: 'assistant',
|
|
1811
|
+
message: { role: 'assistant', content: [{ type: 'text', text: `${roleName} 被中断` }] }
|
|
1812
|
+
});
|
|
1813
|
+
|
|
1814
|
+
// 创建新 query 并 dispatch
|
|
1815
|
+
await dispatchToRole(session, roleName, newContent, fromSource);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1729
1818
|
async function stopRole(session, roleName) {
|
|
1730
1819
|
const roleState = session.roleStates.get(roleName);
|
|
1731
1820
|
if (roleState) {
|
|
@@ -1911,6 +2000,32 @@ function sendCrewOutput(session, roleName, outputType, rawMessage, extra = {}) {
|
|
|
1911
2000
|
}
|
|
1912
2001
|
}
|
|
1913
2002
|
}
|
|
2003
|
+
// Check for image blocks in tool_result content
|
|
2004
|
+
const resultContent = rawMessage?.message?.content;
|
|
2005
|
+
if (Array.isArray(resultContent)) {
|
|
2006
|
+
for (const item of resultContent) {
|
|
2007
|
+
if (item.type === 'image' && item.source?.type === 'base64') {
|
|
2008
|
+
sendCrewMessage({
|
|
2009
|
+
type: 'crew_image',
|
|
2010
|
+
sessionId: session.id,
|
|
2011
|
+
role: roleName,
|
|
2012
|
+
roleIcon,
|
|
2013
|
+
roleName: displayName,
|
|
2014
|
+
toolId: toolId || '',
|
|
2015
|
+
mimeType: item.source.media_type,
|
|
2016
|
+
data: item.source.data,
|
|
2017
|
+
taskId, taskTitle
|
|
2018
|
+
});
|
|
2019
|
+
session.uiMessages.push({
|
|
2020
|
+
role: roleName, roleIcon, roleName: displayName,
|
|
2021
|
+
type: 'image', toolId: toolId || '',
|
|
2022
|
+
mimeType: item.source.media_type,
|
|
2023
|
+
taskId, taskTitle,
|
|
2024
|
+
timestamp: Date.now()
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
1914
2029
|
}
|
|
1915
2030
|
// tool 只保存精简信息(toolName + 摘要),不存完整 toolInput/toolResult
|
|
1916
2031
|
}
|