@hamp10/agentforge 0.2.24 → 0.2.26
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 +54 -8
- package/src/OpenClawCLI.js +20 -7
- package/src/selfUpdate.js +41 -5
package/package.json
CHANGED
|
@@ -648,8 +648,8 @@ try {
|
|
|
648
648
|
));
|
|
649
649
|
assert.match(
|
|
650
650
|
cli._detectScreenshotVisualDiscontinuities(seamPng).join('\n'),
|
|
651
|
-
/
|
|
652
|
-
'screenshot discontinuity scan should catch large
|
|
651
|
+
/screenshot continuity break/i,
|
|
652
|
+
'screenshot discontinuity scan should catch large rectangular visual discontinuities'
|
|
653
653
|
);
|
|
654
654
|
const subtlePartialSurfacePng = path.join(fixture.repo, 'subtle-partial-surface.png');
|
|
655
655
|
writeRgbPng(subtlePartialSurfacePng, 1000, 700, (x, y) => {
|
|
@@ -658,8 +658,8 @@ try {
|
|
|
658
658
|
});
|
|
659
659
|
assert.match(
|
|
660
660
|
cli._detectScreenshotVisualDiscontinuities(subtlePartialSurfacePng).join('\n'),
|
|
661
|
-
/partial-surface
|
|
662
|
-
'screenshot discontinuity scan should catch large subtle partial-surface
|
|
661
|
+
/partial-surface screenshot continuity break/i,
|
|
662
|
+
'screenshot discontinuity scan should catch large subtle partial-surface visual discontinuities'
|
|
663
663
|
);
|
|
664
664
|
} finally {
|
|
665
665
|
rmSync(fixture.repo, { recursive: true, force: true });
|
|
@@ -668,6 +668,7 @@ try {
|
|
|
668
668
|
const openClawSource = readFileSync(new URL('../src/OpenClawCLI.js', import.meta.url), 'utf-8');
|
|
669
669
|
const workerSource = readFileSync(new URL('../src/worker.js', import.meta.url), 'utf-8');
|
|
670
670
|
const workerBinSource = readFileSync(new URL('../bin/agentforge.js', import.meta.url), 'utf-8');
|
|
671
|
+
const selfUpdateSource = readFileSync(new URL('../src/selfUpdate.js', import.meta.url), 'utf-8');
|
|
671
672
|
const defaultGuidesSource = readFileSync(new URL('../src/default-task-guides.js', import.meta.url), 'utf-8');
|
|
672
673
|
const browserSource = readFileSync(new URL('../src/browser.js', import.meta.url), 'utf-8');
|
|
673
674
|
const previewServerSource = readFileSync(new URL('../src/preview-server.js', import.meta.url), 'utf-8');
|
|
@@ -690,13 +691,13 @@ assert.match(
|
|
|
690
691
|
);
|
|
691
692
|
assert.match(
|
|
692
693
|
openClawSource,
|
|
693
|
-
/
|
|
694
|
-
'screenshot discontinuity detection should fail large
|
|
694
|
+
/visible screenshot continuity break across a dominant UI surface/i,
|
|
695
|
+
'screenshot discontinuity detection should fail large visual continuity breaks'
|
|
695
696
|
);
|
|
696
697
|
assert.match(
|
|
697
698
|
openClawSource,
|
|
698
|
-
/partial-surface
|
|
699
|
-
'screenshot discontinuity detection should fail subtle partial-surface
|
|
699
|
+
/partial-surface screenshot continuity break/i,
|
|
700
|
+
'screenshot discontinuity detection should fail subtle partial-surface visual continuity breaks'
|
|
700
701
|
);
|
|
701
702
|
assert.match(
|
|
702
703
|
openClawSource,
|
|
@@ -838,16 +839,41 @@ assert.match(
|
|
|
838
839
|
/app\.delete\('\/api\/agents\/delete\/cleanup-empty'[\s\S]*activeTasks[\s\S]*type: 'agents_pruned'/i,
|
|
839
840
|
'empty-agent cleanup should preserve live tasks and broadcast removed agents'
|
|
840
841
|
);
|
|
842
|
+
assert.match(
|
|
843
|
+
serverSource,
|
|
844
|
+
/CREATE TABLE IF NOT EXISTS agent_flows[\s\S]*config JSONB DEFAULT NULL/i,
|
|
845
|
+
'agent flow emergency schema patch should include config so model routing survives skipped migrations'
|
|
846
|
+
);
|
|
847
|
+
assert.match(
|
|
848
|
+
serverSource,
|
|
849
|
+
/ALTER TABLE agent_flows ADD COLUMN IF NOT EXISTS config JSONB DEFAULT NULL/i,
|
|
850
|
+
'agent flow startup should self-heal missing config column'
|
|
851
|
+
);
|
|
852
|
+
assert.match(
|
|
853
|
+
serverSource,
|
|
854
|
+
/ON CONFLICT \(id\)[\s\S]*WHERE agent_flows\.user_id = EXCLUDED\.user_id[\s\S]*RETURNING id/i,
|
|
855
|
+
'flow upserts should not update another user flow with the same id'
|
|
856
|
+
);
|
|
841
857
|
assert.match(
|
|
842
858
|
dashboardSource,
|
|
843
859
|
/confirmDeleteAgent[\s\S]*await fetch[\s\S]*removeAgentFromLocalState/i,
|
|
844
860
|
'single-agent delete should only remove local UI after the server confirms deletion'
|
|
845
861
|
);
|
|
862
|
+
assert.match(
|
|
863
|
+
dashboardSource,
|
|
864
|
+
/confirmPruneAgents[\s\S]*\/api\/agents\/delete\/prune\?keep=15[\s\S]*removeAgentFromLocalState/i,
|
|
865
|
+
'dashboard should expose old-agent pruning through the safe keep-latest endpoint'
|
|
866
|
+
);
|
|
846
867
|
assert.match(
|
|
847
868
|
dashboardSource,
|
|
848
869
|
/function finalizeLiveFeedBeforeGuide[\s\S]*commitChunkBuffer[\s\S]*hideTypingIndicator/i,
|
|
849
870
|
'live Guide messages should close the current visible agent feed before rendering the user bubble'
|
|
850
871
|
);
|
|
872
|
+
assert.match(
|
|
873
|
+
selfUpdateSource,
|
|
874
|
+
/ensureWritableGlobalInstallEnv[\s\S]*canWriteNpmRoot[\s\S]*configureUserOwnedNpmGlobalEnv/i,
|
|
875
|
+
'self-update should preflight npm global permissions before printing a failed system-prefix install'
|
|
876
|
+
);
|
|
851
877
|
assert.match(
|
|
852
878
|
dashboardSource,
|
|
853
879
|
/async function sendGuideMessage[\s\S]*agent\._guideInflight[\s\S]*finalizeLiveFeedBeforeGuide\(agentId\)[\s\S]*addMessage/i,
|
|
@@ -953,11 +979,31 @@ assert.doesNotMatch(
|
|
|
953
979
|
/taskRequiresVisualVerification\s*&&\s*directIteration\s*<=\s*1\s*&&\s*explicitScopeForTask\.pageOnly/s,
|
|
954
980
|
'comparable UI context gate should apply on retries, not only first iteration'
|
|
955
981
|
);
|
|
982
|
+
assert.match(
|
|
983
|
+
openClawSource,
|
|
984
|
+
/read-only project context, not as a target page/,
|
|
985
|
+
'read-only comparable page inspection should not be narrated as target-page work'
|
|
986
|
+
);
|
|
987
|
+
assert.match(
|
|
988
|
+
openClawSource,
|
|
989
|
+
/verifiedLocalUrl = isLocalUiUrl\(resultUrl \|\| url\)/,
|
|
990
|
+
'scoped auto-verification should count a clean inferred local target URL even when browser output formatting is imperfect'
|
|
991
|
+
);
|
|
956
992
|
assert.match(
|
|
957
993
|
openClawSource,
|
|
958
994
|
/const minRatio = isLargeText \? 3 : 4\.5;/,
|
|
959
995
|
'control/link contrast checks should use text-size-aware WCAG thresholds'
|
|
960
996
|
);
|
|
997
|
+
assert.match(
|
|
998
|
+
dashboardSource,
|
|
999
|
+
/typingEl\.dataset\.startedAt\s*=\s*startedAt/,
|
|
1000
|
+
'live guide insertion should preserve the active feed start time for chronological reload ordering'
|
|
1001
|
+
);
|
|
1002
|
+
assert.match(
|
|
1003
|
+
dashboardSource,
|
|
1004
|
+
/time:\s*startedAt/,
|
|
1005
|
+
'completed live feeds should save with their start time so guide bubbles appear after the feed they interrupted'
|
|
1006
|
+
);
|
|
961
1007
|
assert.match(
|
|
962
1008
|
defaultGuidesSource,
|
|
963
1009
|
/anything that reads as rendering damage rather than deliberate design/i,
|
package/src/OpenClawCLI.js
CHANGED
|
@@ -3128,7 +3128,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
3128
3128
|
horizontal.y < height * 0.94;
|
|
3129
3129
|
if (!awayFromViewportEdge) continue;
|
|
3130
3130
|
warnings.push([
|
|
3131
|
-
`
|
|
3131
|
+
`visible screenshot continuity break across a dominant UI surface near x=${vertical.x}, y=${horizontal.y}`,
|
|
3132
3132
|
`(vertical span y=${vertical.minY}-${vertical.maxY}, horizontal span x=${horizontal.minX}-${horizontal.maxX})`,
|
|
3133
3133
|
].join(' '));
|
|
3134
3134
|
break;
|
|
@@ -3144,7 +3144,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
3144
3144
|
edge.average >= 38
|
|
3145
3145
|
);
|
|
3146
3146
|
if (dominantVertical) {
|
|
3147
|
-
warnings.push(`
|
|
3147
|
+
warnings.push(`visible vertical screenshot continuity break across a dominant UI surface near x=${dominantVertical.x} spanning y=${dominantVertical.minY}-${dominantVertical.maxY}`);
|
|
3148
3148
|
}
|
|
3149
3149
|
}
|
|
3150
3150
|
|
|
@@ -3197,7 +3197,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
3197
3197
|
}
|
|
3198
3198
|
|
|
3199
3199
|
if (partialHorizontal) {
|
|
3200
|
-
warnings.push(`
|
|
3200
|
+
warnings.push(`visible partial-surface screenshot continuity break near y=${partialHorizontal.y}, running from x=${partialHorizontal.start} to x=${partialHorizontal.end}`);
|
|
3201
3201
|
}
|
|
3202
3202
|
}
|
|
3203
3203
|
|
|
@@ -3249,7 +3249,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
3249
3249
|
}
|
|
3250
3250
|
|
|
3251
3251
|
if (partialVertical) {
|
|
3252
|
-
warnings.push(`
|
|
3252
|
+
warnings.push(`visible partial-surface screenshot continuity break near x=${partialVertical.x}, running from y=${partialVertical.start} to y=${partialVertical.end}`);
|
|
3253
3253
|
}
|
|
3254
3254
|
}
|
|
3255
3255
|
return warnings;
|
|
@@ -3850,7 +3850,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
3850
3850
|
? `${visualWarningPrefix}: ${summary.brokenImages.length} visible image(s) failed to load: ${summary.brokenImages.map(img => img.alt || img.src || '(unlabeled image)').join(', ')}. ${visualWarningTail}`
|
|
3851
3851
|
: '';
|
|
3852
3852
|
const screenshotDiscontinuityWarning = screenshotDiscontinuityWarnings.length > 0
|
|
3853
|
-
? `${visualWarningPrefix}: screenshot
|
|
3853
|
+
? `${visualWarningPrefix}: screenshot pixel scan flagged visible continuity issue(s): ${screenshotDiscontinuityWarnings.slice(0, 2).join('; ')}. Inspect the screenshot and source to identify the actual rendering cause, then rework the affected layout/background/layering so the changed screen reads as intentional. Verify the page again. ${visualWarningTail}`
|
|
3854
3854
|
: '';
|
|
3855
3855
|
const reportLowContrastControls = options?.afterMutation && isScopedBrowserTarget && explicitScopeSlugs.length > 0
|
|
3856
3856
|
? (summary.lowContrastControls || []).filter(c => !c.chrome)
|
|
@@ -4629,8 +4629,10 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4629
4629
|
recordDirectToolSummary('browser', result);
|
|
4630
4630
|
continue;
|
|
4631
4631
|
}
|
|
4632
|
-
|
|
4633
|
-
|
|
4632
|
+
const resultUrl = browserResultUrl(result);
|
|
4633
|
+
rememberLocalScopedUrl(resultUrl);
|
|
4634
|
+
const verifiedLocalUrl = isLocalUiUrl(resultUrl || url);
|
|
4635
|
+
if (isLocalUiVerificationResult(result) || verifiedLocalUrl) {
|
|
4634
4636
|
recordCleanLocalVerification(result, [slug]);
|
|
4635
4637
|
verifiedAny = true;
|
|
4636
4638
|
}
|
|
@@ -4766,6 +4768,13 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
4766
4768
|
return `I am checking the live browser${url} to judge the actual product surface before deciding the next change.`;
|
|
4767
4769
|
}
|
|
4768
4770
|
if (name === 'read_file' || name === 'read') {
|
|
4771
|
+
const scope = extractExplicitScope(task);
|
|
4772
|
+
const targetText = String(targetPath || base || '').toLowerCase();
|
|
4773
|
+
const looksLikePageSource = /\.(html?|css|s[ac]ss|jsx?|tsx?|vue|svelte|astro|mdx?)$/i.test(targetText);
|
|
4774
|
+
const isNamedTarget = scope.slugs.length > 0 && scopeSlugsMatchingText(targetText, scope.slugs).length > 0;
|
|
4775
|
+
if (base && looksLikePageSource && scope.slugs.length > 0 && !isNamedTarget) {
|
|
4776
|
+
return `I am reading ${base} as read-only project context, not as a target page.`;
|
|
4777
|
+
}
|
|
4769
4778
|
return base
|
|
4770
4779
|
? `I am reading ${base} to work from the current implementation instead of guessing.`
|
|
4771
4780
|
: `I am reading the current code before making the next change.`;
|
|
@@ -5097,6 +5106,10 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
5097
5106
|
lastDirectVisualWarning = visualWarning;
|
|
5098
5107
|
} else if (!taskRequiresVisualVerification || isLocalUiVerificationResult(result)) {
|
|
5099
5108
|
recordCleanLocalVerification(result);
|
|
5109
|
+
} else {
|
|
5110
|
+
const verifiedUrl = resultUrl || args?.url || '';
|
|
5111
|
+
const verifiedSlugs = isLocalUiUrl(verifiedUrl) ? scopeSlugsInText(verifiedUrl) : [];
|
|
5112
|
+
if (verifiedSlugs.length > 0) recordCleanLocalVerification(result, verifiedSlugs);
|
|
5100
5113
|
}
|
|
5101
5114
|
}
|
|
5102
5115
|
}
|
package/src/selfUpdate.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* prevent infinite re-exec loops).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { execSync, spawn } from 'child_process';
|
|
10
|
-
import { mkdirSync } from 'fs';
|
|
9
|
+
import { execFileSync, execSync, spawn } from 'child_process';
|
|
10
|
+
import { mkdirSync, rmSync } from 'fs';
|
|
11
11
|
import { homedir } from 'os';
|
|
12
12
|
import path from 'path';
|
|
13
13
|
|
|
@@ -75,22 +75,58 @@ export async function checkAndUpdate(packageName, currentVersion) {
|
|
|
75
75
|
|
|
76
76
|
function installGlobalPackage(pkg, { force = false } = {}) {
|
|
77
77
|
const forceFlag = force ? ' --force' : '';
|
|
78
|
+
const preflightEnv = ensureWritableGlobalInstallEnv();
|
|
78
79
|
try {
|
|
79
|
-
execSync(`npm install -g${forceFlag} ${pkg}`, { stdio: 'inherit' });
|
|
80
|
+
execSync(`npm install -g${forceFlag} ${pkg}`, { stdio: 'inherit', env: preflightEnv });
|
|
80
81
|
return;
|
|
81
82
|
} catch (error) {
|
|
82
83
|
if (!looksLikePermissionFailure(error)) throw error;
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
const env = configureUserOwnedNpmGlobalEnv();
|
|
87
|
+
execSync(`npm install -g${forceFlag} ${pkg}`, { stdio: 'inherit', env });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function npmConfigGet(args) {
|
|
91
|
+
try {
|
|
92
|
+
return execFileSync('npm', args, {
|
|
93
|
+
encoding: 'utf8',
|
|
94
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
95
|
+
}).trim();
|
|
96
|
+
} catch {
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function canWriteNpmRoot(rootDir) {
|
|
102
|
+
if (!rootDir) return false;
|
|
103
|
+
const testDir = path.join(rootDir, `.agentforge-write-test-${process.pid}`);
|
|
104
|
+
try {
|
|
105
|
+
mkdirSync(testDir, { recursive: true });
|
|
106
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
107
|
+
return true;
|
|
108
|
+
} catch {
|
|
109
|
+
try { rmSync(testDir, { recursive: true, force: true }); } catch {}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function ensureWritableGlobalInstallEnv() {
|
|
115
|
+
const root = npmConfigGet(['root', '-g']);
|
|
116
|
+
if (canWriteNpmRoot(root)) return process.env;
|
|
117
|
+
return configureUserOwnedNpmGlobalEnv();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function configureUserOwnedNpmGlobalEnv() {
|
|
85
121
|
const prefix = path.join(homedir(), '.npm-global');
|
|
86
122
|
mkdirSync(path.join(prefix, 'bin'), { recursive: true });
|
|
87
123
|
mkdirSync(path.join(prefix, 'lib', 'node_modules'), { recursive: true });
|
|
88
|
-
|
|
124
|
+
execFileSync('npm', ['config', 'set', 'prefix', prefix], { stdio: ['ignore', 'ignore', 'inherit'] });
|
|
89
125
|
const env = {
|
|
90
126
|
...process.env,
|
|
91
127
|
PATH: `${path.join(prefix, 'bin')}${path.delimiter}${process.env.PATH || ''}`,
|
|
92
128
|
};
|
|
93
|
-
|
|
129
|
+
return env;
|
|
94
130
|
}
|
|
95
131
|
|
|
96
132
|
function looksLikePermissionFailure(error) {
|