agent-afk 5.250.0 → 5.251.0

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.
@@ -161,6 +161,41 @@ export function isManualBotRunning(pidFilePath, probeFn = process.kill) {
161
161
  }
162
162
  }
163
163
 
164
+ /**
165
+ * Pure function. Encapsulates the platform + isGlobalInstall gate that guards
166
+ * daemon restarts in the main block. Extracted so unit tests can exercise the
167
+ * decision logic without running the `isMain` block.
168
+ *
169
+ * The function returns the array of labels that were restarted (empty when the
170
+ * guard fires and the restart is skipped), mirroring restartLaunchdServices.
171
+ *
172
+ * @param {object} [opts]
173
+ * @param {string} [opts.platform] - Platform string; defaults to process.platform.
174
+ * @param {object} [opts.env] - Environment variable bag; defaults to process.env.
175
+ * @param {string} [opts.pkgRoot] - Absolute path to the package root.
176
+ * @param {function} [opts.existsFn] - Injectable fs.existsSync for isGlobalInstall.
177
+ * @param {function} [opts.restartFn] - Injectable restartLaunchdServices; defaults to the module export.
178
+ * @returns {string[]} Labels that were restarted, or [] when the guard fires.
179
+ */
180
+ export function maybeRestartServices(opts = {}) {
181
+ const platform = opts.platform ?? process.platform;
182
+ const env = opts.env ?? process.env;
183
+ const pkgRoot =
184
+ opts.pkgRoot ?? fileURLToPath(new URL('..', import.meta.url));
185
+ const existsFn = opts.existsFn ?? existsSync;
186
+ const restartFn = opts.restartFn ?? restartLaunchdServices;
187
+
188
+ if (platform !== 'darwin') return [];
189
+ if (!isGlobalInstall(pkgRoot, env, existsFn)) return [];
190
+
191
+ try {
192
+ return restartFn({ labels: ['com.afk.daemon'] });
193
+ } catch {
194
+ // restartLaunchdServices is already fail-open; belt-and-suspenders.
195
+ return [];
196
+ }
197
+ }
198
+
164
199
  /**
165
200
  * Restart installed AFK launchd services so they pick up the just-installed
166
201
  * code. A long-running Node process keeps the OLD module graph in memory after
@@ -355,26 +390,19 @@ if (isMain) {
355
390
  // global package upgrade (`npm install -g`). A local `pnpm install` inside a
356
391
  // source checkout or managed worktree must NEVER trigger a restart — doing so
357
392
  // kills any in-flight sessions owned by that daemon. The isGlobalInstall()
358
- // guard below enforces this; it fails closed toward NOT restarting whenever
359
- // either detection signal is absent or ambiguous.
360
- if (process.platform === 'darwin') {
361
- if (isGlobalInstall(_pkgRoot, process.env, existsSync)) {
362
- try {
363
- const restarted = restartLaunchdServices({
364
- labels: ['com.afk.daemon'],
365
- });
366
- if (restarted.length > 0) {
367
- const names = restarted.map((l) => l.replace(/^com\.afk\./, '')).join(', ');
368
- process.stdout.write(
369
- `\n↻ Restarted AFK service(s) onto the new version: ${names}\n`,
370
- );
371
- }
372
- } catch {
373
- // restartLaunchdServices is already fail-open; belt-and-suspenders.
374
- }
393
+ // guard inside maybeRestartServices enforces this; it fails closed toward NOT
394
+ // restarting whenever either detection signal is absent or ambiguous.
395
+ try {
396
+ const restarted = maybeRestartServices({ pkgRoot: _pkgRoot });
397
+ if (restarted.length > 0) {
398
+ const names = restarted.map((l) => l.replace(/^com\.afk\./, '')).join(', ');
399
+ process.stdout.write(
400
+ `\n↻ Restarted AFK service(s) onto the new version: ${names}\n`,
401
+ );
375
402
  }
403
+ } catch {
404
+ // maybeRestartServices is already fail-open; belt-and-suspenders.
376
405
  }
377
-
378
406
  // 2. A manually-started telegram bot (`afk telegram start`) is not supervised
379
407
  // by launchd, so we cannot relaunch it safely from an npm lifecycle script.
380
408
  // Earlier versions SIGTERM'd it here, which left it dead with nothing to