@yeaft/webchat-agent 0.1.498 → 0.1.499
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 +47 -5
- package/crew/task-files.js +29 -3
- package/package.json +1 -1
package/crew/routing.js
CHANGED
|
@@ -235,7 +235,7 @@ export function resolveRoleName(to, session, fromRole) {
|
|
|
235
235
|
* @param {Array<{mimeType, data}>} [turnImages] - auto-attached images from the turn (max 3)
|
|
236
236
|
*/
|
|
237
237
|
export async function executeRoute(session, fromRole, route, turnImages = []) {
|
|
238
|
-
|
|
238
|
+
let { to, summary, taskId, taskTitle } = route;
|
|
239
239
|
|
|
240
240
|
// Auto-resume: paused/stopped → running (route execution means work should continue)
|
|
241
241
|
if (session.status === 'paused' || session.status === 'stopped') {
|
|
@@ -244,14 +244,51 @@ export async function executeRoute(session, fromRole, route, turnImages = []) {
|
|
|
244
244
|
sendStatusUpdate(session);
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
// ─── task-321: taskId fallback chain ─────────────────────────────
|
|
248
|
+
// When a ROUTE omits `task:` (shorthand, bare dispatch, human messages,
|
|
249
|
+
// PM forgetting the field), fall back to:
|
|
250
|
+
// (a) the sender's currentTask.taskId
|
|
251
|
+
// (b) the most recent non-system entry in session.messageHistory
|
|
252
|
+
// This keeps prev-* / designer / architect / shorthand messages from
|
|
253
|
+
// becoming taskId=null orphans that never appear on any feature card.
|
|
254
|
+
if (!taskId) {
|
|
255
|
+
const fromRoleState = session.roleStates?.get(fromRole);
|
|
256
|
+
if (fromRoleState?.currentTask?.taskId) {
|
|
257
|
+
taskId = fromRoleState.currentTask.taskId;
|
|
258
|
+
taskTitle = taskTitle || fromRoleState.currentTask.taskTitle || null;
|
|
259
|
+
} else if (Array.isArray(session.messageHistory) && session.messageHistory.length > 0) {
|
|
260
|
+
for (let i = session.messageHistory.length - 1; i >= 0; i--) {
|
|
261
|
+
const h = session.messageHistory[i];
|
|
262
|
+
if (h && h.from !== 'system' && h.taskId) {
|
|
263
|
+
taskId = h.taskId;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Mirror the fallback back into the route object so downstream
|
|
269
|
+
// consumers (dispatchToRole / sendCrewOutput) see the inferred id.
|
|
270
|
+
if (taskId) {
|
|
271
|
+
route.taskId = taskId;
|
|
272
|
+
if (taskTitle) route.taskTitle = taskTitle;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
247
276
|
// Task 文件自动管理(fire-and-forget)
|
|
248
277
|
if (taskId && summary) {
|
|
249
278
|
const fromRoleConfig = session.roles.get(fromRole);
|
|
250
|
-
|
|
251
|
-
|
|
279
|
+
// task-321: Auto-create feature file even when a non-PM role is the
|
|
280
|
+
// first to mention the taskId. Any role carrying a taskId (PM, devs,
|
|
281
|
+
// reviewers, designer, architect) now triggers creation — not just PM
|
|
282
|
+
// with explicit taskTitle. appendTaskRecord itself also creates the
|
|
283
|
+
// file if missing, so this is a best-effort fast path.
|
|
284
|
+
const effectiveTitle = taskTitle
|
|
285
|
+
|| session.features?.get(taskId)?.taskTitle
|
|
286
|
+
|| null;
|
|
287
|
+
if (effectiveTitle && to !== 'human') {
|
|
288
|
+
ensureTaskFile(session, taskId, effectiveTitle, fromRoleConfig?.isDecisionMaker ? to : fromRole, summary)
|
|
252
289
|
.catch(e => console.warn(`[Crew] Failed to create task file ${taskId}:`, e.message));
|
|
253
290
|
}
|
|
254
|
-
appendTaskRecord(session, taskId, fromRole, summary)
|
|
291
|
+
appendTaskRecord(session, taskId, fromRole, summary, { taskTitle: effectiveTitle })
|
|
255
292
|
.catch(e => console.warn(`[Crew] Failed to append task record ${taskId}:`, e.message));
|
|
256
293
|
|
|
257
294
|
// 更新工作看板:推断状态
|
|
@@ -375,8 +412,13 @@ export async function dispatchToRole(session, roleName, content, fromSource, tas
|
|
|
375
412
|
}
|
|
376
413
|
|
|
377
414
|
// 设置 task
|
|
415
|
+
// task-321: keep currentTask sticky. A new taskId updates it; a dispatch
|
|
416
|
+
// without taskId preserves the previous currentTask so subsequent
|
|
417
|
+
// sendCrewOutput calls (which read roleState.currentTask.taskId) keep
|
|
418
|
+
// attaching to the right feature card — instead of falling back to null
|
|
419
|
+
// the moment the sender omits the `task:` field.
|
|
378
420
|
if (taskId) {
|
|
379
|
-
roleState.currentTask = { taskId, taskTitle };
|
|
421
|
+
roleState.currentTask = { taskId, taskTitle: taskTitle || roleState.currentTask?.taskTitle || null };
|
|
380
422
|
}
|
|
381
423
|
|
|
382
424
|
// Task 上下文注入
|
package/crew/task-files.js
CHANGED
|
@@ -59,15 +59,41 @@ ${m.workRecord}
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* 追加工作记录到 task 文件
|
|
62
|
+
*
|
|
63
|
+
* task-321: auto-create the feature file if it doesn't exist yet. Previously
|
|
64
|
+
* a missing file silently dropped the record, which meant that whenever a
|
|
65
|
+
* non-PM role was first to mention a taskId, the file was never created and
|
|
66
|
+
* every subsequent record — including from PM — would also be dropped. We
|
|
67
|
+
* now recover a title from opts.taskTitle → session.features cache → taskId
|
|
68
|
+
* itself, and create the file on the fly.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} session
|
|
71
|
+
* @param {string} taskId
|
|
72
|
+
* @param {string} roleName
|
|
73
|
+
* @param {string} summary
|
|
74
|
+
* @param {{ taskTitle?: string|null, assignee?: string|null }} [opts]
|
|
62
75
|
*/
|
|
63
|
-
export async function appendTaskRecord(session, taskId, roleName, summary) {
|
|
76
|
+
export async function appendTaskRecord(session, taskId, roleName, summary, opts = {}) {
|
|
64
77
|
const filePath = join(session.sharedDir, 'context', 'features', `${taskId}.md`);
|
|
65
78
|
|
|
79
|
+
let exists = true;
|
|
66
80
|
try {
|
|
67
81
|
await fs.access(filePath);
|
|
68
82
|
} catch {
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
exists = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!exists) {
|
|
87
|
+
const recoveredTitle = opts.taskTitle
|
|
88
|
+
|| session.features?.get(taskId)?.taskTitle
|
|
89
|
+
|| taskId;
|
|
90
|
+
const assignee = opts.assignee || roleName;
|
|
91
|
+
try {
|
|
92
|
+
await ensureTaskFile(session, taskId, recoveredTitle, assignee, summary);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.warn(`[Crew] Failed to auto-create task file ${taskId} on append:`, e.message);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
71
97
|
}
|
|
72
98
|
|
|
73
99
|
const role = session.roles.get(roleName);
|