@iamsamyiok/agents-chat 3.32.0 → 3.33.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/README.md +75 -23
- package/app/public/index.html +90 -5
- package/app/server.js +6 -1
- package/package.json +1 -1
- package/scripts/build-exe.js +2 -0
package/README.md
CHANGED
|
@@ -1,42 +1,94 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agents Chat
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个页面指挥一支 AI 智能体团队:你说需求,管家拆解调度,多个智能体并行干活、互相验收。本地运行、数据不出本机,支持免安装单文件 exe。
|
|
4
4
|
|
|
5
|
-
>
|
|
6
|
-
> 因此使用官方建议的 scoped 包名 `@iamsamyiok/agents-chat`,安装后的命令仍是 `agents-chat`。
|
|
5
|
+
> 生态位说明:同类开源工具 Vibe Kanban(28k★)已停止维护、Crystal 已转商业化,Agents Chat 是这个赛道持续维护的免费开源选择,并额外提供定时任务、人工审批、卡牌协作等编排能力。
|
|
7
6
|
|
|
8
|
-
##
|
|
7
|
+
## 30 秒上手
|
|
9
8
|
|
|
10
9
|
```bash
|
|
11
|
-
|
|
10
|
+
# 安装(Node >= 18;也可直接下载免安装 exe,见下)
|
|
11
|
+
npm install -g agents-chat-cli
|
|
12
|
+
|
|
13
|
+
# 启动:自动打开浏览器;首次打开有新手引导,点「一键体验示例」即可看完整流程
|
|
14
|
+
agents-chat
|
|
12
15
|
```
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
没装 AI 内核也能跑:未检测到内核时自动进入演示模式(模拟输出),先体验界面再安装真实内核。
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
```bash
|
|
20
|
+
# 推荐内核(任选其一,装完重启 agents-chat 即真实执行)
|
|
21
|
+
npm install -g opencode-ai
|
|
22
|
+
```
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
- **Claude Code**: `npm install -g @anthropic-ai/claude-code`
|
|
20
|
-
- **Codex CLI**: `npm install -g @openai/codex`
|
|
21
|
-
- **pi**: `npm install -g @earendil-works/pi-coding-agent`
|
|
24
|
+
## 核心玩法
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
| 能力 | 说明 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| 群聊编排 | 直接输入需求,管家智能体拆解成阶段计划,调度子智能体并行执行,自动验收不合格返工 |
|
|
29
|
+
| 批量任务 | 粘贴一段文本一次导入多条任务(`1. xxx` 每行一条),可拖拽排序、批量执行 |
|
|
30
|
+
| 定时任务 | 行首写时间即定时(`2026-08-18 13:07 生成日报`),到点自动执行,停机错过的启动时补跑,可开机自启 |
|
|
31
|
+
| Git 隔离 | 导入时勾选「Git 隔离执行」:每任务独立 worktree 分支互不污染,完成后逐文件看 diff、一键合并或丢弃 |
|
|
32
|
+
| 人工审批 | 可选在「方案确定后 / 最终交付前」暂停等你点头,超时 10 分钟自动否决 |
|
|
33
|
+
| 完成通知 | 页面在后台弹系统通知;webhook 转发钉钉/飞书/自建中转 |
|
|
34
|
+
| 单聊工作台 | 与 OpenCode 单体多轮续聊;任务链支持 `-` 接续上一会话、`//` 并行执行 |
|
|
35
|
+
| 卡牌协作 | 每个智能体一张卡牌实时直播工作过程,可中途插话纠偏或委派转交 |
|
|
24
36
|
|
|
25
|
-
|
|
26
|
-
# 启动服务(后台运行,自动打开浏览器)
|
|
27
|
-
agents-chat
|
|
37
|
+
## 安装方式
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
agents-chat status
|
|
39
|
+
### npm(推荐)
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
agents-chat
|
|
41
|
+
```bash
|
|
42
|
+
npm install -g agents-chat-cli
|
|
34
43
|
```
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
> 旧包名 `@iamsamyiok/agents-chat` 同步发布,两个包内容一致,命令都叫 `agents-chat`。
|
|
46
|
+
|
|
47
|
+
### 免安装单文件 exe
|
|
48
|
+
|
|
49
|
+
从 [Releases](https://github.com/iamsamyiok/agents-chat/releases) 下载对应平台文件(Windows / Linux / macOS Intel / Apple Silicon),双击即用,数据保存在 exe 旁 `.data/` 目录。SHA-256 校验值见 checksums.txt。
|
|
50
|
+
|
|
51
|
+
- Windows SmartScreen 提示「已保护你的电脑」→「更多信息」→「仍要运行」
|
|
52
|
+
- macOS 首次运行:`xattr -d com.apple.quarantine agents-chat-darwin-*`
|
|
53
|
+
|
|
54
|
+
## 常用命令
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
agents-chat # 启动(后台运行,自动开浏览器)
|
|
58
|
+
agents-chat status # 查看运行状态
|
|
59
|
+
agents-chat stop # 停止(自动清理执行中的 AI 子进程)
|
|
60
|
+
agents-chat update # 一键升级到最新版
|
|
61
|
+
agents-chat autostart on # 开机自启(定时任务无人值守推荐开启)
|
|
62
|
+
```
|
|
37
63
|
|
|
38
64
|
## 配置
|
|
39
65
|
|
|
40
|
-
|
|
66
|
+
配置文件位于 `~/.agents-chat/.env`(exe 形态在 `.data/.env`),全部可省略:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
AGENTS_CHAT_WEBHOOK_URL= # 任务完成转发地址(钉钉/飞书中转)
|
|
70
|
+
AGENTS_CHAT_PRUNE_DAYS=15 # 历史数据保留天数
|
|
71
|
+
AGENTS_CHAT_TIMEOUT_MS=600000 # 单任务超时
|
|
72
|
+
AGENTS_CHAT_AUTO_APPROVE=0 # 关闭 OpenCode 自动放行
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
完整说明见仓库 `.env.example` 与页面内「帮助」。
|
|
76
|
+
|
|
77
|
+
## 支持的执行内核
|
|
78
|
+
|
|
79
|
+
OpenCode(推荐)/ Claude Code / Codex CLI / pi,页面右上角可切换。多智能体编排深度适配 OpenCode,其余内核以单轮对话方式执行。
|
|
80
|
+
|
|
81
|
+
## 开发
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/iamsamyiok/agents-chat
|
|
85
|
+
cd agents-chat && npm install
|
|
86
|
+
AGENTS_CHAT_MOCK=1 npm start # 演示模式开发,无需内核
|
|
87
|
+
npm test # 66 个测试用例
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
零运行时依赖,Node 原生模块实现(HTTP/SSE/存储均为自研轻量层)。
|
|
91
|
+
|
|
92
|
+
## License
|
|
41
93
|
|
|
42
|
-
|
|
94
|
+
MIT
|
package/app/public/index.html
CHANGED
|
@@ -215,14 +215,14 @@
|
|
|
215
215
|
.at-tx i { font-style: normal; color: #999; font-size: 11px; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 380px; }
|
|
216
216
|
|
|
217
217
|
/* ---------- 弹窗 ---------- */
|
|
218
|
-
#modalMask, #batchMask, #agentsMask, #historyMask, #helpMask, #flowMask, #cardMask, #teamsMask, #memMask, #diffMask { position: fixed; inset: 0; background: rgba(0,0,0,.4); display: none; align-items: center; justify-content: center; z-index: 51; }
|
|
218
|
+
#modalMask, #batchMask, #agentsMask, #historyMask, #helpMask, #flowMask, #cardMask, #teamsMask, #memMask, #diffMask, #welcomeMask { position: fixed; inset: 0; background: rgba(0,0,0,.4); display: none; align-items: center; justify-content: center; z-index: 51; }
|
|
219
219
|
/* 弹窗出现动画:遮罩淡入(含毛玻璃)+ 内容上浮,全站弹窗统一节奏 */
|
|
220
220
|
div[id$="Mask"] { backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
|
|
221
221
|
div[id$="Mask"].show { animation: fade-in .18s ease; }
|
|
222
222
|
.modal, .preview-card { animation: modal-in .24s cubic-bezier(.2,.7,.3,1); }
|
|
223
223
|
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
224
224
|
@keyframes modal-in { from { opacity: 0; transform: translateY(14px) scale(.985); } to { opacity: 1; transform: none; } }
|
|
225
|
-
#modalMask.show, #batchMask.show, #agentsMask.show, #historyMask.show, #helpMask.show, #flowMask.show, #cardMask.show, #teamsMask.show, #memMask.show, #diffMask.show { display: flex; }
|
|
225
|
+
#modalMask.show, #batchMask.show, #agentsMask.show, #historyMask.show, #helpMask.show, #flowMask.show, #cardMask.show, #teamsMask.show, #memMask.show, #diffMask.show, #welcomeMask.show { display: flex; }
|
|
226
226
|
/* 团队仓库卡片 */
|
|
227
227
|
.team-card { border: 1px solid #e5e7eb; border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; }
|
|
228
228
|
.team-card .tc-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
|
@@ -261,6 +261,15 @@
|
|
|
261
261
|
.flow-detail .fd-title { font-weight: 600; margin-bottom: 6px; }
|
|
262
262
|
.flow-detail .fd-files { color: #7d5ac8; }
|
|
263
263
|
.modal { background: #fff; border-radius: 10px; width: 560px; max-width: 92vw; max-height: 88vh; overflow-y: auto; padding: 20px; }
|
|
264
|
+
/* 新手引导步骤卡 */
|
|
265
|
+
.onb-step { flex: 1; background: #f5f7fa; border-radius: 8px; padding: 10px; font-size: 12px; line-height: 1.6; display: flex; flex-direction: column; gap: 4px; }
|
|
266
|
+
.onb-step .onb-num { width: 22px; height: 22px; border-radius: 50%; background: #10aeff; color: #fff; font-weight: 600; display: flex; align-items: center; justify-content: center; font-size: 13px; }
|
|
267
|
+
.onb-step span { color: #666; }
|
|
268
|
+
/* 主会话空状态欢迎卡 */
|
|
269
|
+
.main-welcome { text-align: center; padding: 44px 18px; color: #666; }
|
|
270
|
+
.main-welcome h4 { font-size: 16px; margin: 0 0 8px; color: #222; }
|
|
271
|
+
.main-welcome p { font-size: 13px; line-height: 1.9; margin: 0 0 14px; }
|
|
272
|
+
.main-welcome .onb-acts { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
|
|
264
273
|
/* Git 改动视图:行级着色 */
|
|
265
274
|
.diff-pre { margin: 6px 0 0; padding: 8px; background: #fff; border-radius: 6px; font-size: 12px; line-height: 1.55; overflow-x: auto; white-space: pre; font-family: ui-monospace, Menlo, Consolas, monospace; }
|
|
266
275
|
.diff-pre .dl { padding: 0 4px; border-radius: 3px; }
|
|
@@ -516,6 +525,24 @@
|
|
|
516
525
|
</div>
|
|
517
526
|
</div>
|
|
518
527
|
|
|
528
|
+
<!-- 新手引导弹窗 -->
|
|
529
|
+
<div id="welcomeMask">
|
|
530
|
+
<div class="modal" style="width:600px">
|
|
531
|
+
<h3>👋 欢迎使用 Agents Chat</h3>
|
|
532
|
+
<div class="tip">一个页面指挥一支 AI 智能体团队:你说需求,管家拆解调度,多个智能体并行干活、互相验收。三步上手:</div>
|
|
533
|
+
<div style="display:flex;gap:10px;margin:10px 0">
|
|
534
|
+
<div class="onb-step"><div class="onb-num">1</div><b>直接聊</b><span>下方输入框发需求,管家自动安排团队分工协作</span></div>
|
|
535
|
+
<div class="onb-step"><div class="onb-num">2</div><b>批量任务</b><span>「导入任务」一次粘贴多条,可排序、可定时、可 Git 隔离</span></div>
|
|
536
|
+
<div class="onb-step"><div class="onb-num">3</div><b>无人值守</b><span>定时任务到点自动跑,完成推送通知,错过自动补跑</span></div>
|
|
537
|
+
</div>
|
|
538
|
+
<div class="tip" id="onbKernelTip" style="margin-bottom:6px"></div>
|
|
539
|
+
<div class="modal-actions">
|
|
540
|
+
<button onclick="closeWelcome(true)">我自己逛逛</button>
|
|
541
|
+
<button class="primary" onclick="seedDemoTasks()">🚀 一键体验示例(自动导入并执行)</button>
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
|
|
519
546
|
<!-- 任务 Git 改动弹窗(隔离区 diff / 合并 / 丢弃) -->
|
|
520
547
|
<div id="diffMask">
|
|
521
548
|
<div class="modal" style="max-width:860px">
|
|
@@ -675,6 +702,8 @@
|
|
|
675
702
|
<h3>❓ 使用帮助</h3>
|
|
676
703
|
<div id="helpBody"></div>
|
|
677
704
|
<div class="modal-actions">
|
|
705
|
+
<button class="mini-btn" onclick="closeHelp(); openWelcome()">👋 重新查看新手引导</button>
|
|
706
|
+
<span style="flex:1"></span>
|
|
678
707
|
<button class="primary" onclick="closeHelp()">关闭</button>
|
|
679
708
|
</div>
|
|
680
709
|
</div>
|
|
@@ -720,7 +749,7 @@
|
|
|
720
749
|
</div>
|
|
721
750
|
|
|
722
751
|
<script>
|
|
723
|
-
const PAGE_VERSION = '3.
|
|
752
|
+
const PAGE_VERSION = '3.33.0'; // 与服务端 /api/health.version 互检,不一致说明页面缓存过期
|
|
724
753
|
const AV_COLORS = ['#5b8def','#07c160','#fa9d3b','#10aeff','#8a6fe8','#fa5151','#e8a33d','#3aa7a3'];
|
|
725
754
|
const PHASE_LABEL = { plan: '调度规划', work: '执行', review: '验收', report: '汇总', talk: '圆桌发言', task: '任务执行' };
|
|
726
755
|
// 职业图标库(智能体配置可选)
|
|
@@ -839,6 +868,15 @@ async function init() {
|
|
|
839
868
|
renderUpdateInfo(health);
|
|
840
869
|
refreshNotifyBtn();
|
|
841
870
|
|
|
871
|
+
// 新手引导:从未使用过(主会话无消息、无任务、本机未看过引导)→ 自动弹欢迎
|
|
872
|
+
try {
|
|
873
|
+
if (!localStorage.getItem(ONBOARD_KEY)
|
|
874
|
+
&& getSess('').length === 0
|
|
875
|
+
&& tasks.filter(t => t.kind !== 'scheduled').length === 0) {
|
|
876
|
+
openWelcome();
|
|
877
|
+
}
|
|
878
|
+
} catch { /* localStorage 不可用时跳过 */ }
|
|
879
|
+
|
|
842
880
|
input.addEventListener('input', handleAtInput);
|
|
843
881
|
input.addEventListener('blur', () => setTimeout(hideAt, 150));
|
|
844
882
|
// 消息区滚动:贴底时收起「新消息」悬浮按钮
|
|
@@ -1483,7 +1521,7 @@ function renderTasks() {
|
|
|
1483
1521
|
${wtBtn(t)}
|
|
1484
1522
|
<button class="mini-btn danger" title="删除任务及其会话" onclick="event.stopPropagation();delTask('${escAttr(t.id)}')">✕</button>
|
|
1485
1523
|
</div>`;
|
|
1486
|
-
}).join('') : '<div class="task-empty">暂无顺序任务<br>点上方「导入任务」添加<br>(单聊任务由 OpenCode
|
|
1524
|
+
}).join('') : '<div class="task-empty">暂无顺序任务<br>点上方「导入任务」添加<br>(单聊任务由 OpenCode 单体执行)<br><button class="mini-btn" style="margin-top:6px" onclick="seedDemoTasks()">🚀 试试示例任务</button></div>';
|
|
1487
1525
|
}
|
|
1488
1526
|
el.innerHTML = `<div class="list-sep">${taskView === 'sched' ? '定时任务(单聊 · 到点自动执行)' : '单聊任务(点击查看会话,可拖拽排序)'}</div>` + tasksHtml;
|
|
1489
1527
|
return;
|
|
@@ -1531,7 +1569,7 @@ function renderTasks() {
|
|
|
1531
1569
|
|
|
1532
1570
|
let tasksHtml;
|
|
1533
1571
|
if (vis.length === 0) {
|
|
1534
|
-
tasksHtml = '<div class="task-empty">暂无顺序任务<br>点上方「导入任务」添加<br>定时任务请切到上方「⏰
|
|
1572
|
+
tasksHtml = '<div class="task-empty">暂无顺序任务<br>点上方「导入任务」添加<br>定时任务请切到上方「⏰ 定时任务」<br><button class="mini-btn" style="margin-top:6px" onclick="seedDemoTasks()">🚀 试试示例任务</button></div>';
|
|
1535
1573
|
} else {
|
|
1536
1574
|
tasksHtml = vis.map((t) => {
|
|
1537
1575
|
const icon = t.status === 'done' ? '✓' : t.status === 'failed' ? '!' : t.status === 'running' ? '▶' : '•';
|
|
@@ -1974,6 +2012,18 @@ function renderMessages() {
|
|
|
1974
2012
|
box.appendChild(node);
|
|
1975
2013
|
finalizeMsgLinks(all[i]); // 定稿消息中的 .md 路径变成可点击预览链接
|
|
1976
2014
|
}
|
|
2015
|
+
// 主会话空状态:欢迎卡 + 快捷动作(新用户零点击成本看到入口)
|
|
2016
|
+
if (curTaskId === '' && chatMode === 'group' && all.length === 0) {
|
|
2017
|
+
box.innerHTML = `<div class="main-welcome">
|
|
2018
|
+
<h4>👋 今天想让团队做点什么?</h4>
|
|
2019
|
+
<p>直接在下方输入需求,管家会拆解调度智能体团队协作完成;<br>也可以先导入一批任务批量执行,或点「试试示例」看完整流程。</p>
|
|
2020
|
+
<div class="onb-acts">
|
|
2021
|
+
<button class="primary" onclick="seedDemoTasks()">🚀 试试示例</button>
|
|
2022
|
+
<button onclick="openImport()">📥 导入任务</button>
|
|
2023
|
+
<button onclick="openHelp()">📖 查看帮助</button>
|
|
2024
|
+
</div>
|
|
2025
|
+
</div>`;
|
|
2026
|
+
}
|
|
1977
2027
|
scrollBottom();
|
|
1978
2028
|
requestAnimationFrame(() => requestAnimationFrame(() => box.classList.remove('bulk')));
|
|
1979
2029
|
const jb = document.getElementById('jumpBtn'); if (jb) jb.style.display = 'none';
|
|
@@ -2693,6 +2743,41 @@ async function discardTaskDiff() {
|
|
|
2693
2743
|
else toast(data.error || '丢弃失败');
|
|
2694
2744
|
}
|
|
2695
2745
|
|
|
2746
|
+
// ---------- 新手引导与一键示例 ----------
|
|
2747
|
+
const ONBOARD_KEY = 'acOnboarded';
|
|
2748
|
+
function closeWelcome(markSeen) {
|
|
2749
|
+
document.getElementById('welcomeMask').classList.remove('show');
|
|
2750
|
+
if (markSeen) try { localStorage.setItem(ONBOARD_KEY, '1'); } catch { /* 隐私模式忽略 */ }
|
|
2751
|
+
}
|
|
2752
|
+
function openWelcome() {
|
|
2753
|
+
const tip = document.getElementById('onbKernelTip');
|
|
2754
|
+
const rs = runnerState || {};
|
|
2755
|
+
if (rs.kind === 'demo') tip.innerHTML = '🧪 当前为<b>演示模式</b>(模拟输出,无需任何配置即可体验完整流程);安装 OpenCode 后即为真实执行';
|
|
2756
|
+
else if (rs.kind === 'missing') tip.innerHTML = '⚠ 未检测到执行内核,真实任务前需安装:npm install -g opencode-ai(详见帮助)';
|
|
2757
|
+
else tip.innerHTML = `✅ 运行内核就绪:${rs.label || 'OpenCode'}`;
|
|
2758
|
+
document.getElementById('welcomeMask').classList.add('show');
|
|
2759
|
+
}
|
|
2760
|
+
// 一键体验:自动导入一组示例任务并立即执行(群聊=管家编排双任务;单聊=OpenCode 续聊链)
|
|
2761
|
+
async function seedDemoTasks() {
|
|
2762
|
+
closeWelcome(true);
|
|
2763
|
+
const solo = chatMode === 'solo';
|
|
2764
|
+
const text = solo
|
|
2765
|
+
? '1. 用一句话介绍你能帮我做什么,再列出你最擅长的三类任务\n2. -继续:针对上面三类任务,各举一个具体例子说明你会怎么完成'
|
|
2766
|
+
: '1. 盘点当前工作目录的文件与子目录结构,输出一份简明的目录说明\n2. 根据上一任务的盘点结果,为这个项目生成一份 README.md 草稿';
|
|
2767
|
+
const data = await api('/api/tasks/import', {
|
|
2768
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
2769
|
+
body: JSON.stringify({ text, mode: 'sequential', runner: solo ? 'solo' : '' })
|
|
2770
|
+
});
|
|
2771
|
+
if (!data.success) { toast(data.error || '示例导入失败'); return; }
|
|
2772
|
+
tasks = data.tasks || [];
|
|
2773
|
+
renderSidebar();
|
|
2774
|
+
addSys(`🚀 已导入 ${data.added} 个示例任务,开始体验(随时可在任务列表删除)`);
|
|
2775
|
+
const ids = tasks.filter(t => t.runner === (solo ? 'solo' : '') && t.kind === 'sequential' && t.status === 'pending').map(t => t.id);
|
|
2776
|
+
if (!ids.length) { toast('示例任务已导入,请手动执行'); return; }
|
|
2777
|
+
if (solo) await soloExecTasks(ids.slice(0, 2));
|
|
2778
|
+
else await runTask(ids[0]);
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2696
2781
|
async function clearTasks() {
|
|
2697
2782
|
if (!confirm('确定清空全部任务?(任务会话的对话记录也会从列表移除)')) return;
|
|
2698
2783
|
await api('/api/tasks/clear', { method: 'POST' });
|
package/app/server.js
CHANGED
|
@@ -30,9 +30,14 @@ const { spawn } = require('child_process');
|
|
|
30
30
|
} catch { /* 日志系统自身失败:静默,服务照常 */ }
|
|
31
31
|
})();
|
|
32
32
|
|
|
33
|
-
// 首先加载 .env
|
|
33
|
+
// 首先加载 .env(行为开关配置):
|
|
34
|
+
// 1) 数据目录 .env(npm/exe 形态用户配置处:~/.agents-chat/.env 或 exe 旁 .data/.env,优先)
|
|
35
|
+
// 2) 程序根目录 .env(源码开发形态)
|
|
34
36
|
const { loadEnv } = require('./lib/env');
|
|
35
37
|
const ROOT_DIR = path.join(__dirname, '..');
|
|
38
|
+
const ENV_DATA_DIR = process.env.AGENTS_CHAT_DATA
|
|
39
|
+
|| (process.env.AGENTS_CHAT_STANDALONE === '1' && process.execPath ? path.join(path.dirname(process.execPath), '.data') : '');
|
|
40
|
+
loadEnv(path.join(ENV_DATA_DIR || path.join(ROOT_DIR, '.data'), '.env'));
|
|
36
41
|
loadEnv(path.join(ROOT_DIR, '.env'));
|
|
37
42
|
|
|
38
43
|
// ---------- 日志 tee:控制台输出同时写入 .data/server.log ----------
|
package/package.json
CHANGED
package/scripts/build-exe.js
CHANGED
|
@@ -76,7 +76,9 @@ if (failed.length) {
|
|
|
76
76
|
const { createHash } = require('crypto');
|
|
77
77
|
const lines = [];
|
|
78
78
|
for (const f of fs.readdirSync(DIST).sort()) {
|
|
79
|
+
if (!/^agents-chat-/.test(f)) continue; // 只计算产物文件(跳过 .data 等目录与旧清单)
|
|
79
80
|
const fp = path.join(DIST, f);
|
|
81
|
+
try { if (!fs.statSync(fp).isFile()) continue; } catch { continue; }
|
|
80
82
|
const h = createHash('sha256').update(fs.readFileSync(fp)).digest('hex');
|
|
81
83
|
lines.push(`${h} ${f}`);
|
|
82
84
|
}
|