@hamp10/agentforge 0.2.40 → 0.2.42

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": "@hamp10/agentforge",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "AgentForge worker — connect your machine to agentforge.ai",
5
5
  "type": "module",
6
6
  "bin": {
@@ -340,6 +340,49 @@ try {
340
340
  /removed 1 rejected generated scoped file/i,
341
341
  'retry feedback should tell the agent that generated scoped pages were removed as failed output'
342
342
  );
343
+ const generatedDeltaRel = 'public_html/domains/delta.html';
344
+ const generatedEpsilonRel = 'public_html/domains/epsilon.html';
345
+ writeFileSync(
346
+ path.join(fixture.repo, generatedDeltaRel),
347
+ '<!doctype html><html><body><main><section><h1>Delta</h1><p>New scoped page.</p></section></main></body></html>'
348
+ );
349
+ writeFileSync(
350
+ path.join(fixture.repo, generatedEpsilonRel),
351
+ '<!doctype html><html><body><main><section><h1>Epsilon</h1><p>New scoped page.</p></section></main></body></html>'
352
+ );
353
+ const multiGeneratedQualityMessage = 'Work on the Example.com listing pages for Delta.ai and Epsilon.ai. Make both pages visually polished.';
354
+ assert.deepEqual(
355
+ worker._extractUiVerificationFailureSlugs(
356
+ 'delta.html: Visual warning: hero text fails local contrast verification.',
357
+ multiGeneratedQualityMessage
358
+ ),
359
+ ['delta-ai'],
360
+ 'visual verification parsing should identify the scoped page named by a page-prefixed warning'
361
+ );
362
+ assert.deepEqual(
363
+ worker._extractUiVerificationFailureSlugs(
364
+ 'Visual warning: hero text fails local contrast verification.',
365
+ multiGeneratedQualityMessage
366
+ ),
367
+ [],
368
+ 'generic visual warnings should not be treated as evidence to reset every generated scoped page'
369
+ );
370
+ worker._restoreGeneratedScopedUiTargets(
371
+ [{ root: fixture.repo, head: fixture.head, initialDirtyPaths: [] }],
372
+ multiGeneratedQualityMessage,
373
+ { onlySlugs: ['delta-ai'] }
374
+ );
375
+ assert.equal(
376
+ existsSync(path.join(fixture.repo, generatedDeltaRel)),
377
+ false,
378
+ 'page-aware generated resets should remove the rejected generated page'
379
+ );
380
+ assert.equal(
381
+ existsSync(path.join(fixture.repo, generatedEpsilonRel)),
382
+ true,
383
+ 'page-aware generated resets should preserve unrelated generated scoped pages'
384
+ );
385
+ rmSync(path.join(fixture.repo, generatedEpsilonRel), { force: true });
343
386
  const projectsRoot = mkdtempSync(path.join(tmpdir(), 'agentforge-project-list-'));
344
387
  let agentWorkspace = null;
345
388
  try {
@@ -461,6 +504,16 @@ try {
461
504
  /Requested scoped UI targets: alpha, beta/i,
462
505
  'multi-target UI quality retries should preserve the full scoped target set'
463
506
  );
507
+ assert.equal(
508
+ worker._formatRepeatedVisualRepairNudge(2),
509
+ '',
510
+ 'early visual repair retries should stay concise before escalation'
511
+ );
512
+ assert.match(
513
+ worker._formatRepeatedVisualRepairNudge(3),
514
+ /current repair strategy is not converging/i,
515
+ 'repeated visual failures should force a strategy change instead of another micro-edit loop'
516
+ );
464
517
  assert.match(
465
518
  worker._buildScopedUiTargetSetNudge(baseline, message),
466
519
  /one delivery set/i,
@@ -1076,11 +1129,31 @@ assert.match(
1076
1129
  /UI_QUALITY_NUDGE_LIMIT\s*=\s*isVisualUiTask\s*\?\s*Math\.min\(MAX_NUDGES,\s*3\)/,
1077
1130
  'visual UI quality repair loops should use the UI repair budget, not the smaller generic nudge limit'
1078
1131
  );
1079
- assert.match(
1080
- workerSource,
1081
- /UI_REPAIR_NUDGE_LIMIT[\s\S]*consumeUiRepairNudge[\s\S]*reached \$\{UI_REPAIR_NUDGE_LIMIT\} total repair retries/i,
1132
+ assert.match(
1133
+ workerSource,
1134
+ /UI_REPAIR_NUDGE_LIMIT[\s\S]*consumeUiRepairNudge[\s\S]*reached \$\{UI_REPAIR_NUDGE_LIMIT\} total repair retries/i,
1082
1135
  'visual UI retries need a global repair budget across rejection types'
1083
1136
  );
1137
+ assert.doesNotMatch(
1138
+ workerSource,
1139
+ /consumeUiRepairNudge\('(?:completion |publish )?missed scoped UI target'/,
1140
+ 'missing scoped target recovery should not spend the visual QA budget that may have been consumed by prior screenshot failures'
1141
+ );
1142
+ assert.match(
1143
+ workerSource,
1144
+ /_formatRepeatedVisualRepairNudge[\s\S]*current repair strategy is not converging/i,
1145
+ 'repeated visual failures should inject a generic strategy-change nudge'
1146
+ );
1147
+ assert.match(
1148
+ workerSource,
1149
+ /_formatUiVerificationFailureLogSummary[\s\S]*UI visual verification issue\(s\)/i,
1150
+ 'worker logs should expose concise visual verification issues for operators'
1151
+ );
1152
+ assert.match(
1153
+ workerSource,
1154
+ /Address every requested target that is currently missing or changed in one coherent pass/i,
1155
+ 'generated-page visual retries should preserve the full delivery set after selective resets'
1156
+ );
1084
1157
  assert.match(
1085
1158
  workerSource,
1086
1159
  /AGENTFORGE_UI_REPAIR_BUDGET \|\| '12'/,
package/src/worker.js CHANGED
@@ -945,11 +945,19 @@ export class AgentForgeWorker extends EventEmitter {
945
945
  return restored;
946
946
  }
947
947
 
948
- _restoreGeneratedScopedUiTargets(repoBaselines, userMessage) {
948
+ _scopedUiPathMatchesAnySlug(relativePath, slugs) {
949
+ const matches = this._scopeSlugsMatchingText(relativePath, slugs);
950
+ return matches.length > 0;
951
+ }
952
+
953
+ _restoreGeneratedScopedUiTargets(repoBaselines, userMessage, options = {}) {
949
954
  if (!this._isBroadUiQualityTask(userMessage)) return [];
950
955
  if (!Array.isArray(repoBaselines) || repoBaselines.length === 0) return [];
951
956
  const { slugs: allowedSlugs, pageOnly } = this._extractExplicitScope(userMessage);
952
957
  if (allowedSlugs.length === 0 || !pageOnly) return [];
958
+ const onlySlugs = Array.isArray(options.onlySlugs)
959
+ ? [...new Set(options.onlySlugs.map(slug => this._scopeSlug(slug)).filter(Boolean))]
960
+ : [];
953
961
 
954
962
  const restored = [];
955
963
  for (const baseline of repoBaselines) {
@@ -970,6 +978,7 @@ export class AgentForgeWorker extends EventEmitter {
970
978
  const files = [...names]
971
979
  .filter(rel => !initialDirty.has(rel))
972
980
  .filter(rel => this._scopeAllowsChangedPath(baseline, rel, allowedSlugs, pageOnly, userMessage))
981
+ .filter(rel => onlySlugs.length === 0 || this._scopedUiPathMatchesAnySlug(rel, onlySlugs))
973
982
  .filter(rel => !this._gitPathExistsAtRef(baseline.root, baseline.head || 'HEAD', rel))
974
983
  .sort();
975
984
  if (files.length === 0) continue;
@@ -1444,10 +1453,30 @@ export class AgentForgeWorker extends EventEmitter {
1444
1453
  return [
1445
1454
  'AgentForge removed the rejected generated target file(s) before this retry.',
1446
1455
  ...lines,
1447
- 'Treat those removed files as failed output, not source material. Rebuild the requested target page-owned file(s) from inspected project context and comparable existing pages, then verify the rebuilt pages visually.',
1456
+ 'Treat those removed files as failed output, not source material. Rebuild the requested target page-owned file(s) from inspected project context and comparable existing pages before optional refinements to other targets.',
1457
+ 'If the task names multiple target pages, keep them as one delivery set: every named target must exist, be materially addressed, and be visually verified before completion.',
1448
1458
  ].join('\n');
1449
1459
  }
1450
1460
 
1461
+ _formatRepeatedVisualRepairNudge(attempt = 1) {
1462
+ if (Number(attempt || 1) < 3) return '';
1463
+ return [
1464
+ 'Repeated visual verification is still failing, so the current repair strategy is not converging.',
1465
+ 'Switch strategy before the next verification pass: make a coherent page-owned structural pass instead of isolated warning fixes, stale-string edits, color tweaks, or hero-only adjustments.',
1466
+ 'Inspect the screenshots as whole screens across top, middle, and lower viewports. Resolve the underlying composition, readability, spacing, section balance, and content-depth problems in the target-owned files, then verify every requested target page again.',
1467
+ ].join('\n');
1468
+ }
1469
+
1470
+ _formatUiVerificationFailureLogSummary(nudge) {
1471
+ const lines = String(nudge || '')
1472
+ .split('\n')
1473
+ .map(line => line.trim())
1474
+ .filter(line => /(?:^|[\s:])(Visual warning|Style warning):/i.test(line))
1475
+ .slice(0, 3)
1476
+ .map(line => line.replace(/\s+/g, ' ').slice(0, 240));
1477
+ return lines.join(' | ');
1478
+ }
1479
+
1451
1480
  _findUiImplementationArtifactChanges(repoBaselines, userMessage) {
1452
1481
  if (!this._isBroadUiQualityTask(userMessage)) return [];
1453
1482
  if (!Array.isArray(repoBaselines) || repoBaselines.length === 0) return [];
@@ -4244,9 +4273,8 @@ export class AgentForgeWorker extends EventEmitter {
4244
4273
  const interimIncompleteScopedUiTargetsNudge = this._buildIncompleteScopedUiTargetsNudge(repoBaselines, scopeAwareUserMessage);
4245
4274
  if (interimIncompleteScopedUiTargetsNudge) {
4246
4275
  nudgeCount++;
4247
- const repairBudget = consumeUiRepairNudge('missed scoped UI target', interimIncompleteScopedUiTargetsNudge);
4248
4276
  if (nudgeCount < MAX_NUDGES) {
4249
- console.log(`[${taskId}] Iteration missed scoped UI target(s) — continuing (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${repairBudget})`);
4277
+ console.log(`[${taskId}] Iteration missed scoped UI target(s) — continuing (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${uiRepairNudgeCount}/${UI_REPAIR_NUDGE_LIMIT})`);
4250
4278
  iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${interimIncompleteScopedUiTargetsNudge}\n\nContinue from the current changed files. Do not restart from scratch; address the missing target page(s), then verify each edited target screen after the final edit.`);
4251
4279
  continue;
4252
4280
  }
@@ -4365,9 +4393,15 @@ export class AgentForgeWorker extends EventEmitter {
4365
4393
  if (uiVerificationFailureNudge) {
4366
4394
  nudgeCount++;
4367
4395
  const repairBudget = consumeUiRepairNudge('visual verification warnings', uiVerificationFailureNudge);
4396
+ const scopedTargetSetNudge = this._formatScopedUiTargetSetReminder(scopeAwareUserMessage);
4397
+ const repeatedVisualRepairNudge = this._formatRepeatedVisualRepairNudge(uiRepairNudgeCount);
4398
+ const visualFailureSummary = this._formatUiVerificationFailureLogSummary(uiVerificationFailureNudge);
4368
4399
  if (nudgeCount < MAX_NUDGES) {
4369
4400
  console.log(`[${taskId}] UI completion still had visual verification warnings — nudging (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${repairBudget})`);
4370
- iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${uiVerificationFailureNudge}\n\nFix the visible UI issues, verify again with the browser, and only then end with ✓ TASK_COMPLETE.`);
4401
+ if (visualFailureSummary) {
4402
+ console.log(`[${taskId}] UI visual verification issue(s): ${visualFailureSummary}`);
4403
+ }
4404
+ iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${[uiVerificationFailureNudge, scopedTargetSetNudge, repeatedVisualRepairNudge].filter(Boolean).join('\n\n')}\n\nFix the visible UI issues, verify again with the browser, and only then end with ✓ TASK_COMPLETE.`);
4371
4405
  continue;
4372
4406
  }
4373
4407
  throw new Error('UI task claimed completion while visual verification still reported issues');
@@ -4406,9 +4440,8 @@ export class AgentForgeWorker extends EventEmitter {
4406
4440
  const incompleteScopedUiTargetsNudge = this._buildIncompleteScopedUiTargetsNudge(repoBaselines, scopeAwareUserMessage);
4407
4441
  if (incompleteScopedUiTargetsNudge) {
4408
4442
  nudgeCount++;
4409
- const repairBudget = consumeUiRepairNudge('completion missed scoped UI target', incompleteScopedUiTargetsNudge);
4410
4443
  if (nudgeCount < MAX_NUDGES) {
4411
- console.log(`[${taskId}] UI completion missed scoped target(s) — nudging (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${repairBudget})`);
4444
+ console.log(`[${taskId}] UI completion missed scoped target(s) — nudging (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${uiRepairNudgeCount}/${UI_REPAIR_NUDGE_LIMIT})`);
4412
4445
  iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${incompleteScopedUiTargetsNudge}\n\nDo not mark complete until every named target page has been addressed directly and visually verified.`);
4413
4446
  continue;
4414
4447
  }
@@ -4632,9 +4665,15 @@ export class AgentForgeWorker extends EventEmitter {
4632
4665
  if (uiVerificationFailureNudge) {
4633
4666
  nudgeCount++;
4634
4667
  const repairBudget = consumeUiRepairNudge('publish visual verification warnings', uiVerificationFailureNudge);
4668
+ const scopedTargetSetNudge = this._formatScopedUiTargetSetReminder(scopeAwareUserMessage);
4669
+ const repeatedVisualRepairNudge = this._formatRepeatedVisualRepairNudge(uiRepairNudgeCount);
4670
+ const visualFailureSummary = this._formatUiVerificationFailureLogSummary(uiVerificationFailureNudge);
4635
4671
  if (nudgeCount < MAX_NUDGES) {
4636
4672
  console.log(`[${taskId}] Publish evidence still had visual verification warnings — continuing (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${repairBudget})`);
4637
- iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${uiVerificationFailureNudge}\n\nContinue from the current repo state, fix the visible UI issues, verify again, then commit/push any additional changes before reporting delivery complete.`);
4673
+ if (visualFailureSummary) {
4674
+ console.log(`[${taskId}] UI visual verification issue(s): ${visualFailureSummary}`);
4675
+ }
4676
+ iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${[uiVerificationFailureNudge, scopedTargetSetNudge, repeatedVisualRepairNudge].filter(Boolean).join('\n\n')}\n\nContinue from the current repo state, fix the visible UI issues, verify again, then commit/push any additional changes before reporting delivery complete.`);
4638
4677
  continue;
4639
4678
  }
4640
4679
  throw new Error('Publish task still had visual verification issues after repeated retries');
@@ -4673,9 +4712,8 @@ export class AgentForgeWorker extends EventEmitter {
4673
4712
  const incompleteScopedUiTargetsNudge = this._buildIncompleteScopedUiTargetsNudge(repoBaselines, scopeAwareUserMessage);
4674
4713
  if (incompleteScopedUiTargetsNudge) {
4675
4714
  nudgeCount++;
4676
- const repairBudget = consumeUiRepairNudge('publish missed scoped UI target', incompleteScopedUiTargetsNudge);
4677
4715
  if (nudgeCount < MAX_NUDGES) {
4678
- console.log(`[${taskId}] Publish evidence missed scoped target(s) — continuing (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${repairBudget})`);
4716
+ console.log(`[${taskId}] Publish evidence missed scoped target(s) — continuing (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${uiRepairNudgeCount}/${UI_REPAIR_NUDGE_LIMIT})`);
4679
4717
  iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${incompleteScopedUiTargetsNudge}\n\nContinue from the current repo state, address the missing target page(s), verify all edited target screens, then commit/push any additional changes before reporting delivery complete.`);
4680
4718
  continue;
4681
4719
  }
@@ -4765,15 +4803,24 @@ export class AgentForgeWorker extends EventEmitter {
4765
4803
  throw new Error('UI task failed visual verification after repeated repair attempts');
4766
4804
  }
4767
4805
  const repairBudget = consumeUiRepairNudge('visual verification warnings', visualVerificationFailureNudge);
4806
+ const visualFailureSlugs = this._extractUiVerificationFailureSlugs(output, scopeAwareUserMessage);
4768
4807
  const generatedResetNudge = this._formatGeneratedScopedUiResetNudge(
4769
- this._restoreGeneratedScopedUiTargets(repoBaselines, scopeAwareUserMessage)
4808
+ visualFailureSlugs.length > 0
4809
+ ? this._restoreGeneratedScopedUiTargets(repoBaselines, scopeAwareUserMessage, { onlySlugs: visualFailureSlugs })
4810
+ : []
4770
4811
  );
4812
+ const scopedTargetSetNudge = this._formatScopedUiTargetSetReminder(scopeAwareUserMessage);
4813
+ const repeatedVisualRepairNudge = this._formatRepeatedVisualRepairNudge(uiVerificationRetryCount);
4814
+ const visualFailureSummary = this._formatUiVerificationFailureLogSummary(visualVerificationFailureNudge);
4771
4815
  nudgeCount = 0;
4772
4816
  console.log(`[${taskId}] UI task visual verification still reported visible issues — retrying (${uiVerificationRetryCount}/${UI_REPAIR_NUDGE_LIMIT}, total UI repairs ${repairBudget})`);
4817
+ if (visualFailureSummary) {
4818
+ console.log(`[${taskId}] UI visual verification issue(s): ${visualFailureSummary}`);
4819
+ }
4773
4820
  const retryInstruction = generatedResetNudge
4774
- ? 'Rebuild the removed target page file(s) from already-inspected project context and comparable existing pages. Do not reuse the rejected generated page as the basis for the next attempt. Fix the visible issues reported by local browser verification, reopen each edited target screen locally after the final edit, and only then end with ✓ TASK_COMPLETE.'
4821
+ ? 'Rebuild the removed target page file(s) from already-inspected project context and comparable existing pages. Do not reuse the rejected generated page as the basis for the next attempt. Address every requested target that is currently missing or changed in one coherent pass, fix the visible issues reported by local browser verification, reopen each edited target screen locally after the final edit, and only then end with ✓ TASK_COMPLETE.'
4775
4822
  : 'Continue from the current changed files and latest browser evidence. Fix the visible issues reported by the local browser verification, reopen each edited target screen locally after the final edit, and only then end with ✓ TASK_COMPLETE.';
4776
- iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${[visualVerificationFailureNudge, generatedResetNudge].filter(Boolean).join('\n\n')}\n\n${retryInstruction}`);
4823
+ iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\n${[visualVerificationFailureNudge, generatedResetNudge, scopedTargetSetNudge, repeatedVisualRepairNudge].filter(Boolean).join('\n\n')}\n\n${retryInstruction}`);
4777
4824
  } else if (hasMissingLocalUiVerification) {
4778
4825
  uiVerificationRetryCount++;
4779
4826
  const uiVerificationFailureDetails = this._extractUiVerificationFailureDetails(output);
@@ -4782,15 +4829,20 @@ export class AgentForgeWorker extends EventEmitter {
4782
4829
  throw new Error('UI task failed visual verification after repeated repair attempts');
4783
4830
  }
4784
4831
  const repairBudget = consumeUiRepairNudge('missing local visual verification', uiVerificationFailureDetails);
4832
+ const visualFailureSlugs = this._extractUiVerificationFailureSlugs(output, scopeAwareUserMessage);
4785
4833
  const generatedResetNudge = this._formatGeneratedScopedUiResetNudge(
4786
- this._restoreGeneratedScopedUiTargets(repoBaselines, scopeAwareUserMessage)
4834
+ visualFailureSlugs.length > 0
4835
+ ? this._restoreGeneratedScopedUiTargets(repoBaselines, scopeAwareUserMessage, { onlySlugs: visualFailureSlugs })
4836
+ : []
4787
4837
  );
4838
+ const scopedTargetSetNudge = this._formatScopedUiTargetSetReminder(scopeAwareUserMessage);
4839
+ const repeatedVisualRepairNudge = this._formatRepeatedVisualRepairNudge(uiVerificationRetryCount);
4788
4840
  nudgeCount = 0;
4789
4841
  console.log(`[${taskId}] UI task missing local visual verification — retrying with local-app repair instruction (${uiVerificationRetryCount}/${UI_REPAIR_NUDGE_LIMIT}, total UI repairs ${repairBudget})`);
4790
4842
  const retryInstruction = generatedResetNudge
4791
4843
  ? 'Rebuild the removed target page file(s) from already-inspected project context and comparable existing pages. Do not reuse the rejected generated page as the basis for the next attempt.'
4792
4844
  : 'Continue from the current changed files and already-inspected project context. Do not restart from scratch, repeat initial delete/reset/setup steps, discard existing progress, or reread site indexes, shared/global CSS, header/footer partials, or unrelated reference pages unless a specific verification issue requires it.';
4793
- iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\nYour edits are not complete because the changed UI was not successfully loaded and inspected cleanly in its actual local app URL.${uiVerificationFailureDetails ? `\n\nSpecific verification issue(s):\n${uiVerificationFailureDetails}` : ''}${generatedResetNudge ? `\n\n${generatedResetNudge}` : ''}\n\n${retryInstruction} Do not use external sites, site indexes, listing indexes, shared style files, or reference pages as final verification for scoped UI edits. Start or repair the project's local dev/static server if needed, use the port the project actually declares, open the real localhost/127.0.0.1 URL for each edited target screen, inspect the changed screen, fix visible issues, and only then end with ✓ TASK_COMPLETE.`);
4845
+ iterationMessage = withTaskContext(`The task is: "${userMessage}"\n\nYour edits are not complete because the changed UI was not successfully loaded and inspected cleanly in its actual local app URL.${uiVerificationFailureDetails ? `\n\nSpecific verification issue(s):\n${uiVerificationFailureDetails}` : ''}${[generatedResetNudge, scopedTargetSetNudge, repeatedVisualRepairNudge].filter(Boolean).map(text => `\n\n${text}`).join('')}\n\n${retryInstruction} Do not use external sites, site indexes, listing indexes, shared style files, or reference pages as final verification for scoped UI edits. Start or repair the project's local dev/static server if needed, use the port the project actually declares, open the real localhost/127.0.0.1 URL for each edited target screen, inspect the changed screen, fix visible issues, and only then end with ✓ TASK_COMPLETE.`);
4794
4846
  } else if (hasIncompleteTurn) {
4795
4847
  // openclaw reported an incomplete turn (payloads=0) after the agent used a tool.
4796
4848
  // This is a tool timeout, not a narration. Reset nudgeCount and give a targeted retry hint.
@@ -5876,7 +5928,7 @@ end tell`.trim().replace(/\n/g, '\n');
5876
5928
  .split('\n')
5877
5929
  .map(line => line.trim())
5878
5930
  .filter(line =>
5879
- /^(?:Style|Visual) warning:/i.test(line) ||
5931
+ /(?:^|[\s:])(?:Style|Visual) warning:/i.test(line) ||
5880
5932
  /Browser failed to load/i.test(line) ||
5881
5933
  /changed UI was not successfully loaded and inspected/i.test(line) ||
5882
5934
  /latest visual verification still reported/i.test(line) ||
@@ -5886,6 +5938,29 @@ end tell`.trim().replace(/\n/g, '\n');
5886
5938
  return unique.join('\n').slice(0, 1800);
5887
5939
  }
5888
5940
 
5941
+ _extractUiVerificationFailureSlugs(output, userMessage) {
5942
+ const { slugs: allowedSlugs } = this._extractExplicitScope(userMessage);
5943
+ if (allowedSlugs.length === 0) return [];
5944
+ const lines = String(output || '')
5945
+ .split('\n')
5946
+ .map(line => line.trim())
5947
+ .filter(line =>
5948
+ /(?:^|[\s:])(?:Style|Visual) warning:/i.test(line) ||
5949
+ /Browser failed to load/i.test(line) ||
5950
+ /changed UI was not successfully loaded and inspected/i.test(line) ||
5951
+ /latest visual verification still reported/i.test(line) ||
5952
+ /This is not complete/i.test(line)
5953
+ );
5954
+ if (lines.length === 0) return [];
5955
+ const matched = new Set();
5956
+ for (const line of lines) {
5957
+ for (const slug of this._scopeSlugsMatchingText(line, allowedSlugs)) {
5958
+ matched.add(slug);
5959
+ }
5960
+ }
5961
+ return [...matched];
5962
+ }
5963
+
5889
5964
  _buildUiVerificationFailureNudge(output) {
5890
5965
  const details = this._extractUiVerificationFailureDetails(output);
5891
5966
  const genericFailure =