@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 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
- const { to, summary, taskId, taskTitle } = route;
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
- if (fromRoleConfig?.isDecisionMaker && taskTitle && to !== 'human') {
251
- ensureTaskFile(session, taskId, taskTitle, to, summary)
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 上下文注入
@@ -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
- return;
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.498",
3
+ "version": "0.1.499",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",