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,