iOS: harden share relay and extraction openclaw#19424 thanks @mbelinky

This commit is contained in:
Mariano Belinky
2026-02-17 21:03:57 +01:00
parent 049c88877d
commit a9e2693136
5 changed files with 23 additions and 78 deletions

View File

@@ -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.

View File

@@ -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? {

View File

@@ -17,20 +17,7 @@
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>ai.openclaw.ios</string>
<key>CFBundleURLSchemes</key>
<array>
<string>openclaw</string>
</array>
</dict>
</array>
<key>CFBundleShortVersionString</key>
<string>2026.2.16</string>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>20260216</string>
<key>NSAppTransportSecurity</key>

View File

@@ -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
}
}

View File

@@ -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,