@iamsamyiok/agents-chat 3.19.2 → 3.20.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 +19 -1
- package/app/public/cards.html +11 -1
- package/app/public/index.html +1 -2
- package/app/server.js +17 -5
- package/package.json +1 -1
package/app/lib/cards.js
CHANGED
|
@@ -209,6 +209,24 @@ function isEligible(card, all) {
|
|
|
209
209
|
return true;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// 环检测:把 cardId 的依赖改为 newDeps 后,沿依赖边 DFS 是否能回到 cardId 自身
|
|
213
|
+
// 用于 PUT 写入拦截(间接环也拦截,如 A→B→C→A)
|
|
214
|
+
function wouldCycle(cardId, newDeps) {
|
|
215
|
+
const all = CardStore.list();
|
|
216
|
+
const byId = new Map(all.filter(c => c.id !== cardId).map(c => [c.id, c]));
|
|
217
|
+
const seen = new Set();
|
|
218
|
+
const stack = [...(newDeps || [])];
|
|
219
|
+
while (stack.length) {
|
|
220
|
+
const cur = stack.pop();
|
|
221
|
+
if (cur === cardId) return true;
|
|
222
|
+
if (seen.has(cur)) continue;
|
|
223
|
+
seen.add(cur);
|
|
224
|
+
const c = byId.get(cur);
|
|
225
|
+
if (c) stack.push(...(c.dependsOn || []));
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
212
230
|
function pickEligible(all, activeIds) {
|
|
213
231
|
const eligible = all.filter(c => isEligible(c, all) && !activeIds.has(c.id));
|
|
214
232
|
eligible.sort((a, b) => {
|
|
@@ -529,4 +547,4 @@ function isValidDir(p) {
|
|
|
529
547
|
|
|
530
548
|
const runner = new CardRunner();
|
|
531
549
|
|
|
532
|
-
module.exports = { CardStore, CardRunner, runner, sseSubscribe, buildCardPrompt, isEligible, pickEligible, MAX_PARALLEL, CARDS_PATH, CARDS_CFG_PATH };
|
|
550
|
+
module.exports = { CardStore, CardRunner, runner, sseSubscribe, buildCardPrompt, isEligible, pickEligible, wouldCycle, MAX_PARALLEL, CARDS_PATH, CARDS_CFG_PATH };
|
package/app/public/cards.html
CHANGED
|
@@ -310,10 +310,20 @@ function errSummary(err){
|
|
|
310
310
|
if(!err) return '';
|
|
311
311
|
return String(err).split('\n').map(s=>s.trim()).filter(Boolean)[0] || '';
|
|
312
312
|
}
|
|
313
|
-
// 依赖阻塞判定:pending
|
|
313
|
+
// 依赖阻塞判定:pending 卡的依赖存在未完成(等待)或已失败(阻塞);依赖链成环时明确提示
|
|
314
314
|
function blockedInfo(card){
|
|
315
315
|
if(card.status!=='pending' || !(card.dependsOn&&card.dependsOn.length)) return null;
|
|
316
316
|
const byId=Object.fromEntries(CARDS.map(c=>[c.id,c]));
|
|
317
|
+
// 环检测:沿依赖边 DFS,回到自身即成环(历史数据兜底,正常写入已被服务端拦截)
|
|
318
|
+
const seen=new Set(), stack=[...card.dependsOn];
|
|
319
|
+
while(stack.length){
|
|
320
|
+
const cur=stack.pop();
|
|
321
|
+
if(cur===card.id) return {kind:'fail',text:'循环依赖:依赖链最终依赖本任务,全量执行时将被跳过'};
|
|
322
|
+
if(seen.has(cur)) continue;
|
|
323
|
+
seen.add(cur);
|
|
324
|
+
const c=byId[cur];
|
|
325
|
+
if(c) stack.push(...(c.dependsOn||[]));
|
|
326
|
+
}
|
|
317
327
|
const failedDeps=[], waitDeps=[];
|
|
318
328
|
for(const d of card.dependsOn){
|
|
319
329
|
const dc=byId[d];
|
package/app/public/index.html
CHANGED
|
@@ -634,7 +634,7 @@
|
|
|
634
634
|
</div>
|
|
635
635
|
|
|
636
636
|
<script>
|
|
637
|
-
const PAGE_VERSION = '3.
|
|
637
|
+
const PAGE_VERSION = '3.20.0'; // 与服务端 /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
|
// 职业图标库(智能体配置可选)
|
|
@@ -2319,7 +2319,6 @@ async function doImport() {
|
|
|
2319
2319
|
(data.warnings || []).forEach(w => chatMode === 'solo' ? addSoloSys('⚠ ' + w) : addSys('⚠ ' + w));
|
|
2320
2320
|
closeImport();
|
|
2321
2321
|
importText.value = '';
|
|
2322
|
-
if (importMode === 'scheduled' && schedOpen) renderSchedList();
|
|
2323
2322
|
} else toast(data.error || '导入失败');
|
|
2324
2323
|
}
|
|
2325
2324
|
|
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.
|
|
3
|
+
const APP_VERSION = '3.20.0'; // 页面与服务端版本互检,不一致提示强刷
|
|
4
4
|
const http = require('http');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
@@ -38,7 +38,7 @@ const { runAgent, stopScope, stopAllChildren } = require('./lib/agent');
|
|
|
38
38
|
const { runButler, runMentioned, runRoundtable, runTasks, prepareRerun } = require('./lib/orchestrator');
|
|
39
39
|
const oc = require('./lib/oc');
|
|
40
40
|
const memoryMod = require('./lib/memory');
|
|
41
|
-
const { CardStore, runner: cardRunner, sseSubscribe, MAX_PARALLEL } = require('./lib/cards');
|
|
41
|
+
const { CardStore, runner: cardRunner, sseSubscribe, MAX_PARALLEL, wouldCycle } = require('./lib/cards');
|
|
42
42
|
|
|
43
43
|
// ---------- 人工审批关卡:orchestrator 暂停等待用户放行(方案/交付),SSE 断线后可经 /api/approvals 恢复 ----------
|
|
44
44
|
const pendingApprovals = new Map(); // approvalId -> {kind,label,taskId,resolve,timer}
|
|
@@ -1071,16 +1071,22 @@ const server = http.createServer(async (req, res) => {
|
|
|
1071
1071
|
const card = CardStore.get(id);
|
|
1072
1072
|
if (!card) { json(res, 404, { success: false, error: '卡牌不存在' }); return; }
|
|
1073
1073
|
const msgs = store.getMessages(id);
|
|
1074
|
+
// 中文友好的日期时间(YYYY-MM-DD HH:mm,避免随系统 locale 变成英文格式)
|
|
1075
|
+
const fmtDateTime = (t) => {
|
|
1076
|
+
const d = new Date(t || Date.now());
|
|
1077
|
+
const p2 = (n) => String(n).padStart(2, '0');
|
|
1078
|
+
return `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())} ${p2(d.getHours())}:${p2(d.getMinutes())}`;
|
|
1079
|
+
};
|
|
1074
1080
|
const roleOf = (m) => (m.role === 'user' ? '用户' : (m.phase === 'archive' ? '归档' : (m.phase === 'system' ? '系统' : 'Agent')));
|
|
1075
1081
|
const lines = [
|
|
1076
1082
|
`# ${card.title}`, '',
|
|
1077
1083
|
`- 状态:${card.status}|模式:${card.mode}|优先级:P${card.priority}`,
|
|
1078
|
-
`- 创建:${
|
|
1084
|
+
`- 创建:${fmtDateTime(card.createdAt)}${card.finishedAt ? `|完成:${fmtDateTime(card.finishedAt)}` : ''}`,
|
|
1079
1085
|
card.error ? `- 失败原因:${card.error.replace(/\n/g, ' ')}` : '',
|
|
1080
1086
|
'', '## 任务内容', '', String(card.content || '').trim(), '', '## 执行过程', ''
|
|
1081
1087
|
];
|
|
1082
1088
|
for (const m of msgs) {
|
|
1083
|
-
lines.push(`### ${
|
|
1089
|
+
lines.push(`### ${fmtDateTime(m.timestamp || Date.now())} · ${roleOf(m)}`, '');
|
|
1084
1090
|
lines.push(String(m.content || '').trim() || '(无内容)');
|
|
1085
1091
|
lines.push('');
|
|
1086
1092
|
}
|
|
@@ -1113,7 +1119,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
1113
1119
|
if (body.priority !== undefined) patch.priority = Number(body.priority) || 999;
|
|
1114
1120
|
if (body.mode !== undefined) patch.mode = ['new', 'continue', 'parallel'].includes(body.mode) ? body.mode : 'new';
|
|
1115
1121
|
if (body.chainId !== undefined) patch.chainId = String(body.chainId).slice(0, 100);
|
|
1116
|
-
if (Array.isArray(body.dependsOn))
|
|
1122
|
+
if (Array.isArray(body.dependsOn)) {
|
|
1123
|
+
const deps = body.dependsOn.map(String).slice(0, 50);
|
|
1124
|
+
// 环检测:修改依赖后若形成(直接或间接)循环依赖则拒绝写入
|
|
1125
|
+
if (wouldCycle(id, deps)) { json(res, 409, { success: false, error: '循环依赖:该任务的依赖链最终会依赖它自己,请调整依赖关系' }); return; }
|
|
1126
|
+
patch.dependsOn = deps;
|
|
1127
|
+
}
|
|
1117
1128
|
if (body.model !== undefined) patch.model = String(body.model).slice(0, 80);
|
|
1118
1129
|
// 状态手动复位:failed/pending -> pending 可重跑
|
|
1119
1130
|
if (body.status === 'pending') { patch.status = 'pending'; patch.result = ''; patch.error = ''; patch.ocSessionId = ''; }
|
|
@@ -1125,6 +1136,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1125
1136
|
|
|
1126
1137
|
if (p.startsWith('/api/cards/') && req.method === 'DELETE') {
|
|
1127
1138
|
const id = p.slice('/api/cards/'.length);
|
|
1139
|
+
if (!CardStore.get(id)) { json(res, 404, { success: false, error: '卡牌不存在' }); return; }
|
|
1128
1140
|
cardRunner.killCard(id);
|
|
1129
1141
|
CardStore.remove(id);
|
|
1130
1142
|
json(res, 200, { success: true });
|