diff --git a/apps/macos/Sources/Clawdis/AppState.swift b/apps/macos/Sources/Clawdis/AppState.swift index 4c08113a4..a39f4b5d7 100644 --- a/apps/macos/Sources/Clawdis/AppState.swift +++ b/apps/macos/Sources/Clawdis/AppState.swift @@ -157,6 +157,7 @@ final class AppState: ObservableObject { var storedForwardCommand = UserDefaults.standard .string(forKey: voiceWakeForwardCommandKey) ?? defaultVoiceWakeForwardCommand + // Guard against older prefs missing flags; the forwarder depends on these for replies. if !storedForwardCommand.contains("--deliver") || !storedForwardCommand.contains("--session") { storedForwardCommand = defaultVoiceWakeForwardCommand UserDefaults.standard.set(storedForwardCommand, forKey: voiceWakeForwardCommandKey) diff --git a/apps/macos/Sources/Clawdis/HealthStore.swift b/apps/macos/Sources/Clawdis/HealthStore.swift index 2a80335b3..4a506c621 100644 --- a/apps/macos/Sources/Clawdis/HealthStore.swift +++ b/apps/macos/Sources/Clawdis/HealthStore.swift @@ -108,21 +108,23 @@ final class HealthStore: ObservableObject { env: env, timeout: 15) - guard response.ok, let data = response.payload, !data.isEmpty else { - self.lastError = response.message ?? "health probe failed" - if onDemand { self.snapshot = nil } + // Always try to decode JSON even when the CLI exits non-zero; it prints the + // failure snapshot before exiting so we can surface a useful message. + if let data = response.payload, !data.isEmpty, + let decoded = try? JSONDecoder().decode(HealthSnapshot.self, from: data) + { + self.snapshot = decoded + if response.ok { + self.lastSuccess = Date() + self.lastError = nil + } else { + self.lastError = self.describeFailure(from: decoded) + } return } - do { - let decoded = try JSONDecoder().decode(HealthSnapshot.self, from: data) - self.snapshot = decoded - self.lastSuccess = Date() - self.lastError = nil - } catch { - self.lastError = error.localizedDescription - if onDemand { self.snapshot = nil } - } + self.lastError = response.message ?? "health probe failed" + if onDemand { self.snapshot = nil } } var state: HealthState { @@ -147,6 +149,22 @@ final class HealthStore: ObservableObject { } return "linked ยท auth \(auth) ยท socket ok" } + + private func describeFailure(from snap: HealthSnapshot) -> String { + if !snap.web.linked { + return "Not linked โ€” run clawdis login" + } + if let connect = snap.web.connect, !connect.ok { + let code = connect.status.map { "status \($0)" } ?? "status unknown" + let elapsed = connect.elapsedMs.map { "\(Int($0))ms" } ?? "unknown duration" + let reason = connect.error ?? "connect failed" + return "\(reason) (\(code), \(elapsed))" + } + if !snap.ipc.exists { + return "IPC socket missing at \(snap.ipc.path)" + } + return "health probe failed" + } } func msToAge(_ ms: Double) -> String { diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 161b2b381..3518716ae 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ onlyBuiltDependencies: - '@whiskeysockets/baileys' + - esbuild - sharp diff --git a/scripts/codesign-mac-app.sh b/scripts/codesign-mac-app.sh index 30f7305cd..e98d3f556 100755 --- a/scripts/codesign-mac-app.sh +++ b/scripts/codesign-mac-app.sh @@ -3,7 +3,7 @@ set -euo pipefail APP_BUNDLE="${1:-dist/Clawdis.app}" IDENTITY="${SIGN_IDENTITY:-}" -ENT_TMP=$(mktemp /tmp/clawdis-entitlements.XXXXXX.plist) +ENT_TMP=$(mktemp -t clawdis-entitlements) if [ ! -d "$APP_BUNDLE" ]; then echo "App bundle not found: $APP_BUNDLE" >&2 @@ -51,6 +51,8 @@ cat > "$ENT_TMP" <<'PLIST' com.apple.security.hardened-runtime + com.apple.security.cs.allow-jit + com.apple.security.automation.apple-events com.apple.security.device.audio-input @@ -75,6 +77,13 @@ if [ -f "$APP_BUNDLE/Contents/MacOS/ClawdisCLI" ]; then echo "Signing CLI helper"; sign_item "$APP_BUNDLE/Contents/MacOS/ClawdisCLI" fi +# Sign bundled relay runtime bits (bun, native addons, libvips dylibs) +if [ -d "$APP_BUNDLE/Contents/Resources/Relay" ]; then + find "$APP_BUNDLE/Contents/Resources/Relay" -type f \( -name "bun" -o -name "*.node" -o -name "*.dylib" \) -print0 | while IFS= read -r -d '' f; do + echo "Signing relay payload: $f"; sign_item "$f" + done +fi + # Sign any embedded frameworks/dylibs if they ever appear if [ -d "$APP_BUNDLE/Contents/Frameworks" ]; then find "$APP_BUNDLE/Contents/Frameworks" \( -name "*.framework" -o -name "*.dylib" \) -print0 | while IFS= read -r -d '' f; do diff --git a/scripts/package-mac-app.sh b/scripts/package-mac-app.sh index bea6ae1d4..03c9b28d6 100755 --- a/scripts/package-mac-app.sh +++ b/scripts/package-mac-app.sh @@ -15,6 +15,9 @@ GIT_COMMIT=$(cd "$ROOT_DIR" && git rev-parse --short HEAD 2>/dev/null || echo "u APP_VERSION="${APP_VERSION:-$PKG_VERSION}" APP_BUILD="${APP_BUILD:-$PKG_VERSION}" +echo "๐Ÿ“ฆ Building JS (pnpm build)" +(cd "$ROOT_DIR" && pnpm build) + cd "$ROOT_DIR/apps/macos" echo "๐Ÿ”จ Building $PRODUCT (debug)" @@ -26,6 +29,7 @@ echo "๐Ÿงน Cleaning old app bundle" rm -rf "$APP_ROOT" mkdir -p "$APP_ROOT/Contents/MacOS" mkdir -p "$APP_ROOT/Contents/Resources" +mkdir -p "$APP_ROOT/Contents/Resources/Relay" echo "๐Ÿ“„ Writing Info.plist" cat > "$APP_ROOT/Contents/Info.plist" <&2 + exit 1 +fi + +echo "๐Ÿงฐ Staging relay runtime (bun + dist + node_modules)" +cp "$BUN_SRC" "$RELAY_DIR/bun" +chmod +x "$RELAY_DIR/bun" +rsync -a --delete --exclude "Clawdis.app" "$ROOT_DIR/dist/" "$RELAY_DIR/dist/" +cp "$ROOT_DIR/package.json" "$RELAY_DIR/" +cp "$ROOT_DIR/pnpm-lock.yaml" "$RELAY_DIR/" +if [ -f "$ROOT_DIR/.npmrc" ]; then + cp "$ROOT_DIR/.npmrc" "$RELAY_DIR/" +fi + +echo "๐Ÿ“ฆ Installing prod node_modules into bundle (hoisted, scripts enabled for sharp)" +PNPM_STORE_DIR="$RELAY_DIR/.pnpm-store" \ +PNPM_HOME="$HOME/Library/pnpm" \ +pnpm install \ + --prod \ + --frozen-lockfile \ + --config.node-linker=hoisted \ + --config.ignore-workspace-root-check=true \ + --config.shared-workspace-lockfile=false \ + --modules-dir "$RELAY_DIR/node_modules" \ + --lockfile-dir "$ROOT_DIR" \ + --dir "$RELAY_DIR" + if [ -f "$CLI_BIN" ]; then echo "๐Ÿ”ง Copying CLI helper" cp "$CLI_BIN" "$APP_ROOT/Contents/MacOS/ClawdisCLI" diff --git a/src/cli/program.ts b/src/cli/program.ts index 4bd8f31c4..39789cbe5 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -17,7 +17,7 @@ import { setHeartbeatsEnabled, type WebMonitorTuning, } from "../provider-web.js"; -import { defaultRuntime } from "../runtime.js"; +import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import { VERSION } from "../version.js"; import { resolveHeartbeatSeconds, @@ -252,10 +252,12 @@ Examples: } const logs: string[] = []; - const runtime = { + const runtime: RuntimeEnv = { log: (msg: string) => logs.push(String(msg)), error: (msg: string) => logs.push(String(msg)), - exit: (_code: number) => {}, + exit: (_code: number): never => { + throw new Error("agentCommand requested exit"); + }, }; const opts: {