@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hamp10/agentforge",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
4
4
  "description": "AgentForge worker — connect your machine to agentforge.ai",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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,
@@ -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
- const canonical = this._nestedProjectDuplicateCanonicalPath(path.resolve(workDir, rel), workDir);
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 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(task);
4356
- const taskAllowsDeletedScopedPages = this._allowsDirectScopedPageSourcesToRemainDeleted(task);
4357
- 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(task);
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(task);
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(task);
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, task, { directMutationCount, taskRequiresFileChange });
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, task, { directMutationCount, taskRequiresFileChange });
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(task);
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, task, { directMutationCount, taskRequiresFileChange });
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, task, command);
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 directTaskText = String(taskForRuntime || '');
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(directTaskText);
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(directTaskText);
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) {