@iamsamyiok/agents-chat 3.19.1 → 3.19.2

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
@@ -356,6 +356,9 @@ class CardRunner {
356
356
  let sesId = ocSessionId;
357
357
 
358
358
  let child = null;
359
+ // 进程登记:spawn 前占位(执行中即可见于进程条),spawn 后回填真实 PID
360
+ this.procs.set(card.id, { pid: null, child: null, lastActive: Date.now() });
361
+ broadcast({ type: 'proc', cardId: card.id, pid: null });
359
362
  try {
360
363
  await new Promise((resolve) => {
361
364
  child = oc.chatSolo(kind, runner, {
@@ -385,15 +388,15 @@ class CardRunner {
385
388
  resolve();
386
389
  }
387
390
  });
391
+ // chatSolo 同步返回 child(spawn 已完成):立即回填真实 PID,执行中即可见于进程条
392
+ const proc0 = this.procs.get(card.id);
393
+ if (proc0 && child) { proc0.pid = child.pid; proc0.child = child; }
394
+ broadcast({ type: 'proc', cardId: card.id, pid: child ? child.pid : null });
388
395
  });
389
396
  } catch (err) {
390
397
  doneError = String((err && err.message) || err).slice(0, 2000);
391
398
  }
392
399
 
393
- // 登记进程信息(PID + 是否工作中),并广播给前端
394
- this.procs.set(card.id, { pid: child ? child.pid : null, child, lastActive: Date.now() });
395
- broadcast({ type: 'proc', cardId: card.id, pid: child ? child.pid : null });
396
-
397
400
  const finalText = order.map(id => texts.get(id)).join('\n\n').trim();
398
401
  const stopped = myToken !== this.token;
399
402
  if (finalText) {
@@ -403,8 +406,9 @@ class CardRunner {
403
406
  CardStore.update(card.id, {
404
407
  status,
405
408
  ocSessionId: sesId,
406
- result: (doneError ? `执行出错:${doneError}` : finalText).slice(0, 20000),
407
- error: doneError || '',
409
+ // 手动停止回到待执行:清掉残留,避免 pending 卡带着脏结果/错误
410
+ result: stopped ? '' : (doneError ? `执行出错:${doneError}` : finalText).slice(0, 20000),
411
+ error: stopped ? '' : (doneError || ''),
408
412
  finishedAt: Date.now()
409
413
  });
410
414
  broadcast({ type: 'task_done', cardId: card.id, status, title: card.title });
@@ -415,6 +419,8 @@ class CardRunner {
415
419
  async runOne(cardId) {
416
420
  const card = CardStore.get(cardId);
417
421
  if (!card || card.status === 'running') return false;
422
+ // 并发防护:追加聊天进行中的卡不可同时执行(避免同一会话被两条链路并发续写)
423
+ if (this.followups.has(cardId)) return false;
418
424
  this.active.add(cardId);
419
425
  const myToken = this.token;
420
426
  await this.runCard(card, myToken).catch(() => {});
@@ -428,6 +434,7 @@ class CardRunner {
428
434
  const card = CardStore.get(cardId);
429
435
  if (!card) return { ok: false, error: '任务不存在' };
430
436
  if (card.status === 'running' || card.status === 'pending') return { ok: false, error: '任务尚未执行完成,先运行任务再追加聊天' };
437
+ if (this.active.has(cardId)) return { ok: false, error: '任务正在执行中,稍后再追加聊天' };
431
438
  if (this.followups.has(cardId)) return { ok: false, error: '该任务已有追加聊天进行中' };
432
439
  this.followups.add(cardId);
433
440
 
@@ -449,6 +456,8 @@ class CardRunner {
449
456
  if (!userText) return { ok: false, error: '请输入追加内容' };
450
457
  store.addMessage({ role: 'user', agentId: 'solo', agentName: '我', actor: 'user', phase: 'followup', taskId: cardId, content: userText.slice(0, 20000) });
451
458
  broadcast({ type: 'followup_start', cardId: cardId });
459
+ // 进程登记:spawn 前占位,spawn 后回填 PID(追加聊天执行中亦可见于进程条)
460
+ this.procs.set(cardId, { pid: null, child: null, lastActive: Date.now() });
452
461
 
453
462
  const texts = new Map();
454
463
  const order = [];
@@ -481,9 +490,14 @@ class CardRunner {
481
490
  resolve();
482
491
  }
483
492
  });
493
+ const proc0 = this.procs.get(cardId);
494
+ if (proc0 && child) { proc0.pid = child.pid; proc0.child = child; }
495
+ broadcast({ type: 'proc', cardId, pid: child ? child.pid : null });
484
496
  });
485
497
  } catch (err) {
486
498
  doneError = String((err && err.message) || err).slice(0, 2000);
499
+ } finally {
500
+ this.procs.delete(cardId);
487
501
  }
488
502
 
489
503
  const finalText = order.map(id => texts.get(id)).join('\n\n').trim();
@@ -525,8 +525,15 @@ function fuSetState(running, tipText){
525
525
  btn.textContent = running ? '回复中…' : '发送';
526
526
  if(tipText!==undefined) $('#fuTip').textContent = tipText;
527
527
  }
528
+ let DETAIL_SEQ = 0; // 详情加载序号:快速切换任务时只认最新请求(响应乱序防护)
528
529
  async function openDetail(id){
529
- OPEN_ID=id; const data=await api('/api/cards/'+id+'/log'); const card=data.card; const msgs=data.messages||[];
530
+ if(!id) return;
531
+ const seq = ++DETAIL_SEQ;
532
+ OPEN_ID = id;
533
+ const data = await api('/api/cards/'+encodeURIComponent(id)+'/log');
534
+ if (seq !== DETAIL_SEQ || OPEN_ID !== id) return; // 已切换到其他任务或关闭弹窗:丢弃过期响应
535
+ if(!data || !data.success || !data.card){ toast('任务不存在或已被删除'); return; }
536
+ const card=data.card; const msgs=data.messages||[];
530
537
  $('#dTitle').textContent=card.title;
531
538
  $('#dMeta').innerHTML=badge(card)+`<span class="badge">${card.status}</span>`;
532
539
  const depTitles = (card.dependsOn||[]).map(d=>{const c=CARDS.find(x=>x.id===d);return c?c.title:d;});
@@ -560,7 +567,7 @@ async function openDetail(id){
560
567
  fuSetState(FU_RUNNING);
561
568
  $('#maskDetail').classList.add('show');
562
569
  }
563
- $('#dClose').onclick=()=>{ OPEN_ID=null; $('#maskDetail').classList.remove('show'); };
570
+ $('#dClose').onclick=()=>{ OPEN_ID=null; DETAIL_SEQ++; $('#maskDetail').classList.remove('show'); };
564
571
  $('#dRun').onclick=async()=>{ if(!OPEN_ID)return; await runOne(OPEN_ID); openDetail(OPEN_ID); };
565
572
 
566
573
  // ---- 追加聊天:复用会话第二轮输入 ----
@@ -774,7 +781,7 @@ es.onmessage = (e)=>{
774
781
  const log=$('#dLog'); if(log.querySelector('.empty')) log.innerHTML='';
775
782
  if(ev.type==='task_start') TEXT_PARTS.clear();
776
783
  else if(ev.type==='tool'){ appendLog(log,'🔧 ['+ev.name+'] '+(ev.summary||''),'msg-tool'); log.scrollTop=log.scrollHeight; }
777
- else if(ev.type==='task_done'){ loadCards().then(()=>openDetail(OPEN_ID)); }
784
+ else if(ev.type==='task_done'){ loadCards().then(()=>{ if(OPEN_ID===ev.cardId) openDetail(ev.cardId); }); }
778
785
  }
779
786
  }
780
787
  // 正文快照独立渲染(不依赖上面的状态事件)
@@ -783,7 +790,7 @@ es.onmessage = (e)=>{
783
790
  if(ev.type==='followup_done' && OPEN_ID===ev.cardId){
784
791
  fuSetState(false);
785
792
  if(ev.error){ appendLog($('#dLog'),'⚠ '+ev.error,'msg-err'); toast('追加聊天失败:'+errSummary(ev.error)); }
786
- loadCards().then(()=>openDetail(OPEN_ID));
793
+ loadCards().then(()=>{ if(OPEN_ID===ev.cardId) openDetail(ev.cardId); });
787
794
  }
788
795
  };
789
796
  es.onerror=()=>{};
@@ -634,7 +634,7 @@
634
634
  </div>
635
635
 
636
636
  <script>
637
- const PAGE_VERSION = '3.19.1'; // 与服务端 /api/health.version 互检,不一致说明页面缓存过期
637
+ const PAGE_VERSION = '3.19.2'; // 与服务端 /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.1'; // 页面与服务端版本互检,不一致提示强刷
3
+ const APP_VERSION = '3.19.2'; // 页面与服务端版本互检,不一致提示强刷
4
4
  const http = require('http');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
@@ -1111,9 +1111,9 @@ const server = http.createServer(async (req, res) => {
1111
1111
  if (body.title !== undefined) patch.title = String(body.title).slice(0, 500);
1112
1112
  if (body.content !== undefined) patch.content = String(body.content).slice(0, 20000);
1113
1113
  if (body.priority !== undefined) patch.priority = Number(body.priority) || 999;
1114
- if (body.mode !== undefined) patch.mode = body.mode;
1115
- if (body.chainId !== undefined) patch.chainId = body.chainId;
1116
- if (Array.isArray(body.dependsOn)) patch.dependsOn = body.dependsOn.map(String);
1114
+ if (body.mode !== undefined) patch.mode = ['new', 'continue', 'parallel'].includes(body.mode) ? body.mode : 'new';
1115
+ if (body.chainId !== undefined) patch.chainId = String(body.chainId).slice(0, 100);
1116
+ if (Array.isArray(body.dependsOn)) patch.dependsOn = body.dependsOn.map(String).slice(0, 50);
1117
1117
  if (body.model !== undefined) patch.model = String(body.model).slice(0, 80);
1118
1118
  // 状态手动复位:failed/pending -> pending 可重跑
1119
1119
  if (body.status === 'pending') { patch.status = 'pending'; patch.result = ''; patch.error = ''; patch.ocSessionId = ''; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamsamyiok/agents-chat",
3
- "version": "3.19.1",
3
+ "version": "3.19.2",
4
4
  "description": "多智能体群聊工具 - 支持 OpenCode/Claude Code/Codex/pi 内核,微信风格聊天界面",
5
5
  "main": "lib/start.js",
6
6
  "bin": {