@pushpalsdev/cli 1.1.4 → 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,13 @@ 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;
1645
+ var DEFAULT_STARTUP_GIT_PROBE_TIMEOUT_MS = 5000;
1646
+ var DEFAULT_STARTUP_GIT_REMOTE_TIMEOUT_MS = 1e4;
1623
1647
  var EMBEDDED_SERVICE_RESTART_MAX_ATTEMPTS = 4;
1624
1648
  var WORKERPAL_STARTUP_READINESS_PROBE_MAX_MS = 15000;
1625
1649
  var EMBEDDED_RUNTIME_SAFETY_CAP_DISABLE_ENV = "PUSHPALS_DISABLE_EMBEDDED_SAFETY_CAPS";
@@ -1917,9 +1941,119 @@ function parsePositiveInt(value, fallback) {
1917
1941
  return fallback;
1918
1942
  return parsed;
1919
1943
  }
1944
+ function clampPositiveInt(value, min, max) {
1945
+ if (!Number.isFinite(value))
1946
+ return min;
1947
+ return Math.max(min, Math.min(max, Math.trunc(value)));
1948
+ }
1949
+ function resolveStartupGitProbeTimeoutMs(env) {
1950
+ return clampPositiveInt(parsePositiveInt(env.PUSHPALS_STARTUP_GIT_PROBE_TIMEOUT_MS, DEFAULT_STARTUP_GIT_PROBE_TIMEOUT_MS), 1000, 30000);
1951
+ }
1952
+ function resolveStartupGitRemoteTimeoutMs(env) {
1953
+ return clampPositiveInt(parsePositiveInt(env.PUSHPALS_STARTUP_GIT_REMOTE_TIMEOUT_MS, DEFAULT_STARTUP_GIT_REMOTE_TIMEOUT_MS), 1000, 60000);
1954
+ }
1955
+ async function withStartupTimeout(promise, timeoutMs, timeoutValue) {
1956
+ let timer = null;
1957
+ try {
1958
+ return await Promise.race([
1959
+ promise,
1960
+ new Promise((resolveTimeout) => {
1961
+ timer = setTimeout(() => resolveTimeout(timeoutValue()), Math.max(1, timeoutMs));
1962
+ })
1963
+ ]);
1964
+ } finally {
1965
+ if (timer)
1966
+ clearTimeout(timer);
1967
+ }
1968
+ }
1920
1969
  function jsonHtmlBootstrap(value) {
1921
1970
  return JSON.stringify(value).replace(/</g, "\\u003c");
1922
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
+ }
1923
2057
  async function runCommandWithEnv(command, cwd, env, timeoutMs) {
1924
2058
  try {
1925
2059
  const proc = Bun.spawn(command, {
@@ -1928,32 +2062,37 @@ async function runCommandWithEnv(command, cwd, env, timeoutMs) {
1928
2062
  stdout: "pipe",
1929
2063
  stderr: "pipe"
1930
2064
  });
2065
+ const stdoutCollector = createProcessOutputCollector(proc.stdout);
2066
+ const stderrCollector = createProcessOutputCollector(proc.stderr);
1931
2067
  let timedOut = false;
1932
- let timer = null;
1933
- if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
1934
- timer = setTimeout(() => {
1935
- timedOut = true;
1936
- try {
1937
- const stopCommand = buildServiceStopCommand(proc.pid, process.platform);
1938
- if (stopCommand) {
1939
- Bun.spawnSync(stopCommand, {
1940
- stdin: "ignore",
1941
- stdout: "ignore",
1942
- stderr: "ignore"
1943
- });
1944
- } else {
1945
- proc.kill("SIGKILL");
1946
- }
1947
- } catch {}
1948
- }, 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;
2083
+ }
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();
1949
2091
  }
1950
- const [stdout, stderr, exitCode] = await Promise.all([
1951
- new Response(proc.stdout).text(),
1952
- new Response(proc.stderr).text(),
1953
- proc.exited
2092
+ const [stdout, stderr] = await Promise.all([
2093
+ finishProcessOutputCollector(stdoutCollector),
2094
+ finishProcessOutputCollector(stderrCollector)
1954
2095
  ]);
1955
- if (timer)
1956
- clearTimeout(timer);
1957
2096
  const normalizedStdout = stdout.trim();
1958
2097
  const normalizedStderr = stderr.trim();
1959
2098
  if (timedOut) {
@@ -2000,15 +2139,15 @@ function withWindowsGitSchannelEnv(env, platform = process.platform) {
2000
2139
  return env;
2001
2140
  return appendGitConfigEnv(env, "http.sslBackend", "schannel");
2002
2141
  }
2003
- async function runGitWithEnv(args, cwd, env) {
2004
- return await runCommandWithEnv(["git", ...args], cwd, withWindowsGitSchannelEnv(env));
2142
+ async function runGitWithEnv(args, cwd, env, timeoutMs) {
2143
+ return await runCommandWithEnv(["git", ...args], cwd, withWindowsGitSchannelEnv(env), timeoutMs);
2005
2144
  }
2006
- async function runGit(args, cwd) {
2145
+ async function runGit(args, cwd, timeoutMs) {
2007
2146
  return await runGitWithEnv(args, cwd, {
2008
2147
  ...process.env,
2009
2148
  GIT_TERMINAL_PROMPT: "0",
2010
2149
  GCM_INTERACTIVE: "Never"
2011
- });
2150
+ }, timeoutMs);
2012
2151
  }
2013
2152
  async function resolveCurrentGitRepoRoot(cwd) {
2014
2153
  const inside = await runGit(["rev-parse", "--is-inside-work-tree"], cwd);
@@ -2738,20 +2877,14 @@ function normalizeChildProcessEnv(baseEnv, platform = process.platform) {
2738
2877
  }
2739
2878
  return env;
2740
2879
  }
2741
- async function resolveCommandPath(command, cwd, env) {
2880
+ async function resolveCommandPath(command, cwd, env, timeoutMs = resolveStartupGitProbeTimeoutMs(env)) {
2742
2881
  const lookupCommands = process.platform === "win32" ? resolveWindowsWhereExecutableCandidatesForEnv(env, process.platform).map((lookup) => [lookup, command]) : [["which", command]];
2743
2882
  for (const lookup of lookupCommands) {
2744
2883
  try {
2745
- const proc = Bun.spawn(lookup, {
2746
- cwd,
2747
- env,
2748
- stdout: "pipe",
2749
- stderr: "ignore"
2750
- });
2751
- const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
2752
- if (exitCode !== 0)
2884
+ const result = await runCommandWithEnv(lookup, cwd, env, timeoutMs);
2885
+ if (!result.ok)
2753
2886
  continue;
2754
- const resolved = stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2887
+ const resolved = result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
2755
2888
  if (resolved)
2756
2889
  return resolved;
2757
2890
  } catch {}
@@ -2791,6 +2924,23 @@ function readLogTail(logPath, maxLines = 40) {
2791
2924
  function hasStandaloneBunCrashSignature(text) {
2792
2925
  return /\bpanic\(main thread\)|\bsegmentation fault\b|oh no:\s*bun has crashed\b/i.test(String(text ?? ""));
2793
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
+ }
2794
2944
  function extractRemoteBuddyAutonomousEngineState(logText) {
2795
2945
  const text = String(logText ?? "");
2796
2946
  if (!text)
@@ -2819,21 +2969,33 @@ function readRemoteBuddyAutonomousEngineState(logPath) {
2819
2969
  async function downloadBinaryAsset(tag, assetName, outPath) {
2820
2970
  console.log(`[pushpals] Downloading embedded runtime binary ${assetName} from ${tag}...`);
2821
2971
  const url = `${GITHUB_RELEASE_URL}/${encodeURIComponent(tag)}/${assetName}`;
2822
- try {
2823
- const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
2824
- if (!response.ok) {
2825
- throw new Error(`Failed to download ${assetName} from ${tag} (HTTP ${response.status})`);
2826
- }
2827
- const bytes = new Uint8Array(await response.arrayBuffer());
2828
- mkdirSync(dirname(outPath), { recursive: true });
2829
- await Bun.write(outPath, bytes);
2830
- return;
2831
- } catch (err) {
2832
- const fallback = await downloadBinaryAssetWithWindowsCurlFallback(url, outPath, err);
2833
- 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);
2834
2982
  return;
2835
- 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
+ }
2836
2997
  }
2998
+ throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "unknown error"));
2837
2999
  }
2838
3000
  async function downloadBinaryAssetWithWindowsCurlFallback(url, outPath, cause) {
2839
3001
  if (process.platform !== "win32")
@@ -2926,7 +3088,9 @@ function stopRuntimeServices(services) {
2926
3088
  Bun.spawnSync(stopCommand, {
2927
3089
  stdin: "ignore",
2928
3090
  stdout: "ignore",
2929
- stderr: "ignore"
3091
+ stderr: "ignore",
3092
+ timeout: WINDOWS_TASKKILL_TIMEOUT_MS2,
3093
+ killSignal: "SIGKILL"
2930
3094
  });
2931
3095
  } else {
2932
3096
  service.proc.kill("SIGKILL");
@@ -2961,6 +3125,11 @@ async function stopRuntimeServicesGracefully(services, timeoutMs = 1e4) {
2961
3125
  if (running.length === 0)
2962
3126
  return;
2963
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
+ }
2964
3133
  const nonServer = ordered.filter((service) => service.name !== "server");
2965
3134
  const server = ordered.filter((service) => service.name === "server");
2966
3135
  for (const service of nonServer) {
@@ -3159,50 +3328,31 @@ function computeEmbeddedServiceRestartBackoffMs(attempt) {
3159
3328
  function shouldRestartEmbeddedService(attempts, maxAttempts = EMBEDDED_SERVICE_RESTART_MAX_ATTEMPTS) {
3160
3329
  return shouldRestartService(attempts, maxAttempts);
3161
3330
  }
3162
- async function canSpawnCommand(command, cwd, env) {
3163
- try {
3164
- const proc = Bun.spawn(command, {
3165
- cwd,
3166
- env,
3167
- stdin: "ignore",
3168
- stdout: "ignore",
3169
- stderr: "ignore"
3170
- });
3171
- const exitCode = await proc.exited;
3172
- return exitCode === 0;
3173
- } catch {
3174
- return false;
3175
- }
3331
+ async function canSpawnCommand(command, cwd, env, timeoutMs = resolveStartupGitProbeTimeoutMs(env)) {
3332
+ const result = await runCommandWithEnv(command, cwd, env, timeoutMs);
3333
+ return result.ok;
3176
3334
  }
3177
- async function canSpawnGitViaWindowsShell(commandArgs, cwd, env, platform = process.platform) {
3335
+ async function canSpawnGitViaWindowsShell(commandArgs, cwd, env, platform = process.platform, timeoutMs = resolveStartupGitProbeTimeoutMs(env)) {
3178
3336
  if (platform !== "win32")
3179
3337
  return false;
3180
3338
  const commandLine = commandArgs.map((arg) => quoteWindowsCmdArg(arg)).join(" ");
3181
3339
  for (const shellExecutable of resolveWindowsShellExecutableCandidatesForEnv(env, platform)) {
3182
- try {
3183
- const proc = Bun.spawn([shellExecutable, "/d", "/s", "/c", commandLine], {
3184
- cwd,
3185
- env,
3186
- stdin: "ignore",
3187
- stdout: "ignore",
3188
- stderr: "ignore"
3189
- });
3190
- const exitCode = await proc.exited;
3191
- return exitCode === 0;
3192
- } catch {}
3340
+ const result = await runCommandWithEnv([shellExecutable, "/d", "/s", "/c", commandLine], cwd, env, timeoutMs);
3341
+ if (result.ok)
3342
+ return true;
3193
3343
  }
3194
3344
  return false;
3195
3345
  }
3196
- async function resolveSourceControlManagerGitProbe(cwd, env, platform = process.platform) {
3346
+ async function resolveSourceControlManagerGitProbe(cwd, env, platform = process.platform, timeoutMs = resolveStartupGitProbeTimeoutMs(env)) {
3197
3347
  const candidates = resolveRuntimeGitExecutableCandidates(env, platform);
3198
3348
  for (const candidate of candidates) {
3199
- if (await canSpawnCommand([candidate, "--version"], cwd, env)) {
3349
+ if (await canSpawnCommand([candidate, "--version"], cwd, env, timeoutMs)) {
3200
3350
  return { ok: true, detail: candidate };
3201
3351
  }
3202
3352
  }
3203
3353
  if (platform === "win32") {
3204
3354
  for (const candidate of candidates) {
3205
- if (await canSpawnGitViaWindowsShell([candidate, "--version"], cwd, env, platform)) {
3355
+ if (await canSpawnGitViaWindowsShell([candidate, "--version"], cwd, env, platform, timeoutMs)) {
3206
3356
  return { ok: true, detail: `${candidate} via shell` };
3207
3357
  }
3208
3358
  }
@@ -3630,7 +3780,12 @@ async function precheckSourceControlManagerGitAvailability(opts) {
3630
3780
  if (preconfiguredGitBinary) {
3631
3781
  applyResolvedGitBinaryToRuntimeEnv(env, preconfiguredGitBinary, platform);
3632
3782
  }
3633
- const remoteStatus = opts.gitRemoteCheckFn ? await opts.gitRemoteCheckFn(opts.repoRoot, opts.remote, env) : opts.repoHasRemoteFn ? await opts.repoHasRemoteFn(opts.repoRoot, opts.remote) ? { status: "ok", remote: opts.remote } : { status: "missing_remote", remote: opts.remote } : await checkGitRemoteConfigured(opts.repoRoot, opts.remote, env);
3783
+ const remoteTimeoutMs = resolveStartupGitRemoteTimeoutMs(env);
3784
+ const remoteStatus = await withStartupTimeout(opts.gitRemoteCheckFn ? opts.gitRemoteCheckFn(opts.repoRoot, opts.remote, env) : opts.repoHasRemoteFn ? opts.repoHasRemoteFn(opts.repoRoot, opts.remote).then((hasRemote) => hasRemote ? { status: "ok", remote: opts.remote } : { status: "missing_remote", remote: opts.remote }) : checkGitRemoteConfigured(opts.repoRoot, opts.remote, env, remoteTimeoutMs), remoteTimeoutMs, () => ({
3785
+ status: "error",
3786
+ remote: opts.remote,
3787
+ detail: `timed out after ${remoteTimeoutMs}ms`
3788
+ }));
3634
3789
  if (remoteStatus.status === "missing_remote") {
3635
3790
  return {
3636
3791
  status: "skipped",
@@ -3719,7 +3874,7 @@ function resolveWorkerpalStartupReadinessProbeTimeoutMs(config) {
3719
3874
  function shouldRunEmbeddedRuntimeStartupPrechecks(opts) {
3720
3875
  return !opts.serverHealthy && !opts.noAutoStart;
3721
3876
  }
3722
- async function checkGitRemoteConfigured(repoRoot, remote, env) {
3877
+ async function checkGitRemoteConfigured(repoRoot, remote, env, timeoutMs = resolveStartupGitRemoteTimeoutMs(env ?? process.env)) {
3723
3878
  const normalizedRemote = String(remote ?? "").trim();
3724
3879
  if (!normalizedRemote) {
3725
3880
  return { status: "missing_remote", remote: normalizedRemote };
@@ -3728,7 +3883,7 @@ async function checkGitRemoteConfigured(repoRoot, remote, env) {
3728
3883
  ...process.env,
3729
3884
  GIT_TERMINAL_PROMPT: "0",
3730
3885
  GCM_INTERACTIVE: "Never"
3731
- });
3886
+ }, timeoutMs);
3732
3887
  if (result.ok && result.stdout) {
3733
3888
  return { status: "ok", remote: normalizedRemote };
3734
3889
  }
@@ -3757,7 +3912,7 @@ async function checkPushpalsBranchOnRemote(repoRoot, remote, branch) {
3757
3912
  };
3758
3913
  }
3759
3914
  const ref = `refs/heads/${normalizedBranch}`;
3760
- const result = await runGit(["ls-remote", "--heads", normalizedRemote, ref], repoRoot);
3915
+ const result = await runGit(["ls-remote", "--heads", normalizedRemote, ref], repoRoot, resolveStartupGitRemoteTimeoutMs(process.env));
3761
3916
  if (!result.ok) {
3762
3917
  const detail = result.stderr || result.stdout || `exit ${result.exitCode}`;
3763
3918
  return {
@@ -3789,8 +3944,9 @@ async function enforcePushpalsRemoteBranchPrecheck(repoRoot, remote, branch) {
3789
3944
  console.error("[pushpals] Precheck failed: create/push that branch first or set source_control_manager.pushpals_branch to an existing remote branch.");
3790
3945
  return false;
3791
3946
  }
3792
- console.error(`[pushpals] Precheck failed: could not verify remote branch "${result.remote}/${result.branch}": ${result.detail}`);
3793
- return false;
3947
+ console.warn(`[pushpals] Precheck warning: could not verify remote branch "${result.remote}/${result.branch}": ${result.detail}`);
3948
+ console.warn("[pushpals] Precheck warning: continuing startup without SourceControlManager branch verification because the remote check was inconclusive.");
3949
+ return true;
3794
3950
  }
3795
3951
  function isPathEqualOrWithin(parentPath, childPath) {
3796
3952
  const parent = normalizeRepoPathForComparison(parentPath);
@@ -4338,16 +4494,25 @@ async function autoStartRuntimeServices(opts) {
4338
4494
  bundlePath: sandboxPaths.remotebuddyFallbackBundlePath
4339
4495
  } : null;
4340
4496
  let remoteBuddyFallbackActivated = false;
4341
- const maybeActivateRemoteBuddyWindowsFallback = () => {
4497
+ const maybeActivateRemoteBuddyWindowsFallback = (reason = "crash") => {
4342
4498
  if (remoteBuddyFallbackActivated || !remoteBuddySourceFallback)
4343
4499
  return false;
4344
4500
  const tail = readLogTail(serviceLogPaths.remotebuddy, 120);
4345
- if (!hasStandaloneBunCrashSignature(tail))
4501
+ if (reason === "crash" && !hasStandaloneBunCrashSignature(tail))
4346
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
+ })) {
4509
+ return false;
4510
+ }
4347
4511
  remoteBuddyFallbackActivated = true;
4348
4512
  lastReportedRemoteBuddyAutonomyState = "unknown";
4349
- console.warn("[pushpals] Embedded RemoteBuddy standalone binary crashed on Windows; retrying with source fallback under bun.");
4350
- 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.`);
4351
4516
  replaceService("remotebuddy", [
4352
4517
  remoteBuddySourceFallback.bunBin,
4353
4518
  remoteBuddySourceFallback.bundlePath,
@@ -4459,31 +4624,35 @@ ${tail}` : ""}`);
4459
4624
  recordStartupPhase("workerpal", Date.now(), "disabled");
4460
4625
  }
4461
4626
  const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
4462
- const scmGitProbe = await resolveSourceControlManagerGitProbe(opts.repoRoot, runtimeEnv, process.platform);
4463
- const scmRemoteStatus = await checkGitRemoteConfigured(opts.repoRoot, opts.sourceControlManagerRemote, runtimeEnv);
4464
4627
  if (!scmHealthy) {
4465
4628
  const scmPhaseStartedAt = Date.now();
4629
+ console.log("[pushpals] Checking embedded SourceControlManager git/remote preflight...");
4630
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] checking embedded source_control_manager git/remote preflight.");
4631
+ const scmGitProbe = await resolveSourceControlManagerGitProbe(opts.repoRoot, runtimeEnv, process.platform);
4466
4632
  if (!scmGitProbe.ok) {
4467
4633
  console.warn("[pushpals] Git is not available to embedded SourceControlManager; skipping SCM startup.");
4468
4634
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: git is unavailable in embedded runtime env (${scmGitProbe.detail}).`);
4469
4635
  recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_git");
4470
- } else if (scmRemoteStatus.status === "error") {
4471
- console.warn(`[pushpals] Could not inspect SourceControlManager git remote "${opts.sourceControlManagerRemote}"; skipping SCM startup.`);
4472
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: remote "${opts.sourceControlManagerRemote}" could not be inspected (${scmRemoteStatus.detail}).`);
4473
- recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_remote_error");
4474
- } else if (scmRemoteStatus.status === "ok") {
4475
- console.log(`[pushpals] Embedded SourceControlManager git=${scmGitProbe.detail}`);
4476
- console.log("[pushpals] Starting embedded SourceControlManager...");
4477
- const sourceControlManagerService = launchService("source_control_manager", [
4478
- runtimeBinaries.sourceControlManager,
4479
- "--skip-clean-check"
4480
- ]);
4481
- console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath ?? serviceLogPaths.source_control_manager}`);
4482
- recordStartupPhase("source_control_manager", scmPhaseStartedAt, "started");
4483
4636
  } else {
4484
- console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
4485
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: repo has no remote "${opts.sourceControlManagerRemote}".`);
4486
- recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_remote");
4637
+ const scmRemoteStatus = await checkGitRemoteConfigured(opts.repoRoot, opts.sourceControlManagerRemote, runtimeEnv);
4638
+ if (scmRemoteStatus.status === "error") {
4639
+ console.warn(`[pushpals] Could not inspect SourceControlManager git remote "${opts.sourceControlManagerRemote}"; skipping SCM startup.`);
4640
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: remote "${opts.sourceControlManagerRemote}" could not be inspected (${scmRemoteStatus.detail}).`);
4641
+ recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_remote_error");
4642
+ } else if (scmRemoteStatus.status === "ok") {
4643
+ console.log(`[pushpals] Embedded SourceControlManager git=${scmGitProbe.detail}`);
4644
+ console.log("[pushpals] Starting embedded SourceControlManager...");
4645
+ const sourceControlManagerService = launchService("source_control_manager", [
4646
+ runtimeBinaries.sourceControlManager,
4647
+ "--skip-clean-check"
4648
+ ]);
4649
+ console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath ?? serviceLogPaths.source_control_manager}`);
4650
+ recordStartupPhase("source_control_manager", scmPhaseStartedAt, "started");
4651
+ } else {
4652
+ console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
4653
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: repo has no remote "${opts.sourceControlManagerRemote}".`);
4654
+ recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_remote");
4655
+ }
4487
4656
  }
4488
4657
  } else {
4489
4658
  recordStartupPhase("source_control_manager", Date.now(), "reused");
@@ -4495,6 +4664,10 @@ ${tail}` : ""}`);
4495
4664
  const optionalServiceExitWarned = new Set;
4496
4665
  while (Date.now() < deadline) {
4497
4666
  reportRemoteBuddyAutonomousEngineState();
4667
+ if (maybeActivateRemoteBuddyWindowsFallback("silent_startup")) {
4668
+ await Bun.sleep(DEFAULT_RUNTIME_BOOT_POLL_MS);
4669
+ continue;
4670
+ }
4498
4671
  for (const service of serviceManager.getServices()) {
4499
4672
  if (service.exited) {
4500
4673
  if (service.name === "remotebuddy" && maybeActivateRemoteBuddyWindowsFallback()) {
@@ -5249,8 +5422,7 @@ async function main() {
5249
5422
  sessionId
5250
5423
  });
5251
5424
  if (scmGitPrecheck.status === "failed") {
5252
- console.error(`[pushpals] Precheck failed: embedded SourceControlManager git command is unavailable (${scmGitPrecheck.detail}).`);
5253
- process.exit(1);
5425
+ console.warn(`[pushpals] Embedded SourceControlManager precheck failed (${scmGitPrecheck.detail}); continuing startup without blocking on SCM.`);
5254
5426
  }
5255
5427
  workerpalDockerPrecheck = await precheckWorkerpalDockerAvailability({
5256
5428
  repoRoot,
@@ -5460,12 +5632,16 @@ async function main() {
5460
5632
  }
5461
5633
  process.exit(1);
5462
5634
  }
5463
- const workerpalCapacity = await waitForWorkerpalCapacity({
5635
+ const shouldProbeWorkerpalStartupCapacity = Boolean(config.remotebuddy.autoSpawnWorkerpals);
5636
+ const workerpalCapacity = shouldProbeWorkerpalStartupCapacity ? await waitForWorkerpalCapacity({
5464
5637
  serverUrl,
5465
5638
  timeoutMs: resolveWorkerpalStartupReadinessProbeTimeoutMs(config),
5466
5639
  ttlMs: config.remotebuddy.workerpalOnlineTtlMs
5467
- });
5468
- if (!workerpalCapacity.ok) {
5640
+ }) : {
5641
+ ok: false,
5642
+ detail: "WorkerPal auto-spawn is disabled"
5643
+ };
5644
+ if (shouldProbeWorkerpalStartupCapacity && !workerpalCapacity.ok) {
5469
5645
  console.warn(`[pushpals] WorkerPal readiness probe did not find idle capacity yet (${workerpalCapacity.detail}).`);
5470
5646
  console.warn("[pushpals] Continuing startup; WorkerPal warmup may still be in progress and first task dispatch can be delayed.");
5471
5647
  if (workerpalDockerPrecheck.status === "failed") {
@@ -5478,7 +5654,13 @@ async function main() {
5478
5654
  const startupWorkerExecutionReadiness = workerpalCapacity.ok ? {
5479
5655
  state: "ready",
5480
5656
  detail: workerpalCapacity.detail
5481
- } : 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({
5482
5664
  autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
5483
5665
  requireDocker: Boolean(config.remotebuddy.workerpalDocker) && Boolean(config.remotebuddy.workerpalRequireDocker),
5484
5666
  dockerPrecheck: workerpalDockerPrecheck,
@@ -5689,8 +5871,10 @@ export {
5689
5871
  waitForWorkerpalCapacity,
5690
5872
  startEmbeddedMonitoringHub,
5691
5873
  shutdownEmbeddedServiceManagerGracefully,
5874
+ shouldUseRemoteBuddySilentStartupFallback,
5692
5875
  shouldRunEmbeddedRuntimeStartupPrechecks,
5693
5876
  shouldRestartEmbeddedService,
5877
+ runCommandWithEnv,
5694
5878
  resolveWorkerpalDockerProbe,
5695
5879
  resolveWorkerExecutionReadiness,
5696
5880
  resolveWindowsWhereExecutableCandidatesForEnv,
@@ -5716,6 +5900,7 @@ export {
5716
5900
  isDockerCleanupTimeoutDetail,
5717
5901
  isCliExitCommand,
5718
5902
  injectMonitoringHubBootstrap,
5903
+ hasRemoteBuddyRuntimeOutput,
5719
5904
  formatWorkerExecutionReadinessLines,
5720
5905
  formatTimestampedCliLine,
5721
5906
  formatSessionEventLine,