@pushpalsdev/cli 1.1.5 → 1.1.6

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.
@@ -37,6 +37,7 @@ var DEFAULT_SERVICE_MANAGER_MAX_RESTART_ATTEMPTS = 4;
37
37
  var DEFAULT_SERVICE_MANAGER_STABLE_WINDOW_MS = 60000;
38
38
  var DEFAULT_SERVICE_MANAGER_BASE_BACKOFF_MS = 2000;
39
39
  var DEFAULT_SERVICE_MANAGER_MAX_BACKOFF_MS = 30000;
40
+ var WINDOWS_TASKKILL_TIMEOUT_MS = 5000;
40
41
  function formatEmbeddedRuntimeHealthLines(health) {
41
42
  if (!health)
42
43
  return [];
@@ -165,7 +166,9 @@ class ServiceManager {
165
166
  Bun.spawnSync(["taskkill", "/PID", String(pid), "/T", "/F"], {
166
167
  stdin: "ignore",
167
168
  stdout: "ignore",
168
- stderr: "ignore"
169
+ stderr: "ignore",
170
+ timeout: WINDOWS_TASKKILL_TIMEOUT_MS,
171
+ killSignal: "SIGKILL"
169
172
  });
170
173
  } else {
171
174
  existing.proc.kill("SIGKILL");
@@ -226,7 +229,9 @@ class ServiceManager {
226
229
  Bun.spawnSync(["taskkill", "/PID", String(pid), "/T", "/F"], {
227
230
  stdin: "ignore",
228
231
  stdout: "ignore",
229
- stderr: "ignore"
232
+ stderr: "ignore",
233
+ timeout: WINDOWS_TASKKILL_TIMEOUT_MS,
234
+ killSignal: "SIGKILL"
230
235
  });
231
236
  } else {
232
237
  service.proc.kill("SIGKILL");
@@ -457,7 +462,8 @@ var DEFAULT_WORKERPALS_OUTPUT_MAX_CHARS = 192 * 1024;
457
462
  var DEFAULT_WORKERPALS_OUTPUT_MAX_LINES = 600;
458
463
  var DEFAULT_WORKERPALS_OUTPUT_MAX_HEAD_LINES = 120;
459
464
  var DEFAULT_WORKERPALS_QUALITY_VALIDATION_STEP_TIMEOUT_MS = 180000;
460
- var DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS = 45000;
465
+ var DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS = 90000;
466
+ var DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_BEHAVIOR = "retry_once";
461
467
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_DIFF_CHARS = 16000;
462
468
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_VALIDATION_OUTPUT_CHARS = 8000;
463
469
  var DEFAULT_WORKERPALS_EXECUTOR = "openai_codex";
@@ -536,6 +542,13 @@ function asString(value, fallback) {
536
542
  return value.trim();
537
543
  return fallback;
538
544
  }
545
+ function asQualityCriticTimeoutBehavior(value) {
546
+ const normalized = String(value ?? "").trim().toLowerCase().replace(/-/g, "_");
547
+ if (normalized === "skip" || normalized === "retry_once" || normalized === "block") {
548
+ return normalized;
549
+ }
550
+ return DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_BEHAVIOR;
551
+ }
539
552
  function asBoolean(value, fallback) {
540
553
  if (typeof value === "boolean")
541
554
  return value;
@@ -830,6 +843,7 @@ function loadPushPalsConfig(options = {}) {
830
843
  const workerOutputMaxHeadLines = Math.max(1, Math.min(workerOutputMaxLines, asInt(parseIntEnv("WORKERPALS_OUTPUT_MAX_HEAD_LINES") ?? workerNode.output_max_head_lines, DEFAULT_WORKERPALS_OUTPUT_MAX_HEAD_LINES)));
831
844
  const workerQualityValidationStepTimeoutMs = Math.max(1000, asInt(parseIntEnv("WORKERPALS_QUALITY_VALIDATION_STEP_TIMEOUT_MS") ?? workerNode.quality_validation_step_timeout_ms, DEFAULT_WORKERPALS_QUALITY_VALIDATION_STEP_TIMEOUT_MS));
832
845
  const workerQualityCriticTimeoutMs = Math.max(1000, asInt(parseIntEnv("WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS") ?? workerNode.quality_critic_timeout_ms, DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS));
846
+ const workerQualityCriticTimeoutBehavior = asQualityCriticTimeoutBehavior(process.env.WORKERPALS_QUALITY_CRITIC_TIMEOUT_BEHAVIOR ?? workerNode.quality_critic_timeout_behavior);
833
847
  const workerQualitySoftPassOnExhausted = parseBoolEnv("WORKERPALS_QUALITY_SOFT_PASS_ON_EXHAUSTED") ?? asBoolean(workerNode.quality_soft_pass_on_exhausted, true);
834
848
  const workerQualityScopeGateEnabled = parseBoolEnv("WORKERPALS_QUALITY_SCOPE_GATE_ENABLED") ?? asBoolean(workerNode.quality_scope_gate_enabled, true);
835
849
  const workerQualityValidationGateEnabled = parseBoolEnv("WORKERPALS_QUALITY_VALIDATION_GATE_ENABLED") ?? asBoolean(workerNode.quality_validation_gate_enabled, true);
@@ -843,6 +857,7 @@ function loadPushPalsConfig(options = {}) {
843
857
  return DEFAULT_WORKERPALS_QUALITY_CRITIC_MIN_SCORE;
844
858
  return Math.max(0, Math.min(10, parsed));
845
859
  })();
860
+ const workerQualityCriticModel = firstNonEmpty(process.env.WORKERPALS_QUALITY_CRITIC_MODEL, asString(workerNode.quality_critic_model, ""), "");
846
861
  const workerQualityCriticMaxDiffChars = Math.max(256, Math.min(524288, asInt(parseIntEnv("WORKERPALS_QUALITY_CRITIC_MAX_DIFF_CHARS") ?? workerNode.quality_critic_max_diff_chars, DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_DIFF_CHARS)));
847
862
  const workerQualityCriticMaxValidationOutputChars = Math.max(256, Math.min(524288, asInt(parseIntEnv("WORKERPALS_QUALITY_CRITIC_MAX_VALIDATION_OUTPUT_CHARS") ?? workerNode.quality_critic_max_validation_output_chars, DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_VALIDATION_OUTPUT_CHARS)));
848
863
  const workerExecutorResultPrefix = (() => {
@@ -1132,8 +1147,10 @@ function loadPushPalsConfig(options = {}) {
1132
1147
  qualityPublishGateEnabled: workerQualityPublishGateEnabled,
1133
1148
  qualityValidationStepTimeoutMs: workerQualityValidationStepTimeoutMs,
1134
1149
  qualityCriticTimeoutMs: workerQualityCriticTimeoutMs,
1150
+ qualityCriticTimeoutBehavior: workerQualityCriticTimeoutBehavior,
1135
1151
  qualitySoftPassOnExhausted: workerQualitySoftPassOnExhausted,
1136
1152
  qualityCriticMinScore: workerQualityCriticMinScore,
1153
+ qualityCriticModel: workerQualityCriticModel,
1137
1154
  qualityCriticMaxDiffChars: workerQualityCriticMaxDiffChars,
1138
1155
  qualityCriticMaxValidationOutputChars: workerQualityCriticMaxValidationOutputChars,
1139
1156
  executorResultPrefix: workerExecutorResultPrefix,
@@ -1620,6 +1637,11 @@ var DEFAULT_RUNTIME_BOOT_TIMEOUT_MS = 90000;
1620
1637
  var DEFAULT_RUNTIME_BOOT_POLL_MS = 1000;
1621
1638
  var DEFAULT_SERVER_BOOT_TIMEOUT_MS = 20000;
1622
1639
  var DEFAULT_SERVICE_STABILITY_GRACE_MS = 4000;
1640
+ var DEFAULT_COMMAND_OUTPUT_DRAIN_TIMEOUT_MS = 2000;
1641
+ var DEFAULT_COMMAND_OUTPUT_MAX_CHARS = 512000;
1642
+ var DEFAULT_REMOTEBUDDY_SILENT_STARTUP_FALLBACK_MS = 20000;
1643
+ var WINDOWS_TASKKILL_TIMEOUT_MS2 = 5000;
1644
+ var RUNTIME_BINARY_DOWNLOAD_ATTEMPTS = 3;
1623
1645
  var DEFAULT_STARTUP_GIT_PROBE_TIMEOUT_MS = 5000;
1624
1646
  var DEFAULT_STARTUP_GIT_REMOTE_TIMEOUT_MS = 1e4;
1625
1647
  var EMBEDDED_SERVICE_RESTART_MAX_ATTEMPTS = 4;
@@ -1947,6 +1969,91 @@ async function withStartupTimeout(promise, timeoutMs, timeoutValue) {
1947
1969
  function jsonHtmlBootstrap(value) {
1948
1970
  return JSON.stringify(value).replace(/</g, "\\u003c");
1949
1971
  }
1972
+ function appendBoundedProcessOutput(existing, next, maxChars) {
1973
+ if (!next)
1974
+ return existing;
1975
+ const combined = `${existing}${next}`;
1976
+ if (combined.length <= maxChars)
1977
+ return combined;
1978
+ return combined.slice(combined.length - maxChars);
1979
+ }
1980
+ function createProcessOutputCollector(stream, maxChars = DEFAULT_COMMAND_OUTPUT_MAX_CHARS) {
1981
+ if (!stream) {
1982
+ return {
1983
+ done: Promise.resolve(""),
1984
+ snapshot: () => "",
1985
+ cancel: () => {}
1986
+ };
1987
+ }
1988
+ const reader = stream.getReader();
1989
+ const decoder = new TextDecoder;
1990
+ let output = "";
1991
+ let cancelled = false;
1992
+ let finished = false;
1993
+ const done = (async () => {
1994
+ try {
1995
+ while (true) {
1996
+ const chunk = await reader.read();
1997
+ if (chunk.done)
1998
+ break;
1999
+ output = appendBoundedProcessOutput(output, decoder.decode(chunk.value, { stream: true }), maxChars);
2000
+ }
2001
+ output = appendBoundedProcessOutput(output, decoder.decode(), maxChars);
2002
+ } catch (err) {
2003
+ if (!cancelled) {
2004
+ output = appendBoundedProcessOutput(output, `
2005
+ [pushpals] output read failed: ${String(err)}`, maxChars);
2006
+ }
2007
+ } finally {
2008
+ finished = true;
2009
+ try {
2010
+ reader.releaseLock();
2011
+ } catch {}
2012
+ }
2013
+ return output;
2014
+ })();
2015
+ return {
2016
+ done,
2017
+ snapshot: () => output,
2018
+ cancel: () => {
2019
+ if (finished || cancelled)
2020
+ return;
2021
+ cancelled = true;
2022
+ try {
2023
+ reader.cancel().catch(() => {});
2024
+ } catch {}
2025
+ }
2026
+ };
2027
+ }
2028
+ async function finishProcessOutputCollector(collector, timeoutMs = DEFAULT_COMMAND_OUTPUT_DRAIN_TIMEOUT_MS) {
2029
+ const result = await Promise.race([
2030
+ collector.done.then((text) => ({ text, timedOut: false })),
2031
+ Bun.sleep(Math.max(1, timeoutMs)).then(() => ({
2032
+ text: collector.snapshot(),
2033
+ timedOut: true
2034
+ }))
2035
+ ]);
2036
+ if (result.timedOut) {
2037
+ collector.cancel();
2038
+ }
2039
+ return result.text;
2040
+ }
2041
+ function terminateSpawnedProcessTree(proc, platform = process.platform) {
2042
+ try {
2043
+ const stopCommand = buildServiceStopCommand(proc.pid, platform);
2044
+ if (stopCommand) {
2045
+ Bun.spawnSync(stopCommand, {
2046
+ stdin: "ignore",
2047
+ stdout: "ignore",
2048
+ stderr: "ignore",
2049
+ timeout: WINDOWS_TASKKILL_TIMEOUT_MS2,
2050
+ killSignal: "SIGKILL"
2051
+ });
2052
+ return;
2053
+ }
2054
+ proc.kill("SIGKILL");
2055
+ } catch {}
2056
+ }
1950
2057
  async function runCommandWithEnv(command, cwd, env, timeoutMs) {
1951
2058
  try {
1952
2059
  const proc = Bun.spawn(command, {
@@ -1955,32 +2062,37 @@ async function runCommandWithEnv(command, cwd, env, timeoutMs) {
1955
2062
  stdout: "pipe",
1956
2063
  stderr: "pipe"
1957
2064
  });
2065
+ const stdoutCollector = createProcessOutputCollector(proc.stdout);
2066
+ const stderrCollector = createProcessOutputCollector(proc.stderr);
1958
2067
  let timedOut = false;
1959
- let timer = null;
1960
- if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
1961
- timer = setTimeout(() => {
1962
- timedOut = true;
1963
- try {
1964
- const stopCommand = buildServiceStopCommand(proc.pid, process.platform);
1965
- if (stopCommand) {
1966
- Bun.spawnSync(stopCommand, {
1967
- stdin: "ignore",
1968
- stdout: "ignore",
1969
- stderr: "ignore"
1970
- });
1971
- } else {
1972
- proc.kill("SIGKILL");
1973
- }
1974
- } catch {}
1975
- }, timeoutMs);
2068
+ const hasTimeout = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0;
2069
+ let timeout = null;
2070
+ const exitCode = await Promise.race([
2071
+ proc.exited,
2072
+ hasTimeout ? new Promise((resolveTimeout) => {
2073
+ timeout = setTimeout(() => {
2074
+ timedOut = true;
2075
+ terminateSpawnedProcessTree(proc);
2076
+ resolveTimeout(-1);
2077
+ }, timeoutMs);
2078
+ }) : new Promise(() => {})
2079
+ ]);
2080
+ if (timeout) {
2081
+ clearTimeout(timeout);
2082
+ timeout = null;
1976
2083
  }
1977
- const [stdout, stderr, exitCode] = await Promise.all([
1978
- new Response(proc.stdout).text(),
1979
- new Response(proc.stderr).text(),
1980
- proc.exited
2084
+ if (timedOut) {
2085
+ await Promise.race([
2086
+ proc.exited.catch(() => -1),
2087
+ Bun.sleep(DEFAULT_COMMAND_OUTPUT_DRAIN_TIMEOUT_MS)
2088
+ ]);
2089
+ stdoutCollector.cancel();
2090
+ stderrCollector.cancel();
2091
+ }
2092
+ const [stdout, stderr] = await Promise.all([
2093
+ finishProcessOutputCollector(stdoutCollector),
2094
+ finishProcessOutputCollector(stderrCollector)
1981
2095
  ]);
1982
- if (timer)
1983
- clearTimeout(timer);
1984
2096
  const normalizedStdout = stdout.trim();
1985
2097
  const normalizedStderr = stderr.trim();
1986
2098
  if (timedOut) {
@@ -2812,6 +2924,23 @@ function readLogTail(logPath, maxLines = 40) {
2812
2924
  function hasStandaloneBunCrashSignature(text) {
2813
2925
  return /\bpanic\(main thread\)|\bsegmentation fault\b|oh no:\s*bun has crashed\b/i.test(String(text ?? ""));
2814
2926
  }
2927
+ function hasRemoteBuddyRuntimeOutput(logText) {
2928
+ return String(logText ?? "").split(/\r?\n/).some((line) => /\[(?:stdout|stderr)\]/i.test(line));
2929
+ }
2930
+ function shouldUseRemoteBuddySilentStartupFallback(opts) {
2931
+ const platform = opts.platform ?? process.platform;
2932
+ const thresholdMs = Math.max(1, opts.thresholdMs ?? DEFAULT_REMOTEBUDDY_SILENT_STARTUP_FALLBACK_MS);
2933
+ if (platform !== "win32")
2934
+ return false;
2935
+ if (!opts.fallbackAvailable)
2936
+ return false;
2937
+ if (opts.elapsedMs < thresholdMs)
2938
+ return false;
2939
+ const logText = String(opts.logText ?? "");
2940
+ if (hasStandaloneBunCrashSignature(logText))
2941
+ return false;
2942
+ return !hasRemoteBuddyRuntimeOutput(logText);
2943
+ }
2815
2944
  function extractRemoteBuddyAutonomousEngineState(logText) {
2816
2945
  const text = String(logText ?? "");
2817
2946
  if (!text)
@@ -2840,21 +2969,33 @@ function readRemoteBuddyAutonomousEngineState(logPath) {
2840
2969
  async function downloadBinaryAsset(tag, assetName, outPath) {
2841
2970
  console.log(`[pushpals] Downloading embedded runtime binary ${assetName} from ${tag}...`);
2842
2971
  const url = `${GITHUB_RELEASE_URL}/${encodeURIComponent(tag)}/${assetName}`;
2843
- try {
2844
- const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
2845
- if (!response.ok) {
2846
- throw new Error(`Failed to download ${assetName} from ${tag} (HTTP ${response.status})`);
2847
- }
2848
- const bytes = new Uint8Array(await response.arrayBuffer());
2849
- mkdirSync(dirname(outPath), { recursive: true });
2850
- await Bun.write(outPath, bytes);
2851
- return;
2852
- } catch (err) {
2853
- const fallback = await downloadBinaryAssetWithWindowsCurlFallback(url, outPath, err);
2854
- if (fallback)
2972
+ let lastError = null;
2973
+ for (let attempt = 1;attempt <= RUNTIME_BINARY_DOWNLOAD_ATTEMPTS; attempt += 1) {
2974
+ try {
2975
+ const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
2976
+ if (!response.ok) {
2977
+ throw new Error(`Failed to download ${assetName} from ${tag} (HTTP ${response.status})`);
2978
+ }
2979
+ const bytes = new Uint8Array(await response.arrayBuffer());
2980
+ mkdirSync(dirname(outPath), { recursive: true });
2981
+ await Bun.write(outPath, bytes);
2855
2982
  return;
2856
- throw err;
2983
+ } catch (err) {
2984
+ try {
2985
+ const fallback = await downloadBinaryAssetWithWindowsCurlFallback(url, outPath, err);
2986
+ if (fallback)
2987
+ return;
2988
+ lastError = err;
2989
+ } catch (fallbackErr) {
2990
+ lastError = fallbackErr;
2991
+ }
2992
+ if (attempt < RUNTIME_BINARY_DOWNLOAD_ATTEMPTS) {
2993
+ console.warn(`[pushpals] Runtime binary download for ${assetName} failed on attempt ${attempt}/${RUNTIME_BINARY_DOWNLOAD_ATTEMPTS}; retrying...`);
2994
+ await Bun.sleep(1000 * attempt);
2995
+ }
2996
+ }
2857
2997
  }
2998
+ throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "unknown error"));
2858
2999
  }
2859
3000
  async function downloadBinaryAssetWithWindowsCurlFallback(url, outPath, cause) {
2860
3001
  if (process.platform !== "win32")
@@ -2947,7 +3088,9 @@ function stopRuntimeServices(services) {
2947
3088
  Bun.spawnSync(stopCommand, {
2948
3089
  stdin: "ignore",
2949
3090
  stdout: "ignore",
2950
- stderr: "ignore"
3091
+ stderr: "ignore",
3092
+ timeout: WINDOWS_TASKKILL_TIMEOUT_MS2,
3093
+ killSignal: "SIGKILL"
2951
3094
  });
2952
3095
  } else {
2953
3096
  service.proc.kill("SIGKILL");
@@ -2982,6 +3125,11 @@ async function stopRuntimeServicesGracefully(services, timeoutMs = 1e4) {
2982
3125
  if (running.length === 0)
2983
3126
  return;
2984
3127
  const ordered = [...running].sort((a, b) => resolveGracefulShutdownPriority(a.name) - resolveGracefulShutdownPriority(b.name));
3128
+ if (process.platform === "win32") {
3129
+ stopRuntimeServices(ordered);
3130
+ await waitForRuntimeServicesExit(ordered, Math.min(1000, timeoutMs));
3131
+ return;
3132
+ }
2985
3133
  const nonServer = ordered.filter((service) => service.name !== "server");
2986
3134
  const server = ordered.filter((service) => service.name === "server");
2987
3135
  for (const service of nonServer) {
@@ -4346,16 +4494,25 @@ async function autoStartRuntimeServices(opts) {
4346
4494
  bundlePath: sandboxPaths.remotebuddyFallbackBundlePath
4347
4495
  } : null;
4348
4496
  let remoteBuddyFallbackActivated = false;
4349
- const maybeActivateRemoteBuddyWindowsFallback = () => {
4497
+ const maybeActivateRemoteBuddyWindowsFallback = (reason = "crash") => {
4350
4498
  if (remoteBuddyFallbackActivated || !remoteBuddySourceFallback)
4351
4499
  return false;
4352
4500
  const tail = readLogTail(serviceLogPaths.remotebuddy, 120);
4353
- if (!hasStandaloneBunCrashSignature(tail))
4501
+ if (reason === "crash" && !hasStandaloneBunCrashSignature(tail))
4502
+ return false;
4503
+ if (reason === "silent_startup" && !shouldUseRemoteBuddySilentStartupFallback({
4504
+ logText: tail,
4505
+ elapsedMs: Date.now() - remoteBuddyPhaseStartedAt,
4506
+ platform: process.platform,
4507
+ fallbackAvailable: Boolean(remoteBuddySourceFallback)
4508
+ })) {
4354
4509
  return false;
4510
+ }
4355
4511
  remoteBuddyFallbackActivated = true;
4356
4512
  lastReportedRemoteBuddyAutonomyState = "unknown";
4357
- console.warn("[pushpals] Embedded RemoteBuddy standalone binary crashed on Windows; retrying with source fallback under bun.");
4358
- appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] embedded remotebuddy standalone binary crashed on Windows; retrying with source fallback under bun.");
4513
+ const fallbackReason = reason === "silent_startup" ? "produced no startup output on Windows" : "crashed on Windows";
4514
+ console.warn(`[pushpals] Embedded RemoteBuddy standalone binary ${fallbackReason}; retrying with source fallback under bun.`);
4515
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded remotebuddy standalone binary ${fallbackReason}; retrying with source fallback under bun.`);
4359
4516
  replaceService("remotebuddy", [
4360
4517
  remoteBuddySourceFallback.bunBin,
4361
4518
  remoteBuddySourceFallback.bundlePath,
@@ -4507,6 +4664,10 @@ ${tail}` : ""}`);
4507
4664
  const optionalServiceExitWarned = new Set;
4508
4665
  while (Date.now() < deadline) {
4509
4666
  reportRemoteBuddyAutonomousEngineState();
4667
+ if (maybeActivateRemoteBuddyWindowsFallback("silent_startup")) {
4668
+ await Bun.sleep(DEFAULT_RUNTIME_BOOT_POLL_MS);
4669
+ continue;
4670
+ }
4510
4671
  for (const service of serviceManager.getServices()) {
4511
4672
  if (service.exited) {
4512
4673
  if (service.name === "remotebuddy" && maybeActivateRemoteBuddyWindowsFallback()) {
@@ -5471,12 +5632,16 @@ async function main() {
5471
5632
  }
5472
5633
  process.exit(1);
5473
5634
  }
5474
- const workerpalCapacity = await waitForWorkerpalCapacity({
5635
+ const shouldProbeWorkerpalStartupCapacity = Boolean(config.remotebuddy.autoSpawnWorkerpals);
5636
+ const workerpalCapacity = shouldProbeWorkerpalStartupCapacity ? await waitForWorkerpalCapacity({
5475
5637
  serverUrl,
5476
5638
  timeoutMs: resolveWorkerpalStartupReadinessProbeTimeoutMs(config),
5477
5639
  ttlMs: config.remotebuddy.workerpalOnlineTtlMs
5478
- });
5479
- if (!workerpalCapacity.ok) {
5640
+ }) : {
5641
+ ok: false,
5642
+ detail: "WorkerPal auto-spawn is disabled"
5643
+ };
5644
+ if (shouldProbeWorkerpalStartupCapacity && !workerpalCapacity.ok) {
5480
5645
  console.warn(`[pushpals] WorkerPal readiness probe did not find idle capacity yet (${workerpalCapacity.detail}).`);
5481
5646
  console.warn("[pushpals] Continuing startup; WorkerPal warmup may still be in progress and first task dispatch can be delayed.");
5482
5647
  if (workerpalDockerPrecheck.status === "failed") {
@@ -5489,7 +5654,13 @@ async function main() {
5489
5654
  const startupWorkerExecutionReadiness = workerpalCapacity.ok ? {
5490
5655
  state: "ready",
5491
5656
  detail: workerpalCapacity.detail
5492
- } : workerpalDockerPrecheck.status === "failed" ? describeWorkerExecutionReadiness({
5657
+ } : !shouldProbeWorkerpalStartupCapacity ? describeWorkerExecutionReadiness({
5658
+ autoSpawnWorkerpals: false,
5659
+ requireDocker: Boolean(config.remotebuddy.workerpalDocker) && Boolean(config.remotebuddy.workerpalRequireDocker),
5660
+ dockerPrecheck: workerpalDockerPrecheck,
5661
+ onlineWorkers: 0,
5662
+ idleWorkers: 0
5663
+ }) : workerpalDockerPrecheck.status === "failed" ? describeWorkerExecutionReadiness({
5493
5664
  autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
5494
5665
  requireDocker: Boolean(config.remotebuddy.workerpalDocker) && Boolean(config.remotebuddy.workerpalRequireDocker),
5495
5666
  dockerPrecheck: workerpalDockerPrecheck,
@@ -5700,8 +5871,10 @@ export {
5700
5871
  waitForWorkerpalCapacity,
5701
5872
  startEmbeddedMonitoringHub,
5702
5873
  shutdownEmbeddedServiceManagerGracefully,
5874
+ shouldUseRemoteBuddySilentStartupFallback,
5703
5875
  shouldRunEmbeddedRuntimeStartupPrechecks,
5704
5876
  shouldRestartEmbeddedService,
5877
+ runCommandWithEnv,
5705
5878
  resolveWorkerpalDockerProbe,
5706
5879
  resolveWorkerExecutionReadiness,
5707
5880
  resolveWindowsWhereExecutableCandidatesForEnv,
@@ -5727,6 +5900,7 @@ export {
5727
5900
  isDockerCleanupTimeoutDetail,
5728
5901
  isCliExitCommand,
5729
5902
  injectMonitoringHubBootstrap,
5903
+ hasRemoteBuddyRuntimeOutput,
5730
5904
  formatWorkerExecutionReadinessLines,
5731
5905
  formatTimestampedCliLine,
5732
5906
  formatSessionEventLine,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -159,9 +159,11 @@ quality_publish_gate_enabled = true
159
159
  # Browser/e2e validation commands get a longer built-in floor (10m) because they
160
160
  # may need to start a dev server and run browser automation.
161
161
  quality_validation_step_timeout_ms = 180000
162
- quality_critic_timeout_ms = 45000
162
+ quality_critic_timeout_ms = 90000
163
+ quality_critic_timeout_behavior = "retry_once" # skip | retry_once | block
163
164
  quality_soft_pass_on_exhausted = true
164
165
  quality_critic_min_score = 8.0
166
+ quality_critic_model = "" # Optional faster/smaller model override for the critic.
165
167
  quality_critic_max_diff_chars = 16000
166
168
  quality_critic_max_validation_output_chars = 8000
167
169
  executor_result_prefix = "__PUSHPALS_OH_RESULT__ "
@@ -75,9 +75,11 @@ quality_publish_gate_enabled = true
75
75
  # Browser/e2e validation commands get a longer built-in floor (10m) because they
76
76
  # may need to start a dev server and run browser automation.
77
77
  quality_validation_step_timeout_ms = 180000
78
- quality_critic_timeout_ms = 45000
78
+ quality_critic_timeout_ms = 90000
79
+ quality_critic_timeout_behavior = "retry_once" # skip | retry_once | block
79
80
  quality_soft_pass_on_exhausted = true
80
81
  quality_critic_min_score = 8.0
82
+ quality_critic_model = "" # Optional faster/smaller model override for the critic.
81
83
  quality_critic_max_diff_chars = 16000
82
84
  quality_critic_max_validation_output_chars = 8000
83
85
  executor_result_prefix = "__PUSHPALS_OH_RESULT__ "
@@ -743,7 +743,8 @@ var DEFAULT_WORKERPALS_OUTPUT_MAX_CHARS = 192 * 1024;
743
743
  var DEFAULT_WORKERPALS_OUTPUT_MAX_LINES = 600;
744
744
  var DEFAULT_WORKERPALS_OUTPUT_MAX_HEAD_LINES = 120;
745
745
  var DEFAULT_WORKERPALS_QUALITY_VALIDATION_STEP_TIMEOUT_MS = 180000;
746
- var DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS = 45000;
746
+ var DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS = 90000;
747
+ var DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_BEHAVIOR = "retry_once";
747
748
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_DIFF_CHARS = 16000;
748
749
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_VALIDATION_OUTPUT_CHARS = 8000;
749
750
  var DEFAULT_WORKERPALS_EXECUTOR = "openai_codex";
@@ -824,6 +825,13 @@ function asString(value, fallback) {
824
825
  return value.trim();
825
826
  return fallback;
826
827
  }
828
+ function asQualityCriticTimeoutBehavior(value) {
829
+ const normalized = String(value ?? "").trim().toLowerCase().replace(/-/g, "_");
830
+ if (normalized === "skip" || normalized === "retry_once" || normalized === "block") {
831
+ return normalized;
832
+ }
833
+ return DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_BEHAVIOR;
834
+ }
827
835
  function asBoolean(value, fallback) {
828
836
  if (typeof value === "boolean")
829
837
  return value;
@@ -1118,6 +1126,7 @@ function loadPushPalsConfig(options = {}) {
1118
1126
  const workerOutputMaxHeadLines = Math.max(1, Math.min(workerOutputMaxLines, asInt(parseIntEnv("WORKERPALS_OUTPUT_MAX_HEAD_LINES") ?? workerNode.output_max_head_lines, DEFAULT_WORKERPALS_OUTPUT_MAX_HEAD_LINES)));
1119
1127
  const workerQualityValidationStepTimeoutMs = Math.max(1000, asInt(parseIntEnv("WORKERPALS_QUALITY_VALIDATION_STEP_TIMEOUT_MS") ?? workerNode.quality_validation_step_timeout_ms, DEFAULT_WORKERPALS_QUALITY_VALIDATION_STEP_TIMEOUT_MS));
1120
1128
  const workerQualityCriticTimeoutMs = Math.max(1000, asInt(parseIntEnv("WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS") ?? workerNode.quality_critic_timeout_ms, DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS));
1129
+ const workerQualityCriticTimeoutBehavior = asQualityCriticTimeoutBehavior(process.env.WORKERPALS_QUALITY_CRITIC_TIMEOUT_BEHAVIOR ?? workerNode.quality_critic_timeout_behavior);
1121
1130
  const workerQualitySoftPassOnExhausted = parseBoolEnv("WORKERPALS_QUALITY_SOFT_PASS_ON_EXHAUSTED") ?? asBoolean(workerNode.quality_soft_pass_on_exhausted, true);
1122
1131
  const workerQualityScopeGateEnabled = parseBoolEnv("WORKERPALS_QUALITY_SCOPE_GATE_ENABLED") ?? asBoolean(workerNode.quality_scope_gate_enabled, true);
1123
1132
  const workerQualityValidationGateEnabled = parseBoolEnv("WORKERPALS_QUALITY_VALIDATION_GATE_ENABLED") ?? asBoolean(workerNode.quality_validation_gate_enabled, true);
@@ -1131,6 +1140,7 @@ function loadPushPalsConfig(options = {}) {
1131
1140
  return DEFAULT_WORKERPALS_QUALITY_CRITIC_MIN_SCORE;
1132
1141
  return Math.max(0, Math.min(10, parsed));
1133
1142
  })();
1143
+ const workerQualityCriticModel = firstNonEmpty(process.env.WORKERPALS_QUALITY_CRITIC_MODEL, asString(workerNode.quality_critic_model, ""), "");
1134
1144
  const workerQualityCriticMaxDiffChars = Math.max(256, Math.min(524288, asInt(parseIntEnv("WORKERPALS_QUALITY_CRITIC_MAX_DIFF_CHARS") ?? workerNode.quality_critic_max_diff_chars, DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_DIFF_CHARS)));
1135
1145
  const workerQualityCriticMaxValidationOutputChars = Math.max(256, Math.min(524288, asInt(parseIntEnv("WORKERPALS_QUALITY_CRITIC_MAX_VALIDATION_OUTPUT_CHARS") ?? workerNode.quality_critic_max_validation_output_chars, DEFAULT_WORKERPALS_QUALITY_CRITIC_MAX_VALIDATION_OUTPUT_CHARS)));
1136
1146
  const workerExecutorResultPrefix = (() => {
@@ -1420,8 +1430,10 @@ function loadPushPalsConfig(options = {}) {
1420
1430
  qualityPublishGateEnabled: workerQualityPublishGateEnabled,
1421
1431
  qualityValidationStepTimeoutMs: workerQualityValidationStepTimeoutMs,
1422
1432
  qualityCriticTimeoutMs: workerQualityCriticTimeoutMs,
1433
+ qualityCriticTimeoutBehavior: workerQualityCriticTimeoutBehavior,
1423
1434
  qualitySoftPassOnExhausted: workerQualitySoftPassOnExhausted,
1424
1435
  qualityCriticMinScore: workerQualityCriticMinScore,
1436
+ qualityCriticModel: workerQualityCriticModel,
1425
1437
  qualityCriticMaxDiffChars: workerQualityCriticMaxDiffChars,
1426
1438
  qualityCriticMaxValidationOutputChars: workerQualityCriticMaxValidationOutputChars,
1427
1439
  executorResultPrefix: workerExecutorResultPrefix,