@yeaft/webchat-agent 0.1.213 → 0.1.217
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/role-output.js +10 -1
- package/crew/role-query.js +15 -1
- package/crew/routing.js +1 -1
- package/crew/task-files.js +43 -6
- package/package.json +1 -1
package/crew/role-output.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/crew/role-query.js
CHANGED
|
@@ -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
|
-
...(
|
|
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
|
}
|
package/crew/task-files.js
CHANGED
|
@@ -92,16 +92,53 @@ export async function readTaskFile(session, taskId) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
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 (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|