@yeaft/webchat-agent 0.0.134 → 0.0.135

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/crew.js +125 -10
  2. package/package.json +1 -1
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
- const humanPrompt = `人工回复:\n${content}`;
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
- session.humanMessageQueue.push({ target, content: message, timestamp: Date.now() });
1558
- console.log(`[Crew] Human message queued for ${target} (busy)`);
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
- const humanPrompt = `人工消息:\n${message}`;
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
- const humanPrompt = `人工消息:\n${content}`;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.134",
3
+ "version": "0.0.135",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",