@yeaft/webchat-agent 0.1.213 → 0.1.218

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.
@@ -127,7 +127,8 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
127
127
  // 决策者 turn 完成:检测 TASKS block 中新完成的任务
128
128
  const roleConfig = session.roles.get(roleName);
129
129
  if (roleConfig?.isDecisionMaker) {
130
- const nowCompleted = parseCompletedTasks(roleState.accumulatedText);
130
+ const knownTaskIds = Array.from(session.features.keys());
131
+ const nowCompleted = parseCompletedTasks(roleState.accumulatedText, knownTaskIds);
131
132
  if (nowCompleted.size > 0) {
132
133
  const prev = session._completedTaskIds || new Set();
133
134
  const newlyDone = [];
@@ -197,6 +198,14 @@ export async function processRoleOutput(session, roleName, roleQuery, roleState)
197
198
  if (session.status === 'paused' && roleState.accumulatedText) {
198
199
  const routes = parseRoutes(roleState.accumulatedText);
199
200
  if (routes.length > 0 && session.pendingRoutes.length === 0) {
201
+ // Fill missing taskId from roleState.currentTask (same as normal path L170-175)
202
+ const currentTask = roleState.currentTask;
203
+ for (const route of routes) {
204
+ if (!route.taskId && currentTask) {
205
+ route.taskId = currentTask.taskId;
206
+ route.taskTitle = currentTask.taskTitle;
207
+ }
208
+ }
200
209
  session.pendingRoutes = routes.map(route => ({ fromRole: roleName, route }));
201
210
  console.log(`[Crew] Saved ${routes.length} pending route(s) from aborted ${roleName}`);
202
211
  }
@@ -6,6 +6,7 @@ import { query, Stream } from '../sdk/index.js';
6
6
  import { promises as fs, mkdirSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { getMessages } from '../crew-i18n.js';
9
+ import { handleAskUserQuestion } from '../conversation.js';
9
10
  import ctx from '../context.js';
10
11
 
11
12
  /** Format role label */
@@ -146,6 +147,9 @@ async function _createRoleQueryInner(session, roleName) {
146
147
 
147
148
  // 继承全局 MCP disallowedTools,避免不必要的 tool schema token 消耗
148
149
  const globalDisallowed = ctx.CONFIG?.disallowedTools || [];
150
+ // Crew 角色禁用 Agent 工具,强制通过 ROUTE 块协作
151
+ const crewDisallowed = ['Agent'];
152
+ const effectiveDisallowed = [...globalDisallowed, ...crewDisallowed];
149
153
 
150
154
  const queryOptions = {
151
155
  cwd: roleCwd,
@@ -153,9 +157,19 @@ async function _createRoleQueryInner(session, roleName) {
153
157
  abort: abortController.signal,
154
158
  model: role.model || undefined,
155
159
  appendSystemPrompt: systemPrompt,
156
- ...(globalDisallowed.length > 0 && { disallowedTools: globalDisallowed })
160
+ ...(effectiveDisallowed.length > 0 && { disallowedTools: effectiveDisallowed })
157
161
  };
158
162
 
163
+ // Only the decision maker intercepts AskUserQuestion to forward to Web UI
164
+ if (role.isDecisionMaker) {
165
+ queryOptions.canCallTool = async (toolName, input, toolCtx) => {
166
+ if (toolName === 'AskUserQuestion') {
167
+ return await handleAskUserQuestion(session.id, input, toolCtx);
168
+ }
169
+ return input;
170
+ };
171
+ }
172
+
159
173
  if (savedSessionId) {
160
174
  queryOptions.resume = savedSessionId;
161
175
  console.log(`[Crew] Resuming ${roleName} with sessionId: ${savedSessionId}`);
package/crew/routing.js CHANGED
@@ -140,7 +140,7 @@ export async function executeRoute(session, fromRole, route) {
140
140
  }
141
141
  }
142
142
  updateKanban(session, {
143
- taskId, taskTitle, assignee: to,
143
+ taskId, taskTitle, assignee: resolvedKanbanTo || to,
144
144
  status, summary: summary.substring(0, 100)
145
145
  }).catch(e => console.warn(`[Crew] Failed to update kanban:`, e.message));
146
146
  }
@@ -92,16 +92,53 @@ export async function readTaskFile(session, taskId) {
92
92
  }
93
93
 
94
94
  /**
95
- * 从 TASKS block 文本中提取已完成任务的 taskId 集合
95
+ * 从文本中提取已完成任务的 taskId 集合。
96
+ *
97
+ * Primary: parse TASKS block (---TASKS--- / ---END_TASKS---) for checked items with #taskId.
98
+ * Fallback: when no TASKS block is found AND knownTaskIds are provided,
99
+ * scan the full text for known taskId + completion keyword combinations.
100
+ * This handles cases where the PM mentions task completion in prose without
101
+ * emitting a formal TASKS block.
102
+ *
103
+ * @param {string} text - accumulated role output text
104
+ * @param {string[]} [knownTaskIds] - list of known task IDs to search for in fallback mode
105
+ * @returns {Set<string>} completed task IDs
96
106
  */
97
- export function parseCompletedTasks(text) {
107
+ export function parseCompletedTasks(text, knownTaskIds) {
98
108
  const ids = new Set();
109
+
110
+ // Primary: TASKS block parsing
99
111
  const match = text.match(/---TASKS---([\s\S]*?)---END_TASKS---/);
100
- if (!match) return ids;
101
- for (const line of match[1].split('\n')) {
102
- const m = line.match(/^-\s*\[[xX]\]\s*.+#(\S+)/);
103
- if (m) ids.add(m[1]);
112
+ if (match) {
113
+ for (const line of match[1].split('\n')) {
114
+ const m = line.match(/^-\s*\[[xX]\]\s*.+#(\S+)/);
115
+ if (m) ids.add(m[1]);
116
+ }
117
+ return ids;
104
118
  }
119
+
120
+ // Fallback: scan plain text for known taskId + completion keywords
121
+ if (!knownTaskIds || knownTaskIds.length === 0) return ids;
122
+
123
+ const completionPatterns = [
124
+ /已完成/, /完成/, /已合并/, /合并/, /DONE/, /DONE_MERGED/, /MERGED/,
125
+ /已关闭/, /关闭/, /✅/, /通过/, /passed/i, /merged/i, /completed/i
126
+ ];
127
+
128
+ for (const taskId of knownTaskIds) {
129
+ // Search for lines/sentences containing the taskId
130
+ const escaped = taskId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
131
+ const re = new RegExp(`^.*${escaped}.*$`, 'gm');
132
+ let lineMatch;
133
+ while ((lineMatch = re.exec(text)) !== null) {
134
+ const line = lineMatch[0];
135
+ if (completionPatterns.some(p => p.test(line))) {
136
+ ids.add(taskId);
137
+ break;
138
+ }
139
+ }
140
+ }
141
+
105
142
  return ids;
106
143
  }
107
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.213",
3
+ "version": "0.1.218",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",