@pushpalsdev/cli 1.0.30 → 1.0.31
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 +316 -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,19 @@ 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;
|
|
1949
2058
|
const inherited = { ...env };
|
|
1950
2059
|
if (!useRuntimeConfig) {
|
|
1951
2060
|
delete inherited.PUSHPALS_CONFIG_DIR_OVERRIDE;
|
|
1952
2061
|
delete inherited.PUSHPALS_WORKERPALS_SANDBOX_ROOT;
|
|
1953
2062
|
delete inherited.PUSHPALS_RUNTIME_TAG;
|
|
1954
2063
|
}
|
|
2064
|
+
const disableEmbeddedSafetyCaps = parseBooleanFlag(env[EMBEDDED_RUNTIME_SAFETY_CAP_DISABLE_ENV]) === true;
|
|
2065
|
+
const shouldApplyEmbeddedWindowsSafetyCaps = useRuntimeConfig && platform === "win32" && !disableEmbeddedSafetyCaps;
|
|
2066
|
+
const embeddedWindowsSafetyCaps = shouldApplyEmbeddedWindowsSafetyCaps ? Object.fromEntries(Object.entries(EMBEDDED_RUNTIME_WINDOWS_SAFETY_CAPS).filter(([key]) => {
|
|
2067
|
+
const existing = env[key];
|
|
2068
|
+
return typeof existing !== "string" || existing.trim().length === 0;
|
|
2069
|
+
})) : {};
|
|
1955
2070
|
return {
|
|
1956
2071
|
...inherited,
|
|
1957
2072
|
PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
|
|
@@ -1966,12 +2081,23 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
|
|
|
1966
2081
|
},
|
|
1967
2082
|
PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas"),
|
|
1968
2083
|
...typeof opts.sessionId === "string" && opts.sessionId.trim() ? { PUSHPALS_SESSION_ID: opts.sessionId.trim() } : {},
|
|
2084
|
+
...embeddedWindowsSafetyCaps,
|
|
1969
2085
|
...typeof env.PUSHPALS_GIT_BIN === "string" && env.PUSHPALS_GIT_BIN.trim() ? { PUSHPALS_GIT_BIN: env.PUSHPALS_GIT_BIN.trim() } : {},
|
|
1970
2086
|
...typeof env.PUSHPALS_GIT_BIN_ABSOLUTE === "string" && env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() ? { PUSHPALS_GIT_BIN_ABSOLUTE: env.PUSHPALS_GIT_BIN_ABSOLUTE.trim() } : {},
|
|
1971
2087
|
...typeof env.PUSHPALS_DOCKER_BIN === "string" && env.PUSHPALS_DOCKER_BIN.trim() ? { PUSHPALS_DOCKER_BIN: env.PUSHPALS_DOCKER_BIN.trim() } : {},
|
|
1972
2088
|
...typeof env.PUSHPALS_DOCKER_BIN_ABSOLUTE === "string" && env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() ? { PUSHPALS_DOCKER_BIN_ABSOLUTE: env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() } : {}
|
|
1973
2089
|
};
|
|
1974
2090
|
}
|
|
2091
|
+
function parseBooleanFlag(raw) {
|
|
2092
|
+
const normalized = String(raw ?? "").trim().toLowerCase();
|
|
2093
|
+
if (!normalized)
|
|
2094
|
+
return null;
|
|
2095
|
+
if (["1", "true", "yes", "on", "y"].includes(normalized))
|
|
2096
|
+
return true;
|
|
2097
|
+
if (["0", "false", "no", "off", "n"].includes(normalized))
|
|
2098
|
+
return false;
|
|
2099
|
+
return null;
|
|
2100
|
+
}
|
|
1975
2101
|
function normalizeChildProcessEnv(baseEnv, platform = process.platform) {
|
|
1976
2102
|
const env = {};
|
|
1977
2103
|
for (const [key, value] of Object.entries(baseEnv)) {
|
|
@@ -2840,6 +2966,12 @@ async function precheckWorkerpalDockerAvailability(opts) {
|
|
|
2840
2966
|
function resolveWorkerpalCapacityTimeoutMs(config) {
|
|
2841
2967
|
return Math.max(config.remotebuddy.waitForWorkerpalMs, config.remotebuddy.workerpalStartupTimeoutMs, config.remotebuddy.workerpalDocker ? config.workerpals.dockerAgentStartupTimeoutMs + 15000 : 0, 1e4);
|
|
2842
2968
|
}
|
|
2969
|
+
function resolveWorkerpalStartupReadinessProbeTimeoutMs(config) {
|
|
2970
|
+
return Math.max(5000, Math.min(resolveWorkerpalCapacityTimeoutMs(config), WORKERPAL_STARTUP_READINESS_PROBE_MAX_MS));
|
|
2971
|
+
}
|
|
2972
|
+
function shouldRunEmbeddedRuntimeStartupPrechecks(opts) {
|
|
2973
|
+
return !opts.serverHealthy && !opts.noAutoStart;
|
|
2974
|
+
}
|
|
2843
2975
|
async function checkGitRemoteConfigured(repoRoot, remote, env) {
|
|
2844
2976
|
const normalizedRemote = String(remote ?? "").trim();
|
|
2845
2977
|
if (!normalizedRemote) {
|
|
@@ -3193,15 +3325,14 @@ async function waitForWorkerpalCapacity(opts) {
|
|
|
3193
3325
|
let lastObservedOnline = 0;
|
|
3194
3326
|
while (Date.now() < deadline) {
|
|
3195
3327
|
const workers = await (opts.fetchWorkersFn ?? fetchWorkerStatusRows)(opts.serverUrl, opts.ttlMs);
|
|
3196
|
-
const
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
lastObservedOnline = Math.max(lastObservedOnline, onlineWorkers.length);
|
|
3328
|
+
const summary = summarizeWorkerStatusRows(workers);
|
|
3329
|
+
if (summary.onlineWorkers > 0) {
|
|
3330
|
+
lastObservedOnline = Math.max(lastObservedOnline, summary.onlineWorkers);
|
|
3200
3331
|
}
|
|
3201
|
-
if (idleWorkers
|
|
3332
|
+
if (summary.idleWorkers > 0) {
|
|
3202
3333
|
return {
|
|
3203
3334
|
ok: true,
|
|
3204
|
-
detail: `${idleWorkers
|
|
3335
|
+
detail: `${summary.idleWorkers} idle / ${summary.onlineWorkers} online`
|
|
3205
3336
|
};
|
|
3206
3337
|
}
|
|
3207
3338
|
await (opts.sleepFn ?? Bun.sleep)(DEFAULT_RUNTIME_BOOT_POLL_MS);
|
|
@@ -3324,6 +3455,29 @@ async function autoStartRuntimeServices(opts) {
|
|
|
3324
3455
|
applyResolvedGitBinaryToRuntimeEnv(runtimeEnv, resolvedGitBinary);
|
|
3325
3456
|
}
|
|
3326
3457
|
const services = [];
|
|
3458
|
+
const startupStartedAt = Date.now();
|
|
3459
|
+
const startupPhases = [];
|
|
3460
|
+
const recordStartupPhase = (name, startedAt, status) => {
|
|
3461
|
+
startupPhases.push({
|
|
3462
|
+
name,
|
|
3463
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
3464
|
+
status
|
|
3465
|
+
});
|
|
3466
|
+
};
|
|
3467
|
+
const emitStartupTimingSummary = (outcome, detail) => {
|
|
3468
|
+
const summary = formatRuntimeStartupTimingSummary({
|
|
3469
|
+
outcome,
|
|
3470
|
+
totalDurationMs: Date.now() - startupStartedAt,
|
|
3471
|
+
phases: startupPhases,
|
|
3472
|
+
detail
|
|
3473
|
+
});
|
|
3474
|
+
if (outcome === "failed") {
|
|
3475
|
+
console.warn(summary);
|
|
3476
|
+
} else {
|
|
3477
|
+
console.log(summary);
|
|
3478
|
+
}
|
|
3479
|
+
appendRuntimeServicesLogLine(runtimeServicesLogPath, summary);
|
|
3480
|
+
};
|
|
3327
3481
|
const runToken = timestampFileToken();
|
|
3328
3482
|
const logDir = join2(runtimeRoot, "logs", "bootstrap");
|
|
3329
3483
|
mkdirSync(logDir, { recursive: true });
|
|
@@ -3341,6 +3495,7 @@ async function autoStartRuntimeServices(opts) {
|
|
|
3341
3495
|
console.log(`[pushpals] service log (source_control_manager)=${serviceLogPaths.source_control_manager}`);
|
|
3342
3496
|
const serverHealthy = await probeServer(opts.serverUrl);
|
|
3343
3497
|
if (!serverHealthy) {
|
|
3498
|
+
const serverPhaseStartedAt = Date.now();
|
|
3344
3499
|
console.log("[pushpals] Starting embedded server...");
|
|
3345
3500
|
const serverService = spawnRuntimeService("server", [runtimeBinaries.server], opts.repoRoot, runtimeEnv, serviceLogPaths.server, runtimeServicesLogPath);
|
|
3346
3501
|
services.push(serverService);
|
|
@@ -3351,6 +3506,8 @@ async function autoStartRuntimeServices(opts) {
|
|
|
3351
3506
|
if (serverService.exited) {
|
|
3352
3507
|
const tail = readLogTail(serverService.logPath);
|
|
3353
3508
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}).`);
|
|
3509
|
+
recordStartupPhase("server", serverPhaseStartedAt, "exited");
|
|
3510
|
+
emitStartupTimingSummary("failed", "server exited during bootstrap");
|
|
3354
3511
|
stopRuntimeServices(services);
|
|
3355
3512
|
throw new Error(`Embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}). ` + `See ${serverService.logPath}${tail ? `
|
|
3356
3513
|
--- server log tail ---
|
|
@@ -3365,29 +3522,38 @@ ${tail}` : ""}`);
|
|
|
3365
3522
|
if (!serverIsReady) {
|
|
3366
3523
|
const tail = readLogTail(serverService.logPath);
|
|
3367
3524
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms.`);
|
|
3525
|
+
recordStartupPhase("server", serverPhaseStartedAt, "timeout");
|
|
3526
|
+
emitStartupTimingSummary("failed", "server health timeout");
|
|
3368
3527
|
stopRuntimeServices(services);
|
|
3369
3528
|
throw new Error(`Embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms. ` + `See ${serverService.logPath}${tail ? `
|
|
3370
3529
|
--- server log tail ---
|
|
3371
3530
|
${tail}` : ""}`);
|
|
3372
3531
|
}
|
|
3532
|
+
recordStartupPhase("server", serverPhaseStartedAt, "started");
|
|
3373
3533
|
console.log("[pushpals] Embedded server is healthy.");
|
|
3374
3534
|
} else {
|
|
3535
|
+
recordStartupPhase("server", Date.now(), "reused");
|
|
3375
3536
|
console.log("[pushpals] Server already healthy; skipping embedded server start.");
|
|
3376
3537
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] server already healthy; embedded server start skipped.");
|
|
3377
3538
|
}
|
|
3378
3539
|
if (localBuddyEnabled) {
|
|
3540
|
+
const localBuddyPhaseStartedAt = Date.now();
|
|
3379
3541
|
console.log("[pushpals] Starting embedded LocalBuddy...");
|
|
3380
3542
|
const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, serviceLogPaths.localbuddy, runtimeServicesLogPath);
|
|
3381
3543
|
services.push(localbuddyService);
|
|
3382
3544
|
console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
|
|
3545
|
+
recordStartupPhase("localbuddy", localBuddyPhaseStartedAt, "started");
|
|
3383
3546
|
} else {
|
|
3547
|
+
recordStartupPhase("localbuddy", Date.now(), "skipped");
|
|
3384
3548
|
console.log("[pushpals] Embedded LocalBuddy disabled for this CLI session; skipping start.");
|
|
3385
3549
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] localbuddy disabled for this CLI session; embedded localbuddy start skipped.");
|
|
3386
3550
|
}
|
|
3551
|
+
const remoteBuddyPhaseStartedAt = Date.now();
|
|
3387
3552
|
console.log("[pushpals] Starting embedded RemoteBuddy...");
|
|
3388
3553
|
const remotebuddyService = spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv, serviceLogPaths.remotebuddy, runtimeServicesLogPath);
|
|
3389
3554
|
services.push(remotebuddyService);
|
|
3390
3555
|
console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
|
|
3556
|
+
recordStartupPhase("remotebuddy", remoteBuddyPhaseStartedAt, "started");
|
|
3391
3557
|
let lastReportedRemoteBuddyAutonomyState = "unknown";
|
|
3392
3558
|
const reportRemoteBuddyAutonomousEngineState = () => {
|
|
3393
3559
|
const autonomyState = readRemoteBuddyAutonomousEngineState(remotebuddyService.logPath);
|
|
@@ -3405,48 +3571,58 @@ ${tail}` : ""}`);
|
|
|
3405
3571
|
};
|
|
3406
3572
|
reportRemoteBuddyAutonomousEngineState();
|
|
3407
3573
|
if (runtimePreflight.config.remotebuddy.autoSpawnWorkerpals) {
|
|
3408
|
-
const
|
|
3574
|
+
const workerpalPhaseStartedAt = Date.now();
|
|
3575
|
+
const workerpalReadinessProbeTimeoutMs = resolveWorkerpalStartupReadinessProbeTimeoutMs(runtimePreflight.config);
|
|
3409
3576
|
const workerpalCapacity = await waitForWorkerpalCapacity({
|
|
3410
3577
|
serverUrl: opts.serverUrl,
|
|
3411
|
-
timeoutMs:
|
|
3578
|
+
timeoutMs: workerpalReadinessProbeTimeoutMs,
|
|
3412
3579
|
ttlMs: runtimePreflight.config.remotebuddy.workerpalOnlineTtlMs
|
|
3413
3580
|
});
|
|
3414
3581
|
if (!workerpalCapacity.ok) {
|
|
3415
|
-
const
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3582
|
+
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.`;
|
|
3583
|
+
console.warn(`[pushpals] ${startupProbeWarning}`);
|
|
3584
|
+
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] ${startupProbeWarning}`);
|
|
3585
|
+
recordStartupPhase("workerpal", workerpalPhaseStartedAt, "deferred");
|
|
3586
|
+
} else {
|
|
3587
|
+
console.log(`[pushpals] Embedded WorkerPal capacity is ready (${workerpalCapacity.detail}).`);
|
|
3588
|
+
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded workerpal capacity ready (${workerpalCapacity.detail}).`);
|
|
3589
|
+
recordStartupPhase("workerpal", workerpalPhaseStartedAt, "ready");
|
|
3421
3590
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3591
|
+
} else {
|
|
3592
|
+
recordStartupPhase("workerpal", Date.now(), "disabled");
|
|
3424
3593
|
}
|
|
3425
3594
|
const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
|
|
3426
3595
|
const scmGitProbe = await resolveSourceControlManagerGitProbe(opts.repoRoot, runtimeEnv, process.platform);
|
|
3427
3596
|
const scmRemoteStatus = await checkGitRemoteConfigured(opts.repoRoot, opts.sourceControlManagerRemote, runtimeEnv);
|
|
3428
3597
|
if (!scmHealthy) {
|
|
3598
|
+
const scmPhaseStartedAt = Date.now();
|
|
3429
3599
|
if (!scmGitProbe.ok) {
|
|
3430
3600
|
console.warn("[pushpals] Git is not available to embedded SourceControlManager; skipping SCM startup.");
|
|
3431
3601
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: git is unavailable in embedded runtime env (${scmGitProbe.detail}).`);
|
|
3602
|
+
recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_git");
|
|
3432
3603
|
} else if (scmRemoteStatus.status === "error") {
|
|
3433
3604
|
console.warn(`[pushpals] Could not inspect SourceControlManager git remote "${opts.sourceControlManagerRemote}"; skipping SCM startup.`);
|
|
3434
3605
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: remote "${opts.sourceControlManagerRemote}" could not be inspected (${scmRemoteStatus.detail}).`);
|
|
3606
|
+
recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_remote_error");
|
|
3435
3607
|
} else if (scmRemoteStatus.status === "ok") {
|
|
3436
3608
|
console.log(`[pushpals] Embedded SourceControlManager git=${scmGitProbe.detail}`);
|
|
3437
3609
|
console.log("[pushpals] Starting embedded SourceControlManager...");
|
|
3438
3610
|
const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, serviceLogPaths.source_control_manager, runtimeServicesLogPath);
|
|
3439
3611
|
services.push(sourceControlManagerService);
|
|
3440
3612
|
console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
|
|
3613
|
+
recordStartupPhase("source_control_manager", scmPhaseStartedAt, "started");
|
|
3441
3614
|
} else {
|
|
3442
3615
|
console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
|
|
3443
3616
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] source_control_manager skipped: repo has no remote "${opts.sourceControlManagerRemote}".`);
|
|
3617
|
+
recordStartupPhase("source_control_manager", scmPhaseStartedAt, "skipped_no_remote");
|
|
3444
3618
|
}
|
|
3445
3619
|
} else {
|
|
3620
|
+
recordStartupPhase("source_control_manager", Date.now(), "reused");
|
|
3446
3621
|
console.log("[pushpals] SourceControlManager already healthy; skipping embedded start.");
|
|
3447
3622
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] source_control_manager already healthy; embedded start skipped.");
|
|
3448
3623
|
}
|
|
3449
3624
|
const deadline = Date.now() + DEFAULT_RUNTIME_BOOT_TIMEOUT_MS;
|
|
3625
|
+
const readinessPhaseStartedAt = Date.now();
|
|
3450
3626
|
while (Date.now() < deadline) {
|
|
3451
3627
|
reportRemoteBuddyAutonomousEngineState();
|
|
3452
3628
|
for (let i = services.length - 1;i >= 0; i--) {
|
|
@@ -3467,6 +3643,8 @@ ${tail2}`);
|
|
|
3467
3643
|
}
|
|
3468
3644
|
const tail = readLogTail(service.logPath);
|
|
3469
3645
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}).`);
|
|
3646
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "failed");
|
|
3647
|
+
emitStartupTimingSummary("failed", `${service.name} exited during startup`);
|
|
3470
3648
|
stopRuntimeServices(services);
|
|
3471
3649
|
throw new Error(`Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
|
|
3472
3650
|
--- ${service.name} log tail ---
|
|
@@ -3499,6 +3677,8 @@ ${tail2}`);
|
|
|
3499
3677
|
}
|
|
3500
3678
|
const tail = readLogTail(service.logPath);
|
|
3501
3679
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}).`);
|
|
3680
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "failed");
|
|
3681
|
+
emitStartupTimingSummary("failed", `${service.name} exited immediately after bootstrap`);
|
|
3502
3682
|
stopRuntimeServices(services);
|
|
3503
3683
|
throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
|
|
3504
3684
|
--- ${service.name} log tail ---
|
|
@@ -3507,6 +3687,8 @@ ${tail}` : ""}`);
|
|
|
3507
3687
|
await Bun.sleep(250);
|
|
3508
3688
|
}
|
|
3509
3689
|
console.log("[pushpals] Embedded runtime is ready.");
|
|
3690
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "ready");
|
|
3691
|
+
emitStartupTimingSummary("ready");
|
|
3510
3692
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] embedded runtime is ready.");
|
|
3511
3693
|
return {
|
|
3512
3694
|
services,
|
|
@@ -3519,13 +3701,19 @@ ${tail}` : ""}`);
|
|
|
3519
3701
|
const remoteBuddyHealth = await probeRemoteBuddySessionConsumer(opts.serverUrl, opts.sessionId);
|
|
3520
3702
|
if (!localBuddyEnabled && !remoteBuddyHealth.ok) {
|
|
3521
3703
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for RemoteBuddy session consumer readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms (${remoteBuddyHealth.detail}).`);
|
|
3704
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
|
|
3705
|
+
emitStartupTimingSummary("failed", "remotebuddy readiness timeout");
|
|
3522
3706
|
throw new Error(`Timed out waiting for RemoteBuddy session consumer readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms (${remoteBuddyHealth.detail})`);
|
|
3523
3707
|
}
|
|
3524
3708
|
if (!localBuddyEnabled) {
|
|
3525
3709
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms.`);
|
|
3710
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
|
|
3711
|
+
emitStartupTimingSummary("failed", "embedded runtime readiness timeout");
|
|
3526
3712
|
throw new Error(`Timed out waiting for embedded runtime readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
3527
3713
|
}
|
|
3528
3714
|
appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for LocalBuddy at ${opts.localAgentUrl} and RemoteBuddy session consumer after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms.`);
|
|
3715
|
+
recordStartupPhase("readiness", readinessPhaseStartedAt, "timeout");
|
|
3716
|
+
emitStartupTimingSummary("failed", "localbuddy and remotebuddy readiness timeout");
|
|
3529
3717
|
throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} and RemoteBuddy session consumer after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
3530
3718
|
}
|
|
3531
3719
|
function readCliState(pathValue) {
|
|
@@ -4127,32 +4315,59 @@ async function main() {
|
|
|
4127
4315
|
} else {
|
|
4128
4316
|
console.warn("[pushpals] RemoteBuddy autonomy is disabled in config (remotebuddy.autonomy.enabled=false); continuing.");
|
|
4129
4317
|
}
|
|
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
4318
|
const serverUrl = normalizeLoopbackUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
|
|
4154
4319
|
const localAgentUrl = normalizeLoopbackUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
|
|
4155
4320
|
const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
|
|
4321
|
+
let serverHealthy = await probeServer(serverUrl);
|
|
4322
|
+
const serverWasAlreadyHealthy = serverHealthy;
|
|
4323
|
+
const runEmbeddedRuntimeStartupPrechecks = shouldRunEmbeddedRuntimeStartupPrechecks({
|
|
4324
|
+
serverHealthy,
|
|
4325
|
+
noAutoStart: parsed.noAutoStart
|
|
4326
|
+
});
|
|
4327
|
+
if (runEmbeddedRuntimeStartupPrechecks) {
|
|
4328
|
+
const precheckPassed = await enforcePushpalsRemoteBranchPrecheck(repoRoot, config.sourceControlManager.remote, config.sourceControlManager.mainBranch);
|
|
4329
|
+
if (!precheckPassed) {
|
|
4330
|
+
process.exit(1);
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
let scmGitPrecheck = {
|
|
4334
|
+
status: "skipped",
|
|
4335
|
+
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",
|
|
4336
|
+
env: buildEmbeddedRuntimeEnv(process.env, {
|
|
4337
|
+
repoRoot,
|
|
4338
|
+
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4339
|
+
useRuntimeConfig: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4340
|
+
sessionId
|
|
4341
|
+
})
|
|
4342
|
+
};
|
|
4343
|
+
let workerpalDockerPrecheck = {
|
|
4344
|
+
status: "skipped",
|
|
4345
|
+
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",
|
|
4346
|
+
env: scmGitPrecheck.env
|
|
4347
|
+
};
|
|
4348
|
+
if (runEmbeddedRuntimeStartupPrechecks) {
|
|
4349
|
+
scmGitPrecheck = await precheckSourceControlManagerGitAvailability({
|
|
4350
|
+
repoRoot,
|
|
4351
|
+
remote: config.sourceControlManager.remote,
|
|
4352
|
+
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4353
|
+
preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4354
|
+
sessionId
|
|
4355
|
+
});
|
|
4356
|
+
if (scmGitPrecheck.status === "failed") {
|
|
4357
|
+
console.error(`[pushpals] Precheck failed: embedded SourceControlManager git command is unavailable (${scmGitPrecheck.detail}).`);
|
|
4358
|
+
process.exit(1);
|
|
4359
|
+
}
|
|
4360
|
+
workerpalDockerPrecheck = await precheckWorkerpalDockerAvailability({
|
|
4361
|
+
repoRoot,
|
|
4362
|
+
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4363
|
+
preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4364
|
+
autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
|
|
4365
|
+
dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
|
|
4366
|
+
requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
|
|
4367
|
+
sessionId,
|
|
4368
|
+
baseEnv: scmGitPrecheck.env
|
|
4369
|
+
});
|
|
4370
|
+
}
|
|
4156
4371
|
const cliVersion = String(process.env.PUSHPALS_CLI_PACKAGE_VERSION ?? "").trim() || "unknown";
|
|
4157
4372
|
const cliClient = {
|
|
4158
4373
|
clientId: createRuntimeClientId("cli"),
|
|
@@ -4196,6 +4411,31 @@ async function main() {
|
|
|
4196
4411
|
console.log(`[pushpals] ${cleanup.detail} (${phase}).`);
|
|
4197
4412
|
}
|
|
4198
4413
|
};
|
|
4414
|
+
const reportWorkerExecutionReadiness = async () => {
|
|
4415
|
+
const readiness = await resolveWorkerExecutionReadiness({
|
|
4416
|
+
serverUrl,
|
|
4417
|
+
ttlMs: config.remotebuddy.workerpalOnlineTtlMs,
|
|
4418
|
+
autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
|
|
4419
|
+
dockerEnabled: Boolean(config.remotebuddy.workerpalDocker),
|
|
4420
|
+
requireDocker: Boolean(config.remotebuddy.workerpalRequireDocker),
|
|
4421
|
+
repoRoot,
|
|
4422
|
+
runtimeRoot: preparedRuntime.runtimeRoot,
|
|
4423
|
+
preflightUsesEmbeddedRuntime: preparedRuntime.preflightUsesEmbeddedRuntime,
|
|
4424
|
+
sessionId,
|
|
4425
|
+
dockerPrecheck: workerpalDockerPrecheck,
|
|
4426
|
+
baseEnv: workerpalDockerPrecheck.env
|
|
4427
|
+
});
|
|
4428
|
+
for (const line of formatWorkerExecutionReadinessLines(readiness)) {
|
|
4429
|
+
console.log(line);
|
|
4430
|
+
}
|
|
4431
|
+
return readiness;
|
|
4432
|
+
};
|
|
4433
|
+
const reportWorkerExecutionReadinessFromSnapshot = (readiness) => {
|
|
4434
|
+
for (const line of formatWorkerExecutionReadinessLines(readiness)) {
|
|
4435
|
+
console.log(line);
|
|
4436
|
+
}
|
|
4437
|
+
return readiness;
|
|
4438
|
+
};
|
|
4199
4439
|
const stopAutoStartedServices = () => {
|
|
4200
4440
|
if (autoStartedServices.length === 0)
|
|
4201
4441
|
return;
|
|
@@ -4220,14 +4460,12 @@ async function main() {
|
|
|
4220
4460
|
await cleanupWorkerpalWarmContainersIfNeeded("cli shutdown");
|
|
4221
4461
|
await cleanupPushPalsGitWorktreesIfNeeded("cli shutdown");
|
|
4222
4462
|
};
|
|
4223
|
-
let serverHealthy = await probeServer(serverUrl);
|
|
4224
|
-
const serverWasAlreadyHealthy = serverHealthy;
|
|
4225
4463
|
if (!serverHealthy && workerpalDockerPrecheck.status === "failed") {
|
|
4226
4464
|
console.error(`[pushpals] Precheck failed: Docker-backed WorkerPal auto-spawn is required but Docker is unavailable (${workerpalDockerPrecheck.detail}).`);
|
|
4227
4465
|
console.error("[pushpals] Precheck failed: start Docker Desktop or the Docker daemon, then retry pushpals.");
|
|
4228
4466
|
process.exit(1);
|
|
4229
4467
|
}
|
|
4230
|
-
if (workerpalDockerPrecheck.status !== "failed") {
|
|
4468
|
+
if (runEmbeddedRuntimeStartupPrechecks && workerpalDockerPrecheck.status !== "failed") {
|
|
4231
4469
|
const workerpalImagePrecheck = await prepareEmbeddedWorkerpalDockerImageIfNeeded({
|
|
4232
4470
|
preparedRuntime,
|
|
4233
4471
|
config,
|
|
@@ -4247,8 +4485,10 @@ async function main() {
|
|
|
4247
4485
|
detail: `No connected RemoteBuddy session consumer found for session ${sessionId}`
|
|
4248
4486
|
};
|
|
4249
4487
|
if (!serverHealthy) {
|
|
4250
|
-
|
|
4251
|
-
|
|
4488
|
+
if (runEmbeddedRuntimeStartupPrechecks) {
|
|
4489
|
+
await cleanupWorkerpalWarmContainersIfNeeded("startup preflight");
|
|
4490
|
+
await cleanupPushPalsGitWorktreesIfNeeded("startup preflight");
|
|
4491
|
+
}
|
|
4252
4492
|
if (!parsed.noAutoStart) {
|
|
4253
4493
|
try {
|
|
4254
4494
|
const startedRuntime = await autoStartRuntimeServices({
|
|
@@ -4269,6 +4509,10 @@ async function main() {
|
|
|
4269
4509
|
} catch (err) {
|
|
4270
4510
|
console.error(`[pushpals] Auto-start failed: ${String(err)}`);
|
|
4271
4511
|
stopAutoStartedServices();
|
|
4512
|
+
if (runEmbeddedRuntimeStartupPrechecks) {
|
|
4513
|
+
await cleanupWorkerpalWarmContainersIfNeeded("startup failure cleanup");
|
|
4514
|
+
await cleanupPushPalsGitWorktreesIfNeeded("startup failure cleanup");
|
|
4515
|
+
}
|
|
4272
4516
|
}
|
|
4273
4517
|
}
|
|
4274
4518
|
if (!serverHealthy) {
|
|
@@ -4318,20 +4562,33 @@ async function main() {
|
|
|
4318
4562
|
}
|
|
4319
4563
|
const workerpalCapacity = await waitForWorkerpalCapacity({
|
|
4320
4564
|
serverUrl,
|
|
4321
|
-
timeoutMs:
|
|
4565
|
+
timeoutMs: resolveWorkerpalStartupReadinessProbeTimeoutMs(config),
|
|
4322
4566
|
ttlMs: config.remotebuddy.workerpalOnlineTtlMs
|
|
4323
4567
|
});
|
|
4324
4568
|
if (!workerpalCapacity.ok) {
|
|
4325
|
-
|
|
4326
|
-
console.
|
|
4569
|
+
console.warn(`[pushpals] WorkerPal readiness probe did not find idle capacity yet (${workerpalCapacity.detail}).`);
|
|
4570
|
+
console.warn("[pushpals] Continuing startup; WorkerPal warmup may still be in progress and first task dispatch can be delayed.");
|
|
4327
4571
|
if (workerpalDockerPrecheck.status === "failed") {
|
|
4328
|
-
console.
|
|
4572
|
+
console.warn(`[pushpals] Docker precheck detail: ${workerpalDockerPrecheck.detail}`);
|
|
4329
4573
|
} else if (serverWasAlreadyHealthy) {
|
|
4330
|
-
console.
|
|
4331
|
-
console.
|
|
4574
|
+
console.warn("[pushpals] A PushPals runtime is already serving this repo, but it does not currently have an idle WorkerPal available.");
|
|
4575
|
+
console.warn("[pushpals] Wait for a worker to become idle or restart the runtime after fixing WorkerPal startup.");
|
|
4332
4576
|
}
|
|
4333
|
-
process.exit(1);
|
|
4334
4577
|
}
|
|
4578
|
+
const startupWorkerExecutionReadiness = workerpalCapacity.ok ? {
|
|
4579
|
+
state: "ready",
|
|
4580
|
+
detail: workerpalCapacity.detail
|
|
4581
|
+
} : workerpalDockerPrecheck.status === "failed" ? describeWorkerExecutionReadiness({
|
|
4582
|
+
autoSpawnWorkerpals: Boolean(config.remotebuddy.autoSpawnWorkerpals),
|
|
4583
|
+
requireDocker: Boolean(config.remotebuddy.workerpalDocker) && Boolean(config.remotebuddy.workerpalRequireDocker),
|
|
4584
|
+
dockerPrecheck: workerpalDockerPrecheck,
|
|
4585
|
+
onlineWorkers: 0,
|
|
4586
|
+
idleWorkers: 0
|
|
4587
|
+
}) : {
|
|
4588
|
+
state: "warming",
|
|
4589
|
+
detail: workerpalCapacity.detail,
|
|
4590
|
+
action: "Wait for WorkerPal auto-spawn/warmup to finish, then rerun /status."
|
|
4591
|
+
};
|
|
4335
4592
|
const saved = statePath ? readCliState(statePath) : {};
|
|
4336
4593
|
pushpalsLogPath = pushpalsLogPath || (typeof saved.pushpalsLogPath === "string" ? saved.pushpalsLogPath : undefined);
|
|
4337
4594
|
const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
|
|
@@ -4369,6 +4626,7 @@ async function main() {
|
|
|
4369
4626
|
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
4370
4627
|
console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
|
|
4371
4628
|
console.log(`[pushpals] cliStateFile=${statePath ?? "unavailable"}`);
|
|
4629
|
+
reportWorkerExecutionReadinessFromSnapshot(startupWorkerExecutionReadiness);
|
|
4372
4630
|
if (parsed.runtimeOnly) {
|
|
4373
4631
|
console.log("[pushpals] runtimeOnly=true");
|
|
4374
4632
|
} else {
|
|
@@ -4480,6 +4738,7 @@ ${line}
|
|
|
4480
4738
|
console.log(`[pushpals] repoRoot=${repoRoot}`);
|
|
4481
4739
|
console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
|
|
4482
4740
|
console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
|
|
4741
|
+
await reportWorkerExecutionReadiness();
|
|
4483
4742
|
rl.prompt();
|
|
4484
4743
|
continue;
|
|
4485
4744
|
}
|
|
@@ -4518,6 +4777,8 @@ if (import.meta.main) {
|
|
|
4518
4777
|
export {
|
|
4519
4778
|
waitForWorkerpalCapacity,
|
|
4520
4779
|
startEmbeddedMonitoringHub,
|
|
4780
|
+
shouldRunEmbeddedRuntimeStartupPrechecks,
|
|
4781
|
+
resolveWorkerExecutionReadiness,
|
|
4521
4782
|
resolveWindowsWhereExecutableCandidatesForEnv,
|
|
4522
4783
|
resolveWindowsShellExecutableCandidatesForEnv,
|
|
4523
4784
|
resolveRuntimeGitExecutableCandidates,
|
|
@@ -4538,13 +4799,16 @@ export {
|
|
|
4538
4799
|
normalizeChildProcessEnv,
|
|
4539
4800
|
isCliExitCommand,
|
|
4540
4801
|
injectMonitoringHubBootstrap,
|
|
4802
|
+
formatWorkerExecutionReadinessLines,
|
|
4541
4803
|
formatTimestampedCliLine,
|
|
4542
4804
|
formatSessionEventLine,
|
|
4805
|
+
formatRuntimeStartupTimingSummary,
|
|
4543
4806
|
extractRemoteBuddySessionConsumerHealth,
|
|
4544
4807
|
extractRemoteBuddyAutonomousEngineState,
|
|
4545
4808
|
ensureWorkerpalDockerImageReady,
|
|
4546
4809
|
ensureRuntimeBinaries,
|
|
4547
4810
|
downloadRuntimeAssetsFromSourceTag,
|
|
4811
|
+
describeWorkerExecutionReadiness,
|
|
4548
4812
|
createSessionEventReplayFilter,
|
|
4549
4813
|
copyTrackedRepoPath,
|
|
4550
4814
|
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",
|