@hamp10/agentforge 0.2.47 → 0.2.49

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.47",
3
+ "version": "0.2.49",
4
4
  "description": "AgentForge worker — connect your machine to agentforge.ai",
5
5
  "type": "module",
6
6
  "bin": {
@@ -522,6 +522,71 @@ try {
522
522
  /Requested scoped UI targets: alpha, beta/i,
523
523
  'multi-target UI quality retries should preserve the full scoped target set'
524
524
  );
525
+ const originalFindIncompleteScopedUiTargets = worker._findIncompleteScopedUiTargets;
526
+ worker._findIncompleteScopedUiTargets = () => [{
527
+ repo: fixture.repo,
528
+ missingSlugs: ['beta-ai'],
529
+ touchedSlugs: ['alpha-ai'],
530
+ changedFiles: ['public_html/domains/alpha-ai.html'],
531
+ }];
532
+ const incompleteRepairLead = worker._formatIncompleteScopedUiRepairLead(
533
+ baseline,
534
+ 'Work on the Example.com listing pages for Alpha.ai and Beta.ai. Delete and rebuild both listing pages from a clean start.'
535
+ );
536
+ worker._findIncompleteScopedUiTargets = originalFindIncompleteScopedUiTargets;
537
+ assert.match(
538
+ incompleteRepairLead,
539
+ /missing-target pass for target page\(s\) not yet changed in the current diff: beta-ai/i,
540
+ 'missing-target retries should name only the targets not yet addressed'
541
+ );
542
+ assert.match(
543
+ incompleteRepairLead,
544
+ /Do not delete, rewrite, or restart target page\(s\) already changed[\s\S]*alpha-ai/i,
545
+ 'missing-target retries should avoid replaying earlier work on targets already touched in the diff'
546
+ );
547
+ assert.doesNotMatch(
548
+ incompleteRepairLead,
549
+ /The task is:|Delete and rebuild both/i,
550
+ 'missing-target retries should not put the original reset-heavy task text at the top of the retry prompt'
551
+ );
552
+ assert.throws(
553
+ () => cli._guardDirectFileWritePath(
554
+ path.join(fixture.repo, 'public_html', 'domains', 'alpha.html'),
555
+ fixture.repo,
556
+ { task: incompleteRepairLead }
557
+ ),
558
+ /focused retry pass/i,
559
+ 'direct writes should block already-touched scoped pages during a missing-target retry pass'
560
+ );
561
+ assert.doesNotThrow(
562
+ () => cli._guardDirectFileWritePath(
563
+ path.join(fixture.repo, 'public_html', 'domains', 'beta.html'),
564
+ fixture.repo,
565
+ { task: incompleteRepairLead }
566
+ ),
567
+ 'direct writes should allow the missing scoped page during a missing-target retry pass'
568
+ );
569
+ const visualRepairLead = worker._formatVisualRepairTaskLead(
570
+ 'Work on the Example.com listing pages for Alpha.ai and Beta.ai. Make both visually polished.',
571
+ 'beta-ai: Visual warning: heading is clipped.'
572
+ );
573
+ assert.throws(
574
+ () => cli._guardDirectFileWritePath(
575
+ path.join(fixture.repo, 'public_html', 'domains', 'alpha.html'),
576
+ fixture.repo,
577
+ { task: visualRepairLead }
578
+ ),
579
+ /focused retry pass/i,
580
+ 'direct writes should block non-failing scoped pages during a focused visual repair pass'
581
+ );
582
+ assert.doesNotThrow(
583
+ () => cli._guardDirectFileWritePath(
584
+ path.join(fixture.repo, 'public_html', 'domains', 'beta.html'),
585
+ fixture.repo,
586
+ { task: visualRepairLead }
587
+ ),
588
+ 'direct writes should allow the failing scoped page during a focused visual repair pass'
589
+ );
525
590
  assert.equal(
526
591
  worker._formatRepeatedVisualRepairNudge(2),
527
592
  '',
@@ -914,6 +914,24 @@ export class OpenClawCLI extends EventEmitter {
914
914
  return err;
915
915
  }
916
916
 
917
+ _directFocusedRetryAllowedSlugs(task) {
918
+ const text = String(task || '');
919
+ const patterns = [
920
+ /missing-target pass for target page\(s\) not yet changed in the current diff:\s*([^\n.]+)/i,
921
+ /visual repair pass for currently failing target page\(s\):\s*([^\n.]+)/i,
922
+ ];
923
+ for (const pattern of patterns) {
924
+ const match = text.match(pattern);
925
+ if (!match) continue;
926
+ const slugs = String(match[1] || '')
927
+ .split(/[,;]/)
928
+ .map(item => scopeSlug(item.trim()))
929
+ .filter(Boolean);
930
+ if (slugs.length > 0) return new Set(slugs);
931
+ }
932
+ return null;
933
+ }
934
+
917
935
  _isNestedProjectCopyPath(filePath, workDir) {
918
936
  const relative = path.relative(workDir, filePath);
919
937
  if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return false;
@@ -1048,6 +1066,20 @@ export class OpenClawCLI extends EventEmitter {
1048
1066
  if (!this._directTaskScopeAllowsPath(filePath, workDir, options.task)) {
1049
1067
  throw this._directScopeViolationError(this._formatDirectScopeError(filePath, workDir, options.task));
1050
1068
  }
1069
+ const focusedRetrySlugs = this._directFocusedRetryAllowedSlugs(options.task);
1070
+ if (focusedRetrySlugs && slugs.length > 0) {
1071
+ const matchedScopedSlugs = this._directScopeSlugsMatchingText(lowerRelativePath, slugs);
1072
+ const matchesFocusedSlug = matchedScopedSlugs.some(slug => focusedRetrySlugs.has(slug));
1073
+ if (matchedScopedSlugs.length > 0 && !matchesFocusedSlug) {
1074
+ throw this._directScopeViolationError([
1075
+ 'Refusing to rewrite an already-addressed scoped target during a focused retry pass.',
1076
+ `Requested path: ${relativePath}`,
1077
+ `Current retry target(s): ${[...focusedRetrySlugs].join(', ')}.`,
1078
+ `Path appears to belong to: ${matchedScopedSlugs.join(', ')}.`,
1079
+ 'Continue from the current repo state and edit the focused target page(s) named by the retry instruction, then verify every requested target page locally.',
1080
+ ].join(' '));
1081
+ }
1082
+ }
1051
1083
  const collectionViolation = this._directNewHtmlPageCollectionViolation(
1052
1084
  filePath,
1053
1085
  workDir,
package/src/worker.js CHANGED
@@ -1732,6 +1732,23 @@ export class AgentForgeWorker extends EventEmitter {
1732
1732
  return warnings.length > 0 ? this._formatIncompleteScopedUiTargetsNudge(warnings) : '';
1733
1733
  }
1734
1734
 
1735
+ _formatIncompleteScopedUiRepairLead(repoBaselines, userMessage) {
1736
+ const warnings = this._findIncompleteScopedUiTargets(repoBaselines, userMessage);
1737
+ if (warnings.length === 0) return `The task is: "${userMessage}"`;
1738
+ const { slugs: allSlugs } = this._extractExplicitScope(userMessage);
1739
+ const missingSlugs = [...new Set(warnings.flatMap(w => w.missingSlugs || []))];
1740
+ if (allSlugs.length < 2 || missingSlugs.length === 0) return `The task is: "${userMessage}"`;
1741
+ const alreadyTouchedSlugs = allSlugs.filter(slug => !missingSlugs.includes(slug));
1742
+ return [
1743
+ `Original scoped UI target set: ${allSlugs.join(', ')}.`,
1744
+ `This retry is a missing-target pass for target page(s) not yet changed in the current diff: ${missingSlugs.join(', ')}.`,
1745
+ alreadyTouchedSlugs.length > 0
1746
+ ? `Do not delete, rewrite, or restart target page(s) already changed in this diff just to repeat earlier work: ${alreadyTouchedSlugs.join(', ')}.`
1747
+ : '',
1748
+ 'Continue from the current repo state, edit the missing target page(s) directly, then verify every requested target page locally before completion or delivery.',
1749
+ ].filter(Boolean).join('\n');
1750
+ }
1751
+
1735
1752
  _formatScopedUiTargetSetReminder(userMessage) {
1736
1753
  if (!this._isBroadUiQualityTask(userMessage)) return '';
1737
1754
  const { slugs: allowedSlugs, pageOnly } = this._extractExplicitScope(userMessage);
@@ -4321,7 +4338,8 @@ export class AgentForgeWorker extends EventEmitter {
4321
4338
  nudgeCount++;
4322
4339
  if (nudgeCount < MAX_NUDGES) {
4323
4340
  console.log(`[${taskId}] Iteration missed scoped UI target(s) — continuing (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${uiRepairNudgeCount}/${UI_REPAIR_NUDGE_LIMIT})`);
4324
- 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.`);
4341
+ const incompleteScopedRepairLead = this._formatIncompleteScopedUiRepairLead(repoBaselines, scopeAwareUserMessage);
4342
+ iterationMessage = withTaskContext(`${incompleteScopedRepairLead}\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.`);
4325
4343
  continue;
4326
4344
  }
4327
4345
  throw new Error('Task missed scoped UI target pages after repeated retries');
@@ -4490,7 +4508,8 @@ export class AgentForgeWorker extends EventEmitter {
4490
4508
  nudgeCount++;
4491
4509
  if (nudgeCount < MAX_NUDGES) {
4492
4510
  console.log(`[${taskId}] UI completion missed scoped target(s) — nudging (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${uiRepairNudgeCount}/${UI_REPAIR_NUDGE_LIMIT})`);
4493
- 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.`);
4511
+ const incompleteScopedRepairLead = this._formatIncompleteScopedUiRepairLead(repoBaselines, scopeAwareUserMessage);
4512
+ iterationMessage = withTaskContext(`${incompleteScopedRepairLead}\n\n${incompleteScopedUiTargetsNudge}\n\nDo not mark complete until every named target page has been addressed directly and visually verified.`);
4494
4513
  continue;
4495
4514
  }
4496
4515
  throw new Error('UI task claimed completion while missing scoped target pages after repeated retries');
@@ -4764,7 +4783,8 @@ export class AgentForgeWorker extends EventEmitter {
4764
4783
  nudgeCount++;
4765
4784
  if (nudgeCount < MAX_NUDGES) {
4766
4785
  console.log(`[${taskId}] Publish evidence missed scoped target(s) — continuing (${nudgeCount}/${MAX_NUDGES}, total UI repairs ${uiRepairNudgeCount}/${UI_REPAIR_NUDGE_LIMIT})`);
4767
- 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.`);
4786
+ const incompleteScopedRepairLead = this._formatIncompleteScopedUiRepairLead(repoBaselines, scopeAwareUserMessage);
4787
+ iterationMessage = withTaskContext(`${incompleteScopedRepairLead}\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.`);
4768
4788
  continue;
4769
4789
  }
4770
4790
  throw new Error('Publish task missed scoped target pages after repeated retries');