@iamsamyiok/agents-chat 3.19.0 → 3.19.1

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
@@ -227,6 +227,8 @@ class CardRunner {
227
227
  this.running = false;
228
228
  this.timer = null;
229
229
  this.procs = new Map(); // cardId -> { pid, child, lastActive, status }
230
+ this.followups = new Set(); // 追加聊天中的卡牌(并发防护)
231
+ this.baseline = null; // 本轮编排开始时的 done/failed 基线(all_done 报增量)
230
232
  }
231
233
  isRunning() { return this.running; }
232
234
 
@@ -271,6 +273,9 @@ class CardRunner {
271
273
  if (this.running) return;
272
274
  this.running = true;
273
275
  this.token++;
276
+ // 记录基线:all_done 时报告本轮增量(成功/失败数),避免混入历史任务
277
+ const all0 = CardStore.list();
278
+ this.baseline = { done: all0.filter(c => c.status === 'done').length, failed: all0.filter(c => c.status === 'failed').length };
274
279
  broadcast({ type: 'runner_started' });
275
280
  this.tick();
276
281
  }
@@ -283,9 +288,11 @@ class CardRunner {
283
288
  if (!eligible.length) {
284
289
  if (this.active.size === 0) {
285
290
  this.running = false;
286
- // 完成通知附统计(成功/失败数),前端据此提醒
287
- const done = all.filter(c => c.status === 'done').length;
288
- const failed = all.filter(c => c.status === 'failed').length;
291
+ // 完成通知附本轮增量统计(成功/失败数),前端据此提醒
292
+ const all = CardStore.list();
293
+ const done = Math.max(0, all.filter(c => c.status === 'done').length - (this.baseline ? this.baseline.done : 0));
294
+ const failed = Math.max(0, all.filter(c => c.status === 'failed').length - (this.baseline ? this.baseline.failed : 0));
295
+ this.baseline = null;
289
296
  broadcast({ type: 'all_done', done, failed });
290
297
  }
291
298
  return;
@@ -421,8 +428,7 @@ class CardRunner {
421
428
  const card = CardStore.get(cardId);
422
429
  if (!card) return { ok: false, error: '任务不存在' };
423
430
  if (card.status === 'running' || card.status === 'pending') return { ok: false, error: '任务尚未执行完成,先运行任务再追加聊天' };
424
- if (this.followups && this.followups.has(cardId)) return { ok: false, error: '该任务已有追加聊天进行中' };
425
- if (!this.followups) this.followups = new Set();
431
+ if (this.followups.has(cardId)) return { ok: false, error: '该任务已有追加聊天进行中' };
426
432
  this.followups.add(cardId);
427
433
 
428
434
  const runner = resolveRunner();
@@ -540,6 +540,8 @@ async function openDetail(id){
540
540
  if(!msgs.length) log.innerHTML='<span class="empty">暂无过程记录</span>';
541
541
  for(const m of msgs){
542
542
  if(m.role==='user') appendLog(log,'👤 '+m.content,'msg-user');
543
+ else if(m.phase==='archive') appendLog(log,'📦 '+m.content,'msg-tool');
544
+ else if(m.phase==='system') appendLog(log,'⚠ '+m.content,'msg-err');
543
545
  else if(/工具|执行完成/.test(m.content||'')) appendLog(log,'🔧 '+m.content,'msg-tool');
544
546
  else if(/出错|失败/.test(m.content||'')) appendLog(log,'⚠ '+m.content,'msg-err');
545
547
  else appendLog(log,'🤖 '+(m.content||'').slice(0,4000),'msg-assistant');
@@ -586,7 +588,7 @@ let G_SIG=''; // 结构+状态指纹:无变化时跳过重绘(避免流式
586
588
  function renderGraph(force){
587
589
  const box=$('#graphBody');
588
590
  const cards=CARDS;
589
- const sig=cards.map(c=>c.id+':'+c.status+':'+(c.dependsOn||[]).join(',')).join('|');
591
+ const sig=cards.map(c=>c.id+':'+c.status+':'+(c.dependsOn||[]).join(',')+':'+c.title+':'+c.mode).join('|');
590
592
  if(!force && sig===G_SIG && box.querySelector('svg')) return;
591
593
  G_SIG=sig;
592
594
  if(!cards.length){ box.innerHTML='<div class="empty" style="padding:24px 8px">暂无任务<br/>新建任务后此处展示依赖关系</div>'; return; }
@@ -764,17 +766,19 @@ es.onmessage = (e)=>{
764
766
  }
765
767
  if(ev.type==='ws_warning'){ toast('⚠ 工作区路径无效:'+ev.path+',任务将在默认目录执行'); }
766
768
  if(ev.type==='followup_start'||ev.type==='followup_done'){ loadCards(); }
767
- if(['task_start','text','tool','task_done','proc'].includes(ev.type)){
769
+ // text 为正文快照(不改变卡片状态):仅实时渲染,不触发全量刷新,避免流式期间频繁重建 DOM
770
+ if(['task_start','tool','task_done','proc'].includes(ev.type)){
768
771
  loadCards();
769
772
  refreshProcs();
770
773
  if(OPEN_ID && ev.cardId===OPEN_ID){
771
774
  const log=$('#dLog'); if(log.querySelector('.empty')) log.innerHTML='';
772
775
  if(ev.type==='task_start') TEXT_PARTS.clear();
773
- if(ev.type==='text') renderSnapText(ev);
774
776
  else if(ev.type==='tool'){ appendLog(log,'🔧 ['+ev.name+'] '+(ev.summary||''),'msg-tool'); log.scrollTop=log.scrollHeight; }
775
777
  else if(ev.type==='task_done'){ loadCards().then(()=>openDetail(OPEN_ID)); }
776
778
  }
777
779
  }
780
+ // 正文快照独立渲染(不依赖上面的状态事件)
781
+ if(ev.type==='text' && OPEN_ID===ev.cardId) renderSnapText(ev);
778
782
  if(ev.type==='followup_start' && OPEN_ID===ev.cardId) TEXT_PARTS.clear();
779
783
  if(ev.type==='followup_done' && OPEN_ID===ev.cardId){
780
784
  fuSetState(false);
@@ -634,7 +634,7 @@
634
634
  </div>
635
635
 
636
636
  <script>
637
- const PAGE_VERSION = '3.19.0'; // 与服务端 /api/health.version 互检,不一致说明页面缓存过期
637
+ const PAGE_VERSION = '3.19.1'; // 与服务端 /api/health.version 互检,不一致说明页面缓存过期
638
638
  const AV_COLORS = ['#5b8def','#07c160','#fa9d3b','#10aeff','#8a6fe8','#fa5151','#e8a33d','#3aa7a3'];
639
639
  const PHASE_LABEL = { plan: '调度规划', work: '执行', review: '验收', report: '汇总', talk: '圆桌发言', task: '任务执行' };
640
640
  // 职业图标库(智能体配置可选)
package/app/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Agents Chat Portable - 零依赖 HTTP 服务
2
2
  // 启动:node app/server.js [--port 3456]
3
- const APP_VERSION = '3.19.0'; // 页面与服务端版本互检,不一致提示强刷
3
+ const APP_VERSION = '3.19.1'; // 页面与服务端版本互检,不一致提示强刷
4
4
  const http = require('http');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
@@ -968,22 +968,24 @@ const server = http.createServer(async (req, res) => {
968
968
  }
969
969
  if (p === '/api/cards/config' && req.method === 'POST') {
970
970
  const body = await readBody(req);
971
- const cfg = CardStore.setConfig({ workspace: String(body.workspace || '').trim().slice(0, 500) });
972
- // 工作区校验:保存允许(可能是尚未创建的目录),但路径不存在时显式警告
973
- let warning = '';
974
- const ws = (cfg.workspace || '').trim();
975
- if (body.workspace !== undefined && ws) {
976
- try { if (!fs.existsSync(ws) || !fs.statSync(ws).isDirectory()) warning = `工作区路径当前不存在或不是目录:${ws},任务执行时将回退到默认目录`; } catch { warning = `工作区路径无法访问:${ws}`; }
977
- }
978
- // 并行度(1-8):热更新,调度器每轮 tick 动态读取
971
+ // patch 语义合并:仅写入请求中显式出现的字段(避免单项更新时清空另一项)
972
+ const patch = {};
973
+ if (body.workspace !== undefined) patch.workspace = String(body.workspace || '').trim().slice(0, 500);
979
974
  let parallelBad = false;
980
975
  if (body.maxParallel !== undefined) {
981
976
  const n = Number(body.maxParallel);
982
- if (n >= 1 && n <= 8) CardStore.setConfig({ maxParallel: Math.floor(n) });
977
+ if (n >= 1 && n <= 8) patch.maxParallel = Math.floor(n);
983
978
  else parallelBad = true;
984
979
  }
985
980
  if (parallelBad) { json(res, 400, { success: false, error: 'maxParallel 需在 1-8 之间' }); return; }
986
- json(res, 200, { success: true, config: CardStore.getConfig(), warning });
981
+ const cfg = Object.keys(patch).length ? CardStore.setConfig(patch) : CardStore.getConfig();
982
+ // 工作区校验:保存允许(可能是尚未创建的目录),但路径不存在时显式警告
983
+ let warning = '';
984
+ const ws = (cfg.workspace || '').trim();
985
+ if (patch.workspace !== undefined && ws) {
986
+ try { if (!fs.existsSync(ws) || !fs.statSync(ws).isDirectory()) warning = `工作区路径当前不存在或不是目录:${ws},任务执行时将回退到默认目录`; } catch { warning = `工作区路径无法访问:${ws}`; }
987
+ }
988
+ json(res, 200, { success: true, config: cfg, warning });
987
989
  return;
988
990
  }
989
991
 
@@ -1152,7 +1154,7 @@ const server = http.createServer(async (req, res) => {
1152
1154
  // SSE:实时推送卡牌生命周期事件(task_start/text/tool/task_done/all_done/runner_*)
1153
1155
  const send = sse(req, res);
1154
1156
  const unsub = sseSubscribe(send);
1155
- send({ type: 'init', running: cardRunner.isRunning(), maxParallel: MAX_PARALLEL });
1157
+ send({ type: 'init', running: cardRunner.isRunning(), maxParallel: cardRunner.maxP() });
1156
1158
  req.on('close', unsub);
1157
1159
  return;
1158
1160
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamsamyiok/agents-chat",
3
- "version": "3.19.0",
3
+ "version": "3.19.1",
4
4
  "description": "多智能体群聊工具 - 支持 OpenCode/Claude Code/Codex/pi 内核,微信风格聊天界面",
5
5
  "main": "lib/start.js",
6
6
  "bin": {