diff --git a/apps/macos/Sources/Clawdis/NotificationManager.swift b/apps/macos/Sources/Clawdis/NotificationManager.swift
index 3f48da8c2..4650d3afe 100644
--- a/apps/macos/Sources/Clawdis/NotificationManager.swift
+++ b/apps/macos/Sources/Clawdis/NotificationManager.swift
@@ -1,9 +1,17 @@
import ClawdisIPC
import Foundation
+import Security
import UserNotifications
@MainActor
struct NotificationManager {
+ private static let hasTimeSensitiveEntitlement: Bool = {
+ guard let task = SecTaskCreateFromSelf(nil) else { return false }
+ let key = "com.apple.developer.usernotifications.time-sensitive" as CFString
+ guard let val = SecTaskCopyValueForEntitlement(task, key, nil) else { return false }
+ return (val as? Bool) == true
+ }()
+
func send(title: String, body: String, sound: String?, priority: NotificationPriority? = nil) async -> Bool {
let center = UNUserNotificationCenter.current()
let status = await center.notificationSettings()
@@ -29,7 +37,7 @@ struct NotificationManager {
case .active:
content.interruptionLevel = .active
case .timeSensitive:
- content.interruptionLevel = .timeSensitive
+ content.interruptionLevel = Self.hasTimeSensitiveEntitlement ? .timeSensitive : .active
}
}
diff --git a/docs/clawdis-mac.md b/docs/clawdis-mac.md
index b2970e7f3..6cdfd68a6 100644
--- a/docs/clawdis-mac.md
+++ b/docs/clawdis-mac.md
@@ -70,6 +70,7 @@ struct Response { ok: Bool; message?: String; payload?: Data }
- `run -- cmd args... [--cwd] [--env KEY=VAL] [--timeout 30] [--needs-screen-recording]`
- `status`
- Sounds: supply any macOS alert name with `--sound` per notification; omit the flag to use the system default. There is no longer a persisted “default sound” in the app UI.
+- Priority: `timeSensitive` is best-effort and falls back to `active` unless the app is signed with the Time Sensitive Notifications entitlement.
- Internals: builds Request, connects via AsyncXPCConnection, prints Response as JSON to stdout.
## Integration with clawdis/Clawdis (Node/TS)
diff --git a/scripts/codesign-mac-app.sh b/scripts/codesign-mac-app.sh
index 25f000527..3a124d9f8 100755
--- a/scripts/codesign-mac-app.sh
+++ b/scripts/codesign-mac-app.sh
@@ -3,7 +3,9 @@ set -euo pipefail
APP_BUNDLE="${1:-dist/Clawdis.app}"
IDENTITY="${SIGN_IDENTITY:-}"
-ENT_TMP=$(mktemp -t clawdis-entitlements)
+ENT_TMP_BASE=$(mktemp -t clawdis-entitlements-base)
+ENT_TMP_APP=$(mktemp -t clawdis-entitlements-app)
+ENT_TMP_APP_BASE=$(mktemp -t clawdis-entitlements-app-base)
if [ ! -d "$APP_BUNDLE" ]; then
echo "App bundle not found: $APP_BUNDLE" >&2
@@ -44,7 +46,41 @@ fi
echo "Using signing identity: $IDENTITY"
-cat > "$ENT_TMP" <<'PLIST'
+cat > "$ENT_TMP_BASE" <<'PLIST'
+
+
+
+
+ com.apple.security.hardened-runtime
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.automation.apple-events
+
+ com.apple.security.device.audio-input
+
+
+
+PLIST
+
+cat > "$ENT_TMP_APP_BASE" <<'PLIST'
+
+
+
+
+ com.apple.security.hardened-runtime
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.automation.apple-events
+
+ com.apple.security.device.audio-input
+
+
+
+PLIST
+
+cat > "$ENT_TMP_APP" <<'PLIST'
@@ -63,12 +99,27 @@ cat > "$ENT_TMP" <<'PLIST'
PLIST
+# The time-sensitive entitlement is restricted and needs to be present in a
+# matching provisioning profile when using Apple Development signing.
+# Avoid breaking local debug builds by only enabling it when forced, or when
+# using distribution-style identities.
+APP_ENTITLEMENTS="$ENT_TMP_APP_BASE"
+if [[ "${ENABLE_TIME_SENSITIVE_NOTIFICATIONS:-}" == "1" ]]; then
+ APP_ENTITLEMENTS="$ENT_TMP_APP"
+elif [[ "$IDENTITY" == *"Developer ID Application"* ]] || [[ "$IDENTITY" == *"Apple Distribution"* ]]; then
+ APP_ENTITLEMENTS="$ENT_TMP_APP"
+else
+ echo "Note: Time Sensitive Notifications entitlement disabled for this signing identity."
+ echo " To force it: ENABLE_TIME_SENSITIVE_NOTIFICATIONS=1 scripts/codesign-mac-app.sh "
+fi
+
# clear extended attributes to avoid stale signatures
xattr -cr "$APP_BUNDLE" 2>/dev/null || true
sign_item() {
local target="$1"
- codesign --force --options runtime --timestamp=none --entitlements "$ENT_TMP" --sign "$IDENTITY" "$target"
+ local entitlements="$2"
+ codesign --force --options runtime --timestamp=none --entitlements "$entitlements" --sign "$IDENTITY" "$target"
}
sign_plain_item() {
@@ -78,16 +129,16 @@ sign_plain_item() {
# Sign main binary and CLI helper if present
if [ -f "$APP_BUNDLE/Contents/MacOS/Clawdis" ]; then
- echo "Signing main binary"; sign_item "$APP_BUNDLE/Contents/MacOS/Clawdis"
+ echo "Signing main binary"; sign_item "$APP_BUNDLE/Contents/MacOS/Clawdis" "$APP_ENTITLEMENTS"
fi
if [ -f "$APP_BUNDLE/Contents/MacOS/ClawdisCLI" ]; then
- echo "Signing CLI helper"; sign_item "$APP_BUNDLE/Contents/MacOS/ClawdisCLI"
+ echo "Signing CLI helper"; sign_item "$APP_BUNDLE/Contents/MacOS/ClawdisCLI" "$ENT_TMP_BASE"
fi
# Sign bundled gateway payload (native addons, libvips dylibs)
if [ -d "$APP_BUNDLE/Contents/Resources/Relay" ]; then
find "$APP_BUNDLE/Contents/Resources/Relay" -type f \( -name "*.node" -o -name "*.dylib" \) -print0 | while IFS= read -r -d '' f; do
- echo "Signing gateway payload: $f"; sign_item "$f"
+ echo "Signing gateway payload: $f"; sign_item "$f" "$ENT_TMP_BASE"
done
fi
@@ -115,7 +166,7 @@ if [ -d "$APP_BUNDLE/Contents/Frameworks" ]; then
fi
# Finally sign the bundle
-sign_item "$APP_BUNDLE"
+sign_item "$APP_BUNDLE" "$APP_ENTITLEMENTS"
-rm -f "$ENT_TMP"
+rm -f "$ENT_TMP_BASE" "$ENT_TMP_APP_BASE" "$ENT_TMP_APP"
echo "Codesign complete for $APP_BUNDLE"