@pushpalsdev/cli 1.0.35 → 1.0.37

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.
@@ -6,23 +6,323 @@ import {
6
6
  appendFileSync,
7
7
  chmodSync,
8
8
  cpSync,
9
- existsSync as existsSync4,
9
+ existsSync as existsSync5,
10
10
  lstatSync,
11
11
  mkdirSync,
12
12
  readdirSync,
13
13
  readFileSync as readFileSync4,
14
- rmSync,
14
+ rmSync as rmSync2,
15
15
  writeFileSync
16
16
  } from "fs";
17
17
  import { basename, delimiter, dirname, extname, join as join2, resolve as resolve4, win32 as pathWin32 } from "path";
18
18
  import { createInterface } from "readline";
19
19
 
20
+ // ../shared/src/localbuddy_runtime.ts
21
+ var TRUTHY = new Set(["1", "true", "yes", "on"]);
22
+ var FALSY = new Set(["0", "false", "no", "off"]);
23
+
24
+ // ../../scripts/start_runtime_services.ts
25
+ var DEFAULT_SERVICE_MANAGER_POLL_MS = 1000;
26
+ var DEFAULT_SERVICE_MANAGER_MAX_RESTART_ATTEMPTS = 4;
27
+ var DEFAULT_SERVICE_MANAGER_STABLE_WINDOW_MS = 60000;
28
+ var DEFAULT_SERVICE_MANAGER_BASE_BACKOFF_MS = 2000;
29
+ var DEFAULT_SERVICE_MANAGER_MAX_BACKOFF_MS = 30000;
30
+ function formatEmbeddedRuntimeHealthLines(health) {
31
+ if (!health)
32
+ return [];
33
+ const lines = [`[pushpals] embeddedRuntime=${health.state} detail=${health.detail}`];
34
+ if (health.action) {
35
+ lines.push(`[pushpals] embeddedRuntimeAction=${health.action}`);
36
+ }
37
+ return lines;
38
+ }
39
+ function computeServiceRestartBackoffMs(attempt) {
40
+ const boundedAttempt = Math.max(1, Math.floor(attempt));
41
+ const exponential = DEFAULT_SERVICE_MANAGER_BASE_BACKOFF_MS * Math.pow(2, boundedAttempt - 1);
42
+ return Math.max(DEFAULT_SERVICE_MANAGER_BASE_BACKOFF_MS, Math.min(DEFAULT_SERVICE_MANAGER_MAX_BACKOFF_MS, Math.floor(exponential)));
43
+ }
44
+ function shouldRestartService(attempts, maxAttempts = DEFAULT_SERVICE_MANAGER_MAX_RESTART_ATTEMPTS) {
45
+ const normalizedAttempts = Math.max(0, Math.floor(attempts));
46
+ const normalizedMax = Math.max(1, Math.floor(maxAttempts));
47
+ return normalizedAttempts < normalizedMax;
48
+ }
49
+ function pipeProcessStreamToLines(stream, onLine) {
50
+ if (!stream || typeof stream === "number" || typeof stream.getReader !== "function")
51
+ return;
52
+ const reader = stream.getReader();
53
+ const decoder = new TextDecoder;
54
+ let pending = "";
55
+ (async () => {
56
+ try {
57
+ while (true) {
58
+ const { done, value } = await reader.read();
59
+ if (done)
60
+ break;
61
+ pending += decoder.decode(value, { stream: true });
62
+ const lines = pending.split(/\r?\n/);
63
+ pending = lines.pop() ?? "";
64
+ for (const line of lines) {
65
+ const trimmed = line.trimEnd();
66
+ if (!trimmed)
67
+ continue;
68
+ onLine?.(trimmed);
69
+ }
70
+ }
71
+ const rest = decoder.decode();
72
+ if (rest)
73
+ pending += rest;
74
+ const tail = pending.trimEnd();
75
+ if (tail)
76
+ onLine?.(tail);
77
+ } catch {} finally {
78
+ reader.releaseLock();
79
+ }
80
+ })();
81
+ }
82
+ function spawnManagedService(spec) {
83
+ const env = { ...spec.env ?? {} };
84
+ const proc = Bun.spawn(spec.command, {
85
+ cwd: spec.cwd,
86
+ env,
87
+ stdout: "pipe",
88
+ stderr: "pipe"
89
+ });
90
+ pipeProcessStreamToLines(proc.stdout, spec.onStdoutLine);
91
+ pipeProcessStreamToLines(proc.stderr, spec.onStderrLine);
92
+ const service = {
93
+ name: spec.name,
94
+ proc,
95
+ command: [...spec.command],
96
+ cwd: spec.cwd,
97
+ env,
98
+ exited: false,
99
+ exitCode: null,
100
+ launchedAtMs: Date.now(),
101
+ logPath: spec.logPath
102
+ };
103
+ proc.exited.then((code) => {
104
+ service.exited = true;
105
+ service.exitCode = code;
106
+ });
107
+ return service;
108
+ }
109
+
110
+ class ServiceManager {
111
+ services = new Map;
112
+ launchSpecs = new Map;
113
+ stateByService = new Map;
114
+ degradedServiceReasons = new Map;
115
+ pollMs;
116
+ maxRestartAttempts;
117
+ stableWindowMs;
118
+ computeRestartBackoffMs;
119
+ degradedAction;
120
+ spawnService;
121
+ onHealthChange;
122
+ onServiceDegraded;
123
+ onEvent;
124
+ timer;
125
+ stopped = false;
126
+ constructor(options = {}) {
127
+ this.pollMs = Math.max(50, Math.floor(options.pollMs ?? DEFAULT_SERVICE_MANAGER_POLL_MS));
128
+ this.maxRestartAttempts = Math.max(1, Math.floor(options.maxRestartAttempts ?? DEFAULT_SERVICE_MANAGER_MAX_RESTART_ATTEMPTS));
129
+ this.stableWindowMs = Math.max(1000, Math.floor(options.stableWindowMs ?? DEFAULT_SERVICE_MANAGER_STABLE_WINDOW_MS));
130
+ this.computeRestartBackoffMs = options.computeRestartBackoffMs ?? computeServiceRestartBackoffMs;
131
+ this.degradedAction = options.degradedAction ?? "Inspect the affected service logs or restart the runtime after fixing the failure.";
132
+ this.spawnService = options.spawnService ?? spawnManagedService;
133
+ this.onHealthChange = options.onHealthChange;
134
+ this.onServiceDegraded = options.onServiceDegraded;
135
+ this.onEvent = options.onEvent;
136
+ this.timer = setInterval(() => this.tick(), this.pollMs);
137
+ }
138
+ startService(spec) {
139
+ this.launchSpecs.set(spec.name, {
140
+ ...spec,
141
+ command: [...spec.command],
142
+ env: { ...spec.env ?? {} }
143
+ });
144
+ const service = this.spawnService(spec);
145
+ this.services.set(spec.name, service);
146
+ return service;
147
+ }
148
+ getServices() {
149
+ return Array.from(this.services.values());
150
+ }
151
+ getService(name) {
152
+ return this.services.get(name) ?? null;
153
+ }
154
+ getHealth() {
155
+ if (this.degradedServiceReasons.size === 0)
156
+ return null;
157
+ const detail = Array.from(this.degradedServiceReasons.entries()).map(([name, reason]) => `${name}: ${reason}`).join(" | ");
158
+ return {
159
+ state: "degraded",
160
+ detail,
161
+ action: this.degradedAction
162
+ };
163
+ }
164
+ stop() {
165
+ if (this.stopped)
166
+ return;
167
+ this.stopped = true;
168
+ clearInterval(this.timer);
169
+ for (const state of this.stateByService.values()) {
170
+ if (!state.pendingRestartTimer)
171
+ continue;
172
+ clearTimeout(state.pendingRestartTimer);
173
+ state.pendingRestartTimer = null;
174
+ }
175
+ for (const service of this.services.values()) {
176
+ try {
177
+ const pid = service.proc.pid;
178
+ if (process.platform === "win32" && typeof pid === "number" && pid > 0) {
179
+ Bun.spawnSync(["taskkill", "/PID", String(pid), "/T", "/F"], {
180
+ stdin: "ignore",
181
+ stdout: "ignore",
182
+ stderr: "ignore"
183
+ });
184
+ } else {
185
+ service.proc.kill();
186
+ }
187
+ } catch {}
188
+ }
189
+ }
190
+ ensureState(name) {
191
+ const existing = this.stateByService.get(name);
192
+ if (existing)
193
+ return existing;
194
+ const created = {
195
+ attempts: 0,
196
+ nextRestartAtMs: 0,
197
+ lastRestartReason: "",
198
+ pendingRestartTimer: null
199
+ };
200
+ this.stateByService.set(name, created);
201
+ return created;
202
+ }
203
+ emitHealthChange() {
204
+ this.onHealthChange?.(this.getHealth());
205
+ }
206
+ emitEvent(level, line) {
207
+ this.onEvent?.(level, line);
208
+ }
209
+ tick() {
210
+ if (this.stopped)
211
+ return;
212
+ const now = Date.now();
213
+ for (const [name, service] of this.services.entries()) {
214
+ const launchSpec = this.launchSpecs.get(name);
215
+ if (!launchSpec)
216
+ continue;
217
+ const state = this.ensureState(name);
218
+ if (!service.exited) {
219
+ if (state.attempts > 0 && now - service.launchedAtMs >= this.stableWindowMs) {
220
+ state.attempts = 0;
221
+ state.nextRestartAtMs = 0;
222
+ state.lastRestartReason = "";
223
+ }
224
+ continue;
225
+ }
226
+ if (state.pendingRestartTimer)
227
+ continue;
228
+ if (state.nextRestartAtMs > now)
229
+ continue;
230
+ const reason = `exit code ${service.exitCode ?? "unknown"}`;
231
+ if (!shouldRestartService(state.attempts, this.maxRestartAttempts)) {
232
+ this.emitEvent("error", `Managed ${name} exited (${reason}) and reached restart limit (${state.attempts}/${this.maxRestartAttempts}).`);
233
+ this.launchSpecs.delete(name);
234
+ if (!this.degradedServiceReasons.has(name)) {
235
+ const degradationReason = `reached restart limit after ${reason} (${state.attempts}/${this.maxRestartAttempts})`;
236
+ this.degradedServiceReasons.set(name, degradationReason);
237
+ const health = this.getHealth();
238
+ if (health) {
239
+ this.onHealthChange?.(health);
240
+ this.onServiceDegraded?.(name, degradationReason, health);
241
+ }
242
+ }
243
+ continue;
244
+ }
245
+ const nextAttempt = state.attempts + 1;
246
+ state.lastRestartReason = reason;
247
+ const backoffMs = Math.max(1, Math.floor(this.computeRestartBackoffMs(nextAttempt)));
248
+ this.emitEvent("warn", `Managed ${name} exited (${reason}); restarting attempt ${nextAttempt}/${this.maxRestartAttempts} in ${backoffMs}ms.`);
249
+ state.nextRestartAtMs = now + backoffMs;
250
+ state.pendingRestartTimer = setTimeout(() => {
251
+ state.pendingRestartTimer = null;
252
+ state.nextRestartAtMs = 0;
253
+ if (this.stopped)
254
+ return;
255
+ const current = this.services.get(name);
256
+ if (!current || !current.exited)
257
+ return;
258
+ const spec = this.launchSpecs.get(name);
259
+ if (!spec)
260
+ return;
261
+ if (!shouldRestartService(state.attempts, this.maxRestartAttempts))
262
+ return;
263
+ state.attempts += 1;
264
+ const restarted = this.spawnService(spec);
265
+ this.services.set(name, restarted);
266
+ this.emitEvent("log", `Restarted managed ${name}.`);
267
+ }, backoffMs);
268
+ }
269
+ }
270
+ }
271
+
272
+ // ../../apps/workerpals/src/common/worktree_cleanup.ts
273
+ import { existsSync, rmSync } from "fs";
274
+ function defaultSleep(ms) {
275
+ return new Promise((resolve) => setTimeout(resolve, ms));
276
+ }
277
+ function windowsDeletionCandidates(worktreePath) {
278
+ const seen = new Set;
279
+ const out = [];
280
+ const add = (value) => {
281
+ if (!value || seen.has(value))
282
+ return;
283
+ seen.add(value);
284
+ out.push(value);
285
+ };
286
+ add(worktreePath);
287
+ if (process.platform === "win32" && /^[A-Za-z]:[\\/]/.test(worktreePath)) {
288
+ add(`\\\\?\\${worktreePath}`);
289
+ }
290
+ return out;
291
+ }
292
+ async function forceDeleteWorktreePath(worktreePath, options = {}) {
293
+ const retries = Math.max(1, Math.floor(options.retries ?? 5));
294
+ const delayMs = Math.max(0, Math.floor(options.delayMs ?? 120));
295
+ const sleep = options.sleepFn ?? defaultSleep;
296
+ const removePath = options.removeFn ?? ((targetPath) => rmSync(targetPath, { recursive: true, force: true }));
297
+ const pathExists = options.existsFn ?? ((targetPath) => existsSync(targetPath));
298
+ let lastError = "";
299
+ for (let attempt = 1;attempt <= retries; attempt++) {
300
+ if (!pathExists(worktreePath))
301
+ return { removed: true };
302
+ for (const candidate of windowsDeletionCandidates(worktreePath)) {
303
+ try {
304
+ removePath(candidate);
305
+ } catch (error) {
306
+ lastError = String(error);
307
+ }
308
+ }
309
+ if (!pathExists(worktreePath))
310
+ return { removed: true };
311
+ if (attempt < retries)
312
+ await sleep(delayMs * attempt);
313
+ }
314
+ return {
315
+ removed: !pathExists(worktreePath),
316
+ ...lastError ? { lastError } : {}
317
+ };
318
+ }
319
+
20
320
  // ../shared/src/client_preflight.ts
21
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
321
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
22
322
  import { relative, resolve as resolve2 } from "path";
23
323
 
24
324
  // ../shared/src/config.ts
25
- import { existsSync, readFileSync } from "fs";
325
+ import { existsSync as existsSync2, readFileSync } from "fs";
26
326
  import { join, resolve, isAbsolute } from "path";
27
327
 
28
328
  // ../shared/src/autonomy_policy.ts
@@ -101,8 +401,8 @@ function normalizeLoopbackHttpUrl(value, fallbackPort) {
101
401
  // ../shared/src/config.ts
102
402
  var PROJECT_ROOT = resolve(import.meta.dir, "..", "..", "..");
103
403
  var DEFAULT_CONFIG_DIR = "configs";
104
- var TRUTHY = new Set(["1", "true", "yes", "on"]);
105
- var FALSY = new Set(["0", "false", "no", "off"]);
404
+ var TRUTHY2 = new Set(["1", "true", "yes", "on"]);
405
+ var FALSY2 = new Set(["0", "false", "no", "off"]);
106
406
  var DEFAULT_WORKERPALS_QUALITY_CRITIC_MIN_SCORE = 8;
107
407
  var DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS = 1;
108
408
  var DEFAULT_WORKERPALS_FILE_MODIFYING_JOBS = ["task.execute"];
@@ -133,9 +433,9 @@ function parseBoolEnv(name) {
133
433
  const raw = (process.env[name] ?? "").trim().toLowerCase();
134
434
  if (!raw)
135
435
  return null;
136
- if (TRUTHY.has(raw))
436
+ if (TRUTHY2.has(raw))
137
437
  return true;
138
- if (FALSY.has(raw))
438
+ if (FALSY2.has(raw))
139
439
  return false;
140
440
  return null;
141
441
  }
@@ -147,7 +447,7 @@ function parseIntEnv(name) {
147
447
  return Number.isFinite(parsed) ? parsed : null;
148
448
  }
149
449
  function parseTomlFile(path) {
150
- if (!existsSync(path))
450
+ if (!existsSync2(path))
151
451
  return {};
152
452
  const raw = readFileSync(path, "utf-8");
153
453
  const parsed = Bun.TOML.parse(raw);
@@ -156,7 +456,7 @@ function parseTomlFile(path) {
156
456
  return parsed;
157
457
  }
158
458
  function parseRequiredTomlFile(path) {
159
- if (!existsSync(path)) {
459
+ if (!existsSync2(path)) {
160
460
  throw new Error(`Missing required runtime config file: ${path}`);
161
461
  }
162
462
  return parseTomlFile(path);
@@ -192,9 +492,9 @@ function asBoolean(value, fallback) {
192
492
  return value;
193
493
  if (typeof value === "string") {
194
494
  const lowered = value.trim().toLowerCase();
195
- if (TRUTHY.has(lowered))
495
+ if (TRUTHY2.has(lowered))
196
496
  return true;
197
- if (FALSY.has(lowered))
497
+ if (FALSY2.has(lowered))
198
498
  return false;
199
499
  }
200
500
  return fallback;
@@ -949,7 +1249,7 @@ function validateVisionDocStructure(markdown) {
949
1249
  // ../shared/src/client_preflight.ts
950
1250
  function runtimeHasConfigDir(runtimeRoot, dirName) {
951
1251
  const dirPath = resolve2(runtimeRoot, dirName);
952
- return existsSync2(resolve2(dirPath, "default.toml")) || existsSync2(resolve2(dirPath, "local.example.toml")) || existsSync2(resolve2(dirPath, "local.toml"));
1252
+ return existsSync3(resolve2(dirPath, "default.toml")) || existsSync3(resolve2(dirPath, "local.example.toml")) || existsSync3(resolve2(dirPath, "local.toml"));
953
1253
  }
954
1254
  function resolveClientConfigDir(projectRoot, runtimeRoot, explicitConfigDir) {
955
1255
  if (explicitConfigDir && explicitConfigDir.trim()) {
@@ -1003,21 +1303,21 @@ function evaluateClientRuntimePreflight(options) {
1003
1303
  });
1004
1304
  const issues = [];
1005
1305
  const envPath = resolve2(runtimeRoot, ".env");
1006
- if (!existsSync2(envPath)) {
1306
+ if (!existsSync3(envPath)) {
1007
1307
  const envExamplePath = resolve2(runtimeRoot, ".env.example");
1008
1308
  issues.push({
1009
1309
  code: "missing_env_file",
1010
1310
  message: `Missing required local env file: ${toDisplayPath(projectRoot, envPath)}.`,
1011
- copyCommands: existsSync2(envExamplePath) ? buildCopyCommands(projectRoot, envExamplePath, envPath) : undefined
1311
+ copyCommands: existsSync3(envExamplePath) ? buildCopyCommands(projectRoot, envExamplePath, envPath) : undefined
1012
1312
  });
1013
1313
  }
1014
1314
  const localTomlPath = resolve2(runtimeRoot, "configs", "local.toml");
1015
- if (!existsSync2(localTomlPath)) {
1315
+ if (!existsSync3(localTomlPath)) {
1016
1316
  const localExamplePath = resolve2(runtimeRoot, "configs", "local.example.toml");
1017
1317
  issues.push({
1018
1318
  code: "missing_local_toml",
1019
1319
  message: `Missing required local config file: ${toDisplayPath(projectRoot, localTomlPath)}.`,
1020
- copyCommands: existsSync2(localExamplePath) ? buildCopyCommands(projectRoot, localExamplePath, localTomlPath) : undefined
1320
+ copyCommands: existsSync3(localExamplePath) ? buildCopyCommands(projectRoot, localExamplePath, localTomlPath) : undefined
1021
1321
  });
1022
1322
  }
1023
1323
  const autonomyEnabled = Boolean(config.remotebuddy.autonomy.enabled);
@@ -1034,11 +1334,11 @@ function evaluateClientRuntimePreflight(options) {
1034
1334
  }
1035
1335
  const visionPath = resolve2(projectRoot, "vision.md");
1036
1336
  const visionTemplatePath = resolve2(visionTemplateRoot, "vision.example.md");
1037
- if (!existsSync2(visionPath)) {
1337
+ if (!existsSync3(visionPath)) {
1038
1338
  issues.push({
1039
1339
  code: "missing_vision_doc",
1040
1340
  message: "Missing required autonomy vision file: vision.md " + "(required when remotebuddy.autonomy.enabled=true).",
1041
- copyCommands: existsSync2(visionTemplatePath) ? buildCopyCommands(projectRoot, visionTemplatePath, visionPath) : undefined
1341
+ copyCommands: existsSync3(visionTemplatePath) ? buildCopyCommands(projectRoot, visionTemplatePath, visionPath) : undefined
1042
1342
  });
1043
1343
  return {
1044
1344
  ok: false,
@@ -1152,14 +1452,14 @@ function normalizePresenceLookupToken(value) {
1152
1452
  }
1153
1453
 
1154
1454
  // ../shared/src/repo.ts
1155
- import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "fs";
1455
+ import { existsSync as existsSync4, readFileSync as readFileSync3, statSync } from "fs";
1156
1456
  import { resolve as resolve3 } from "path";
1157
1457
  function resolveDotGitEntry(repoRoot) {
1158
1458
  return resolve3(repoRoot, ".git");
1159
1459
  }
1160
1460
  function resolveGitMetadataDir(repoRoot) {
1161
1461
  const dotGitPath = resolveDotGitEntry(repoRoot);
1162
- if (!existsSync3(dotGitPath))
1462
+ if (!existsSync4(dotGitPath))
1163
1463
  return null;
1164
1464
  try {
1165
1465
  const stat = statSync(dotGitPath);
@@ -1178,7 +1478,7 @@ function resolveGitMetadataDir(repoRoot) {
1178
1478
  if (!match)
1179
1479
  return null;
1180
1480
  const gitDir = resolve3(repoRoot, match[1].trim());
1181
- return existsSync3(gitDir) ? gitDir : null;
1481
+ return existsSync4(gitDir) ? gitDir : null;
1182
1482
  } catch {
1183
1483
  return null;
1184
1484
  }
@@ -1217,6 +1517,7 @@ var DEFAULT_RUNTIME_BOOT_TIMEOUT_MS = 90000;
1217
1517
  var DEFAULT_RUNTIME_BOOT_POLL_MS = 1000;
1218
1518
  var DEFAULT_SERVER_BOOT_TIMEOUT_MS = 20000;
1219
1519
  var DEFAULT_SERVICE_STABILITY_GRACE_MS = 4000;
1520
+ var EMBEDDED_SERVICE_RESTART_MAX_ATTEMPTS = 4;
1220
1521
  var WORKERPAL_STARTUP_READINESS_PROBE_MAX_MS = 15000;
1221
1522
  var EMBEDDED_RUNTIME_SAFETY_CAP_DISABLE_ENV = "PUSHPALS_DISABLE_EMBEDDED_SAFETY_CAPS";
1222
1523
  var EMBEDDED_RUNTIME_WINDOWS_SAFETY_CAPS = {
@@ -1292,6 +1593,9 @@ function formatWorkerExecutionReadinessLines(readiness) {
1292
1593
  }
1293
1594
  return lines;
1294
1595
  }
1596
+ function formatEmbeddedRuntimeHealthLines2(health) {
1597
+ return formatEmbeddedRuntimeHealthLines(health);
1598
+ }
1295
1599
  function summarizeWorkerStatusRows(workers) {
1296
1600
  const onlineWorkers = workers.filter((worker) => Boolean(worker?.isOnline) && String(worker?.status ?? "").trim().toLowerCase() !== "offline");
1297
1601
  const idleWorkers = onlineWorkers.filter((worker) => Number(worker?.activeJobCount ?? 0) <= 0);
@@ -1600,7 +1904,7 @@ function copyTrackedRepoPath(repoRoot, sourcePath, destinationPath, force = true
1600
1904
  throw new Error("sourcePath is required");
1601
1905
  }
1602
1906
  const absoluteSource = resolve4(repoRoot, normalizedSource);
1603
- if (!existsSync4(absoluteSource)) {
1907
+ if (!existsSync5(absoluteSource)) {
1604
1908
  throw new Error(`tracked repo source is missing: ${absoluteSource}`);
1605
1909
  }
1606
1910
  const trackedFiles = listTrackedRepoFilesForPath(repoRoot, normalizedSource);
@@ -1633,7 +1937,7 @@ function copyTrackedRepoPath(repoRoot, sourcePath, destinationPath, force = true
1633
1937
  }
1634
1938
  }
1635
1939
  function isCompleteWorkerpalSandboxRoot(root) {
1636
- return existsSync4(join2(root, "package.json")) && existsSync4(join2(root, "apps", "workerpals", "Dockerfile.sandbox")) && existsSync4(join2(root, "packages", "shared", "package.json")) && existsSync4(join2(root, "packages", "protocol", "package.json")) && existsSync4(join2(root, "configs", "default.toml")) && existsSync4(join2(root, "prompts", "workerpals")) && existsSync4(join2(root, "protocol", "schemas", "envelope.schema.json")) && existsSync4(join2(root, "protocol", "schemas", "events.schema.json"));
1940
+ return existsSync5(join2(root, "package.json")) && existsSync5(join2(root, "apps", "workerpals", "Dockerfile.sandbox")) && existsSync5(join2(root, "packages", "shared", "package.json")) && existsSync5(join2(root, "packages", "protocol", "package.json")) && existsSync5(join2(root, "configs", "default.toml")) && existsSync5(join2(root, "prompts", "workerpals")) && existsSync5(join2(root, "protocol", "schemas", "envelope.schema.json")) && existsSync5(join2(root, "protocol", "schemas", "events.schema.json"));
1637
1941
  }
1638
1942
  function populateWorkerpalSandboxRuntimeAssets(runtimeRoot, force) {
1639
1943
  const sandbox = buildWorkerpalSandboxPaths(runtimeRoot);
@@ -1664,7 +1968,7 @@ function copySourceCheckoutWorkerpalSandboxBuildContext(sourceRoot, runtimeRoot,
1664
1968
  for (const [fromPath, toPath] of copyPairs) {
1665
1969
  copyTrackedRepoPath(sourceRoot, fromPath, toPath, force);
1666
1970
  }
1667
- if (existsSync4(join2(sourceRoot, "bun.lock"))) {
1971
+ if (existsSync5(join2(sourceRoot, "bun.lock"))) {
1668
1972
  copyTrackedRepoPath(sourceRoot, "bun.lock", join2(sandbox.root, "bun.lock"), force);
1669
1973
  }
1670
1974
  populateWorkerpalSandboxRuntimeAssets(runtimeRoot, force);
@@ -1682,7 +1986,7 @@ function copyWorkerpalSandboxBuildContext(source, runtimeRoot, force) {
1682
1986
  copySourceCheckoutWorkerpalSandboxBuildContext(source.root, runtimeRoot, force);
1683
1987
  }
1684
1988
  function isCompleteRuntimeAssetSource(source) {
1685
- return existsSync4(source.envExamplePath) && existsSync4(source.visionExamplePath) && existsSync4(join2(source.configsDir, "default.toml")) && existsSync4(source.promptsDir) && existsSync4(join2(source.protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(source.protocolSchemasDir, "events.schema.json"));
1989
+ return existsSync5(source.envExamplePath) && existsSync5(source.visionExamplePath) && existsSync5(join2(source.configsDir, "default.toml")) && existsSync5(source.promptsDir) && existsSync5(join2(source.protocolSchemasDir, "envelope.schema.json")) && existsSync5(join2(source.protocolSchemasDir, "events.schema.json"));
1686
1990
  }
1687
1991
  function resolveBundledRuntimeAssetSource() {
1688
1992
  const candidates = [
@@ -1697,10 +2001,10 @@ function resolveBundledRuntimeAssetSource() {
1697
2001
  return null;
1698
2002
  }
1699
2003
  function looksLikeMonitoringHubBuild(root) {
1700
- return existsSync4(join2(root, "index.html")) && existsSync4(join2(root, "_expo"));
2004
+ return existsSync5(join2(root, "index.html")) && existsSync5(join2(root, "_expo"));
1701
2005
  }
1702
2006
  function latestPathMtimeMs(pathValue) {
1703
- if (!existsSync4(pathValue))
2007
+ if (!existsSync5(pathValue))
1704
2008
  return 0;
1705
2009
  const stat = lstatSync(pathValue);
1706
2010
  let latest = stat.mtimeMs;
@@ -1753,7 +2057,7 @@ function resolveCliSourceCheckoutRoot() {
1753
2057
  resolve4(import.meta.dir, "..", "..", "..")
1754
2058
  ];
1755
2059
  for (const candidate of candidates) {
1756
- if (existsSync4(join2(candidate, "package.json")) && existsSync4(join2(candidate, "apps", "client", "app.json")) && existsSync4(join2(candidate, "scripts", "sync-cli-monitor-ui.ts"))) {
2060
+ if (existsSync5(join2(candidate, "package.json")) && existsSync5(join2(candidate, "apps", "client", "app.json")) && existsSync5(join2(candidate, "scripts", "sync-cli-monitor-ui.ts"))) {
1757
2061
  return candidate;
1758
2062
  }
1759
2063
  }
@@ -1787,7 +2091,7 @@ async function ensureBundledMonitoringHubRoot() {
1787
2091
  return resolveBundledMonitoringHubRoot();
1788
2092
  }
1789
2093
  function repoLooksLikePushPalsSourceCheckout(repoRoot) {
1790
- return existsSync4(join2(repoRoot, "configs", "default.toml"));
2094
+ return existsSync5(join2(repoRoot, "configs", "default.toml"));
1791
2095
  }
1792
2096
  function parseSemverFromPackageVersion(value) {
1793
2097
  const raw = String(value ?? "").trim();
@@ -1837,13 +2141,13 @@ async function resolveRuntimeReleaseTag(explicitTag) {
1837
2141
  return await fetchLatestReleaseTag();
1838
2142
  }
1839
2143
  function writeTextFileIfMissing(pathValue, text) {
1840
- if (existsSync4(pathValue))
2144
+ if (existsSync5(pathValue))
1841
2145
  return;
1842
2146
  mkdirSync(dirname(pathValue), { recursive: true });
1843
2147
  writeFileSync(pathValue, text, "utf8");
1844
2148
  }
1845
2149
  function migrateEmbeddedRuntimeLocalToml(localTomlPath) {
1846
- if (!existsSync4(localTomlPath))
2150
+ if (!existsSync5(localTomlPath))
1847
2151
  return;
1848
2152
  let original;
1849
2153
  try {
@@ -1893,8 +2197,8 @@ function copyBundledRuntimeAssets(runtimeRoot, force = true) {
1893
2197
  }
1894
2198
  function hasSeededRuntimePreflightAssets(runtimeRoot) {
1895
2199
  const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
1896
- const hasProtocolSchemas = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
1897
- 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"));
2200
+ const hasProtocolSchemas = existsSync5(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync5(join2(protocolSchemasDir, "events.schema.json"));
2201
+ return existsSync5(join2(runtimeRoot, ".env.example")) && existsSync5(join2(runtimeRoot, "vision.example.md")) && existsSync5(join2(runtimeRoot, "configs", "default.toml")) && existsSync5(join2(runtimeRoot, "prompts")) && hasProtocolSchemas && isCompleteWorkerpalSandboxRoot(join2(runtimeRoot, "sandbox"));
1898
2202
  }
1899
2203
  function seedRuntimePreflightAssets(runtimeRoot) {
1900
2204
  if (!hasSeededRuntimePreflightAssets(runtimeRoot)) {
@@ -1904,7 +2208,7 @@ function seedRuntimePreflightAssets(runtimeRoot) {
1904
2208
  `);
1905
2209
  const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
1906
2210
  const localTomlPath = join2(runtimeRoot, "configs", "local.toml");
1907
- if (existsSync4(localExamplePath)) {
2211
+ if (existsSync5(localExamplePath)) {
1908
2212
  writeTextFileIfMissing(localTomlPath, readFileSync4(localExamplePath, "utf8"));
1909
2213
  } else {
1910
2214
  writeTextFileIfMissing(localTomlPath, `# Local PushPals runtime overrides
@@ -1949,15 +2253,15 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
1949
2253
  async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
1950
2254
  console.log(`[pushpals] Preparing embedded runtime assets for ${runtimeTag}...`);
1951
2255
  const markerPath = join2(runtimeRoot, ".runtime-assets-tag");
1952
- const currentTag = existsSync4(markerPath) ? readFileSync4(markerPath, "utf8").trim() : "";
2256
+ const currentTag = existsSync5(markerPath) ? readFileSync4(markerPath, "utf8").trim() : "";
1953
2257
  const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
1954
- const hasProtocolSchemas = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
1955
- const hasAssets = 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"));
2258
+ const hasProtocolSchemas = existsSync5(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync5(join2(protocolSchemasDir, "events.schema.json"));
2259
+ const hasAssets = existsSync5(join2(runtimeRoot, ".env.example")) && existsSync5(join2(runtimeRoot, "vision.example.md")) && existsSync5(join2(runtimeRoot, "configs", "default.toml")) && existsSync5(join2(runtimeRoot, "prompts")) && hasProtocolSchemas && isCompleteWorkerpalSandboxRoot(join2(runtimeRoot, "sandbox"));
1956
2260
  if (!hasAssets || currentTag !== runtimeTag) {
1957
2261
  console.log(`[pushpals] Embedded runtime assets ${hasAssets ? "are stale" : "are missing"}; refreshing bundle...`);
1958
2262
  copyBundledRuntimeAssets(runtimeRoot);
1959
- const hasProtocolSchemasAfterCopy = existsSync4(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync4(join2(protocolSchemasDir, "events.schema.json"));
1960
- const hasAssetsAfterCopy = existsSync4(join2(runtimeRoot, ".env.example")) && existsSync4(join2(runtimeRoot, "vision.example.md")) && existsSync4(join2(runtimeRoot, "configs", "default.toml")) && existsSync4(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy && isCompleteWorkerpalSandboxRoot(join2(runtimeRoot, "sandbox"));
2263
+ const hasProtocolSchemasAfterCopy = existsSync5(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync5(join2(protocolSchemasDir, "events.schema.json"));
2264
+ const hasAssetsAfterCopy = existsSync5(join2(runtimeRoot, ".env.example")) && existsSync5(join2(runtimeRoot, "vision.example.md")) && existsSync5(join2(runtimeRoot, "configs", "default.toml")) && existsSync5(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy && isCompleteWorkerpalSandboxRoot(join2(runtimeRoot, "sandbox"));
1961
2265
  if (!hasAssetsAfterCopy) {
1962
2266
  console.log("[pushpals] Bundled runtime assets are incomplete; falling back to release source downloads...");
1963
2267
  await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
@@ -1969,7 +2273,7 @@ async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
1969
2273
  `);
1970
2274
  const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
1971
2275
  const localTomlPath = join2(runtimeRoot, "configs", "local.toml");
1972
- if (existsSync4(localExamplePath)) {
2276
+ if (existsSync5(localExamplePath)) {
1973
2277
  writeTextFileIfMissing(localTomlPath, readFileSync4(localExamplePath, "utf8"));
1974
2278
  } else {
1975
2279
  writeTextFileIfMissing(localTomlPath, `# Local PushPals runtime overrides
@@ -2023,12 +2327,12 @@ function runtimeBinaryFilename(serviceName, platformKey) {
2023
2327
  function resolveRuntimeBinaryInstallState(runtimeRoot, platformKey) {
2024
2328
  const binDir = join2(runtimeRoot, "bin", platformKey);
2025
2329
  const tagMarkerPath = join2(binDir, ".runtime-tag");
2026
- const installedTag = existsSync4(tagMarkerPath) ? readFileSync4(tagMarkerPath, "utf8").trim() : "";
2330
+ const installedTag = existsSync5(tagMarkerPath) ? readFileSync4(tagMarkerPath, "utf8").trim() : "";
2027
2331
  return { binDir, tagMarkerPath, installedTag };
2028
2332
  }
2029
2333
  function cleanupLegacyRuntimeBinaryLayouts(runtimeRoot, platformKey, activeBinDir) {
2030
2334
  const legacyRoot = join2(runtimeRoot, "bin");
2031
- if (!existsSync4(legacyRoot))
2335
+ if (!existsSync5(legacyRoot))
2032
2336
  return;
2033
2337
  let entries;
2034
2338
  try {
@@ -2045,7 +2349,7 @@ function cleanupLegacyRuntimeBinaryLayouts(runtimeRoot, platformKey, activeBinDi
2045
2349
  if (!entry.name.endsWith(`-${platformKey}`))
2046
2350
  continue;
2047
2351
  try {
2048
- rmSync(candidateDir, { recursive: true, force: true });
2352
+ rmSync2(candidateDir, { recursive: true, force: true });
2049
2353
  } catch {}
2050
2354
  }
2051
2355
  }
@@ -2117,7 +2421,7 @@ function resolveEmbeddedBunExecutableFromEnv(env, platform = process.platform, c
2117
2421
  continue;
2118
2422
  for (const candidate of candidates) {
2119
2423
  const fullPath = join2(dir, candidate);
2120
- if (existsSync4(fullPath)) {
2424
+ if (existsSync5(fullPath)) {
2121
2425
  return fullPath;
2122
2426
  }
2123
2427
  }
@@ -2190,7 +2494,7 @@ function appendRuntimeServicesLogLine(logPath, line) {
2190
2494
  } catch {}
2191
2495
  }
2192
2496
  function readLogTail(logPath, maxLines = 40) {
2193
- if (!existsSync4(logPath))
2497
+ if (!existsSync5(logPath))
2194
2498
  return "";
2195
2499
  const raw = readFileSync4(logPath, "utf8");
2196
2500
  const lines = raw.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
@@ -2216,7 +2520,7 @@ function extractRemoteBuddyAutonomousEngineState(logText) {
2216
2520
  return state;
2217
2521
  }
2218
2522
  function readRemoteBuddyAutonomousEngineState(logPath) {
2219
- if (!existsSync4(logPath))
2523
+ if (!existsSync5(logPath))
2220
2524
  return "unknown";
2221
2525
  try {
2222
2526
  return extractRemoteBuddyAutonomousEngineState(readFileSync4(logPath, "utf8"));
@@ -2258,7 +2562,7 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
2258
2562
  const shouldRefreshAll = installedTag !== runtimeTag;
2259
2563
  let downloadedCount = 0;
2260
2564
  for (const binaryPath of requiredAssets) {
2261
- if (!shouldRefreshAll && existsSync4(binaryPath))
2565
+ if (!shouldRefreshAll && existsSync5(binaryPath))
2262
2566
  continue;
2263
2567
  const assetName = binaryPath.split(/[\\/]/).pop() || "";
2264
2568
  await downloadBinaryAsset(runtimeTag, assetName, binaryPath);
@@ -2282,71 +2586,6 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
2282
2586
  console.log("[pushpals] Embedded runtime binaries are ready.");
2283
2587
  return runtimeBinaries;
2284
2588
  }
2285
- function spawnRuntimeService(name, command, cwd, env, logPath, runtimeServicesLogPath) {
2286
- const header = `[pushpals] service=${name} command=${command.join(" ")} cwd=${cwd}`;
2287
- writeFileSync(logPath, `${header}
2288
- `, "utf8");
2289
- if (runtimeServicesLogPath) {
2290
- appendRuntimeServicesLogLine(runtimeServicesLogPath, header);
2291
- }
2292
- const proc = Bun.spawn(command, {
2293
- cwd,
2294
- env,
2295
- stdout: "pipe",
2296
- stderr: "pipe"
2297
- });
2298
- const pipeToLog = async (stream, channel) => {
2299
- if (!stream)
2300
- return;
2301
- const reader = stream.getReader();
2302
- const decoder = new TextDecoder;
2303
- let pending = "";
2304
- while (true) {
2305
- const { done, value } = await reader.read();
2306
- if (done)
2307
- break;
2308
- const chunk = decoder.decode(value, { stream: true });
2309
- if (!chunk)
2310
- continue;
2311
- pending += chunk;
2312
- const lines = pending.split(/\r?\n/);
2313
- pending = lines.pop() ?? "";
2314
- for (const line of lines) {
2315
- const serviceLine = `[${channel}] ${line}`;
2316
- appendFileSync(logPath, `${serviceLine}
2317
- `, "utf8");
2318
- if (runtimeServicesLogPath) {
2319
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[${name}] ${serviceLine}`);
2320
- }
2321
- }
2322
- }
2323
- const rest = decoder.decode();
2324
- if (rest)
2325
- pending += rest;
2326
- if (pending.trim().length > 0) {
2327
- const serviceLine = `[${channel}] ${pending.trimEnd()}`;
2328
- appendFileSync(logPath, `${serviceLine}
2329
- `, "utf8");
2330
- if (runtimeServicesLogPath) {
2331
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[${name}] ${serviceLine}`);
2332
- }
2333
- }
2334
- };
2335
- pipeToLog(proc.stdout, "stdout");
2336
- pipeToLog(proc.stderr, "stderr");
2337
- const service = {
2338
- name,
2339
- proc,
2340
- logPath,
2341
- exited: false,
2342
- exitCode: null
2343
- };
2344
- proc.exited.then((code) => {
2345
- service.exited = true;
2346
- service.exitCode = code;
2347
- });
2348
- return service;
2349
- }
2350
2589
  function buildServiceStopCommand(pid, platform = process.platform) {
2351
2590
  if (platform === "win32" && typeof pid === "number" && pid > 0) {
2352
2591
  return ["taskkill", "/PID", String(pid), "/T", "/F"];
@@ -2560,6 +2799,12 @@ function quoteWindowsCmdArg(value) {
2560
2799
  function isOptionalEmbeddedService(name) {
2561
2800
  return name === "source_control_manager";
2562
2801
  }
2802
+ function computeEmbeddedServiceRestartBackoffMs(attempt) {
2803
+ return computeServiceRestartBackoffMs(attempt);
2804
+ }
2805
+ function shouldRestartEmbeddedService(attempts, maxAttempts = EMBEDDED_SERVICE_RESTART_MAX_ATTEMPTS) {
2806
+ return shouldRestartService(attempts, maxAttempts);
2807
+ }
2563
2808
  async function canSpawnCommand(command, cwd, env) {
2564
2809
  try {
2565
2810
  const proc = Bun.spawn(command, {
@@ -2721,6 +2966,7 @@ async function cleanupLingeringWorkerpalWarmContainers(opts) {
2721
2966
  }
2722
2967
  async function cleanupLingeringPushPalsGitWorktrees(opts) {
2723
2968
  const runCommandWithEnvFn = opts.runCommandWithEnvFn ?? runCommandWithEnv;
2969
+ const forceDeleteWorktreePathFn = opts.forceDeleteWorktreePathFn ?? forceDeleteWorktreePath;
2724
2970
  const list = await runCommandWithEnvFn(["git", "worktree", "list", "--porcelain"], opts.repoRoot, opts.env);
2725
2971
  if (!list.ok) {
2726
2972
  const detail = list.stderr || list.stdout || `exit ${list.exitCode}`;
@@ -2748,7 +2994,13 @@ async function cleanupLingeringPushPalsGitWorktrees(opts) {
2748
2994
  removed += 1;
2749
2995
  continue;
2750
2996
  }
2751
- failures.push(`${entry.path}: ${remove.stderr || remove.stdout || `exit ${remove.exitCode}`}`);
2997
+ const forced = await forceDeleteWorktreePathFn(entry.path);
2998
+ if (forced.removed) {
2999
+ removed += 1;
3000
+ continue;
3001
+ }
3002
+ const removeDetail = remove.stderr || remove.stdout || `exit ${remove.exitCode}`;
3003
+ failures.push(`${entry.path}: ${removeDetail}${forced.lastError ? ` | fallback: ${forced.lastError}` : ""}`);
2752
3004
  }
2753
3005
  const prune = await runCommandWithEnvFn(["git", "worktree", "prune"], opts.repoRoot, opts.env);
2754
3006
  if (!prune.ok) {
@@ -3103,10 +3355,10 @@ function buildCliClearTargets(opts) {
3103
3355
  return targets;
3104
3356
  }
3105
3357
  function removeCliClearTarget(target) {
3106
- if (!existsSync4(target.path))
3358
+ if (!existsSync5(target.path))
3107
3359
  return "missing";
3108
3360
  try {
3109
- rmSync(target.path, { recursive: true, force: true });
3361
+ rmSync2(target.path, { recursive: true, force: true });
3110
3362
  return "removed";
3111
3363
  } catch (err) {
3112
3364
  return {
@@ -3482,7 +3734,6 @@ async function autoStartRuntimeServices(opts) {
3482
3734
  if (resolvedGitBinary) {
3483
3735
  applyResolvedGitBinaryToRuntimeEnv(runtimeEnv, resolvedGitBinary);
3484
3736
  }
3485
- const services = [];
3486
3737
  const startupStartedAt = Date.now();
3487
3738
  const startupPhases = [];
3488
3739
  const recordStartupPhase = (name, startedAt, status) => {
@@ -3521,23 +3772,70 @@ async function autoStartRuntimeServices(opts) {
3521
3772
  console.log(`[pushpals] service log (localbuddy)=${serviceLogPaths.localbuddy}`);
3522
3773
  console.log(`[pushpals] service log (remotebuddy)=${serviceLogPaths.remotebuddy}`);
3523
3774
  console.log(`[pushpals] service log (source_control_manager)=${serviceLogPaths.source_control_manager}`);
3775
+ const serviceManager = new ServiceManager({
3776
+ degradedAction: "Inspect the embedded service log or restart pushpals after fixing the runtime failure.",
3777
+ onEvent: (level, line) => {
3778
+ const cliLine = `[pushpals] ${line.replace(/^Managed /, "Embedded ").replace(/^Restarted managed /, "Restarted embedded ")}`;
3779
+ if (level === "error") {
3780
+ console.error(cliLine);
3781
+ } else if (level === "warn") {
3782
+ console.warn(cliLine);
3783
+ } else {
3784
+ console.log(cliLine);
3785
+ }
3786
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, cliLine);
3787
+ },
3788
+ onHealthChange: (health) => {
3789
+ for (const line of formatEmbeddedRuntimeHealthLines2(health)) {
3790
+ console.error(line);
3791
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, line);
3792
+ }
3793
+ }
3794
+ });
3795
+ const launchService = (name, command) => {
3796
+ const logPath = serviceLogPaths[name];
3797
+ const header = `[pushpals] service=${name} command=${command.join(" ")} cwd=${opts.repoRoot}`;
3798
+ writeFileSync(logPath, `${header}
3799
+ `, "utf8");
3800
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, header);
3801
+ return serviceManager.startService({
3802
+ name,
3803
+ color: "",
3804
+ command,
3805
+ cwd: opts.repoRoot,
3806
+ env: runtimeEnv,
3807
+ logPath,
3808
+ onStdoutLine: (line) => {
3809
+ const serviceLine = `[stdout] ${line}`;
3810
+ appendFileSync(logPath, `${serviceLine}
3811
+ `, "utf8");
3812
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[${name}] ${serviceLine}`);
3813
+ },
3814
+ onStderrLine: (line) => {
3815
+ const serviceLine = `[stderr] ${line}`;
3816
+ appendFileSync(logPath, `${serviceLine}
3817
+ `, "utf8");
3818
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[${name}] ${serviceLine}`);
3819
+ }
3820
+ });
3821
+ };
3524
3822
  const serverHealthy = await probeServer(opts.serverUrl);
3525
3823
  if (!serverHealthy) {
3526
3824
  const serverPhaseStartedAt = Date.now();
3527
3825
  console.log("[pushpals] Starting embedded server...");
3528
- const serverService = spawnRuntimeService("server", [runtimeBinaries.server], opts.repoRoot, runtimeEnv, serviceLogPaths.server, runtimeServicesLogPath);
3529
- services.push(serverService);
3530
- console.log(`[pushpals] server log: ${serverService.logPath}`);
3826
+ const serverService = launchService("server", [runtimeBinaries.server]);
3827
+ const serverLogPath = serverService.logPath ?? serviceLogPaths.server;
3828
+ console.log(`[pushpals] server log: ${serverLogPath}`);
3531
3829
  const serverDeadline = Date.now() + DEFAULT_SERVER_BOOT_TIMEOUT_MS;
3532
3830
  let serverIsReady = false;
3533
3831
  while (Date.now() < serverDeadline) {
3534
3832
  if (serverService.exited) {
3535
- const tail = readLogTail(serverService.logPath);
3833
+ const tail = readLogTail(serverLogPath);
3536
3834
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}).`);
3537
3835
  recordStartupPhase("server", serverPhaseStartedAt, "exited");
3538
3836
  emitStartupTimingSummary("failed", "server exited during bootstrap");
3539
- stopRuntimeServices(services);
3540
- throw new Error(`Embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}). ` + `See ${serverService.logPath}${tail ? `
3837
+ stopRuntimeServices(serviceManager.getServices());
3838
+ throw new Error(`Embedded server exited during bootstrap (code=${serverService.exitCode ?? "unknown"}). ` + `See ${serverLogPath}${tail ? `
3541
3839
  --- server log tail ---
3542
3840
  ${tail}` : ""}`);
3543
3841
  }
@@ -3548,12 +3846,12 @@ ${tail}` : ""}`);
3548
3846
  await Bun.sleep(DEFAULT_RUNTIME_BOOT_POLL_MS);
3549
3847
  }
3550
3848
  if (!serverIsReady) {
3551
- const tail = readLogTail(serverService.logPath);
3849
+ const tail = readLogTail(serverLogPath);
3552
3850
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms.`);
3553
3851
  recordStartupPhase("server", serverPhaseStartedAt, "timeout");
3554
3852
  emitStartupTimingSummary("failed", "server health timeout");
3555
- stopRuntimeServices(services);
3556
- throw new Error(`Embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms. ` + `See ${serverService.logPath}${tail ? `
3853
+ stopRuntimeServices(serviceManager.getServices());
3854
+ throw new Error(`Embedded server did not become healthy within ${DEFAULT_SERVER_BOOT_TIMEOUT_MS}ms. ` + `See ${serverLogPath}${tail ? `
3557
3855
  --- server log tail ---
3558
3856
  ${tail}` : ""}`);
3559
3857
  }
@@ -3567,9 +3865,8 @@ ${tail}` : ""}`);
3567
3865
  if (localBuddyEnabled) {
3568
3866
  const localBuddyPhaseStartedAt = Date.now();
3569
3867
  console.log("[pushpals] Starting embedded LocalBuddy...");
3570
- const localbuddyService = spawnRuntimeService("localbuddy", [runtimeBinaries.localbuddy], opts.repoRoot, runtimeEnv, serviceLogPaths.localbuddy, runtimeServicesLogPath);
3571
- services.push(localbuddyService);
3572
- console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath}`);
3868
+ const localbuddyService = launchService("localbuddy", [runtimeBinaries.localbuddy]);
3869
+ console.log(`[pushpals] localbuddy log: ${localbuddyService.logPath ?? serviceLogPaths.localbuddy}`);
3573
3870
  recordStartupPhase("localbuddy", localBuddyPhaseStartedAt, "started");
3574
3871
  } else {
3575
3872
  recordStartupPhase("localbuddy", Date.now(), "skipped");
@@ -3578,13 +3875,13 @@ ${tail}` : ""}`);
3578
3875
  }
3579
3876
  const remoteBuddyPhaseStartedAt = Date.now();
3580
3877
  console.log("[pushpals] Starting embedded RemoteBuddy...");
3581
- const remotebuddyService = spawnRuntimeService("remotebuddy", [runtimeBinaries.remotebuddy], opts.repoRoot, runtimeEnv, serviceLogPaths.remotebuddy, runtimeServicesLogPath);
3582
- services.push(remotebuddyService);
3583
- console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
3878
+ const remotebuddyService = launchService("remotebuddy", [runtimeBinaries.remotebuddy]);
3879
+ const remotebuddyLogPath = remotebuddyService.logPath ?? serviceLogPaths.remotebuddy;
3880
+ console.log(`[pushpals] remotebuddy log: ${remotebuddyLogPath}`);
3584
3881
  recordStartupPhase("remotebuddy", remoteBuddyPhaseStartedAt, "started");
3585
3882
  let lastReportedRemoteBuddyAutonomyState = "unknown";
3586
3883
  const reportRemoteBuddyAutonomousEngineState = () => {
3587
- const autonomyState = readRemoteBuddyAutonomousEngineState(remotebuddyService.logPath);
3884
+ const autonomyState = readRemoteBuddyAutonomousEngineState(remotebuddyLogPath);
3588
3885
  if (autonomyState === "unknown" || autonomyState === lastReportedRemoteBuddyAutonomyState) {
3589
3886
  return;
3590
3887
  }
@@ -3635,9 +3932,11 @@ ${tail}` : ""}`);
3635
3932
  } else if (scmRemoteStatus.status === "ok") {
3636
3933
  console.log(`[pushpals] Embedded SourceControlManager git=${scmGitProbe.detail}`);
3637
3934
  console.log("[pushpals] Starting embedded SourceControlManager...");
3638
- const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, serviceLogPaths.source_control_manager, runtimeServicesLogPath);
3639
- services.push(sourceControlManagerService);
3640
- console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
3935
+ const sourceControlManagerService = launchService("source_control_manager", [
3936
+ runtimeBinaries.sourceControlManager,
3937
+ "--skip-clean-check"
3938
+ ]);
3939
+ console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath ?? serviceLogPaths.source_control_manager}`);
3641
3940
  recordStartupPhase("source_control_manager", scmPhaseStartedAt, "started");
3642
3941
  } else {
3643
3942
  console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
@@ -3651,30 +3950,36 @@ ${tail}` : ""}`);
3651
3950
  }
3652
3951
  const deadline = Date.now() + DEFAULT_RUNTIME_BOOT_TIMEOUT_MS;
3653
3952
  const readinessPhaseStartedAt = Date.now();
3953
+ const optionalServiceExitWarned = new Set;
3654
3954
  while (Date.now() < deadline) {
3655
3955
  reportRemoteBuddyAutonomousEngineState();
3656
- for (let i = services.length - 1;i >= 0; i--) {
3657
- const service = services[i];
3956
+ for (const service of serviceManager.getServices()) {
3658
3957
  if (service.exited) {
3659
3958
  if (isOptionalEmbeddedService(service.name)) {
3660
- console.warn(`[pushpals] Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}); continuing without SCM.`);
3661
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}); continuing.`);
3662
- const tail2 = readLogTail(service.logPath);
3663
- if (tail2) {
3664
- console.warn(`[pushpals] ${service.name} log tail:
3959
+ const runtimeServiceName2 = service.name;
3960
+ const serviceLogPath2 = service.logPath ?? serviceLogPaths[runtimeServiceName2];
3961
+ if (!optionalServiceExitWarned.has(runtimeServiceName2)) {
3962
+ console.warn(`[pushpals] Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}); startup will continue and host supervisor will attempt recovery.`);
3963
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}); startup will continue and host supervisor will attempt recovery.`);
3964
+ const tail2 = readLogTail(serviceLogPath2);
3965
+ if (tail2) {
3966
+ console.warn(`[pushpals] ${service.name} log tail:
3665
3967
  ${tail2}`);
3666
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] ${service.name} log tail:
3968
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] ${service.name} log tail:
3667
3969
  ${tail2}`);
3970
+ }
3971
+ optionalServiceExitWarned.add(runtimeServiceName2);
3668
3972
  }
3669
- services.splice(i, 1);
3670
3973
  continue;
3671
3974
  }
3672
- const tail = readLogTail(service.logPath);
3975
+ const runtimeServiceName = service.name;
3976
+ const serviceLogPath = service.logPath ?? serviceLogPaths[runtimeServiceName];
3977
+ const tail = readLogTail(serviceLogPath);
3673
3978
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}).`);
3674
3979
  recordStartupPhase("readiness", readinessPhaseStartedAt, "failed");
3675
3980
  emitStartupTimingSummary("failed", `${service.name} exited during startup`);
3676
- stopRuntimeServices(services);
3677
- throw new Error(`Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
3981
+ stopRuntimeServices(serviceManager.getServices());
3982
+ throw new Error(`Embedded ${service.name} exited during startup (code=${service.exitCode ?? "unknown"}). ` + `See ${serviceLogPath}${tail ? `
3678
3983
  --- ${service.name} log tail ---
3679
3984
  ${tail}` : ""}`);
3680
3985
  }
@@ -3686,29 +3991,34 @@ ${tail}` : ""}`);
3686
3991
  const stabilityDeadline = Date.now() + DEFAULT_SERVICE_STABILITY_GRACE_MS;
3687
3992
  while (Date.now() < stabilityDeadline) {
3688
3993
  reportRemoteBuddyAutonomousEngineState();
3689
- for (let i = services.length - 1;i >= 0; i--) {
3690
- const service = services[i];
3994
+ for (const service of serviceManager.getServices()) {
3691
3995
  if (!service.exited)
3692
3996
  continue;
3693
3997
  if (isOptionalEmbeddedService(service.name)) {
3694
- const tail2 = readLogTail(service.logPath);
3695
- console.warn(`[pushpals] Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}); continuing without SCM.`);
3696
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}); continuing.`);
3697
- if (tail2) {
3698
- console.warn(`[pushpals] ${service.name} log tail:
3998
+ const runtimeServiceName2 = service.name;
3999
+ const serviceLogPath2 = service.logPath ?? serviceLogPaths[runtimeServiceName2];
4000
+ if (!optionalServiceExitWarned.has(runtimeServiceName2)) {
4001
+ const tail2 = readLogTail(serviceLogPath2);
4002
+ console.warn(`[pushpals] Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}); startup will continue and host supervisor will attempt recovery.`);
4003
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}); startup will continue and host supervisor will attempt recovery.`);
4004
+ if (tail2) {
4005
+ console.warn(`[pushpals] ${service.name} log tail:
3699
4006
  ${tail2}`);
3700
- appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] ${service.name} log tail:
4007
+ appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] ${service.name} log tail:
3701
4008
  ${tail2}`);
4009
+ }
4010
+ optionalServiceExitWarned.add(runtimeServiceName2);
3702
4011
  }
3703
- services.splice(i, 1);
3704
4012
  continue;
3705
4013
  }
3706
- const tail = readLogTail(service.logPath);
4014
+ const runtimeServiceName = service.name;
4015
+ const serviceLogPath = service.logPath ?? serviceLogPaths[runtimeServiceName];
4016
+ const tail = readLogTail(serviceLogPath);
3707
4017
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}).`);
3708
4018
  recordStartupPhase("readiness", readinessPhaseStartedAt, "failed");
3709
4019
  emitStartupTimingSummary("failed", `${service.name} exited immediately after bootstrap`);
3710
- stopRuntimeServices(services);
3711
- throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
4020
+ stopRuntimeServices(serviceManager.getServices());
4021
+ throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${serviceLogPath}${tail ? `
3712
4022
  --- ${service.name} log tail ---
3713
4023
  ${tail}` : ""}`);
3714
4024
  }
@@ -3719,13 +4029,13 @@ ${tail}` : ""}`);
3719
4029
  emitStartupTimingSummary("ready");
3720
4030
  appendRuntimeServicesLogLine(runtimeServicesLogPath, "[pushpals] embedded runtime is ready.");
3721
4031
  return {
3722
- services,
4032
+ serviceManager,
3723
4033
  pushpalsLogPath: runtimeServicesLogPath
3724
4034
  };
3725
4035
  }
3726
4036
  await Bun.sleep(DEFAULT_RUNTIME_BOOT_POLL_MS);
3727
4037
  }
3728
- stopRuntimeServices(services);
4038
+ stopRuntimeServices(serviceManager.getServices());
3729
4039
  const remoteBuddyHealth = await probeRemoteBuddySessionConsumer(opts.serverUrl, opts.sessionId);
3730
4040
  if (!localBuddyEnabled && !remoteBuddyHealth.ok) {
3731
4041
  appendRuntimeServicesLogLine(runtimeServicesLogPath, `[pushpals] timed out waiting for RemoteBuddy session consumer readiness after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms (${remoteBuddyHealth.detail}).`);
@@ -3745,7 +4055,7 @@ ${tail}` : ""}`);
3745
4055
  throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} and RemoteBuddy session consumer after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
3746
4056
  }
3747
4057
  function readCliState(pathValue) {
3748
- if (!existsSync4(pathValue))
4058
+ if (!existsSync5(pathValue))
3749
4059
  return {};
3750
4060
  try {
3751
4061
  const raw = readFileSync4(pathValue, "utf8");
@@ -3848,11 +4158,11 @@ function resolveMonitoringHubAssetPath(assetRoot, pathname) {
3848
4158
  const candidatePath = resolve4(root, relativePath);
3849
4159
  if (candidatePath !== root && !candidatePath.startsWith(rootPrefix))
3850
4160
  return null;
3851
- if (existsSync4(candidatePath))
4161
+ if (existsSync5(candidatePath))
3852
4162
  return candidatePath;
3853
4163
  if (!extname(relativePath)) {
3854
4164
  const nestedIndexPath = resolve4(root, relativePath, "index.html");
3855
- if ((nestedIndexPath === root || nestedIndexPath.startsWith(rootPrefix)) && existsSync4(nestedIndexPath)) {
4165
+ if ((nestedIndexPath === root || nestedIndexPath.startsWith(rootPrefix)) && existsSync5(nestedIndexPath)) {
3856
4166
  return nestedIndexPath;
3857
4167
  }
3858
4168
  return join2(root, "index.html");
@@ -3861,7 +4171,7 @@ function resolveMonitoringHubAssetPath(assetRoot, pathname) {
3861
4171
  }
3862
4172
  async function serveBundledMonitoringHub(assetRoot, pathname, bootstrap) {
3863
4173
  const assetPath = resolveMonitoringHubAssetPath(assetRoot, pathname);
3864
- if (!assetPath || !existsSync4(assetPath))
4174
+ if (!assetPath || !existsSync5(assetPath))
3865
4175
  return null;
3866
4176
  if (assetPath.endsWith("index.html")) {
3867
4177
  const html = injectMonitoringHubBootstrap(readFileSync4(assetPath, "utf8"), bootstrap);
@@ -4405,7 +4715,7 @@ async function main() {
4405
4715
  platform: `${process.platform}/${process.arch}`,
4406
4716
  repoRoot
4407
4717
  };
4408
- let autoStartedServices = [];
4718
+ let autoStartedServiceManager = null;
4409
4719
  let pushpalsLogPath;
4410
4720
  let resolvedRuntimeTagForAutoStart = preparedRuntime.runtimeTag || parsed.runtimeTag || "";
4411
4721
  const cleanupWorkerpalWarmContainersIfNeeded = async (phase) => {
@@ -4464,17 +4774,24 @@ async function main() {
4464
4774
  }
4465
4775
  return readiness;
4466
4776
  };
4777
+ const reportEmbeddedRuntimeHealth = () => {
4778
+ const health = autoStartedServiceManager?.getHealth() ?? null;
4779
+ for (const line of formatEmbeddedRuntimeHealthLines2(health)) {
4780
+ console.log(line);
4781
+ }
4782
+ return health;
4783
+ };
4467
4784
  const stopAutoStartedServices = () => {
4468
- if (autoStartedServices.length === 0)
4785
+ if (!autoStartedServiceManager)
4469
4786
  return;
4470
- stopRuntimeServices(autoStartedServices);
4471
- autoStartedServices = [];
4787
+ autoStartedServiceManager.stop();
4788
+ autoStartedServiceManager = null;
4472
4789
  };
4473
4790
  const stopAutoStartedServicesGracefully = async (reason) => {
4474
- if (autoStartedServices.length === 0)
4791
+ if (!autoStartedServiceManager)
4475
4792
  return;
4476
- const services = autoStartedServices;
4477
- autoStartedServices = [];
4793
+ const serviceManager = autoStartedServiceManager;
4794
+ autoStartedServiceManager = null;
4478
4795
  const shutdown = await requestLocalRuntimeShutdown(serverUrl, repoRoot, reason);
4479
4796
  if (shutdown.attempted && shutdown.accepted) {
4480
4797
  console.log("[pushpals] Local runtime shutdown accepted; waiting for services to exit...");
@@ -4484,6 +4801,8 @@ async function main() {
4484
4801
  } else if (shutdown.detail) {
4485
4802
  console.warn(`[pushpals] ${shutdown.detail}`);
4486
4803
  }
4804
+ serviceManager.stop();
4805
+ const services = serviceManager.getServices();
4487
4806
  await stopRuntimeServicesGracefully(services);
4488
4807
  await cleanupWorkerpalWarmContainersIfNeeded("cli shutdown");
4489
4808
  await cleanupPushPalsGitWorktreesIfNeeded("cli shutdown");
@@ -4531,7 +4850,7 @@ async function main() {
4531
4850
  startLocalBuddy: resolveCliLocalBuddyAutostart(parsed.runtimeOnly, Boolean(config.localbuddy.enabled)),
4532
4851
  baseEnv: workerpalDockerPrecheck.env
4533
4852
  });
4534
- autoStartedServices = startedRuntime.services;
4853
+ autoStartedServiceManager = startedRuntime.serviceManager;
4535
4854
  pushpalsLogPath = startedRuntime.pushpalsLogPath;
4536
4855
  serverHealthy = await probeServer(serverUrl);
4537
4856
  } catch (err) {
@@ -4655,6 +4974,7 @@ async function main() {
4655
4974
  console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
4656
4975
  console.log(`[pushpals] cliStateFile=${statePath ?? "unavailable"}`);
4657
4976
  reportWorkerExecutionReadinessFromSnapshot(startupWorkerExecutionReadiness);
4977
+ reportEmbeddedRuntimeHealth();
4658
4978
  if (parsed.runtimeOnly) {
4659
4979
  console.log("[pushpals] runtimeOnly=true");
4660
4980
  } else {
@@ -4689,7 +5009,7 @@ ${line}
4689
5009
  try {
4690
5010
  monitoringHub?.stop();
4691
5011
  } catch {}
4692
- if (autoStartedServices.length > 0) {
5012
+ if (autoStartedServiceManager) {
4693
5013
  console.log("[pushpals] Stopping embedded runtime services...");
4694
5014
  }
4695
5015
  await stopAutoStartedServicesGracefully("pushpals CLI exit");
@@ -4767,6 +5087,7 @@ ${line}
4767
5087
  console.log(`[pushpals] pushpalsLog=${pushpalsLogPath ?? "unavailable"}`);
4768
5088
  console.log(monitoringHubUrl ? `[pushpals] monitoringHubUrl=${monitoringHubUrl}` : "[pushpals] monitoringHubUrl=unavailable");
4769
5089
  await reportWorkerExecutionReadiness();
5090
+ reportEmbeddedRuntimeHealth();
4770
5091
  rl.prompt();
4771
5092
  continue;
4772
5093
  }
@@ -4806,6 +5127,7 @@ export {
4806
5127
  waitForWorkerpalCapacity,
4807
5128
  startEmbeddedMonitoringHub,
4808
5129
  shouldRunEmbeddedRuntimeStartupPrechecks,
5130
+ shouldRestartEmbeddedService,
4809
5131
  resolveWorkerExecutionReadiness,
4810
5132
  resolveWindowsWhereExecutableCandidatesForEnv,
4811
5133
  resolveWindowsShellExecutableCandidatesForEnv,
@@ -4832,6 +5154,7 @@ export {
4832
5154
  formatTimestampedCliLine,
4833
5155
  formatSessionEventLine,
4834
5156
  formatRuntimeStartupTimingSummary,
5157
+ formatEmbeddedRuntimeHealthLines2 as formatEmbeddedRuntimeHealthLines,
4835
5158
  extractRemoteBuddySessionConsumerHealth,
4836
5159
  extractRemoteBuddyAutonomousEngineState,
4837
5160
  ensureWorkerpalDockerImageReady,
@@ -4840,6 +5163,7 @@ export {
4840
5163
  describeWorkerExecutionReadiness,
4841
5164
  createSessionEventReplayFilter,
4842
5165
  copyTrackedRepoPath,
5166
+ computeEmbeddedServiceRestartBackoffMs,
4843
5167
  cleanupLingeringWorkerpalWarmContainers,
4844
5168
  cleanupLingeringPushPalsGitWorktrees,
4845
5169
  bundledMonitoringHubNeedsRefresh,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,6 +45,7 @@
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
47
  "test:cli:e2e": "bun test ./tests/integration/cli.e2e.ts",
48
+ "test:start:e2e": "bun test ./tests/integration/start.e2e.ts",
48
49
  "test:root": "bun test tests",
49
50
  "test:protocol": "bun run tests/protocol.integration.ts",
50
51
  "test:integration": "python -u tests/integration/integration_controller.py --mode integration",