diff --git a/CHANGELOG.md b/CHANGELOG.md
index 195f42e1de2..0cb71018bb0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
- iOS/Talk: add a `Background Listening` toggle that keeps Talk Mode active while the app is backgrounded (off by default for battery safety). Thanks @zeulewan.
- iOS/Talk: harden barge-in behavior by disabling interrupt-on-speech when output route is built-in speaker/receiver, reducing false interruptions from local TTS bleed-through. Thanks @zeulewan.
- iOS/Talk: add a `Voice Directive Hint` toggle for Talk Mode prompts so users can disable ElevenLabs voice-switching instructions to save tokens when not needed. (#18250) Thanks @zeulewan.
+- iOS/Share: add an iOS share extension that forwards shared URL/text/image content directly to gateway `agent.request`, with delivery-route fallback and optional receipt acknowledgements. (#19424) Thanks @mbelinky.
- Telegram/Agents: add inline button `style` support (`primary|success|danger`) across message tool schema, Telegram action parsing, send pipeline, and runtime prompt guidance. (#18241) Thanks @obviyus.
- Telegram: surface user message reactions as system events, with configurable `channels.telegram.reactionNotifications` scope. (#10075) Thanks @Glucksberg.
- Mattermost: add emoji reaction actions plus reaction event notifications, including an explicit boolean `remove` flag to avoid accidental removals. (#18608) Thanks @echo931.
diff --git a/apps/ios/ShareExtension/ShareViewController.swift b/apps/ios/ShareExtension/ShareViewController.swift
index dce2e1d9034..0f720f16a9a 100644
--- a/apps/ios/ShareExtension/ShareViewController.swift
+++ b/apps/ios/ShareExtension/ShareViewController.swift
@@ -380,9 +380,6 @@ final class ShareViewController: UIViewController {
unknownCount += 1
}
- if sharedURL != nil, sharedText != nil {
- break
- }
}
}
@@ -403,26 +400,16 @@ final class ShareViewController: UIViewController {
}
let maxBytes = 5_000_000
- var data = rawData
- var mimeType: String
- var fileExt: String
- if let image = UIImage(data: rawData), let normalized = self.normalizedJPEGData(from: image, maxBytes: maxBytes) {
- data = normalized
- mimeType = "image/jpeg"
- fileExt = "jpg"
- } else {
- let utType = UTType(imageUTI)
- mimeType = utType?.preferredMIMEType ?? "application/octet-stream"
- fileExt = utType?.preferredFilenameExtension ?? "bin"
- if data.count > maxBytes {
- data = Data(data.prefix(maxBytes))
- }
+ guard let image = UIImage(data: rawData),
+ let data = self.normalizedJPEGData(from: image, maxBytes: maxBytes)
+ else {
+ return nil
}
return ShareAttachment(
type: "image",
- mimeType: mimeType,
- fileName: "shared-image-\(index + 1).\(fileExt)",
+ mimeType: "image/jpeg",
+ fileName: "shared-image-\(index + 1).jpg",
content: data.base64EncodedString())
}
@@ -446,7 +433,7 @@ final class ShareViewController: UIViewController {
}
guard let fallback = image.jpegData(compressionQuality: 0.35) else { return nil }
if fallback.count <= maxBytes { return fallback }
- return Data(fallback.prefix(maxBytes))
+ return nil
}
private func loadURL(from provider: NSItemProvider) async -> URL? {
diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist
index cce0332dd10..d56fc5b6586 100644
--- a/apps/ios/Sources/Info.plist
+++ b/apps/ios/Sources/Info.plist
@@ -17,20 +17,7 @@
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
- APPL
- CFBundleURLTypes
-
-
- CFBundleURLName
- ai.openclaw.ios
- CFBundleURLSchemes
-
- openclaw
-
-
-
- CFBundleShortVersionString
- 2026.2.16
+ APPL
CFBundleVersion
20260216
NSAppTransportSecurity
diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/ShareGatewayRelaySettings.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/ShareGatewayRelaySettings.swift
index 8450284d834..7b4c3864b37 100644
--- a/apps/shared/OpenClawKit/Sources/OpenClawKit/ShareGatewayRelaySettings.swift
+++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/ShareGatewayRelaySettings.swift
@@ -1,7 +1,4 @@
import Foundation
-#if canImport(UIKit)
-import UIKit
-#endif
public struct ShareGatewayRelayConfig: Codable, Sendable, Equatable {
public let gatewayURLString: String
@@ -29,64 +26,37 @@ public struct ShareGatewayRelayConfig: Codable, Sendable, Equatable {
}
public enum ShareGatewayRelaySettings {
- private static let relayPasteboardName = "ai.openclaw.share.gatewayRelay"
- private static let relayPasteboardType = "ai.openclaw.share.gatewayRelay.v1"
- private static let eventPasteboardName = "ai.openclaw.share.events"
- private static let lastEventType = "ai.openclaw.share.gatewayRelay.event.v1"
+ private static let suiteName = "group.ai.openclaw.shared"
+ private static let relayConfigKey = "share.gatewayRelay.config.v1"
+ private static let lastEventKey = "share.gatewayRelay.event.v1"
+
+ private static var defaults: UserDefaults {
+ UserDefaults(suiteName: self.suiteName) ?? .standard
+ }
public static func loadConfig() -> ShareGatewayRelayConfig? {
- #if canImport(UIKit)
- guard let pasteboard = UIPasteboard(name: UIPasteboard.Name(self.relayPasteboardName), create: false) else {
- return nil
- }
- guard let data = pasteboard.data(forPasteboardType: self.relayPasteboardType) else { return nil }
+ guard let data = self.defaults.data(forKey: self.relayConfigKey) else { return nil }
return try? JSONDecoder().decode(ShareGatewayRelayConfig.self, from: data)
- #else
- return nil
- #endif
}
public static func saveConfig(_ config: ShareGatewayRelayConfig) {
- #if canImport(UIKit)
guard let data = try? JSONEncoder().encode(config) else { return }
- guard let pasteboard = UIPasteboard(name: UIPasteboard.Name(self.relayPasteboardName), create: true) else {
- return
- }
- pasteboard.setData(data, forPasteboardType: self.relayPasteboardType)
- #endif
+ self.defaults.set(data, forKey: self.relayConfigKey)
}
public static func clearConfig() {
- #if canImport(UIKit)
- guard let pasteboard = UIPasteboard(name: UIPasteboard.Name(self.relayPasteboardName), create: false) else {
- return
- }
- pasteboard.items = []
- #endif
+ self.defaults.removeObject(forKey: self.relayConfigKey)
}
public static func saveLastEvent(_ message: String) {
- #if canImport(UIKit)
let timestamp = ISO8601DateFormatter().string(from: Date())
let payload = "[\(timestamp)] \(message)"
- guard let data = payload.data(using: .utf8) else { return }
- guard let pasteboard = UIPasteboard(name: UIPasteboard.Name(self.eventPasteboardName), create: true) else {
- return
- }
- pasteboard.setData(data, forPasteboardType: self.lastEventType)
- #endif
+ self.defaults.set(payload, forKey: self.lastEventKey)
}
public static func loadLastEvent() -> String? {
- #if canImport(UIKit)
- guard let pasteboard = UIPasteboard(name: UIPasteboard.Name(self.eventPasteboardName), create: false) else {
- return nil
- }
- guard let data = pasteboard.data(forPasteboardType: self.lastEventType) else { return nil }
- let value = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ let value = self.defaults.string(forKey: self.lastEventKey)?
+ .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return value.isEmpty ? nil : value
- #else
- return nil
- #endif
}
}
diff --git a/src/gateway/server-node-events.ts b/src/gateway/server-node-events.ts
index dafadd06380..05d2d59dc87 100644
--- a/src/gateway/server-node-events.ts
+++ b/src/gateway/server-node-events.ts
@@ -1,5 +1,4 @@
import { randomUUID } from "node:crypto";
-import type { NodeEvent, NodeEventContext } from "./server-node-events-types.js";
import { resolveSessionAgentId } from "../agents/agent-scope.js";
import { normalizeChannelId } from "../channels/plugins/index.js";
import { createOutboundSendDeps } from "../cli/outbound-send-deps.js";
@@ -15,6 +14,7 @@ import { normalizeMainKey } from "../routing/session-key.js";
import { defaultRuntime } from "../runtime.js";
import { parseMessageWithAttachments } from "./chat-attachments.js";
import { normalizeRpcAttachmentsToChatAttachments } from "./server-methods/attachment-normalize.js";
+import type { NodeEvent, NodeEventContext } from "./server-node-events-types.js";
import {
loadSessionEntry,
pruneLegacyStoreKeys,