@pushpalsdev/cli 1.1.11 → 1.1.13
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 +14 -8
- package/monitor-ui/+not-found.html +1 -1
- package/monitor-ui/_expo/static/js/web/{entry-ff425ab85ad13c1920b8ee00abfae7dd.js → entry-e8aa7049f746aaa8f1e865615e248f21.js} +1 -1
- package/monitor-ui/_sitemap.html +1 -1
- package/monitor-ui/index.html +1 -1
- package/monitor-ui/modal.html +1 -1
- package/package.json +1 -1
- package/runtime/configs/default.toml +1 -1
- package/runtime/protocol/schemas/events.schema.json +2 -1
- package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +1 -1
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +176 -1
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +213 -0
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex_backend.ts +0 -1
- package/runtime/sandbox/apps/workerpals/src/backends/shared/executor_base.py +64 -7
- package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +39 -1
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +1 -1
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +93 -1
- package/runtime/sandbox/configs/default.toml +1 -1
- package/runtime/sandbox/packages/protocol/src/schemas/events.schema.json +2 -1
- package/runtime/sandbox/packages/protocol/src/types.ts +1 -0
- package/runtime/sandbox/packages/shared/src/config.ts +1 -1
- package/runtime/sandbox/protocol/schemas/events.schema.json +2 -1
|
@@ -136,6 +136,29 @@ export function resolveGenericPythonExecutorTimeoutMs(params: {
|
|
|
136
136
|
return configuredTimeoutMs;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
function toSnakeConfigKey(key: string): string {
|
|
140
|
+
return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function formatGenericPythonExecutorTimeoutDetail(
|
|
144
|
+
config: GenericPythonExecutorConfig,
|
|
145
|
+
configuredTimeoutMs: number,
|
|
146
|
+
executionBudgetMs: number | null,
|
|
147
|
+
timeoutMs: number,
|
|
148
|
+
): string {
|
|
149
|
+
const configPath = `workerpals.${toSnakeConfigKey(config.timeoutConfigKey)}`;
|
|
150
|
+
if (executionBudgetMs == null) {
|
|
151
|
+
return `${configPath}=${configuredTimeoutMs}ms`;
|
|
152
|
+
}
|
|
153
|
+
if (config.capTimeoutToExecutionBudget === false) {
|
|
154
|
+
return `${configPath}=${configuredTimeoutMs}ms; planning executionBudgetMs=${executionBudgetMs}ms ignored by backend opt-out`;
|
|
155
|
+
}
|
|
156
|
+
if (timeoutMs < configuredTimeoutMs) {
|
|
157
|
+
return `${configPath}=${configuredTimeoutMs}ms capped by planning executionBudgetMs=${executionBudgetMs}ms`;
|
|
158
|
+
}
|
|
159
|
+
return `${configPath}=${configuredTimeoutMs}ms within planning executionBudgetMs=${executionBudgetMs}ms`;
|
|
160
|
+
}
|
|
161
|
+
|
|
139
162
|
export function createGenericPythonExecutor(
|
|
140
163
|
config: GenericPythonExecutorConfig,
|
|
141
164
|
): BackendTaskExecutor {
|
|
@@ -172,6 +195,12 @@ export function createGenericPythonExecutor(
|
|
|
172
195
|
executionBudgetMs,
|
|
173
196
|
capTimeoutToExecutionBudget: config.capTimeoutToExecutionBudget,
|
|
174
197
|
});
|
|
198
|
+
const timeoutDetail = formatGenericPythonExecutorTimeoutDetail(
|
|
199
|
+
config,
|
|
200
|
+
configuredTimeoutMs,
|
|
201
|
+
executionBudgetMs,
|
|
202
|
+
timeoutMs,
|
|
203
|
+
);
|
|
175
204
|
const payloadBase64 = Buffer.from(
|
|
176
205
|
JSON.stringify({
|
|
177
206
|
kind,
|
|
@@ -184,7 +213,7 @@ export function createGenericPythonExecutor(
|
|
|
184
213
|
|
|
185
214
|
onLog?.(
|
|
186
215
|
"stdout",
|
|
187
|
-
`[${backendLabel}Executor] Spawning ${backendName} executor (timeout=${timeoutMs}ms)`,
|
|
216
|
+
`[${backendLabel}Executor] Spawning ${backendName} executor (timeout=${timeoutMs}ms; ${timeoutDetail})`,
|
|
188
217
|
);
|
|
189
218
|
|
|
190
219
|
try {
|
|
@@ -207,6 +236,7 @@ export function createGenericPythonExecutor(
|
|
|
207
236
|
});
|
|
208
237
|
|
|
209
238
|
let timedOut = false;
|
|
239
|
+
let hardKillTimer: ReturnType<typeof setTimeout> | null = null;
|
|
210
240
|
const timeoutTimer = setTimeout(() => {
|
|
211
241
|
timedOut = true;
|
|
212
242
|
onLog?.(
|
|
@@ -214,6 +244,13 @@ export function createGenericPythonExecutor(
|
|
|
214
244
|
`[${backendLabel}Executor] Timeout reached after ${timeoutMs}ms; terminating process.`,
|
|
215
245
|
);
|
|
216
246
|
proc.kill();
|
|
247
|
+
hardKillTimer = setTimeout(() => {
|
|
248
|
+
onLog?.(
|
|
249
|
+
"stdout",
|
|
250
|
+
`[${backendLabel}Executor] Process did not exit after graceful timeout termination; forcing kill.`,
|
|
251
|
+
);
|
|
252
|
+
proc.kill("SIGKILL");
|
|
253
|
+
}, 5_000);
|
|
217
254
|
}, timeoutMs);
|
|
218
255
|
|
|
219
256
|
const progressIntervalMs = 15_000;
|
|
@@ -246,6 +283,7 @@ export function createGenericPythonExecutor(
|
|
|
246
283
|
]);
|
|
247
284
|
|
|
248
285
|
clearTimeout(timeoutTimer);
|
|
286
|
+
if (hardKillTimer) clearTimeout(hardKillTimer);
|
|
249
287
|
clearInterval(progressTimer);
|
|
250
288
|
|
|
251
289
|
const stdout = rawStdout ?? "";
|
|
@@ -303,7 +303,7 @@ export function resolveDockerJobTimeoutMs(
|
|
|
303
303
|
if (!hasBrowserValidationCommand(job)) return baseTimeoutMs;
|
|
304
304
|
|
|
305
305
|
const planning = maybeRecord(job.params.planning);
|
|
306
|
-
const executionBudgetMs = readPositiveNumber(planning?.executionBudgetMs) ??
|
|
306
|
+
const executionBudgetMs = readPositiveNumber(planning?.executionBudgetMs) ?? 1_200_000;
|
|
307
307
|
const finalizationBudgetMs = readPositiveNumber(planning?.finalizationBudgetMs) ?? 120_000;
|
|
308
308
|
const attempts = BROWSER_VALIDATION_JOB_REPAIR_ATTEMPTS + 1; // initial attempt plus repairs
|
|
309
309
|
const estimatedTimeoutMs =
|
|
@@ -1145,6 +1145,21 @@ export function isLongRunningBrowserValidationCommand(command: string): boolean
|
|
|
1145
1145
|
);
|
|
1146
1146
|
}
|
|
1147
1147
|
|
|
1148
|
+
export function isParallelSafeFastValidationCommand(repo: string, command: string): boolean {
|
|
1149
|
+
if (isLongRunningBrowserValidationCommand(command)) return false;
|
|
1150
|
+
if (shouldEnsurePlaywrightBrowserRuntime(repo, command)) return false;
|
|
1151
|
+
const tokens = tokenizeValidationCommandArgv(command);
|
|
1152
|
+
if (!tokens || tokens.length === 0) return false;
|
|
1153
|
+
const lower = tokens.map((token) => token.toLowerCase());
|
|
1154
|
+
if (lower[0] !== "bun") return false;
|
|
1155
|
+
if (lower[1] === "test") return true;
|
|
1156
|
+
if (lower[1] === "x" && lower[2] === "tsc") return true;
|
|
1157
|
+
if (lower[1] === "run" && ["lint", "typecheck", "test", "test:unit"].includes(lower[2] ?? "")) {
|
|
1158
|
+
return true;
|
|
1159
|
+
}
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1148
1163
|
function readPackageJson(repo: string): {
|
|
1149
1164
|
scripts?: Record<string, unknown>;
|
|
1150
1165
|
dependencies?: Record<string, unknown>;
|
|
@@ -3116,7 +3131,71 @@ async function runDeterministicQualityGate(
|
|
|
3116
3131
|
);
|
|
3117
3132
|
}
|
|
3118
3133
|
const playwrightBrowserRuntimeReadyTargets = new Set<string>();
|
|
3119
|
-
for (
|
|
3134
|
+
for (let commandIndex = 0; commandIndex < commandsToRun.length; ) {
|
|
3135
|
+
const parallelBatch: string[] = [];
|
|
3136
|
+
while (
|
|
3137
|
+
commandIndex + parallelBatch.length < commandsToRun.length &&
|
|
3138
|
+
parallelBatch.length < 3
|
|
3139
|
+
) {
|
|
3140
|
+
const candidate = commandsToRun[commandIndex + parallelBatch.length];
|
|
3141
|
+
if (!isParallelSafeFastValidationCommand(repo, candidate)) break;
|
|
3142
|
+
parallelBatch.push(candidate);
|
|
3143
|
+
}
|
|
3144
|
+
if (parallelBatch.length > 1) {
|
|
3145
|
+
onLog?.(
|
|
3146
|
+
"stdout",
|
|
3147
|
+
`[ValidationGate] Running fast validation batch in parallel: ${parallelBatch.join(" | ")}`,
|
|
3148
|
+
);
|
|
3149
|
+
const batchRuns = await Promise.all(
|
|
3150
|
+
parallelBatch.map(async (command) => {
|
|
3151
|
+
const commandMissingTools = requirementsForValidationCommand(
|
|
3152
|
+
toolchainPlan,
|
|
3153
|
+
command,
|
|
3154
|
+
).filter((requirement) =>
|
|
3155
|
+
missingToolRequirements.some((missing) => missing.tool === requirement.tool),
|
|
3156
|
+
);
|
|
3157
|
+
if (commandMissingTools.length > 0) {
|
|
3158
|
+
const stderr = `Validation skipped before execution because required tool(s) are missing: ${formatMissingToolRequirements(
|
|
3159
|
+
commandMissingTools,
|
|
3160
|
+
)}.`;
|
|
3161
|
+
return {
|
|
3162
|
+
run: {
|
|
3163
|
+
step: command,
|
|
3164
|
+
command,
|
|
3165
|
+
ok: false,
|
|
3166
|
+
exitCode: 127,
|
|
3167
|
+
stdout: "",
|
|
3168
|
+
stderr,
|
|
3169
|
+
elapsedMs: 1,
|
|
3170
|
+
} satisfies ValidationExecutionResult,
|
|
3171
|
+
stream: "stderr" as const,
|
|
3172
|
+
summary: `[ValidationGate] Validation skipped (missing toolchain): ${command}`,
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
const run = await runValidationCommand(
|
|
3176
|
+
repo,
|
|
3177
|
+
command,
|
|
3178
|
+
resolveValidationCommandTimeoutMs(command, qualityValidationStepTimeoutMs),
|
|
3179
|
+
outputPolicy,
|
|
3180
|
+
);
|
|
3181
|
+
const digest = run.ok ? "" : extractValidationFailureDigest(run);
|
|
3182
|
+
return {
|
|
3183
|
+
run,
|
|
3184
|
+
stream: (run.ok ? "stdout" : "stderr") as "stdout" | "stderr",
|
|
3185
|
+
summary: `[ValidationGate] ${run.ok ? "Passed" : "Failed"} (${run.elapsedMs}ms, exit ${run.exitCode}): ${command}${digest ? ` - ${digest}` : ""}`,
|
|
3186
|
+
};
|
|
3187
|
+
}),
|
|
3188
|
+
);
|
|
3189
|
+
for (const { run, stream, summary } of batchRuns) {
|
|
3190
|
+
validationRuns.push(run);
|
|
3191
|
+
onLog?.(stream, summary);
|
|
3192
|
+
}
|
|
3193
|
+
commandIndex += parallelBatch.length;
|
|
3194
|
+
continue;
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
const command = commandsToRun[commandIndex];
|
|
3198
|
+
commandIndex += 1;
|
|
3120
3199
|
const commandMissingTools = requirementsForValidationCommand(toolchainPlan, command).filter(
|
|
3121
3200
|
(requirement) =>
|
|
3122
3201
|
missingToolRequirements.some((missing) => missing.tool === requirement.tool),
|
|
@@ -6665,6 +6744,7 @@ export async function executeJob(
|
|
|
6665
6744
|
const previousValidationFailureDigests = new Map<string, string>();
|
|
6666
6745
|
const failureJobFamily = buildTaskFailureJobFamily(normalizedParams);
|
|
6667
6746
|
while (revisionAttempt <= qualityRevisionLoopMax) {
|
|
6747
|
+
const attemptStartedAt = Date.now();
|
|
6668
6748
|
const attemptParams: Record<string, unknown> = { ...normalizedParams };
|
|
6669
6749
|
if (revisionHint) {
|
|
6670
6750
|
attemptParams.qualityRevisionHint = revisionHint;
|
|
@@ -6683,6 +6763,7 @@ export async function executeJob(
|
|
|
6683
6763
|
}
|
|
6684
6764
|
let result: Awaited<ReturnType<typeof runExecutor>> | null = null;
|
|
6685
6765
|
let mergeConflictPass = 0;
|
|
6766
|
+
let executorElapsedMs = 0;
|
|
6686
6767
|
while (true) {
|
|
6687
6768
|
const currentResult = await runExecutor(
|
|
6688
6769
|
kind,
|
|
@@ -6751,6 +6832,7 @@ export async function executeJob(
|
|
|
6751
6832
|
exitCode: 4,
|
|
6752
6833
|
};
|
|
6753
6834
|
}
|
|
6835
|
+
executorElapsedMs = Date.now() - attemptStartedAt;
|
|
6754
6836
|
|
|
6755
6837
|
const preQualityStatus = await git(repo, ["status", "--porcelain"]);
|
|
6756
6838
|
const preQualityChangedPaths = preQualityStatus.ok
|
|
@@ -6799,6 +6881,7 @@ export async function executeJob(
|
|
|
6799
6881
|
};
|
|
6800
6882
|
}
|
|
6801
6883
|
|
|
6884
|
+
const qualityStartedAt = Date.now();
|
|
6802
6885
|
const quality = await runDeterministicQualityGate(
|
|
6803
6886
|
repo,
|
|
6804
6887
|
attemptParams,
|
|
@@ -6810,6 +6893,15 @@ export async function executeJob(
|
|
|
6810
6893
|
revisionAttempt,
|
|
6811
6894
|
},
|
|
6812
6895
|
);
|
|
6896
|
+
const qualityElapsedMs = Date.now() - qualityStartedAt;
|
|
6897
|
+
const validationCommandElapsedMs = quality.validationRuns.reduce(
|
|
6898
|
+
(total, run) => total + Math.max(0, Number(run.elapsedMs) || 0),
|
|
6899
|
+
0,
|
|
6900
|
+
);
|
|
6901
|
+
onLog?.(
|
|
6902
|
+
"stdout",
|
|
6903
|
+
`[JobRunner] Performance summary: attempt=${revisionAttempt}, executor=${executorElapsedMs}ms, quality=${qualityElapsedMs}ms, validation_commands=${quality.validationRuns.length}, validation_command_time=${validationCommandElapsedMs}ms, changed_files=${quality.changedPaths.length}`,
|
|
6904
|
+
);
|
|
6813
6905
|
let browserRepairPacket = buildBrowserValidationRepairPacket(
|
|
6814
6906
|
quality.validationRuns,
|
|
6815
6907
|
previousValidationFailureDigests,
|
|
@@ -51,7 +51,7 @@ workerpal_heartbeat_ms = 0
|
|
|
51
51
|
workerpal_labels = []
|
|
52
52
|
execution_budget_interactive_ms = 600000
|
|
53
53
|
execution_budget_normal_ms = 1500000
|
|
54
|
-
execution_budget_background_ms =
|
|
54
|
+
execution_budget_background_ms = 1200000
|
|
55
55
|
finalization_budget_ms = 120000
|
|
56
56
|
crash_restart_enabled = true
|
|
57
57
|
crash_restart_max_restarts = 3
|
|
@@ -515,7 +515,8 @@
|
|
|
515
515
|
"stream": { "type": "string", "enum": ["stdout", "stderr"] },
|
|
516
516
|
"seq": { "type": "integer", "minimum": 1 },
|
|
517
517
|
"line": { "type": "string" },
|
|
518
|
-
"ts": { "type": "string" }
|
|
518
|
+
"ts": { "type": "string" },
|
|
519
|
+
"phase": { "type": ["string", "null"] }
|
|
519
520
|
},
|
|
520
521
|
"additionalProperties": false
|
|
521
522
|
}
|
|
@@ -1619,7 +1619,7 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
|
|
|
1619
1619
|
asInt(
|
|
1620
1620
|
parseIntEnv("REMOTEBUDDY_EXECUTION_BUDGET_BACKGROUND_MS") ??
|
|
1621
1621
|
remoteNode.execution_budget_background_ms,
|
|
1622
|
-
|
|
1622
|
+
1_200_000,
|
|
1623
1623
|
),
|
|
1624
1624
|
),
|
|
1625
1625
|
finalizationBudgetMs: Math.max(
|
|
@@ -515,7 +515,8 @@
|
|
|
515
515
|
"stream": { "type": "string", "enum": ["stdout", "stderr"] },
|
|
516
516
|
"seq": { "type": "integer", "minimum": 1 },
|
|
517
517
|
"line": { "type": "string" },
|
|
518
|
-
"ts": { "type": "string" }
|
|
518
|
+
"ts": { "type": "string" },
|
|
519
|
+
"phase": { "type": ["string", "null"] }
|
|
519
520
|
},
|
|
520
521
|
"additionalProperties": false
|
|
521
522
|
}
|