opencode-swarm 6.22.21 → 6.23.1
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/dist/cli/index.js +80 -26
- package/dist/index.js +134 -41
- package/dist/tools/update-task-status.d.ts +11 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -33333,8 +33333,9 @@ function isHighEntropyString(str) {
|
|
|
33333
33333
|
function containsPathTraversal(str) {
|
|
33334
33334
|
if (/\.\.[/\\]/.test(str))
|
|
33335
33335
|
return true;
|
|
33336
|
-
|
|
33337
|
-
|
|
33336
|
+
if (/[/\\]\.\.$/.test(str) || str === "..")
|
|
33337
|
+
return true;
|
|
33338
|
+
if (/\.\.[/\\]/.test(path12.normalize(str.replace(/\*/g, "x"))))
|
|
33338
33339
|
return true;
|
|
33339
33340
|
if (str.includes("%2e%2e") || str.includes("%2E%2E"))
|
|
33340
33341
|
return true;
|
|
@@ -33342,6 +33343,58 @@ function containsPathTraversal(str) {
|
|
|
33342
33343
|
return true;
|
|
33343
33344
|
return false;
|
|
33344
33345
|
}
|
|
33346
|
+
function validateExcludePattern(exc) {
|
|
33347
|
+
if (exc.length === 0)
|
|
33348
|
+
return null;
|
|
33349
|
+
if (exc.length > MAX_FILE_PATH_LENGTH) {
|
|
33350
|
+
return `invalid exclude path: exceeds maximum length of ${MAX_FILE_PATH_LENGTH}`;
|
|
33351
|
+
}
|
|
33352
|
+
if (containsControlChars(exc)) {
|
|
33353
|
+
return "invalid exclude path: contains path traversal or control characters";
|
|
33354
|
+
}
|
|
33355
|
+
if (containsPathTraversal(exc)) {
|
|
33356
|
+
return "invalid exclude path: contains path traversal or control characters";
|
|
33357
|
+
}
|
|
33358
|
+
if (exc.startsWith("!")) {
|
|
33359
|
+
return "invalid exclude path: negation patterns are not supported";
|
|
33360
|
+
}
|
|
33361
|
+
if (exc.startsWith("/") || exc.startsWith("\\")) {
|
|
33362
|
+
return "invalid exclude path: absolute paths are not supported";
|
|
33363
|
+
}
|
|
33364
|
+
return null;
|
|
33365
|
+
}
|
|
33366
|
+
function isGlobOrPathPattern(pattern) {
|
|
33367
|
+
return pattern.includes("/") || pattern.includes("\\") || /[*?[\]{}]/.test(pattern);
|
|
33368
|
+
}
|
|
33369
|
+
function loadSecretScanIgnore(scanDir) {
|
|
33370
|
+
const ignorePath = path12.join(scanDir, ".secretscanignore");
|
|
33371
|
+
try {
|
|
33372
|
+
if (!fs4.existsSync(ignorePath))
|
|
33373
|
+
return [];
|
|
33374
|
+
const content = fs4.readFileSync(ignorePath, "utf8");
|
|
33375
|
+
const patterns = [];
|
|
33376
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
33377
|
+
const line = rawLine.trim();
|
|
33378
|
+
if (!line || line.startsWith("#"))
|
|
33379
|
+
continue;
|
|
33380
|
+
if (validateExcludePattern(line) === null) {
|
|
33381
|
+
patterns.push(line);
|
|
33382
|
+
}
|
|
33383
|
+
}
|
|
33384
|
+
return patterns;
|
|
33385
|
+
} catch {
|
|
33386
|
+
return [];
|
|
33387
|
+
}
|
|
33388
|
+
}
|
|
33389
|
+
function isExcluded(entry, relPath, exactNames, globPatterns) {
|
|
33390
|
+
if (exactNames.has(entry))
|
|
33391
|
+
return true;
|
|
33392
|
+
for (const pattern of globPatterns) {
|
|
33393
|
+
if (path12.matchesGlob(relPath, pattern))
|
|
33394
|
+
return true;
|
|
33395
|
+
}
|
|
33396
|
+
return false;
|
|
33397
|
+
}
|
|
33345
33398
|
function containsControlChars(str) {
|
|
33346
33399
|
return /[\0\r]/.test(str);
|
|
33347
33400
|
}
|
|
@@ -33502,7 +33555,7 @@ function isPathWithinScope(realPath, scanDir) {
|
|
|
33502
33555
|
const resolvedRealPath = path12.resolve(realPath);
|
|
33503
33556
|
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path12.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
|
|
33504
33557
|
}
|
|
33505
|
-
function findScannableFiles(dir,
|
|
33558
|
+
function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, stats = {
|
|
33506
33559
|
skippedDirs: 0,
|
|
33507
33560
|
skippedFiles: 0,
|
|
33508
33561
|
fileErrors: 0,
|
|
@@ -33526,11 +33579,12 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
33526
33579
|
return a.localeCompare(b);
|
|
33527
33580
|
});
|
|
33528
33581
|
for (const entry of entries) {
|
|
33529
|
-
|
|
33582
|
+
const fullPath = path12.join(dir, entry);
|
|
33583
|
+
const relPath = path12.relative(scanDir, fullPath).replace(/\\/g, "/");
|
|
33584
|
+
if (isExcluded(entry, relPath, excludeExact, excludeGlobs)) {
|
|
33530
33585
|
stats.skippedDirs++;
|
|
33531
33586
|
continue;
|
|
33532
33587
|
}
|
|
33533
|
-
const fullPath = path12.join(dir, entry);
|
|
33534
33588
|
let lstat;
|
|
33535
33589
|
try {
|
|
33536
33590
|
lstat = fs4.lstatSync(fullPath);
|
|
@@ -33558,7 +33612,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
33558
33612
|
stats.symlinkSkipped++;
|
|
33559
33613
|
continue;
|
|
33560
33614
|
}
|
|
33561
|
-
const subFiles = findScannableFiles(fullPath,
|
|
33615
|
+
const subFiles = findScannableFiles(fullPath, excludeExact, excludeGlobs, scanDir, visited, stats);
|
|
33562
33616
|
files.push(...subFiles);
|
|
33563
33617
|
} else if (lstat.isFile()) {
|
|
33564
33618
|
const ext = path12.extname(fullPath).toLowerCase();
|
|
@@ -33572,10 +33626,10 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
33572
33626
|
return files;
|
|
33573
33627
|
}
|
|
33574
33628
|
var secretscan = tool({
|
|
33575
|
-
description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default.",
|
|
33629
|
+
description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default. Supports glob patterns (e.g. **/.svelte-kit/**, **/*.test.ts) and reads .secretscanignore at the scan root.",
|
|
33576
33630
|
args: {
|
|
33577
33631
|
directory: tool.schema.string().describe('Directory to scan for secrets (e.g., "." or "./src")'),
|
|
33578
|
-
exclude: tool.schema.array(tool.schema.string()).optional().describe("
|
|
33632
|
+
exclude: tool.schema.array(tool.schema.string()).optional().describe("Patterns to exclude: plain directory names (e.g. node_modules), relative paths, or globs (e.g. **/.svelte-kit/**, **/*.test.ts). Added to default exclusions.")
|
|
33579
33633
|
},
|
|
33580
33634
|
async execute(args, _context) {
|
|
33581
33635
|
let directory;
|
|
@@ -33611,20 +33665,10 @@ var secretscan = tool({
|
|
|
33611
33665
|
}
|
|
33612
33666
|
if (exclude) {
|
|
33613
33667
|
for (const exc of exclude) {
|
|
33614
|
-
|
|
33615
|
-
|
|
33616
|
-
error: `invalid exclude path: exceeds maximum length of ${MAX_FILE_PATH_LENGTH}`,
|
|
33617
|
-
scan_dir: directory,
|
|
33618
|
-
findings: [],
|
|
33619
|
-
count: 0,
|
|
33620
|
-
files_scanned: 0,
|
|
33621
|
-
skipped_files: 0
|
|
33622
|
-
};
|
|
33623
|
-
return JSON.stringify(errorResult, null, 2);
|
|
33624
|
-
}
|
|
33625
|
-
if (containsPathTraversal(exc) || containsControlChars(exc)) {
|
|
33668
|
+
const err = validateExcludePattern(exc);
|
|
33669
|
+
if (err) {
|
|
33626
33670
|
const errorResult = {
|
|
33627
|
-
error:
|
|
33671
|
+
error: err,
|
|
33628
33672
|
scan_dir: directory,
|
|
33629
33673
|
findings: [],
|
|
33630
33674
|
count: 0,
|
|
@@ -33660,10 +33704,20 @@ var secretscan = tool({
|
|
|
33660
33704
|
};
|
|
33661
33705
|
return JSON.stringify(errorResult, null, 2);
|
|
33662
33706
|
}
|
|
33663
|
-
const
|
|
33664
|
-
|
|
33665
|
-
|
|
33666
|
-
|
|
33707
|
+
const excludeExact = new Set(DEFAULT_EXCLUDE_DIRS);
|
|
33708
|
+
const excludeGlobs = [];
|
|
33709
|
+
const ignoreFilePatterns = loadSecretScanIgnore(scanDir);
|
|
33710
|
+
const allUserPatterns = [
|
|
33711
|
+
...exclude ?? [],
|
|
33712
|
+
...ignoreFilePatterns
|
|
33713
|
+
];
|
|
33714
|
+
for (const exc of allUserPatterns) {
|
|
33715
|
+
if (exc.length === 0)
|
|
33716
|
+
continue;
|
|
33717
|
+
if (isGlobOrPathPattern(exc)) {
|
|
33718
|
+
excludeGlobs.push(exc);
|
|
33719
|
+
} else {
|
|
33720
|
+
excludeExact.add(exc);
|
|
33667
33721
|
}
|
|
33668
33722
|
}
|
|
33669
33723
|
const stats = {
|
|
@@ -33673,7 +33727,7 @@ var secretscan = tool({
|
|
|
33673
33727
|
symlinkSkipped: 0
|
|
33674
33728
|
};
|
|
33675
33729
|
const visited = new Set;
|
|
33676
|
-
const files = findScannableFiles(scanDir,
|
|
33730
|
+
const files = findScannableFiles(scanDir, excludeExact, excludeGlobs, scanDir, visited, stats);
|
|
33677
33731
|
files.sort((a, b) => {
|
|
33678
33732
|
const aLower = a.toLowerCase();
|
|
33679
33733
|
const bLower = b.toLowerCase();
|
package/dist/index.js
CHANGED
|
@@ -33574,8 +33574,9 @@ function isHighEntropyString(str) {
|
|
|
33574
33574
|
function containsPathTraversal(str) {
|
|
33575
33575
|
if (/\.\.[/\\]/.test(str))
|
|
33576
33576
|
return true;
|
|
33577
|
-
|
|
33578
|
-
|
|
33577
|
+
if (/[/\\]\.\.$/.test(str) || str === "..")
|
|
33578
|
+
return true;
|
|
33579
|
+
if (/\.\.[/\\]/.test(path21.normalize(str.replace(/\*/g, "x"))))
|
|
33579
33580
|
return true;
|
|
33580
33581
|
if (str.includes("%2e%2e") || str.includes("%2E%2E"))
|
|
33581
33582
|
return true;
|
|
@@ -33583,6 +33584,58 @@ function containsPathTraversal(str) {
|
|
|
33583
33584
|
return true;
|
|
33584
33585
|
return false;
|
|
33585
33586
|
}
|
|
33587
|
+
function validateExcludePattern(exc) {
|
|
33588
|
+
if (exc.length === 0)
|
|
33589
|
+
return null;
|
|
33590
|
+
if (exc.length > MAX_FILE_PATH_LENGTH) {
|
|
33591
|
+
return `invalid exclude path: exceeds maximum length of ${MAX_FILE_PATH_LENGTH}`;
|
|
33592
|
+
}
|
|
33593
|
+
if (containsControlChars(exc)) {
|
|
33594
|
+
return "invalid exclude path: contains path traversal or control characters";
|
|
33595
|
+
}
|
|
33596
|
+
if (containsPathTraversal(exc)) {
|
|
33597
|
+
return "invalid exclude path: contains path traversal or control characters";
|
|
33598
|
+
}
|
|
33599
|
+
if (exc.startsWith("!")) {
|
|
33600
|
+
return "invalid exclude path: negation patterns are not supported";
|
|
33601
|
+
}
|
|
33602
|
+
if (exc.startsWith("/") || exc.startsWith("\\")) {
|
|
33603
|
+
return "invalid exclude path: absolute paths are not supported";
|
|
33604
|
+
}
|
|
33605
|
+
return null;
|
|
33606
|
+
}
|
|
33607
|
+
function isGlobOrPathPattern(pattern) {
|
|
33608
|
+
return pattern.includes("/") || pattern.includes("\\") || /[*?[\]{}]/.test(pattern);
|
|
33609
|
+
}
|
|
33610
|
+
function loadSecretScanIgnore(scanDir) {
|
|
33611
|
+
const ignorePath = path21.join(scanDir, ".secretscanignore");
|
|
33612
|
+
try {
|
|
33613
|
+
if (!fs9.existsSync(ignorePath))
|
|
33614
|
+
return [];
|
|
33615
|
+
const content = fs9.readFileSync(ignorePath, "utf8");
|
|
33616
|
+
const patterns = [];
|
|
33617
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
33618
|
+
const line = rawLine.trim();
|
|
33619
|
+
if (!line || line.startsWith("#"))
|
|
33620
|
+
continue;
|
|
33621
|
+
if (validateExcludePattern(line) === null) {
|
|
33622
|
+
patterns.push(line);
|
|
33623
|
+
}
|
|
33624
|
+
}
|
|
33625
|
+
return patterns;
|
|
33626
|
+
} catch {
|
|
33627
|
+
return [];
|
|
33628
|
+
}
|
|
33629
|
+
}
|
|
33630
|
+
function isExcluded(entry, relPath, exactNames, globPatterns) {
|
|
33631
|
+
if (exactNames.has(entry))
|
|
33632
|
+
return true;
|
|
33633
|
+
for (const pattern of globPatterns) {
|
|
33634
|
+
if (path21.matchesGlob(relPath, pattern))
|
|
33635
|
+
return true;
|
|
33636
|
+
}
|
|
33637
|
+
return false;
|
|
33638
|
+
}
|
|
33586
33639
|
function containsControlChars(str) {
|
|
33587
33640
|
return /[\0\r]/.test(str);
|
|
33588
33641
|
}
|
|
@@ -33742,7 +33795,7 @@ function isPathWithinScope(realPath, scanDir) {
|
|
|
33742
33795
|
const resolvedRealPath = path21.resolve(realPath);
|
|
33743
33796
|
return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path21.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
|
|
33744
33797
|
}
|
|
33745
|
-
function findScannableFiles(dir,
|
|
33798
|
+
function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, stats = {
|
|
33746
33799
|
skippedDirs: 0,
|
|
33747
33800
|
skippedFiles: 0,
|
|
33748
33801
|
fileErrors: 0,
|
|
@@ -33766,11 +33819,12 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
33766
33819
|
return a.localeCompare(b);
|
|
33767
33820
|
});
|
|
33768
33821
|
for (const entry of entries) {
|
|
33769
|
-
|
|
33822
|
+
const fullPath = path21.join(dir, entry);
|
|
33823
|
+
const relPath = path21.relative(scanDir, fullPath).replace(/\\/g, "/");
|
|
33824
|
+
if (isExcluded(entry, relPath, excludeExact, excludeGlobs)) {
|
|
33770
33825
|
stats.skippedDirs++;
|
|
33771
33826
|
continue;
|
|
33772
33827
|
}
|
|
33773
|
-
const fullPath = path21.join(dir, entry);
|
|
33774
33828
|
let lstat;
|
|
33775
33829
|
try {
|
|
33776
33830
|
lstat = fs9.lstatSync(fullPath);
|
|
@@ -33798,7 +33852,7 @@ function findScannableFiles(dir, excludeDirs, scanDir, visited, stats = {
|
|
|
33798
33852
|
stats.symlinkSkipped++;
|
|
33799
33853
|
continue;
|
|
33800
33854
|
}
|
|
33801
|
-
const subFiles = findScannableFiles(fullPath,
|
|
33855
|
+
const subFiles = findScannableFiles(fullPath, excludeExact, excludeGlobs, scanDir, visited, stats);
|
|
33802
33856
|
files.push(...subFiles);
|
|
33803
33857
|
} else if (lstat.isFile()) {
|
|
33804
33858
|
const ext = path21.extname(fullPath).toLowerCase();
|
|
@@ -34003,10 +34057,10 @@ var init_secretscan = __esm(() => {
|
|
|
34003
34057
|
];
|
|
34004
34058
|
O_NOFOLLOW = process.platform !== "win32" ? fs9.constants.O_NOFOLLOW : undefined;
|
|
34005
34059
|
secretscan = tool({
|
|
34006
|
-
description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default.",
|
|
34060
|
+
description: "Scan directory for potential secrets (API keys, tokens, passwords) using regex patterns and entropy heuristics. Returns metadata-only findings with redacted previews - NEVER returns raw secrets. Excludes common directories (node_modules, .git, dist, etc.) by default. Supports glob patterns (e.g. **/.svelte-kit/**, **/*.test.ts) and reads .secretscanignore at the scan root.",
|
|
34007
34061
|
args: {
|
|
34008
34062
|
directory: tool.schema.string().describe('Directory to scan for secrets (e.g., "." or "./src")'),
|
|
34009
|
-
exclude: tool.schema.array(tool.schema.string()).optional().describe("
|
|
34063
|
+
exclude: tool.schema.array(tool.schema.string()).optional().describe("Patterns to exclude: plain directory names (e.g. node_modules), relative paths, or globs (e.g. **/.svelte-kit/**, **/*.test.ts). Added to default exclusions.")
|
|
34010
34064
|
},
|
|
34011
34065
|
async execute(args2, _context) {
|
|
34012
34066
|
let directory;
|
|
@@ -34042,20 +34096,10 @@ var init_secretscan = __esm(() => {
|
|
|
34042
34096
|
}
|
|
34043
34097
|
if (exclude) {
|
|
34044
34098
|
for (const exc of exclude) {
|
|
34045
|
-
|
|
34046
|
-
|
|
34047
|
-
error: `invalid exclude path: exceeds maximum length of ${MAX_FILE_PATH_LENGTH}`,
|
|
34048
|
-
scan_dir: directory,
|
|
34049
|
-
findings: [],
|
|
34050
|
-
count: 0,
|
|
34051
|
-
files_scanned: 0,
|
|
34052
|
-
skipped_files: 0
|
|
34053
|
-
};
|
|
34054
|
-
return JSON.stringify(errorResult, null, 2);
|
|
34055
|
-
}
|
|
34056
|
-
if (containsPathTraversal(exc) || containsControlChars(exc)) {
|
|
34099
|
+
const err2 = validateExcludePattern(exc);
|
|
34100
|
+
if (err2) {
|
|
34057
34101
|
const errorResult = {
|
|
34058
|
-
error:
|
|
34102
|
+
error: err2,
|
|
34059
34103
|
scan_dir: directory,
|
|
34060
34104
|
findings: [],
|
|
34061
34105
|
count: 0,
|
|
@@ -34091,10 +34135,20 @@ var init_secretscan = __esm(() => {
|
|
|
34091
34135
|
};
|
|
34092
34136
|
return JSON.stringify(errorResult, null, 2);
|
|
34093
34137
|
}
|
|
34094
|
-
const
|
|
34095
|
-
|
|
34096
|
-
|
|
34097
|
-
|
|
34138
|
+
const excludeExact = new Set(DEFAULT_EXCLUDE_DIRS);
|
|
34139
|
+
const excludeGlobs = [];
|
|
34140
|
+
const ignoreFilePatterns = loadSecretScanIgnore(scanDir);
|
|
34141
|
+
const allUserPatterns = [
|
|
34142
|
+
...exclude ?? [],
|
|
34143
|
+
...ignoreFilePatterns
|
|
34144
|
+
];
|
|
34145
|
+
for (const exc of allUserPatterns) {
|
|
34146
|
+
if (exc.length === 0)
|
|
34147
|
+
continue;
|
|
34148
|
+
if (isGlobOrPathPattern(exc)) {
|
|
34149
|
+
excludeGlobs.push(exc);
|
|
34150
|
+
} else {
|
|
34151
|
+
excludeExact.add(exc);
|
|
34098
34152
|
}
|
|
34099
34153
|
}
|
|
34100
34154
|
const stats = {
|
|
@@ -34104,7 +34158,7 @@ var init_secretscan = __esm(() => {
|
|
|
34104
34158
|
symlinkSkipped: 0
|
|
34105
34159
|
};
|
|
34106
34160
|
const visited = new Set;
|
|
34107
|
-
const files = findScannableFiles(scanDir,
|
|
34161
|
+
const files = findScannableFiles(scanDir, excludeExact, excludeGlobs, scanDir, visited, stats);
|
|
34108
34162
|
files.sort((a, b) => {
|
|
34109
34163
|
const aLower = a.toLowerCase();
|
|
34110
34164
|
const bLower = b.toLowerCase();
|
|
@@ -47948,8 +48002,8 @@ function isOutsideSwarmDir(filePath, directory) {
|
|
|
47948
48002
|
return false;
|
|
47949
48003
|
const swarmDir = path26.resolve(directory, ".swarm");
|
|
47950
48004
|
const resolved = path26.resolve(directory, filePath);
|
|
47951
|
-
const
|
|
47952
|
-
return
|
|
48005
|
+
const relative4 = path26.relative(swarmDir, resolved);
|
|
48006
|
+
return relative4.startsWith("..") || path26.isAbsolute(relative4);
|
|
47953
48007
|
}
|
|
47954
48008
|
function isSourceCodePath(filePath) {
|
|
47955
48009
|
if (!filePath)
|
|
@@ -48734,10 +48788,8 @@ function createDelegationGateHook(config3) {
|
|
|
48734
48788
|
break;
|
|
48735
48789
|
}
|
|
48736
48790
|
}
|
|
48737
|
-
|
|
48738
|
-
|
|
48739
|
-
}
|
|
48740
|
-
const afterCoder = delegationChain.slice(lastCoderIndex);
|
|
48791
|
+
const searchStart = lastCoderIndex === -1 ? 0 : lastCoderIndex;
|
|
48792
|
+
const afterCoder = delegationChain.slice(searchStart);
|
|
48741
48793
|
for (const delegation of afterCoder) {
|
|
48742
48794
|
const target = stripKnownSwarmPrefix(delegation.to);
|
|
48743
48795
|
if (target === "reviewer")
|
|
@@ -48745,7 +48797,7 @@ function createDelegationGateHook(config3) {
|
|
|
48745
48797
|
if (target === "test_engineer")
|
|
48746
48798
|
hasTestEngineer = true;
|
|
48747
48799
|
}
|
|
48748
|
-
if (hasReviewer && hasTestEngineer) {
|
|
48800
|
+
if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer) {
|
|
48749
48801
|
session.qaSkipCount = 0;
|
|
48750
48802
|
session.qaSkipTaskIds = [];
|
|
48751
48803
|
}
|
|
@@ -58085,13 +58137,13 @@ function validatePath(inputPath, baseDir, workspaceDir) {
|
|
|
58085
58137
|
resolved = path41.resolve(baseDir, inputPath);
|
|
58086
58138
|
}
|
|
58087
58139
|
const workspaceResolved = path41.resolve(workspaceDir);
|
|
58088
|
-
let
|
|
58140
|
+
let relative5;
|
|
58089
58141
|
if (isWinAbs) {
|
|
58090
|
-
|
|
58142
|
+
relative5 = path41.win32.relative(workspaceResolved, resolved);
|
|
58091
58143
|
} else {
|
|
58092
|
-
|
|
58144
|
+
relative5 = path41.relative(workspaceResolved, resolved);
|
|
58093
58145
|
}
|
|
58094
|
-
if (
|
|
58146
|
+
if (relative5.startsWith("..")) {
|
|
58095
58147
|
return "path traversal detected";
|
|
58096
58148
|
}
|
|
58097
58149
|
return null;
|
|
@@ -60900,6 +60952,7 @@ var todo_extract = createSwarmTool({
|
|
|
60900
60952
|
});
|
|
60901
60953
|
// src/tools/update-task-status.ts
|
|
60902
60954
|
init_tool();
|
|
60955
|
+
init_schema();
|
|
60903
60956
|
init_manager2();
|
|
60904
60957
|
import * as fs35 from "fs";
|
|
60905
60958
|
import * as path47 from "path";
|
|
@@ -60949,10 +61002,6 @@ function checkReviewerGate(taskId, workingDirectory) {
|
|
|
60949
61002
|
const state = getTaskState(session, taskId);
|
|
60950
61003
|
stateEntries.push(`${sessionId}: ${state}`);
|
|
60951
61004
|
}
|
|
60952
|
-
const allIdle = stateEntries.length > 0 && stateEntries.every((e) => e.endsWith(": idle"));
|
|
60953
|
-
if (allIdle) {
|
|
60954
|
-
return { blocked: false, reason: "" };
|
|
60955
|
-
}
|
|
60956
61005
|
try {
|
|
60957
61006
|
const resolvedDir = workingDirectory ?? process.cwd();
|
|
60958
61007
|
const planPath = path47.join(resolvedDir, ".swarm", "plan.json");
|
|
@@ -60975,6 +61024,49 @@ function checkReviewerGate(taskId, workingDirectory) {
|
|
|
60975
61024
|
return { blocked: false, reason: "" };
|
|
60976
61025
|
}
|
|
60977
61026
|
}
|
|
61027
|
+
function recoverTaskStateFromDelegations(taskId) {
|
|
61028
|
+
let hasReviewer = false;
|
|
61029
|
+
let hasTestEngineer = false;
|
|
61030
|
+
for (const [, chain] of swarmState.delegationChains) {
|
|
61031
|
+
for (const delegation of chain) {
|
|
61032
|
+
const target = stripKnownSwarmPrefix(delegation.to);
|
|
61033
|
+
if (target === "reviewer")
|
|
61034
|
+
hasReviewer = true;
|
|
61035
|
+
if (target === "test_engineer")
|
|
61036
|
+
hasTestEngineer = true;
|
|
61037
|
+
}
|
|
61038
|
+
}
|
|
61039
|
+
if (!hasReviewer && !hasTestEngineer)
|
|
61040
|
+
return;
|
|
61041
|
+
for (const [, session] of swarmState.agentSessions) {
|
|
61042
|
+
if (!(session.taskWorkflowStates instanceof Map))
|
|
61043
|
+
continue;
|
|
61044
|
+
const currentState = getTaskState(session, taskId);
|
|
61045
|
+
if (currentState === "tests_run" || currentState === "complete")
|
|
61046
|
+
continue;
|
|
61047
|
+
if (hasReviewer && currentState === "idle") {
|
|
61048
|
+
try {
|
|
61049
|
+
advanceTaskState(session, taskId, "coder_delegated");
|
|
61050
|
+
} catch {}
|
|
61051
|
+
}
|
|
61052
|
+
if (hasReviewer) {
|
|
61053
|
+
const stateNow = getTaskState(session, taskId);
|
|
61054
|
+
if (stateNow === "coder_delegated" || stateNow === "pre_check_passed") {
|
|
61055
|
+
try {
|
|
61056
|
+
advanceTaskState(session, taskId, "reviewer_run");
|
|
61057
|
+
} catch {}
|
|
61058
|
+
}
|
|
61059
|
+
}
|
|
61060
|
+
if (hasTestEngineer) {
|
|
61061
|
+
const stateNow = getTaskState(session, taskId);
|
|
61062
|
+
if (stateNow === "reviewer_run") {
|
|
61063
|
+
try {
|
|
61064
|
+
advanceTaskState(session, taskId, "tests_run");
|
|
61065
|
+
} catch {}
|
|
61066
|
+
}
|
|
61067
|
+
}
|
|
61068
|
+
}
|
|
61069
|
+
}
|
|
60978
61070
|
async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
60979
61071
|
const statusError = validateStatus(args2.status);
|
|
60980
61072
|
if (statusError) {
|
|
@@ -61058,6 +61150,7 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
|
|
|
61058
61150
|
directory = fallbackDir ?? process.cwd();
|
|
61059
61151
|
}
|
|
61060
61152
|
if (args2.status === "completed") {
|
|
61153
|
+
recoverTaskStateFromDelegations(args2.task_id);
|
|
61061
61154
|
const reviewerCheck = checkReviewerGate(args2.task_id, directory);
|
|
61062
61155
|
if (reviewerCheck.blocked) {
|
|
61063
61156
|
return {
|
|
@@ -50,6 +50,17 @@ export interface ReviewerGateResult {
|
|
|
50
50
|
* @returns ReviewerGateResult indicating whether the gate is blocked
|
|
51
51
|
*/
|
|
52
52
|
export declare function checkReviewerGate(taskId: string, workingDirectory?: string): ReviewerGateResult;
|
|
53
|
+
/**
|
|
54
|
+
* Recovery mechanism: reconcile task state with delegation history.
|
|
55
|
+
* When reviewer/test_engineer delegations occurred but the state machine
|
|
56
|
+
* was not advanced (e.g., toolAfter didn't fire, subagent_type missing,
|
|
57
|
+
* cross-session gaps, or pure verification tasks without coder delegation),
|
|
58
|
+
* this function walks all delegation chains and advances the task state
|
|
59
|
+
* so that checkReviewerGate can make an accurate decision.
|
|
60
|
+
*
|
|
61
|
+
* @param taskId - The task ID to recover state for
|
|
62
|
+
*/
|
|
63
|
+
export declare function recoverTaskStateFromDelegations(taskId: string): void;
|
|
53
64
|
/**
|
|
54
65
|
* Execute the update_task_status tool.
|
|
55
66
|
* Validates the task_id and status, then updates the task status in the plan.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.23.1",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|