reset_bluetooth_module

reset_bluetooth_module

Restarts the system Bluetooth daemon (macOS bluetoothd / Windows bthserv) so the OS re-enumerates paired devices and re-establishes connections. Last-resort fix for paired-but-disconnected devices when toggling the device itself has failed. Requires admin privilege; will briefly drop all Bluetooth connections including any audio call in progress.

Metadata

Name

reset_bluetooth_module

Updated

2 months ago

Source

GitHub source

Risk

Medium

Requires consent

true

Affected scope

system

Code

/**
 * mcp/skills/resetBluetoothModule.ts — reset_bluetooth_module skill
 *
 * Restarts the Bluetooth daemon / service so the OS re-enumerates paired
 * devices and re-establishes connections.  Used as a last-resort fix
 * when devices are paired-but-disconnected, after toggling the device
 * itself failed.
 *
 * Platform strategy
 * -----------------
 * darwin  `sudo launchctl kickstart -k system/com.apple.bluetoothd`
 *         restarts the system bluetoothd.  Requires admin — the G4
 *         scope-boundary check (`affectedScope: ["system"]`) blocks
 *         non-admin runs by aborting the step.
 * win32   `Restart-Service -Name bthserv -Force` via elevated
 *         PowerShell.  Same admin requirement.
 *
 * Dry-run: returns the exact command(s) that would run + reports
 * whether the agent has admin already (best-effort detection via a
 * harmless probe).  Does NOT touch the system in dry-run mode.
 *
 * Notes on side effects
 * ---------------------
 *   - Active connections drop briefly (1–3 s).  A user on a Bluetooth
 *     audio call will hear the audio interrupt.
 *   - Bluetooth-input devices (keyboard, trackpad) reconnect within
 *     2–5 s; the user may temporarily lose input.  This is why the
 *     skill always surfaces the dry-run preview + consent gate.
 */

import * as os from "os";
import { z }   from "zod";

import {
  execAsync,
  runPS,
  isDarwin,
  isWin32,
}                from "./_shared/platform";

// -- Meta ---------------------------------------------------------------------

export const meta = {
  name: "reset_bluetooth_module",
  description:
    "Restarts the system Bluetooth daemon (macOS bluetoothd / Windows bthserv) " +
    "so the OS re-enumerates paired devices and re-establishes connections. " +
    "Last-resort fix for paired-but-disconnected devices when toggling the " +
    "device itself has failed. Requires admin privilege; will briefly drop " +
    "all Bluetooth connections including any audio call in progress.",
  riskLevel:       "medium",
  destructive:     false,
  requiresConsent: true,
  supportsDryRun:  true,
  affectedScope:   ["system"],
  auditRequired:   true,
  escalationHint:  {
    darwin: "sudo /bin/launchctl kickstart -k system/com.apple.bluetoothd",
    win32:  "Restart-Service -Name bthserv -Force  # run from elevated PowerShell",
  },
  schema: {
    dryRun: z
      .boolean()
      .optional()
      .describe("If true, report what would be restarted without touching the system."),
  },
} as const;

// -- Types --------------------------------------------------------------------

export interface ResetBluetoothModuleResult {
  platform:      NodeJS.Platform;
  dryRun:        boolean;
  command:       string;
  /** Best-effort probe — true when the call succeeded. False when stderr indicated permission denial. */
  succeeded:     boolean;
  /** Diagnostic message — usually a stderr line on failure, or a confirmation on success. */
  message:       string;
  durationMs?:   number;
}

// -- Platform helpers ---------------------------------------------------------

const DARWIN_CMD = "sudo /bin/launchctl kickstart -k system/com.apple.bluetoothd";
const WIN_CMD    = "Restart-Service -Name bthserv -Force";

function isPermissionFailure(stderr: string): boolean {
  const s = stderr.toLowerCase();
  return (
    s.includes("permission denied") ||
    s.includes("not permitted") ||
    s.includes("access is denied") ||
    s.includes("requires administrative") ||
    s.includes("must be run as administrator") ||
    s.includes("needed root privileges") ||
    s.includes("a password is required")
  );
}

async function executeDarwin(): Promise<{ succeeded: boolean; message: string; durationMs: number }> {
  const start = Date.now();
  try {
    const { stdout, stderr } = await execAsync(DARWIN_CMD, { timeout: 15_000 });
    const elapsed = Date.now() - start;
    if (stderr && isPermissionFailure(stderr)) {
      return { succeeded: false, message: stderr.trim(), durationMs: elapsed };
    }
    return {
      succeeded: true,
      message:   stdout.trim() || "bluetoothd restarted via launchctl kickstart",
      durationMs: elapsed,
    };
  } catch (err) {
    const elapsed = Date.now() - start;
    const msg = (err as Error).message;
    return { succeeded: false, message: msg, durationMs: elapsed };
  }
}

async function executeWin32(): Promise<{ succeeded: boolean; message: string; durationMs: number }> {
  const start = Date.now();
  try {
    const stdout = await runPS(`$ErrorActionPreference='Stop'; ${WIN_CMD}; 'OK'`, { timeoutMs: 15_000 });
    const elapsed = Date.now() - start;
    if (stdout.trim().toLowerCase().endsWith("ok")) {
      return { succeeded: true, message: "bthserv restarted via Restart-Service", durationMs: elapsed };
    }
    return { succeeded: false, message: stdout.trim() || "Restart-Service returned no output", durationMs: elapsed };
  } catch (err) {
    const elapsed = Date.now() - start;
    const msg = (err as Error).message;
    return { succeeded: false, message: msg, durationMs: elapsed };
  }
}

// -- Exported run function ----------------------------------------------------

export async function run({
  dryRun = false,
}: { dryRun?: boolean } = {}): Promise<ResetBluetoothModuleResult> {
  const platform = os.platform();

if (!isDarwin() && !isWin32()) {
    throw new Error(`reset_bluetooth_module: unsupported platform "${platform}"`);
  }

const command = isDarwin() ? DARWIN_CMD : WIN_CMD;

if (dryRun) {
    return {
      platform,
      dryRun:    true,
      command,
      succeeded: true,
      message:
        `Would restart the Bluetooth daemon. ` +
        `Active Bluetooth connections (audio, keyboards, mice) will drop ` +
        `for 1–3 seconds while the daemon restarts. Requires admin privilege.`,
    };
  }

const result = isDarwin() ? await executeDarwin() : await executeWin32();
  return {
    platform,
    dryRun:    false,
    command,
    succeeded: result.succeeded,
    message:   result.message,
    durationMs: result.durationMs,
  };
}

// -- Test helpers -------------------------------------------------------------

/** Exported for unit tests only — do not use from production code. */
export const __testing = {
  isPermissionFailure,
  DARWIN_CMD,
  WIN_CMD,
};