neoagent 2.5.2-beta.17 → 2.5.2-beta.18
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 +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/services/ai/deliverables/artifact_helpers.js +38 -3
- package/server/services/ai/loop/conversation_loop.js +21 -35
- package/server/services/ai/loop/progress_classification.js +164 -0
- package/server/services/ai/taskAnalysis.js +1 -0
- package/server/services/ai/toolEvidence.js +8 -1
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f7852ae0fc3e8f369c66383642692e12
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1291084377" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -134794,7 +134794,7 @@ r===$&&A.b()
|
|
|
134794
134794
|
p.push(A.jP(q,A.j9(!1,new A.a_(B.uG,A.d8(new A.cA(B.jt,new A.a7N(r,q),q),q,q),q),!1,B.H,!0),q,q,0,0,0,q))}r=!1
|
|
134795
134795
|
if(!s.ay)if(!s.ch){r=s.e
|
|
134796
134796
|
r===$&&A.b()
|
|
134797
|
-
r=B.b.u("
|
|
134797
|
+
r=B.b.u("mqgevy6k-3f81df2").length!==0&&r.b}if(r){r=s.d
|
|
134798
134798
|
r===$&&A.b()
|
|
134799
134799
|
r=r.aP&&!r.ai?84:0
|
|
134800
134800
|
s=s.e
|
|
@@ -140506,7 +140506,7 @@ $S:0}
|
|
|
140506
140506
|
A.a_6.prototype={}
|
|
140507
140507
|
A.SQ.prototype={
|
|
140508
140508
|
nb(a){var s=this
|
|
140509
|
-
if(B.b.u("
|
|
140509
|
+
if(B.b.u("mqgevy6k-3f81df2").length===0||s.a!=null)return
|
|
140510
140510
|
s.AU()
|
|
140511
140511
|
s.a=A.on(B.RH,new A.bc8(s))},
|
|
140512
140512
|
AU(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
|
|
@@ -140524,7 +140524,7 @@ if(!t.f.b(k)){s=1
|
|
|
140524
140524
|
break}i=J.a3(k,"buildId")
|
|
140525
140525
|
h=i==null?null:B.b.u(J.p(i))
|
|
140526
140526
|
j=h==null?"":h
|
|
140527
|
-
if(J.bi(j)===0||J.d(j,"
|
|
140527
|
+
if(J.bi(j)===0||J.d(j,"mqgevy6k-3f81df2")){s=1
|
|
140528
140528
|
break}n.b=!0
|
|
140529
140529
|
n.F()
|
|
140530
140530
|
p=2
|
|
@@ -140541,7 +140541,7 @@ case 2:return A.i(o.at(-1),r)}})
|
|
|
140541
140541
|
return A.k($async$AU,r)},
|
|
140542
140542
|
vE(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
|
|
140543
140543
|
var $async$vE=A.h(function(a2,a3){if(a2===1){o.push(a3)
|
|
140544
|
-
s=p}for(;;)switch(s){case 0:if(B.b.u("
|
|
140544
|
+
s=p}for(;;)switch(s){case 0:if(B.b.u("mqgevy6k-3f81df2").length===0||n.c){s=1
|
|
140545
140545
|
break}n.c=!0
|
|
140546
140546
|
n.F()
|
|
140547
140547
|
p=4
|
|
@@ -86,6 +86,25 @@ const CANDIDATE_KEYS = [
|
|
|
86
86
|
'downloadUris',
|
|
87
87
|
];
|
|
88
88
|
|
|
89
|
+
const GENERIC_CANDIDATE_KEYS = new Set([
|
|
90
|
+
'path',
|
|
91
|
+
'paths',
|
|
92
|
+
'file',
|
|
93
|
+
'files',
|
|
94
|
+
'filePath',
|
|
95
|
+
'filePaths',
|
|
96
|
+
'fullPath',
|
|
97
|
+
'fullPaths',
|
|
98
|
+
'downloadUrl',
|
|
99
|
+
'downloadUrls',
|
|
100
|
+
'downloadUri',
|
|
101
|
+
'downloadUris',
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
const EXPLICIT_CANDIDATE_KEYS = new Set(
|
|
105
|
+
CANDIDATE_KEYS.filter((key) => !GENERIC_CANDIDATE_KEYS.has(key))
|
|
106
|
+
);
|
|
107
|
+
|
|
89
108
|
const ARTIFACT_CONTAINER_KEYS = new Set([
|
|
90
109
|
'artifact',
|
|
91
110
|
'artifacts',
|
|
@@ -105,8 +124,22 @@ const ARTIFACT_CONTAINER_KEYS = new Set([
|
|
|
105
124
|
|
|
106
125
|
const CONTAINER_URL_KEYS = new Set(['url', 'urls', 'uri', 'uris', 'href', 'hrefs']);
|
|
107
126
|
|
|
108
|
-
|
|
109
|
-
|
|
127
|
+
const EVIDENCE_RESULT_TOOLS = /^(execute_command|github_|list_|search_|read_|get_|find_|http_request|web_search|browser_get|browser_read|code_navigate|query_structured_data|memory_|session_search|recordings_|read_health_data)/;
|
|
128
|
+
|
|
129
|
+
function allowsGenericCandidateKeys(toolName = '') {
|
|
130
|
+
return !EVIDENCE_RESULT_TOOLS.test(String(toolName || ''));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isExplicitCandidateKey(keyHint = '', parentKeyHint = '', options = {}) {
|
|
134
|
+
if (EXPLICIT_CANDIDATE_KEYS.has(keyHint)) return true;
|
|
135
|
+
if (
|
|
136
|
+
ARTIFACT_CONTAINER_KEYS.has(parentKeyHint)
|
|
137
|
+
&& CANDIDATE_KEYS.includes(keyHint)
|
|
138
|
+
&& (!GENERIC_CANDIDATE_KEYS.has(parentKeyHint) || options.allowGenericKeys === true)
|
|
139
|
+
) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
if (GENERIC_CANDIDATE_KEYS.has(keyHint)) return options.allowGenericKeys === true;
|
|
110
143
|
if (!CONTAINER_URL_KEYS.has(keyHint)) return false;
|
|
111
144
|
return ARTIFACT_CONTAINER_KEYS.has(parentKeyHint);
|
|
112
145
|
}
|
|
@@ -198,6 +231,7 @@ async function extractArtifactsFromResult(toolName, result) {
|
|
|
198
231
|
const seen = new Set();
|
|
199
232
|
const seenCandidates = new Set();
|
|
200
233
|
const fallbackKind = inferArtifactKind(toolName, 'artifact');
|
|
234
|
+
const allowGenericKeys = allowsGenericCandidateKeys(toolName);
|
|
201
235
|
|
|
202
236
|
async function pushCandidate(candidate) {
|
|
203
237
|
const candidateKey = String(candidate || '').trim();
|
|
@@ -214,7 +248,7 @@ async function extractArtifactsFromResult(toolName, result) {
|
|
|
214
248
|
async function visit(value, keyHint = '', parentKeyHint = '') {
|
|
215
249
|
if (value == null) return;
|
|
216
250
|
if (typeof value === 'string') {
|
|
217
|
-
const explicit = isExplicitCandidateKey(keyHint, parentKeyHint);
|
|
251
|
+
const explicit = isExplicitCandidateKey(keyHint, parentKeyHint, { allowGenericKeys });
|
|
218
252
|
if (explicit) {
|
|
219
253
|
if (normalizePathOrUri(value)) await pushCandidate(value);
|
|
220
254
|
return;
|
|
@@ -240,6 +274,7 @@ async function extractArtifactsFromResult(toolName, result) {
|
|
|
240
274
|
}
|
|
241
275
|
|
|
242
276
|
module.exports = {
|
|
277
|
+
allowsGenericCandidateKeys,
|
|
243
278
|
extractArtifactsFromResult,
|
|
244
279
|
inferArtifactKind,
|
|
245
280
|
inferMimeType,
|
|
@@ -125,6 +125,9 @@ const {
|
|
|
125
125
|
getAvailableTools: getAvailableToolsImpl,
|
|
126
126
|
isReadOnlyToolCall: isReadOnlyToolCallImpl,
|
|
127
127
|
} = require('./tool_dispatch');
|
|
128
|
+
const {
|
|
129
|
+
isProgressToolCall,
|
|
130
|
+
} = require('./progress_classification');
|
|
128
131
|
const {
|
|
129
132
|
normalizeOutgoingMessage,
|
|
130
133
|
clampRunContext,
|
|
@@ -225,8 +228,17 @@ function buildErrorPatternGuidance(key, count) {
|
|
|
225
228
|
|
|
226
229
|
const OUTPUT_FINGERPRINT_TOOLS = /^(list_|search_|read_|get_|find_|github_list|github_get|github_search)/;
|
|
227
230
|
|
|
228
|
-
function fingerprintOutput(toolName, result) {
|
|
229
|
-
|
|
231
|
+
function fingerprintOutput(toolName, result, toolArgs = {}) {
|
|
232
|
+
const name = String(toolName || '');
|
|
233
|
+
if (
|
|
234
|
+
!name
|
|
235
|
+
|| (
|
|
236
|
+
!OUTPUT_FINGERPRINT_TOOLS.test(name)
|
|
237
|
+
&& !(name === 'execute_command' && !isProgressToolCall(name, toolArgs))
|
|
238
|
+
)
|
|
239
|
+
) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
230
242
|
const raw = typeof result === 'string' ? result : JSON.stringify(result ?? '');
|
|
231
243
|
if (raw.length < 200) return null;
|
|
232
244
|
// djb2 hash over first 3000 chars — fast, collision-unlikely for our sizes
|
|
@@ -236,18 +248,6 @@ function fingerprintOutput(toolName, result) {
|
|
|
236
248
|
return h >>> 0;
|
|
237
249
|
}
|
|
238
250
|
|
|
239
|
-
// Tools that represent concrete forward progress (write, create, send, update, run).
|
|
240
|
-
// Anything NOT in this set is considered read-only for the analysis-paralysis gate.
|
|
241
|
-
// execute_command counts as progress — it can do anything, including modify state.
|
|
242
|
-
function isProgressTool(toolName) {
|
|
243
|
-
if (!toolName) return false;
|
|
244
|
-
// Neutral / bookkeeping — don't count either way
|
|
245
|
-
if (toolName === 'activate_tools' || toolName === 'save_widget_snapshot') return false;
|
|
246
|
-
// Explicitly read-only patterns
|
|
247
|
-
if (/^(list_|search_|read_file|get_file|find_files?|github_list|github_get|github_search|browser_get|browser_read)/.test(toolName)) return false;
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
251
|
function cloneInterimHistory(history = []) {
|
|
252
252
|
if (!Array.isArray(history)) return [];
|
|
253
253
|
return history.map((item) => ({
|
|
@@ -1084,7 +1084,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
|
|
|
1084
1084
|
const urgency = readOnlyCount >= 6 ? 'CRITICAL' : 'ACTION REQUIRED';
|
|
1085
1085
|
messages.push({
|
|
1086
1086
|
role: 'system',
|
|
1087
|
-
content: `${urgency} — ${readOnlyCount} consecutive read-only turns: You have been gathering information for ${readOnlyCount} turns without writing, creating, sending, or running anything.
|
|
1087
|
+
content: `${urgency} — ${readOnlyCount} consecutive read-only turns: You have been gathering information for ${readOnlyCount} turns without writing, creating, sending, or running anything. Switch method now: establish or reuse a writable checkout, create a task branch, edit files, run verification, open/update a PR, send a concrete progress update, or call task_complete with the real blocker. Do not do more remote tree/list/content scraping first.`,
|
|
1088
1088
|
});
|
|
1089
1089
|
}
|
|
1090
1090
|
}
|
|
@@ -1732,7 +1732,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
|
|
|
1732
1732
|
// Output fingerprint guard: steer away from re-fetching data already seen.
|
|
1733
1733
|
if (!toolErrorMessage) {
|
|
1734
1734
|
const currentRunMeta = engine.getRunMeta(runId);
|
|
1735
|
-
const fp = fingerprintOutput(toolName, toolResult);
|
|
1735
|
+
const fp = fingerprintOutput(toolName, toolResult, toolArgs);
|
|
1736
1736
|
if (fp !== null && currentRunMeta?.seenOutputHashes) {
|
|
1737
1737
|
const prior = currentRunMeta.seenOutputHashes.get(fp);
|
|
1738
1738
|
if (prior) {
|
|
@@ -1742,24 +1742,6 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
|
|
|
1742
1742
|
});
|
|
1743
1743
|
} else {
|
|
1744
1744
|
currentRunMeta.seenOutputHashes.set(fp, { toolName, iteration });
|
|
1745
|
-
// External state: persist large read results to disk so the
|
|
1746
|
-
// model can reference them after context compaction without
|
|
1747
|
-
// re-fetching. Only for significant payloads.
|
|
1748
|
-
const persistRaw = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult ?? '');
|
|
1749
|
-
if (persistRaw.length >= 1000 && runId) {
|
|
1750
|
-
const persistPath = `/tmp/run-${runId.slice(0, 8)}-${toolName}.json`;
|
|
1751
|
-
try {
|
|
1752
|
-
require('fs').writeFileSync(persistPath, persistRaw.slice(0, 40000));
|
|
1753
|
-
if (!currentRunMeta.persistedDataPaths) currentRunMeta.persistedDataPaths = [];
|
|
1754
|
-
if (!currentRunMeta.persistedDataPaths.includes(persistPath)) {
|
|
1755
|
-
currentRunMeta.persistedDataPaths.push(persistPath);
|
|
1756
|
-
messages.push({
|
|
1757
|
-
role: 'system',
|
|
1758
|
-
content: `Data from "${toolName}" (iteration ${iteration}) persisted to ${persistPath}. If context compacts and you need this data again, use execute_command with \`cat ${persistPath}\` instead of re-fetching.`,
|
|
1759
|
-
});
|
|
1760
|
-
}
|
|
1761
|
-
} catch { /* non-fatal — disk full or permissions */ }
|
|
1762
|
-
}
|
|
1763
1745
|
}
|
|
1764
1746
|
}
|
|
1765
1747
|
}
|
|
@@ -1830,7 +1812,11 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
|
|
|
1830
1812
|
&& (analysis.mode === 'execute' || analysis.mode === 'plan_execute')) {
|
|
1831
1813
|
const iterMeta = engine.getRunMeta(runId);
|
|
1832
1814
|
if (iterMeta) {
|
|
1833
|
-
const calledProgress = response.toolCalls.some((tc) =>
|
|
1815
|
+
const calledProgress = response.toolCalls.some((tc) => {
|
|
1816
|
+
let parsedArgs = {};
|
|
1817
|
+
try { parsedArgs = JSON.parse(tc.function?.arguments || '{}'); } catch {}
|
|
1818
|
+
return isProgressToolCall(tc.function?.name || '', parsedArgs);
|
|
1819
|
+
});
|
|
1834
1820
|
iterMeta.consecutiveReadOnlyIterations = calledProgress
|
|
1835
1821
|
? 0
|
|
1836
1822
|
: (iterMeta.consecutiveReadOnlyIterations || 0) + 1;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const READ_ONLY_COMMANDS = new Set([
|
|
4
|
+
'awk',
|
|
5
|
+
'cat',
|
|
6
|
+
'curl',
|
|
7
|
+
'diff',
|
|
8
|
+
'du',
|
|
9
|
+
'egrep',
|
|
10
|
+
'env',
|
|
11
|
+
'fgrep',
|
|
12
|
+
'find',
|
|
13
|
+
'git',
|
|
14
|
+
'grep',
|
|
15
|
+
'head',
|
|
16
|
+
'jq',
|
|
17
|
+
'less',
|
|
18
|
+
'ls',
|
|
19
|
+
'pwd',
|
|
20
|
+
'rg',
|
|
21
|
+
'sed',
|
|
22
|
+
'sort',
|
|
23
|
+
'tail',
|
|
24
|
+
'tee',
|
|
25
|
+
'test',
|
|
26
|
+
'tr',
|
|
27
|
+
'tree',
|
|
28
|
+
'wc',
|
|
29
|
+
'which',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const GIT_READ_ONLY_SUBCOMMANDS = new Set([
|
|
33
|
+
'branch',
|
|
34
|
+
'diff',
|
|
35
|
+
'grep',
|
|
36
|
+
'log',
|
|
37
|
+
'ls-files',
|
|
38
|
+
'ls-remote',
|
|
39
|
+
'rev-parse',
|
|
40
|
+
'show',
|
|
41
|
+
'status',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const STATE_CHANGING_COMMANDS = new Set([
|
|
45
|
+
'apply_patch',
|
|
46
|
+
'chmod',
|
|
47
|
+
'chown',
|
|
48
|
+
'cp',
|
|
49
|
+
'git-clone',
|
|
50
|
+
'git-commit',
|
|
51
|
+
'git-push',
|
|
52
|
+
'git-switch',
|
|
53
|
+
'git-checkout',
|
|
54
|
+
'git-merge',
|
|
55
|
+
'git-rebase',
|
|
56
|
+
'install',
|
|
57
|
+
'mkdir',
|
|
58
|
+
'mv',
|
|
59
|
+
'npm',
|
|
60
|
+
'pnpm',
|
|
61
|
+
'rm',
|
|
62
|
+
'rmdir',
|
|
63
|
+
'touch',
|
|
64
|
+
'yarn',
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
function stripShellNoise(command = '') {
|
|
68
|
+
return String(command || '')
|
|
69
|
+
.replace(/(^|\n)\s*#.*(?=\n|$)/g, '\n')
|
|
70
|
+
.replace(/\s+/g, ' ')
|
|
71
|
+
.trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function firstToken(segment = '') {
|
|
75
|
+
const match = String(segment || '').trim().match(/^([A-Za-z0-9_./-]+)/);
|
|
76
|
+
return match ? match[1] : '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeCommandName(token = '') {
|
|
80
|
+
return String(token || '').trim().split('/').pop().toLowerCase();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function splitCommandSegments(command = '') {
|
|
84
|
+
return stripShellNoise(command)
|
|
85
|
+
.split(/\s*(?:&&|\|\||;|\||\n)\s*/g)
|
|
86
|
+
.map((segment) => segment.trim())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function stripEnvAssignments(segment = '') {
|
|
91
|
+
let text = String(segment || '').trim();
|
|
92
|
+
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(text)) {
|
|
93
|
+
text = text.replace(/^[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s*/, '').trim();
|
|
94
|
+
}
|
|
95
|
+
return text;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function gitSubcommand(segment = '') {
|
|
99
|
+
const parts = stripEnvAssignments(segment).split(/\s+/).filter(Boolean);
|
|
100
|
+
if (normalizeCommandName(parts[0]) !== 'git') return '';
|
|
101
|
+
return String(parts[1] || '').toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isReadOnlyGitCommand(segment = '') {
|
|
105
|
+
const subcommand = gitSubcommand(segment);
|
|
106
|
+
if (!subcommand) return false;
|
|
107
|
+
return GIT_READ_ONLY_SUBCOMMANDS.has(subcommand);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isReadOnlyInterpreterCommand(segment = '') {
|
|
111
|
+
const normalized = stripEnvAssignments(segment);
|
|
112
|
+
const commandName = normalizeCommandName(firstToken(normalized));
|
|
113
|
+
if (!['node', 'perl', 'python', 'python3'].includes(commandName)) return false;
|
|
114
|
+
if (/\b(open|write|writefile|appendfile|unlink|rename|mkdir|rmdir|remove|rm|spawn|exec)\b/i.test(normalized)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return /\b(print|json\.|json_tool|json\.load|json\.loads|sys\.stdin|process\.exit|console\.log)\b|-m\s+json\.tool/i.test(normalized);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isStateChangingShellSegment(segment = '') {
|
|
121
|
+
const normalized = stripEnvAssignments(segment);
|
|
122
|
+
const command = normalizeCommandName(firstToken(normalized));
|
|
123
|
+
if (!command) return false;
|
|
124
|
+
if (command === 'git') {
|
|
125
|
+
const subcommand = gitSubcommand(normalized);
|
|
126
|
+
return subcommand && !GIT_READ_ONLY_SUBCOMMANDS.has(subcommand);
|
|
127
|
+
}
|
|
128
|
+
return STATE_CHANGING_COMMANDS.has(command);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isClearlyReadOnlyShellCommand(command = '') {
|
|
132
|
+
const segments = splitCommandSegments(command);
|
|
133
|
+
if (segments.length === 0) return false;
|
|
134
|
+
return segments.every((segment) => {
|
|
135
|
+
const normalized = stripEnvAssignments(segment);
|
|
136
|
+
if (isStateChangingShellSegment(normalized)) return false;
|
|
137
|
+
if (isReadOnlyGitCommand(normalized)) return true;
|
|
138
|
+
if (isReadOnlyInterpreterCommand(normalized)) return true;
|
|
139
|
+
const commandName = normalizeCommandName(firstToken(normalized));
|
|
140
|
+
if (!commandName) return false;
|
|
141
|
+
return READ_ONLY_COMMANDS.has(commandName);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isProgressToolCall(toolName, toolArgs = {}) {
|
|
146
|
+
const name = String(toolName || '');
|
|
147
|
+
if (!name) return false;
|
|
148
|
+
if (name === 'activate_tools' || name === 'save_widget_snapshot') return false;
|
|
149
|
+
if (/^(list_|search_|read_file|get_file|find_files?|github_list|github_get|github_search|browser_get|browser_read)/.test(name)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (name === 'http_request') {
|
|
153
|
+
return String(toolArgs?.method || 'GET').toUpperCase() !== 'GET';
|
|
154
|
+
}
|
|
155
|
+
if (name === 'execute_command') {
|
|
156
|
+
return !isClearlyReadOnlyShellCommand(toolArgs?.command || '');
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
isClearlyReadOnlyShellCommand,
|
|
163
|
+
isProgressToolCall,
|
|
164
|
+
};
|
|
@@ -81,6 +81,7 @@ const VERIFIER_PROMPT_INSTRUCTIONS = [
|
|
|
81
81
|
];
|
|
82
82
|
const EXECUTION_GUIDANCE_ACTION_LINES = [
|
|
83
83
|
'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.',
|
|
84
|
+
'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.',
|
|
84
85
|
'Use send_interim_update sparingly when a short real update or question would help.',
|
|
85
86
|
'When you must ask for missing required user input, ask once, then wait for the reply instead of re-asking in the same run.',
|
|
86
87
|
'For outbound messages, calls, emails, shared edits, installs, restarts, or task mutations, verify the action result before claiming it happened. If user confirmation is required and missing, draft or ask instead of sending.',
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
const { compactToolResult } = require('./toolResult');
|
|
9
9
|
const { summarizeForLog } = require('./logFormat');
|
|
10
10
|
const { normalizeOutgoingMessage, clampRunContext } = require('./messagingFallback');
|
|
11
|
+
const {
|
|
12
|
+
isClearlyReadOnlyShellCommand,
|
|
13
|
+
} = require('./loop/progress_classification');
|
|
11
14
|
|
|
12
15
|
// Ordered classification rules mapping a tool name to its evidence "source"
|
|
13
16
|
// bucket. First matching rule wins, so order is significant. Declared as data
|
|
@@ -83,7 +86,11 @@ function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = '
|
|
|
83
86
|
|
|
84
87
|
const evidenceRelevant = evidenceRelevantExact.has(name)
|
|
85
88
|
|| evidenceRelevantPrefixes.some((prefix) => name.startsWith(prefix));
|
|
86
|
-
const stateChanged =
|
|
89
|
+
const stateChanged = (
|
|
90
|
+
name === 'execute_command'
|
|
91
|
+
? !isClearlyReadOnlyShellCommand(toolArgs?.command || '')
|
|
92
|
+
: stateChangingExact.has(name)
|
|
93
|
+
)
|
|
87
94
|
|| name.startsWith('android_')
|
|
88
95
|
|| ['browser_click', 'browser_type', 'browser_evaluate'].includes(name);
|
|
89
96
|
|