@iamsamyiok/agents-chat 3.22.0 → 3.24.0

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/app/lib/cards.js CHANGED
@@ -343,12 +343,17 @@ class CardRunner {
343
343
  return true;
344
344
  }
345
345
 
346
- // 供前端展示:当前存活进程 + opencode 是否在工作中(近 5s 有活动即视为工作中)
346
+ // 供前端展示:当前存活进程 + 是否有近期活动
347
+ // 可靠性加固:
348
+ // 1) 死进程过滤——close 事件竞争窗口或异常挂起时,登记表可能残留已退出进程,
349
+ // 以 child.exitCode/killed 为准过滤,绝不显示死 PID(占位阶段 child=null 保留,代表启动中)
350
+ // 2) 活动窗口 15s——AI 长思考期间(无流式输出)不误报「空闲」
347
351
  getProcesses() {
348
352
  const out = [];
349
353
  const now = Date.now();
350
354
  for (const [cardId, p] of this.procs) {
351
- out.push({ cardId, pid: p.pid || null, working: (now - (p.lastActive || 0)) < 5000 });
355
+ if (p.child && (p.child.exitCode !== null || p.child.killed)) continue; // 已退出的子进程:不展示
356
+ out.push({ cardId, pid: p.pid || null, working: (now - (p.lastActive || 0)) < 15000 });
352
357
  }
353
358
  return out;
354
359
  }
@@ -14,10 +14,18 @@
14
14
  *{box-sizing:border-box}
15
15
  html,body{height:100%}
16
16
  body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
17
- background:var(--bg);color:var(--txt);font-size:14px;display:flex;flex-direction:column;overflow:hidden}
17
+ background:var(--bg);color:var(--txt);font-size:14px;display:flex;flex-direction:column;overflow:hidden;
18
+ -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
19
+ /* 细滚动条 + 键盘焦点环(与群聊页统一) */
20
+ ::-webkit-scrollbar{width:6px;height:6px}
21
+ ::-webkit-scrollbar-thumb{background:rgba(0,0,0,.16);border-radius:3px}
22
+ ::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.3)}
23
+ ::-webkit-scrollbar-track,::-webkit-scrollbar-corner{background:transparent}
24
+ html{scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.16) transparent}
25
+ button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid rgba(16,174,255,.55);outline-offset:1px}
18
26
  header{display:flex;align-items:center;gap:12px;padding:12px 18px;background:var(--panel);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:10}
19
27
  header h1{font-size:16px;margin:0;font-weight:600}
20
- .backlink{font-size:13px;color:var(--acc);text-decoration:none;border:1px solid var(--acc);border-radius:6px;padding:5px 10px}
28
+ .backlink{font-size:13px;color:var(--acc);text-decoration:none;border:1px solid var(--acc);border-radius:6px;padding:5px 10px;transition:background .15s}
21
29
  .backlink:hover{background:#f0faf4}
22
30
  .sp{flex:1}
23
31
  .btn{background:var(--panel);color:var(--txt);border:1px solid var(--line);border-radius:8px;padding:7px 12px;cursor:pointer;font-size:13px;transition:.15s}
@@ -29,8 +37,9 @@
29
37
  .btn.danger{border-color:var(--err);color:var(--err)}
30
38
  .btn:disabled{opacity:.45;cursor:not-allowed}
31
39
  .status-pill{font-size:12px;color:var(--dim)}
32
- .filter-box{display:flex;align-items:center;gap:4px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:5px 8px}
40
+ .filter-box{display:flex;align-items:center;gap:4px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:5px 8px;transition:border-color .15s}
33
41
  .filter-box input{border:none;outline:none;font-size:12px;width:130px;background:transparent;color:inherit}
42
+ .filter-box:focus-within{border-color:var(--acc2)}
34
43
  /* 详情复制按钮 */
35
44
  .copy-btn{border:1px solid var(--line);background:transparent;color:var(--dim);font-size:11px;border-radius:4px;padding:1px 8px;cursor:pointer;margin-left:6px}
36
45
  .copy-btn:hover{color:var(--acc2);border-color:var(--acc2)}
@@ -40,7 +49,8 @@
40
49
  #boardStats b{color:inherit}
41
50
  .done-bar{flex:1;height:6px;border-radius:3px;background:var(--line);overflow:hidden;max-width:360px}
42
51
  .done-bar i{display:block;height:100%;background:linear-gradient(90deg,var(--acc2),#2f9e44);border-radius:3px;transition:width .4s}
43
- .ws-box{display:flex;align-items:center;gap:6px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:5px 8px}
52
+ .ws-box{display:flex;align-items:center;gap:6px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:5px 8px;transition:border-color .15s}
53
+ .ws-box:focus-within{border-color:var(--acc)}
44
54
  .ws-box input{border:none;outline:none;font-size:12px;width:200px;background:transparent}
45
55
  .ws-box .ws-save{font-size:12px;color:var(--acc);cursor:pointer;white-space:nowrap}
46
56
  .ws-box .ws-pick{font-size:12px;color:var(--acc2);cursor:pointer;white-space:nowrap}
@@ -68,8 +78,8 @@
68
78
  .col .drop-zone{flex:1;overflow-y:auto;min-height:0}
69
79
  .col h2{font-size:13px;margin:0 0 10px;color:var(--dim);display:flex;align-items:center;gap:8px;text-transform:uppercase;letter-spacing:.5px}
70
80
  .dot{width:8px;height:8px;border-radius:50%}
71
- .card{background:#fafbfc;border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin-bottom:10px;cursor:grab;transition:.12s;position:relative}
72
- .card:hover{border-color:var(--acc);box-shadow:0 2px 8px rgba(0,0,0,.08)}
81
+ .card{background:#fafbfc;border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin-bottom:10px;cursor:grab;transition:.14s;position:relative}
82
+ .card:hover{border-color:var(--acc);box-shadow:0 3px 10px rgba(0,0,0,.09);transform:translateY(-1px)}
73
83
  .card.dragging{opacity:.4}
74
84
  .card.drag-over{border-color:var(--acc);border-style:dashed}
75
85
  .card .title{font-weight:600;margin-bottom:6px;line-height:1.4;word-break:break-word;padding-right:150px}
@@ -85,9 +95,11 @@
85
95
  .icon-btn{background:transparent;border:1px solid var(--line);color:var(--dim);border-radius:6px;width:24px;height:24px;cursor:pointer;font-size:12px;line-height:1}
86
96
  .icon-btn:hover{color:var(--acc);border-color:var(--acc)}
87
97
  .empty{color:var(--dim);font-size:12px;text-align:center;padding:18px 0}
88
- .mask{position:fixed;inset:0;background:rgba(0,0,0,.45);display:none;align-items:center;justify-content:center;z-index:50}
89
- .mask.show{display:flex}
90
- .modal{background:var(--panel);border:1px solid var(--line);border-radius:14px;width:min(680px,94vw);max-height:90vh;overflow:auto;padding:20px;box-shadow:var(--shadow)}
98
+ .mask{position:fixed;inset:0;background:rgba(0,0,0,.45);display:none;align-items:center;justify-content:center;z-index:50;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px)}
99
+ .mask.show{display:flex;animation:fade-in .18s ease}
100
+ @keyframes fade-in{from{opacity:0}to{opacity:1}}
101
+ @keyframes modal-in{from{opacity:0;transform:translateY(14px) scale(.985)}to{opacity:1;transform:none}}
102
+ .modal{background:var(--panel);border:1px solid var(--line);border-radius:14px;width:min(680px,94vw);max-height:90vh;overflow:auto;padding:20px;box-shadow:var(--shadow);animation:modal-in .24s cubic-bezier(.2,.7,.3,1)}
91
103
  .modal h3{margin:0 0 14px;font-size:16px}
92
104
  label{display:block;font-size:12px;color:var(--dim);margin:10px 0 4px}
93
105
  input,textarea,select{width:100%;background:#fff;border:1px solid var(--line);color:var(--txt);border-radius:8px;padding:8px 10px;font-size:13px;font-family:inherit}
@@ -919,7 +931,7 @@ $('#trashFab').onclick=openTrash;
919
931
  $('#trashClose').onclick=()=>$('#trashMask').classList.remove('show');
920
932
  $('#trashEmpty').onclick=async()=>{ if(!confirm('确认清空垃圾桶?所有快照及其过程日志将被永久删除,不可恢复。'))return; const r=await post('/api/cards/trash/empty'); toast('已清空 '+r.cleared+' 项'); await loadCards(); };
921
933
 
922
- // ---- 进程状态条(PID + 是否工作中) ----
934
+ // ---- 进程状态条(PID + 工作状态,SSE 事件驱动 + 2s 轮询兜底) ----
923
935
  async function refreshProcs(){
924
936
  try{
925
937
  const d = await api('/api/cards/processes');
@@ -928,8 +940,9 @@ async function refreshProcs(){
928
940
  const kname = (RUNNER_INFO && RUNNER_INFO.label) || 'opencode';
929
941
  if(!procs.length){ bar.innerHTML='<span class="ptitle">进程:</span><span class="proc-empty">无运行中的 '+esc(kname)+' 进程</span>'; return; }
930
942
  bar.innerHTML='<span class="ptitle">进程:</span>'+procs.map(p=>{
931
- const working=p.working;
932
- return `<span class="proc-chip ${working?'':'idle'}"><span class="dot"></span><span class="pid">PID ${p.pid}</span><span class="st">${working?'工作中':'空闲'}</span></span>`;
943
+ const pidLabel = p.pid ? ('PID '+p.pid) : '启动中…';
944
+ const st = p.working ? '工作中' : '等待输出';
945
+ return `<span class="proc-chip ${p.working?'':'idle'}" title="${p.working?'近 15 秒内有输出活动':'进程存活,暂无新输出(长思考属正常)'}"><span class="dot"></span><span class="pid">${pidLabel}</span><span class="st">${st}</span></span>`;
933
946
  }).join('');
934
947
  }catch{ /* ignore */ }
935
948
  }
@@ -994,6 +1007,8 @@ es.onmessage = (e)=>{
994
1007
  }
995
1008
  // proc 仅刷新进程条(另有 2s 轮询兜底),不重建看板 DOM
996
1009
  if(ev.type==='proc'){ refreshProcs(); }
1010
+ // 任务结束/编排停止:进程条目随之移除,立即刷新进程条(不等 2s 轮询)
1011
+ if(ev.type==='task_done'||ev.type==='followup_done'||ev.type==='runner_stopped') refreshProcs();
997
1012
  // tool 事件仅在打开对应详情时追加过程日志,避免工具密集时高频全量刷新
998
1013
  if(ev.type==='tool' && OPEN_ID===ev.cardId){
999
1014
  const log=$('#dLog'); if(log.querySelector('.empty')) log.innerHTML='';
@@ -10,7 +10,16 @@
10
10
  body {
11
11
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
12
12
  background: #f2f2f2; color: #191919; overflow: hidden;
13
- }
13
+ -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
14
+ }
15
+ /* 细滚动条:与界面精致度一致(Chrome/Safari + Firefox) */
16
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
17
+ ::-webkit-scrollbar-thumb { background: rgba(0,0,0,.16); border-radius: 3px; }
18
+ ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,.3); }
19
+ ::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
20
+ html { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,.16) transparent; }
21
+ /* 键盘导航焦点环:统一品牌绿描边 */
22
+ button:focus-visible, a:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible { outline: 2px solid rgba(7,193,96,.55); outline-offset: 1px; }
14
23
  #app { display: flex; height: 100vh; max-width: 1240px; margin: 0 auto; background: #fff; box-shadow: 0 0 24px rgba(0,0,0,.08); }
15
24
 
16
25
  /* ---------- 左侧:会话列表(微信风格:主会话 + 任务会话) ---------- */
@@ -19,13 +28,13 @@
19
28
  .side-header h2 { font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 6px; }
20
29
  .badge { font-size: 11px; background: #fa5151; color: #fff; border-radius: 8px; padding: 0 6px; line-height: 16px; }
21
30
  .side-actions { display: flex; gap: 6px; }
22
- .mini-btn { font-size: 12px; color: #07c160; border: 1px solid #07c160; background: #fff; border-radius: 4px; padding: 3px 8px; cursor: pointer; }
31
+ .mini-btn { font-size: 12px; color: #07c160; border: 1px solid #07c160; background: #fff; border-radius: 6px; padding: 3px 8px; cursor: pointer; transition: background .15s, opacity .15s; }
23
32
  .mini-btn.gray { color: #888; border-color: #ddd; }
24
- .mini-btn:hover { opacity: .8; }
33
+ .mini-btn:hover { opacity: .85; background: #f0faf4; }
25
34
 
26
35
  .task-empty { color: #b0b0b0; font-size: 13px; text-align: center; padding: 30px 20px; line-height: 1.8; }
27
36
  .list-sep { font-size: 11px; color: #b2b2b2; padding: 10px 14px 4px; background: #fff; position: sticky; top: 0; z-index: 1; }
28
- .task-item { display: flex; gap: 10px; padding: 12px 14px; border-bottom: 1px solid #f5f5f5; cursor: pointer; align-items: center; position: relative; }
37
+ .task-item { display: flex; gap: 10px; padding: 12px 14px; border-bottom: 1px solid #f5f5f5; cursor: pointer; align-items: center; position: relative; transition: background .15s; }
29
38
  .task-item:hover { background: #f7f7f7; }
30
39
  .task-item.active { background: #eef7f0; }
31
40
  /* 未读绿点:会话有未查看的完成消息 */
@@ -61,7 +70,7 @@
61
70
  .runner-tag.opencode { color: #07c160; background: #eef7f0; }
62
71
  .runner-tag.demo { color: #c2821d; background: #faf3e3; }
63
72
  .runner-tag.missing { color: #d54941; background: #fbeceb; }
64
- .cfg-btn { font-size: 12px; color: #07c160; border: 1px solid #07c160; background: #fff; border-radius: 4px; padding: 4px 10px; cursor: pointer; white-space: nowrap; }
73
+ .cfg-btn { font-size: 12px; color: #07c160; border: 1px solid #07c160; background: #fff; border-radius: 6px; padding: 4px 10px; cursor: pointer; white-space: nowrap; transition: background .15s; }
65
74
  .cfg-btn:hover { background: #f0faf4; }
66
75
  .hdr-right { display: flex; align-items: center; gap: 10px; }
67
76
 
@@ -72,7 +81,7 @@
72
81
  .copy-btn:hover { background: rgba(0,0,0,.1); color: #333; }
73
82
  .copy-btn.done { color: #07c160; opacity: 1; }
74
83
  /* 回到最新:阅读历史时新消息不再强制拽底,悬浮按钮一键回底 */
75
- #jumpBtn { position: absolute; right: 18px; bottom: 100px; z-index: 5; display: none; border: 1px solid #dcdcdc; background: #fff; color: #333; border-radius: 16px; padding: 6px 14px; font-size: 12px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,.15); }
84
+ #jumpBtn { position: absolute; right: 18px; bottom: 100px; z-index: 5; display: none; border: 1px solid #dcdcdc; background: #fff; color: #333; border-radius: 16px; padding: 6px 14px; font-size: 12px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,.15); animation: fade-in .2s ease; transition: color .15s, border-color .15s; }
76
85
  #jumpBtn:hover { color: #07c160; border-color: #07c160; }
77
86
  .sys-tip { text-align: center; margin: 10px 0; }
78
87
  .approval-card { max-width: 480px; margin: 10px auto; background: #fff8e6; border: 1px solid #e8a33d; border-radius: 10px; padding: 10px 14px; text-align: left; }
@@ -83,8 +92,12 @@
83
92
  .approval-card .ap-ok { background: #07c160; color: #fff; }
84
93
  .approval-card .ap-no { background: #fa5151; color: #fff; }
85
94
  .approval-card .ap-done { font-size: 12px; color: #888; text-align: center; padding: 2px 0; }
86
- .sys-tip span { font-size: 12px; color: #9e9e9e; background: #e8e8e8; border-radius: 4px; padding: 3px 10px; display: inline-block; max-width: 80%; }
87
- .msg-row { display: flex; gap: 10px; margin: 14px 0; align-items: flex-start; }
95
+ .sys-tip span { font-size: 12px; color: #9e9e9e; background: #e8e8e8; border-radius: 8px; padding: 3px 10px; display: inline-block; max-width: 80%; animation: msg-in .18s ease; }
96
+ #messages.bulk .sys-tip span { animation: none; }
97
+ .msg-row { display: flex; gap: 10px; margin: 14px 0; align-items: flex-start; animation: msg-in .18s ease; }
98
+ /* 批量重建(切换会话/打开历史)时抑制逐条动画,避免满屏闪烁 */
99
+ #messages.bulk .msg-row, #messages.bulk .sys-tip { animation: none; }
100
+ @keyframes msg-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
88
101
  .msg-row.self { flex-direction: row-reverse; }
89
102
  .avatar { width: 38px; height: 38px; border-radius: 6px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 14px; }
90
103
  .avatar.me { background: #57be6a; }
@@ -116,8 +129,8 @@
116
129
 
117
130
  .composer { background: #fff; border-top: 1px solid #e7e7e7; padding: 12px 14px; position: relative; }
118
131
  .input-row { display: flex; gap: 10px; }
119
- #input, #soloInput { flex: 1; border: 1px solid #e2e2e2; border-radius: 6px; padding: 10px 12px; font-size: 14px; outline: none; }
120
- #input:focus, #soloInput:focus { border-color: #07c160; }
132
+ #input, #soloInput { flex: 1; border: 1px solid #e2e2e2; border-radius: 6px; padding: 10px 12px; font-size: 14px; outline: none; transition: border-color .15s, box-shadow .15s; background: #fff; }
133
+ #input:focus, #soloInput:focus { border-color: #07c160; box-shadow: 0 0 0 3px rgba(7,193,96,.12); }
121
134
  #sendBtn, #soloSendBtn { background: #07c160; color: #fff; border: none; border-radius: 6px; padding: 0 22px; font-size: 14px; cursor: pointer; }
122
135
  #rtBtn { background: #f2f3f5; color: #555; border: none; border-radius: 6px; padding: 0 14px; font-size: 13px; cursor: pointer; white-space: nowrap; }
123
136
  #rtBtn:hover { background: #e8eaee; }
@@ -190,6 +203,12 @@
190
203
 
191
204
  /* ---------- 弹窗 ---------- */
192
205
  #modalMask, #batchMask, #agentsMask, #historyMask, #helpMask, #flowMask, #cardMask, #teamsMask, #memMask { position: fixed; inset: 0; background: rgba(0,0,0,.4); display: none; align-items: center; justify-content: center; z-index: 51; }
206
+ /* 弹窗出现动画:遮罩淡入(含毛玻璃)+ 内容上浮,全站弹窗统一节奏 */
207
+ div[id$="Mask"] { backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
208
+ div[id$="Mask"].show { animation: fade-in .18s ease; }
209
+ .modal, .preview-card { animation: modal-in .24s cubic-bezier(.2,.7,.3,1); }
210
+ @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
211
+ @keyframes modal-in { from { opacity: 0; transform: translateY(14px) scale(.985); } to { opacity: 1; transform: none; } }
193
212
  #modalMask.show, #batchMask.show, #agentsMask.show, #historyMask.show, #helpMask.show, #flowMask.show, #cardMask.show, #teamsMask.show, #memMask.show { display: flex; }
194
213
  /* 团队仓库卡片 */
195
214
  .team-card { border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; }
@@ -1778,6 +1797,7 @@ function msgNode(m) {
1778
1797
  // 全量重绘当前会话(切换会话 / 载入历史时)
1779
1798
  function renderMessages() {
1780
1799
  const box = document.getElementById('messages');
1800
+ box.classList.add('bulk'); // 批量重建:抑制逐条入场动画
1781
1801
  box.innerHTML = '';
1782
1802
  for (const m of getSess(curKey())) {
1783
1803
  const node = msgNode(m);
@@ -1786,6 +1806,7 @@ function renderMessages() {
1786
1806
  finalizeMsgLinks(m); // 定稿消息中的 .md 路径变成可点击预览链接
1787
1807
  }
1788
1808
  scrollBottom();
1809
+ requestAnimationFrame(() => requestAnimationFrame(() => box.classList.remove('bulk')));
1789
1810
  const jb = document.getElementById('jumpBtn'); if (jb) jb.style.display = 'none';
1790
1811
  }
1791
1812
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamsamyiok/agents-chat",
3
- "version": "3.22.0",
3
+ "version": "3.24.0",
4
4
  "description": "多智能体群聊工具 - 支持 OpenCode/Claude Code/Codex/pi 内核,微信风格聊天界面",
5
5
  "main": "lib/start.js",
6
6
  "bin": {