@iamsamyiok/agents-chat 3.23.0 → 3.25.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
  }
package/app/lib/store.js CHANGED
@@ -508,6 +508,13 @@ function getMessages(taskId) {
508
508
  return Array.isArray(list) ? list : [];
509
509
  }
510
510
 
511
+ // 单会话消息上限:防止定时任务长跑数月将会话分片膨胀到读写不可承受
512
+ // 默认 500 条,AGENTS_CHAT_MSG_LIMIT 可调(0 = 不限制);主会话豁免(已有 epoch + 过期清理机制)
513
+ const MSG_LIMIT = (() => {
514
+ const n = Number(process.env.AGENTS_CHAT_MSG_LIMIT);
515
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 500;
516
+ })();
517
+
511
518
  function addMessage(msg) {
512
519
  migrateLegacyMessages();
513
520
  fs.mkdirSync(MSG_DIR, { recursive: true });
@@ -529,9 +536,38 @@ function addMessage(msg) {
529
536
  timestamp: msg.timestamp || new Date().toISOString()
530
537
  };
531
538
  msgs.push(rec);
539
+ // 滚动清理:超上限裁掉最旧消息,开头留一条系统标记说明去向(标记本身也会随时间被裁)
540
+ if (key !== '' && MSG_LIMIT > 0 && msgs.length > MSG_LIMIT) {
541
+ const kept = msgs.slice(msgs.length - MSG_LIMIT);
542
+ const first = kept[0];
543
+ if (!(first && first.role === 'sys' && /已按上限自动清理/.test(String(first.content || '')))) {
544
+ kept.unshift({ role: 'sys', content: `(已达单会话消息上限 ${MSG_LIMIT} 条,更早的过程消息已自动滚动清理,任务结果以最终结果区为准)`, taskId: key, timestamp: new Date().toISOString() });
545
+ }
546
+ msgs.length = 0;
547
+ msgs.push(...kept);
548
+ }
532
549
  writeJson(msgShardPath(key), msgs);
533
550
  }
534
551
 
552
+ // 按会话统计消息数(逐分片累加,不合并大数组:统计/导出场景低内存)
553
+ function countMessagesByTask() {
554
+ migrateLegacyMessages();
555
+ const counts = {};
556
+ let main = 0;
557
+ let files = [];
558
+ try { files = fs.readdirSync(MSG_DIR); } catch { return { counts, main }; }
559
+ for (const name of files) {
560
+ if (!name.endsWith('.json') || name.startsWith('.')) continue;
561
+ let list;
562
+ try { list = JSON.parse(fs.readFileSync(path.join(MSG_DIR, name), 'utf8')); } catch { continue; }
563
+ if (!Array.isArray(list) || !list.length) continue;
564
+ const tid = list[0].taskId || '';
565
+ if (tid) counts[tid] = (counts[tid] || 0) + list.length;
566
+ else main += list.length;
567
+ }
568
+ return { counts, main };
569
+ }
570
+
535
571
  function clearMessages() {
536
572
  migrateLegacyMessages();
537
573
  // 清空全部会话消息:删除所有分片,主会话分片重置为空数组
@@ -754,6 +790,7 @@ module.exports = {
754
790
  getMessages,
755
791
  addMessage,
756
792
  clearMessages,
793
+ countMessagesByTask,
757
794
  addFlowEvent,
758
795
  getFlow,
759
796
  listFlowRuns,
@@ -753,7 +753,8 @@ async function openDetail(id){
753
753
  const log=$('#dLog'); log.innerHTML=''; TEXT_PARTS.clear();
754
754
  if(!msgs.length) log.innerHTML='<span class="empty">暂无过程记录</span>';
755
755
  for(const m of msgs){
756
- if(m.role==='user') appendLog(log,'👤 '+m.content,'msg-user');
756
+ if(m.role==='sys') appendLog(log,' '+m.content,'msg-tool');
757
+ else if(m.role==='user') appendLog(log,'👤 '+m.content,'msg-user');
757
758
  else if(m.phase==='archive') appendLog(log,'📦 '+m.content,'msg-tool');
758
759
  else if(m.phase==='system') appendLog(log,'⚠ '+m.content,'msg-err');
759
760
  else if(/工具|执行完成/.test(m.content||'')) appendLog(log,'🔧 '+m.content,'msg-tool');
@@ -931,7 +932,7 @@ $('#trashFab').onclick=openTrash;
931
932
  $('#trashClose').onclick=()=>$('#trashMask').classList.remove('show');
932
933
  $('#trashEmpty').onclick=async()=>{ if(!confirm('确认清空垃圾桶?所有快照及其过程日志将被永久删除,不可恢复。'))return; const r=await post('/api/cards/trash/empty'); toast('已清空 '+r.cleared+' 项'); await loadCards(); };
933
934
 
934
- // ---- 进程状态条(PID + 是否工作中) ----
935
+ // ---- 进程状态条(PID + 工作状态,SSE 事件驱动 + 2s 轮询兜底) ----
935
936
  async function refreshProcs(){
936
937
  try{
937
938
  const d = await api('/api/cards/processes');
@@ -940,8 +941,9 @@ async function refreshProcs(){
940
941
  const kname = (RUNNER_INFO && RUNNER_INFO.label) || 'opencode';
941
942
  if(!procs.length){ bar.innerHTML='<span class="ptitle">进程:</span><span class="proc-empty">无运行中的 '+esc(kname)+' 进程</span>'; return; }
942
943
  bar.innerHTML='<span class="ptitle">进程:</span>'+procs.map(p=>{
943
- const working=p.working;
944
- return `<span class="proc-chip ${working?'':'idle'}"><span class="dot"></span><span class="pid">PID ${p.pid}</span><span class="st">${working?'工作中':'空闲'}</span></span>`;
944
+ const pidLabel = p.pid ? ('PID '+p.pid) : '启动中…';
945
+ const st = p.working ? '工作中' : '等待输出';
946
+ 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>`;
945
947
  }).join('');
946
948
  }catch{ /* ignore */ }
947
949
  }
@@ -1006,6 +1008,8 @@ es.onmessage = (e)=>{
1006
1008
  }
1007
1009
  // proc 仅刷新进程条(另有 2s 轮询兜底),不重建看板 DOM
1008
1010
  if(ev.type==='proc'){ refreshProcs(); }
1011
+ // 任务结束/编排停止:进程条目随之移除,立即刷新进程条(不等 2s 轮询)
1012
+ if(ev.type==='task_done'||ev.type==='followup_done'||ev.type==='runner_stopped') refreshProcs();
1009
1013
  // tool 事件仅在打开对应详情时追加过程日志,避免工具密集时高频全量刷新
1010
1014
  if(ev.type==='tool' && OPEN_ID===ev.cardId){
1011
1015
  const log=$('#dLog'); if(log.querySelector('.empty')) log.innerHTML='';
@@ -84,6 +84,9 @@
84
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; }
85
85
  #jumpBtn:hover { color: #07c160; border-color: #07c160; }
86
86
  .sys-tip { text-align: center; margin: 10px 0; }
87
+ /* 长会话增量渲染:加载更早入口 */
88
+ .load-earlier { display: block; margin: 4px auto 16px; border: 1px solid #dcdcdc; background: #fff; color: #888; border-radius: 14px; padding: 5px 14px; font-size: 12px; cursor: pointer; transition: color .15s, border-color .15s; }
89
+ .load-earlier:hover { color: #07c160; border-color: #07c160; }
87
90
  .approval-card { max-width: 480px; margin: 10px auto; background: #fff8e6; border: 1px solid #e8a33d; border-radius: 10px; padding: 10px 14px; text-align: left; }
88
91
  .approval-card .ap-title { font-weight: 600; font-size: 13px; color: #8a5a00; margin-bottom: 4px; }
89
92
  .approval-card .ap-label { font-size: 12px; color: #555; margin-bottom: 8px; word-break: break-all; }
@@ -960,9 +963,36 @@ function soloRenderMessages() {
960
963
  return;
961
964
  }
962
965
  box.innerHTML = '';
963
- for (const m of getSess(curOcId)) box.appendChild(soloMsgNode(m));
966
+ const all = getSess(curOcId);
967
+ soloShownFrom = Math.max(0, all.length - RENDER_BATCH);
968
+ if (soloShownFrom > 0) box.appendChild(makeLoadEarlierBtn(loadEarlierSolo));
969
+ for (let i = soloShownFrom; i < all.length; i++) {
970
+ const n = soloMsgNode(all[i]);
971
+ all[i]._dom = n;
972
+ box.appendChild(n);
973
+ }
964
974
  soloScroll();
965
975
  }
976
+ function loadEarlierSolo() {
977
+ const box = document.getElementById('soloMessages');
978
+ const all = getSess(curOcId);
979
+ // 单聊消息节点构造器不同,这里专用批次回填
980
+ const fromIdx = soloShownFrom;
981
+ if (fromIdx <= 0) return;
982
+ const prevHeight = box.scrollHeight, prevTop = box.scrollTop;
983
+ const from = Math.max(0, fromIdx - RENDER_BATCH);
984
+ const frag = document.createDocumentFragment();
985
+ const oldBtn = box.querySelector('.load-earlier'); if (oldBtn) oldBtn.remove();
986
+ if (from > 0) frag.appendChild(makeLoadEarlierBtn(loadEarlierSolo));
987
+ for (let i = from; i < fromIdx; i++) {
988
+ const n = soloMsgNode(all[i]);
989
+ all[i]._dom = n;
990
+ frag.appendChild(n);
991
+ }
992
+ box.insertBefore(frag, box.firstChild);
993
+ box.scrollTop = prevTop + (box.scrollHeight - prevHeight);
994
+ soloShownFrom = from;
995
+ }
966
996
 
967
997
  function pushSoloMsg(key, m) {
968
998
  getSess(key).push(m);
@@ -1795,15 +1825,52 @@ function msgNode(m) {
1795
1825
  }
1796
1826
 
1797
1827
  // 全量重绘当前会话(切换会话 / 载入历史时)
1828
+ // ---------- 长会话增量渲染:初始只渲染最近 RENDER_BATCH 条,更早的按需加载 ----------
1829
+ // 超长会话(数千条)全量渲染会拖慢浏览器;批次化后 DOM 数量恒定,加载更早时保持视口不跳动
1830
+ const RENDER_BATCH = 100;
1831
+ function makeLoadEarlierBtn(onClick) {
1832
+ const b = document.createElement('button');
1833
+ b.className = 'load-earlier';
1834
+ b.textContent = '加载更早的消息';
1835
+ b.onclick = onClick;
1836
+ return b;
1837
+ }
1838
+ function loadEarlierInto(box, all, fromIdx, onClick) {
1839
+ if (fromIdx <= 0) return 0;
1840
+ const prevHeight = box.scrollHeight, prevTop = box.scrollTop;
1841
+ const from = Math.max(0, fromIdx - RENDER_BATCH);
1842
+ const frag = document.createDocumentFragment();
1843
+ const oldBtn = box.querySelector('.load-earlier'); if (oldBtn) oldBtn.remove();
1844
+ if (from > 0) frag.appendChild(makeLoadEarlierBtn(onClick));
1845
+ for (let i = from; i < fromIdx; i++) {
1846
+ const node = msgNode(all[i]);
1847
+ all[i]._dom = node;
1848
+ frag.appendChild(node);
1849
+ finalizeMsgLinks(all[i]);
1850
+ }
1851
+ box.insertBefore(frag, box.firstChild);
1852
+ box.scrollTop = prevTop + (box.scrollHeight - prevHeight); // 视口停在原消息处
1853
+ return from;
1854
+ }
1855
+ let mainShownFrom = 0; // 主聊天区已渲染起始下标
1856
+ let soloShownFrom = 0; // 单聊区已渲染起始下标
1857
+ function loadEarlierMain() {
1858
+ const box = document.getElementById('messages');
1859
+ mainShownFrom = loadEarlierInto(box, getSess(curKey()), mainShownFrom, loadEarlierMain);
1860
+ }
1861
+
1798
1862
  function renderMessages() {
1799
1863
  const box = document.getElementById('messages');
1800
1864
  box.classList.add('bulk'); // 批量重建:抑制逐条入场动画
1801
1865
  box.innerHTML = '';
1802
- for (const m of getSess(curKey())) {
1803
- const node = msgNode(m);
1804
- m._dom = node;
1866
+ const all = getSess(curKey());
1867
+ mainShownFrom = Math.max(0, all.length - RENDER_BATCH);
1868
+ if (mainShownFrom > 0) box.appendChild(makeLoadEarlierBtn(loadEarlierMain));
1869
+ for (let i = mainShownFrom; i < all.length; i++) {
1870
+ const node = msgNode(all[i]);
1871
+ all[i]._dom = node;
1805
1872
  box.appendChild(node);
1806
- finalizeMsgLinks(m); // 定稿消息中的 .md 路径变成可点击预览链接
1873
+ finalizeMsgLinks(all[i]); // 定稿消息中的 .md 路径变成可点击预览链接
1807
1874
  }
1808
1875
  scrollBottom();
1809
1876
  requestAnimationFrame(() => requestAnimationFrame(() => box.classList.remove('bulk')));
package/app/server.js CHANGED
@@ -16,7 +16,14 @@ loadEnv(path.join(ROOT_DIR, '.env'));
16
16
  const LOG_DIR = process.env.AGENTS_CHAT_DATA || path.join(ROOT_DIR, '.data');
17
17
  try { fs.mkdirSync(LOG_DIR, { recursive: true }); } catch { /* ignore */ }
18
18
  const LOG_PATH = path.join(LOG_DIR, 'server.log');
19
- try { fs.writeFileSync(LOG_PATH, `=== Agents Chat started ${new Date().toISOString()} ===\n`); } catch { /* ignore */ }
19
+ const LOG_MAX_BYTES = 5 * 1024 * 1024; // 单文件 5MB 滚动:常驻/定时任务长跑不无限增长
20
+ function logRotateIfNeeded() {
21
+ try {
22
+ const st = fs.statSync(LOG_PATH);
23
+ if (st.size > LOG_MAX_BYTES) fs.renameSync(LOG_PATH, LOG_PATH + '.1'); // 保留一代,旧的 .1 被覆盖
24
+ } catch { /* 无文件 */ }
25
+ }
26
+ try { logRotateIfNeeded(); fs.writeFileSync(LOG_PATH, `=== Agents Chat started ${new Date().toISOString()} ===\n`); } catch { /* ignore */ }
20
27
  function teeWrite(args) {
21
28
  try { fs.appendFileSync(LOG_PATH, args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ') + '\n'); } catch { /* ignore */ }
22
29
  }
@@ -123,12 +130,19 @@ function sse(req, res) {
123
130
  Connection: 'keep-alive'
124
131
  });
125
132
  sseConns++;
133
+ // 背压保护:慢客户端的写缓冲积压超 1MB 时主动断开(防内存无限堆积),客户端重连后经 init 补拉状态
126
134
  const send = (obj) => {
127
- try { res.write(`data: ${JSON.stringify(obj)}\n\n`); } catch { /* closed */ }
135
+ try {
136
+ if (res.writableLength > 1024 * 1024) { try { res.destroy(); } catch { /* ignore */ } return; }
137
+ res.write(`data: ${JSON.stringify(obj)}\n\n`);
138
+ } catch { /* closed */ }
128
139
  };
129
140
  // 心跳防止代理断开
130
141
  const hb = setInterval(() => {
131
- try { res.write(': hb\n\n'); } catch { clearInterval(hb); }
142
+ try {
143
+ if (res.writableLength > 1024 * 1024) { res.destroy(); clearInterval(hb); return; }
144
+ res.write(': hb\n\n');
145
+ } catch { clearInterval(hb); }
132
146
  }, 15000);
133
147
  let closed = false;
134
148
  const onClose = () => {
@@ -871,7 +885,8 @@ const server = http.createServer(async (req, res) => {
871
885
 
872
886
  // ---------- 历史管理:一键清空全部会话 / 导出 sessions.md ----------
873
887
  if (p === '/api/history/clear' && req.method === 'POST') {
874
- const msgCount = store.getMessages().length;
888
+ const msgStat = store.countMessagesByTask();
889
+ const msgCount = msgStat.main + Object.values(msgStat.counts).reduce((a, b) => a + b, 0);
875
890
  let outDirs = 0;
876
891
  store.clearMessages();
877
892
  // 单聊会话记录一并清空(消息已清,保留空会话列表无意义)
@@ -891,18 +906,20 @@ const server = http.createServer(async (req, res) => {
891
906
  }
892
907
 
893
908
  if (p === '/api/history/export' && req.method === 'GET') {
894
- const msgs = store.getMessages();
909
+ // 统计与头部:逐分片计数(低内存);正文按会话逐段拼接(导出为显式用户动作)
910
+ const stat = store.countMessagesByTask();
895
911
  const tasksAll = store.getTasks();
896
912
  const ocAll = store.getOcSessions();
897
913
  const fmtTs = (t) => { const d = new Date(t); const p2 = (n) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())} ${p2(d.getHours())}:${p2(d.getMinutes())}`; };
898
914
  const roleOf = (m) => m.role === 'user' ? '👤 用户' : (m.agentName || '智能体') + (m.phase ? `(${m.phase})` : '');
915
+ const total = stat.main + Object.values(stat.counts).reduce((a, b) => a + b, 0);
899
916
  const lines = [
900
917
  '# Agents Chat 会话导出', '',
901
918
  `- 导出时间:${fmtTs(Date.now())}`,
902
- `- 会话数:${1 + tasksAll.filter(t => store.getMessages(t.id).length > 0).length + ocAll.filter(s => store.getMessages(s.id).length > 0).length}(主会话 + 任务会话 + 单聊会话)`,
903
- `- 消息总数:${msgs.length}`, ''
919
+ `- 会话数:${1 + tasksAll.filter(t => (stat.counts[t.id] || 0) > 0).length + ocAll.filter(s => (stat.counts[s.id] || 0) > 0).length}(主会话 + 任务会话 + 单聊会话)`,
920
+ `- 消息总数:${total}`, ''
904
921
  ];
905
- const main = msgs.filter(m => !m.taskId);
922
+ const main = store.getMessages('');
906
923
  lines.push('---', '', '## 主会话', '');
907
924
  for (const m of main) {
908
925
  lines.push(`### ${fmtTs(m.timestamp)} · ${roleOf(m)}`, '');
@@ -945,11 +962,9 @@ const server = http.createServer(async (req, res) => {
945
962
  // ---------- 事项管控(卡牌)API ----------
946
963
  if (p === '/api/cards' && req.method === 'GET') {
947
964
  const cards = CardStore.list();
948
- // 附带每个卡牌当前过程消息条数(供 UI 角标展示)
949
- const counts = {};
950
- try {
951
- for (const m of store.getMessages()) if (m.taskId) counts[m.taskId] = (counts[m.taskId] || 0) + 1;
952
- } catch { /* ignore */ }
965
+ // 附带每个卡牌当前过程消息条数(供 UI 角标展示):逐分片计数,不合并全量消息
966
+ let counts = {};
967
+ try { counts = store.countMessagesByTask().counts; } catch { /* ignore */ }
953
968
  // 执行内核状态(前端据此显示内核徽标/缺失警告)与追加聊天进行中的卡
954
969
  let runnerInfo = { kind: 'unknown', label: '' };
955
970
  try {
@@ -1204,20 +1219,35 @@ const server = http.createServer(async (req, res) => {
1204
1219
  });
1205
1220
 
1206
1221
  // 优雅退出:停掉全部子进程、把执行中任务复位为待执行,避免残留与假死状态
1222
+ // 幂等防重入:信号钩子与 exit 钩子可能先后触发,清理只执行一次
1223
+ let shutdownDone = false;
1207
1224
  function shutdown(code) {
1208
- try {
1209
- if (termProc) { try { termProc.kill(); } catch { /* ignore */ } termProc = null; }
1210
- const n = stopAllChildren();
1211
- const m = store.resetRunningTasks();
1212
- const mc = CardStore.resetRunning();
1213
- if (n || m || mc) console.log(`退出清理:终止 ${n} 个子进程,复位 ${m} 个执行中任务、${mc} 张卡牌`);
1214
- } catch (err) {
1215
- console.error('[shutdown] 清理失败:', err && (err.stack || err));
1225
+ if (!shutdownDone) {
1226
+ shutdownDone = true;
1227
+ try {
1228
+ if (termProc) { try { termProc.kill(); } catch { /* ignore */ } termProc = null; }
1229
+ const n = stopAllChildren();
1230
+ const m = store.resetRunningTasks();
1231
+ const mc = CardStore.resetRunning();
1232
+ if (n || m || mc) console.log(`退出清理:终止 ${n} 个子进程,复位 ${m} 个执行中任务、${mc} 张卡牌`);
1233
+ } catch (err) {
1234
+ console.error('[shutdown] 清理失败:', err && (err.stack || err));
1235
+ }
1216
1236
  }
1217
1237
  process.exit(code);
1218
1238
  }
1219
1239
  process.on('SIGINT', () => shutdown(0));
1220
1240
  process.on('SIGTERM', () => shutdown(0));
1241
+ // Windows 关闭控制台窗口(CTRL_CLOSE_EVENT)在 Node 上映射为 SIGHUP,系统给约 5 秒处理宽限:
1242
+ // 同步 killTree 足够完成,正在执行的 AI 子进程不再变孤儿
1243
+ process.on('SIGHUP', () => shutdown(0));
1244
+ // 兜底:显式 process.exit 路径(exit 钩子内仅允许同步操作,killTree 为同步调用)
1245
+ process.on('exit', () => {
1246
+ if (!shutdownDone) {
1247
+ shutdownDone = true;
1248
+ try { stopAllChildren(); } catch { /* ignore */ }
1249
+ }
1250
+ });
1221
1251
 
1222
1252
  // ---------- 任务批次执行(SSE 手动触发与定时调度共用) ----------
1223
1253
  // send: 事件推送(SSE 为真实推送,定时触发为 no-op,消息仍会持久化)
@@ -1428,6 +1458,7 @@ server.listen(PORT, () => {
1428
1458
  startPruneTimer();
1429
1459
  console.log(`Agents Chat 已启动: http://localhost:${PORT}`);
1430
1460
  console.log(`运行内核: ${kindText}`);
1461
+ console.log('退出提示: 请用 Ctrl+C(或 agents-chat stop)退出,会自动清理执行中的 AI 子进程;直接关闭窗口可能残留正在执行的进程');
1431
1462
  console.log(`本机可用内核: ${avail}(配置页可切换)`);
1432
1463
  console.log(`数据目录: ${store.DATA_DIR}`);
1433
1464
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamsamyiok/agents-chat",
3
- "version": "3.23.0",
3
+ "version": "3.25.0",
4
4
  "description": "多智能体群聊工具 - 支持 OpenCode/Claude Code/Codex/pi 内核,微信风格聊天界面",
5
5
  "main": "lib/start.js",
6
6
  "bin": {