neoagent 3.2.1-beta.7 → 3.2.1-beta.9
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/package.json +2 -2
- package/server/http/routes.js +7 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +5 -5
- package/server/services/ai/loop/agent_engine_core.js +21 -1
- package/server/services/ai/loop/completion_judge.js +91 -13
- package/server/services/ai/loop/conversation_loop.js +122 -129
- package/server/services/ai/model_failure_cache.js +47 -8
- package/server/services/ai/providers/google.js +101 -95
- package/server/services/ai/taskAnalysis.js +15 -0
- package/server/services/ai/terminal_reply.js +13 -1
- package/server/services/ai/toolEvidence.js +354 -0
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { isAbortError } = require('../../utils/abort');
|
|
4
|
+
const {
|
|
5
|
+
getErrorCode,
|
|
6
|
+
getHttpStatus,
|
|
7
|
+
isTransientIoError,
|
|
8
|
+
retryAfterMilliseconds,
|
|
9
|
+
} = require('../../utils/retry');
|
|
10
|
+
|
|
3
11
|
const DEFAULT_COOLDOWN_MS = 15 * 60 * 1000;
|
|
12
|
+
const DEFAULT_RECOVERY_COOLDOWN_MS = 60 * 1000;
|
|
4
13
|
const failures = new Map();
|
|
5
14
|
|
|
6
15
|
function cacheKey(userId, agentId, modelSelectionId) {
|
|
@@ -11,28 +20,57 @@ function cacheKey(userId, agentId, modelSelectionId) {
|
|
|
11
20
|
].join(':');
|
|
12
21
|
}
|
|
13
22
|
|
|
14
|
-
function
|
|
23
|
+
function readPermanentCooldownMs() {
|
|
15
24
|
const configured = Number(process.env.NEOAGENT_MODEL_NOT_FOUND_COOLDOWN_MS);
|
|
16
25
|
if (!Number.isFinite(configured)) return DEFAULT_COOLDOWN_MS;
|
|
17
26
|
return Math.max(1_000, Math.min(configured, 24 * 60 * 60 * 1000));
|
|
18
27
|
}
|
|
19
28
|
|
|
29
|
+
function readRecoveryCooldownMs() {
|
|
30
|
+
const configured = Number(process.env.NEOAGENT_MODEL_RECOVERY_COOLDOWN_MS);
|
|
31
|
+
if (!Number.isFinite(configured)) return DEFAULT_RECOVERY_COOLDOWN_MS;
|
|
32
|
+
return Math.max(1_000, Math.min(configured, 15 * 60 * 1000));
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
function isPermanentModelFailure(error) {
|
|
21
|
-
const status =
|
|
22
|
-
error?.status
|
|
23
|
-
?? error?.statusCode
|
|
24
|
-
?? error?.response?.status,
|
|
25
|
-
);
|
|
36
|
+
const status = getHttpStatus(error);
|
|
26
37
|
if (status === 404) return true;
|
|
27
38
|
return /\b404\b.*\b(model|nim|provider|request)\b|\b(model|nim)\b.*\b404\b/i
|
|
28
39
|
.test(String(error?.message || ''));
|
|
29
40
|
}
|
|
30
41
|
|
|
42
|
+
function isRecoverableModelFailure(error) {
|
|
43
|
+
if (!error || isAbortError(error) || isPermanentModelFailure(error)) return false;
|
|
44
|
+
const status = getHttpStatus(error);
|
|
45
|
+
if (status === 401 || status === 403 || status === 429 || (status >= 500 && status < 600)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const code = String(getErrorCode(error) || '');
|
|
49
|
+
if (code === 'MODEL_EMPTY_RESPONSE' || code === 'MODEL_CALL_TIMEOUT') return true;
|
|
50
|
+
if (isTransientIoError(error)) return true;
|
|
51
|
+
return /\bempty response|temporarily unavailable|overloaded|rate.?limit|timed? ?out|network error\b/i
|
|
52
|
+
.test(String(error?.message || ''));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveCooldownMs(error, now) {
|
|
56
|
+
if (isPermanentModelFailure(error)) return readPermanentCooldownMs();
|
|
57
|
+
if (!isRecoverableModelFailure(error)) return 0;
|
|
58
|
+
|
|
59
|
+
const configured = readRecoveryCooldownMs();
|
|
60
|
+
const retryAfter = retryAfterMilliseconds(
|
|
61
|
+
error?.headers || error?.response?.headers,
|
|
62
|
+
now,
|
|
63
|
+
);
|
|
64
|
+
if (!Number.isFinite(retryAfter)) return configured;
|
|
65
|
+
return Math.max(configured, Math.min(retryAfter, 15 * 60 * 1000));
|
|
66
|
+
}
|
|
67
|
+
|
|
31
68
|
function recordModelFailure(userId, agentId, modelSelectionId, error, now = Date.now()) {
|
|
32
69
|
const modelId = String(modelSelectionId || '').trim();
|
|
33
|
-
|
|
70
|
+
const cooldownMs = resolveCooldownMs(error, now);
|
|
71
|
+
if (!modelId || cooldownMs <= 0) return false;
|
|
34
72
|
failures.set(cacheKey(userId, agentId, modelId), {
|
|
35
|
-
expiresAt: now +
|
|
73
|
+
expiresAt: now + cooldownMs,
|
|
36
74
|
});
|
|
37
75
|
return true;
|
|
38
76
|
}
|
|
@@ -64,6 +102,7 @@ module.exports = {
|
|
|
64
102
|
clearModelFailureCache,
|
|
65
103
|
isModelCoolingDown,
|
|
66
104
|
isPermanentModelFailure,
|
|
105
|
+
isRecoverableModelFailure,
|
|
67
106
|
recordModelFailure,
|
|
68
107
|
recordModelSuccess,
|
|
69
108
|
};
|
|
@@ -1,7 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { GoogleGenAI } = require('@google/genai');
|
|
2
4
|
const { BaseProvider } = require('./base');
|
|
3
5
|
const { fetchResponseText } = require('../../network/http');
|
|
4
6
|
|
|
7
|
+
function parseToolArguments(value) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(value || '{}');
|
|
10
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
11
|
+
? parsed
|
|
12
|
+
: {};
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeUsage(usage) {
|
|
19
|
+
if (!usage) return null;
|
|
20
|
+
return {
|
|
21
|
+
inputTokens: usage.promptTokenCount || 0,
|
|
22
|
+
outputTokens: usage.candidatesTokenCount || 0,
|
|
23
|
+
reasoningTokens: usage.thoughtsTokenCount || 0,
|
|
24
|
+
cachedReadTokens: usage.cachedContentTokenCount || 0,
|
|
25
|
+
cacheWriteTokens: 0,
|
|
26
|
+
promptTokens: usage.promptTokenCount || 0,
|
|
27
|
+
completionTokens: usage.candidatesTokenCount || 0,
|
|
28
|
+
totalTokens: usage.totalTokenCount || 0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function collectResponseParts(response, toolCalls, seenToolCalls = null) {
|
|
33
|
+
let content = '';
|
|
34
|
+
for (const candidate of response?.candidates || []) {
|
|
35
|
+
for (const part of candidate.content?.parts || []) {
|
|
36
|
+
if (part.text && part.thought !== true) content += part.text;
|
|
37
|
+
if (!part.functionCall?.name) continue;
|
|
38
|
+
|
|
39
|
+
const args = part.functionCall.args || {};
|
|
40
|
+
const providerCallId = String(part.functionCall.id || '').trim();
|
|
41
|
+
const signature = providerCallId
|
|
42
|
+
|| `${part.functionCall.name}:${JSON.stringify(args)}`;
|
|
43
|
+
if (seenToolCalls?.has(signature)) continue;
|
|
44
|
+
seenToolCalls?.add(signature);
|
|
45
|
+
|
|
46
|
+
toolCalls.push({
|
|
47
|
+
id: providerCallId
|
|
48
|
+
|| `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
49
|
+
type: 'function',
|
|
50
|
+
function: {
|
|
51
|
+
name: part.functionCall.name,
|
|
52
|
+
arguments: JSON.stringify(args),
|
|
53
|
+
thought_signature: part.thoughtSignature
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return content;
|
|
59
|
+
}
|
|
60
|
+
|
|
5
61
|
class GoogleProvider extends BaseProvider {
|
|
6
62
|
constructor(config = {}) {
|
|
7
63
|
super(config);
|
|
@@ -29,7 +85,7 @@ class GoogleProvider extends BaseProvider {
|
|
|
29
85
|
'gemini-1.5-flash': 1048576,
|
|
30
86
|
};
|
|
31
87
|
this.apiKey = config.apiKey || process.env.GOOGLE_AI_KEY;
|
|
32
|
-
this.genAI = new
|
|
88
|
+
this.genAI = new GoogleGenAI({ apiKey: this.apiKey });
|
|
33
89
|
}
|
|
34
90
|
|
|
35
91
|
async listModels(signal = null) {
|
|
@@ -78,11 +134,23 @@ class GoogleProvider extends BaseProvider {
|
|
|
78
134
|
functionDeclarations: tools.map(tool => ({
|
|
79
135
|
name: tool.name,
|
|
80
136
|
description: tool.description,
|
|
81
|
-
|
|
137
|
+
parametersJsonSchema: tool.parameters || { type: 'object', properties: {} }
|
|
82
138
|
}))
|
|
83
139
|
}];
|
|
84
140
|
}
|
|
85
141
|
|
|
142
|
+
buildGenerateConfig(systemInstruction, tools, options) {
|
|
143
|
+
const config = {};
|
|
144
|
+
if (systemInstruction) config.systemInstruction = systemInstruction;
|
|
145
|
+
if (tools.length > 0) config.tools = this.formatTools(tools);
|
|
146
|
+
if (options.signal) config.abortSignal = options.signal;
|
|
147
|
+
const maxOutputTokens = Number(options.maxTokens);
|
|
148
|
+
if (Number.isFinite(maxOutputTokens) && maxOutputTokens > 0) {
|
|
149
|
+
config.maxOutputTokens = Math.floor(maxOutputTokens);
|
|
150
|
+
}
|
|
151
|
+
return config;
|
|
152
|
+
}
|
|
153
|
+
|
|
86
154
|
convertMessages(messages) {
|
|
87
155
|
let systemInstruction = '';
|
|
88
156
|
const history = [];
|
|
@@ -93,16 +161,16 @@ class GoogleProvider extends BaseProvider {
|
|
|
93
161
|
continue;
|
|
94
162
|
}
|
|
95
163
|
if (msg.role === 'tool') {
|
|
164
|
+
const functionResponse = {
|
|
165
|
+
name: msg.name || 'tool',
|
|
166
|
+
response: { result: msg.content }
|
|
167
|
+
};
|
|
168
|
+
if (msg.tool_call_id) functionResponse.id = msg.tool_call_id;
|
|
96
169
|
history.push({
|
|
97
|
-
//
|
|
98
|
-
//
|
|
170
|
+
// The current Gemini API represents tool results as functionResponse
|
|
171
|
+
// parts on a user turn.
|
|
99
172
|
role: 'user',
|
|
100
|
-
parts: [{
|
|
101
|
-
functionResponse: {
|
|
102
|
-
name: msg.name || 'tool',
|
|
103
|
-
response: { result: msg.content }
|
|
104
|
-
}
|
|
105
|
-
}]
|
|
173
|
+
parts: [{ functionResponse }]
|
|
106
174
|
});
|
|
107
175
|
continue;
|
|
108
176
|
}
|
|
@@ -113,9 +181,10 @@ class GoogleProvider extends BaseProvider {
|
|
|
113
181
|
const functionCallPart = {
|
|
114
182
|
functionCall: {
|
|
115
183
|
name: tc.function.name,
|
|
116
|
-
args:
|
|
184
|
+
args: parseToolArguments(tc.function.arguments)
|
|
117
185
|
}
|
|
118
186
|
};
|
|
187
|
+
if (tc.id) functionCallPart.functionCall.id = tc.id;
|
|
119
188
|
if (tc.function.thought_signature) {
|
|
120
189
|
functionCallPart.thoughtSignature = tc.function.thought_signature;
|
|
121
190
|
}
|
|
@@ -149,8 +218,8 @@ class GoogleProvider extends BaseProvider {
|
|
|
149
218
|
normalizedHistory.push({ role: currentRole, parts: currentParts });
|
|
150
219
|
}
|
|
151
220
|
|
|
152
|
-
// Structured helpers can consist entirely of system instructions. Gemini
|
|
153
|
-
//
|
|
221
|
+
// Structured helpers can consist entirely of system instructions. Gemini
|
|
222
|
+
// still requires a user turn to trigger generation.
|
|
154
223
|
if (
|
|
155
224
|
normalizedHistory.length === 0
|
|
156
225
|
|| normalizedHistory[normalizedHistory.length - 1].role === 'model'
|
|
@@ -174,53 +243,18 @@ class GoogleProvider extends BaseProvider {
|
|
|
174
243
|
async chat(messages, tools = [], options = {}) {
|
|
175
244
|
const model = options.model || this.config.model || this.getDefaultModel();
|
|
176
245
|
const { systemInstruction, history } = this.convertMessages(messages);
|
|
177
|
-
|
|
178
|
-
const genModel = this.genAI.getGenerativeModel({
|
|
246
|
+
const response = await this.genAI.models.generateContent({
|
|
179
247
|
model,
|
|
180
|
-
|
|
181
|
-
|
|
248
|
+
contents: history,
|
|
249
|
+
config: this.buildGenerateConfig(systemInstruction, tools, options)
|
|
182
250
|
});
|
|
183
|
-
|
|
184
|
-
const lastMessage = history.pop();
|
|
185
|
-
const chat = genModel.startChat({ history });
|
|
186
|
-
const result = await chat.sendMessage(lastMessage.parts, { signal: options.signal });
|
|
187
|
-
const response = result.response;
|
|
188
|
-
|
|
189
|
-
let content = '';
|
|
190
251
|
const toolCalls = [];
|
|
191
|
-
|
|
192
|
-
for (const candidate of response.candidates || []) {
|
|
193
|
-
for (const part of candidate.content?.parts || []) {
|
|
194
|
-
if (part.text) content += part.text;
|
|
195
|
-
if (part.functionCall) {
|
|
196
|
-
toolCalls.push({
|
|
197
|
-
id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
198
|
-
type: 'function',
|
|
199
|
-
function: {
|
|
200
|
-
name: part.functionCall.name,
|
|
201
|
-
arguments: JSON.stringify(part.functionCall.args || {}),
|
|
202
|
-
thought_signature: part.thoughtSignature
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const usage = response.usageMetadata;
|
|
252
|
+
const content = collectResponseParts(response, toolCalls);
|
|
210
253
|
return {
|
|
211
254
|
content,
|
|
212
255
|
toolCalls,
|
|
213
256
|
finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
|
|
214
|
-
usage:
|
|
215
|
-
inputTokens: usage.promptTokenCount || 0,
|
|
216
|
-
outputTokens: usage.candidatesTokenCount || 0,
|
|
217
|
-
reasoningTokens: usage.thoughtsTokenCount || 0,
|
|
218
|
-
cachedReadTokens: usage.cachedContentTokenCount || 0,
|
|
219
|
-
cacheWriteTokens: 0,
|
|
220
|
-
promptTokens: usage.promptTokenCount || 0,
|
|
221
|
-
completionTokens: usage.candidatesTokenCount || 0,
|
|
222
|
-
totalTokens: usage.totalTokenCount || 0
|
|
223
|
-
} : null,
|
|
257
|
+
usage: normalizeUsage(response.usageMetadata),
|
|
224
258
|
model
|
|
225
259
|
};
|
|
226
260
|
}
|
|
@@ -228,60 +262,32 @@ class GoogleProvider extends BaseProvider {
|
|
|
228
262
|
async *stream(messages, tools = [], options = {}) {
|
|
229
263
|
const model = options.model || this.config.model || this.getDefaultModel();
|
|
230
264
|
const { systemInstruction, history } = this.convertMessages(messages);
|
|
231
|
-
|
|
232
|
-
const genModel = this.genAI.getGenerativeModel({
|
|
265
|
+
const responseStream = await this.genAI.models.generateContentStream({
|
|
233
266
|
model,
|
|
234
|
-
|
|
235
|
-
|
|
267
|
+
contents: history,
|
|
268
|
+
config: this.buildGenerateConfig(systemInstruction, tools, options)
|
|
236
269
|
});
|
|
237
|
-
|
|
238
|
-
const lastMessage = history.pop();
|
|
239
|
-
const chat = genModel.startChat({ history });
|
|
240
|
-
const result = await chat.sendMessageStream(lastMessage.parts, { signal: options.signal });
|
|
241
|
-
|
|
242
270
|
let content = '';
|
|
243
271
|
const toolCalls = [];
|
|
272
|
+
const seenToolCalls = new Set();
|
|
273
|
+
let usage = null;
|
|
244
274
|
|
|
245
|
-
for await (const chunk of
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
yield { type: 'content', content: part.text };
|
|
251
|
-
}
|
|
252
|
-
if (part.functionCall) {
|
|
253
|
-
toolCalls.push({
|
|
254
|
-
id: `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
255
|
-
type: 'function',
|
|
256
|
-
function: {
|
|
257
|
-
name: part.functionCall.name,
|
|
258
|
-
arguments: JSON.stringify(part.functionCall.args || {}),
|
|
259
|
-
thought_signature: part.thoughtSignature
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
275
|
+
for await (const chunk of responseStream) {
|
|
276
|
+
const chunkContent = collectResponseParts(chunk, toolCalls, seenToolCalls);
|
|
277
|
+
if (chunkContent) {
|
|
278
|
+
content += chunkContent;
|
|
279
|
+
yield { type: 'content', content: chunkContent };
|
|
264
280
|
}
|
|
281
|
+
if (chunk.usageMetadata) usage = chunk.usageMetadata;
|
|
265
282
|
}
|
|
266
283
|
|
|
267
|
-
const finalResponse = await result.response;
|
|
268
|
-
const usage = finalResponse.usageMetadata;
|
|
269
|
-
|
|
270
284
|
yield {
|
|
271
285
|
type: 'done',
|
|
272
286
|
content,
|
|
273
287
|
toolCalls,
|
|
274
288
|
finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
|
|
275
|
-
usage: usage
|
|
276
|
-
|
|
277
|
-
outputTokens: usage.candidatesTokenCount || 0,
|
|
278
|
-
reasoningTokens: usage.thoughtsTokenCount || 0,
|
|
279
|
-
cachedReadTokens: usage.cachedContentTokenCount || 0,
|
|
280
|
-
cacheWriteTokens: 0,
|
|
281
|
-
promptTokens: usage.promptTokenCount || 0,
|
|
282
|
-
completionTokens: usage.candidatesTokenCount || 0,
|
|
283
|
-
totalTokens: usage.totalTokenCount || 0
|
|
284
|
-
} : null
|
|
289
|
+
usage: normalizeUsage(usage),
|
|
290
|
+
model
|
|
285
291
|
};
|
|
286
292
|
}
|
|
287
293
|
}
|
|
@@ -24,6 +24,7 @@ const ANALYSIS_SCHEMA_EXAMPLE = {
|
|
|
24
24
|
draft_reply: '',
|
|
25
25
|
goal: 'Answer the user accurately.',
|
|
26
26
|
success_criteria: ['Final reply is correct and specific.'],
|
|
27
|
+
research_targets: [],
|
|
27
28
|
suggested_tools: ['web_search', 'browser_navigate'],
|
|
28
29
|
complexity: 'standard',
|
|
29
30
|
autonomy_level: 'normal',
|
|
@@ -59,6 +60,8 @@ const ANALYSIS_PROMPT_INSTRUCTIONS = [
|
|
|
59
60
|
'Do not suggest create_task, update_task, delete_task, or list_tasks for ordinary immediate work. Use task tools only when the user asks for future, recurring, scheduled, monitored, background, or existing-task management behavior.',
|
|
60
61
|
'Set parallel_work=true when independent tool calls or subagents can materially reduce latency.',
|
|
61
62
|
'Set completion_confidence_required="high" when wrong completion would be costly, state-changing, user-visible, or hard to recover.',
|
|
63
|
+
'For research, product/device comparisons, reviews, fact-checks, or multi-entity look-into requests, use mode="execute" or mode="plan_execute", never direct_answer. Set needs_verification=true, freshness_risk="possible" or higher, and completion_confidence_required="high".',
|
|
64
|
+
'When the user asks to look into multiple devices, products, options, or entities, put each named target into research_targets and success_criteria and keep them exact. Prefer suggested_tools that can open primary sources (web_search plus browser_navigate/http_request) rather than answering from memory.',
|
|
62
65
|
];
|
|
63
66
|
const PLAN_PROMPT_INSTRUCTIONS = [
|
|
64
67
|
'Create a concise execution plan for the current task.',
|
|
@@ -83,9 +86,12 @@ const VERIFIER_PROMPT_INSTRUCTIONS = [
|
|
|
83
86
|
'A successful create_task or update_task tool call is required before claiming a task schedule changed.',
|
|
84
87
|
'If external evidence conflicts with memory, history, or another tool result, preserve the uncertainty instead of flattening it into a single confident claim.',
|
|
85
88
|
'When the draft reply is already correct and fully supported by the evidence, return it unchanged. Do not rewrite for style.',
|
|
89
|
+
'For multi-entity research or comparison replies, every compared target needs supporting tool evidence. If a target was never searched or opened, mark insufficient_evidence and rewrite the reply so it does not invent that target\'s specs.',
|
|
90
|
+
'Search-result snippets alone are weak evidence for concrete product claims. Prefer opened pages, fetched docs, or other primary tool output. If only snippets exist, either keep the claim clearly provisional or mark missing_evidence.',
|
|
86
91
|
];
|
|
87
92
|
const EXECUTION_GUIDANCE_ACTION_LINES = [
|
|
88
93
|
'Act end-to-end. Run independent searches or inspections in parallel when possible. Prefer native integration tools and structured APIs over browser automation or shell scraping. Use exact IDs and required parameters; list or search first when you do not have them.',
|
|
94
|
+
'For research and multi-entity comparisons, cover each requested target with its own search/open path. Do not stop after one partial lead or invent the remaining devices from memory. Open primary sources before stating concrete specs.',
|
|
89
95
|
'For GitHub issue implementation or PR work, fetch the issue once, then establish or reuse a writable local checkout, create a task branch, inspect/edit/test locally, and push/open the PR. Use direct GitHub file mutation tools only as a fallback when a local checkout is unavailable.',
|
|
90
96
|
'Prefer the highest-level available tool for the job. If a tool accepts normal text, JSON, file paths, or line ranges, pass those directly instead of reconstructing equivalent data through shell commands.',
|
|
91
97
|
'Your shell (execute_command) starts in your workspace, and the file tools (read_file, read_files, write_file, edit_file, replace_file_range, list_directory, search_files) operate on that same workspace. Keep source checkouts and generated files in the shared workspace, then prefer file tools for inspection and edits instead of shell snippets. Clone a repo once and reuse it; do not re-clone or re-list the same tree.',
|
|
@@ -317,6 +323,13 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
317
323
|
'successCriteria',
|
|
318
324
|
TASK_ANALYSIS_SUCCESS_CRITERIA_LIMIT,
|
|
319
325
|
);
|
|
326
|
+
const researchTargets = resolveAliasedStringList(
|
|
327
|
+
raw,
|
|
328
|
+
fallback,
|
|
329
|
+
'research_targets',
|
|
330
|
+
'researchTargets',
|
|
331
|
+
TASK_ANALYSIS_SUCCESS_CRITERIA_LIMIT,
|
|
332
|
+
);
|
|
320
333
|
|
|
321
334
|
const draftReply = resolveAliasedText(raw, fallback, 'draft_reply', 'draftReply', '');
|
|
322
335
|
const initialMode = pickEnum(
|
|
@@ -398,6 +411,7 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
398
411
|
draft_reply: draftReply,
|
|
399
412
|
goal: resolveAliasedText(raw, fallback, 'goal', 'goal', ''),
|
|
400
413
|
success_criteria: successCriteria,
|
|
414
|
+
research_targets: researchTargets,
|
|
401
415
|
complexity: mode === 'plan_execute' ? 'complex' : normalizedComplexity,
|
|
402
416
|
autonomy_level: mode === 'plan_execute' ? 'high' : normalizedAutonomyLevel,
|
|
403
417
|
progress_update_policy: pickEnum(
|
|
@@ -567,6 +581,7 @@ function buildExecutionGuidance({ analysis, plan = null, capabilityHealth }) {
|
|
|
567
581
|
`Execution mode: ${analysis.mode}.`,
|
|
568
582
|
analysis.goal ? `Goal: ${analysis.goal}` : '',
|
|
569
583
|
formatBulletSection('Success criteria', analysis.success_criteria),
|
|
584
|
+
formatBulletSection('Research targets', analysis.research_targets),
|
|
570
585
|
analysis.suggested_tools?.length
|
|
571
586
|
? `Advisory tool suggestions: ${analysis.suggested_tools.join(', ')}`
|
|
572
587
|
: '',
|
|
@@ -10,7 +10,13 @@ function normalizeReply(content) {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function hasExternalBlocker(text) {
|
|
13
|
-
return /\b(?:blocked|cannot|can't|could not|couldn't|unable to|do not have access|don't have access|missing (?:access|credentials|permission|information)|need you to|requires? your|waiting for your|please (?:provide|send|choose|confirm|authorize|approve))\b/.test(text);
|
|
13
|
+
return /\b(?:blocked|cannot|can't|could not|couldn't|unable to|do not have access|don't have access|missing (?:access|credentials|permission|information)|need you to|requires? your|waiting for your|please (?:provide|send|choose|confirm|authorize|approve)|blockiert|kann nicht|konnte nicht|mir fehlt|ich brauche von dir|warte auf dein(?:e|en)?|bitte (?:gib|schick|sende|nenn|bestätige|waehle|wähle|genehmige))\b/.test(text);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isTerminalQuestionOrBlockerReply(content) {
|
|
17
|
+
const text = normalizeReply(content);
|
|
18
|
+
if (!text) return false;
|
|
19
|
+
return /[??]/.test(text) || hasExternalBlocker(text);
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/**
|
|
@@ -36,10 +42,16 @@ function isDeferredWorkReply(content) {
|
|
|
36
42
|
/^(?:working on it|checking now|on it)[.!…]*$/,
|
|
37
43
|
/\bi(?:'ll| will)\s+(?:get back to you|update you|report back|let you know|keep you posted)\b/,
|
|
38
44
|
/\b(?:i(?:'ll| will)\s+follow up|stay tuned)\b/,
|
|
45
|
+
/^(?:(?:klar|okay|ok|alles klar)[,.!]?\s+)?ich\s+(?:(?:arbeite|pruefe|prüfe|schaue|untersuche|teste|debugge|starte)\s+(?:(?:gerade|aktuell|jetzt|noch)\b|(?:das|dies|die logs?|den code|deine anfrage|deinen auftrag)\b)|(?:kuemmere|kümmere)\s+mich\s+(?:gerade|aktuell|jetzt|noch)\b)/,
|
|
46
|
+
/^(?:(?:klar|okay|ok|alles klar)[,.!]?\s+)?ich\s+(?:werde|wuerde|würde)\s+(?:jetzt\s+)?(?:pruefen|prüfen|nachsehen|untersuchen|testen|debuggen|starten|fixen|erledigen)\b/,
|
|
47
|
+
/^(?:(?:klar|okay|ok|alles klar)[,.!]?\s+)?(?:lass|lasst)\s+mich\s+(?:(?:das|dies|die logs?|den code)\s+)?(?:pruefen|prüfen|nachsehen|untersuchen|testen|debuggen)\b/,
|
|
48
|
+
/^(?:gib mir|gebt mir)\s+(?:einen\s+)?(?:moment|augenblick)|^(?:bin dran|mache ich)[.!…]*$/,
|
|
49
|
+
/\bich\s+(?:melde mich|gebe dir bescheid|halte dich auf dem laufenden)\b/,
|
|
39
50
|
];
|
|
40
51
|
return patterns.some((pattern) => pattern.test(text));
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
module.exports = {
|
|
44
55
|
isDeferredWorkReply,
|
|
56
|
+
isTerminalQuestionOrBlockerReply,
|
|
45
57
|
};
|