@pushpalsdev/cli 1.0.30 → 1.0.32
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 +345 -52
- package/package.json +1 -1
- package/runtime/sandbox/package.json +1 -0
package/dist/pushpals-cli.js
CHANGED
|
@@ -1217,6 +1217,15 @@ var DEFAULT_RUNTIME_BOOT_TIMEOUT_MS = 90000;
|
|
|
1217
1217
|
var DEFAULT_RUNTIME_BOOT_POLL_MS = 1000;
|
|
1218
1218
|
var DEFAULT_SERVER_BOOT_TIMEOUT_MS = 20000;
|
|
1219
1219
|
var DEFAULT_SERVICE_STABILITY_GRACE_MS = 4000;
|
|
1220
|
+
var WORKERPAL_STARTUP_READINESS_PROBE_MAX_MS = 15000;
|
|
1221
|
+
var EMBEDDED_RUNTIME_SAFETY_CAP_DISABLE_ENV = "PUSHPALS_DISABLE_EMBEDDED_SAFETY_CAPS";
|
|
1222
|
+
var EMBEDDED_RUNTIME_WINDOWS_SAFETY_CAPS = {
|
|
1223
|
+
REMOTEBUDDY_WORKERPAL_STARTUP_TIMEOUT_MS: "120000",
|
|
1224
|
+
WORKERPALS_DOCKER_AGENT_STARTUP_TIMEOUT_MS: "90000",
|
|
1225
|
+
WORKERPALS_SKIP_DOCKER_SELF_CHECK: "1",
|
|
1226
|
+
WORKERPALS_DOCKER_WARM_MEMORY_MB: "1024",
|
|
1227
|
+
WORKERPALS_DOCKER_WARM_CPUS: "1"
|
|
1228
|
+
};
|
|
1220
1229
|
var GITHUB_OWNER = "PushPalsDev";
|
|
1221
1230
|
var GITHUB_REPO = "pushpals";
|
|
1222
1231
|
var GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}`;
|
|
@@ -1235,6 +1244,98 @@ function formatTimestampedCliLine(line, at = new Date) {
|
|
|
1235
1244
|
}
|
|
1236
1245
|
return `[${at.toISOString()}]${text}`;
|
|
1237
1246
|
}
|
|
1247
|
+
function formatRuntimeStartupTimingSummary(input) {
|
|
1248
|
+
const phaseSummary = input.phases.map((phase) => `${phase.name}=${Math.max(0, Math.floor(phase.durationMs))}ms(${phase.status.trim() || "unknown"})`).join(" ");
|
|
1249
|
+
const detail = typeof input.detail === "string" && input.detail.trim() ? ` detail=${input.detail.trim()}` : "";
|
|
1250
|
+
return `[pushpals] startup timing summary: outcome=${input.outcome} ` + `total=${Math.max(0, Math.floor(input.totalDurationMs))}ms${detail}` + (phaseSummary ? ` ${phaseSummary}` : "");
|
|
1251
|
+
}
|
|
1252
|
+
function describeWorkerExecutionReadiness(opts) {
|
|
1253
|
+
const onlineWorkers = Math.max(0, Math.floor(opts.onlineWorkers));
|
|
1254
|
+
const idleWorkers = Math.max(0, Math.floor(opts.idleWorkers));
|
|
1255
|
+
if (idleWorkers > 0) {
|
|
1256
|
+
return {
|
|
1257
|
+
state: "ready",
|
|
1258
|
+
detail: `${idleWorkers} idle / ${onlineWorkers} online`
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
if (onlineWorkers > 0) {
|
|
1262
|
+
return {
|
|
1263
|
+
state: "warming",
|
|
1264
|
+
detail: `${idleWorkers} idle / ${onlineWorkers} online`,
|
|
1265
|
+
action: "Wait for WorkerPal warmup or active jobs to finish, then retry /status or send the request again."
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
if (!opts.autoSpawnWorkerpals) {
|
|
1269
|
+
return {
|
|
1270
|
+
state: "blocked",
|
|
1271
|
+
detail: "No online WorkerPals are reported and auto-spawn is disabled.",
|
|
1272
|
+
action: "Start a WorkerPals backend manually or enable RemoteBuddy auto-spawn."
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
if (opts.requireDocker && opts.dockerPrecheck?.status === "failed") {
|
|
1276
|
+
return {
|
|
1277
|
+
state: "blocked",
|
|
1278
|
+
detail: `Docker-backed WorkerPal auto-spawn is unavailable: ${opts.dockerPrecheck.detail}`,
|
|
1279
|
+
action: "Start Docker Desktop or the Docker daemon, then retry startup or rerun /status."
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
return {
|
|
1283
|
+
state: "warming",
|
|
1284
|
+
detail: "No online WorkerPals are reported yet.",
|
|
1285
|
+
action: "Wait for WorkerPal auto-spawn/warmup to finish, then rerun /status."
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
function formatWorkerExecutionReadinessLines(readiness) {
|
|
1289
|
+
const lines = [
|
|
1290
|
+
`[pushpals] workerExecution=${readiness.state} detail=${readiness.detail}`
|
|
1291
|
+
];
|
|
1292
|
+
if (readiness.action) {
|
|
1293
|
+
lines.push(`[pushpals] workerExecutionAction=${readiness.action}`);
|
|
1294
|
+
}
|
|
1295
|
+
return lines;
|
|
1296
|
+
}
|
|
1297
|
+
function summarizeWorkerStatusRows(workers) {
|
|
1298
|
+
const onlineWorkers = workers.filter((worker) => Boolean(worker?.isOnline) && String(worker?.status ?? "").trim().toLowerCase() !== "offline");
|
|
1299
|
+
const idleWorkers = onlineWorkers.filter((worker) => Number(worker?.activeJobCount ?? 0) <= 0);
|
|
1300
|
+
return {
|
|
1301
|
+
onlineWorkers: onlineWorkers.length,
|
|
1302
|
+
idleWorkers: idleWorkers.length
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
async function resolveWorkerExecutionReadiness(opts) {
|
|
1306
|
+
let workers = [];
|
|
1307
|
+
try {
|
|
1308
|
+
workers = await (opts.fetchWorkersFn ?? fetchWorkerStatusRows)(opts.serverUrl, opts.ttlMs);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
return {
|
|
1311
|
+
state: "blocked",
|
|
1312
|
+
detail: `Unable to query WorkerPal status: ${String(error)}`,
|
|
1313
|
+
action: "Check runtime connectivity, then retry /status or restart the runtime."
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
const { onlineWorkers, idleWorkers } = summarizeWorkerStatusRows(workers);
|
|
1317
|
+
let dockerPrecheck = opts.dockerPrecheck ?? null;
|
|
1318
|
+
const shouldProbeDockerAvailability = onlineWorkers === 0 && opts.autoSpawnWorkerpals && opts.dockerEnabled && opts.requireDocker && !dockerPrecheck && typeof opts.repoRoot === "string" && opts.repoRoot.trim().length > 0 && typeof opts.runtimeRoot === "string" && opts.runtimeRoot.trim().length > 0 && typeof opts.preflightUsesEmbeddedRuntime === "boolean";
|
|
1319
|
+
if (shouldProbeDockerAvailability) {
|
|
1320
|
+
dockerPrecheck = await (opts.precheckDockerAvailabilityFn ?? precheckWorkerpalDockerAvailability)({
|
|
1321
|
+
repoRoot: opts.repoRoot,
|
|
1322
|
+
runtimeRoot: opts.runtimeRoot,
|
|
1323
|
+
preflightUsesEmbeddedRuntime: opts.preflightUsesEmbeddedRuntime,
|
|
1324
|
+
autoSpawnWorkerpals: opts.autoSpawnWorkerpals,
|
|
1325
|
+
dockerEnabled: opts.dockerEnabled,
|
|
1326
|
+
requireDocker: opts.requireDocker,
|
|
1327
|
+
sessionId: opts.sessionId,
|
|
1328
|
+
baseEnv: opts.baseEnv
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
return describeWorkerExecutionReadiness({
|
|
1332
|
+
autoSpawnWorkerpals: opts.autoSpawnWorkerpals,
|
|
1333
|
+
requireDocker: opts.dockerEnabled && opts.requireDocker,
|
|
1334
|
+
dockerPrecheck,
|
|
1335
|
+
onlineWorkers,
|
|
1336
|
+
idleWorkers
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1238
1339
|
function normalizeCliInteractiveMessage(input) {
|
|
1239
1340
|
const trimmed = String(input ?? "").trim();
|
|
1240
1341
|
const command = ASK_REMOTE_BUDDY_COMMAND.toLowerCase();
|
|
@@ -1792,8 +1893,15 @@ function copyBundledRuntimeAssets(runtimeRoot, force = true) {
|
|
|
1792
1893
|
copyRuntimeAssetBundle(bundledSource, runtimeRoot, force);
|
|
1793
1894
|
return true;
|
|
1794
1895
|
}
|
|
1896
|
+
function hasSeededRuntimePreflightAssets(runtimeRoot) {
|
|
1897
|
+
const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
|
|
1898
|
+
const hasProtocolSchemas = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
|
|
1899
|
+
return existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemas && isCompleteWorkerpalSandboxRoot(join2(runtimeRoot, "sandbox"));
|
|
1900
|
+
}
|
|
1795
1901
|
function seedRuntimePreflightAssets(runtimeRoot) {
|
|
1796
|
-
|
|
1902
|
+
if (!hasSeededRuntimePreflightAssets(runtimeRoot)) {
|
|
1903
|
+
copyBundledRuntimeAssets(runtimeRoot, false);
|
|
1904
|
+
}
|
|
1797
1905
|
writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
|
|
1798
1906
|
`);
|
|
1799
1907
|
const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
|
|
@@ -1946,12 +2054,20 @@ function cleanupLegacyRuntimeBinaryLayouts(runtimeRoot, platformKey, activeBinDi
|
|
|
1946
2054
|
function buildEmbeddedRuntimeEnv(baseEnv, opts) {
|
|
1947
2055
|
const env = normalizeChildProcessEnv(baseEnv);
|
|
1948
2056
|
const useRuntimeConfig = opts.useRuntimeConfig !== false;
|
|
2057
|
+
const platform = opts.platform ?? process.platform;
|
|
2058
|
+
const embeddedBunBin = resolveEmbeddedBunExecutableFromEnv(env, platform);
|
|
1949
2059
|
const inherited = { ...env };
|
|
1950
2060
|
if (!useRuntimeConfig) {
|
|
1951
2061
|
delete inherited.PUSHPALS_CONFIG_DIR_OVERRIDE;
|
|
1952
2062
|
delete inherited.PUSHPALS_WORKERPALS_SANDBOX_ROOT;
|
|
1953
2063
|
delete inherited.PUSHPALS_RUNTIME_TAG;
|
|
1954
2064
|
}
|
|
2065
|
+
const disableEmbeddedSafetyCaps = parseBooleanFlag(env[EMBEDDED_RUNTIME_SAFETY_CAP_DISABLE_ENV]) === true;
|
|
2066
|
+
const shouldApplyEmbeddedWindowsSafetyCaps = useRuntimeConfig && platform === "win32" && !disableEmbeddedSafetyCaps;
|
|
2067
|
+
const embeddedWindowsSafetyCaps = shouldApplyEmbeddedWindowsSafetyCaps ? Object.fromEntries(Object.entries(EMBEDDED_RUNTIME_WINDOWS_SAFETY_CAPS).filter(([key]) => {
|
|
2068
|
+
const existing = env[key];
|
|
2069
|
+
return typeof existing !== "string" || existing.trim().length === 0;
|
|
2070
|
+
})) : {};
|
|
1955
2071
|
return {
|
|
1956
2072
|
...inherited,
|
|
1957
2073
|
PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
|
|
@@ -1966,12 +2082,50 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
|
|
|
1966
2082
|
},
|
|
1967
2083
|
PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
|
|
1968
2084
|
...typeof opts.sessionId === "string" && opts.sessionId.trim() ? { PUSHPALS_SESSION_ID: opts.sessionId.trim() } : {},
|
|
2085
|
+
...embeddedBunBin ? { PUSHPALS_BUN_BIN: embeddedBunBin } : {},
|
|
2086
|
+
...embeddedWindowsSafetyCaps,
|
|
1969
2087
|
...typeof env.PUSHPALS_GIT_BIN === "string" && env.PUSHPALS_GIT_BIN.trim() ? { PUSHPALS_GIT_BIN: env.PUSHPALS_GIT_BIN.trim() } : {},
|
|
1970
2088
|
...typeof env.PUSHPALS_GIT_BIN_ABSOLUTE === "string" && env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() ? { PUSHPALS_GIT_BIN_ABSOLUTE: env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() } : {},
|
|
1971
2089
|
...typeof env.PUSHPALS_DOCKER_BIN === "string" && env.PUSHPALS_DOCKER_BIN.trim() ? { PUSHPALS_DOCKER_BIN: env.PUSHPALS_DOCKER_BIN.trim() } : {},
|
|
1972
2090
|
...typeof env.PUSHPALS_DOCKER_BIN_ABSOLUTE === "string" && env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() ? { PUSHPALS_DOCKER_BIN_ABSOLUTE: env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() } : {}
|
|
1973
2091
|
};
|
|
1974
2092
|
}
|
|
2093
|
+
function parseBooleanFlag(raw) {
|
|
2094
|
+
const normalized = String(raw ?? "").trim().toLowerCase();
|
|
2095
|
+
if (!normalized)
|
|
2096
|
+
return null;
|
|
2097
|
+
if (["1", "true", "yes", "on", "y"].includes(normalized))
|
|
2098
|
+
return true;
|
|
2099
|
+
if (["0", "false", "no", "off", "n"].includes(normalized))
|
|
2100
|
+
return false;
|
|
2101
|
+
return null;
|
|
2102
|
+
}
|
|
2103
|
+
function resolveEmbeddedBunExecutableFromEnv(env, platform = process.platform, currentExecPathOverride) {
|
|
2104
|
+
const explicit = String(env.PUSHPALS_BUN_BIN ?? "").trim();
|
|
2105
|
+
if (explicit)
|
|
2106
|
+
return explicit;
|
|
2107
|
+
const currentExecPath = String(currentExecPathOverride ?? process.execPath ?? "").trim();
|
|
2108
|
+
const currentExecLeaf = basename(currentExecPath).toLowerCase();
|
|
2109
|
+
if (currentExecLeaf === "bun" || currentExecLeaf === "bun.exe") {
|
|
2110
|
+
return currentExecPath;
|
|
2111
|
+
}
|
|
2112
|
+
const pathValue = platform === "win32" ? String(env.PATH ?? env.Path ?? "").trim() : String(env.PATH ?? "").trim();
|
|
2113
|
+
if (!pathValue)
|
|
2114
|
+
return "";
|
|
2115
|
+
const candidates = platform === "win32" ? ["bun.exe", "bun", "bun.cmd", "bun.bat"] : ["bun"];
|
|
2116
|
+
for (const rawDir of pathValue.split(delimiter)) {
|
|
2117
|
+
const dir = rawDir.trim();
|
|
2118
|
+
if (!dir)
|
|
2119
|
+
continue;
|
|
2120
|
+
for (const candidate of candidates) {
|
|
2121
|
+
const fullPath = join2(dir, candidate);
|
|
2122
|
+
if (existsSync4(fullPath)) {
|
|
2123
|
+
return fullPath;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
return "";
|
|
2128
|
+
}
|
|
1975
2129
|
function normalizeChildProcessEnv(baseEnv, platform = process.platform) {
|
|
1976
2130
|
const env = {};
|
|
1977
2131
|
for (const [key, value] of Object.entries(baseEnv)) {
|
|
@@ -2840,6 +2994,12 @@ async function precheckWorkerpalDockerAvailability(opts) {
|
|
|
2840
2994
|
function resolveWorkerpalCapacityTimeoutMs(config) {
|
|
2841
2995
|
return Math.max(config.remotebuddy.waitForWorkerpalMs, config.remotebuddy.workerpalStartupTimeoutMs, config.remotebuddy.workerpalDocker ? config.workerpals.dockerAgentStartupTimeoutMs + 15000 : 0, 1e4);
|
|
2842
2996
|
}
|
|
2997
|
+
function resolveWorkerpalStartupReadinessProbeTimeoutMs(config) {
|
|
2998
|
+
return Math.max(5000, Math.min(resolveWorkerpalCapacityTimeoutMs(config), WORKERPAL_STARTUP_READINESS_PROBE_MAX_MS));
|
|
2999
|
+
}
|
|
3000
|
+
function shouldRunEmbeddedRuntimeStartupPrechecks(opts) {
|
|
3001
|
+
return !opts.serverHealthy && !opts.noAutoStart;
|
|
3002
|
+
}
|
|
2843
3003
|
async function checkGitRemoteConfigured(repoRoot, remote, env) {
|
|
2844
3004
|
const normalizedRemote = String(remote ?? "").trim();
|
|
2845
3005
|
if (!normalizedRemote) {
|
|
@@ -3193,15 +3353,14 @@ async function waitForWorkerpalCapacity(opts) {
|
|
|
3193
3353
|
let lastObservedOnline = 0;
|
|
3194
3354
|
while (Date.now() < deadline) {
|
|
3195
3355
|
const workers = await (opts.fetchWorkersFn ?? fetchWorkerStatusRows)(opts.serverUrl, opts.ttlMs);
|
|
3196
|
-
const
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
lastObservedOnline = Math.max(lastObservedOnline, onlineWorkers.length);
|
|
3356
|
+
const summary = summarizeWorkerStatusRows(workers);
|
|
3357
|
+
if (summary.onlineWorkers > 0) {
|
|
3358
|
+
lastObservedOnline = Math.max(lastObservedOnline, summary.onlineWorkers);
|
|
3200
3359
|
}
|
|
3201
|
-
if (idleWorkers
|
|
3360
|
+
if (summary.idleWorkers > 0) {
|
|
3202
3361
|
return {
|
|
3203
3362
|
ok: true,
|
|
3204
|
-
detail: `${idleWorkers
|
|
3363
|
+
detail: `${summary.idleWorkers} idle / ${summary.onlineWorkers} online`
|
|
3205
3364
|
};
|
|
3206
3365
|
}
|
|
3207
3366
|
await (opts.sleepFn ?? Bun.sleep)(DEFAULT_RUNTIME_BOOT_POLL_MS);
|
|
@@ -3324,6 +3483,29 @@ async function autoStartRuntimeServices(opts) {
|
|
|
3324
3483
|
applyResolvedGitBinaryToRuntimeEnv(runtimeEnv, resolvedGitBinary);
|
|
3325
3484
|
}
|
|
3326
3485
|
const services = [];
|
|
3486
|
+
const startupStartedAt = Date.now();
|
|
3487
|
+
const startupPhases = [];
|
|
3488
|
+
const recordStartupPhase = (name, startedAt, status) => {
|
|
3489
|
+
startupPhases.push({
|
|
3490
|
+
name,
|
|
3491
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
3492
|
+
status
|
|
3493
|
+
});
|
|
3494
|
+
};
|
|
3495
|
+
const emitStartupTimingSummary = (outcome, detail) => {
|
|
3496
|
+
const summary = formatRuntimeStartupTimingSummary({
|
|
3497
|
+
outcome,
|
|
3498
|
+
totalDurationMs: Date.now() - startupStartedAt,
|
|
3499
|
+
phases: startupPhases,
|
|
3500
|
+
detail
|
|
3501
|
+
});
|
|
3502
|
+
if (outcome === "failed") {
|
|
3503
|
+
console.warn(summary);
|
|
3504
|
+
} else {
|
|
3505
|
+
console.log(summary);
|
|
3506
|
+
}
|
|
3507
|
+
appendRuntimeServicesLogLine(runtimeServicesLogPath, summary);
|
|
3508
|
+
};
|
|
3327
3509
|
const runToken = timestampFileToken();
|
|
3328
3510
|
const logDir = join2(runtimeRoot, "logs", "bootstrap");
|
|
3329
3511
|
mkdirSync(logDir, { recursive: true });
|
|
@@ -3341,6 +3523,7 @@ async function autoStartRuntimeServices(opts) {
|
|
|
3341
3523
|
console.log(`[pushpals] service log (source_control_manager)=${serviceLogPaths.source_control_manager}`);
|
|
3342
3524
|
const serverHealthy = await probeServer(opts.serverUrl);
|
|
3343
3525
|
if (!serverHealthy) {
|
|
3526
|
+
const serverPhaseStartedAt = Date.now();
|
|
3344
3527
|
console.log("[pushpals] Starting embedded server...");
|
|
3345
3528
|
const serverService = spawnRuntimeService("server", [runtimeBinaries.server], opts.repoRoot, runtimeEnv, serviceLogPaths.server, runtimeServicesLogPath);
|
|
3346
3529
|
services.push(serverService);
|
|
@@ -3351,6 +3534,8 @@ async function autoStartRuntimeServices(opts) {
|
|
|
3351
3534
|
if (serverService.exited) {
|
|
3352
3535
|
const tail = readLogTail(serverService.logPath);
|
|
3353
3536
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}).`);
|
|
3537
|
+
recordStartupPhase("server", serverPhaseStartedAt, "exited");
|
|
3538
|
+
emitStartupTimingSummary("failed", "server exited during bootstrap");
|
|
3354
3539
|
stopRuntimeServices(services);
|
|
3355
3540
|
throw new Error(`Embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}). ` + `See ${serverService.logPath}${tail ? `
|
|
3356
3541
|
--- server log tail ---
|
|
@@ -3365,29 +3550,38 @@ ${tail}` : ""}`);
|
|
|
3365
3550
|
if (!serverIsReady) {
|
|
3366
3551
|
const tail = readLogTail(serverService.logPath);
|
|
3367
3552
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms.`);
|
|
3553
|
+
recordStartupPhase("server", serverPhaseStartedAt, "timeout");
|
|
3554
|
+
emitStartupTimingSummary("failed", "server health timeout");
|
|
3368
3555
|
stopRuntimeServices(services);
|
|
3369
3556
|
throw new Error(`Embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms. ` + `See ${serverService.logPath}${tail ? `
|
|
3370
3557
|
--- server log tail ---
|
|
3371
3558
|
${tail}` : ""}`);
|
|
3372
3559
|
}
|
|
3560
|
+
recordStartupPhase("server", serverPhaseStartedAt, "started");
|
|
3373
3561
|
console.log("[pushpals] Embedded server is healthy.");
|
|
3374
3562
|
} else {
|
|
3563
|
+
recordStartupPhase("server", Date.now(), "reused");
|
|
3375
3564
|
console.log("[pushpals] Server already healthy; skipping embedded server start.");
|
|
3376
3565
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] server already healthy; embedded server start skipped.");
|
|
3377
3566
|
}
|
|
3378
3567
|
if (localBuddyEnabled) {
|
|
3568
|
+
const localBuddyPhaseStartedAt = Date.now();
|
|
3379
3569
|
console.log("[pushpals] Starting embedded LocalBuddy...");
|
|
3380
3570
|
const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, serviceLogPaths.localbuddy, runtimeServicesLogPath);
|
|
3381
3571
|
services.push(localbuddyService);
|
|
3382
3572
|
console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
|
|
3573
|
+
recordStartupPhase("localbuddy", localBuddyPhaseStartedAt, "started");
|
|
3383
3574
|
} else {
|
|
3575
|
+
recordStartupPhase("localbuddy", Date.now(), "skipped");
|
|
3384
3576
|
console.log("[pushpals] Embedded LocalBuddy disabled for this CLI session; skipping start.");
|
|
3385
3577
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] localbuddy disabled for this CLI session; embedded localbuddy start skipped.");
|
|
3386
3578
|
}
|
|
3579
|
+
const remoteBuddyPhaseStartedAt = Date.now();
|
|
3387
3580
|
console.log("[pushpals] Starting embedded RemoteBuddy...");
|
|
3388
3581
|
const remotebuddyService = spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv, serviceLogPaths.remotebuddy, runtimeServicesLogPath);
|
|
3389
3582
|
services.push(remotebuddyService);
|
|
3390
3583
|
console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
|
|
3584
|
+
recordStartupPhase("remotebuddy", remoteBuddyPhaseStartedAt, "started");
|
|
3391
3585
|
let lastReportedRemoteBuddyAutonomyState = "unknown";
|
|
3392
3586
|
const reportRemoteBuddyAutonomousEngineState = () => {
|
|
3393
3587
|
const autonomyState = readRemoteBuddyAutonomousEngineState(remotebuddyService.logPath);
|
|
@@ -3405,48 +3599,58 @@ ${tail}` : ""}`);
|
|
|
3405
3599
|
};
|
|
3406
3600
|
reportRemoteBuddyAutonomousEngineState();
|
|
3407
3601
|
if (runtimePreflight.config.remotebuddy.autoSpawnWorkerpals) {
|
|
3408
|
-
const
|
|
3602
|
+
const workerpalPhaseStartedAt = Date.now();
|
|
3603
|
+
const workerpalReadinessProbeTimeoutMs = resolveWorkerpalStartupReadinessProbeTimeoutMs(runtimePreflight.config);
|
|
3409
3604
|
const workerpalCapacity = await waitForWorkerpalCapacity({
|
|
3410
3605
|
serverUrl: opts.serverUrl,
|
|
3411
|
-
timeoutMs:
|
|
3606
|
+
timeoutMs: workerpalReadinessProbeTimeoutMs,
|
|
3412
3607
|
ttlMs: runtimePreflight.config.remotebuddy.workerpalOnlineTtlMs
|
|
3413
3608
|
});
|
|
3414
3609
|
if (!workerpalCapacity.ok) {
|
|
3415
|
-
const
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3610
|
+
const startupProbeWarning = `embedded workerpal readiness probe did not find idle capacity within ${workerpalReadinessProbeTimeoutMs}ms ` + `(${workerpalCapacity.detail}); continuing startup while WorkerPal warmup finishes in the background.`;
|
|
3611
|
+
console.warn(`[pushpals] ${startupProbeWarning}`);
|
|
3612
|
+
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] ${startupProbeWarning}`);
|
|
3613
|
+
recordStartupPhase("workerpal", workerpalPhaseStartedAt, "deferred");
|
|
3614
|
+
} else {
|
|
3615
|
+
console.log(`[pushpals] Embedded WorkerPal capacity is ready (${workerpalCapacity.detail}).`);
|
|
3616
|
+
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded workerpal capacity ready (${workerpalCapacity.detail}).`);
|
|
3617
|
+
recordStartupPhase("workerpal", workerpalPhaseStartedAt, "ready");
|
|
3421
3618
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3619
|
+
} else {
|
|
3620
|
+
recordStartupPhase("workerpal", Date.now(), "disabled");
|
|
3424
3621
|
}
|
|
3425
3622
|
const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
|
|
3426
3623
|
const scmGitProbe = await resolveSourceControlManagerGitProbe(opts.repoRoot, runtimeEnv, process.platform);
|
|
3427
3624
|
const scmRemoteStatus = await checkGitRemoteConfigured(opts.repoRoot, opts.sourceControlManagerRemote, runtimeEnv);
|
|
3428
3625
|
if (!scmHealthy) {
|
|
3626
|
+
const scmPhaseStartedAt = Date.now();
|
|
3429
3627
|
if (!scmGitProbe.ok) {
|
|
3430
3628
|
console.warn("[pushpals] Git is not available to embedded SourceControlManager; skipping SCM startup.");
|
|
3431
3629
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: git is unavailable in embedded runtime env (${scmGitProbe.detail}).`);
|
|
3630
|
+
recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_git");
|
|
3432
3631
|
} else if (scmRemoteStatus.status === "error") {
|
|
3433
3632
|
console.warn(`[pushpals] Could not inspect SourceControlManager git remote "${opts.sourceControlManagerRemote}"; skipping SCM startup.`);
|
|
3434
3633
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: remote "${opts.sourceControlManagerRemote}" could not be inspected (${scmRemoteStatus.detail}).`);
|
|
3634
|
+
recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_remote_error");
|
|
3435
3635
|
} else if (scmRemoteStatus.status === "ok") {
|
|
3436
3636
|
console.log(`[pushpals] Embedded SourceControlManager git=${scmGitProbe.detail}`);
|
|
3437
3637
|
console.log("[pushpals] Starting embedded SourceControlManager...");
|
|
3438
3638
|
const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, serviceLogPaths.source_control_manager, runtimeServicesLogPath);
|
|
3439
3639
|
services.push(sourceControlManagerService);
|
|
3440
3640
|
console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
|
|
3641
|
+
recordStartupPhase("source_control_manager", scmPhaseStartedAt, "started");
|
|
3441
3642
|
} else {
|
|
3442
3643
|
console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
|
|
3443
3644
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: repo has no remote "${opts.sourceControlManagerRemote}".`);
|
|
3645
|
+
recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_remote");
|
|
3444
3646
|
}
|
|
3445
3647
|
} else {
|
|
3648
|
+
recordStartupPhase("source_control_manager", Date.now(), "reused");
|
|
3446
3649
|
console.log("[pushpals] SourceControlManager already healthy; skipping embedded start.");
|
|
3447
3650
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] source_control_manager already healthy; embedded start skipped.");
|
|
3448
3651
|
}
|
|
3449
3652
|
const deadline = Date.now() + DEFAULT_RUNTIME_BOOT_TIMEOUT_MS;
|
|
3653
|
+
const readinessPhaseStartedAt = Date.now();
|
|
3450
3654
|
while (Date.now() < deadline) {
|
|
3451
3655
|
reportRemoteBuddyAutonomousEngineState();
|
|
3452
3656
|
for (let i = services.length - 1;i >= 0; i--) {
|
|
@@ -3467,6 +3671,8 @@ ${tail2}`);
|
|
|
3467
3671
|
}
|
|
3468
3672
|
const tail = readLogTail(service.logPath);
|
|
3469
3673
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}).`);
|
|
3674
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "failed");
|
|
3675
|
+
emitStartupTimingSummary("failed", `${service.name} exited during startup`);
|
|
3470
3676
|
stopRuntimeServices(services);
|
|
3471
3677
|
throw new Error(`Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
|
|
3472
3678
|
--- ${service.name} log tail ---
|
|
@@ -3499,6 +3705,8 @@ ${tail2}`);
|
|
|
3499
3705
|
}
|
|
3500
3706
|
const tail = readLogTail(service.logPath);
|
|
3501
3707
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}).`);
|
|
3708
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "failed");
|
|
3709
|
+
emitStartupTimingSummary("failed", `${service.name} exited immediately after bootstrap`);
|
|
3502
3710
|
stopRuntimeServices(services);
|
|
3503
3711
|
throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
|
|
3504
3712
|
--- ${service.name} log tail ---
|
|
@@ -3507,6 +3715,8 @@ ${tail}` : ""}`);
|
|
|
3507
3715
|
await Bun.sleep(250);
|
|
3508
3716
|
}
|
|
3509
3717
|
console.log("[pushpals] Embedded runtime is ready.");
|
|
3718
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "ready");
|
|
3719
|
+
emitStartupTimingSummary("ready");
|
|
3510
3720
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] embedded runtime is ready.");
|
|
3511
3721
|
return {
|
|
3512
3722
|
services,
|
|
@@ -3519,13 +3729,19 @@ ${tail}` : ""}`);
|
|
|
3519
3729
|
const remoteBuddyHealth = await probeRemoteBuddySessionConsumer(opts.serverUrl, opts.sessionId);
|
|
3520
3730
|
if (!localBuddyEnabled && !remoteBuddyHealth.ok) {
|
|
3521
3731
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for RemoteBuddy session consumer readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms (${remoteBuddyHealth.detail}).`);
|
|
3732
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
|
|
3733
|
+
emitStartupTimingSummary("failed", "remotebuddy readiness timeout");
|
|
3522
3734
|
throw new Error(`Timed out waiting for RemoteBuddy session consumer readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms (${remoteBuddyHealth.detail})`);
|
|
3523
3735
|
}
|
|
3524
3736
|
if (!localBuddyEnabled) {
|
|
3525
3737
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms.`);
|
|
3738
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
|
|
3739
|
+
emitStartupTimingSummary("failed", "embedded runtime readiness timeout");
|
|
3526
3740
|
throw new Error(`Timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
3527
3741
|
}
|
|
3528
3742
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for LocalBuddy at ${opts.localAgentUrl} and RemoteBuddy session consumer after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms.`);
|
|
3743
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
|
|
3744
|
+
emitStartupTimingSummary("failed", "localbuddy and remotebuddy readiness timeout");
|
|
3529
3745
|
throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} and RemoteBuddy session consumer after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
3530
3746
|
}
|
|
3531
3747
|
function readCliState(pathValue) {
|
|
@@ -4127,32 +4343,59 @@ async function main() {
|
|
|
4127
4343
|
} else {
|
|
4128
4344
|
console.warn("[pushpals] RemoteBuddy autonomy is disabled in config (remotebuddy.autonomy.enabled=false); continuing.");
|
|
4129
4345
|
}
|
|
4130
|
-
const scmGitPrecheck = await precheckSourceControlManagerGitAvailability({
|
|
4131
|
-
repoRoot,
|
|
4132
|
-
remote: config.sourceControlManager.remote,
|
|
4133
|
-
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4134
|
-
preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime
|
|
4135
|
-
});
|
|
4136
|
-
if (scmGitPrecheck.status === "failed") {
|
|
4137
|
-
console.error(`[pushpals] Precheck failed: embedded SourceControlManager git command is unavailable (${scmGitPrecheck.detail}).`);
|
|
4138
|
-
process.exit(1);
|
|
4139
|
-
}
|
|
4140
|
-
const workerpalDockerPrecheck = await precheckWorkerpalDockerAvailability({
|
|
4141
|
-
repoRoot,
|
|
4142
|
-
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4143
|
-
preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4144
|
-
autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
|
|
4145
|
-
dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
|
|
4146
|
-
requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
|
|
4147
|
-
baseEnv: scmGitPrecheck.env
|
|
4148
|
-
});
|
|
4149
|
-
const precheckPassed = await enforcePushpalsRemoteBranchPrecheck(repoRoot, config.sourceControlManager.remote, config.sourceControlManager.mainBranch);
|
|
4150
|
-
if (!precheckPassed) {
|
|
4151
|
-
process.exit(1);
|
|
4152
|
-
}
|
|
4153
4346
|
const serverUrl = normalizeLoopbackUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
|
|
4154
4347
|
const localAgentUrl = normalizeLoopbackUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
|
|
4155
4348
|
const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
|
|
4349
|
+
let serverHealthy = await probeServer(serverUrl);
|
|
4350
|
+
const serverWasAlreadyHealthy = serverHealthy;
|
|
4351
|
+
const runEmbeddedRuntimeStartupPrechecks = shouldRunEmbeddedRuntimeStartupPrechecks({
|
|
4352
|
+
serverHealthy,
|
|
4353
|
+
noAutoStart: parsed.noAutoStart
|
|
4354
|
+
});
|
|
4355
|
+
if (runEmbeddedRuntimeStartupPrechecks) {
|
|
4356
|
+
const precheckPassed = await enforcePushpalsRemoteBranchPrecheck(repoRoot, config.sourceControlManager.remote, config.sourceControlManager.mainBranch);
|
|
4357
|
+
if (!precheckPassed) {
|
|
4358
|
+
process.exit(1);
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
let scmGitPrecheck = {
|
|
4362
|
+
status: "skipped",
|
|
4363
|
+
detail: runEmbeddedRuntimeStartupPrechecks ? "embedded SourceControlManager startup precheck not run" : serverHealthy ? "embedded SourceControlManager startup precheck skipped because runtime is already healthy" : "embedded SourceControlManager startup precheck skipped because auto-start is disabled",
|
|
4364
|
+
env: buildEmbeddedRuntimeEnv(process.env, {
|
|
4365
|
+
repoRoot,
|
|
4366
|
+
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4367
|
+
useRuntimeConfig: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4368
|
+
sessionId
|
|
4369
|
+
})
|
|
4370
|
+
};
|
|
4371
|
+
let workerpalDockerPrecheck = {
|
|
4372
|
+
status: "skipped",
|
|
4373
|
+
detail: runEmbeddedRuntimeStartupPrechecks ? "embedded WorkerPal Docker startup precheck not run" : serverHealthy ? "embedded WorkerPal Docker startup precheck skipped because runtime is already healthy" : "embedded WorkerPal Docker startup precheck skipped because auto-start is disabled",
|
|
4374
|
+
env: scmGitPrecheck.env
|
|
4375
|
+
};
|
|
4376
|
+
if (runEmbeddedRuntimeStartupPrechecks) {
|
|
4377
|
+
scmGitPrecheck = await precheckSourceControlManagerGitAvailability({
|
|
4378
|
+
repoRoot,
|
|
4379
|
+
remote: config.sourceControlManager.remote,
|
|
4380
|
+
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4381
|
+
preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4382
|
+
sessionId
|
|
4383
|
+
});
|
|
4384
|
+
if (scmGitPrecheck.status === "failed") {
|
|
4385
|
+
console.error(`[pushpals] Precheck failed: embedded SourceControlManager git command is unavailable (${scmGitPrecheck.detail}).`);
|
|
4386
|
+
process.exit(1);
|
|
4387
|
+
}
|
|
4388
|
+
workerpalDockerPrecheck = await precheckWorkerpalDockerAvailability({
|
|
4389
|
+
repoRoot,
|
|
4390
|
+
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4391
|
+
preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4392
|
+
autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
|
|
4393
|
+
dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
|
|
4394
|
+
requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
|
|
4395
|
+
sessionId,
|
|
4396
|
+
baseEnv: scmGitPrecheck.env
|
|
4397
|
+
});
|
|
4398
|
+
}
|
|
4156
4399
|
const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
|
|
4157
4400
|
const cliClient = {
|
|
4158
4401
|
clientId: createRuntimeClientId("cli"),
|
|
@@ -4196,6 +4439,31 @@ async function main() {
|
|
|
4196
4439
|
console.log(`[pushpals] ${cleanup.detail} (${phase}).`);
|
|
4197
4440
|
}
|
|
4198
4441
|
};
|
|
4442
|
+
const reportWorkerExecutionReadiness = async () => {
|
|
4443
|
+
const readiness = await resolveWorkerExecutionReadiness({
|
|
4444
|
+
serverUrl,
|
|
4445
|
+
ttlMs: config.remotebuddy.workerpalOnlineTtlMs,
|
|
4446
|
+
autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
|
|
4447
|
+
dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
|
|
4448
|
+
requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
|
|
4449
|
+
repoRoot,
|
|
4450
|
+
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4451
|
+
preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4452
|
+
sessionId,
|
|
4453
|
+
dockerPrecheck: workerpalDockerPrecheck,
|
|
4454
|
+
baseEnv: workerpalDockerPrecheck.env
|
|
4455
|
+
});
|
|
4456
|
+
for (const line of formatWorkerExecutionReadinessLines(readiness)) {
|
|
4457
|
+
console.log(line);
|
|
4458
|
+
}
|
|
4459
|
+
return readiness;
|
|
4460
|
+
};
|
|
4461
|
+
const reportWorkerExecutionReadinessFromSnapshot = (readiness) => {
|
|
4462
|
+
for (const line of formatWorkerExecutionReadinessLines(readiness)) {
|
|
4463
|
+
console.log(line);
|
|
4464
|
+
}
|
|
4465
|
+
return readiness;
|
|
4466
|
+
};
|
|
4199
4467
|
const stopAutoStartedServices = () => {
|
|
4200
4468
|
if (autoStartedServices.length === 0)
|
|
4201
4469
|
return;
|
|
@@ -4220,14 +4488,12 @@ async function main() {
|
|
|
4220
4488
|
await cleanupWorkerpalWarmContainersIfNeeded("cli shutdown");
|
|
4221
4489
|
await cleanupPushPalsGitWorktreesIfNeeded("cli shutdown");
|
|
4222
4490
|
};
|
|
4223
|
-
let serverHealthy = await probeServer(serverUrl);
|
|
4224
|
-
const serverWasAlreadyHealthy = serverHealthy;
|
|
4225
4491
|
if (!serverHealthy && workerpalDockerPrecheck.status === "failed") {
|
|
4226
4492
|
console.error(`[pushpals] Precheck failed: Docker-backed WorkerPal auto-spawn is required but Docker is unavailable (${workerpalDockerPrecheck.detail}).`);
|
|
4227
4493
|
console.error("[pushpals] Precheck failed: start Docker Desktop or the Docker daemon, then retry pushpals.");
|
|
4228
4494
|
process.exit(1);
|
|
4229
4495
|
}
|
|
4230
|
-
if (workerpalDockerPrecheck.status !== "failed") {
|
|
4496
|
+
if (runEmbeddedRuntimeStartupPrechecks && workerpalDockerPrecheck.status !== "failed") {
|
|
4231
4497
|
const workerpalImagePrecheck = await prepareEmbeddedWorkerpalDockerImageIfNeeded({
|
|
4232
4498
|
preparedRuntime,
|
|
4233
4499
|
config,
|
|
@@ -4247,8 +4513,10 @@ async function main() {
|
|
|
4247
4513
|
detail: `No connected RemoteBuddy session consumer found for session ${sessionId}`
|
|
4248
4514
|
};
|
|
4249
4515
|
if (!serverHealthy) {
|
|
4250
|
-
|
|
4251
|
-
|
|
4516
|
+
if (runEmbeddedRuntimeStartupPrechecks) {
|
|
4517
|
+
await cleanupWorkerpalWarmContainersIfNeeded("startup preflight");
|
|
4518
|
+
await cleanupPushPalsGitWorktreesIfNeeded("startup preflight");
|
|
4519
|
+
}
|
|
4252
4520
|
if (!parsed.noAutoStart) {
|
|
4253
4521
|
try {
|
|
4254
4522
|
const startedRuntime = await autoStartRuntimeServices({
|
|
@@ -4269,6 +4537,10 @@ async function main() {
|
|
|
4269
4537
|
} catch (err) {
|
|
4270
4538
|
console.error(`[pushpals] Auto-start failed: ${String(err)}`);
|
|
4271
4539
|
stopAutoStartedServices();
|
|
4540
|
+
if (runEmbeddedRuntimeStartupPrechecks) {
|
|
4541
|
+
await cleanupWorkerpalWarmContainersIfNeeded("startup failure cleanup");
|
|
4542
|
+
await cleanupPushPalsGitWorktreesIfNeeded("startup failure cleanup");
|
|
4543
|
+
}
|
|
4272
4544
|
}
|
|
4273
4545
|
}
|
|
4274
4546
|
if (!serverHealthy) {
|
|
@@ -4318,20 +4590,33 @@ async function main() {
|
|
|
4318
4590
|
}
|
|
4319
4591
|
const workerpalCapacity = await waitForWorkerpalCapacity({
|
|
4320
4592
|
serverUrl,
|
|
4321
|
-
timeoutMs:
|
|
4593
|
+
timeoutMs: resolveWorkerpalStartupReadinessProbeTimeoutMs(config),
|
|
4322
4594
|
ttlMs: config.remotebuddy.workerpalOnlineTtlMs
|
|
4323
4595
|
});
|
|
4324
4596
|
if (!workerpalCapacity.ok) {
|
|
4325
|
-
|
|
4326
|
-
console.
|
|
4597
|
+
console.warn(`[pushpals] WorkerPal readiness probe did not find idle capacity yet (${workerpalCapacity.detail}).`);
|
|
4598
|
+
console.warn("[pushpals] Continuing startup; WorkerPal warmup may still be in progress and first task dispatch can be delayed.");
|
|
4327
4599
|
if (workerpalDockerPrecheck.status === "failed") {
|
|
4328
|
-
console.
|
|
4600
|
+
console.warn(`[pushpals] Docker precheck detail: ${workerpalDockerPrecheck.detail}`);
|
|
4329
4601
|
} else if (serverWasAlreadyHealthy) {
|
|
4330
|
-
console.
|
|
4331
|
-
console.
|
|
4602
|
+
console.warn("[pushpals] A PushPals runtime is already serving this repo, but it does not currently have an idle WorkerPal available.");
|
|
4603
|
+
console.warn("[pushpals] Wait for a worker to become idle or restart the runtime after fixing WorkerPal startup.");
|
|
4332
4604
|
}
|
|
4333
|
-
process.exit(1);
|
|
4334
4605
|
}
|
|
4606
|
+
const startupWorkerExecutionReadiness = workerpalCapacity.ok ? {
|
|
4607
|
+
state: "ready",
|
|
4608
|
+
detail: workerpalCapacity.detail
|
|
4609
|
+
} : workerpalDockerPrecheck.status === "failed" ? describeWorkerExecutionReadiness({
|
|
4610
|
+
autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
|
|
4611
|
+
requireDocker: Boolean(config.remotebuddy.workerpalDocker) && Boolean(config.remotebuddy.workerpalRequireDocker),
|
|
4612
|
+
dockerPrecheck: workerpalDockerPrecheck,
|
|
4613
|
+
onlineWorkers: 0,
|
|
4614
|
+
idleWorkers: 0
|
|
4615
|
+
}) : {
|
|
4616
|
+
state: "warming",
|
|
4617
|
+
detail: workerpalCapacity.detail,
|
|
4618
|
+
action: "Wait for WorkerPal auto-spawn/warmup to finish, then rerun /status."
|
|
4619
|
+
};
|
|
4335
4620
|
const saved = statePath ? readCliState(statePath) : {};
|
|
4336
4621
|
pushpalsLogPath = pushpalsLogPath || (typeof saved.pushpalsLogPath === "string" ? saved.pushpalsLogPath : undefined);
|
|
4337
4622
|
const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
|
|
@@ -4369,6 +4654,7 @@ async function main() {
|
|
|
4369
4654
|
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
4370
4655
|
console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
|
|
4371
4656
|
console.log(`[pushpals] cliStateFile=${statePath ?? "unavailable"}`);
|
|
4657
|
+
reportWorkerExecutionReadinessFromSnapshot(startupWorkerExecutionReadiness);
|
|
4372
4658
|
if (parsed.runtimeOnly) {
|
|
4373
4659
|
console.log("[pushpals] runtimeOnly=true");
|
|
4374
4660
|
} else {
|
|
@@ -4480,6 +4766,7 @@ ${line}
|
|
|
4480
4766
|
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
4481
4767
|
console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
|
|
4482
4768
|
console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
|
|
4769
|
+
await reportWorkerExecutionReadiness();
|
|
4483
4770
|
rl.prompt();
|
|
4484
4771
|
continue;
|
|
4485
4772
|
}
|
|
@@ -4518,11 +4805,14 @@ if (import.meta.main) {
|
|
|
4518
4805
|
export {
|
|
4519
4806
|
waitForWorkerpalCapacity,
|
|
4520
4807
|
startEmbeddedMonitoringHub,
|
|
4808
|
+
shouldRunEmbeddedRuntimeStartupPrechecks,
|
|
4809
|
+
resolveWorkerExecutionReadiness,
|
|
4521
4810
|
resolveWindowsWhereExecutableCandidatesForEnv,
|
|
4522
4811
|
resolveWindowsShellExecutableCandidatesForEnv,
|
|
4523
4812
|
resolveRuntimeGitExecutableCandidates,
|
|
4524
4813
|
resolveRuntimeDockerExecutableCandidates,
|
|
4525
4814
|
resolvePreferredRuntimeReleaseTag,
|
|
4815
|
+
resolveEmbeddedBunExecutableFromEnv,
|
|
4526
4816
|
resolveCommandPath,
|
|
4527
4817
|
resolveCliStatePath,
|
|
4528
4818
|
resolveCliLocalBuddyAutostart,
|
|
@@ -4538,13 +4828,16 @@ export {
|
|
|
4538
4828
|
normalizeChildProcessEnv,
|
|
4539
4829
|
isCliExitCommand,
|
|
4540
4830
|
injectMonitoringHubBootstrap,
|
|
4831
|
+
formatWorkerExecutionReadinessLines,
|
|
4541
4832
|
formatTimestampedCliLine,
|
|
4542
4833
|
formatSessionEventLine,
|
|
4834
|
+
formatRuntimeStartupTimingSummary,
|
|
4543
4835
|
extractRemoteBuddySessionConsumerHealth,
|
|
4544
4836
|
extractRemoteBuddyAutonomousEngineState,
|
|
4545
4837
|
ensureWorkerpalDockerImageReady,
|
|
4546
4838
|
ensureRuntimeBinaries,
|
|
4547
4839
|
downloadRuntimeAssetsFromSourceTag,
|
|
4840
|
+
describeWorkerExecutionReadiness,
|
|
4548
4841
|
createSessionEventReplayFilter,
|
|
4549
4842
|
copyTrackedRepoPath,
|
|
4550
4843
|
cleanupLingeringWorkerpalWarmContainers,
|
package/package.json
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"format:check": "prettier --check .",
|
|
45
45
|
"test:prompt-policy": "bun test tests/prompt-policy.enforcement.test.ts",
|
|
46
46
|
"test:cli:integration": "bun test tests/cli.invocation-logging.test.ts tests/cli.runtime-bootstrap.test.ts tests/client.runtime-bootstrap.test.ts tests/shared.client-preflight.test.ts",
|
|
47
|
+
"test:cli:e2e": "bun test ./tests/integration/cli.e2e.ts",
|
|
47
48
|
"test:root": "bun test tests",
|
|
48
49
|
"test:protocol": "bun run tests/protocol.integration.ts",
|
|
49
50
|
"test:integration": "python -u tests/integration/integration_controller.py --mode integration",
|