@pushpalsdev/cli 1.0.83 → 1.0.85
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/pushpals-cli.js +11 -1
- package/package.json +1 -1
- package/runtime/configs/default.toml +8 -1
- package/runtime/configs/local.example.toml +8 -1
- package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +21 -1
- package/runtime/sandbox/apps/workerpals/src/backends/openhands_task_execute.ts +2 -1
- package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +2 -1
- package/runtime/sandbox/apps/workerpals/src/common/sandbox_env.ts +76 -0
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +348 -133
- package/runtime/sandbox/configs/default.toml +8 -1
- package/runtime/sandbox/configs/local.example.toml +8 -1
- package/runtime/sandbox/packages/shared/src/config.ts +34 -1
- package/runtime/sandbox/packages/shared/src/toolchain.ts +207 -15
|
@@ -150,7 +150,14 @@ file_modifying_jobs = ["task.execute"]
|
|
|
150
150
|
output_max_chars = 196608
|
|
151
151
|
output_max_lines = 600
|
|
152
152
|
output_max_head_lines = 120
|
|
153
|
-
quality_max_auto_revisions =
|
|
153
|
+
quality_max_auto_revisions = 3
|
|
154
|
+
quality_validation_max_auto_revisions = 3
|
|
155
|
+
quality_scope_gate_enabled = true
|
|
156
|
+
quality_validation_gate_enabled = true
|
|
157
|
+
quality_critic_gate_enabled = true
|
|
158
|
+
quality_publish_gate_enabled = true
|
|
159
|
+
# Browser/e2e validation commands get a longer built-in floor (10m) because they
|
|
160
|
+
# may need to start a dev server and run browser automation.
|
|
154
161
|
quality_validation_step_timeout_ms = 180000
|
|
155
162
|
quality_critic_timeout_ms = 45000
|
|
156
163
|
quality_soft_pass_on_exhausted = true
|
|
@@ -66,7 +66,14 @@ file_modifying_jobs = ["task.execute"]
|
|
|
66
66
|
output_max_chars = 196608
|
|
67
67
|
output_max_lines = 600
|
|
68
68
|
output_max_head_lines = 120
|
|
69
|
-
quality_max_auto_revisions =
|
|
69
|
+
quality_max_auto_revisions = 3
|
|
70
|
+
quality_validation_max_auto_revisions = 3
|
|
71
|
+
quality_scope_gate_enabled = true
|
|
72
|
+
quality_validation_gate_enabled = true
|
|
73
|
+
quality_critic_gate_enabled = true
|
|
74
|
+
quality_publish_gate_enabled = true
|
|
75
|
+
# Browser/e2e validation commands get a longer built-in floor (10m) because they
|
|
76
|
+
# may need to start a dev server and run browser automation.
|
|
70
77
|
quality_validation_step_timeout_ms = 180000
|
|
71
78
|
quality_critic_timeout_ms = 45000
|
|
72
79
|
quality_soft_pass_on_exhausted = true
|
|
@@ -14,7 +14,7 @@ const DEFAULT_CONFIG_DIR = "configs";
|
|
|
14
14
|
const TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
15
15
|
const FALSY = new Set(["0", "false", "no", "off"]);
|
|
16
16
|
const DEFAULT_WORKERPALS_QUALITY_CRITIC_MIN_SCORE = 8;
|
|
17
|
-
const DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS =
|
|
17
|
+
const DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS = 3;
|
|
18
18
|
const DEFAULT_WORKERPALS_FILE_MODIFYING_JOBS = ["task.execute"];
|
|
19
19
|
const DEFAULT_WORKERPALS_OUTPUT_MAX_CHARS = 192 * 1024;
|
|
20
20
|
const DEFAULT_WORKERPALS_OUTPUT_MAX_LINES = 600;
|
|
@@ -210,6 +210,11 @@ export interface PushPalsConfig {
|
|
|
210
210
|
outputMaxLines: number;
|
|
211
211
|
outputMaxHeadLines: number;
|
|
212
212
|
qualityMaxAutoRevisions: number;
|
|
213
|
+
qualityValidationMaxAutoRevisions: number;
|
|
214
|
+
qualityScopeGateEnabled: boolean;
|
|
215
|
+
qualityValidationGateEnabled: boolean;
|
|
216
|
+
qualityCriticGateEnabled: boolean;
|
|
217
|
+
qualityPublishGateEnabled: boolean;
|
|
213
218
|
qualityValidationStepTimeoutMs: number;
|
|
214
219
|
qualityCriticTimeoutMs: number;
|
|
215
220
|
qualitySoftPassOnExhausted: boolean;
|
|
@@ -931,6 +936,17 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
|
|
|
931
936
|
),
|
|
932
937
|
),
|
|
933
938
|
);
|
|
939
|
+
const workerQualityValidationMaxAutoRevisions = Math.max(
|
|
940
|
+
0,
|
|
941
|
+
Math.min(
|
|
942
|
+
10,
|
|
943
|
+
asInt(
|
|
944
|
+
parseIntEnv("WORKERPALS_QUALITY_VALIDATION_MAX_AUTO_REVISIONS") ??
|
|
945
|
+
workerNode.quality_validation_max_auto_revisions,
|
|
946
|
+
DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS,
|
|
947
|
+
),
|
|
948
|
+
),
|
|
949
|
+
);
|
|
934
950
|
const workerFileModifyingJobs = (() => {
|
|
935
951
|
const envRaw = firstNonEmpty(process.env.WORKERPALS_FILE_MODIFYING_JOBS);
|
|
936
952
|
const parsed = envRaw
|
|
@@ -990,6 +1006,18 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
|
|
|
990
1006
|
const workerQualitySoftPassOnExhausted =
|
|
991
1007
|
parseBoolEnv("WORKERPALS_QUALITY_SOFT_PASS_ON_EXHAUSTED") ??
|
|
992
1008
|
asBoolean(workerNode.quality_soft_pass_on_exhausted, true);
|
|
1009
|
+
const workerQualityScopeGateEnabled =
|
|
1010
|
+
parseBoolEnv("WORKERPALS_QUALITY_SCOPE_GATE_ENABLED") ??
|
|
1011
|
+
asBoolean(workerNode.quality_scope_gate_enabled, true);
|
|
1012
|
+
const workerQualityValidationGateEnabled =
|
|
1013
|
+
parseBoolEnv("WORKERPALS_QUALITY_VALIDATION_GATE_ENABLED") ??
|
|
1014
|
+
asBoolean(workerNode.quality_validation_gate_enabled, true);
|
|
1015
|
+
const workerQualityCriticGateEnabled =
|
|
1016
|
+
parseBoolEnv("WORKERPALS_QUALITY_CRITIC_GATE_ENABLED") ??
|
|
1017
|
+
asBoolean(workerNode.quality_critic_gate_enabled, true);
|
|
1018
|
+
const workerQualityPublishGateEnabled =
|
|
1019
|
+
parseBoolEnv("WORKERPALS_QUALITY_PUBLISH_GATE_ENABLED") ??
|
|
1020
|
+
asBoolean(workerNode.quality_publish_gate_enabled, true);
|
|
993
1021
|
const workerQualityCriticMinScore = (() => {
|
|
994
1022
|
const configThresholdRaw =
|
|
995
1023
|
workerNode.quality_critic_min_score == null
|
|
@@ -2032,6 +2060,11 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
|
|
|
2032
2060
|
outputMaxLines: workerOutputMaxLines,
|
|
2033
2061
|
outputMaxHeadLines: workerOutputMaxHeadLines,
|
|
2034
2062
|
qualityMaxAutoRevisions: workerQualityMaxAutoRevisions,
|
|
2063
|
+
qualityValidationMaxAutoRevisions: workerQualityValidationMaxAutoRevisions,
|
|
2064
|
+
qualityScopeGateEnabled: workerQualityScopeGateEnabled,
|
|
2065
|
+
qualityValidationGateEnabled: workerQualityValidationGateEnabled,
|
|
2066
|
+
qualityCriticGateEnabled: workerQualityCriticGateEnabled,
|
|
2067
|
+
qualityPublishGateEnabled: workerQualityPublishGateEnabled,
|
|
2035
2068
|
qualityValidationStepTimeoutMs: workerQualityValidationStepTimeoutMs,
|
|
2036
2069
|
qualityCriticTimeoutMs: workerQualityCriticTimeoutMs,
|
|
2037
2070
|
qualitySoftPassOnExhausted: workerQualitySoftPassOnExhausted,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
2
|
-
import { isAbsolute, join, normalize } from "path";
|
|
2
|
+
import { isAbsolute, join, normalize, resolve } from "path";
|
|
3
3
|
|
|
4
4
|
export type ToolchainEnvironmentSource =
|
|
5
5
|
| "devcontainer"
|
|
@@ -93,6 +93,17 @@ const DIRECT_TOOL_CANDIDATES: Record<string, string[]> = {
|
|
|
93
93
|
yarn: ["yarn"],
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
+
const BUN_OPTIONS_WITH_VALUE = new Set(["--cwd", "-C"]);
|
|
97
|
+
const PACKAGE_MANAGER_OPTIONS_WITH_VALUE = new Set([
|
|
98
|
+
"--cwd",
|
|
99
|
+
"--dir",
|
|
100
|
+
"--filter",
|
|
101
|
+
"--prefix",
|
|
102
|
+
"--workspace",
|
|
103
|
+
"-C",
|
|
104
|
+
"-F",
|
|
105
|
+
]);
|
|
106
|
+
|
|
96
107
|
interface NativeSignals {
|
|
97
108
|
hasC: boolean;
|
|
98
109
|
hasCxx: boolean;
|
|
@@ -374,8 +385,9 @@ function resolveBunSubcommand(tokens: string[]): { kind: "run" | "x"; value: str
|
|
|
374
385
|
let index = 1;
|
|
375
386
|
while (index < tokens.length) {
|
|
376
387
|
const token = tokens[index] ?? "";
|
|
377
|
-
|
|
378
|
-
|
|
388
|
+
const bunOption = parseOptionWithValue(token, BUN_OPTIONS_WITH_VALUE, tokens[index + 1]);
|
|
389
|
+
if (bunOption) {
|
|
390
|
+
index += bunOption.consumed;
|
|
379
391
|
continue;
|
|
380
392
|
}
|
|
381
393
|
if (token.startsWith("--")) {
|
|
@@ -402,9 +414,10 @@ function resolvePackageScript(
|
|
|
402
414
|
let index = 1;
|
|
403
415
|
while (index < tokens.length) {
|
|
404
416
|
const token = tokens[index] ?? "";
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
417
|
+
const bunOption = parseOptionWithValue(token, BUN_OPTIONS_WITH_VALUE, tokens[index + 1]);
|
|
418
|
+
if (bunOption) {
|
|
419
|
+
cwd = resolveWorkspacePath(repoRoot, bunOption.value);
|
|
420
|
+
index += bunOption.consumed;
|
|
408
421
|
continue;
|
|
409
422
|
}
|
|
410
423
|
if (token.startsWith("--")) {
|
|
@@ -426,17 +439,31 @@ function resolvePackageScript(
|
|
|
426
439
|
while (index < tokens.length) {
|
|
427
440
|
const token = tokens[index] ?? "";
|
|
428
441
|
const normalized = normalizeToolToken(token);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
442
|
+
const packageOption = parseOptionWithValue(
|
|
443
|
+
token,
|
|
444
|
+
packageManagerOptionsWithValue(first),
|
|
445
|
+
tokens[index + 1],
|
|
446
|
+
);
|
|
447
|
+
if (packageOption) {
|
|
448
|
+
if (packageOption.name !== "--filter" && packageOption.name !== "-F") {
|
|
449
|
+
const optionCwd = resolvePackageOptionCwd(
|
|
450
|
+
repoRoot,
|
|
451
|
+
packageOption.name,
|
|
452
|
+
packageOption.value,
|
|
453
|
+
);
|
|
454
|
+
if (!optionCwd && isWorkspacePackageOption(packageOption.name)) return null;
|
|
455
|
+
if (optionCwd) cwd = optionCwd;
|
|
456
|
+
}
|
|
457
|
+
index += packageOption.consumed;
|
|
438
458
|
continue;
|
|
439
459
|
}
|
|
460
|
+
if (first === "yarn" && normalized === "workspace" && tokens[index + 2]) {
|
|
461
|
+
const workspaceCwd = resolveWorkspacePackageCwd(repoRoot, tokens[index + 1] ?? "");
|
|
462
|
+
if (!workspaceCwd) return null;
|
|
463
|
+
cwd = workspaceCwd;
|
|
464
|
+
scriptName = tokens[index + 2] ?? "";
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
440
467
|
if (normalized === "run") {
|
|
441
468
|
scriptName = tokens[index + 1] ?? "";
|
|
442
469
|
break;
|
|
@@ -483,6 +510,171 @@ function inferReferencedScriptPaths(repoRoot: string, scriptCwd: string, tokens:
|
|
|
483
510
|
return out;
|
|
484
511
|
}
|
|
485
512
|
|
|
513
|
+
function resolveWorkspacePath(repoRoot: string, pathValue: string): string {
|
|
514
|
+
return isAbsolute(pathValue) ? normalize(pathValue) : resolve(repoRoot, pathValue);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function resolvePackageOptionCwd(
|
|
518
|
+
repoRoot: string,
|
|
519
|
+
optionName: string,
|
|
520
|
+
optionValue: string,
|
|
521
|
+
): string | null {
|
|
522
|
+
if (isWorkspacePackageOption(optionName)) {
|
|
523
|
+
return resolveWorkspacePackageCwd(repoRoot, optionValue);
|
|
524
|
+
}
|
|
525
|
+
const optionCwd = resolveWorkspacePath(repoRoot, optionValue);
|
|
526
|
+
return existsSync(join(optionCwd, "package.json")) ? optionCwd : null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function resolveWorkspacePackageCwd(repoRoot: string, workspaceRef: string): string | null {
|
|
530
|
+
const directCwd = resolveWorkspacePath(repoRoot, workspaceRef);
|
|
531
|
+
if (existsSync(join(directCwd, "package.json"))) return directCwd;
|
|
532
|
+
for (const candidate of expandWorkspacePackageDirs(repoRoot)) {
|
|
533
|
+
try {
|
|
534
|
+
const parsed = JSON.parse(readFileSync(join(candidate, "package.json"), "utf8")) as {
|
|
535
|
+
name?: unknown;
|
|
536
|
+
};
|
|
537
|
+
if (parsed.name === workspaceRef) return candidate;
|
|
538
|
+
} catch {
|
|
539
|
+
// Ignore malformed workspace packages; validation will report real repo failures.
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function expandWorkspacePackageDirs(repoRoot: string, maxPackages = 200): string[] {
|
|
546
|
+
const packageJsonPath = join(repoRoot, "package.json");
|
|
547
|
+
if (!existsSync(packageJsonPath)) return [];
|
|
548
|
+
let patterns: string[] = [];
|
|
549
|
+
try {
|
|
550
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
|
|
551
|
+
workspaces?: unknown;
|
|
552
|
+
};
|
|
553
|
+
if (Array.isArray(parsed.workspaces)) {
|
|
554
|
+
patterns = parsed.workspaces.filter((entry): entry is string => typeof entry === "string");
|
|
555
|
+
} else if (
|
|
556
|
+
parsed.workspaces &&
|
|
557
|
+
typeof parsed.workspaces === "object" &&
|
|
558
|
+
Array.isArray((parsed.workspaces as { packages?: unknown }).packages)
|
|
559
|
+
) {
|
|
560
|
+
patterns = (parsed.workspaces as { packages: unknown[] }).packages.filter(
|
|
561
|
+
(entry): entry is string => typeof entry === "string",
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
} catch {
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
const out: string[] = [];
|
|
568
|
+
const seen = new Set<string>();
|
|
569
|
+
for (const pattern of patterns) {
|
|
570
|
+
if (out.length >= maxPackages) break;
|
|
571
|
+
for (const candidate of expandWorkspacePattern(repoRoot, pattern, maxPackages - out.length)) {
|
|
572
|
+
const key = normalize(candidate);
|
|
573
|
+
if (seen.has(key) || !existsSync(join(candidate, "package.json"))) continue;
|
|
574
|
+
seen.add(key);
|
|
575
|
+
out.push(candidate);
|
|
576
|
+
if (out.length >= maxPackages) break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return out;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function expandWorkspacePattern(repoRoot: string, pattern: string, maxPackages: number): string[] {
|
|
583
|
+
const normalizedPattern = pattern.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
584
|
+
if (!normalizedPattern || normalizedPattern.startsWith("!")) return [];
|
|
585
|
+
const segments = normalizedPattern.split("/").filter(Boolean);
|
|
586
|
+
let dirs = [repoRoot];
|
|
587
|
+
for (const segment of segments) {
|
|
588
|
+
const next: string[] = [];
|
|
589
|
+
for (const dir of dirs) {
|
|
590
|
+
if (next.length >= maxPackages) break;
|
|
591
|
+
if (segment === "**") {
|
|
592
|
+
next.push(...collectDescendantDirs(dir, Math.max(0, maxPackages - next.length), 3));
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (segment.includes("*")) {
|
|
596
|
+
const patternRegex = wildcardSegmentRegex(segment);
|
|
597
|
+
for (const entry of safeReadDir(dir)) {
|
|
598
|
+
if (next.length >= maxPackages) break;
|
|
599
|
+
if (!patternRegex.test(entry)) continue;
|
|
600
|
+
const candidate = join(dir, entry);
|
|
601
|
+
if (safeIsDirectory(candidate)) next.push(candidate);
|
|
602
|
+
}
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
const candidate = join(dir, segment);
|
|
606
|
+
if (safeIsDirectory(candidate)) next.push(candidate);
|
|
607
|
+
}
|
|
608
|
+
dirs = next;
|
|
609
|
+
if (dirs.length === 0) break;
|
|
610
|
+
}
|
|
611
|
+
return dirs;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function collectDescendantDirs(dir: string, limit: number, maxDepth: number): string[] {
|
|
615
|
+
const out: string[] = [];
|
|
616
|
+
const visit = (current: string, depth: number) => {
|
|
617
|
+
if (out.length >= limit || depth > maxDepth) return;
|
|
618
|
+
for (const entry of safeReadDir(current)) {
|
|
619
|
+
if (entry === "node_modules" || entry === ".git") continue;
|
|
620
|
+
const candidate = join(current, entry);
|
|
621
|
+
if (!safeIsDirectory(candidate)) continue;
|
|
622
|
+
out.push(candidate);
|
|
623
|
+
visit(candidate, depth + 1);
|
|
624
|
+
if (out.length >= limit) return;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
visit(dir, 0);
|
|
628
|
+
return out;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function safeReadDir(dir: string): string[] {
|
|
632
|
+
try {
|
|
633
|
+
return readdirSync(dir);
|
|
634
|
+
} catch {
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function safeIsDirectory(pathValue: string): boolean {
|
|
640
|
+
try {
|
|
641
|
+
return statSync(pathValue).isDirectory();
|
|
642
|
+
} catch {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function wildcardSegmentRegex(segment: string): RegExp {
|
|
648
|
+
return new RegExp(`^${segment.split("*").map(escapeRegExp).join(".*")}$`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function isWorkspacePackageOption(optionName: string): boolean {
|
|
652
|
+
return optionName === "--workspace" || optionName === "-w";
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function parseOptionWithValue(
|
|
656
|
+
token: string,
|
|
657
|
+
optionsWithValue: Set<string>,
|
|
658
|
+
nextToken?: string,
|
|
659
|
+
): { name: string; value: string; consumed: number } | null {
|
|
660
|
+
const equalsIndex = token.indexOf("=");
|
|
661
|
+
if (equalsIndex > 0) {
|
|
662
|
+
const name = token.slice(0, equalsIndex);
|
|
663
|
+
if (!optionsWithValue.has(name)) return null;
|
|
664
|
+
const value = token.slice(equalsIndex + 1);
|
|
665
|
+
return value ? { name, value, consumed: 1 } : null;
|
|
666
|
+
}
|
|
667
|
+
if (!optionsWithValue.has(token) || !nextToken) return null;
|
|
668
|
+
return { name: token, value: nextToken, consumed: 2 };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function packageManagerOptionsWithValue(packageManager: string): Set<string> {
|
|
672
|
+
if (packageManager === "npm") {
|
|
673
|
+
return new Set([...PACKAGE_MANAGER_OPTIONS_WITH_VALUE, "-w"]);
|
|
674
|
+
}
|
|
675
|
+
return PACKAGE_MANAGER_OPTIONS_WITH_VALUE;
|
|
676
|
+
}
|
|
677
|
+
|
|
486
678
|
function normalizeReferencedScriptToken(token: string): string | null {
|
|
487
679
|
let normalized = token.replace(/\\/g, "/");
|
|
488
680
|
if (normalized.startsWith("-")) {
|