@yeaft/webchat-agent 0.1.471 → 0.1.473
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/crew/routing.js +1 -1
- package/crew/task-files.js +75 -8
- package/package.json +1 -1
package/crew/routing.js
CHANGED
|
@@ -247,7 +247,7 @@ export async function executeRoute(session, fromRole, route, turnImages = []) {
|
|
|
247
247
|
}
|
|
248
248
|
updateKanban(session, {
|
|
249
249
|
taskId, taskTitle, assignee: resolvedKanbanTo || to,
|
|
250
|
-
status, summary
|
|
250
|
+
status, summary
|
|
251
251
|
}).catch(e => console.warn(`[Crew] Failed to update kanban:`, e.message));
|
|
252
252
|
}
|
|
253
253
|
|
package/crew/task-files.js
CHANGED
|
@@ -254,6 +254,61 @@ export async function saveRoleWorkSummary(session, roleName, accumulatedText) {
|
|
|
254
254
|
// 看板写入锁:防止并发写入
|
|
255
255
|
let _kanbanWriteLock = Promise.resolve();
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* 校验 taskId 是否合法。
|
|
259
|
+
* 拒绝:占位符(<task-id>、task-XX)、纯数字、空/带尖括号。
|
|
260
|
+
* 允许:以字母开头、字母数字/连字符/下划线,例如 task-289、fix-crew-xxx。
|
|
261
|
+
*
|
|
262
|
+
* @param {string} id
|
|
263
|
+
* @returns {boolean}
|
|
264
|
+
*/
|
|
265
|
+
export function isValidTaskId(id) {
|
|
266
|
+
if (typeof id !== 'string') return false;
|
|
267
|
+
const s = id.trim();
|
|
268
|
+
if (!s) return false;
|
|
269
|
+
if (s.includes('<') || s.includes('>')) return false;
|
|
270
|
+
// explicit placeholder
|
|
271
|
+
if (/^task-x+$/i.test(s)) return false;
|
|
272
|
+
// pure digits (e.g. bare "279")
|
|
273
|
+
if (/^\d+$/.test(s)) return false;
|
|
274
|
+
// must start with a letter and contain only alnum/_/-
|
|
275
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]{1,79}$/.test(s)) return false;
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 规范化 "最新进展" 摘要:
|
|
281
|
+
* - 换行/回车合并为单空格
|
|
282
|
+
* - 去掉 markdown 分隔符 `---`、heading `##`
|
|
283
|
+
* - 转义 `|` 为 `\|`(避免撑坏表格)
|
|
284
|
+
* - 合并多余空白
|
|
285
|
+
* - 截断到 maxLen 字符(默认 80),超出追加 …
|
|
286
|
+
*
|
|
287
|
+
* @param {string} summary
|
|
288
|
+
* @param {number} [maxLen=80]
|
|
289
|
+
* @returns {string}
|
|
290
|
+
*/
|
|
291
|
+
export function sanitizeKanbanSummary(summary, maxLen = 80) {
|
|
292
|
+
if (!summary || typeof summary !== 'string') return '-';
|
|
293
|
+
let s = summary;
|
|
294
|
+
// strip horizontal rule lines and heading markers
|
|
295
|
+
s = s.replace(/^\s*-{3,}\s*$/gm, ' ');
|
|
296
|
+
s = s.replace(/^\s*#{1,6}\s+/gm, '');
|
|
297
|
+
// strip leading list/quote markers on each line
|
|
298
|
+
s = s.replace(/^\s*[-*>]\s+/gm, '');
|
|
299
|
+
// newlines → space
|
|
300
|
+
s = s.replace(/[\r\n]+/g, ' ');
|
|
301
|
+
// escape pipe chars so table doesn't break
|
|
302
|
+
s = s.replace(/\|/g, '\\|');
|
|
303
|
+
// collapse whitespace
|
|
304
|
+
s = s.replace(/\s+/g, ' ').trim();
|
|
305
|
+
if (!s) return '-';
|
|
306
|
+
if (s.length > maxLen) {
|
|
307
|
+
s = s.substring(0, Math.max(1, maxLen - 1)) + '…';
|
|
308
|
+
}
|
|
309
|
+
return s;
|
|
310
|
+
}
|
|
311
|
+
|
|
257
312
|
/**
|
|
258
313
|
* 更新工作看板 .crew/context/kanban.md
|
|
259
314
|
*
|
|
@@ -286,12 +341,14 @@ export async function updateKanban(session, opts = {}) {
|
|
|
286
341
|
else if (line.startsWith('|') && !line.startsWith('|--') && section) {
|
|
287
342
|
const cols = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
288
343
|
if (cols.length >= 3 && cols[0] !== m.colTaskId && cols[0] !== 'task-id') {
|
|
344
|
+
// Skip illegal / placeholder rows (e.g. <task-id>, task-XX, bare "279")
|
|
345
|
+
if (!isValidTaskId(cols[0])) continue;
|
|
289
346
|
const entry = {
|
|
290
347
|
taskId: cols[0],
|
|
291
|
-
taskTitle: cols[1],
|
|
348
|
+
taskTitle: cols[1] || cols[0],
|
|
292
349
|
assignee: cols[2] || '-',
|
|
293
350
|
status: cols[3] || '-',
|
|
294
|
-
summary: cols[4] || '-'
|
|
351
|
+
summary: sanitizeKanbanSummary(cols[4] || '-')
|
|
295
352
|
};
|
|
296
353
|
if (section === 'completed') {
|
|
297
354
|
completedEntries.set(entry.taskId, entry);
|
|
@@ -306,6 +363,7 @@ export async function updateKanban(session, opts = {}) {
|
|
|
306
363
|
// 从 session.features 补充缺失的任务
|
|
307
364
|
const completed = session._completedTaskIds || new Set();
|
|
308
365
|
for (const [taskId, feature] of session.features) {
|
|
366
|
+
if (!isValidTaskId(taskId)) continue;
|
|
309
367
|
if (completed.has(taskId)) {
|
|
310
368
|
if (!completedEntries.has(taskId)) {
|
|
311
369
|
completedEntries.set(taskId, {
|
|
@@ -330,11 +388,14 @@ export async function updateKanban(session, opts = {}) {
|
|
|
330
388
|
|
|
331
389
|
// 应用更新
|
|
332
390
|
if (opts.taskId) {
|
|
333
|
-
|
|
391
|
+
// Reject illegal task ids early (placeholders, bare numbers, angle brackets, etc.)
|
|
392
|
+
if (!isValidTaskId(opts.taskId)) {
|
|
393
|
+
console.warn(`[Crew] updateKanban: rejected invalid taskId "${opts.taskId}"`);
|
|
394
|
+
} else if (opts.completed) {
|
|
334
395
|
const entry = entries.get(opts.taskId) || completedEntries.get(opts.taskId);
|
|
335
396
|
if (entry) {
|
|
336
397
|
entry.status = '✅';
|
|
337
|
-
if (opts.summary) entry.summary = opts.summary;
|
|
398
|
+
if (opts.summary) entry.summary = sanitizeKanbanSummary(opts.summary);
|
|
338
399
|
completedEntries.set(opts.taskId, entry);
|
|
339
400
|
entries.delete(opts.taskId);
|
|
340
401
|
}
|
|
@@ -353,15 +414,21 @@ export async function updateKanban(session, opts = {}) {
|
|
|
353
414
|
if (opts.assignee) entry.assignee = opts.assignee;
|
|
354
415
|
if (opts.status) entry.status = opts.status;
|
|
355
416
|
if (opts.summary) {
|
|
356
|
-
//
|
|
357
|
-
entry.summary = opts.summary
|
|
358
|
-
? opts.summary.substring(0, 97) + '...'
|
|
359
|
-
: opts.summary;
|
|
417
|
+
// 单行化、去 markdown、转义 |、截断到 80
|
|
418
|
+
entry.summary = sanitizeKanbanSummary(opts.summary);
|
|
360
419
|
}
|
|
361
420
|
entries.set(opts.taskId, entry);
|
|
362
421
|
}
|
|
363
422
|
}
|
|
364
423
|
|
|
424
|
+
// Final safety: drop any lingering invalid ids before writing
|
|
425
|
+
for (const id of Array.from(entries.keys())) {
|
|
426
|
+
if (!isValidTaskId(id)) entries.delete(id);
|
|
427
|
+
}
|
|
428
|
+
for (const id of Array.from(completedEntries.keys())) {
|
|
429
|
+
if (!isValidTaskId(id)) completedEntries.delete(id);
|
|
430
|
+
}
|
|
431
|
+
|
|
365
432
|
// 生成看板文件
|
|
366
433
|
const locale = (session.language === 'en') ? 'en-US' : 'zh-CN';
|
|
367
434
|
const now = new Date().toLocaleString(locale, { timeZone: 'Asia/Shanghai' });
|