@hamp10/agentforge 0.2.34 → 0.2.35
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 +24 -0
- package/src/OpenClawCLI.js +39 -5
package/package.json
CHANGED
|
@@ -604,6 +604,15 @@ try {
|
|
|
604
604
|
const nestedDuplicateGamma = path.join(fixture.repo, path.basename(fixture.repo), 'public_html', 'domains', 'gamma.html');
|
|
605
605
|
mkdirSync(path.dirname(nestedDuplicateGamma), { recursive: true });
|
|
606
606
|
writeFileSync(nestedDuplicateGamma, '<!doctype html><html><body><h1>Gamma</h1></body></html>');
|
|
607
|
+
assert.throws(
|
|
608
|
+
() => cli._guardDirectFileWritePath(
|
|
609
|
+
nestedDuplicateGamma,
|
|
610
|
+
fixture.repo,
|
|
611
|
+
{ task: 'Can you make listing pages for Gamma?' }
|
|
612
|
+
),
|
|
613
|
+
/nested duplicate of the current project/i,
|
|
614
|
+
'same-name nested project-copy paths should be blocked even when the nested copy is tracked or preexisting'
|
|
615
|
+
);
|
|
607
616
|
assert.doesNotThrow(
|
|
608
617
|
() => cli._guardDirectFileWritePath(
|
|
609
618
|
path.join(fixture.repo, 'public_html', 'domains', 'gamma.html'),
|
|
@@ -612,6 +621,21 @@ try {
|
|
|
612
621
|
),
|
|
613
622
|
'nested duplicate project copies should not block recreating the canonical page path'
|
|
614
623
|
);
|
|
624
|
+
const linkedWorkspace = mkdtempSync(path.join(tmpdir(), 'agentforge-linked-project-'));
|
|
625
|
+
try {
|
|
626
|
+
const linkedProjectPath = path.join(linkedWorkspace, path.basename(fixture.repo));
|
|
627
|
+
symlinkSync(fixture.repo, linkedProjectPath, 'dir');
|
|
628
|
+
assert.doesNotThrow(
|
|
629
|
+
() => cli._guardDirectFileWritePath(
|
|
630
|
+
path.join(linkedProjectPath, 'public_html', 'domains', 'gamma.html'),
|
|
631
|
+
fixture.repo,
|
|
632
|
+
{ task: 'Can you make listing pages for Gamma?' }
|
|
633
|
+
),
|
|
634
|
+
'agent-workspace symlink paths should canonicalize to the real project before scoped page guard checks'
|
|
635
|
+
);
|
|
636
|
+
} finally {
|
|
637
|
+
rmSync(linkedWorkspace, { recursive: true, force: true });
|
|
638
|
+
}
|
|
615
639
|
assert.equal(
|
|
616
640
|
cli._directScopeFileCandidates(fixture.repo, ['gamma']).some(candidate => candidate.startsWith(`${path.basename(fixture.repo)}/`)),
|
|
617
641
|
false,
|
package/src/OpenClawCLI.js
CHANGED
|
@@ -381,6 +381,38 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
381
381
|
return rel === '' || (!!rel && !rel.startsWith('..') && !path.isAbsolute(rel));
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
_realPathForMaybeMissingPath(targetPath) {
|
|
385
|
+
const resolved = path.resolve(String(targetPath || ''));
|
|
386
|
+
if (!resolved) return resolved;
|
|
387
|
+
if (existsSync(resolved)) {
|
|
388
|
+
try {
|
|
389
|
+
return realpathSync(resolved);
|
|
390
|
+
} catch {
|
|
391
|
+
return resolved;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const missingParts = [];
|
|
395
|
+
let cursor = resolved;
|
|
396
|
+
while (cursor && !existsSync(cursor)) {
|
|
397
|
+
const parent = path.dirname(cursor);
|
|
398
|
+
if (!parent || parent === cursor) break;
|
|
399
|
+
missingParts.unshift(path.basename(cursor));
|
|
400
|
+
cursor = parent;
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
return path.join(realpathSync(cursor), ...missingParts);
|
|
404
|
+
} catch {
|
|
405
|
+
return resolved;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
_canonicalizeLinkedProjectPath(filePath, workDir) {
|
|
410
|
+
if (!filePath || !workDir) return filePath;
|
|
411
|
+
const realFilePath = this._realPathForMaybeMissingPath(filePath);
|
|
412
|
+
const realWorkDir = this._realPathForMaybeMissingPath(workDir);
|
|
413
|
+
return this._isPathInside(realFilePath, realWorkDir) ? realFilePath : filePath;
|
|
414
|
+
}
|
|
415
|
+
|
|
384
416
|
_projectsRootForPath(candidate) {
|
|
385
417
|
if (!candidate) return null;
|
|
386
418
|
return this._knownProjectsRoots().find(root => existsSync(root) && this._isPathInside(candidate, root)) || null;
|
|
@@ -867,8 +899,8 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
867
899
|
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return false;
|
|
868
900
|
const [firstPart] = relative.split(path.sep).filter(Boolean);
|
|
869
901
|
if (!firstPart || firstPart.toLowerCase() !== path.basename(workDir).toLowerCase()) return false;
|
|
870
|
-
|
|
871
|
-
return
|
|
902
|
+
const canonicalPath = this._nestedProjectCopyCanonicalPath(filePath, workDir);
|
|
903
|
+
return !!canonicalPath && (existsSync(canonicalPath) || existsSync(path.dirname(canonicalPath)));
|
|
872
904
|
}
|
|
873
905
|
|
|
874
906
|
_gitTracksPathOrPrefix(workDir, relativePath, prefix = '') {
|
|
@@ -912,11 +944,11 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
912
944
|
|
|
913
945
|
_nestedProjectDuplicateCanonicalPath(filePath, workDir) {
|
|
914
946
|
const canonicalPath = this._nestedProjectCopyCanonicalPath(filePath, workDir);
|
|
915
|
-
if (!canonicalPath || canonicalPath === filePath
|
|
947
|
+
if (!canonicalPath || canonicalPath === filePath) return null;
|
|
916
948
|
if (!this._isDirectPageSourcePath(filePath) || !this._isDirectPageSourcePath(canonicalPath)) return null;
|
|
917
949
|
const canonicalRel = path.relative(workDir, canonicalPath);
|
|
918
950
|
if (!canonicalRel || canonicalRel.startsWith('..') || path.isAbsolute(canonicalRel)) return null;
|
|
919
|
-
if (!
|
|
951
|
+
if (!existsSync(canonicalPath) && !existsSync(path.dirname(canonicalPath))) return null;
|
|
920
952
|
return canonicalPath;
|
|
921
953
|
}
|
|
922
954
|
|
|
@@ -956,6 +988,8 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
956
988
|
}
|
|
957
989
|
|
|
958
990
|
_guardDirectFileWritePath(filePath, workDir, options = {}) {
|
|
991
|
+
filePath = this._canonicalizeLinkedProjectPath(filePath, workDir);
|
|
992
|
+
workDir = this._realPathForMaybeMissingPath(workDir);
|
|
959
993
|
const { slugs, pageOnly } = this._extractDirectExplicitScope(options.task);
|
|
960
994
|
const relative = path.relative(workDir, filePath);
|
|
961
995
|
const relativePath = (!relative || relative.startsWith('..') || path.isAbsolute(relative))
|
|
@@ -967,7 +1001,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
967
1001
|
: null;
|
|
968
1002
|
if (duplicateCanonicalPath) {
|
|
969
1003
|
throw this._directScopeViolationError([
|
|
970
|
-
'Refusing to write into a nested duplicate of the current project
|
|
1004
|
+
'Refusing to write into a nested duplicate of the current project.',
|
|
971
1005
|
`Workspace: ${workDir}`,
|
|
972
1006
|
`Target: ${filePath}`,
|
|
973
1007
|
`Use the canonical project path instead: ${duplicateCanonicalPath}`,
|