@pushpalsdev/cli 1.0.23 → 1.0.25
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 +162 -25
- package/package.json +1 -1
- package/runtime/configs/default.toml +4 -4
- package/runtime/configs/local.example.toml +7 -7
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +22 -5
- package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +12 -3
- package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +24 -0
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +6 -3
- package/runtime/sandbox/configs/default.toml +4 -4
- package/runtime/sandbox/configs/local.example.toml +7 -7
- package/runtime/sandbox/packages/shared/src/config.ts +4 -1
package/dist/pushpals-cli.js
CHANGED
|
@@ -637,7 +637,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
637
637
|
workerpalOnlineTtlMs: Math.max(1000, asInt(parseIntEnv("REMOTEBUDDY_WORKERPAL_ONLINE_TTL_MS") ?? remoteNode.workerpal_online_ttl_ms, 15000)),
|
|
638
638
|
waitForWorkerpalMs: Math.max(0, asInt(parseIntEnv("REMOTEBUDDY_WAIT_FOR_WORKERPAL_MS") ?? remoteNode.wait_for_workerpal_ms, 15000)),
|
|
639
639
|
autoSpawnWorkerpals: parseBoolEnv("REMOTEBUDDY_AUTO_SPAWN_WORKERPALS") ?? asBoolean(remoteNode.auto_spawn_workerpals, true),
|
|
640
|
-
maxWorkerpals: Math.max(1, asInt(remoteNode.max_workerpals, 20)),
|
|
640
|
+
maxWorkerpals: Math.max(1, asInt(parseIntEnv("REMOTEBUDDY_MAX_WORKERPALS") ?? remoteNode.max_workerpals, 20)),
|
|
641
641
|
workerpalStartupTimeoutMs: Math.max(1000, asInt(parseIntEnv("REMOTEBUDDY_WORKERPAL_STARTUP_TIMEOUT_MS") ?? remoteNode.workerpal_startup_timeout_ms, 1e4)),
|
|
642
642
|
workerpalDocker: parseBoolEnv("REMOTEBUDDY_WORKERPAL_DOCKER") ?? asBoolean(remoteNode.workerpal_docker, true),
|
|
643
643
|
workerpalRequireDocker: parseBoolEnv("REMOTEBUDDY_WORKERPAL_REQUIRE_DOCKER") ?? asBoolean(remoteNode.workerpal_require_docker, true),
|
|
@@ -2158,6 +2158,52 @@ function stopRuntimeServices(services) {
|
|
|
2158
2158
|
} catch {}
|
|
2159
2159
|
}
|
|
2160
2160
|
}
|
|
2161
|
+
function resolveGracefulShutdownPriority(name) {
|
|
2162
|
+
if (name === "source_control_manager")
|
|
2163
|
+
return 0;
|
|
2164
|
+
if (name === "remotebuddy")
|
|
2165
|
+
return 1;
|
|
2166
|
+
if (name === "localbuddy")
|
|
2167
|
+
return 2;
|
|
2168
|
+
return 3;
|
|
2169
|
+
}
|
|
2170
|
+
async function waitForRuntimeServicesExit(services, timeoutMs) {
|
|
2171
|
+
if (services.length === 0)
|
|
2172
|
+
return true;
|
|
2173
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
2174
|
+
while (Date.now() < deadline) {
|
|
2175
|
+
if (services.every((service) => service.exited))
|
|
2176
|
+
return true;
|
|
2177
|
+
await Bun.sleep(100);
|
|
2178
|
+
}
|
|
2179
|
+
return services.every((service) => service.exited);
|
|
2180
|
+
}
|
|
2181
|
+
async function stopRuntimeServicesGracefully(services, timeoutMs = 1e4) {
|
|
2182
|
+
if (services.length === 0)
|
|
2183
|
+
return;
|
|
2184
|
+
const running = services.filter((service) => !service.exited);
|
|
2185
|
+
if (running.length === 0)
|
|
2186
|
+
return;
|
|
2187
|
+
const ordered = [...running].sort((a, b) => resolveGracefulShutdownPriority(a.name) - resolveGracefulShutdownPriority(b.name));
|
|
2188
|
+
const nonServer = ordered.filter((service) => service.name !== "server");
|
|
2189
|
+
const server = ordered.filter((service) => service.name === "server");
|
|
2190
|
+
for (const service of nonServer) {
|
|
2191
|
+
try {
|
|
2192
|
+
service.proc.kill("SIGTERM");
|
|
2193
|
+
} catch {}
|
|
2194
|
+
}
|
|
2195
|
+
await waitForRuntimeServicesExit(nonServer, Math.max(1000, timeoutMs - 2000));
|
|
2196
|
+
for (const service of server) {
|
|
2197
|
+
try {
|
|
2198
|
+
service.proc.kill("SIGTERM");
|
|
2199
|
+
} catch {}
|
|
2200
|
+
}
|
|
2201
|
+
await waitForRuntimeServicesExit(server, Math.min(3000, timeoutMs));
|
|
2202
|
+
const remaining = ordered.filter((service) => !service.exited);
|
|
2203
|
+
if (remaining.length > 0) {
|
|
2204
|
+
stopRuntimeServices(remaining);
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2161
2207
|
function prependExecutableDirToPath(env, executablePath, platform = process.platform) {
|
|
2162
2208
|
const resolvedPath = String(executablePath ?? "").trim();
|
|
2163
2209
|
if (!resolvedPath)
|
|
@@ -2384,10 +2430,54 @@ async function resolveWorkerpalDockerProbe(cwd, env, platform = process.platform
|
|
|
2384
2430
|
}
|
|
2385
2431
|
var WORKERPAL_SANDBOX_RUNTIME_TAG_LABEL = "pushpals.runtime_tag";
|
|
2386
2432
|
var WORKERPAL_SANDBOX_COMPONENT_LABEL = "pushpals.component=workerpals-sandbox";
|
|
2433
|
+
var WORKERPAL_WARM_COMPONENT_LABEL = "pushpals.component=workerpals-warm";
|
|
2387
2434
|
function resolveConfiguredDockerExecutable(env, platform = process.platform) {
|
|
2388
2435
|
const configured = String(env.PUSHPALS_DOCKER_BIN_ABSOLUTE ?? env.PUSHPALS_DOCKER_BIN ?? (platform === "win32" ? "docker.exe" : "docker")).trim();
|
|
2389
2436
|
return configured || (platform === "win32" ? "docker.exe" : "docker");
|
|
2390
2437
|
}
|
|
2438
|
+
async function cleanupLingeringWorkerpalWarmContainers(opts) {
|
|
2439
|
+
const runCommandWithEnvFn = opts.runCommandWithEnvFn ?? runCommandWithEnv;
|
|
2440
|
+
const dockerExecutable = resolveConfiguredDockerExecutable(opts.env, opts.platform ?? process.platform);
|
|
2441
|
+
const list = await runCommandWithEnvFn([
|
|
2442
|
+
dockerExecutable,
|
|
2443
|
+
"ps",
|
|
2444
|
+
"-aq",
|
|
2445
|
+
"--filter",
|
|
2446
|
+
`label=${WORKERPAL_WARM_COMPONENT_LABEL}`,
|
|
2447
|
+
"--filter",
|
|
2448
|
+
`label=pushpals.repo=${opts.repoRoot}`
|
|
2449
|
+
], opts.repoRoot, opts.env);
|
|
2450
|
+
if (!list.ok) {
|
|
2451
|
+
const detail = list.stderr || list.stdout || `exit ${list.exitCode}`;
|
|
2452
|
+
return {
|
|
2453
|
+
ok: false,
|
|
2454
|
+
detail: `failed to inspect lingering WorkerPal warm containers: ${detail}`,
|
|
2455
|
+
removed: 0
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
const containerIds = list.stdout.split(/\s+/).map((value) => value.trim()).filter(Boolean);
|
|
2459
|
+
if (containerIds.length === 0) {
|
|
2460
|
+
return {
|
|
2461
|
+
ok: true,
|
|
2462
|
+
detail: "no lingering WorkerPal warm containers found",
|
|
2463
|
+
removed: 0
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
const remove = await runCommandWithEnvFn([dockerExecutable, "rm", "-f", ...containerIds], opts.repoRoot, opts.env);
|
|
2467
|
+
if (!remove.ok) {
|
|
2468
|
+
const detail = remove.stderr || remove.stdout || `exit ${remove.exitCode}`;
|
|
2469
|
+
return {
|
|
2470
|
+
ok: false,
|
|
2471
|
+
detail: `failed to remove lingering WorkerPal warm containers: ${detail}`,
|
|
2472
|
+
removed: 0
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
return {
|
|
2476
|
+
ok: true,
|
|
2477
|
+
detail: `removed ${containerIds.length} lingering WorkerPal warm container(s)`,
|
|
2478
|
+
removed: containerIds.length
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2391
2481
|
async function inspectDockerImageRuntimeTag(dockerExecutable, imageName, cwd, env) {
|
|
2392
2482
|
const inspect = await runCommandWithEnv([
|
|
2393
2483
|
dockerExecutable,
|
|
@@ -2704,7 +2794,7 @@ function removeCliClearTarget(target) {
|
|
|
2704
2794
|
};
|
|
2705
2795
|
}
|
|
2706
2796
|
}
|
|
2707
|
-
async function
|
|
2797
|
+
async function requestLocalRuntimeShutdown(serverUrl, repoRoot, reason) {
|
|
2708
2798
|
if (!await probeServer(serverUrl)) {
|
|
2709
2799
|
return { attempted: false, accepted: false };
|
|
2710
2800
|
}
|
|
@@ -2721,7 +2811,7 @@ async function requestLocalRuntimeShutdownForClear(serverUrl, repoRoot) {
|
|
|
2721
2811
|
const response = await fetchWithTimeout(`${serverUrl}/admin/shutdown`, {
|
|
2722
2812
|
method: "POST",
|
|
2723
2813
|
headers: { "Content-Type": "application/json" },
|
|
2724
|
-
body: JSON.stringify({ reason
|
|
2814
|
+
body: JSON.stringify({ reason })
|
|
2725
2815
|
}, 5000);
|
|
2726
2816
|
if (!response.ok) {
|
|
2727
2817
|
const detail = await response.text().catch(() => "");
|
|
@@ -2742,7 +2832,7 @@ async function requestLocalRuntimeShutdownForClear(serverUrl, repoRoot) {
|
|
|
2742
2832
|
}
|
|
2743
2833
|
async function clearPushpalsState(opts) {
|
|
2744
2834
|
console.log("[pushpals] Clear requested. Removing repo-local PushPals state.");
|
|
2745
|
-
const shutdown = await
|
|
2835
|
+
const shutdown = await requestLocalRuntimeShutdown(opts.serverUrl, opts.repoRoot, "pushpals --clear");
|
|
2746
2836
|
if (shutdown.attempted && shutdown.accepted) {
|
|
2747
2837
|
console.log("[pushpals] Local runtime shutdown accepted; waiting for services to exit...");
|
|
2748
2838
|
await Bun.sleep(1500);
|
|
@@ -3873,12 +3963,47 @@ async function main() {
|
|
|
3873
3963
|
let autoStartedServices = [];
|
|
3874
3964
|
let pushpalsLogPath;
|
|
3875
3965
|
let resolvedRuntimeTagForAutoStart = preparedRuntime.runtimeTag || parsed.runtimeTag || "";
|
|
3966
|
+
const cleanupWorkerpalWarmContainersIfNeeded = async (phase) => {
|
|
3967
|
+
if (workerpalDockerPrecheck.status === "failed")
|
|
3968
|
+
return;
|
|
3969
|
+
if (!config.remotebuddy.autoSpawnWorkerpals || !config.remotebuddy.workerpalDocker || !config.remotebuddy.workerpalRequireDocker) {
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3972
|
+
const cleanup = await cleanupLingeringWorkerpalWarmContainers({
|
|
3973
|
+
repoRoot,
|
|
3974
|
+
env: workerpalDockerPrecheck.env
|
|
3975
|
+
});
|
|
3976
|
+
if (!cleanup.ok) {
|
|
3977
|
+
console.warn(`[pushpals] WorkerPal warm-container cleanup warning (${phase}): ${cleanup.detail}`);
|
|
3978
|
+
return;
|
|
3979
|
+
}
|
|
3980
|
+
if (cleanup.removed > 0) {
|
|
3981
|
+
console.log(`[pushpals] ${cleanup.detail} (${phase}).`);
|
|
3982
|
+
}
|
|
3983
|
+
};
|
|
3876
3984
|
const stopAutoStartedServices = () => {
|
|
3877
3985
|
if (autoStartedServices.length === 0)
|
|
3878
3986
|
return;
|
|
3879
3987
|
stopRuntimeServices(autoStartedServices);
|
|
3880
3988
|
autoStartedServices = [];
|
|
3881
3989
|
};
|
|
3990
|
+
const stopAutoStartedServicesGracefully = async (reason) => {
|
|
3991
|
+
if (autoStartedServices.length === 0)
|
|
3992
|
+
return;
|
|
3993
|
+
const services = autoStartedServices;
|
|
3994
|
+
autoStartedServices = [];
|
|
3995
|
+
const shutdown = await requestLocalRuntimeShutdown(serverUrl, repoRoot, reason);
|
|
3996
|
+
if (shutdown.attempted && shutdown.accepted) {
|
|
3997
|
+
console.log("[pushpals] Local runtime shutdown accepted; waiting for services to exit...");
|
|
3998
|
+
await Bun.sleep(1500);
|
|
3999
|
+
} else if (shutdown.attempted) {
|
|
4000
|
+
console.warn(`[pushpals] Local runtime shutdown request was not accepted${shutdown.detail ? `: ${shutdown.detail}` : "."}`);
|
|
4001
|
+
} else if (shutdown.detail) {
|
|
4002
|
+
console.warn(`[pushpals] ${shutdown.detail}`);
|
|
4003
|
+
}
|
|
4004
|
+
await stopRuntimeServicesGracefully(services);
|
|
4005
|
+
await cleanupWorkerpalWarmContainersIfNeeded("cli shutdown");
|
|
4006
|
+
};
|
|
3882
4007
|
let serverHealthy = await probeServer(serverUrl);
|
|
3883
4008
|
const serverWasAlreadyHealthy = serverHealthy;
|
|
3884
4009
|
if (!serverHealthy && workerpalDockerPrecheck.status === "failed") {
|
|
@@ -3906,6 +4031,7 @@ async function main() {
|
|
|
3906
4031
|
detail: `No connected RemoteBuddy session consumer found for session ${sessionId}`
|
|
3907
4032
|
};
|
|
3908
4033
|
if (!serverHealthy) {
|
|
4034
|
+
await cleanupWorkerpalWarmContainersIfNeeded("startup preflight");
|
|
3909
4035
|
if (!parsed.noAutoStart) {
|
|
3910
4036
|
try {
|
|
3911
4037
|
const startedRuntime = await autoStartRuntimeServices({
|
|
@@ -4046,26 +4172,36 @@ ${line}
|
|
|
4046
4172
|
console.log(line);
|
|
4047
4173
|
};
|
|
4048
4174
|
const streamTask = parsed.noStream ? Promise.resolve() : parsed.runtimeOnly ? Promise.resolve() : runSessionStream(serverUrl, activeSessionId, cliClient, printIncoming, streamAbort.signal);
|
|
4049
|
-
let
|
|
4175
|
+
let stopPromise = null;
|
|
4050
4176
|
const requestStop = () => {
|
|
4051
|
-
if (
|
|
4052
|
-
return;
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
rl
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4177
|
+
if (stopPromise)
|
|
4178
|
+
return stopPromise;
|
|
4179
|
+
stopPromise = (async () => {
|
|
4180
|
+
console.log("[pushpals] Shutting down CLI session...");
|
|
4181
|
+
streamAbort.abort();
|
|
4182
|
+
const activeRl = rl;
|
|
4183
|
+
rl = null;
|
|
4184
|
+
if (activeRl)
|
|
4185
|
+
activeRl.close();
|
|
4186
|
+
try {
|
|
4187
|
+
monitoringHub?.stop();
|
|
4188
|
+
} catch {}
|
|
4189
|
+
if (autoStartedServices.length > 0) {
|
|
4190
|
+
console.log("[pushpals] Stopping embedded runtime services...");
|
|
4191
|
+
}
|
|
4192
|
+
await stopAutoStartedServicesGracefully("pushpals CLI exit");
|
|
4193
|
+
})();
|
|
4194
|
+
return stopPromise;
|
|
4065
4195
|
};
|
|
4066
|
-
process.once("SIGINT",
|
|
4067
|
-
|
|
4068
|
-
|
|
4196
|
+
process.once("SIGINT", () => {
|
|
4197
|
+
requestStop();
|
|
4198
|
+
});
|
|
4199
|
+
process.once("SIGTERM", () => {
|
|
4200
|
+
requestStop();
|
|
4201
|
+
});
|
|
4202
|
+
process.once("exit", () => {
|
|
4203
|
+
stopAutoStartedServices();
|
|
4204
|
+
});
|
|
4069
4205
|
if (parsed.runtimeOnly) {
|
|
4070
4206
|
console.log("[pushpals] Runtime-only mode is active. Send `exit` on stdin or terminate the process to stop.");
|
|
4071
4207
|
await new Promise((resolveStop) => {
|
|
@@ -4095,7 +4231,7 @@ ${line}
|
|
|
4095
4231
|
finish();
|
|
4096
4232
|
});
|
|
4097
4233
|
});
|
|
4098
|
-
requestStop();
|
|
4234
|
+
await requestStop();
|
|
4099
4235
|
await Promise.race([streamTask, Bun.sleep(2000)]);
|
|
4100
4236
|
return;
|
|
4101
4237
|
}
|
|
@@ -4113,7 +4249,7 @@ ${line}
|
|
|
4113
4249
|
continue;
|
|
4114
4250
|
}
|
|
4115
4251
|
if (isCliExitCommand(text)) {
|
|
4116
|
-
requestStop();
|
|
4252
|
+
await requestStop();
|
|
4117
4253
|
break;
|
|
4118
4254
|
}
|
|
4119
4255
|
if (text === "/hub") {
|
|
@@ -4153,7 +4289,7 @@ ${line}
|
|
|
4153
4289
|
}
|
|
4154
4290
|
rl.prompt();
|
|
4155
4291
|
}
|
|
4156
|
-
requestStop();
|
|
4292
|
+
await requestStop();
|
|
4157
4293
|
await Promise.race([streamTask, Bun.sleep(2000)]);
|
|
4158
4294
|
}
|
|
4159
4295
|
if (import.meta.main) {
|
|
@@ -4192,6 +4328,7 @@ export {
|
|
|
4192
4328
|
ensureWorkerpalDockerImageReady,
|
|
4193
4329
|
downloadRuntimeAssetsFromSourceTag,
|
|
4194
4330
|
copyTrackedRepoPath,
|
|
4331
|
+
cleanupLingeringWorkerpalWarmContainers,
|
|
4195
4332
|
bundledMonitoringHubNeedsRefresh,
|
|
4196
4333
|
buildWorkerpalSandboxPaths,
|
|
4197
4334
|
buildServiceStopCommand,
|
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@ status_heartbeat_ms = 120000
|
|
|
40
40
|
workerpal_online_ttl_ms = 15000
|
|
41
41
|
wait_for_workerpal_ms = 15000
|
|
42
42
|
auto_spawn_workerpals = true
|
|
43
|
-
max_workerpals =
|
|
43
|
+
max_workerpals = 3
|
|
44
44
|
workerpal_startup_timeout_ms = 10000
|
|
45
45
|
workerpal_docker = true
|
|
46
46
|
workerpal_require_docker = true
|
|
@@ -143,8 +143,8 @@ docker_warm_max_attempts = 3
|
|
|
143
143
|
docker_warm_retry_backoff_ms = 2000
|
|
144
144
|
docker_job_max_attempts = 2
|
|
145
145
|
docker_job_retry_backoff_ms = 3000
|
|
146
|
-
docker_warm_memory_mb =
|
|
147
|
-
docker_warm_cpus =
|
|
146
|
+
docker_warm_memory_mb = 1024
|
|
147
|
+
docker_warm_cpus = 1
|
|
148
148
|
file_modifying_jobs = ["task.execute"]
|
|
149
149
|
output_max_chars = 196608
|
|
150
150
|
output_max_lines = 600
|
|
@@ -198,7 +198,7 @@ session_id = "workerpals-dev"
|
|
|
198
198
|
[workerpals.openai_codex]
|
|
199
199
|
timeout_ms = 7200000
|
|
200
200
|
progress_log_interval_s = 30
|
|
201
|
-
reasoning_effort = "
|
|
201
|
+
reasoning_effort = "high"
|
|
202
202
|
approval_policy = "never"
|
|
203
203
|
sandbox = "workspace-write"
|
|
204
204
|
color = "never"
|
|
@@ -12,7 +12,7 @@ model = "gpt-5.4"
|
|
|
12
12
|
codex_auth_mode = "chatgpt"
|
|
13
13
|
codex_bin = "bun x --yes @openai/codex"
|
|
14
14
|
codex_timeout_ms = 120000
|
|
15
|
-
reasoning_effort = "
|
|
15
|
+
reasoning_effort = "high"
|
|
16
16
|
|
|
17
17
|
[remotebuddy.llm]
|
|
18
18
|
backend = "openai_codex"
|
|
@@ -20,10 +20,10 @@ model = "gpt-5.4"
|
|
|
20
20
|
codex_auth_mode = "chatgpt"
|
|
21
21
|
codex_bin = "bun x --yes @openai/codex"
|
|
22
22
|
codex_timeout_ms = 120000
|
|
23
|
-
reasoning_effort = "
|
|
23
|
+
reasoning_effort = "high"
|
|
24
24
|
|
|
25
25
|
[remotebuddy]
|
|
26
|
-
max_workerpals =
|
|
26
|
+
max_workerpals = 3
|
|
27
27
|
crash_restart_enabled = true
|
|
28
28
|
crash_restart_max_restarts = 3
|
|
29
29
|
crash_restart_backoff_ms = 3000
|
|
@@ -46,7 +46,7 @@ model = "gpt-5.4"
|
|
|
46
46
|
codex_auth_mode = "chatgpt"
|
|
47
47
|
codex_bin = "bun x --yes @openai/codex"
|
|
48
48
|
codex_timeout_ms = 120000
|
|
49
|
-
reasoning_effort = "
|
|
49
|
+
reasoning_effort = "high"
|
|
50
50
|
|
|
51
51
|
[workerpals]
|
|
52
52
|
executor = "openai_codex"
|
|
@@ -59,8 +59,8 @@ openhands_stuck_guard_explore_limit = 18
|
|
|
59
59
|
openhands_stuck_guard_min_elapsed_ms = 180000
|
|
60
60
|
openhands_stuck_guard_broad_scan_limit = 2
|
|
61
61
|
openhands_stuck_guard_no_progress_max_ms = 300000
|
|
62
|
-
docker_warm_memory_mb =
|
|
63
|
-
docker_warm_cpus =
|
|
62
|
+
docker_warm_memory_mb = 1024
|
|
63
|
+
docker_warm_cpus = 1
|
|
64
64
|
file_modifying_jobs = ["task.execute"]
|
|
65
65
|
output_max_chars = 196608
|
|
66
66
|
output_max_lines = 600
|
|
@@ -92,7 +92,7 @@ bin = "bun x --yes @openai/codex"
|
|
|
92
92
|
timeout_ms = 7200000
|
|
93
93
|
progress_log_interval_s = 30
|
|
94
94
|
# timeout_s = 120 # optional; if set, overrides timeout_ms
|
|
95
|
-
reasoning_effort = "
|
|
95
|
+
reasoning_effort = "high"
|
|
96
96
|
approval_policy = "never"
|
|
97
97
|
sandbox = "workspace-write"
|
|
98
98
|
color = "never"
|
|
@@ -83,6 +83,18 @@ _VALID_AUTH_MODES = {"auto", "api_key", "chatgpt"}
|
|
|
83
83
|
_VALID_REASONING_EFFORTS = {"low", "medium", "high", "xhigh"}
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def _model_supports_xhigh_reasoning(model: str) -> bool:
|
|
87
|
+
normalized = str(model or "").strip().lower()
|
|
88
|
+
if not normalized:
|
|
89
|
+
return False
|
|
90
|
+
return not (
|
|
91
|
+
normalized == "gpt-5.4"
|
|
92
|
+
or normalized.startswith("gpt-5.4-")
|
|
93
|
+
or normalized == "codex-1p"
|
|
94
|
+
or normalized.startswith("codex-1p-")
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
86
98
|
@dataclass(frozen=True)
|
|
87
99
|
class OpenAICodexRuntimeConfig:
|
|
88
100
|
codex_bin_json: str
|
|
@@ -152,7 +164,7 @@ class OpenAICodexRuntimeConfig:
|
|
|
152
164
|
reasoning_effort=cfg.get_str(
|
|
153
165
|
env_names=("WORKERPALS_LLM_REASONING_EFFORT", "WORKERPALS_OPENAI_CODEX_REASONING_EFFORT"),
|
|
154
166
|
config_paths=("workerpals.llm.reasoning_effort", "workerpals.openai_codex.reasoning_effort"),
|
|
155
|
-
default="
|
|
167
|
+
default="high",
|
|
156
168
|
),
|
|
157
169
|
approval_policy=cfg.get_str(
|
|
158
170
|
env_names=("WORKERPALS_OPENAI_CODEX_APPROVAL_POLICY",),
|
|
@@ -316,18 +328,23 @@ def _resolve_communicate_timeout_seconds(config: OpenAICodexRuntimeConfig) -> Op
|
|
|
316
328
|
return max(1, timeout_ms // 1000)
|
|
317
329
|
|
|
318
330
|
|
|
319
|
-
def _resolve_reasoning_effort(config: OpenAICodexRuntimeConfig) -> str:
|
|
331
|
+
def _resolve_reasoning_effort(config: OpenAICodexRuntimeConfig, model: str = DEFAULT_CODEX_MODEL) -> str:
|
|
320
332
|
raw = config.reasoning_effort
|
|
321
333
|
normalized = str(raw).strip().lower()
|
|
322
334
|
if normalized in {"extra high", "extra-high", "extrahigh", "x-high"}:
|
|
323
335
|
normalized = "xhigh"
|
|
336
|
+
if normalized == "xhigh" and not _model_supports_xhigh_reasoning(model):
|
|
337
|
+
log.info(
|
|
338
|
+
f"Downgrading workerpals.openai_codex.reasoning_effort='xhigh' to 'high' for model {model!r}."
|
|
339
|
+
)
|
|
340
|
+
return "high"
|
|
324
341
|
if normalized in _VALID_REASONING_EFFORTS:
|
|
325
342
|
return normalized
|
|
326
343
|
log.info(
|
|
327
344
|
"Invalid workerpals.openai_codex.reasoning_effort="
|
|
328
|
-
f"{raw!r}; using default '
|
|
345
|
+
f"{raw!r}; using default 'high'. Allowed: low, medium, high, xhigh."
|
|
329
346
|
)
|
|
330
|
-
return "
|
|
347
|
+
return "high"
|
|
331
348
|
|
|
332
349
|
|
|
333
350
|
def _resolve_progress_log_interval_seconds(config: OpenAICodexRuntimeConfig) -> int:
|
|
@@ -1006,7 +1023,7 @@ def _run_codex_task(
|
|
|
1006
1023
|
)
|
|
1007
1024
|
# JSON event output is noisy by default; prefer plain text + output-last-message.
|
|
1008
1025
|
use_json = runtime_config.json_output
|
|
1009
|
-
reasoning_effort = _resolve_reasoning_effort(runtime_config)
|
|
1026
|
+
reasoning_effort = _resolve_reasoning_effort(runtime_config, model)
|
|
1010
1027
|
communicate_timeout_s = _resolve_communicate_timeout_seconds(runtime_config)
|
|
1011
1028
|
prompt = _build_instruction(instruction, supplemental_guidance)
|
|
1012
1029
|
baseline_changes = summarize_git_changes(repo)
|
|
@@ -60,17 +60,26 @@ class OpenAICodexRuntimeConfigTests(unittest.TestCase):
|
|
|
60
60
|
self.assertEqual(cfg.approval_policy, "never")
|
|
61
61
|
self.assertEqual(cfg.sandbox, "workspace-write")
|
|
62
62
|
self.assertEqual(cfg.color, "never")
|
|
63
|
-
self.assertEqual(cfg.reasoning_effort, "
|
|
63
|
+
self.assertEqual(cfg.reasoning_effort, "high")
|
|
64
64
|
self.assertFalse(cfg.json_output)
|
|
65
65
|
|
|
66
|
-
def
|
|
66
|
+
def test_reasoning_effort_caps_extra_high_for_gpt_5_4(self) -> None:
|
|
67
67
|
cfg = OpenAICodexRuntimeConfig.from_sources(
|
|
68
68
|
SettingsResolver(
|
|
69
69
|
env={"WORKERPALS_OPENAI_CODEX_REASONING_EFFORT": "extra high"},
|
|
70
70
|
config_loader=lambda: {},
|
|
71
71
|
),
|
|
72
72
|
)
|
|
73
|
-
self.assertEqual(_resolve_reasoning_effort(cfg), "
|
|
73
|
+
self.assertEqual(_resolve_reasoning_effort(cfg), "high")
|
|
74
|
+
|
|
75
|
+
def test_reasoning_effort_preserves_extra_high_for_future_models(self) -> None:
|
|
76
|
+
cfg = OpenAICodexRuntimeConfig.from_sources(
|
|
77
|
+
SettingsResolver(
|
|
78
|
+
env={"WORKERPALS_OPENAI_CODEX_REASONING_EFFORT": "extra high"},
|
|
79
|
+
config_loader=lambda: {},
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
self.assertEqual(_resolve_reasoning_effort(cfg, model="gpt-6-preview"), "xhigh")
|
|
74
83
|
|
|
75
84
|
def test_runtime_config_prefers_explicit_config_dir_override(self) -> None:
|
|
76
85
|
import executor_base
|
|
@@ -1048,6 +1048,7 @@ export class DockerExecutor {
|
|
|
1048
1048
|
|
|
1049
1049
|
const worktreeRelPath = relative(this.options.repo, worktreePath).replace(/\\/g, "/");
|
|
1050
1050
|
const containerWorktreePath = `/repo/${worktreeRelPath}`;
|
|
1051
|
+
await this.waitForWorktreePathInWarmContainer(containerWorktreePath);
|
|
1051
1052
|
|
|
1052
1053
|
const args: string[] = [
|
|
1053
1054
|
"exec",
|
|
@@ -1124,6 +1125,26 @@ export class DockerExecutor {
|
|
|
1124
1125
|
return result;
|
|
1125
1126
|
}
|
|
1126
1127
|
|
|
1128
|
+
private async waitForWorktreePathInWarmContainer(
|
|
1129
|
+
containerWorktreePath: string,
|
|
1130
|
+
timeoutMs = 5_000,
|
|
1131
|
+
): Promise<void> {
|
|
1132
|
+
const deadline = Date.now() + timeoutMs;
|
|
1133
|
+
let lastDetail = "";
|
|
1134
|
+
const command = `test -d ${shellSingleQuote(containerWorktreePath)}`;
|
|
1135
|
+
while (Date.now() < deadline) {
|
|
1136
|
+
const result = await this.runWarmShell(command);
|
|
1137
|
+
if (result.ok) return;
|
|
1138
|
+
lastDetail = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
1139
|
+
await this.sleep(100);
|
|
1140
|
+
}
|
|
1141
|
+
throw new Error(
|
|
1142
|
+
`worktree path not visible inside warm container after ${timeoutMs}ms: ${containerWorktreePath}${
|
|
1143
|
+
lastDetail ? ` (${lastDetail})` : ""
|
|
1144
|
+
}`,
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1127
1148
|
private normalizeProvider(raw: string): string {
|
|
1128
1149
|
const value = raw.trim().toLowerCase();
|
|
1129
1150
|
if (!value) return "auto";
|
|
@@ -1450,6 +1471,9 @@ export class DockerExecutor {
|
|
|
1450
1471
|
/\btemporary failure\b/i,
|
|
1451
1472
|
/\bopenhands wrapper timed out\b/i,
|
|
1452
1473
|
/\bjob timed out in docker executor\b/i,
|
|
1474
|
+
/\bworktree path not visible inside warm container\b/i,
|
|
1475
|
+
/\bchdir to cwd\b/i,
|
|
1476
|
+
/\bunable to start container process\b/i,
|
|
1453
1477
|
];
|
|
1454
1478
|
return transientPatterns.some((pattern) => pattern.test(text));
|
|
1455
1479
|
}
|
|
@@ -2024,17 +2024,19 @@ export function shouldUseCodexCliForExecutor(executor: string): boolean {
|
|
|
2024
2024
|
|
|
2025
2025
|
function normalizeCodexReasoningEffort(
|
|
2026
2026
|
value: unknown,
|
|
2027
|
+
model = "",
|
|
2027
2028
|
): "low" | "medium" | "high" | "xhigh" {
|
|
2028
2029
|
const normalized = String(value ?? "")
|
|
2029
2030
|
.trim()
|
|
2030
2031
|
.toLowerCase();
|
|
2032
|
+
const supportsExtraHigh = !/^(gpt-5\.4(?:$|-)|codex-1p(?:$|-))/i.test(String(model ?? "").trim());
|
|
2031
2033
|
if (
|
|
2032
2034
|
normalized === "low" ||
|
|
2033
2035
|
normalized === "medium" ||
|
|
2034
2036
|
normalized === "high" ||
|
|
2035
2037
|
normalized === "xhigh"
|
|
2036
2038
|
) {
|
|
2037
|
-
return normalized;
|
|
2039
|
+
return normalized === "xhigh" && !supportsExtraHigh ? "high" : normalized;
|
|
2038
2040
|
}
|
|
2039
2041
|
if (
|
|
2040
2042
|
normalized === "extra high" ||
|
|
@@ -2042,9 +2044,9 @@ function normalizeCodexReasoningEffort(
|
|
|
2042
2044
|
normalized === "extrahigh" ||
|
|
2043
2045
|
normalized === "x-high"
|
|
2044
2046
|
) {
|
|
2045
|
-
return "xhigh";
|
|
2047
|
+
return supportsExtraHigh ? "xhigh" : "high";
|
|
2046
2048
|
}
|
|
2047
|
-
return "
|
|
2049
|
+
return "high";
|
|
2048
2050
|
}
|
|
2049
2051
|
|
|
2050
2052
|
async function generateCommitMessageFromDiff(
|
|
@@ -2105,6 +2107,7 @@ async function generateCommitMessageFromDiffViaCodex(
|
|
|
2105
2107
|
})();
|
|
2106
2108
|
const reasoningEffort = normalizeCodexReasoningEffort(
|
|
2107
2109
|
runtimeConfig.workerpals.llm.reasoningEffort,
|
|
2110
|
+
model,
|
|
2108
2111
|
);
|
|
2109
2112
|
const tmpOutputPath = resolve(
|
|
2110
2113
|
Bun.env.TEMP || Bun.env.TMP || Bun.env.TMPDIR || "/tmp",
|
|
@@ -40,7 +40,7 @@ status_heartbeat_ms = 120000
|
|
|
40
40
|
workerpal_online_ttl_ms = 15000
|
|
41
41
|
wait_for_workerpal_ms = 15000
|
|
42
42
|
auto_spawn_workerpals = true
|
|
43
|
-
max_workerpals =
|
|
43
|
+
max_workerpals = 3
|
|
44
44
|
workerpal_startup_timeout_ms = 10000
|
|
45
45
|
workerpal_docker = true
|
|
46
46
|
workerpal_require_docker = true
|
|
@@ -143,8 +143,8 @@ docker_warm_max_attempts = 3
|
|
|
143
143
|
docker_warm_retry_backoff_ms = 2000
|
|
144
144
|
docker_job_max_attempts = 2
|
|
145
145
|
docker_job_retry_backoff_ms = 3000
|
|
146
|
-
docker_warm_memory_mb =
|
|
147
|
-
docker_warm_cpus =
|
|
146
|
+
docker_warm_memory_mb = 1024
|
|
147
|
+
docker_warm_cpus = 1
|
|
148
148
|
file_modifying_jobs = ["task.execute"]
|
|
149
149
|
output_max_chars = 196608
|
|
150
150
|
output_max_lines = 600
|
|
@@ -198,7 +198,7 @@ session_id = "workerpals-dev"
|
|
|
198
198
|
[workerpals.openai_codex]
|
|
199
199
|
timeout_ms = 7200000
|
|
200
200
|
progress_log_interval_s = 30
|
|
201
|
-
reasoning_effort = "
|
|
201
|
+
reasoning_effort = "high"
|
|
202
202
|
approval_policy = "never"
|
|
203
203
|
sandbox = "workspace-write"
|
|
204
204
|
color = "never"
|
|
@@ -12,7 +12,7 @@ model = "gpt-5.4"
|
|
|
12
12
|
codex_auth_mode = "chatgpt"
|
|
13
13
|
codex_bin = "bun x --yes @openai/codex"
|
|
14
14
|
codex_timeout_ms = 120000
|
|
15
|
-
reasoning_effort = "
|
|
15
|
+
reasoning_effort = "high"
|
|
16
16
|
|
|
17
17
|
[remotebuddy.llm]
|
|
18
18
|
backend = "openai_codex"
|
|
@@ -20,10 +20,10 @@ model = "gpt-5.4"
|
|
|
20
20
|
codex_auth_mode = "chatgpt"
|
|
21
21
|
codex_bin = "bun x --yes @openai/codex"
|
|
22
22
|
codex_timeout_ms = 120000
|
|
23
|
-
reasoning_effort = "
|
|
23
|
+
reasoning_effort = "high"
|
|
24
24
|
|
|
25
25
|
[remotebuddy]
|
|
26
|
-
max_workerpals =
|
|
26
|
+
max_workerpals = 3
|
|
27
27
|
crash_restart_enabled = true
|
|
28
28
|
crash_restart_max_restarts = 3
|
|
29
29
|
crash_restart_backoff_ms = 3000
|
|
@@ -46,7 +46,7 @@ model = "gpt-5.4"
|
|
|
46
46
|
codex_auth_mode = "chatgpt"
|
|
47
47
|
codex_bin = "bun x --yes @openai/codex"
|
|
48
48
|
codex_timeout_ms = 120000
|
|
49
|
-
reasoning_effort = "
|
|
49
|
+
reasoning_effort = "high"
|
|
50
50
|
|
|
51
51
|
[workerpals]
|
|
52
52
|
executor = "openai_codex"
|
|
@@ -59,8 +59,8 @@ openhands_stuck_guard_explore_limit = 18
|
|
|
59
59
|
openhands_stuck_guard_min_elapsed_ms = 180000
|
|
60
60
|
openhands_stuck_guard_broad_scan_limit = 2
|
|
61
61
|
openhands_stuck_guard_no_progress_max_ms = 300000
|
|
62
|
-
docker_warm_memory_mb =
|
|
63
|
-
docker_warm_cpus =
|
|
62
|
+
docker_warm_memory_mb = 1024
|
|
63
|
+
docker_warm_cpus = 1
|
|
64
64
|
file_modifying_jobs = ["task.execute"]
|
|
65
65
|
output_max_chars = 196608
|
|
66
66
|
output_max_lines = 600
|
|
@@ -92,7 +92,7 @@ bin = "bun x --yes @openai/codex"
|
|
|
92
92
|
timeout_ms = 7200000
|
|
93
93
|
progress_log_interval_s = 30
|
|
94
94
|
# timeout_s = 120 # optional; if set, overrides timeout_ms
|
|
95
|
-
reasoning_effort = "
|
|
95
|
+
reasoning_effort = "high"
|
|
96
96
|
approval_policy = "never"
|
|
97
97
|
sandbox = "workspace-write"
|
|
98
98
|
color = "never"
|
|
@@ -1508,7 +1508,10 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
|
|
|
1508
1508
|
asBoolean(remoteNode.auto_spawn_workerpals, true),
|
|
1509
1509
|
maxWorkerpals: Math.max(
|
|
1510
1510
|
1,
|
|
1511
|
-
asInt(
|
|
1511
|
+
asInt(
|
|
1512
|
+
parseIntEnv("REMOTEBUDDY_MAX_WORKERPALS") ?? remoteNode.max_workerpals,
|
|
1513
|
+
20,
|
|
1514
|
+
),
|
|
1512
1515
|
),
|
|
1513
1516
|
workerpalStartupTimeoutMs: Math.max(
|
|
1514
1517
|
1_000,
|