Files
openclaw/apps/shared/OpenClawKit/Sources/OpenClawKit/ShareToAgentDeepLink.swift
Colin Johnson f6e51ff99a feat(ios): refresh pro UI and gateway flows (#87367)
Summary:
- Replace the legacy iOS shell with Pro Command, Chat, Agents, and Settings tabs.
- Wire iOS chat/session/settings/diagnostics and realtime Talk flows through gateway-backed APIs.
- Add gateway/session and shared chat coverage for the new iOS flow.

Verification:
- git diff --check
- node scripts/run-vitest.mjs src/gateway/server.sessions.create.test.ts src/gateway/talk-realtime-relay.test.ts
- swift test --filter ChatViewModelTests (apps/shared/OpenClawKit)
- xcodebuild build for Nimrod's iPhone succeeded; install succeeded; launch was blocked because the phone was locked

Known follow-up:
- Preserve traceLevel in sessions.create parent runtime inheritance and keep the changelog credit in the follow-up patch.
2026-05-28 17:23:26 +03:00

71 lines
2.5 KiB
Swift

import Foundation
public struct SharedContentPayload: Sendable, Equatable {
public let title: String?
public let url: URL?
public let text: String?
public init(title: String?, url: URL?, text: String?) {
self.title = title
self.url = url
self.text = text
}
}
public enum ShareToAgentDeepLink {
public static func buildURL(from payload: SharedContentPayload, instruction: String? = nil) -> URL? {
let message = self.buildMessage(from: payload, instruction: instruction)
guard !message.isEmpty else { return nil }
var components = URLComponents()
components.scheme = "openclaw"
components.host = "agent"
components.queryItems = [
URLQueryItem(name: "message", value: message),
URLQueryItem(name: "thinking", value: "low"),
]
return components.url
}
public static func buildMessage(from payload: SharedContentPayload, instruction: String? = nil) -> String {
let title = self.clean(payload.title)
let text = self.clean(payload.text)
let urlText = payload.url?.absoluteString.trimmingCharacters(in: .whitespacesAndNewlines)
let resolvedInstruction = self.clean(instruction) ?? self.clean(ShareToAgentSettings.loadDefaultInstruction())
let hasSharedContent = title != nil || text != nil || self.clean(urlText) != nil
guard hasSharedContent || resolvedInstruction != nil else { return "" }
var lines: [String] = []
if hasSharedContent {
lines.append("Shared from iOS.")
}
if let title, !title.isEmpty {
lines.append("Title: \(title)")
}
if let urlText, !urlText.isEmpty {
lines.append("URL: \(urlText)")
}
if let text, !text.isEmpty {
lines.append("Text:\n\(text)")
}
if let resolvedInstruction {
lines.append(resolvedInstruction)
}
let message = lines.joined(separator: "\n\n")
return self.limit(message, maxCharacters: 2400)
}
private static func clean(_ value: String?) -> String? {
guard let value else { return nil }
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
}
private static func limit(_ value: String, maxCharacters: Int) -> String {
guard value.count > maxCharacters else { return value }
return String(value.prefix(maxCharacters))
}
}