@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 +1 -1
- package/scripts/check-task-semantics.js +65 -0
- package/src/OpenClawCLI.js +32 -0
- package/src/worker.js +23 -3
package/package.json
CHANGED
|
@@ -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
|
'',
|
package/src/OpenClawCLI.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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');
|