@auto-ai/agent 2.1.183 → 2.1.187

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 (83) hide show
  1. package/dist/safe-a/404/index.html +1 -1
  2. package/dist/safe-a/404.html +1 -1
  3. package/dist/safe-a/_next/static/chunks/074a7a892d554827.js +1 -0
  4. package/dist/safe-a/_next/static/chunks/{e42498eec0c8d570.js → 0fde2a8866dd02fc.js} +1 -1
  5. package/dist/safe-a/_next/static/chunks/26f5809c84b263c0.css +7 -0
  6. package/dist/safe-a/_next/static/chunks/2ba468c7afb852bd.js +1 -0
  7. package/dist/safe-a/_next/static/chunks/{5434628aad5e5bc3.js → 2bac8f0a4e762326.js} +1 -1
  8. package/dist/safe-a/_next/static/chunks/{7f341bc0fb5ff708.js → 2d7f6aa6628bccd0.js} +1 -1
  9. package/dist/safe-a/_next/static/chunks/33678ff357855160.js +1 -0
  10. package/dist/safe-a/_next/static/chunks/5022180fa579a73a.js +1 -0
  11. package/dist/safe-a/_next/static/chunks/518d4c901787979a.js +1 -0
  12. package/dist/safe-a/_next/static/chunks/811897ffb3636359.js +1 -0
  13. package/dist/safe-a/_next/static/chunks/96feacf5b8cde310.js +1 -0
  14. package/dist/safe-a/_next/static/chunks/a56ab101155b1427.js +1 -0
  15. package/dist/safe-a/_next/static/chunks/a5b851b271052e89.js +1 -0
  16. package/dist/safe-a/_next/static/chunks/a6aafbc087d75717.js +1 -0
  17. package/dist/safe-a/_next/static/chunks/ceead8f438e931f7.js +1 -0
  18. package/dist/safe-a/_next/static/chunks/d16ae4f1eb111da5.js +1 -0
  19. package/dist/safe-a/_next/static/chunks/d47487bdc4a277fd.css +1 -0
  20. package/dist/safe-a/_next/static/chunks/e00679a7df05e016.js +1 -0
  21. package/dist/safe-a/_next/static/chunks/f1dde9052e8d1d3d.js +1 -0
  22. package/dist/safe-a/index.html +2 -2
  23. package/dist/safe-a/index.txt +26 -27
  24. package/dist/safe-a/manage/about/index.html +2 -2
  25. package/dist/safe-a/manage/about/index.txt +11 -11
  26. package/dist/safe-a/manage/env/index.html +2 -2
  27. package/dist/safe-a/manage/env/index.txt +13 -13
  28. package/dist/safe-a/manage/geelib/index.html +2 -2
  29. package/dist/safe-a/manage/geelib/index.txt +13 -13
  30. package/dist/safe-a/manage/general/index.html +2 -2
  31. package/dist/safe-a/manage/general/index.txt +11 -11
  32. package/dist/safe-a/manage/git/index.html +2 -2
  33. package/dist/safe-a/manage/git/index.txt +13 -13
  34. package/dist/safe-a/manage/im/index.html +2 -2
  35. package/dist/safe-a/manage/im/index.txt +13 -13
  36. package/dist/safe-a/manage/index.html +2 -2
  37. package/dist/safe-a/manage/index.txt +8 -8
  38. package/dist/safe-a/manage/library/index.html +2 -2
  39. package/dist/safe-a/manage/library/index.txt +11 -11
  40. package/dist/safe-a/manage/mcp/index.html +2 -2
  41. package/dist/safe-a/manage/mcp/index.txt +11 -11
  42. package/dist/safe-a/manage/permissions/index.html +2 -2
  43. package/dist/safe-a/manage/permissions/index.txt +11 -11
  44. package/dist/safe-a/manage/skills/index.html +2 -2
  45. package/dist/safe-a/manage/skills/index.txt +11 -11
  46. package/dist/safe-a/manage/system/index.html +2 -2
  47. package/dist/safe-a/manage/system/index.txt +13 -13
  48. package/dist/safe-a/manage/task/index.html +2 -2
  49. package/dist/safe-a/manage/task/index.txt +11 -11
  50. package/dist/safe-a/manage/teams/index.html +2 -2
  51. package/dist/safe-a/manage/teams/index.txt +11 -11
  52. package/dist/safe-a/manage/tools/index.html +2 -2
  53. package/dist/safe-a/manage/tools/index.txt +11 -11
  54. package/dist/ws-test/ws-test.css +89 -1
  55. package/dist/ws-test/ws-test.html +1 -0
  56. package/dist/ws-test/ws-test.js +276 -26
  57. package/mcps-runtime/claude-ws-channel/.mcp.json +18 -0
  58. package/mcps-runtime/claude-ws-channel/server/boot.mjs +30 -0
  59. package/mcps-runtime/claude-ws-channel/server/gateway.bundle.mjs +410306 -0
  60. package/mcps-runtime/claude-ws-channel/server/index.mjs +144 -0
  61. package/package.json +6 -6
  62. package/dist/safe-a/_next/static/chunks/044a195f3374d4b6.js +0 -1
  63. package/dist/safe-a/_next/static/chunks/0a7d5a51593d8c2f.js +0 -1
  64. package/dist/safe-a/_next/static/chunks/12fa90667e8fb0e3.js +0 -1
  65. package/dist/safe-a/_next/static/chunks/4682815462207a37.js +0 -1
  66. package/dist/safe-a/_next/static/chunks/5b44bdce5e5ebd06.js +0 -1
  67. package/dist/safe-a/_next/static/chunks/5b50a8c524507ee0.js +0 -1
  68. package/dist/safe-a/_next/static/chunks/66cdfa3e49dc50f3.js +0 -1
  69. package/dist/safe-a/_next/static/chunks/6b646b7144f0eb9e.js +0 -1
  70. package/dist/safe-a/_next/static/chunks/804376464cecbc36.css +0 -7
  71. package/dist/safe-a/_next/static/chunks/812fc979171f7ded.js +0 -1
  72. package/dist/safe-a/_next/static/chunks/8f31f818efd25c0f.js +0 -1
  73. package/dist/safe-a/_next/static/chunks/9edf76a11bf93bcc.js +0 -1
  74. package/dist/safe-a/_next/static/chunks/b63afdaa820ddad4.js +0 -1
  75. package/dist/safe-a/_next/static/chunks/b9590be9854a83fb.js +0 -1
  76. package/dist/safe-a/_next/static/chunks/dc7a9ce680ab0ce4.css +0 -1
  77. package/dist/safe-a/_next/static/chunks/dd98ccfeaf6ec72e.js +0 -1
  78. package/dist/safe-a/_next/static/chunks/e847138f5b487d3d.js +0 -1
  79. package/dist/safe-a/_next/static/chunks/f3c9f6b778f4cf1a.js +0 -1
  80. package/dist/safe-a/_next/static/chunks/fa686b4028e991a5.js +0 -1
  81. /package/dist/safe-a/_next/static/{yx70m3msfKAM3SckZNBAr → BDdAEI0K2Jg8YCxVKfY0_}/_buildManifest.js +0 -0
  82. /package/dist/safe-a/_next/static/{yx70m3msfKAM3SckZNBAr → BDdAEI0K2Jg8YCxVKfY0_}/_clientMiddlewareManifest.json +0 -0
  83. /package/dist/safe-a/_next/static/{yx70m3msfKAM3SckZNBAr → BDdAEI0K2Jg8YCxVKfY0_}/_ssgManifest.js +0 -0
@@ -32,6 +32,9 @@
32
32
  const promptFileSuggestions = $('promptFileSuggestions')
33
33
  const composerUploadToast = $('composerUploadToast')
34
34
  let composerUploadToastTimer = null
35
+ const debugNotifyStack = $('debugNotifyStack')
36
+ const DEBUG_NOTIFY_TTL_MS = 6000
37
+ const DEBUG_NOTIFY_MAX = 8
35
38
  const btnNewSession = $('btnNewSession')
36
39
  const btnSkillPackageManager = $('btnSkillPackageManager')
37
40
  const btnEditAgentEnv = $('btnEditAgentEnv')
@@ -154,6 +157,10 @@
154
157
  const mcpPackageImportInput = $('mcpPackageImportInput')
155
158
  const sessionListEl = $('sessionList')
156
159
  const sessionListMetaEl = $('sessionListMeta')
160
+ /** 侧栏会话列表运行时状态轮询定时器 */
161
+ let sessionRuntimePollTimer = null
162
+ /** 当前附着 session 的 turn 是否进行中(由 query.progress 驱动) */
163
+ let sessionTurnActive = false
157
164
  const pendingPromptsBarEl = $('pendingPromptsBar')
158
165
  const pendingPromptsListEl = $('pendingPromptsList')
159
166
  let pendingPromptsCache = []
@@ -731,6 +738,11 @@
731
738
  /** HTTP 历史回放进行中:暂存实时 conversation.delta,避免与回放竞态写乱 transcript 下标 */
732
739
  let sessionReplayInProgress = false
733
740
  const pendingConversationDeltaEnvelopes = []
741
+ /**
742
+ * 用户点击发送后、服务端 conversation.delta 到达前的本地回显。
743
+ * 业务语义:避免等待子进程 spawn / 路由期间聊天区无反馈。
744
+ */
745
+ let pendingUserEcho = null
734
746
 
735
747
  function sourceAgentPayloadToText(payload) {
736
748
  if (!payload || typeof payload !== 'object') return ''
@@ -883,6 +895,41 @@
883
895
  })
884
896
  }
885
897
 
898
+ /** 发送 prompt 后立即在聊天区展示用户输入 */
899
+ function setPendingUserEcho(text) {
900
+ const trimmed = typeof text === 'string' ? text.trim() : ''
901
+ if (!trimmed) return
902
+ pendingUserEcho = {
903
+ text: trimmed,
904
+ sourceAgent: currentAgentParam(),
905
+ arrivalSeq: ++chatState.eventClock,
906
+ ts: new Date().toISOString(),
907
+ }
908
+ requestRenderChat()
909
+ }
910
+
911
+ function clearPendingUserEcho() {
912
+ if (!pendingUserEcho) return
913
+ pendingUserEcho = null
914
+ }
915
+
916
+ /** 渲染本地 pending 用户消息气泡(与 appendRunItemsToContainer 的 user 分支一致) */
917
+ function appendPendingUserEchoToContainer(container) {
918
+ if (!pendingUserEcho || !pendingUserEcho.text) return
919
+ const div = document.createElement('div')
920
+ div.className = 'chat-msg chat-msg-user chat-msg-pending-echo'
921
+ const lab = document.createElement('div')
922
+ lab.className = 'chat-msg-label'
923
+ lab.textContent = 'User · agent=' + pendingUserEcho.sourceAgent
924
+ const bodyEl = document.createElement('div')
925
+ bodyEl.className = 'chat-user-body'
926
+ setMessageBodyEl(bodyEl, pendingUserEcho.text)
927
+ div.appendChild(lab)
928
+ div.appendChild(bodyEl)
929
+ container.appendChild(div)
930
+ attachUserMessageCollapse(bodyEl)
931
+ }
932
+
886
933
  if (typeof marked !== 'undefined' && marked.setOptions) {
887
934
  marked.setOptions({ breaks: true, gfm: true })
888
935
  }
@@ -1171,8 +1218,9 @@
1171
1218
  }
1172
1219
 
1173
1220
  if (nonToolBlocks.length > 0) {
1174
- const body = contentToPlainText(nonToolBlocks).trim()
1175
- if (body) {
1221
+ const rawBody = contentToPlainText(nonToolBlocks).trim()
1222
+ const unwrapped = unwrapChannelWrappedText(rawBody)
1223
+ if (unwrapped.body.trim()) {
1176
1224
  units.push({
1177
1225
  kind: 'message',
1178
1226
  ev: ev,
@@ -1359,12 +1407,60 @@
1359
1407
  return ''
1360
1408
  }
1361
1409
 
1410
+ /**
1411
+ * 解析 channel 入站包裹:`<channel source="ws" ...>正文</channel>`。
1412
+ * 业务语义:与 wrapChannelMessage / ChannelManager.parseChannelMetaFromWrappedPrompt 同源,
1413
+ * 页面只展示正文,channel 元信息用于标签行。
1414
+ */
1415
+ function unwrapChannelWrappedText(text) {
1416
+ const raw = typeof text === 'string' ? text : ''
1417
+ const matched = raw.match(/^<channel\s+([^>]+)>\s*([\s\S]*?)\s*<\/channel>\s*$/i)
1418
+ if (!matched) {
1419
+ return { body: raw, meta: null }
1420
+ }
1421
+ const attrs = matched[1] || ''
1422
+ const body = matched[2] != null ? String(matched[2]) : ''
1423
+ const meta = {}
1424
+ const attrRegex = /([a-zA-Z_][a-zA-Z0-9_]*)="([^"]*)"/g
1425
+ let am = null
1426
+ while ((am = attrRegex.exec(attrs)) !== null) {
1427
+ meta[am[1]] = am[2]
1428
+ }
1429
+ return { body: body, meta: meta }
1430
+ }
1431
+
1432
+ /** channel 入站消息标签:channel_type/source + agent */
1433
+ function formatChannelInboundLabel(meta) {
1434
+ if (!meta || typeof meta !== 'object') return ''
1435
+ const parts = []
1436
+ const channelName = meta.channel_type || meta.source || ''
1437
+ if (channelName) parts.push(channelName)
1438
+ if (meta.agent) parts.push('agent=' + meta.agent)
1439
+ return parts.join(' · ')
1440
+ }
1441
+
1442
+ function getRawUserMessageText(m, contentBlocks) {
1443
+ if (contentBlocks) {
1444
+ return contentToPlainText(contentBlocks).trim()
1445
+ }
1446
+ if (!m || m.type !== 'user') return ''
1447
+ const inner = m.message
1448
+ if (inner && inner.content !== undefined) {
1449
+ return contentToPlainText(inner.content).trim()
1450
+ }
1451
+ return ''
1452
+ }
1453
+
1362
1454
  /**
1363
1455
  * 与 messages.shouldShowUserMessage 一致:isMeta 为系统/Skill 注入,进模型但不展示。
1456
+ * channel 入站(正文含 <channel> 包裹)仍展示。
1364
1457
  */
1365
1458
  function shouldShowWsUserMessage(m) {
1366
1459
  if (!m || m.type !== 'user') return false
1367
- if (m.isMeta === true) return false
1460
+ if (m.isMeta === true) {
1461
+ const raw = getRawUserMessageText(m, null)
1462
+ return unwrapChannelWrappedText(raw).meta != null
1463
+ }
1368
1464
  return true
1369
1465
  }
1370
1466
 
@@ -1373,7 +1469,13 @@
1373
1469
  const t = m.type
1374
1470
  if (t === 'user' || t === 'assistant') {
1375
1471
  const inner = m.message
1376
- if (inner && inner.content !== undefined) return contentToPlainText(inner.content)
1472
+ if (inner && inner.content !== undefined) {
1473
+ const plain = contentToPlainText(inner.content)
1474
+ if (t === 'user') {
1475
+ return unwrapChannelWrappedText(plain).body
1476
+ }
1477
+ return plain
1478
+ }
1377
1479
  }
1378
1480
  if (t === 'system' && typeof m.content === 'string') return m.content
1379
1481
  return ''
@@ -1442,7 +1544,11 @@
1442
1544
  )
1443
1545
  })
1444
1546
  if (nonTool.length > 0) {
1445
- return contentToPlainText(nonTool).trim()
1547
+ const plain = contentToPlainText(nonTool).trim()
1548
+ if (m.type === 'user') {
1549
+ return unwrapChannelWrappedText(plain).body.trim()
1550
+ }
1551
+ return plain
1446
1552
  }
1447
1553
  return extractMessageBody(m).trim()
1448
1554
  }
@@ -1501,16 +1607,24 @@
1501
1607
  const t = unit.messageType
1502
1608
 
1503
1609
  if (t === 'user') {
1504
- const body = unit.contentBlocks
1505
- ? contentToPlainText(unit.contentBlocks).trim()
1506
- : extractMessageBody(m).trim()
1610
+ const rawUserText = getRawUserMessageText(m, unit.contentBlocks)
1611
+ const unwrapped = unwrapChannelWrappedText(rawUserText)
1612
+ const body = unwrapped.body.trim()
1507
1613
  if (!body) continue
1614
+ const channelLabel = formatChannelInboundLabel(unwrapped.meta)
1508
1615
  const div = document.createElement('div')
1509
- div.className = 'chat-msg chat-msg-user'
1616
+ div.className =
1617
+ 'chat-msg chat-msg-user' +
1618
+ (channelLabel ? ' chat-msg-channel-inbound' : '')
1510
1619
  applyScheduledMsgClass(div, isScheduled)
1511
1620
  const lab = document.createElement('div')
1512
1621
  lab.className = 'chat-msg-label'
1513
- lab.textContent = formatScheduledLabel('User · agent=' + messageAgent, isScheduled)
1622
+ lab.textContent = formatScheduledLabel(
1623
+ channelLabel
1624
+ ? 'Channel · ' + channelLabel
1625
+ : 'User · agent=' + messageAgent,
1626
+ isScheduled,
1627
+ )
1514
1628
  const bodyEl = document.createElement('div')
1515
1629
  bodyEl.className = 'chat-user-body'
1516
1630
  setMessageBodyEl(bodyEl, body)
@@ -1582,7 +1696,8 @@
1582
1696
  const sourceAgentMessages = chatState.sourceAgentMessages
1583
1697
  const shouldStickBottom = autoScrollState.mode === 'STICKY_BOTTOM' && isNearBottom(chatThreadEl)
1584
1698
  chatThreadEl.innerHTML = ''
1585
- const hasConversation = runs.size > 0
1699
+ const hasConversation =
1700
+ runs.size > 0 || !!pendingUserEcho
1586
1701
  if (!hasConversation && streamEntries.length === 0 && sourceAgentMessages.length === 0) {
1587
1702
  chatThreadEl.innerHTML =
1588
1703
  '<div class="chat-placeholder">连接后将在此展示对话(Markdown 渲染)</div>'
@@ -1757,6 +1872,12 @@
1757
1872
  }
1758
1873
  chatThreadEl.appendChild(groupContainer)
1759
1874
  }
1875
+ if (pendingUserEcho) {
1876
+ const echoWrap = document.createElement('div')
1877
+ echoWrap.className = 'chat-run-group chat-run-group--pending-echo'
1878
+ appendPendingUserEchoToContainer(echoWrap)
1879
+ chatThreadEl.appendChild(echoWrap)
1880
+ }
1760
1881
  if (streamEntries.length > 0) {
1761
1882
  for (let i = 0; i < streamEntries.length; i++) {
1762
1883
  const streamItem = streamEntries[i]
@@ -1798,6 +1919,7 @@
1798
1919
  chatState.seenEventIds.clear()
1799
1920
  sessionReplayInProgress = false
1800
1921
  pendingConversationDeltaEnvelopes.length = 0
1922
+ pendingUserEcho = null
1801
1923
  autoScrollState.mode = 'STICKY_BOTTOM'
1802
1924
  if (logEl) logEl.textContent = ''
1803
1925
  requestRenderChat()
@@ -1907,6 +2029,13 @@
1907
2029
  arrivalSeq: ++chatState.eventClock,
1908
2030
  }
1909
2031
  applyConversationDeltaToRunBuffer(runBuffer, action, index, runMessage)
2032
+ if (
2033
+ msg &&
2034
+ msg.type === 'user' &&
2035
+ shouldShowWsUserMessage(msg)
2036
+ ) {
2037
+ clearPendingUserEcho()
2038
+ }
1910
2039
  requestRenderChat()
1911
2040
  return true
1912
2041
  }
@@ -1968,6 +2097,17 @@
1968
2097
  // 回放边界控制事件:仅用于内部流程标记,不进入聊天消息区。
1969
2098
  return true
1970
2099
  }
2100
+ if (type === 'run.lifecycle' && payload.state === 'console') {
2101
+ const consoleMsg =
2102
+ typeof payload.message === 'string' ? payload.message.trim() : ''
2103
+ if (consoleMsg) {
2104
+ showDebugNotify(
2105
+ consoleMsg,
2106
+ typeof payload.level === 'string' ? payload.level : 'log',
2107
+ )
2108
+ }
2109
+ return true
2110
+ }
1971
2111
  if (
1972
2112
  type === 'run.lifecycle' &&
1973
2113
  payload.state === 'teammateExit' &&
@@ -2027,12 +2167,15 @@
2027
2167
  }
2028
2168
  if (type === 'query.progress') {
2029
2169
  const phase = typeof payload.phase === 'string' ? payload.phase : ''
2170
+ sessionTurnActive = !!(phase && phase !== 'idle')
2171
+ kickSessionRuntimePoll()
2030
2172
  if (phase === 'idle') {
2031
2173
  if (serverReady) setStatus('已就绪', 'ready')
2032
2174
  // 一轮 prompt 收尾后 transcript 已落盘,刷新列表以更新页签 firstPrompt/summary
2033
- void loadSessions()
2175
+ void loadSessions({ silent: true })
2034
2176
  } else if (phase) {
2035
2177
  setStatus('查询阶段: ' + phase + '…', '')
2178
+ void loadSessions({ silent: true })
2036
2179
  }
2037
2180
  return true
2038
2181
  }
@@ -2124,10 +2267,36 @@
2124
2267
  return null
2125
2268
  }
2126
2269
 
2270
+ /**
2271
+ * 右上角通知:仅展示 warn(黄)、error(红);info/log 等蓝色类不弹窗。
2272
+ */
2273
+ function showDebugNotify(message, level) {
2274
+ if (!debugNotifyStack) return
2275
+ const text = typeof message === 'string' ? message.trim() : ''
2276
+ if (!text) return
2277
+ const lvlRaw = typeof level === 'string' ? level.trim().toLowerCase() : 'log'
2278
+ if (lvlRaw !== 'warn' && lvlRaw !== 'error') return
2279
+ const lvl = lvlRaw === 'error' ? 'error' : 'warn'
2280
+ const item = document.createElement('div')
2281
+ item.className = 'debug-notify debug-notify--' + lvl
2282
+ item.setAttribute('role', 'status')
2283
+ item.textContent = text
2284
+ debugNotifyStack.appendChild(item)
2285
+ while (debugNotifyStack.children.length > DEBUG_NOTIFY_MAX) {
2286
+ debugNotifyStack.removeChild(debugNotifyStack.firstChild)
2287
+ }
2288
+ setTimeout(function () {
2289
+ if (item.parentNode === debugNotifyStack) {
2290
+ debugNotifyStack.removeChild(item)
2291
+ }
2292
+ }, DEBUG_NOTIFY_TTL_MS)
2293
+ }
2294
+
2127
2295
  function logLine(type, obj) {
2128
- if (!logEl) return
2129
2296
  const t = new Date().toISOString().slice(11, 23)
2130
- const line = '[' + t + '] ' + type + ' ' + (typeof obj === 'string' ? obj : JSON.stringify(obj, null, 0))
2297
+ const line =
2298
+ '[' + t + '] ' + type + ' ' + (typeof obj === 'string' ? obj : JSON.stringify(obj, null, 0))
2299
+ if (!logEl) return
2131
2300
  logEl.textContent += line + '\n'
2132
2301
  logEl.scrollTop = logEl.scrollHeight
2133
2302
  }
@@ -4616,16 +4785,29 @@
4616
4785
  const title = sessionTitle(s)
4617
4786
  if (isActive) activeTitle = title
4618
4787
 
4788
+ const isRunning = s.running === true
4619
4789
  const row = document.createElement('div')
4620
- row.className = 'sidebar-session-item' + (isActive ? ' active' : '')
4790
+ row.className =
4791
+ 'sidebar-session-item' +
4792
+ (isActive ? ' active' : '') +
4793
+ (isRunning ? ' running' : '')
4621
4794
  row.setAttribute('role', 'tab')
4622
4795
  row.setAttribute('aria-selected', isActive ? 'true' : 'false')
4623
- row.title = s.sessionId
4796
+ row.title = s.sessionId + (isRunning ? '(运行中)' : '')
4624
4797
 
4625
4798
  const mainBtn = document.createElement('button')
4626
4799
  mainBtn.type = 'button'
4627
4800
  mainBtn.className = 'sidebar-session-main'
4628
- mainBtn.textContent = title
4801
+ if (isRunning) {
4802
+ const runBadge = document.createElement('span')
4803
+ runBadge.className = 'sidebar-session-running-badge'
4804
+ runBadge.textContent = '运行中'
4805
+ mainBtn.appendChild(runBadge)
4806
+ }
4807
+ const titleSpan = document.createElement('span')
4808
+ titleSpan.className = 'sidebar-session-title'
4809
+ titleSpan.textContent = title
4810
+ mainBtn.appendChild(titleSpan)
4629
4811
  mainBtn.addEventListener('click', function (ev) {
4630
4812
  ev.stopPropagation()
4631
4813
  switchToSession(s.sessionId)
@@ -4704,8 +4886,62 @@
4704
4886
  return u.toString()
4705
4887
  }
4706
4888
 
4889
+ /** 列表里是否存在运行中 session(子进程存活) */
4890
+ function sessionListHasRunning(sessions) {
4891
+ if (!Array.isArray(sessions)) return false
4892
+ return sessions.some(function (s) {
4893
+ return s && s.running === true
4894
+ })
4895
+ }
4896
+
4897
+ /** 轮询间隔:有运行中或本页 turn 时 1.5s,否则 4s */
4898
+ function sessionRuntimePollDelayMs() {
4899
+ if (sessionTurnActive || sessionListHasRunning(window.__lastSessions)) {
4900
+ return 1500
4901
+ }
4902
+ return 4000
4903
+ }
4904
+
4905
+ function stopSessionRuntimePoll() {
4906
+ if (sessionRuntimePollTimer != null) {
4907
+ clearTimeout(sessionRuntimePollTimer)
4908
+ sessionRuntimePollTimer = null
4909
+ }
4910
+ }
4911
+
4912
+ /**
4913
+ * 调度下一次会话列表运行时状态拉取。
4914
+ * 业务语义:侧栏「运行中」依赖主进程 RPC,不能仅靠当前 WS 事件;后台定时静默刷新。
4915
+ */
4916
+ function scheduleSessionRuntimePoll() {
4917
+ stopSessionRuntimePoll()
4918
+ sessionRuntimePollTimer = setTimeout(function () {
4919
+ sessionRuntimePollTimer = null
4920
+ if (document.visibilityState === 'hidden') {
4921
+ scheduleSessionRuntimePoll()
4922
+ return
4923
+ }
4924
+ void loadSessions({ silent: true }).then(
4925
+ function () {
4926
+ scheduleSessionRuntimePoll()
4927
+ },
4928
+ function () {
4929
+ scheduleSessionRuntimePoll()
4930
+ },
4931
+ )
4932
+ }, sessionRuntimePollDelayMs())
4933
+ }
4934
+
4935
+ /** 立即按最新状态重置轮询节奏(例如 turn 开始/结束) */
4936
+ function kickSessionRuntimePoll() {
4937
+ scheduleSessionRuntimePoll()
4938
+ }
4939
+
4707
4940
  async function loadSessions(options) {
4708
- sessionListMetaEl.textContent = '加载中…'
4941
+ const silent = !!(options && options.silent)
4942
+ if (!silent) {
4943
+ sessionListMetaEl.textContent = '加载中…'
4944
+ }
4709
4945
  let data
4710
4946
  try {
4711
4947
  const r = await fetch(buildSessionsListUrl())
@@ -4713,12 +4949,14 @@
4713
4949
  if (!r.ok) throw new Error(data.message || r.statusText)
4714
4950
  } catch (e) {
4715
4951
  const urlTried = buildSessionsListUrl()
4716
- const hint =
4717
- '(https 页请求 http API 会被拦截。请求: ' + urlTried + ')'
4718
- sessionListMetaEl.textContent = '加载失败: ' + e + hint
4719
- sessionListEl.textContent = ''
4952
+ if (!silent) {
4953
+ const hint =
4954
+ '(https 页请求 http API 会被拦截。请求: ' + urlTried + ')'
4955
+ sessionListMetaEl.textContent = '加载失败: ' + e + hint
4956
+ sessionListEl.textContent = ''
4957
+ }
4720
4958
  logLine('sessions', String(e) + ' url=' + urlTried)
4721
- return
4959
+ throw e
4722
4960
  }
4723
4961
  const sessions = data.sessions || []
4724
4962
  const dir =
@@ -4749,6 +4987,8 @@
4749
4987
  socket = null
4750
4988
  }
4751
4989
  serverReady = false
4990
+ sessionTurnActive = false
4991
+ kickSessionRuntimePoll()
4752
4992
  updateButtons()
4753
4993
  }
4754
4994
 
@@ -4911,6 +5151,9 @@
4911
5151
  if (!socket || socket.readyState !== WebSocket.OPEN) return
4912
5152
  hidePromptFileSuggestions()
4913
5153
  const text = getPromptPlainText()
5154
+ const trimmed = text.trim()
5155
+ if (!trimmed) return
5156
+ setPendingUserEcho(trimmed)
4914
5157
  const payload = { type: 'prompt', text: text }
4915
5158
  autoScrollState.mode = 'FORCE_ON_SEND'
4916
5159
  socket.send(JSON.stringify(payload))
@@ -7021,9 +7264,8 @@
7021
7264
  })
7022
7265
  })
7023
7266
  const buttons = [toggleBtn]
7024
- if (!canDelete) {
7025
- buttons.push(createManageRowBtn('编辑', '', function () { openSkillMdModal(id) }))
7026
- }
7267
+ // 系统 skill 与 agent 导入 skill 均可编辑 SKILL.md;仅 agent 层支持保存
7268
+ buttons.push(createManageRowBtn('编辑', '', function () { openSkillMdModal(id) }))
7027
7269
  if (canDelete) {
7028
7270
  buttons.push(createManageRowBtn('导出', '', function () {
7029
7271
  window.open(buildRuntimeSkillExportUrl(id), '_blank', 'noopener')
@@ -8478,10 +8720,18 @@
8478
8720
 
8479
8721
  async function bootstrapSessionSelection() {
8480
8722
  await loadSessions({ activateFirstIfEmpty: true })
8723
+ kickSessionRuntimePoll()
8481
8724
  if (!sessionIdInput || sessionIdInput.value.trim()) return
8482
8725
  switchToSession('')
8483
8726
  }
8484
8727
 
8728
+ document.addEventListener('visibilitychange', function () {
8729
+ if (document.visibilityState !== 'hidden') {
8730
+ void loadSessions({ silent: true })
8731
+ }
8732
+ kickSessionRuntimePoll()
8733
+ })
8734
+
8485
8735
  function onAgentChanged() {
8486
8736
  updateBrandAgent()
8487
8737
  rewriteSameOriginNavLinks()
@@ -0,0 +1,18 @@
1
+ {
2
+ "mcpServers": {
3
+ "ws": {
4
+ "command": "bun",
5
+ "args": [
6
+ "${AGENT_PACKAGE_ROOT}/mcps-runtime/claude-ws-channel/server/boot.mjs"
7
+ ],
8
+ "env": {
9
+ "WS_PORT": "${WS_PORT}",
10
+ "WS_MAIN_RPC_SOCKET": "${WS_MAIN_RPC_SOCKET}",
11
+ "WS_PROJECT_ROOT": "${WS_PROJECT_ROOT}",
12
+ "WS_AGENTS_ROOT": "${WS_AGENTS_ROOT}",
13
+ "WS_AGENT_NAME": "${WS_AGENT_NAME}",
14
+ "WS_STATE_DIR": "${WS_AGENTS_ROOT}/agent-runtime/${WS_AGENT_NAME}/channels/ws/.state"
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,30 @@
1
+ import { mkdirSync, appendFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { homedir } from 'node:os'
4
+
5
+ const stateDir =
6
+ process.env.WS_STATE_DIR ||
7
+ join(homedir(), '.claude', 'channels', 'ws')
8
+ const bootLog = join(stateDir, 'boot.log')
9
+ mkdirSync(dirname(bootLog), { recursive: true })
10
+
11
+ function log(line) {
12
+ appendFileSync(bootLog, `${new Date().toISOString()} ${line}\n`, {
13
+ encoding: 'utf8',
14
+ mode: 0o600,
15
+ flag: 'a',
16
+ })
17
+ }
18
+
19
+ log(`[BOOT] ws-channel boot.mjs start pid=${process.pid}`)
20
+ log(`[BOOT] WS_PORT=${process.env.WS_PORT || ''} WS_MAIN_RPC_SOCKET=${process.env.WS_MAIN_RPC_SOCKET || ''}`)
21
+
22
+ process.on('uncaughtException', err => {
23
+ log(`[BOOT] uncaughtException ${err?.stack || err}`)
24
+ })
25
+ process.on('unhandledRejection', err => {
26
+ log(`[BOOT] unhandledRejection ${err?.stack || err}`)
27
+ })
28
+
29
+ await import('./index.mjs')
30
+ log('[BOOT] imported index.mjs successfully')