@iamsamyiok/agents-chat 3.25.0 → 3.27.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/store.js +5 -1
- package/app/lib/updatecheck.js +43 -0
- package/app/public/cards.html +108 -0
- package/app/public/index.html +34 -1
- package/app/server.js +60 -3
- package/bin/agents-chat.js +74 -12
- package/package.json +3 -2
package/app/lib/store.js
CHANGED
|
@@ -5,7 +5,11 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const safejson = require('./safejson');
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// 数据目录基准:单文件 exe(构建时 --define 注入 AGENTS_CHAT_STANDALONE=1)= exe 所在目录(便携,数据随身);
|
|
9
|
+
// 源码/npm 运行 = 项目根目录。AGENTS_CHAT_DATA 显式指定时优先。
|
|
10
|
+
let ROOT;
|
|
11
|
+
if (process.env.AGENTS_CHAT_STANDALONE === '1' && process.execPath) ROOT = path.dirname(process.execPath);
|
|
12
|
+
else ROOT = path.join(__dirname, '..', '..');
|
|
9
13
|
const DATA_DIR = process.env.AGENTS_CHAT_DATA || path.join(ROOT, '.data');
|
|
10
14
|
const CONFIG_PATH = path.join(DATA_DIR, 'config.json');
|
|
11
15
|
const TASKS_PATH = path.join(DATA_DIR, 'tasks.json');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// npm 最新版本检查:供启动横幅、/api/health、CLI update/status 共用
|
|
2
|
+
// Node 18+ 自带全局 fetch;3 秒超时 + 1 小时结果缓存,失败静默(离线/内网不影响启动)
|
|
3
|
+
|
|
4
|
+
const PKG = require('../../package.json');
|
|
5
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${PKG.name}/latest`;
|
|
6
|
+
|
|
7
|
+
let cached = { at: 0, latest: null }; // 缓存上次查询结果(1 小时内复用)
|
|
8
|
+
|
|
9
|
+
// 简化 semver 比较:a > b 返回 true(仅支持 x.y.z 数字段, prerelease 忽略比较)
|
|
10
|
+
function semverGt(a, b) {
|
|
11
|
+
const pa = String(a || '').split('-')[0].split('.').map(n => parseInt(n, 10) || 0);
|
|
12
|
+
const pb = String(b || '').split('-')[0].split('.').map(n => parseInt(n, 10) || 0);
|
|
13
|
+
for (let i = 0; i < 3; i++) {
|
|
14
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) > (pb[i] || 0);
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 查询 npm registry 最新版本;force=true 跳过缓存(CLI update 用)
|
|
20
|
+
async function checkLatest({ force = false } = {}) {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
if (!force && cached.latest && now - cached.at < 3600 * 1000) {
|
|
23
|
+
return { latest: cached.latest, updateAvailable: semverGt(cached.latest, PKG.version) };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const ctrl = new AbortController();
|
|
27
|
+
const timer = setTimeout(() => ctrl.abort(), 3000);
|
|
28
|
+
const res = await fetch(REGISTRY_URL, { signal: ctrl.signal, headers: { 'accept': 'application/json' } });
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
if (!res.ok) return null;
|
|
31
|
+
const data = await res.json();
|
|
32
|
+
const latest = String(data.version || '');
|
|
33
|
+
if (!latest) return null;
|
|
34
|
+
cached = { at: now, latest };
|
|
35
|
+
return { latest, updateAvailable: semverGt(latest, PKG.version) };
|
|
36
|
+
} catch {
|
|
37
|
+
return null; // 网络不可达/超时:静默,不阻塞调用方
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const UPDATE_COMMAND = `npm install -g ${PKG.name}@latest`;
|
|
42
|
+
|
|
43
|
+
module.exports = { semverGt, checkLatest, UPDATE_COMMAND, currentVersion: PKG.version };
|
package/app/public/cards.html
CHANGED
|
@@ -190,6 +190,18 @@
|
|
|
190
190
|
/* 空看板引导 */
|
|
191
191
|
.board-guide{grid-column:1/-1;text-align:center;padding:40px 10px;color:var(--dim)}
|
|
192
192
|
.board-guide .bg-actions{display:flex;gap:10px;justify-content:center;margin-top:14px}
|
|
193
|
+
/* 使用帮助 */
|
|
194
|
+
.help-sec{margin:0 0 14px}
|
|
195
|
+
.help-sec h4{margin:0 0 6px;font-size:13.5px;color:var(--acc)}
|
|
196
|
+
.help-sec ul,.help-sec ol{margin:0;padding-left:20px}
|
|
197
|
+
.help-sec li{margin:4px 0;font-size:12.5px;line-height:1.65;color:#333}
|
|
198
|
+
.help-sec code{background:var(--bg2);padding:1px 5px;border-radius:4px;font-size:11.5px;color:var(--acc)}
|
|
199
|
+
.help-sec p{font-size:12.5px;line-height:1.65;color:#333;margin:6px 0}
|
|
200
|
+
/* 版本徽标与更新提示条 */
|
|
201
|
+
.ver-chip{font-size:11px;color:var(--dim);border:1px solid var(--line);border-radius:10px;padding:2px 8px;white-space:nowrap}
|
|
202
|
+
.update-bar{position:fixed;top:0;left:0;right:0;z-index:60;background:#fffbe6;border-bottom:1px solid #ffe58f;color:#614700;font-size:12.5px;padding:7px 14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
203
|
+
.update-bar button{border:1px solid #d8b94a;background:#fff;border-radius:5px;padding:2px 10px;font-size:12px;cursor:pointer;color:#614700}
|
|
204
|
+
.update-bar button:hover{background:#fff7d6}
|
|
193
205
|
</style>
|
|
194
206
|
</head>
|
|
195
207
|
<body>
|
|
@@ -216,6 +228,8 @@
|
|
|
216
228
|
<button class="btn primary" id="btnRun" title="按依赖与优先级调度全部待执行任务">▶ 开始编排</button>
|
|
217
229
|
<button class="btn danger" id="btnStop" disabled title="停止编排:进行中的任务复位为待执行(不影响追加聊天),未开始的任务保留">⏹ 停止</button>
|
|
218
230
|
<button class="btn" id="btnClear">清空</button>
|
|
231
|
+
<button class="btn" id="btnHelp" title="多任务编排使用帮助">❓ 帮助</button>
|
|
232
|
+
<span class="ver-chip" id="verChip" title="当前版本"></span>
|
|
219
233
|
</header>
|
|
220
234
|
|
|
221
235
|
<div class="kernel-banner" id="kernelBanner"></div>
|
|
@@ -316,6 +330,14 @@
|
|
|
316
330
|
</div>
|
|
317
331
|
|
|
318
332
|
<!-- 垃圾桶 -->
|
|
333
|
+
<!-- 使用帮助 -->
|
|
334
|
+
<div class="mask" id="helpMask">
|
|
335
|
+
<div class="modal" style="width:min(720px,94vw);max-height:86vh;overflow-y:auto">
|
|
336
|
+
<h3>❓ 多任务编排使用帮助</h3>
|
|
337
|
+
<div id="helpBody"></div>
|
|
338
|
+
<div class="foot"><button class="btn primary" id="helpClose">关闭</button></div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
319
341
|
<div class="mask" id="trashMask">
|
|
320
342
|
<div class="modal">
|
|
321
343
|
<h3>🗑️ 垃圾桶 <span style="font-size:12px;color:var(--dim)">(保留 30 天内删除的任务快照)</span></h3>
|
|
@@ -360,6 +382,7 @@ async function loadCards(){
|
|
|
360
382
|
if(data.config) $('#wsInput').value = data.config.workspace || '';
|
|
361
383
|
if(data.maxParallel) $('#parallelSel').value = String(data.maxParallel);
|
|
362
384
|
renderKernelBanner();
|
|
385
|
+
renderUpdateInfo(data);
|
|
363
386
|
render();
|
|
364
387
|
renderGraph();
|
|
365
388
|
loadTrashCount();
|
|
@@ -930,6 +953,91 @@ async function openTrash(){
|
|
|
930
953
|
}
|
|
931
954
|
$('#trashFab').onclick=openTrash;
|
|
932
955
|
$('#trashClose').onclick=()=>$('#trashMask').classList.remove('show');
|
|
956
|
+
|
|
957
|
+
// ---------- 使用帮助 ----------
|
|
958
|
+
function openHelp(){
|
|
959
|
+
$('#helpBody').innerHTML = `
|
|
960
|
+
<div class="help-sec"><h4>🗂 这是什么</h4>
|
|
961
|
+
<p>把要做的事写成一张张「任务卡牌」,程序按<b>依赖关系与优先级</b>自动调度 AI 执行:上游完成才启动下游,无依赖的任务并行跑。适合批量任务、流水线作业(翻译→润色→排版)与多分支并行调研。</p>
|
|
962
|
+
<p>与群聊的关系:群聊是「对话式」由管家现场拆解调度;本页是「清单式」由你预先定义好每张卡,执行过程可暂停、追问、重跑。</p>
|
|
963
|
+
</div>
|
|
964
|
+
<div class="help-sec"><h4>🚀 快速开始(三步)</h4>
|
|
965
|
+
<ol>
|
|
966
|
+
<li>「+ 新建任务」:写标题与任务内容(描述越具体越好,支持 Markdown)</li>
|
|
967
|
+
<li>(可选)在表单里选「依赖任务」:被依赖的卡完成后本卡才执行</li>
|
|
968
|
+
<li>「▶ 开始编排」:按依赖拓扑 + 优先级自动调度全部待执行卡牌</li>
|
|
969
|
+
</ol>
|
|
970
|
+
</div>
|
|
971
|
+
<div class="help-sec"><h4>⚙ 三种执行模式(新建任务表单)</h4>
|
|
972
|
+
<ul>
|
|
973
|
+
<li><b>新进程(new)</b>:独立会话执行,互不影响——默认</li>
|
|
974
|
+
<li><b>同会话续聊(continue)</b>:接续某张卡(续聊链首)的会话继续,AI 记得之前的成果,适合「初稿→修改→定稿」链</li>
|
|
975
|
+
<li><b>并行独立进程(parallel)</b>:与前后卡同时开跑,适合批量同类任务(同时查 5 个城市天气)</li>
|
|
976
|
+
</ul>
|
|
977
|
+
</div>
|
|
978
|
+
<div class="help-sec"><h4>🔀 依赖、优先级与并行度</h4>
|
|
979
|
+
<ul>
|
|
980
|
+
<li><b>依赖任务</b>可多选:全部完成后本卡才可执行(失败/跳过的依赖会阻断)</li>
|
|
981
|
+
<li><b>优先级</b>数字越小越先执行;拖拽看板卡到新位置会按新顺序自动重写优先级</li>
|
|
982
|
+
<li>顶栏 <b>⚡ 并行</b>(1-8):同时执行的最大任务数,改完即时生效</li>
|
|
983
|
+
<li>左下角「▶ 依赖图」可视化全部依赖关系,可放大查看</li>
|
|
984
|
+
</ul>
|
|
985
|
+
</div>
|
|
986
|
+
<div class="help-sec"><h4>📌 看板四列</h4>
|
|
987
|
+
<ul>
|
|
988
|
+
<li>待执行 → 执行中 → 完成 / 失败;卡牌角标显示该卡过程消息条数</li>
|
|
989
|
+
<li>失败列的「↻ 全部重跑」把失败卡重置为待执行并立即重新编排</li>
|
|
990
|
+
<li>顶栏搜索框按标题/内容即时过滤看板;统计条实时显示总数与完成率</li>
|
|
991
|
+
<li>删除的卡进入回收站,可随时恢复(清空需二次确认)</li>
|
|
992
|
+
</ul>
|
|
993
|
+
</div>
|
|
994
|
+
<div class="help-sec"><h4>💬 追问(完成后继续聊)</h4>
|
|
995
|
+
<ul>
|
|
996
|
+
<li>点开已完成的卡,详情页底部可输入追加消息:同一会话继续对话(让它改改措辞、补个章节都行)</li>
|
|
997
|
+
<li>追问期间不影响其他卡牌执行;追问结果计入该卡日志</li>
|
|
998
|
+
</ul>
|
|
999
|
+
</div>
|
|
1000
|
+
<div class="help-sec"><h4>📄 详情、日志与导出</h4>
|
|
1001
|
+
<ul>
|
|
1002
|
+
<li>点任意卡看执行过程(实时滚动,含工具调用)、最终结果与失败原因</li>
|
|
1003
|
+
<li>「⬇ 导出」把该卡导出为 Markdown(元信息 + 过程 + 结果),适合汇报存档</li>
|
|
1004
|
+
<li>「🗑 清空」只清看板卡牌,群聊/单聊数据不受影响</li>
|
|
1005
|
+
</ul>
|
|
1006
|
+
</div>
|
|
1007
|
+
<div class="help-sec"><h4>📁 工作区与内核</h4>
|
|
1008
|
+
<ul>
|
|
1009
|
+
<li>顶栏「工作文件夹」:选定后 Agent 在该目录读写相关文件(写代码、改文档直接落到你指定的目录),可选</li>
|
|
1010
|
+
<li>执行内核与群聊共用同一套(opencode / Claude Code / Codex / pi),在群聊页「⚙ 智能体配置」里切换</li>
|
|
1011
|
+
<li>头部徽标显示当前内核;未检测到内核时顶部横幅会给出安装指引</li>
|
|
1012
|
+
</ul>
|
|
1013
|
+
</div>`;
|
|
1014
|
+
$('#helpMask').classList.add('show');
|
|
1015
|
+
}
|
|
1016
|
+
$('#btnHelp').onclick=openHelp;
|
|
1017
|
+
$('#helpClose').onclick=()=>$('#helpMask').classList.remove('show');
|
|
1018
|
+
$('#helpMask').addEventListener('click',e=>{ if(e.target.id==='helpMask') e.target.classList.remove('show'); });
|
|
1019
|
+
|
|
1020
|
+
// ---------- 版本徽标与更新提示 ----------
|
|
1021
|
+
function renderUpdateInfo(data){
|
|
1022
|
+
if(data.version) $('#verChip').textContent='v'+data.version;
|
|
1023
|
+
if(!data.updateAvailable) return;
|
|
1024
|
+
if(document.getElementById('updateBar')) return;
|
|
1025
|
+
const bar=document.createElement('div');
|
|
1026
|
+
bar.id='updateBar'; bar.className='update-bar';
|
|
1027
|
+
if(data.standalone){
|
|
1028
|
+
// 单文件 exe:无 npm,引导去 GitHub Releases 下载覆盖
|
|
1029
|
+
bar.innerHTML='📦 发现新版本 <b>v'+esc(data.latestVersion)+'</b>(当前 v'+esc(data.version)+'):请到 GitHub Releases 下载最新版覆盖安装 <button id="updateOpen">打开放下载页</button>';
|
|
1030
|
+
document.body.prepend(bar);
|
|
1031
|
+
document.body.style.paddingTop='34px';
|
|
1032
|
+
$('#updateOpen').onclick=()=>window.open('https://github.com/iamsamyiok/agents-chat/releases/latest','_blank');
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
bar.innerHTML='📦 发现新版本 <b>v'+esc(data.latestVersion)+'</b>(当前 v'+esc(data.version)+'):终端运行 <code>agents-chat update</code> 一键升级,或执行 <code>'+esc(data.updateCommand)+'</code>'
|
|
1036
|
+
+' <button id="updateCopy">复制升级命令</button>';
|
|
1037
|
+
document.body.prepend(bar);
|
|
1038
|
+
document.body.style.paddingTop='34px';
|
|
1039
|
+
$('#updateCopy').onclick=()=>copyText(data.updateCommand||('npm install -g @iamsamyiok/agents-chat@latest'));
|
|
1040
|
+
}
|
|
933
1041
|
$('#trashEmpty').onclick=async()=>{ if(!confirm('确认清空垃圾桶?所有快照及其过程日志将被永久删除,不可恢复。'))return; const r=await post('/api/cards/trash/empty'); toast('已清空 '+r.cleared+' 项'); await loadCards(); };
|
|
934
1042
|
|
|
935
1043
|
// ---- 进程状态条(PID + 工作状态,SSE 事件驱动 + 2s 轮询兜底) ----
|
package/app/public/index.html
CHANGED
|
@@ -440,6 +440,7 @@
|
|
|
440
440
|
<button class="cfg-btn" id="agentsCfgBtn" onclick="openAgents()">⚙ 智能体配置</button>
|
|
441
441
|
<button class="cfg-btn" id="cardsNavBtn" onclick="openCards()" title="多任务编排 · 卡牌:把任务写成卡牌,Agent 干完一件自动注入下一件">🗂️ 多任务编排</button>
|
|
442
442
|
<span class="runner-tag" id="runnerTag" style="display:none"></span>
|
|
443
|
+
<span class="ver-chip" id="verChip" title="当前版本" style="font-size:11px;color:#999;border:1px solid #e2e2e2;border-radius:10px;padding:2px 8px;white-space:nowrap"></span>
|
|
443
444
|
<button class="cfg-btn" id="helpBtn" onclick="openHelp()" title="使用帮助">❓ 帮助</button>
|
|
444
445
|
</div>
|
|
445
446
|
</header>
|
|
@@ -665,7 +666,7 @@
|
|
|
665
666
|
</div>
|
|
666
667
|
|
|
667
668
|
<script>
|
|
668
|
-
const PAGE_VERSION = '3.
|
|
669
|
+
const PAGE_VERSION = '3.26.0'; // 与服务端 /api/health.version 互检,不一致说明页面缓存过期
|
|
669
670
|
const AV_COLORS = ['#5b8def','#07c160','#fa9d3b','#10aeff','#8a6fe8','#fa5151','#e8a33d','#3aa7a3'];
|
|
670
671
|
const PHASE_LABEL = { plan: '调度规划', work: '执行', review: '验收', report: '汇总', talk: '圆桌发言', task: '任务执行' };
|
|
671
672
|
// 职业图标库(智能体配置可选)
|
|
@@ -759,6 +760,7 @@ async function init() {
|
|
|
759
760
|
if (health.version && health.version !== PAGE_VERSION) {
|
|
760
761
|
addSys(`⚠ 页面版本(${PAGE_VERSION})与服务端(${health.version})不一致:浏览器缓存了旧页面,请按 Ctrl+F5 强制刷新`);
|
|
761
762
|
}
|
|
763
|
+
renderUpdateInfo(health);
|
|
762
764
|
|
|
763
765
|
input.addEventListener('input', handleAtInput);
|
|
764
766
|
input.addEventListener('blur', () => setTimeout(hideAt, 150));
|
|
@@ -3249,8 +3251,39 @@ function openCards() {
|
|
|
3249
3251
|
window.open('/cards', '_blank');
|
|
3250
3252
|
}
|
|
3251
3253
|
|
|
3254
|
+
// 版本徽标与更新提示条(health 下发;无新版静默)
|
|
3255
|
+
function renderUpdateInfo(health) {
|
|
3256
|
+
if (!health) return;
|
|
3257
|
+
const chip = document.getElementById('verChip');
|
|
3258
|
+
if (chip && health.version) chip.textContent = 'v' + health.version;
|
|
3259
|
+
if (!health.updateAvailable || document.getElementById('updateBar')) return;
|
|
3260
|
+
const bar = document.createElement('div');
|
|
3261
|
+
bar.id = 'updateBar';
|
|
3262
|
+
bar.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:60;background:#fffbe6;border-bottom:1px solid #ffe58f;color:#614700;font-size:12.5px;padding:7px 14px;display:flex;align-items:center;gap:8px;flex-wrap:wrap';
|
|
3263
|
+
if (health.standalone) {
|
|
3264
|
+
// 单文件 exe:无 npm,引导去 GitHub Releases 下载覆盖
|
|
3265
|
+
bar.innerHTML = `📦 发现新版本 <b>v${esc(health.latestVersion)}</b>(当前 v${esc(health.version)}):请到 GitHub Releases 下载最新版覆盖安装 <button id="updateOpen" style="border:1px solid #d8b94a;background:#fff;border-radius:5px;padding:2px 10px;font-size:12px;cursor:pointer;color:#614700">打开放下载页</button>`;
|
|
3266
|
+
document.body.prepend(bar);
|
|
3267
|
+
document.body.style.paddingTop = '34px';
|
|
3268
|
+
document.getElementById('updateOpen').onclick = () => window.open('https://github.com/iamsamyiok/agents-chat/releases/latest', '_blank');
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
bar.innerHTML = `📦 发现新版本 <b>v${esc(health.latestVersion)}</b>(当前 v${esc(health.version)}):终端运行 <code>agents-chat update</code> 一键升级,或执行 <code>${esc(health.updateCommand)}</code> <button id="updateCopy" style="border:1px solid #d8b94a;background:#fff;border-radius:5px;padding:2px 10px;font-size:12px;cursor:pointer;color:#614700">复制升级命令</button>`;
|
|
3272
|
+
document.body.prepend(bar);
|
|
3273
|
+
document.body.style.paddingTop = '34px';
|
|
3274
|
+
document.getElementById('updateCopy').onclick = () => copyText(health.updateCommand || 'npm install -g @iamsamyiok/agents-chat@latest');
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3252
3277
|
function openHelp() {
|
|
3253
3278
|
document.getElementById('helpBody').innerHTML = kernelBanner() + `
|
|
3279
|
+
<div class="help-sec"><h4>🧭 三种使用模式(先选对界面)</h4>
|
|
3280
|
+
<ul>
|
|
3281
|
+
<li><b>👥 群聊</b>(本页默认):一句话发给整个团队——管家理解需求、拆解任务、调度子智能体协作完成;也可以 <code>@</code> 点名。适合不确定怎么拆、需要 AI 自己规划的事</li>
|
|
3282
|
+
<li><b>👤 单聊</b>(左上角开关切换):与一个 AI 一对一连续对话,像用普通 AI 助手;同一会话多轮续聊,顶部可选默认模型。适合问答、写作、改代码这类单人事务</li>
|
|
3283
|
+
<li><b>🗂️ 多任务编排</b>(顶栏按钮进入):把任务写成卡牌清单,按依赖与优先级自动批量执行。适合一次布置一批任务、流水线作业。编排在独立页面,有专属帮助(页面右上角 ❓)</li>
|
|
3284
|
+
</ul>
|
|
3285
|
+
<p>三者数据互通(同一数据目录、同一套内核),随时切换界面,互不干扰。</p>
|
|
3286
|
+
</div>
|
|
3254
3287
|
<div class="help-sec"><h4>🚀 快速开始</h4>
|
|
3255
3288
|
<ol>
|
|
3256
3289
|
<li>本机安装任一执行内核并登录模型(模型与密钥由内核自己管理,本程序不做任何 API 配置):<br>
|
package/app/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Agents Chat Portable - 零依赖 HTTP 服务
|
|
2
2
|
// 启动:node app/server.js [--port 3456]
|
|
3
|
-
const APP_VERSION = '
|
|
3
|
+
const APP_VERSION = require('../package.json').version; // 单源版本:与 package.json 始终一致(页面互检/更新检查共用)
|
|
4
|
+
const { checkLatest, UPDATE_COMMAND } = require('./lib/updatecheck');
|
|
4
5
|
const http = require('http');
|
|
5
6
|
const fs = require('fs');
|
|
6
7
|
const path = require('path');
|
|
@@ -106,7 +107,20 @@ function readBody(req) {
|
|
|
106
107
|
});
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
// 单文件 exe(bun compile)构建时由 scripts/build-exe.js 生成,内嵌全部页面资源;
|
|
111
|
+
// npm/源码运行时该文件不存在 → 走磁盘,开发模式改页面即时生效
|
|
112
|
+
let EMBEDDED_ASSETS = null;
|
|
113
|
+
try { EMBEDDED_ASSETS = require('./lib/embedded-assets'); } catch { /* 开发模式 */ }
|
|
114
|
+
const IS_STANDALONE = process.env.AGENTS_CHAT_STANDALONE === '1' || !!process.versions.bun; // 单文件 exe 形态(构建期注入标识)
|
|
115
|
+
const RELEASES_URL = 'https://github.com/iamsamyiok/agents-chat/releases/latest';
|
|
116
|
+
|
|
109
117
|
function serveStatic(res, filePath) {
|
|
118
|
+
const base = path.basename(filePath);
|
|
119
|
+
if (EMBEDDED_ASSETS && EMBEDDED_ASSETS[base] !== undefined) {
|
|
120
|
+
res.writeHead(200, { 'Content-Type': MIME[path.extname(filePath)] || 'text/html; charset=utf-8' });
|
|
121
|
+
res.end(EMBEDDED_ASSETS[base]);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
110
124
|
fs.readFile(filePath, (err, data) => {
|
|
111
125
|
if (err) {
|
|
112
126
|
res.writeHead(404);
|
|
@@ -302,9 +316,15 @@ const server = http.createServer(async (req, res) => {
|
|
|
302
316
|
const { resolveRunner, detectKernels } = require('./lib/agent');
|
|
303
317
|
const runner = resolveRunner();
|
|
304
318
|
const kernels = detectKernels();
|
|
319
|
+
// 更新检查(1 小时缓存,失败静默返回 null 字段)
|
|
320
|
+
const upd = await checkLatest().catch(() => null);
|
|
305
321
|
json(res, 200, {
|
|
306
322
|
success: true,
|
|
307
323
|
version: APP_VERSION,
|
|
324
|
+
latestVersion: upd ? upd.latest : '',
|
|
325
|
+
updateAvailable: upd ? upd.updateAvailable : false,
|
|
326
|
+
updateCommand: upd && upd.updateAvailable && !IS_STANDALONE ? UPDATE_COMMAND : '',
|
|
327
|
+
standalone: IS_STANDALONE,
|
|
308
328
|
runner: runner.kind,
|
|
309
329
|
kernelLabel: runner.kernel ? runner.kernel.label : '',
|
|
310
330
|
kernelCmd: runner.cmd || '',
|
|
@@ -973,9 +993,13 @@ const server = http.createServer(async (req, res) => {
|
|
|
973
993
|
runnerInfo = { kind: r.kind, label: r.kernel ? r.kernel.label : '' };
|
|
974
994
|
} catch { /* ignore */ }
|
|
975
995
|
const { getCorruptedFiles } = require('./lib/cards');
|
|
996
|
+
// 版本与更新信息(cards 页无 health 轮询,随主列表接口下发;1 小时缓存)
|
|
997
|
+
const upd = await checkLatest().catch(() => null);
|
|
976
998
|
json(res, 200, {
|
|
977
999
|
success: true, cards, running: cardRunner.isRunning(), maxParallel: cardRunner.maxP(), msgCounts: counts, config: CardStore.getConfig(),
|
|
978
|
-
followupIds: cardRunner.getFollowupIds(), runner: runnerInfo, corrupted: getCorruptedFiles()
|
|
1000
|
+
followupIds: cardRunner.getFollowupIds(), runner: runnerInfo, corrupted: getCorruptedFiles(),
|
|
1001
|
+
version: APP_VERSION, latestVersion: upd ? upd.latest : '', updateAvailable: upd ? upd.updateAvailable : false,
|
|
1002
|
+
updateCommand: upd && upd.updateAvailable && !IS_STANDALONE ? UPDATE_COMMAND : '', standalone: IS_STANDALONE
|
|
979
1003
|
});
|
|
980
1004
|
return;
|
|
981
1005
|
}
|
|
@@ -1456,9 +1480,42 @@ server.listen(PORT, () => {
|
|
|
1456
1480
|
startScheduler();
|
|
1457
1481
|
startAutoStop();
|
|
1458
1482
|
startPruneTimer();
|
|
1459
|
-
console.log(`Agents Chat 已启动: http://localhost:${PORT}`);
|
|
1483
|
+
console.log(`Agents Chat v${APP_VERSION} 已启动: http://localhost:${PORT}`);
|
|
1460
1484
|
console.log(`运行内核: ${kindText}`);
|
|
1461
1485
|
console.log('退出提示: 请用 Ctrl+C(或 agents-chat stop)退出,会自动清理执行中的 AI 子进程;直接关闭窗口可能残留正在执行的进程');
|
|
1462
1486
|
console.log(`本机可用内核: ${avail}(配置页可切换)`);
|
|
1463
1487
|
console.log(`数据目录: ${store.DATA_DIR}`);
|
|
1488
|
+
// 开发自检:页面内 PAGE_VERSION 落后于服务端 → 提示同步,避免互检误报强刷
|
|
1489
|
+
try {
|
|
1490
|
+
for (const f of ['index.html', 'cards.html']) {
|
|
1491
|
+
const html = fs.readFileSync(path.join(PUBLIC_DIR, f), 'utf8');
|
|
1492
|
+
const m = html.match(/PAGE_VERSION\s*=\s*'([^']+)'/);
|
|
1493
|
+
if (m && m[1] !== APP_VERSION) console.warn(`[版本自检] ${f} 的 PAGE_VERSION(${m[1]})与服务端(${APP_VERSION})不一致,发版前请同步`);
|
|
1494
|
+
}
|
|
1495
|
+
} catch { /* ignore */ }
|
|
1496
|
+
// 异步更新检查:不阻塞启动,有新版打印一键升级提示
|
|
1497
|
+
checkLatest().then((upd) => {
|
|
1498
|
+
if (upd && upd.updateAvailable) {
|
|
1499
|
+
console.log(`──────────────────────────────────────────────`);
|
|
1500
|
+
console.log(`📦 发现新版本 v${upd.latest}(当前 v${APP_VERSION})`);
|
|
1501
|
+
if (IS_STANDALONE) {
|
|
1502
|
+
console.log(` 单文件版:请到 GitHub Releases 下载最新版覆盖`);
|
|
1503
|
+
console.log(` ${RELEASES_URL}`);
|
|
1504
|
+
} else {
|
|
1505
|
+
console.log(` 命令行一键升级: agents-chat update`);
|
|
1506
|
+
console.log(` 或手动执行: ${UPDATE_COMMAND}`);
|
|
1507
|
+
}
|
|
1508
|
+
console.log(`──────────────────────────────────────────────`);
|
|
1509
|
+
}
|
|
1510
|
+
}).catch(() => { /* ignore */ });
|
|
1511
|
+
// 单文件 exe 直接双击运行:自动打开浏览器(AGENTS_CHAT_NO_OPEN=1 可关闭;npm 版由 CLI 开,避免重复)
|
|
1512
|
+
if (IS_STANDALONE && process.env.AGENTS_CHAT_NO_OPEN !== '1') {
|
|
1513
|
+
try {
|
|
1514
|
+
const { exec } = require('child_process');
|
|
1515
|
+
const url = `http://localhost:${PORT}`;
|
|
1516
|
+
const cmd = process.platform === 'win32' ? `start "" "${url}"`
|
|
1517
|
+
: process.platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`;
|
|
1518
|
+
setTimeout(() => { try { exec(cmd, () => {}); } catch { /* ignore */ } }, 600);
|
|
1519
|
+
} catch { /* ignore */ }
|
|
1520
|
+
}
|
|
1464
1521
|
});
|
package/bin/agents-chat.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* agents-chat CLI 入口
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* 用法:
|
|
6
|
-
* agents-chat
|
|
7
|
-
* agents-chat start
|
|
8
|
-
* agents-chat stop
|
|
9
|
-
* agents-chat status
|
|
10
|
-
* agents-chat open
|
|
6
|
+
* agents-chat # 启动服务(后台运行)
|
|
7
|
+
* agents-chat start # 同上
|
|
8
|
+
* agents-chat stop # 停止服务
|
|
9
|
+
* agents-chat status # 查看服务状态(含版本与更新检查)
|
|
10
|
+
* agents-chat open # 仅打开浏览器
|
|
11
|
+
* agents-chat update # 检查并一键升级到 npm 最新版
|
|
12
|
+
* agents-chat version # 查看当前版本
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
const { spawn, execSync } = require('child_process');
|
|
@@ -15,6 +17,9 @@ const fs = require('fs');
|
|
|
15
17
|
const path = require('path');
|
|
16
18
|
const os = require('os');
|
|
17
19
|
|
|
20
|
+
const PKG = require('../package.json');
|
|
21
|
+
const { checkLatest, UPDATE_COMMAND } = require('../app/lib/updatecheck');
|
|
22
|
+
|
|
18
23
|
const PORT = parseInt(process.env.AGENTS_CHAT_PORT || '3456', 10);
|
|
19
24
|
const PID_FILE = path.join(os.homedir(), '.agents-chat.pid');
|
|
20
25
|
const LOG_FILE = path.join(os.homedir(), '.agents-chat.log');
|
|
@@ -216,6 +221,52 @@ function showStatus() {
|
|
|
216
221
|
console.log('发现残留进程,已清理');
|
|
217
222
|
}
|
|
218
223
|
}
|
|
224
|
+
console.log(`当前版本: v${PKG.version}`);
|
|
225
|
+
checkLatest().then((upd) => {
|
|
226
|
+
if (!upd) { console.log('更新检查: 网络不可用,跳过'); return; }
|
|
227
|
+
if (upd.updateAvailable) {
|
|
228
|
+
console.log(`可用更新: v${upd.latest}(运行 agents-chat update 一键升级)`);
|
|
229
|
+
} else {
|
|
230
|
+
console.log(`版本状态: 已是最新(npm 最新 v${upd.latest})`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 一键升级:检测新版 → 停服务(避免文件占用/旧进程残留)→ npm 全局安装最新版
|
|
236
|
+
async function updateSelf() {
|
|
237
|
+
console.log(`当前版本: v${PKG.version}`);
|
|
238
|
+
console.log('正在检查 npm 最新版本...');
|
|
239
|
+
const upd = await checkLatest({ force: true });
|
|
240
|
+
if (!upd) {
|
|
241
|
+
console.error('无法访问 npm registry(网络超时或离线),请稍后重试或手动执行:');
|
|
242
|
+
console.log(` ${UPDATE_COMMAND}`);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
if (!upd.updateAvailable) {
|
|
246
|
+
console.log(`已是最新版本 v${upd.latest},无需升级`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
console.log(`发现新版本 v${upd.latest},开始升级...`);
|
|
250
|
+
const check = isRunning();
|
|
251
|
+
if (check.running) {
|
|
252
|
+
console.log('先停止运行中的服务(升级完成后需手动重新启动)...');
|
|
253
|
+
stopServer();
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
execSync(UPDATE_COMMAND, { stdio: 'inherit' });
|
|
257
|
+
console.log('');
|
|
258
|
+
console.log(`升级完成: v${PKG.version} -> v${upd.latest}`);
|
|
259
|
+
console.log('运行 agents-chat 重新启动服务');
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error('升级失败:', err.message);
|
|
262
|
+
console.log('可手动执行:');
|
|
263
|
+
console.log(` ${UPDATE_COMMAND}`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function showVersion() {
|
|
269
|
+
console.log(`agents-chat v${PKG.version}`);
|
|
219
270
|
}
|
|
220
271
|
|
|
221
272
|
// 主逻辑
|
|
@@ -235,18 +286,29 @@ switch (command) {
|
|
|
235
286
|
case 'open':
|
|
236
287
|
openBrowser();
|
|
237
288
|
break;
|
|
289
|
+
case 'update':
|
|
290
|
+
case 'upgrade':
|
|
291
|
+
updateSelf();
|
|
292
|
+
break;
|
|
293
|
+
case 'version':
|
|
294
|
+
case '-v':
|
|
295
|
+
case '--version':
|
|
296
|
+
showVersion();
|
|
297
|
+
break;
|
|
238
298
|
case 'help':
|
|
239
299
|
case '--help':
|
|
240
300
|
case '-h':
|
|
241
301
|
console.log(`
|
|
242
|
-
Agents Chat CLI
|
|
302
|
+
Agents Chat CLI v${PKG.version}
|
|
243
303
|
|
|
244
304
|
用法:
|
|
245
|
-
agents-chat
|
|
246
|
-
agents-chat start
|
|
247
|
-
agents-chat stop
|
|
248
|
-
agents-chat status
|
|
249
|
-
agents-chat open
|
|
305
|
+
agents-chat 启动服务(后台运行,自动打开浏览器)
|
|
306
|
+
agents-chat start 同上
|
|
307
|
+
agents-chat stop 停止服务
|
|
308
|
+
agents-chat status 查看服务状态(含版本与更新检查)
|
|
309
|
+
agents-chat open 仅打开浏览器
|
|
310
|
+
agents-chat update 检查并一键升级到 npm 最新版
|
|
311
|
+
agents-chat version 查看当前版本
|
|
250
312
|
|
|
251
313
|
环境变量:
|
|
252
314
|
AGENTS_CHAT_PORT 端口号 (默认: 3456)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamsamyiok/agents-chat",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.27.0",
|
|
4
4
|
"description": "多智能体群聊工具 - 支持 OpenCode/Claude Code/Codex/pi 内核,微信风格聊天界面",
|
|
5
5
|
"main": "lib/start.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"homepage": "https://github.com/iamsamyiok/agents-chat",
|
|
35
35
|
"scripts": {
|
|
36
36
|
"start": "node app/server.js",
|
|
37
|
-
"test": "node --test \"test/*.test.js\""
|
|
37
|
+
"test": "node --test \"test/*.test.js\"",
|
|
38
|
+
"build:exe": "node scripts/build-exe.js"
|
|
38
39
|
}
|
|
39
40
|
}
|