neoagent 2.5.2-beta.19 → 2.5.2-beta.20

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.5.2-beta.19",
3
+ "version": "2.5.2-beta.20",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -1 +1 @@
1
- fd7d3312e49bd04c96b7a60923a554e0
1
+ efe2ad6d1aff0358de22202a239e2829
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1886254391" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "2093031085" /* 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("mqggr6tq-b5e1610").length!==0&&r.b}if(r){r=s.d
134797
+ r=B.b.u("mqgmwb41-5bb5db5").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("mqggr6tq-b5e1610").length===0||s.a!=null)return
140509
+ if(B.b.u("mqgmwb41-5bb5db5").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,"mqggr6tq-b5e1610")){s=1
140527
+ if(J.bi(j)===0||J.d(j,"mqgmwb41-5bb5db5")){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("mqggr6tq-b5e1610").length===0||n.c){s=1
140544
+ s=p}for(;;)switch(s){case 0:if(B.b.u("mqgmwb41-5bb5db5").length===0||n.c){s=1
140545
140545
  break}n.c=!0
140546
140546
  n.F()
140547
140547
  p=4
@@ -61,7 +61,6 @@ const {
61
61
  shouldContinueAfterBlankToolFailure,
62
62
  } = require('./blank_recovery');
63
63
  const {
64
- shouldRetryMessagingRun,
65
64
  shouldSendMessagingErrorFallback,
66
65
  } = require('./error_recovery');
67
66
  const {
@@ -143,7 +142,6 @@ const {
143
142
  summarizeToolExecutions,
144
143
  summarizeAvailableTools,
145
144
  inferToolFailureMessage,
146
- buildAutonomousRecoveryContext,
147
145
  } = require('../toolEvidence');
148
146
  const {
149
147
  buildMemoryConsolidationInstructions,
@@ -769,14 +767,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
769
767
  let iteration = 0;
770
768
  let totalTokens = 0;
771
769
  let lastContent = '';
772
- let stepIndex = Number(options.messagingRetryStepOffset || 0);
773
- if (!Number.isFinite(stepIndex) || stepIndex < 0) {
774
- stepIndex = 0;
775
- }
776
- if (options.messagingAutonomousRetryCount > 0) {
777
- const existingStep = db.prepare('SELECT COALESCE(MAX(step_index), 0) AS maxStep FROM agent_steps WHERE run_id = ?').get(runId);
778
- stepIndex = Math.max(stepIndex, Number(existingStep?.maxStep || 0));
779
- }
770
+ let stepIndex = 0;
780
771
  let failedStepCount = 0;
781
772
  let modelFailureRecoveries = 0;
782
773
  let promptMetrics = {};
@@ -1084,7 +1075,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
1084
1075
  const urgency = readOnlyCount >= 6 ? 'CRITICAL' : 'ACTION REQUIRED';
1085
1076
  messages.push({
1086
1077
  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. 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.`,
1078
+ content: `${urgency} — ${readOnlyCount} consecutive read-only turns: You have been gathering information for ${readOnlyCount} turns without implementation progress such as writing, editing, creating a branch, running verification, or delivering a final/blocker. Switch method now: establish or reuse a writable checkout, create a task branch, edit files, run verification, open/update a PR, or call task_complete with the real blocker. If the user has been waiting, send one concise interim update, but that does not satisfy the method switch. Do not do more remote tree/list/content scraping first. Do not assume tool results exist at /tmp paths unless a tool explicitly returned that readable path.`,
1088
1079
  });
1089
1080
  }
1090
1081
  }
@@ -1911,12 +1902,23 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
1911
1902
  reason: recoveryDecision.decision.reason,
1912
1903
  }, { agentId });
1913
1904
  if (recoveryDecision.decision.status === 'continue') {
1914
- throw new Error('Messaging run ended without a judged terminal reply.');
1915
- }
1916
- messages.push({ role: 'assistant', content: lastContent });
1917
- if (conversationId) {
1918
- db.prepare('INSERT INTO conversation_messages (conversation_id, role, content, tokens) VALUES (?, ?, ?, ?)')
1919
- .run(conversationId, 'assistant', lastContent, recoveredTokens);
1905
+ engine.recordRunEvent(userId, runId, 'blank_reply_recovery_blocker_used', {
1906
+ reason: recoveryDecision.decision.reason,
1907
+ stepIndex,
1908
+ failedStepCount,
1909
+ }, { agentId });
1910
+ lastContent = buildDeterministicMessagingFallback({ failedStepCount, stepIndex, toolExecutions });
1911
+ messages.push({ role: 'assistant', content: lastContent });
1912
+ if (conversationId) {
1913
+ db.prepare('INSERT INTO conversation_messages (conversation_id, role, content, tokens) VALUES (?, ?, ?, ?)')
1914
+ .run(conversationId, 'assistant', lastContent, 0);
1915
+ }
1916
+ } else {
1917
+ messages.push({ role: 'assistant', content: lastContent });
1918
+ if (conversationId) {
1919
+ db.prepare('INSERT INTO conversation_messages (conversation_id, role, content, tokens) VALUES (?, ?, ?, ?)')
1920
+ .run(conversationId, 'assistant', lastContent, recoveredTokens);
1921
+ }
1920
1922
  }
1921
1923
  }
1922
1924
  }
@@ -1944,10 +1946,28 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
1944
1946
  stepIndex,
1945
1947
  failedStepCount,
1946
1948
  }, { agentId });
1947
- throw new Error(`Iteration budget exhausted before judged completion after ${maxIterations} iterations.`);
1949
+ if (triggerSource === 'messaging') {
1950
+ lastContent = buildDeterministicMessagingFallback({ failedStepCount, stepIndex, toolExecutions });
1951
+ messages.push({ role: 'assistant', content: lastContent });
1952
+ if (conversationId) {
1953
+ db.prepare('INSERT INTO conversation_messages (conversation_id, role, content, tokens) VALUES (?, ?, ?, ?)')
1954
+ .run(conversationId, 'assistant', lastContent, 0);
1955
+ }
1956
+ } else {
1957
+ throw new Error(`Iteration budget exhausted before judged completion after ${maxIterations} iterations.`);
1958
+ }
1948
1959
  }
1949
1960
  if (stepIndex > 0 && !lastToolWasMessaging && iteration < maxIterations) {
1950
- throw new Error('Run ended without an explicit completion or blocker reply.');
1961
+ if (triggerSource === 'messaging') {
1962
+ lastContent = buildDeterministicMessagingFallback({ failedStepCount, stepIndex, toolExecutions });
1963
+ messages.push({ role: 'assistant', content: lastContent });
1964
+ if (conversationId) {
1965
+ db.prepare('INSERT INTO conversation_messages (conversation_id, role, content, tokens) VALUES (?, ?, ?, ?)')
1966
+ .run(conversationId, 'assistant', lastContent, 0);
1967
+ }
1968
+ } else {
1969
+ throw new Error('Run ended without an explicit completion or blocker reply.');
1970
+ }
1951
1971
  }
1952
1972
  }
1953
1973
 
@@ -2190,78 +2210,6 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
2190
2210
  }
2191
2211
 
2192
2212
  const runMeta = engine.activeRuns.get(runId);
2193
- const retryCount = Number(options.messagingAutonomousRetryCount || 0);
2194
- // Rate-limit errors (429) must not trigger messaging retries: the model
2195
- // won't be available in the milliseconds between retries, so spawning new
2196
- // runs just compounds the rate-limit pressure with no benefit.
2197
- const canRetryMessagingRun = shouldRetryMessagingRun({
2198
- triggerSource,
2199
- options,
2200
- runMeta,
2201
- error: err,
2202
- retryCount,
2203
- retryLimit: engine.getMessagingRetryLimit(maxIterations),
2204
- });
2205
-
2206
- if (canRetryMessagingRun) {
2207
- const recoveryContext = buildAutonomousRecoveryContext({
2208
- err,
2209
- toolExecutions,
2210
- tools,
2211
- userMessage,
2212
- visibleMessageSent: Boolean(
2213
- runMeta?.lastSentMessage
2214
- || runMeta?.lastInterimMessage
2215
- || runMeta?.messagingSent === true
2216
- ),
2217
- });
2218
- db.prepare('UPDATE agent_runs SET status = ?, error = ?, updated_at = datetime(\'now\') WHERE id = ?')
2219
- .run('retrying', err.message, runId);
2220
- console.warn(
2221
- `[Run ${shortenRunId(runId)}] retrying_messaging_attempt=${retryCount + 1} reason=${summarizeForLog(err.message, 140)}`
2222
- );
2223
- engine.cleanupSubagentsForRun(runId, { cancelRunning: true });
2224
- engine.stopMessagingProgressSupervisor(runId);
2225
- engine.activeRuns.delete(runId);
2226
- engine.emit(userId, 'run:interim', {
2227
- runId,
2228
- message: 'Retrying internally after a transient failure.',
2229
- phase: 'retrying'
2230
- });
2231
-
2232
- const retryOptions = {
2233
- ...options,
2234
- runId,
2235
- messagingRetryStepOffset: stepIndex,
2236
- messagingAutonomousRetryCount: retryCount + 1,
2237
- messagingRetryState: {
2238
- lastFinalMessage: String(runMeta?.lastSentMessage || options?.messagingRetryState?.lastFinalMessage || '').trim(),
2239
- explicitMessageSent: runMeta?.explicitMessageSent === true || options?.messagingRetryState?.explicitMessageSent === true,
2240
- interimHistory: cloneInterimHistory([
2241
- ...(Array.isArray(options?.messagingRetryState?.interimHistory) ? options.messagingRetryState.interimHistory : []),
2242
- ...(Array.isArray(runMeta?.interimMessages) ? runMeta.interimMessages : []),
2243
- ]),
2244
- goalContract: mergeGoalContracts(
2245
- options?.messagingRetryState?.goalContract || null,
2246
- runMeta?.goalContract || null,
2247
- ),
2248
- lastUserVisibleUpdateAt: runMeta?.progressLedger?.lastUserVisibleUpdateAt || options?.messagingRetryState?.lastUserVisibleUpdateAt || null,
2249
- lastFinalDeliveryAt: runMeta?.progressLedger?.lastFinalDeliveryAt || options?.messagingRetryState?.lastFinalDeliveryAt || null,
2250
- heartbeatCount: Number(runMeta?.progressLedger?.heartbeatCount || options?.messagingRetryState?.heartbeatCount || 0),
2251
- progressState: runMeta?.progressLedger?.progressState || options?.messagingRetryState?.progressState || 'active',
2252
- lastVerifiedProgressAt: runMeta?.progressLedger?.lastVerifiedProgressAt || options?.messagingRetryState?.lastVerifiedProgressAt || null,
2253
- },
2254
- context: {
2255
- ...(options.context || {}),
2256
- additionalContext: [
2257
- options.context?.additionalContext || '',
2258
- recoveryContext,
2259
- ].filter(Boolean).join('\n\n')
2260
- }
2261
- };
2262
-
2263
- return engine.runWithModel(userId, userMessage, retryOptions, _modelOverride);
2264
- }
2265
2213
 
2266
2214
  const deliverableFailureResponse = err?.deliverableResult?.summary
2267
2215
  || err?.deliverableValidation?.summary
@@ -13,21 +13,10 @@ function isRateLimitError(error = null) {
13
13
  return /429|rate.?limit|free-models-per/i.test(String(error?.message || ''));
14
14
  }
15
15
 
16
- function shouldRetryMessagingRun({
17
- triggerSource,
18
- options = {},
19
- runMeta = null,
20
- error = null,
21
- retryCount = 0,
22
- retryLimit = 0,
23
- } = {}) {
24
- return triggerSource === 'messaging'
25
- && Boolean(options.source)
26
- && Boolean(options.chatId)
27
- && !hasTerminalMessagingDelivery(runMeta)
28
- && error?.disableAutonomousRetry !== true
29
- && !isRateLimitError(error)
30
- && Number(retryCount || 0) < Number(retryLimit || 0);
16
+ function shouldRetryMessagingRun() {
17
+ // Messaging recovery must stay inside the current run. Re-entering
18
+ // runWithModel starts over from the original task and repeats tool work.
19
+ return false;
31
20
  }
32
21
 
33
22
  function shouldSendMessagingErrorFallback({
@@ -16,9 +16,6 @@ const {
16
16
  evaluateProgressLiveness,
17
17
  } = require('./progress_monitor');
18
18
 
19
- // Force a visible WhatsApp status message after this long with no user-visible update
20
- const FORCE_VISIBLE_UPDATE_MS = 2 * 60 * 1000;
21
-
22
19
  function isoNow() {
23
20
  return new Date().toISOString();
24
21
  }
@@ -56,39 +53,7 @@ async function sendRuntimeMessagingHeartbeat(engine, runId, options = {}) {
56
53
  runMeta.lastSupervisorNudgeAt = createdAt;
57
54
  engine.updateRunProgress(runId, { heartbeatCount });
58
55
 
59
- // If the user hasn't seen any update for FORCE_VISIBLE_UPDATE_MS, push one directly
60
- // rather than just queuing another invisible steering message the AI may ignore.
61
56
  const ledger = runMeta.progressLedger || {};
62
- const lastVisibleMs = timestampMs(ledger.lastUserVisibleUpdateAt, 0);
63
- const startedMs = timestampMs(runMeta.startedAtIso, 0) || (Date.now() - 60000);
64
- const silenceSince = Date.now() - (lastVisibleMs > 0 ? lastVisibleMs : startedMs);
65
-
66
- if (silenceSince >= FORCE_VISIBLE_UPDATE_MS && engine.messagingManager && !runMeta.terminalInterim) {
67
- const { platform, chatId } = runMeta.messagingContext || {};
68
- if (platform && chatId) {
69
- const stepNum = ledger.currentStep != null ? ledger.currentStep : '?';
70
- const tool = ledger.currentTool || 'tools';
71
- const statusMsg = options.stalled
72
- ? 'still here, just taking a while on this one...'
73
- : `still working on it... (step ${stepNum}, running ${tool})`;
74
- try {
75
- await engine.messagingManager.sendMessage(runMeta.userId, platform, chatId, statusMsg, {
76
- runId,
77
- agentId: runMeta.agentId,
78
- });
79
- const nowIso = isoNow();
80
- runMeta.progressLedger = { ...ledger, lastUserVisibleUpdateAt: nowIso };
81
- engine.updateRunProgress(runId, { lastUserVisibleUpdateAt: nowIso });
82
- engine.recordRunEvent(runMeta.userId, runId, 'forced_progress_update_sent', {
83
- stepNum, tool, silenceSince, stalled: options.stalled === true,
84
- }, { agentId: runMeta.agentId });
85
- return { sent: true, heartbeat: true, forced: true };
86
- } catch (err) {
87
- console.warn('[Engine] Forced progress update send failed:', err?.message || err);
88
- }
89
- }
90
- }
91
-
92
57
  engine.recordRunEvent(runMeta.userId, runId, 'progress_heartbeat_sent', {
93
58
  stalled: options.stalled === true,
94
59
  currentTool: ledger.currentTool || null,
@@ -117,8 +117,20 @@ function isReadOnlyInterpreterCommand(segment = '') {
117
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
118
  }
119
119
 
120
+ function hasStateChangingRedirect(segment = '') {
121
+ const matches = String(segment || '').matchAll(/(?:^|[^&|;])(?:>>?|1>)\s*(?:"([^"]+)"|'([^']+)'|(\S+))/g);
122
+ for (const match of matches) {
123
+ const target = String(match[1] || match[2] || match[3] || '').trim();
124
+ if (!target || target === '/dev/null') continue;
125
+ if (target.startsWith('/tmp/') || target.startsWith('/var/tmp/')) continue;
126
+ return true;
127
+ }
128
+ return false;
129
+ }
130
+
120
131
  function isStateChangingShellSegment(segment = '') {
121
132
  const normalized = stripEnvAssignments(segment);
133
+ if (hasStateChangingRedirect(normalized)) return true;
122
134
  const command = normalizeCommandName(firstToken(normalized));
123
135
  if (!command) return false;
124
136
  if (command === 'git') {
@@ -146,6 +158,7 @@ function isProgressToolCall(toolName, toolArgs = {}) {
146
158
  const name = String(toolName || '');
147
159
  if (!name) return false;
148
160
  if (name === 'activate_tools' || name === 'save_widget_snapshot') return false;
161
+ if (name === 'send_interim_update') return false;
149
162
  if (/^(list_|search_|read_file|get_file|find_files?|github_list|github_get|github_search|browser_get|browser_read)/.test(name)) {
150
163
  return false;
151
164
  }
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const FIRST_UPDATE_MS = 30 * 1000;
4
- const REPEAT_UPDATE_MS = 45 * 1000;
5
- const STALL_MS = 120 * 1000;
3
+ const FIRST_UPDATE_MS = 60 * 1000;
4
+ const REPEAT_UPDATE_MS = 90 * 1000;
5
+ const STALL_MS = 240 * 1000;
6
6
  const TICK_MS = 15 * 1000;
7
7
 
8
8
  function isoNow() {
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
+ const {
5
+ isClearlyReadOnlyShellCommand,
6
+ } = require('./loop/progress_classification');
4
7
 
5
8
  function canonicalize(value) {
6
9
  if (Array.isArray(value)) return value.map(canonicalize);
@@ -20,6 +23,47 @@ function stableHash(value) {
20
23
  return crypto.createHash('sha256').update(text).digest('hex');
21
24
  }
22
25
 
26
+ function normalizeReadOnlyShellIntent(command = '') {
27
+ const text = String(command || '')
28
+ .replace(/(^|\n)\s*#.*(?=\n|$)/g, '\n')
29
+ .replace(/2?>\s*(?:"[^"]+"|'[^']+'|\S+)/g, '')
30
+ .replace(/\s+/g, ' ')
31
+ .trim();
32
+ const urls = [...text.matchAll(/https?:\/\/[^\s"'`|;&)]+/gi)]
33
+ .map((match) => match[0].replace(/[?#].*$/, ''))
34
+ .sort();
35
+ const paths = [...text.matchAll(/(?:^|\s)(\/[A-Za-z0-9._~/%+-][^\s"'`|;&)]*)/g)]
36
+ .map((match) => match[1].replace(/[?#].*$/, ''))
37
+ .filter((item) => !item.startsWith('/tmp/'))
38
+ .sort();
39
+ const repoSearches = [...text.matchAll(/\brepo:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+/gi)]
40
+ .map((match) => match[0].toLowerCase())
41
+ .sort();
42
+
43
+ if (urls.length || paths.length || repoSearches.length) {
44
+ return {
45
+ kind: 'read_only_shell_intent',
46
+ urls,
47
+ paths,
48
+ repoSearches,
49
+ };
50
+ }
51
+
52
+ return {
53
+ kind: 'read_only_shell_command',
54
+ command: text
55
+ .replace(/\|\s*(cat|head(?:\s+-n?\s+\d+)?|tail(?:\s+-n?\s+\d+)?|wc(?:\s+-[A-Za-z]+)?)\b[^|;&]*/g, '')
56
+ .trim(),
57
+ };
58
+ }
59
+
60
+ function canonicalToolArgs(toolName, args) {
61
+ if (toolName === 'execute_command' && isClearlyReadOnlyShellCommand(args?.command || '')) {
62
+ return normalizeReadOnlyShellIntent(args.command);
63
+ }
64
+ return args || {};
65
+ }
66
+
23
67
  class ToolRepetitionGuard {
24
68
  constructor({ unchangedLimit = 2 } = {}) {
25
69
  this.unchangedLimit = Math.max(1, Number(unchangedLimit) || 2);
@@ -27,7 +71,7 @@ class ToolRepetitionGuard {
27
71
  }
28
72
 
29
73
  key(toolName, args) {
30
- return `${String(toolName || '')}:${stableHash(args || {})}`;
74
+ return `${String(toolName || '')}:${stableHash(canonicalToolArgs(toolName, args))}`;
31
75
  }
32
76
 
33
77
  shouldBlock(toolName, args) {
@@ -44,7 +88,7 @@ class ToolRepetitionGuard {
44
88
  : 1;
45
89
  const next = {
46
90
  toolName,
47
- argsHash: stableHash(args || {}),
91
+ argsHash: stableHash(canonicalToolArgs(toolName, args)),
48
92
  resultHash,
49
93
  unchangedCount,
50
94
  };
@@ -56,5 +100,6 @@ class ToolRepetitionGuard {
56
100
  module.exports = {
57
101
  ToolRepetitionGuard,
58
102
  canonicalize,
103
+ normalizeReadOnlyShellIntent,
59
104
  stableHash,
60
105
  };
@@ -82,6 +82,7 @@ const VERIFIER_PROMPT_INSTRUCTIONS = [
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
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.',
85
+ 'Tool results are already present in the conversation as tool output. Do not assume a tool result was persisted to a readable /tmp path unless that exact path was explicitly returned by the tool result.',
85
86
  'Use send_interim_update sparingly when a short real update or question would help.',
86
87
  'When you must ask for missing required user input, ask once, then wait for the reply instead of re-asking in the same run.',
87
88
  '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.',
@@ -559,6 +560,9 @@ function buildExecutionGuidance({ analysis, plan = null, capabilityHealth }) {
559
560
  ? `Advisory tool suggestions: ${analysis.suggested_tools.join(', ')}`
560
561
  : '',
561
562
  `Autonomy contract: complexity=${analysis.complexity || 'standard'}; autonomy_level=${analysis.autonomy_level || 'normal'}; progress_update_policy=${analysis.progress_update_policy || 'optional'}; parallel_work=${analysis.parallel_work === true}; completion_confidence_required=${analysis.completion_confidence_required || 'medium'}.`,
563
+ analysis.progress_update_policy === 'required'
564
+ ? 'Progress updates are required for this run: when the work becomes slow, changes method, or hits a temporary blocker, use send_interim_update with a concise factual update. Do not use progress updates as completion, and do not continue long read-only work silently after a progress-check nudge.'
565
+ : '',
562
566
  capabilityHealth ? `Capability health:\n${capabilityHealth}` : '',
563
567
  ];
564
568