@hamp10/agentforge 0.2.29 → 0.2.31
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 +31 -0
- package/src/OpenClawCLI.js +23 -18
- package/src/worker.js +2 -1
package/package.json
CHANGED
|
@@ -555,6 +555,22 @@ try {
|
|
|
555
555
|
/existing scoped page target/i,
|
|
556
556
|
'scoped page work should not create a new same-slug page when an existing scoped target page is present'
|
|
557
557
|
);
|
|
558
|
+
const nestedDuplicateGamma = path.join(fixture.repo, path.basename(fixture.repo), 'public_html', 'domains', 'gamma.html');
|
|
559
|
+
mkdirSync(path.dirname(nestedDuplicateGamma), { recursive: true });
|
|
560
|
+
writeFileSync(nestedDuplicateGamma, '<!doctype html><html><body><h1>Gamma</h1></body></html>');
|
|
561
|
+
assert.doesNotThrow(
|
|
562
|
+
() => cli._guardDirectFileWritePath(
|
|
563
|
+
path.join(fixture.repo, 'public_html', 'domains', 'gamma.html'),
|
|
564
|
+
fixture.repo,
|
|
565
|
+
{ task: 'Can you make listing pages for Gamma?' }
|
|
566
|
+
),
|
|
567
|
+
'nested duplicate project copies should not block recreating the canonical page path'
|
|
568
|
+
);
|
|
569
|
+
assert.equal(
|
|
570
|
+
cli._directScopeFileCandidates(fixture.repo, ['gamma']).some(candidate => candidate.startsWith(`${path.basename(fixture.repo)}/`)),
|
|
571
|
+
false,
|
|
572
|
+
'scope candidates should not steer agents toward nested project-copy paths when the canonical parent exists'
|
|
573
|
+
);
|
|
558
574
|
const extractedOldCss = Array.from({ length: 28 }, (_, i) =>
|
|
559
575
|
`.hero-polish-${i} { color: #${String(100000 + i).slice(0, 6)}; padding: ${i + 1}px; margin: ${i}px; box-shadow: 0 0 ${i + 2}px rgba(0,0,0,.1); }`
|
|
560
576
|
).join('\n');
|
|
@@ -949,6 +965,21 @@ assert.match(
|
|
|
949
965
|
/taskId,\n\s*iteration,/i,
|
|
950
966
|
'direct fast path should receive taskId so UI peer context is task-scoped'
|
|
951
967
|
);
|
|
968
|
+
assert.match(
|
|
969
|
+
openClawSource,
|
|
970
|
+
/const semanticTask = String\(directOptions\.rawUserTask \|\| task \|\| ''\);[\s\S]*_guardDirectFileWritePath\(fileWriteTarget, workDir, \{ task: semanticTask \}\)/i,
|
|
971
|
+
'direct scope guards should use the raw user task, not expanded retry/platform context'
|
|
972
|
+
);
|
|
973
|
+
assert.match(
|
|
974
|
+
openClawSource,
|
|
975
|
+
/rawUserTask: rawUserTask \|\| task/i,
|
|
976
|
+
'direct fast path should pass the raw task separately from the expanded runtime prompt'
|
|
977
|
+
);
|
|
978
|
+
assert.match(
|
|
979
|
+
workerSource,
|
|
980
|
+
/imageGenerationModel \|\| null,\n\s*scopeAwareUserMessage/i,
|
|
981
|
+
'worker should pass the resolved raw user scope into direct agent execution'
|
|
982
|
+
);
|
|
952
983
|
assert.match(
|
|
953
984
|
openClawSource,
|
|
954
985
|
/persistDirectUiContext\(\);/i,
|
package/src/OpenClawCLI.js
CHANGED
|
@@ -694,7 +694,10 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
694
694
|
walk(workDir, 0);
|
|
695
695
|
const resultSet = new Set(results);
|
|
696
696
|
return results.filter(rel => {
|
|
697
|
-
|
|
697
|
+
const absolute = path.resolve(workDir, rel);
|
|
698
|
+
const nestedCanonical = this._nestedProjectCopyCanonicalPath(absolute, workDir);
|
|
699
|
+
if (nestedCanonical && existsSync(path.dirname(nestedCanonical))) return false;
|
|
700
|
+
const canonical = this._nestedProjectDuplicateCanonicalPath(absolute, workDir);
|
|
698
701
|
if (!canonical) return true;
|
|
699
702
|
const canonicalRel = path.relative(workDir, canonical) || canonical;
|
|
700
703
|
return !resultSet.has(canonicalRel);
|
|
@@ -4352,9 +4355,10 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4352
4355
|
'For local browser verification, use the URL or port proven by project code, server output, or an already successful browser result. Do not try arbitrary localhost ports such as 3000 or 5000 just because they are common defaults.',
|
|
4353
4356
|
'For git-backed projects with a remote, do not finish with intended changes only on disk. Stage the final intended changes, commit them, push to the configured upstream, and verify the resulting state unless the user explicitly asked not to. Do not create confirmation/status files just to prove the task is done.',
|
|
4354
4357
|
].join('\n');
|
|
4355
|
-
const
|
|
4356
|
-
const
|
|
4357
|
-
const
|
|
4358
|
+
const semanticTask = String(directOptions.rawUserTask || task || '');
|
|
4359
|
+
const taskRequiresFileChange = /\b(make|create|build|add|implement|update|edit|change|fix|improve|redesign|refactor|write|generate|scaffold|wire|ship|work on|delete|remove|drop|unpublish|decommission|take down|take offline)\b/i.test(semanticTask);
|
|
4360
|
+
const taskAllowsDeletedScopedPages = this._allowsDirectScopedPageSourcesToRemainDeleted(semanticTask);
|
|
4361
|
+
const taskRequiresVisualVerification = !taskAllowsDeletedScopedPages && /\b(ui|ux|visual|design|frontend|css|html|pages?|dashboard|website|web app|app screen|layout|component|responsive|professional|polish(?:ed)?|vibecoded|vibe-coded|interface|screen|arena|competitive|competition|scoreboard|ranking|visual hierarchy|user experience|design system|readability|readable|legibility|contrast|accessibility)\b/i.test(semanticTask);
|
|
4358
4362
|
const directRequestTimeoutMs = Math.max(
|
|
4359
4363
|
30_000,
|
|
4360
4364
|
Number(process.env.AGENTFORGE_DIRECT_LLM_REQUEST_TIMEOUT_MS || (taskRequiresVisualVerification ? 120_000 : 90_000))
|
|
@@ -4370,7 +4374,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4370
4374
|
let directMutationCount = 0;
|
|
4371
4375
|
let lastCleanLocalVerificationMutationCount = 0;
|
|
4372
4376
|
let lastDirectVisualWarning = '';
|
|
4373
|
-
const explicitScopeForTask = this._extractDirectExplicitScope(
|
|
4377
|
+
const explicitScopeForTask = this._extractDirectExplicitScope(semanticTask);
|
|
4374
4378
|
const explicitScopeSlugsForTask = explicitScopeForTask.slugs || [];
|
|
4375
4379
|
const taskRequiresComparableUiContext =
|
|
4376
4380
|
taskRequiresVisualVerification &&
|
|
@@ -4815,7 +4819,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4815
4819
|
});
|
|
4816
4820
|
const result = await this._runDirectTool(agentId, 'browser', { action: 'open', url }, workDir, {
|
|
4817
4821
|
signal: directAbortController.signal,
|
|
4818
|
-
task,
|
|
4822
|
+
task: semanticTask,
|
|
4819
4823
|
imageModel: directOptions.imageModel,
|
|
4820
4824
|
agentModel: directOptions.agentModel || model,
|
|
4821
4825
|
fallbackVisionModels: directOptions.fallbackVisionModels,
|
|
@@ -4856,7 +4860,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4856
4860
|
const match = text.match(/^URL:\s*(.+)$/mi);
|
|
4857
4861
|
const urlText = match?.[1]?.trim() || '';
|
|
4858
4862
|
if (!urlText) return false;
|
|
4859
|
-
const { slugs } = this._extractDirectExplicitScope(
|
|
4863
|
+
const { slugs } = this._extractDirectExplicitScope(semanticTask);
|
|
4860
4864
|
if (slugs.length > 0 && scopeSlugsMatchingText(urlText, slugs).length === 0) return false;
|
|
4861
4865
|
return /^(?:https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?::\d+)?(?:\/|$)|file:)/i.test(urlText);
|
|
4862
4866
|
};
|
|
@@ -4868,7 +4872,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4868
4872
|
return warnings.join('\n').slice(0, 1200);
|
|
4869
4873
|
};
|
|
4870
4874
|
const directStopResponse = (reason, forceIncomplete = false) => {
|
|
4871
|
-
const pendingGitDelivery = this._directGitPendingDeliveryMessage(workDir,
|
|
4875
|
+
const pendingGitDelivery = this._directGitPendingDeliveryMessage(workDir, semanticTask, { directMutationCount, taskRequiresFileChange });
|
|
4872
4876
|
const noDeliverable = taskRequiresFileChange && directMutationCount === 0 && !pendingGitDelivery;
|
|
4873
4877
|
const missingScopedMutation = missingScopedMutationSlugs();
|
|
4874
4878
|
const missingVisualVerification = taskRequiresVisualVerification && directMutationCount > 0 && !hasCleanLocalVerificationForLatestMutation();
|
|
@@ -4955,7 +4959,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4955
4959
|
usedTools: directToolCount > 0,
|
|
4956
4960
|
};
|
|
4957
4961
|
}
|
|
4958
|
-
const delivery = this._directMaybeAutoDeliverGitChanges(workDir,
|
|
4962
|
+
const delivery = this._directMaybeAutoDeliverGitChanges(workDir, semanticTask, { directMutationCount, taskRequiresFileChange });
|
|
4959
4963
|
if (delivery.delivered) {
|
|
4960
4964
|
recordDirectToolSummary('git', delivery.message);
|
|
4961
4965
|
} else if (delivery.pending) {
|
|
@@ -4990,7 +4994,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4990
4994
|
return `I am checking the live browser${url} to judge the actual product surface before deciding the next change.`;
|
|
4991
4995
|
}
|
|
4992
4996
|
if (name === 'read_file' || name === 'read') {
|
|
4993
|
-
const scope = extractExplicitScope(
|
|
4997
|
+
const scope = extractExplicitScope(semanticTask);
|
|
4994
4998
|
const targetText = String(targetPath || base || '').toLowerCase();
|
|
4995
4999
|
const looksLikePageSource = /\.(html?|css|s[ac]ss|jsx?|tsx?|vue|svelte|astro|mdx?)$/i.test(targetText);
|
|
4996
5000
|
const isNamedTarget = scope.slugs.length > 0 && scopeSlugsMatchingText(targetText, scope.slugs).length > 0;
|
|
@@ -5073,7 +5077,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
5073
5077
|
const text = parts.filter(p => p.text).map(p => p.text).join('');
|
|
5074
5078
|
if (!functionCalls.length) {
|
|
5075
5079
|
if (step > 0) {
|
|
5076
|
-
const pendingGitDelivery = this._directGitPendingDeliveryMessage(workDir,
|
|
5080
|
+
const pendingGitDelivery = this._directGitPendingDeliveryMessage(workDir, semanticTask, { directMutationCount, taskRequiresFileChange });
|
|
5077
5081
|
if (taskRequiresFileChange && directMutationCount === 0 && !pendingGitDelivery && directInternalNudgeCount < 2) {
|
|
5078
5082
|
directInternalNudgeCount += 1;
|
|
5079
5083
|
contents.push(content);
|
|
@@ -5215,7 +5219,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
5215
5219
|
const fileWriteTarget = directFileWriteTargetPath(name, args);
|
|
5216
5220
|
if (fileWriteTarget) {
|
|
5217
5221
|
try {
|
|
5218
|
-
this._guardDirectFileWritePath(fileWriteTarget, workDir, { task });
|
|
5222
|
+
this._guardDirectFileWritePath(fileWriteTarget, workDir, { task: semanticTask });
|
|
5219
5223
|
} catch (err) {
|
|
5220
5224
|
if (err.code === 'AGENTFORGE_SCOPE_VIOLATION') {
|
|
5221
5225
|
directScopeViolationCount += 1;
|
|
@@ -5270,7 +5274,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
5270
5274
|
responses.push({ functionResponse: { name, response: { error: message } } });
|
|
5271
5275
|
continue;
|
|
5272
5276
|
}
|
|
5273
|
-
const dirtyDeliveryMessage = this._directGitDirtyDeliveryMessage(workDir,
|
|
5277
|
+
const dirtyDeliveryMessage = this._directGitDirtyDeliveryMessage(workDir, semanticTask, command);
|
|
5274
5278
|
if (/\bgit\b[\s\S]*\b(?:commit|push)\b/i.test(command) && dirtyDeliveryMessage) {
|
|
5275
5279
|
emitDirectThought('AgentForge blocked commit/push because the staged delivery does not match the final working tree.');
|
|
5276
5280
|
this.emit('tool_activity', {
|
|
@@ -5295,7 +5299,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
5295
5299
|
try {
|
|
5296
5300
|
const result = await this._runDirectTool(agentId, name, args, workDir, {
|
|
5297
5301
|
signal: directAbortController.signal,
|
|
5298
|
-
task,
|
|
5302
|
+
task: semanticTask,
|
|
5299
5303
|
imageModel: directOptions.imageModel,
|
|
5300
5304
|
agentModel: directOptions.agentModel || model,
|
|
5301
5305
|
fallbackVisionModels: directOptions.fallbackVisionModels,
|
|
@@ -5701,7 +5705,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
5701
5705
|
* Run an agent task
|
|
5702
5706
|
* Images are saved to workspace and referenced in message for vision model analysis
|
|
5703
5707
|
*/
|
|
5704
|
-
async runAgentTask(agentId, task, workDir, sessionId = null, image = null, browserProfile = null, imageWorkDir = null, agentModel = null, customSystemPrompt = null, conversationHistory = null, allImages = null, providerKeys = null, imageModel = null, taskId = null, iteration = 1, fallbackVisionModels = null, imageGenerationModel = null) {
|
|
5708
|
+
async runAgentTask(agentId, task, workDir, sessionId = null, image = null, browserProfile = null, imageWorkDir = null, agentModel = null, customSystemPrompt = null, conversationHistory = null, allImages = null, providerKeys = null, imageModel = null, taskId = null, iteration = 1, fallbackVisionModels = null, imageGenerationModel = null, rawUserTask = null) {
|
|
5705
5709
|
// Gateway auth comes from per-agent auth-profiles.json, not subprocess env.
|
|
5706
5710
|
// Seed real Anthropic API keys up front so anthropic/* models do not fall back
|
|
5707
5711
|
// to Claude account/OAuth tokens and fail with claude.ai "extra usage" limits.
|
|
@@ -5840,10 +5844,10 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
5840
5844
|
// OpenClaw subprocess after a model compatibility fallback.
|
|
5841
5845
|
// If LLM answers with plain text → done in ~1-3s.
|
|
5842
5846
|
// If LLM returns tool calls → fall through to gateway/subprocess as usual.
|
|
5843
|
-
const
|
|
5844
|
-
const directTaskRequiresFileChange = /\b(make|create|build|add|implement|update|edit|change|fix|improve|redesign|refactor|write|generate|scaffold|wire|ship|work on)\b/i.test(
|
|
5847
|
+
const directSemanticTaskText = String(rawUserTask || task || taskForRuntime || '');
|
|
5848
|
+
const directTaskRequiresFileChange = /\b(make|create|build|add|implement|update|edit|change|fix|improve|redesign|refactor|write|generate|scaffold|wire|ship|work on)\b/i.test(directSemanticTaskText);
|
|
5845
5849
|
const directTaskRequiresVisualImplementation = directTaskRequiresFileChange &&
|
|
5846
|
-
/\b(ui|ux|visual|design|frontend|css|html|pages?|dashboard|website|web app|app screen|layout|component|responsive|professional|polish(?:ed)?|vibecoded|vibe-coded|interface|screen|visual hierarchy|user experience|design system)\b/i.test(
|
|
5850
|
+
/\b(ui|ux|visual|design|frontend|css|html|pages?|dashboard|website|web app|app screen|layout|component|responsive|professional|polish(?:ed)?|vibecoded|vibe-coded|interface|screen|visual hierarchy|user experience|design system)\b/i.test(directSemanticTaskText);
|
|
5847
5851
|
const forceSubprocessForVisual = /^(1|true|yes)$/i.test(process.env.AGENTFORGE_FORCE_OPENCLAW_VISUAL || '');
|
|
5848
5852
|
const skipDirectFastPath = directTaskRequiresVisualImplementation && forceSubprocessForVisual;
|
|
5849
5853
|
if (skipDirectFastPath && agentModel) {
|
|
@@ -5858,6 +5862,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
5858
5862
|
fallbackVisionModels,
|
|
5859
5863
|
taskId,
|
|
5860
5864
|
iteration,
|
|
5865
|
+
rawUserTask: rawUserTask || task,
|
|
5861
5866
|
});
|
|
5862
5867
|
if (fastResult !== null) {
|
|
5863
5868
|
if (fastResult.hasToolCalls) {
|
package/src/worker.js
CHANGED
|
@@ -3768,7 +3768,8 @@ export class AgentForgeWorker extends EventEmitter {
|
|
|
3768
3768
|
providerKeys || null, effectiveImageModel || null,
|
|
3769
3769
|
taskId, iteration,
|
|
3770
3770
|
effectiveFallbackVisionModels,
|
|
3771
|
-
imageGenerationModel || null
|
|
3771
|
+
imageGenerationModel || null,
|
|
3772
|
+
scopeAwareUserMessage
|
|
3772
3773
|
);
|
|
3773
3774
|
runtimeStallRetryCount = 0;
|
|
3774
3775
|
} catch (runError) {
|