diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb70c6a..3de14a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,27 @@ permissions: contents: read jobs: + swiftlint: + name: SwiftLint + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Select latest Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest + + - name: Install SwiftLint + run: brew install swiftlint + + - name: Print SwiftLint version + run: swiftlint version + + - name: Lint Swift sources + run: Scripts/lint-swift.sh + swift-validation: name: Swift Validation runs-on: ubuntu-latest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..0c300d7 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,60 @@ +name: Documentation + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: docs-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + docc-site: + name: Build DocC Site + runs-on: macos-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Select latest Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest + + - name: Print Xcode and Swift versions + run: | + xcodebuild -version + xcrun swift --version + + - name: Build DocC site + run: Scripts/build-docs-site.sh + + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: .build/docc-static-site + + deploy: + name: Deploy GitHub Pages + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: docc-site + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + with: + enablement: true + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index f054d94..aaac473 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -25,3 +25,4 @@ jobs: uses: gitleaks/gitleaks-action@v2 env: GITLEAKS_CONFIG: .github/.gitleaks.toml + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..31c7ef3 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,33 @@ +included: + - Sources + - Tests + - Examples +excluded: + - .build + - .cursor + - .swiftpm + - internal-docs +only_rules: + - duplicate_imports + - empty_count + - explicit_init + - first_where + - line_length + - missing_docs + - modifier_order + - trailing_newline + - trailing_whitespace + - unused_import +line_length: + warning: 160 + error: 200 + ignores_urls: true +missing_docs: + warning: + - open + - public + excludes_extensions: true + excludes_inherited_types: true + excludes_trivial_init: false +trailing_whitespace: + ignores_empty_lines: true diff --git a/Examples/tvOS/OpenClawtvOS/OpenClawtvOSUITests/OpenClawtvOSUITests.swift b/Examples/tvOS/OpenClawtvOS/OpenClawtvOSUITests/OpenClawtvOSUITests.swift index a89be10..eef622a 100644 --- a/Examples/tvOS/OpenClawtvOS/OpenClawtvOSUITests/OpenClawtvOSUITests.swift +++ b/Examples/tvOS/OpenClawtvOS/OpenClawtvOSUITests/OpenClawtvOSUITests.swift @@ -15,7 +15,8 @@ final class OpenClawtvOSUITests: XCTestCase { // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + // In UI tests it is important to set the initial state, such as interface + // orientation, before the tests run. `setUpWithError()` is a good place. } override func tearDownWithError() throws { diff --git a/README.md b/README.md index abe08b7..d5644fe 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ # OpenClawKit [![CI](https://github.com/MarcoDotIO/OpenClawKit/actions/workflows/ci.yml/badge.svg)](https://github.com/MarcoDotIO/OpenClawKit/actions/workflows/ci.yml) +[![Documentation](https://github.com/MarcoDotIO/OpenClawKit/actions/workflows/docs.yml/badge.svg)](https://github.com/MarcoDotIO/OpenClawKit/actions/workflows/docs.yml) [![Security](https://github.com/MarcoDotIO/OpenClawKit/actions/workflows/security.yml/badge.svg)](https://github.com/MarcoDotIO/OpenClawKit/actions/workflows/security.yml) [![Release](https://github.com/MarcoDotIO/OpenClawKit/actions/workflows/release.yml/badge.svg)](https://github.com/MarcoDotIO/OpenClawKit/actions/workflows/release.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) @@ -13,6 +14,9 @@ OpenClawKit is a Swift-native agent SDK for Apple platforms and Linux services. It provides a complete runtime surface: protocol contracts, model routing, channels, skills, memory, observability, security, iOS app integrations, and release-grade tooling. +Public SDK documentation is published as a Swift-DocC site on GitHub Pages, with +stable companion notes in `docs/`. + ## 2026.2.4 Highlights ### OpenClaw 2026.3.13 Parity @@ -422,19 +426,17 @@ Scripts/validate-apple-matrix.sh --platform ios CI workflows: -- `ci.yml` - Swift validation, iOS build, and Apple platform matrix checks +- `ci.yml` - Swift validation, linting, iOS build, and Apple platform matrix checks +- `docs.yml` - Swift-DocC build validation and GitHub Pages deployment - `security.yml` - secret/security scanning - `release.yml` - changelog-gated tagged releases ## Documentation +- GitHub Pages DocC site: `https://marcodotio.github.io/OpenClawKit/` - [Architecture](docs/architecture.md) - [API Surface](docs/api-surface.md) - [Testing Guide](docs/testing.md) -- [2026.2.4 Parity Manifest](docs/parity-2026.2.4.md) -- [2026.3.11 Parity Manifest](docs/parity-2026.3.11.md) -- [2026.2.1 Parity Manifest](docs/parity-2026.2.1.md) -- [2026.2.1 Roadmap](docs/roadmap-2026.2.1.md) - [Changelog](CHANGELOG.md) Protocol generation: diff --git a/Scripts/build-docs-site.sh b/Scripts/build-docs-site.sh new file mode 100755 index 0000000..7b54148 --- /dev/null +++ b/Scripts/build-docs-site.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DERIVED_DATA_PATH="${DERIVED_DATA_PATH:-${ROOT_DIR}/.build/docc-derived-data}" +MERGED_ARCHIVE_PATH="${DOCC_MERGED_ARCHIVE_PATH:-${ROOT_DIR}/.build/OpenClawKit.doccarchive}" +SITE_OUTPUT_PATH="${DOCC_STATIC_SITE_OUTPUT_PATH:-${ROOT_DIR}/.build/docc-static-site}" +HOSTING_BASE_PATH="${DOCC_HOSTING_BASE_PATH:-/OpenClawKit}" +SCHEME="${DOCC_SCHEME:-OpenClawKit-Package}" +FIRST_PARTY_MODULES=( + OpenClawProtocol + OpenClawCore + OpenClawGateway + OpenClawAgents + OpenClawPlugins + OpenClawChannels + OpenClawMemory + OpenClawMedia + OpenClawModels + OpenClawSkills + OpenClawKit + OpenClawChatUI +) + +if ! command -v xcodebuild >/dev/null 2>&1; then + echo "xcodebuild is required to generate DocC archives." >&2 + exit 1 +fi + +rm -rf "${DERIVED_DATA_PATH}" "${MERGED_ARCHIVE_PATH}" "${SITE_OUTPUT_PATH}" + +xcodebuild docbuild \ + -scheme "${SCHEME}" \ + -destination 'generic/platform=macOS' \ + -derivedDataPath "${DERIVED_DATA_PATH}" \ + CODE_SIGNING_ALLOWED=NO + +archives=() +for module in "${FIRST_PARTY_MODULES[@]}"; do + archive_path="${DERIVED_DATA_PATH}/Build/Products/Debug/${module}.doccarchive" + if [[ ! -d "${archive_path}" ]]; then + echo "Missing DocC archive for ${module}: ${archive_path}" >&2 + exit 1 + fi + archives+=("${archive_path}") +done + +xcrun docc merge \ + "${archives[@]}" \ + --output-path "${MERGED_ARCHIVE_PATH}" \ + --synthesized-landing-page-name OpenClawKit \ + --synthesized-landing-page-kind Package \ + --synthesized-landing-page-topics-style detailedGrid + +xcrun docc process-archive transform-for-static-hosting \ + "${MERGED_ARCHIVE_PATH}" \ + --output-path "${SITE_OUTPUT_PATH}" \ + --hosting-base-path "${HOSTING_BASE_PATH}" + +test -f "${SITE_OUTPUT_PATH}/index.html" +test -d "${SITE_OUTPUT_PATH}/documentation/openclawkit" diff --git a/Scripts/lint-swift.sh b/Scripts/lint-swift.sh new file mode 100755 index 0000000..342b472 --- /dev/null +++ b/Scripts/lint-swift.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CONFIG_PATH="${ROOT_DIR}/.swiftlint.yml" + +if ! command -v swiftlint >/dev/null 2>&1; then + echo "swiftlint is required. Install it with Homebrew: brew install swiftlint" >&2 + exit 1 +fi + +swift_files=() +while IFS= read -r -d '' file; do + swift_files+=("${file}") +done < <(find "${ROOT_DIR}/Sources" "${ROOT_DIR}/Tests" "${ROOT_DIR}/Examples" -name '*.swift' -print0) +swift_files+=("${ROOT_DIR}/Package.swift") + +swiftlint lint \ + --config "${CONFIG_PATH}" \ + --force-exclude \ + --strict \ + "${swift_files[@]}" diff --git a/Sources/OpenClawChannels/ChannelAdapter.swift b/Sources/OpenClawChannels/ChannelAdapter.swift index c6c8949..4129f78 100644 --- a/Sources/OpenClawChannels/ChannelAdapter.swift +++ b/Sources/OpenClawChannels/ChannelAdapter.swift @@ -103,6 +103,7 @@ public protocol ChannelAdapter: Sendable { } public extension ChannelAdapter { + /// Default no-op typing indicator implementation for channels without typing support. func sendTypingIndicator(accountID _: String?, peerID _: String) async throws {} } diff --git a/Sources/OpenClawChatUI/ChatModels.swift b/Sources/OpenClawChatUI/ChatModels.swift index c58f2d7..fbb441a 100644 --- a/Sources/OpenClawChatUI/ChatModels.swift +++ b/Sources/OpenClawChatUI/ChatModels.swift @@ -6,10 +6,12 @@ import Foundation #if canImport(AppKit) import AppKit +/// Platform image type used by shared chat UI attachment previews on AppKit. public typealias OpenClawPlatformImage = NSImage #elseif canImport(UIKit) import UIKit +/// Platform image type used by shared chat UI attachment previews on UIKit. public typealias OpenClawPlatformImage = UIImage #endif diff --git a/Sources/OpenClawChatUI/ChatTransport.swift b/Sources/OpenClawChatUI/ChatTransport.swift index 49bd91d..d3ab2ab 100644 --- a/Sources/OpenClawChatUI/ChatTransport.swift +++ b/Sources/OpenClawChatUI/ChatTransport.swift @@ -31,8 +31,10 @@ public protocol OpenClawChatTransport: Sendable { } extension OpenClawChatTransport { + /// Updates the active session key used for push-event routing. public func setActiveSessionKey(_: String) async throws {} + /// Resets one session when the transport supports a server-side reset call. public func resetSession(sessionKey _: String) async throws { throw NSError( domain: "OpenClawChatTransport", @@ -40,6 +42,7 @@ extension OpenClawChatTransport { userInfo: [NSLocalizedDescriptionKey: "sessions.reset not supported by this transport"]) } + /// Aborts the currently running assistant response for a session when supported. public func abortRun(sessionKey _: String, runId _: String) async throws { throw NSError( domain: "OpenClawChatTransport", @@ -47,6 +50,7 @@ extension OpenClawChatTransport { userInfo: [NSLocalizedDescriptionKey: "chat.abort not supported by this transport"]) } + /// Lists recent sessions when the backing transport exposes multi-session history. public func listSessions(limit _: Int?) async throws -> OpenClawChatSessionsListResponse { throw NSError( domain: "OpenClawChatTransport", @@ -54,6 +58,7 @@ extension OpenClawChatTransport { userInfo: [NSLocalizedDescriptionKey: "sessions.list not supported by this transport"]) } + /// Lists available model picker choices when the transport supports model discovery. public func listModels() async throws -> [OpenClawChatModelChoice] { throw NSError( domain: "OpenClawChatTransport", @@ -61,6 +66,7 @@ extension OpenClawChatTransport { userInfo: [NSLocalizedDescriptionKey: "models.list not supported by this transport"]) } + /// Changes the current session model when the transport supports session patching. public func setSessionModel(sessionKey _: String, model _: String?) async throws { throw NSError( domain: "OpenClawChatTransport", @@ -68,6 +74,7 @@ extension OpenClawChatTransport { userInfo: [NSLocalizedDescriptionKey: "sessions.patch(model) not supported by this transport"]) } + /// Changes the current session thinking level when the transport supports session patching. public func setSessionThinking(sessionKey _: String, thinkingLevel _: String) async throws { throw NSError( domain: "OpenClawChatTransport", diff --git a/Sources/OpenClawChatUI/ChatViewModel.swift b/Sources/OpenClawChatUI/ChatViewModel.swift index b2e36bb..2fb32d4 100644 --- a/Sources/OpenClawChatUI/ChatViewModel.swift +++ b/Sources/OpenClawChatUI/ChatViewModel.swift @@ -12,28 +12,51 @@ import UIKit private let chatUILogger = Logger(subsystem: "ai.openclaw", category: "OpenClawChatUI") +/// Observable view model for the shared SwiftUI chat experience. +/// +/// Create one instance per active chat surface, then bind its published state to +/// your SwiftUI views and call ``load()``, ``send()``, and the selection helpers +/// in response to user actions. @MainActor @Observable public final class OpenClawChatViewModel { + /// Sentinel value used when the active session should inherit the default model. public static let defaultModelSelectionID = "__default__" + /// Fully decoded conversation transcript for the active session. public private(set) var messages: [OpenClawChatMessage] = [] + /// Draft input text that will be sent on the next user action. public var input: String = "" + /// Currently selected thinking level for the session. public private(set) var thinkingLevel: String + /// Selected model identifier or ``defaultModelSelectionID`` when inheriting defaults. public private(set) var modelSelectionID: String = "__default__" + /// Available model choices fetched from the transport. public private(set) var modelChoices: [OpenClawChatModelChoice] = [] + /// Indicates whether the initial bootstrap or a full refresh is in progress. public private(set) var isLoading = false + /// Indicates whether a message send is currently in flight. public private(set) var isSending = false + /// Indicates whether an abort request is currently being processed. public private(set) var isAborting = false + /// Most recent user-facing error text. public var errorText: String? + /// Pending attachments staged for the next send. public var attachments: [OpenClawPendingAttachment] = [] + /// Latest health result reported by the transport. public private(set) var healthOK: Bool = false + /// Number of runs still pending completion or timeout cleanup. public private(set) var pendingRunCount: Int = 0 + /// Active session key. public private(set) var sessionKey: String + /// Server-side session identifier when the transport exposes one. public private(set) var sessionId: String? + /// Streaming assistant text currently being assembled. public private(set) var streamingAssistantText: String? + /// Pending tool-call state derived from transport events. public private(set) var pendingToolCalls: [OpenClawChatPendingToolCall] = [] + /// Recent sessions fetched from the transport. public private(set) var sessions: [OpenClawChatSessionEntry] = [] private let transport: any OpenClawChatTransport private var sessionDefaults: OpenClawChatSessionsDefaults? @@ -41,13 +64,13 @@ public final class OpenClawChatViewModel { private let onThinkingLevelChanged: (@MainActor @Sendable (String) -> Void)? @ObservationIgnored - private nonisolated(unsafe) var eventTask: Task? + nonisolated(unsafe) private var eventTask: Task? private var pendingRuns = Set() { didSet { self.pendingRunCount = self.pendingRuns.count } } @ObservationIgnored - private nonisolated(unsafe) var pendingRunTimeoutTasks: [String: Task] = [:] + nonisolated(unsafe) private var pendingRunTimeoutTasks: [String: Task] = [:] private let pendingRunTimeoutMs: UInt64 = 120_000 // Session switches can overlap in-flight picker patches, so stale completions // must compare against the latest request and latest desired value for that session. @@ -70,6 +93,12 @@ public final class OpenClawChatViewModel { private var lastHealthPollAt: Date? + /// Creates a chat view model backed by the provided transport. + /// - Parameters: + /// - sessionKey: Initial session key to display. + /// - transport: Transport implementation used for history, send, and event APIs. + /// - initialThinkingLevel: Optional initial thinking level override. + /// - onThinkingLevelChanged: Optional callback invoked after the model applies a new thinking level. public init( sessionKey: String, transport: any OpenClawChatTransport, @@ -102,38 +131,47 @@ public final class OpenClawChatViewModel { } } + /// Starts the initial bootstrap flow for the current session. public func load() { Task { await self.bootstrap() } } + /// Reloads history, health, session, and model data from the transport. public func refresh() { Task { await self.bootstrap() } } + /// Sends the current draft and any staged attachments. public func send() { Task { await self.performSend() } } + /// Requests that the current in-flight run be aborted. public func abort() { Task { await self.performAbort() } } + /// Refreshes the recent session list shown by the picker. public func refreshSessions(limit: Int? = nil) { Task { await self.fetchSessions(limit: limit) } } + /// Switches the view model to a different session key. public func switchSession(to sessionKey: String) { Task { await self.performSwitchSession(to: sessionKey) } } + /// Applies a new thinking level to the active session. public func selectThinkingLevel(_ level: String) { Task { await self.performSelectThinkingLevel(level) } } + /// Applies a new model selection to the active session. public func selectModel(_ selectionID: String) { Task { await self.performSelectModel(selectionID) } } + /// Session choices shown by the UI, with the main session pinned to the top. public var sessionChoices: [OpenClawChatSessionEntry] { let now = Date().timeIntervalSince1970 * 1000 let cutoff = now - (24 * 60 * 60 * 1000) @@ -183,10 +221,12 @@ public final class OpenClawChatViewModel { return trimmed == "onboarding" || trimmed.hasSuffix(":onboarding") } + /// Indicates whether the model picker should currently be shown. public var showsModelPicker: Bool { !self.modelChoices.isEmpty } + /// User-facing label for the default model picker row. public var defaultModelLabel: String { guard let defaultModelID = self.normalizedModelSelectionID(self.sessionDefaults?.model) else { return "Default" @@ -194,18 +234,22 @@ public final class OpenClawChatViewModel { return "Default: \(self.modelLabel(for: defaultModelID))" } + /// Adds file-based attachments to the draft. public func addAttachments(urls: [URL]) { Task { await self.loadAttachments(urls: urls) } } + /// Adds an in-memory image attachment to the draft. public func addImageAttachment(data: Data, fileName: String, mimeType: String) { Task { await self.addImageAttachment(url: nil, data: data, fileName: fileName, mimeType: mimeType) } } + /// Removes one staged attachment from the draft. public func removeAttachment(_ id: OpenClawPendingAttachment.ID) { self.attachments.removeAll { $0.id == id } } + /// Indicates whether the current draft is ready to send. public var canSend: Bool { let trimmed = self.input.trimmingCharacters(in: .whitespacesAndNewlines) return !self.isSending && self.pendingRunCount == 0 && (!trimmed.isEmpty || !self.attachments.isEmpty) diff --git a/Sources/OpenClawCore/AuthProfiles.swift b/Sources/OpenClawCore/AuthProfiles.swift index 988f753..340b15f 100644 --- a/Sources/OpenClawCore/AuthProfiles.swift +++ b/Sources/OpenClawCore/AuthProfiles.swift @@ -420,6 +420,7 @@ public actor AuthProfileStore { private let decoder: JSONDecoder private var state: PersistedAuthProfileStoreState + /// Creates a file-backed auth profile store. public init( fileURL: URL, credentialStore: any CredentialStore @@ -773,6 +774,7 @@ public actor AuthProfileStore { /// Resolver that orders auth profiles using config, cooldowns, and round-robin rules. public enum AuthProfileResolver { + /// Resolves the effective auth-profile order for a provider request. public static func resolveProfileOrder( provider: String, preferredProfileID: String? = nil, diff --git a/Sources/OpenClawCore/BinaryUtils.swift b/Sources/OpenClawCore/BinaryUtils.swift index cda9067..10772d9 100644 --- a/Sources/OpenClawCore/BinaryUtils.swift +++ b/Sources/OpenClawCore/BinaryUtils.swift @@ -20,4 +20,3 @@ public enum BinaryUtils { throw OpenClawCoreError.unavailable("Binary not found on PATH: \(name)") } } - diff --git a/Sources/OpenClawCore/ConfigStore.swift b/Sources/OpenClawCore/ConfigStore.swift index 34fe2fd..e3a8003 100644 --- a/Sources/OpenClawCore/ConfigStore.swift +++ b/Sources/OpenClawCore/ConfigStore.swift @@ -56,4 +56,3 @@ public actor ConfigStore { self.cached = nil } } - diff --git a/Sources/OpenClawCore/CronScheduler.swift b/Sources/OpenClawCore/CronScheduler.swift index ffcb41a..d455056 100644 --- a/Sources/OpenClawCore/CronScheduler.swift +++ b/Sources/OpenClawCore/CronScheduler.swift @@ -97,4 +97,3 @@ public actor CronScheduler { return results } } - diff --git a/Sources/OpenClawCore/CryptoCompat.swift b/Sources/OpenClawCore/CryptoCompat.swift index 8541372..52e9916 100644 --- a/Sources/OpenClawCore/CryptoCompat.swift +++ b/Sources/OpenClawCore/CryptoCompat.swift @@ -26,4 +26,3 @@ public enum OpenClawCrypto { return Data(auth) } } - diff --git a/Sources/OpenClawCore/FileSystemCompat.swift b/Sources/OpenClawCore/FileSystemCompat.swift index 378d8b5..5de7241 100644 --- a/Sources/OpenClawCore/FileSystemCompat.swift +++ b/Sources/OpenClawCore/FileSystemCompat.swift @@ -40,4 +40,3 @@ public enum OpenClawFileSystem { FileManager.default.fileExists(atPath: url.path) } } - diff --git a/Sources/OpenClawCore/HookRegistry.swift b/Sources/OpenClawCore/HookRegistry.swift index b03f979..54973a0 100644 --- a/Sources/OpenClawCore/HookRegistry.swift +++ b/Sources/OpenClawCore/HookRegistry.swift @@ -78,4 +78,3 @@ public actor HookRegistry { return results } } - diff --git a/Sources/OpenClawCore/InteractiveAuthSupport.swift b/Sources/OpenClawCore/InteractiveAuthSupport.swift index 4c4b7c6..0436fb0 100644 --- a/Sources/OpenClawCore/InteractiveAuthSupport.swift +++ b/Sources/OpenClawCore/InteractiveAuthSupport.swift @@ -56,6 +56,7 @@ public struct InteractiveAuthFlowDescriptor: Sendable, Equatable { /// Shared catalog of provider login flows exposed to host apps. public enum InteractiveAuthFlowCatalog { + /// Known interactive auth descriptors keyed by provider ID. public static let descriptors: [InteractiveAuthFlowDescriptor] = [ InteractiveAuthFlowDescriptor( providerID: "openai-codex", diff --git a/Sources/OpenClawCore/NetworkingCompat.swift b/Sources/OpenClawCore/NetworkingCompat.swift index b634e7e..f7756ce 100644 --- a/Sources/OpenClawCore/NetworkingCompat.swift +++ b/Sources/OpenClawCore/NetworkingCompat.swift @@ -48,4 +48,3 @@ public actor HTTPClient { return HTTPResponseData(statusCode: http.statusCode, headers: headers, body: data) } } - diff --git a/Sources/OpenClawCore/OpenClawCore.swift b/Sources/OpenClawCore/OpenClawCore.swift index 9e37fc2..131022b 100644 --- a/Sources/OpenClawCore/OpenClawCore.swift +++ b/Sources/OpenClawCore/OpenClawCore.swift @@ -28,4 +28,3 @@ public struct OpenClawBuildInfo: Sendable { self.protocolVersion = protocolVersion } } - diff --git a/Sources/OpenClawCore/PortUtils.swift b/Sources/OpenClawCore/PortUtils.swift index fee240c..ddc936d 100644 --- a/Sources/OpenClawCore/PortUtils.swift +++ b/Sources/OpenClawCore/PortUtils.swift @@ -55,4 +55,3 @@ public enum PortUtils { } } } - diff --git a/Sources/OpenClawCore/SecretsConfig.swift b/Sources/OpenClawCore/SecretsConfig.swift index 9e0e90d..78ea19e 100644 --- a/Sources/OpenClawCore/SecretsConfig.swift +++ b/Sources/OpenClawCore/SecretsConfig.swift @@ -1,6 +1,8 @@ import Foundation +/// Default alias used when a secret reference omits its provider. public let DEFAULT_SECRET_PROVIDER_ALIAS = "default" +/// Sentinel file-secret ID used when a file contains one raw value instead of JSON. public let SINGLE_VALUE_FILE_SECRET_REF_ID = "value" private enum SecretPatterns { @@ -394,7 +396,8 @@ public struct SecretDefaultsConfig: Codable, Sendable, Equatable { file: String? = nil, exec: String? = nil ) { - self.env = env.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? DEFAULT_SECRET_PROVIDER_ALIAS : env.trimmingCharacters(in: .whitespacesAndNewlines) + let normalizedEnv = env.trimmingCharacters(in: .whitespacesAndNewlines) + self.env = normalizedEnv.isEmpty ? DEFAULT_SECRET_PROVIDER_ALIAS : normalizedEnv self.file = file?.trimmingCharacters(in: .whitespacesAndNewlines) self.exec = exec?.trimmingCharacters(in: .whitespacesAndNewlines) } diff --git a/Sources/OpenClawCore/SecurityAuditReport.swift b/Sources/OpenClawCore/SecurityAuditReport.swift index 1b0de36..a016d69 100644 --- a/Sources/OpenClawCore/SecurityAuditReport.swift +++ b/Sources/OpenClawCore/SecurityAuditReport.swift @@ -227,7 +227,8 @@ public enum SecurityAuditRunner { severity: .warning, summary: "Configuration includes plaintext secrets", detail: "Found non-empty secret values in config keys: \(exposedKeys.sorted().joined(separator: ", "))", - recommendation: "Move sensitive values to CredentialStore/Keychain-backed storage or auth-profile storage and avoid committing plaintext values." + recommendation: + "Move sensitive values to CredentialStore/Keychain-backed storage or auth-profile storage and avoid committing plaintext values." ), ] } diff --git a/Sources/OpenClawCore/SecurityCompat.swift b/Sources/OpenClawCore/SecurityCompat.swift index c7e6980..dc62181 100644 --- a/Sources/OpenClawCore/SecurityCompat.swift +++ b/Sources/OpenClawCore/SecurityCompat.swift @@ -34,4 +34,3 @@ public enum OpenClawSecurity { } #endif } - diff --git a/Sources/OpenClawCore/SecurityRuntime.swift b/Sources/OpenClawCore/SecurityRuntime.swift index 4ee3fe3..738f0fa 100644 --- a/Sources/OpenClawCore/SecurityRuntime.swift +++ b/Sources/OpenClawCore/SecurityRuntime.swift @@ -74,4 +74,3 @@ public actor SecurityRuntime { self.execApprovals[command] == true } } - diff --git a/Sources/OpenClawGateway/GatewayServer.swift b/Sources/OpenClawGateway/GatewayServer.swift index 640c785..71addf6 100644 --- a/Sources/OpenClawGateway/GatewayServer.swift +++ b/Sources/OpenClawGateway/GatewayServer.swift @@ -19,9 +19,12 @@ public typealias GatewaySkillInvokeHandler = @Sendable (GatewaySkillInvokeParams /// Long-running agent execution tracked by the gateway server. public struct GatewayAgentExecution: Sendable { + /// Stable run identifier exposed through `agent.wait`. public let runID: String + /// Task that resolves once the agent run finishes. public let task: Task + /// Creates a tracked gateway agent execution. public init(runID: String, task: Task) { self.runID = runID self.task = task @@ -30,12 +33,18 @@ public struct GatewayAgentExecution: Sendable { /// Closure-backed handlers used by `GatewayServer` for higher-level runtime features. public struct GatewayServerHandlers: Sendable { + /// Handler used to start an agent run. public let runAgent: GatewayAgentRunHandler + /// Handler used to list models. public let listModels: GatewayModelsListHandler + /// Handler used to list skills. public let listSkills: GatewaySkillsListHandler + /// Handler used to invoke a skill. public let invokeSkill: GatewaySkillInvokeHandler + /// Optional browser proxy handler. public let browserRequest: GatewayBrowserRequestHandler? + /// Creates a set of closure-backed gateway server handlers. public init( runAgent: @escaping GatewayAgentRunHandler = { _ in throw OpenClawCoreError.unavailable("Agent execution is not configured for this gateway server") @@ -70,6 +79,7 @@ public actor GatewaySecretVault { private let indexURL: URL? private var keys: Set + /// Creates a metadata-aware secret vault backed by a credential store. public init(credentialStore: any CredentialStore, indexURL: URL? = nil) { self.credentialStore = credentialStore self.indexURL = indexURL @@ -82,10 +92,12 @@ public actor GatewaySecretVault { } } + /// Returns the sorted list of secret keys tracked by the vault. public func listSecretKeys() -> [String] { self.keys.sorted() } + /// Stores or replaces a secret value. public func setSecret(_ value: String, for key: String) async throws { let normalizedKey = try Self.normalizedKey(key) try await self.credentialStore.saveSecret(value, for: normalizedKey) @@ -93,6 +105,7 @@ public actor GatewaySecretVault { try self.persistIndexIfNeeded() } + /// Deletes a secret value and returns whether it existed before removal. public func deleteSecret(for key: String) async throws -> Bool { let normalizedKey = try Self.normalizedKey(key) let existed = self.keys.contains(normalizedKey) @@ -132,6 +145,7 @@ public actor GatewayServer { private let handlers: GatewayServerHandlers private var agentRuns: [String: Task] = [:] + /// Creates an in-process gateway server with session, secret, and runtime handlers. public init( sessionStore: SessionStore, secretVault: GatewaySecretVault, diff --git a/Sources/OpenClawKit/AnyCodable+Helpers.swift b/Sources/OpenClawKit/AnyCodable+Helpers.swift index 4d14074..deec5eb 100644 --- a/Sources/OpenClawKit/AnyCodable+Helpers.swift +++ b/Sources/OpenClawKit/AnyCodable+Helpers.swift @@ -1,8 +1,12 @@ import Foundation public extension AnyCodable { + /// Canonical `null` sentinel used by transport and bridge helpers. static let nullValue = AnyCodable(.null) + /// Converts Foundation container values into `AnyCodable`. + /// - Parameter raw: Foundation-backed value to normalize. + /// - Returns: A normalized `AnyCodable` value when conversion succeeds. static func fromFoundation(_ raw: Any) -> AnyCodable? { switch raw { case let value as AnyCodable: @@ -51,6 +55,7 @@ public extension AnyCodable { } } + /// Returns the underlying string when the wrapped value is a string. var stringValue: String? { switch self.value { case .string(let value): @@ -60,6 +65,7 @@ public extension AnyCodable { } } + /// Returns the underlying Boolean when the wrapped value is a Boolean. var boolValue: Bool? { switch self.value { case .bool(let value): @@ -69,6 +75,7 @@ public extension AnyCodable { } } + /// Returns the wrapped integer value, coercing integral doubles when possible. var intValue: Int? { switch self.value { case .int(let value): @@ -80,6 +87,7 @@ public extension AnyCodable { } } + /// Returns the wrapped number as a double. var doubleValue: Double? { switch self.value { case .double(let value): @@ -91,6 +99,7 @@ public extension AnyCodable { } } + /// Returns the wrapped object dictionary when the value is a JSON object. var dictionaryValue: [String: AnyCodable]? { switch self.value { case .object(let value): @@ -100,6 +109,7 @@ public extension AnyCodable { } } + /// Returns the wrapped array when the value is a JSON array. var arrayValue: [AnyCodable]? { switch self.value { case .array(let value): @@ -109,6 +119,7 @@ public extension AnyCodable { } } + /// Converts the wrapped value back to Foundation container types. var foundationValue: Any { switch self.value { case .null: diff --git a/Sources/OpenClawKit/AnyCodable.swift b/Sources/OpenClawKit/AnyCodable.swift index 02b53e3..8162064 100644 --- a/Sources/OpenClawKit/AnyCodable.swift +++ b/Sources/OpenClawKit/AnyCodable.swift @@ -1,4 +1,4 @@ import OpenClawProtocol +/// Convenience typealias that exposes protocol `AnyCodable` through the umbrella SDK module. public typealias AnyCodable = OpenClawProtocol.AnyCodable - diff --git a/Sources/OpenClawKit/AsyncTimeout.swift b/Sources/OpenClawKit/AsyncTimeout.swift index eed2d75..72bdde3 100644 --- a/Sources/OpenClawKit/AsyncTimeout.swift +++ b/Sources/OpenClawKit/AsyncTimeout.swift @@ -1,6 +1,8 @@ import Foundation +/// Async timeout helpers shared by gateway, media, and command flows. public enum AsyncTimeout { + /// Runs an async operation with a timeout expressed in seconds. public static func withTimeout( seconds: Double, onTimeout: @escaping @Sendable () -> Error, @@ -24,6 +26,7 @@ public enum AsyncTimeout { } } + /// Runs an async operation with a timeout expressed in milliseconds. public static func withTimeoutMs( timeoutMs: Int, onTimeout: @escaping @Sendable () -> Error, diff --git a/Sources/OpenClawKit/AudioStreamingProtocols.swift b/Sources/OpenClawKit/AudioStreamingProtocols.swift index a1366e7..23f2e42 100644 --- a/Sources/OpenClawKit/AudioStreamingProtocols.swift +++ b/Sources/OpenClawKit/AudioStreamingProtocols.swift @@ -3,28 +3,40 @@ import Foundation #if canImport(ElevenLabsKit) import ElevenLabsKit +/// Result returned after streaming playback completes. public typealias StreamingPlaybackResult = ElevenLabsKit.StreamingPlaybackResult +/// Player surface used for encoded audio streams. public typealias StreamingAudioPlayer = ElevenLabsKit.StreamingAudioPlayer +/// Player surface used for raw PCM audio streams. public typealias PCMStreamingAudioPlayer = ElevenLabsKit.PCMStreamingAudioPlayer #else +/// Result returned after a streaming playback attempt. public struct StreamingPlaybackResult: Sendable, Equatable { + /// Total duration that was played when the player reports it. public var durationSeconds: Double? + /// Creates a playback result with an optional duration. public init(durationSeconds: Double? = nil) { self.durationSeconds = durationSeconds } } #endif +/// Playback contract for encoded audio streams. @MainActor public protocol StreamingAudioPlaying { + /// Starts playback for a stream of encoded audio chunks. func play(stream: AsyncThrowingStream) async -> StreamingPlaybackResult + /// Stops playback and returns the elapsed duration when available. func stop() -> Double? } +/// Playback contract for PCM audio streams. @MainActor public protocol PCMStreamingAudioPlaying { + /// Starts playback for a PCM stream at the provided sample rate. func play(stream: AsyncThrowingStream, sampleRate: Double) async -> StreamingPlaybackResult + /// Stops playback and returns the elapsed duration when available. func stop() -> Double? } diff --git a/Sources/OpenClawKit/BonjourEscapes.swift b/Sources/OpenClawKit/BonjourEscapes.swift index 0760314..d9d14c8 100644 --- a/Sources/OpenClawKit/BonjourEscapes.swift +++ b/Sources/OpenClawKit/BonjourEscapes.swift @@ -1,5 +1,6 @@ import Foundation +/// Helpers for decoding Bonjour-escaped service names. public enum BonjourEscapes { /// mDNS / DNS-SD commonly escapes bytes in instance names as `\DDD` (decimal-encoded), /// e.g. spaces are `\032`. diff --git a/Sources/OpenClawKit/BonjourServiceResolverSupport.swift b/Sources/OpenClawKit/BonjourServiceResolverSupport.swift index 604b21a..20c61cd 100644 --- a/Sources/OpenClawKit/BonjourServiceResolverSupport.swift +++ b/Sources/OpenClawKit/BonjourServiceResolverSupport.swift @@ -1,11 +1,14 @@ import Foundation +/// Bonjour service resolution helpers shared by gateway discovery flows. public enum BonjourServiceResolverSupport { + /// Starts resolution for a service on the main run loop. public static func start(_ service: NetService, timeout: TimeInterval = 2.0) { service.schedule(in: .main, forMode: .common) service.resolve(withTimeout: timeout) } + /// Normalizes a resolved Bonjour hostname by trimming whitespace and a trailing dot. public static func normalizeHost(_ raw: String?) -> String? { let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" guard !trimmed.isEmpty else { return nil } diff --git a/Sources/OpenClawKit/BonjourTypes.swift b/Sources/OpenClawKit/BonjourTypes.swift index 5c3c50c..ac53a06 100644 --- a/Sources/OpenClawKit/BonjourTypes.swift +++ b/Sources/OpenClawKit/BonjourTypes.swift @@ -1,14 +1,19 @@ import Foundation +/// Shared Bonjour constants used when discovering OpenClaw gateways on the local network. public enum OpenClawBonjour { // v0: internal-only, subject to rename. + /// Bonjour service type advertised by OpenClaw gateways. public static let gatewayServiceType = "_openclaw-gw._tcp" + /// Default Bonjour service domain used for local discovery. public static let gatewayServiceDomain = "local." + /// Optional wide-area Bonjour domain sourced from the environment. public static var wideAreaGatewayServiceDomain: String? { let env = ProcessInfo.processInfo.environment return resolveWideAreaDomain(env["OPENCLAW_WIDE_AREA_DOMAIN"]) } + /// Ordered list of service domains that should be queried for gateway discovery. public static var gatewayServiceDomains: [String] { var domains = [gatewayServiceDomain] if let wideArea = wideAreaGatewayServiceDomain { @@ -24,6 +29,7 @@ public enum OpenClawBonjour { return normalized == gatewayServiceDomain ? nil : normalized } + /// Normalizes a raw Bonjour service domain into the dotted lowercased form expected by discovery APIs. public static func normalizeServiceDomain(_ raw: String?) -> String { let trimmed = (raw ?? "").trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty { diff --git a/Sources/OpenClawKit/CalendarCommands.swift b/Sources/OpenClawKit/CalendarCommands.swift index c2b4202..ad2dc3d 100644 --- a/Sources/OpenClawKit/CalendarCommands.swift +++ b/Sources/OpenClawKit/CalendarCommands.swift @@ -1,10 +1,12 @@ import Foundation +/// Calendar command identifiers supported by shared Apple integrations. public enum OpenClawCalendarCommand: String, Codable, Sendable { case events = "calendar.events" case add = "calendar.add" } +/// Calendar event query parameters shared with the generic date-range model. public typealias OpenClawCalendarEventsParams = OpenClawDateRangeLimitParams public struct OpenClawCalendarAddParams: Codable, Sendable, Equatable { diff --git a/Sources/OpenClawKit/CameraAuthorization.swift b/Sources/OpenClawKit/CameraAuthorization.swift index c7c1182..79672a7 100644 --- a/Sources/OpenClawKit/CameraAuthorization.swift +++ b/Sources/OpenClawKit/CameraAuthorization.swift @@ -1,6 +1,8 @@ import AVFoundation +/// Shared camera and microphone authorization helper. public enum CameraAuthorization { + /// Returns whether the app is authorized for the requested media type, requesting access when needed. public static func isAuthorized(for mediaType: AVMediaType) async -> Bool { let status = AVCaptureDevice.authorizationStatus(for: mediaType) switch status { diff --git a/Sources/OpenClawKit/CameraCapturePipelineSupport.swift b/Sources/OpenClawKit/CameraCapturePipelineSupport.swift index 075761a..26695bc 100644 --- a/Sources/OpenClawKit/CameraCapturePipelineSupport.swift +++ b/Sources/OpenClawKit/CameraCapturePipelineSupport.swift @@ -1,7 +1,9 @@ import AVFoundation import Foundation +/// Shared camera session assembly helpers used by photo and movie capture commands. public enum CameraCapturePipelineSupport { + /// Prepares a photo capture session and validates camera setup errors. public static func preparePhotoSession( preferFrontCamera: Bool, deviceId: String?, @@ -26,6 +28,7 @@ public enum CameraCapturePipelineSupport { } } + /// Prepares a movie capture session and validates camera or microphone setup errors. public static func prepareMovieSession( preferFrontCamera: Bool, deviceId: String?, @@ -55,6 +58,7 @@ public enum CameraCapturePipelineSupport { } } + /// Prepares and starts a movie session, then waits briefly for the camera pipeline to warm up. public static func prepareWarmMovieSession( preferFrontCamera: Bool, deviceId: String?, @@ -78,6 +82,7 @@ public enum CameraCapturePipelineSupport { return prepared } + /// Runs an async operation against a warmed movie output and stops the session afterward. public static func withWarmMovieSession( preferFrontCamera: Bool, deviceId: String?, @@ -100,6 +105,7 @@ public enum CameraCapturePipelineSupport { return try await operation(prepared.output) } + /// Maps low-level movie setup errors onto higher-level command errors. public static func mapMovieSetupError( _ setupError: CameraSessionConfigurationError, microphoneUnavailableError: @autoclosure () -> E, @@ -111,6 +117,7 @@ public enum CameraCapturePipelineSupport { return captureFailed(setupError.localizedDescription) } + /// Builds photo settings that prefer JPEG output when the device supports it. public static func makePhotoSettings(output: AVCapturePhotoOutput) -> AVCapturePhotoSettings { let settings: AVCapturePhotoSettings = { if output.availablePhotoCodecTypes.contains(.jpeg) { @@ -122,6 +129,7 @@ public enum CameraCapturePipelineSupport { return settings } + /// Captures one photo and resolves with the resulting JPEG or HEIC bytes. public static func capturePhotoData( output: AVCapturePhotoOutput, makeDelegate: (CheckedContinuation) -> any AVCapturePhotoCaptureDelegate) async throws -> Data @@ -136,11 +144,13 @@ public enum CameraCapturePipelineSupport { return rawData } + /// Waits briefly after `startRunning()` to reduce blank first-frame captures on some devices. public static func warmUpCaptureSession() async { // A short delay after `startRunning()` significantly reduces "blank first frame" captures on some devices. try? await Task.sleep(nanoseconds: 150_000_000) // 150ms } + /// Returns a human-readable label for a camera position. public static func positionLabel(_ position: AVCaptureDevice.Position) -> String { switch position { case .front: "front" diff --git a/Sources/OpenClawKit/CameraSessionConfiguration.swift b/Sources/OpenClawKit/CameraSessionConfiguration.swift index 748315e..d64ab51 100644 --- a/Sources/OpenClawKit/CameraSessionConfiguration.swift +++ b/Sources/OpenClawKit/CameraSessionConfiguration.swift @@ -1,6 +1,7 @@ import AVFoundation import CoreMedia +/// Errors thrown while assembling AVFoundation capture sessions. public enum CameraSessionConfigurationError: LocalizedError { case addCameraInputFailed case addPhotoOutputFailed @@ -8,6 +9,7 @@ public enum CameraSessionConfigurationError: LocalizedError { case addMicrophoneInputFailed case addMovieOutputFailed + /// User-facing description of the camera setup failure. public var errorDescription: String? { switch self { case .addCameraInputFailed: @@ -24,7 +26,9 @@ public enum CameraSessionConfigurationError: LocalizedError { } } +/// Low-level AVFoundation helpers that wire camera, microphone, and output objects into a capture session. public enum CameraSessionConfiguration { + /// Adds the requested camera input to a session. public static func addCameraInput(session: AVCaptureSession, camera: AVCaptureDevice) throws { let input = try AVCaptureDeviceInput(device: camera) guard session.canAddInput(input) else { @@ -33,6 +37,7 @@ public enum CameraSessionConfiguration { session.addInput(input) } + /// Adds a photo output configured for high-quality captures. public static func addPhotoOutput(session: AVCaptureSession) throws -> AVCapturePhotoOutput { let output = AVCapturePhotoOutput() guard session.canAddOutput(output) else { @@ -43,6 +48,7 @@ public enum CameraSessionConfiguration { return output } + /// Adds a movie output and optional microphone input to a capture session. public static func addMovieOutput( session: AVCaptureSession, includeAudio: Bool, diff --git a/Sources/OpenClawKit/CaptureRateLimits.swift b/Sources/OpenClawKit/CaptureRateLimits.swift index 5b95bf6..51715f9 100644 --- a/Sources/OpenClawKit/CaptureRateLimits.swift +++ b/Sources/OpenClawKit/CaptureRateLimits.swift @@ -1,6 +1,8 @@ import Foundation +/// Clamp helpers for capture duration and frame-rate values. public enum CaptureRateLimits { + /// Clamps a capture duration into the allowed range. public static func clampDurationMs( _ ms: Int?, defaultMs: Int = 10_000, @@ -11,6 +13,7 @@ public enum CaptureRateLimits { return min(maxMs, max(minMs, value)) } + /// Clamps an FPS value into the allowed range. public static func clampFps( _ fps: Double?, defaultFps: Double = 10, diff --git a/Sources/OpenClawKit/DeepLinks.swift b/Sources/OpenClawKit/DeepLinks.swift index 5f1440c..91a9961 100644 --- a/Sources/OpenClawKit/DeepLinks.swift +++ b/Sources/OpenClawKit/DeepLinks.swift @@ -98,7 +98,9 @@ public struct AgentDeepLink: Codable, Sendable, Equatable { } } +/// Parser for the `openclaw://` deep-link surface. public enum DeepLinkParser { + /// Parses a deep-link URL into the normalized route model. public static func parse(_ url: URL) -> DeepLinkRoute? { guard let scheme = url.scheme?.lowercased(), scheme == "openclaw" diff --git a/Sources/OpenClawKit/DeviceAuthPayload.swift b/Sources/OpenClawKit/DeviceAuthPayload.swift index 9b8e4c2..4ef620e 100644 --- a/Sources/OpenClawKit/DeviceAuthPayload.swift +++ b/Sources/OpenClawKit/DeviceAuthPayload.swift @@ -1,7 +1,9 @@ import Foundation import OpenClawProtocol +/// Helpers for constructing device-auth payloads used in gateway connect flows. public enum GatewayDeviceAuthPayload { + /// Builds the canonical v3 device-auth payload string for signing. public static func buildV3( deviceId: String, clientId: String, @@ -54,6 +56,7 @@ public enum GatewayDeviceAuthPayload { return output } + /// Builds the `device` dictionary sent during gateway connect once the payload is signed. public static func signedDeviceDictionary( payload: String, identity: DeviceIdentity, diff --git a/Sources/OpenClawKit/DeviceAuthStore.swift b/Sources/OpenClawKit/DeviceAuthStore.swift index 80ff20c..5fad01f 100644 --- a/Sources/OpenClawKit/DeviceAuthStore.swift +++ b/Sources/OpenClawKit/DeviceAuthStore.swift @@ -1,11 +1,17 @@ import Foundation +/// Persisted device-auth token entry keyed by role. public struct DeviceAuthEntry: Codable, Sendable { + /// Device-scoped auth token. public let token: String + /// Role associated with the token. public let role: String + /// Granted scopes stored with the token. public let scopes: [String] + /// Last update timestamp in milliseconds since the Unix epoch. public let updatedAtMs: Int + /// Creates a device-auth token entry. public init(token: String, role: String, scopes: [String], updatedAtMs: Int) { self.token = token self.role = role @@ -20,15 +26,18 @@ private struct DeviceAuthStoreFile: Codable { var tokens: [String: DeviceAuthEntry] } +/// File-backed storage for device-auth tokens used by gateway/device flows. public enum DeviceAuthStore { private static let fileName = "device-auth.json" + /// Loads the stored token for a device and role. public static func loadToken(deviceId: String, role: String) -> DeviceAuthEntry? { guard let store = readStore(), store.deviceId == deviceId else { return nil } let role = normalizeRole(role) return store.tokens[role] } + /// Stores or replaces the token for a device-role pair. public static func storeToken( deviceId: String, role: String, @@ -56,6 +65,7 @@ public enum DeviceAuthStore { return entry } + /// Deletes the stored token for a device-role pair. public static func clearToken(deviceId: String, role: String) { guard var store = readStore(), store.deviceId == deviceId else { return } let normalizedRole = normalizeRole(role) diff --git a/Sources/OpenClawKit/DeviceIdentity.swift b/Sources/OpenClawKit/DeviceIdentity.swift index a992bc5..629ab22 100644 --- a/Sources/OpenClawKit/DeviceIdentity.swift +++ b/Sources/OpenClawKit/DeviceIdentity.swift @@ -1,12 +1,18 @@ import CryptoKit import Foundation +/// Device identity payload used for gateway signing and device-auth handshakes. public struct DeviceIdentity: Codable, Sendable { + /// Stable device identifier derived from the public key. public var deviceId: String + /// Base64-encoded public key. public var publicKey: String + /// Base64-encoded private key. public var privateKey: String + /// Creation timestamp in milliseconds since the Unix epoch. public var createdAtMs: Int + /// Creates a device identity payload. public init(deviceId: String, publicKey: String, privateKey: String, createdAtMs: Int) { self.deviceId = deviceId self.publicKey = publicKey @@ -36,9 +42,11 @@ enum DeviceIdentityPaths { } } +/// File-backed helpers for loading, creating, and signing with a device identity. public enum DeviceIdentityStore { private static let fileName = "device.json" + /// Loads the existing device identity or generates and persists a new one. public static func loadOrCreate() -> DeviceIdentity { let url = self.fileURL() if let data = try? Data(contentsOf: url), @@ -53,6 +61,7 @@ public enum DeviceIdentityStore { return identity } + /// Signs a payload string with the device private key and returns a base64url signature. public static func signPayload(_ payload: String, identity: DeviceIdentity) -> String? { guard let privateKeyData = Data(base64Encoded: identity.privateKey) else { return nil } do { @@ -85,6 +94,7 @@ public enum DeviceIdentityStore { .replacingOccurrences(of: "=", with: "") } + /// Returns the device public key encoded as base64url. public static func publicKeyBase64Url(_ identity: DeviceIdentity) -> String? { guard let data = Data(base64Encoded: identity.publicKey) else { return nil } return self.base64UrlEncode(data) diff --git a/Sources/OpenClawKit/GatewayChannel.swift b/Sources/OpenClawKit/GatewayChannel.swift index 13978e8..13e8f31 100644 --- a/Sources/OpenClawKit/GatewayChannel.swift +++ b/Sources/OpenClawKit/GatewayChannel.swift @@ -162,6 +162,7 @@ private enum GatewayConnectErrorCodes { static let deviceIdentityRequired = GatewayConnectAuthDetailCode.deviceIdentityRequired.rawValue } +/// Actor-isolated WebSocket gateway channel with reconnect, auth, and request tracking behavior. public actor GatewayChannelActor { private let logger = Logger(subsystem: "ai.openclaw", category: "gateway") private var task: WebSocketTaskBox? @@ -200,6 +201,7 @@ public actor GatewayChannelActor { private let connectOptions: GatewayConnectOptions? private let disconnectHandler: (@Sendable (String) async -> Void)? + /// Creates a gateway channel actor for one endpoint. public init( url: URL, token: String?, @@ -223,8 +225,10 @@ public actor GatewayChannelActor { } } + /// Returns the auth source used for the most recent connect attempt. public func authSource() -> GatewayAuthSource { self.lastAuthSource } + /// Shuts down the socket, cancels reconnect work, and fails any pending requests. public func shutdown() async { self.shouldReconnect = false self.connected = false @@ -287,6 +291,7 @@ public actor GatewayChannelActor { } } + /// Connects to the gateway if needed and performs the full connect handshake. public func connect() async throws { if self.connected, self.task?.state == .running { return } if self.isConnecting { @@ -692,7 +697,7 @@ public actor GatewayChannelActor { } } - private nonisolated func decodeMessageData(_ msg: URLSessionWebSocketTask.Message) -> Data? { + nonisolated private func decodeMessageData(_ msg: URLSessionWebSocketTask.Message) -> Data? { let data: Data? = switch msg { case let .data(data): data case let .string(text): text.data(using: .utf8) @@ -809,7 +814,7 @@ public actor GatewayChannelActor { return false } - private nonisolated func sleepUnlessCancelled(nanoseconds: UInt64) async -> Bool { + nonisolated private func sleepUnlessCancelled(nanoseconds: UInt64) async -> Bool { do { try await Task.sleep(nanoseconds: nanoseconds) } catch { @@ -818,6 +823,7 @@ public actor GatewayChannelActor { return !Task.isCancelled } + /// Sends a request frame and waits for the matching response payload. public func request( method: String, params: [String: AnyCodable]?, @@ -868,6 +874,7 @@ public actor GatewayChannelActor { return Data() // Should not happen, but tolerate empty payloads. } + /// Sends a fire-and-forget command frame over the gateway socket. public func send(method: String, params: [String: AnyCodable]?) async throws { try await self.connectOrThrow(context: "gateway connect") let payload = try self.encodeRequest(method: method, params: params, kind: "send") diff --git a/Sources/OpenClawKit/GatewayConnectChallengeSupport.swift b/Sources/OpenClawKit/GatewayConnectChallengeSupport.swift index 83dd37d..58770df 100644 --- a/Sources/OpenClawKit/GatewayConnectChallengeSupport.swift +++ b/Sources/OpenClawKit/GatewayConnectChallengeSupport.swift @@ -1,7 +1,9 @@ import Foundation import OpenClawProtocol +/// Helpers for reading and waiting on gateway connect challenges. public enum GatewayConnectChallengeSupport { + /// Reads the nonce value from a `connect.challenge` payload. public static func nonce(from payload: [String: OpenClawProtocol.AnyCodable]?) -> String? { guard let nonce = payload?["nonce"]?.stringValue else { return nil } let trimmed = nonce.trimmingCharacters(in: .whitespacesAndNewlines) @@ -9,6 +11,7 @@ public enum GatewayConnectChallengeSupport { return trimmed } + /// Waits for a non-empty connect nonce while applying a timeout. public static func waitForNonce( timeoutSeconds: Double, onTimeout: @escaping @Sendable () -> E, diff --git a/Sources/OpenClawKit/GatewayDiscoveryBrowserSupport.swift b/Sources/OpenClawKit/GatewayDiscoveryBrowserSupport.swift index 4f477b9..ea87b84 100644 --- a/Sources/OpenClawKit/GatewayDiscoveryBrowserSupport.swift +++ b/Sources/OpenClawKit/GatewayDiscoveryBrowserSupport.swift @@ -1,7 +1,9 @@ import Foundation import Network +/// Bonjour browser helpers used by gateway discovery UIs. public enum GatewayDiscoveryBrowserSupport { + /// Creates and starts a Bonjour `NWBrowser` for gateway discovery. @MainActor public static func makeBrowser( serviceType: String, diff --git a/Sources/OpenClawKit/GatewayDiscoveryStatusText.swift b/Sources/OpenClawKit/GatewayDiscoveryStatusText.swift index e15baf1..c0f869a 100644 --- a/Sources/OpenClawKit/GatewayDiscoveryStatusText.swift +++ b/Sources/OpenClawKit/GatewayDiscoveryStatusText.swift @@ -1,7 +1,9 @@ import Foundation import Network +/// Human-readable status strings for gateway discovery state. public enum GatewayDiscoveryStatusText { + /// Builds a user-facing status string from the current browser states. public static func make(states: [NWBrowser.State], hasBrowsers: Bool) -> String { if states.isEmpty { return hasBrowsers ? "Setup" : "Idle" @@ -36,4 +38,3 @@ public enum GatewayDiscoveryStatusText { return "Searching…" } } - diff --git a/Sources/OpenClawKit/GatewayEndpointID.swift b/Sources/OpenClawKit/GatewayEndpointID.swift index eb2e94f..c154be1 100644 --- a/Sources/OpenClawKit/GatewayEndpointID.swift +++ b/Sources/OpenClawKit/GatewayEndpointID.swift @@ -1,7 +1,9 @@ import Foundation import Network +/// Stable identity helpers for Bonjour and network endpoints. public enum GatewayEndpointID { + /// Returns a stable identifier string for an endpoint. public static func stableID(_ endpoint: NWEndpoint) -> String { switch endpoint { case let .service(name, type, domain, _): @@ -13,6 +15,7 @@ public enum GatewayEndpointID { } } + /// Returns a prettier human-readable endpoint description. public static func prettyDescription(_ endpoint: NWEndpoint) -> String { BonjourEscapes.decode(String(describing: endpoint)) } diff --git a/Sources/OpenClawKit/GatewayNodeSession.swift b/Sources/OpenClawKit/GatewayNodeSession.swift index 65ee9a4..05608b7 100644 --- a/Sources/OpenClawKit/GatewayNodeSession.swift +++ b/Sources/OpenClawKit/GatewayNodeSession.swift @@ -55,7 +55,7 @@ func canonicalizeCanvasHostUrl(raw: String?, activeURL: URL?) -> String? { return parsed.string ?? trimmed } - +/// High-level node-to-gateway session wrapper used by shared app surfaces. public actor GatewayNodeSession { private let logger = Logger(subsystem: "ai.openclaw", category: "node.gateway") private let decoder = JSONDecoder() @@ -154,6 +154,7 @@ public actor GatewayNodeSession { private var serverEventSubscribers: [UUID: AsyncStream.Continuation] = [:] private var canvasHostUrl: String? + /// Creates an empty node session that can be connected later. public init() {} private func connectOptionsKey(_ options: GatewayConnectOptions) -> String { @@ -192,6 +193,7 @@ public actor GatewayNodeSession { ].joined(separator: "|") } + /// Connects the session to a gateway endpoint and installs invoke/event handlers. public func connect( url: URL, token: String?, @@ -257,6 +259,7 @@ public actor GatewayNodeSession { } } + /// Disconnects the underlying gateway channel and clears connection state. public func disconnect() async { await self.channel?.shutdown() self.channel = nil @@ -269,10 +272,12 @@ public actor GatewayNodeSession { self.resetConnectionState() } + /// Returns the currently scoped canvas host URL when one has been advertised by the gateway. public func currentCanvasHostUrl() -> String? { self.canvasHostUrl } + /// Refreshes the scoped canvas capability token exposed by the node gateway. public func refreshNodeCanvasCapability(timeoutMs: Int = 8_000) async -> Bool { guard let channel = self.channel else { return false } do { @@ -313,6 +318,7 @@ public actor GatewayNodeSession { } } + /// Returns the current remote address in host:port form. public func currentRemoteAddress() -> String? { guard let url = self.activeURL else { return nil } guard let host = url.host else { return url.absoluteString } @@ -323,6 +329,7 @@ public actor GatewayNodeSession { return "\(host):\(port)" } + /// Sends a fire-and-forget node event to the connected gateway. public func sendEvent(event: String, payloadJSON: String?) async { guard let channel = self.channel else { return } let params: [String: AnyCodable] = [ @@ -336,6 +343,7 @@ public actor GatewayNodeSession { } } + /// Performs a request/response gateway RPC using JSON-encoded parameters. public func request(method: String, paramsJSON: String?, timeoutSeconds: Int = 15) async throws -> Data { guard let channel = self.channel else { throw NSError(domain: "Gateway", code: 11, userInfo: [ @@ -350,6 +358,7 @@ public actor GatewayNodeSession { timeoutMs: Double(timeoutSeconds * 1000)) } + /// Subscribes to server-side event frames forwarded from the gateway channel. public func subscribeServerEvents(bufferingNewest: Int = 200) -> AsyncStream { let id = UUID() let session = self diff --git a/Sources/OpenClawKit/GatewayPayloadDecoding.swift b/Sources/OpenClawKit/GatewayPayloadDecoding.swift index 139aa7d..d638c24 100644 --- a/Sources/OpenClawKit/GatewayPayloadDecoding.swift +++ b/Sources/OpenClawKit/GatewayPayloadDecoding.swift @@ -1,7 +1,9 @@ import OpenClawProtocol import Foundation +/// JSON bridge helpers for gateway payload values. public enum GatewayPayloadDecoding { + /// Decodes an `AnyCodable` payload into a concrete decodable type. public static func decode( _ payload: AnyCodable, as _: T.Type = T.self) throws -> T @@ -10,6 +12,7 @@ public enum GatewayPayloadDecoding { return try JSONDecoder().decode(T.self, from: data) } + /// Decodes an optional `AnyCodable` payload when it is present. public static func decodeIfPresent( _ payload: AnyCodable?, as _: T.Type = T.self) throws -> T? diff --git a/Sources/OpenClawKit/GatewayTLSPinning.swift b/Sources/OpenClawKit/GatewayTLSPinning.swift index fb3a89a..eff842a 100644 --- a/Sources/OpenClawKit/GatewayTLSPinning.swift +++ b/Sources/OpenClawKit/GatewayTLSPinning.swift @@ -3,11 +3,16 @@ import Foundation import Security public struct GatewayTLSParams: Sendable { + /// Indicates whether TLS trust failures should reject the connection. public let required: Bool + /// Expected certificate fingerprint when pinning to a known endpoint. public let expectedFingerprint: String? + /// Enables trust-on-first-use behavior when no fingerprint is already stored. public let allowTOFU: Bool + /// Stable storage key used when persisting TOFU fingerprints. public let storeKey: String? + /// Creates one set of TLS pinning parameters. public init(required: Bool, expectedFingerprint: String?, allowTOFU: Bool, storeKey: String?) { self.required = required self.expectedFingerprint = expectedFingerprint @@ -16,6 +21,7 @@ public struct GatewayTLSParams: Sendable { } } +/// Keychain-backed store for persisted gateway TLS fingerprints. public enum GatewayTLSStore { private static let keychainService = "ai.openclaw.tls-pinning" @@ -23,6 +29,7 @@ public enum GatewayTLSStore { private static let legacySuiteName = "ai.openclaw.shared" private static let legacyKeyPrefix = "gateway.tls." + /// Loads a stored fingerprint for a stable endpoint identifier. public static func loadFingerprint(stableID: String) -> String? { self.migrateFromUserDefaultsIfNeeded(stableID: stableID) let raw = GenericPasswordKeychainStore.loadString(service: self.keychainService, account: stableID)? @@ -31,6 +38,7 @@ public enum GatewayTLSStore { return nil } + /// Saves a fingerprint for a stable endpoint identifier. public static func saveFingerprint(_ value: String, stableID: String) { _ = GenericPasswordKeychainStore.saveString(value, service: self.keychainService, account: stableID) } @@ -55,6 +63,7 @@ public enum GatewayTLSStore { } } +/// `URLSession` wrapper that enforces gateway TLS pinning and TOFU behavior. public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLSessionDelegate, @unchecked Sendable { private let params: GatewayTLSParams private lazy var session: URLSession = { @@ -63,6 +72,7 @@ public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLS return URLSession(configuration: config, delegate: self, delegateQueue: nil) }() + /// Creates a TLS-aware WebSocket session using the provided pinning parameters. public init(params: GatewayTLSParams) { self.params = params super.init() diff --git a/Sources/OpenClawKit/GenericPasswordKeychainStore.swift b/Sources/OpenClawKit/GenericPasswordKeychainStore.swift index 01603f7..f629877 100644 --- a/Sources/OpenClawKit/GenericPasswordKeychainStore.swift +++ b/Sources/OpenClawKit/GenericPasswordKeychainStore.swift @@ -1,12 +1,15 @@ import Foundation import Security +/// Minimal generic-password Keychain helpers used by shared Apple-platform integrations. public enum GenericPasswordKeychainStore { + /// Loads a UTF-8 string from the generic-password Keychain class. public static func loadString(service: String, account: String) -> String? { guard let data = self.loadData(service: service, account: account) else { return nil } return String(data: data, encoding: .utf8) } + /// Saves a UTF-8 string to the generic-password Keychain class. @discardableResult public static func saveString( _ value: String, @@ -17,6 +20,7 @@ public enum GenericPasswordKeychainStore { self.saveData(Data(value.utf8), service: service, account: account, accessible: accessible) } + /// Deletes an existing generic-password item if it exists. @discardableResult public static func delete(service: String, account: String) -> Bool { let query = self.baseQuery(service: service, account: account) diff --git a/Sources/OpenClawKit/InstanceIdentity.swift b/Sources/OpenClawKit/InstanceIdentity.swift index d18fa4e..cbc8f80 100644 --- a/Sources/OpenClawKit/InstanceIdentity.swift +++ b/Sources/OpenClawKit/InstanceIdentity.swift @@ -4,6 +4,7 @@ import Foundation import UIKit #endif +/// Shared instance and device metadata used by gateway and device-auth handshakes. public enum InstanceIdentity { private static let suiteName = "ai.openclaw.shared" private static let instanceIdKey = "instanceId" @@ -23,6 +24,7 @@ public enum InstanceIdentity { } #endif + /// Stable per-installation identifier persisted in shared defaults. public static let instanceId: String = { let defaults = Self.defaults if let existing = defaults.string(forKey: instanceIdKey)? @@ -37,6 +39,7 @@ public enum InstanceIdentity { return id }() + /// User-facing device or host name used in client identification. public static let displayName: String = { #if canImport(UIKit) let name = Self.readMainActor { @@ -53,6 +56,7 @@ public enum InstanceIdentity { #endif }() + /// Hardware model identifier when the platform exposes one. public static let modelIdentifier: String? = { #if canImport(UIKit) var systemInfo = utsname() @@ -76,6 +80,7 @@ public enum InstanceIdentity { #endif }() + /// Broad device family label such as `iPhone`, `iPad`, or `Mac`. public static let deviceFamily: String = { #if canImport(UIKit) return Self.readMainActor { @@ -90,6 +95,7 @@ public enum InstanceIdentity { #endif }() + /// Operating system name and version string used during gateway connect. public static let platformString: String = { let v = ProcessInfo.processInfo.operatingSystemVersion #if canImport(UIKit) diff --git a/Sources/OpenClawKit/LocalNetworkURLSupport.swift b/Sources/OpenClawKit/LocalNetworkURLSupport.swift index 86177b4..e2a6803 100644 --- a/Sources/OpenClawKit/LocalNetworkURLSupport.swift +++ b/Sources/OpenClawKit/LocalNetworkURLSupport.swift @@ -1,6 +1,8 @@ import Foundation +/// Helpers for classifying local-network HTTP endpoints. public enum LocalNetworkURLSupport { + /// Returns whether a URL points at a local-network or loopback HTTP(S) host. public static func isLocalNetworkHTTPURL(_ url: URL) -> Bool { guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" else { return false diff --git a/Sources/OpenClawKit/LocationCurrentRequest.swift b/Sources/OpenClawKit/LocationCurrentRequest.swift index 80038d6..91a5b7d 100644 --- a/Sources/OpenClawKit/LocationCurrentRequest.swift +++ b/Sources/OpenClawKit/LocationCurrentRequest.swift @@ -1,12 +1,15 @@ import CoreLocation import Foundation +/// Helpers for resolving one-shot location requests with caching and timeout behavior. public enum LocationCurrentRequest { + /// Closure used to wrap one-shot location requests with timeout behavior. public typealias TimeoutRunner = @Sendable ( _ timeoutMs: Int, _ operation: @escaping @Sendable () async throws -> CLLocation ) async throws -> CLLocation + /// Resolves a location, reusing a fresh cached value when it satisfies the requested age budget. @MainActor public static func resolve( manager: CLLocationManager, @@ -31,6 +34,7 @@ public enum LocationCurrentRequest { } } + /// Maps the public location-accuracy enum onto Core Location constants. public static func accuracyValue(_ accuracy: OpenClawLocationAccuracy) -> CLLocationAccuracy { switch accuracy { case .coarse: diff --git a/Sources/OpenClawKit/LocationServiceSupport.swift b/Sources/OpenClawKit/LocationServiceSupport.swift index 1a818c6..01f7104 100644 --- a/Sources/OpenClawKit/LocationServiceSupport.swift +++ b/Sources/OpenClawKit/LocationServiceSupport.swift @@ -2,25 +2,32 @@ import CoreLocation import Foundation @MainActor +/// Shared Core Location behaviors used by the location command helpers. public protocol LocationServiceCommon: AnyObject, CLLocationManagerDelegate { + /// Backing location manager instance. var locationManager: CLLocationManager { get } + /// Continuation resumed when a one-shot location request finishes. var locationRequestContinuation: CheckedContinuation? { get set } } public extension LocationServiceCommon { + /// Applies the standard OpenClawKit location-manager configuration. func configureLocationManager() { self.locationManager.delegate = self self.locationManager.desiredAccuracy = kCLLocationAccuracyBest } + /// Returns the current authorization status from the manager. func authorizationStatus() -> CLAuthorizationStatus { self.locationManager.authorizationStatus } + /// Returns the current reduced/full accuracy authorization when supported by the platform. func accuracyAuthorization() -> CLAccuracyAuthorization { LocationServiceSupport.accuracyAuthorization(manager: self.locationManager) } + /// Requests a single location update and waits for the delegate callback. func requestLocationOnce() async throws -> CLLocation { try await LocationServiceSupport.requestLocation(manager: self.locationManager) { continuation in self.locationRequestContinuation = continuation @@ -28,7 +35,9 @@ public extension LocationServiceCommon { } } +/// Standalone helpers for requesting one-shot Core Location fixes. public enum LocationServiceSupport { + /// Returns the best available accuracy authorization for a manager. public static func accuracyAuthorization(manager: CLLocationManager) -> CLAccuracyAuthorization { if #available(iOS 14.0, macOS 11.0, *) { return manager.accuracyAuthorization @@ -36,6 +45,7 @@ public enum LocationServiceSupport { return .fullAccuracy } + /// Requests one location fix and resumes the provided continuation setter. @MainActor public static func requestLocation( manager: CLLocationManager, diff --git a/Sources/OpenClawKit/LoopbackHost.swift b/Sources/OpenClawKit/LoopbackHost.swift index b090549..c134dd9 100644 --- a/Sources/OpenClawKit/LoopbackHost.swift +++ b/Sources/OpenClawKit/LoopbackHost.swift @@ -1,11 +1,14 @@ import Foundation import Network +/// Hostname classification helpers used by local-network and gateway trust decisions. public enum LoopbackHost { + /// Returns whether a host string points at loopback. public static func isLoopback(_ rawHost: String) -> Bool { self.isLoopbackHost(rawHost) } + /// Returns whether a host string resolves to loopback, wildcard loopback, or localhost names. public static func isLoopbackHost(_ rawHost: String) -> Bool { var host = rawHost .trimmingCharacters(in: .whitespacesAndNewlines) @@ -40,6 +43,7 @@ public enum LoopbackHost { return false } + /// Returns whether a host should be treated as local-network reachable. public static func isLocalNetworkHost(_ rawHost: String) -> Bool { let host = rawHost.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() guard !host.isEmpty else { return false } diff --git a/Sources/OpenClawKit/MotionCommands.swift b/Sources/OpenClawKit/MotionCommands.swift index 04d0ec4..b2ac914 100644 --- a/Sources/OpenClawKit/MotionCommands.swift +++ b/Sources/OpenClawKit/MotionCommands.swift @@ -1,10 +1,12 @@ import Foundation +/// Motion command identifiers supported by shared Apple integrations. public enum OpenClawMotionCommand: String, Codable, Sendable { case activity = "motion.activity" case pedometer = "motion.pedometer" } +/// Motion activity query parameters shared with the generic date-range model. public typealias OpenClawMotionActivityParams = OpenClawDateRangeLimitParams public struct OpenClawMotionActivityEntry: Codable, Sendable, Equatable { diff --git a/Sources/OpenClawKit/NetworkInterfaceIPv4.swift b/Sources/OpenClawKit/NetworkInterfaceIPv4.swift index 57f2b08..6997f2e 100644 --- a/Sources/OpenClawKit/NetworkInterfaceIPv4.swift +++ b/Sources/OpenClawKit/NetworkInterfaceIPv4.swift @@ -1,12 +1,17 @@ import Darwin import Foundation +/// IPv4 interface enumeration helpers. public enum NetworkInterfaceIPv4 { + /// One IPv4 address discovered on a network interface. public struct AddressEntry: Sendable { + /// Interface name such as `en0`. public let name: String + /// IPv4 address string. public let ip: String } + /// Returns non-loopback IPv4 interface addresses that are currently up. public static func addresses() -> [AddressEntry] { var addrList: UnsafeMutablePointer? guard getifaddrs(&addrList) == 0, let first = addrList else { return [] } diff --git a/Sources/OpenClawKit/NetworkInterfaces.swift b/Sources/OpenClawKit/NetworkInterfaces.swift index ac554e8..f1eb33b 100644 --- a/Sources/OpenClawKit/NetworkInterfaces.swift +++ b/Sources/OpenClawKit/NetworkInterfaces.swift @@ -1,6 +1,8 @@ import Foundation +/// Network interface helpers shared by gateway and discovery surfaces. public enum NetworkInterfaces { + /// Returns the preferred primary IPv4 address, preferring `en0` when available. public static func primaryIPv4Address() -> String? { var fallback: String? var en0: String? diff --git a/Sources/OpenClawKit/OpenClawKit.docc/ChannelsAndSessions.md b/Sources/OpenClawKit/OpenClawKit.docc/ChannelsAndSessions.md new file mode 100644 index 0000000..0850912 --- /dev/null +++ b/Sources/OpenClawKit/OpenClawKit.docc/ChannelsAndSessions.md @@ -0,0 +1,33 @@ +# Channels and Sessions + +`OpenClawKit` separates inbound message delivery from session persistence so the +same runtime can serve web chat, bots, and app-hosted conversations. + +## Session Persistence + +Use ``SessionStore`` when you need durable routing state across launches or +server restarts. The SDK facade exposes helper methods to load and save a +file-backed store. + +## High-Level Reply Routing + +For app-hosted chat or bot flows, start with +``OpenClawSDK/getReplyFromConfig(config:sessionStoreURL:inbound:diagnosticsPipeline:)``. +That path resolves the effective session key, invokes the runtime, and returns +an ``OutboundMessage`` you can hand back to the transport that delivered the +user message. + +## Optional Shared Chat UI + +`OpenClawChatUI` packages a reusable view model and transport contract for +SwiftUI clients that want a hosted chat surface without reimplementing session +polling, model selection, or transport event handling. + +## Related Symbols + +- ``SessionStore`` +- ``SessionRoutingContext`` +- ``InboundMessage`` +- ``OutboundMessage`` +- ``OpenClawChatViewModel`` +- ``OpenClawChatTransport`` diff --git a/Sources/OpenClawKit/OpenClawKit.docc/ConfigurationAndSecrets.md b/Sources/OpenClawKit/OpenClawKit.docc/ConfigurationAndSecrets.md new file mode 100644 index 0000000..cbfb773 --- /dev/null +++ b/Sources/OpenClawKit/OpenClawKit.docc/ConfigurationAndSecrets.md @@ -0,0 +1,49 @@ +# Configuration and Secrets + +`OpenClawKit` keeps configuration in ``OpenClawConfig`` and treats secret +material as a first-class concern through ``SecretInput``, ``SecretRef``, and +``CredentialStore``. + +## Plaintext or Secret References + +Secret-bearing fields accept either a plaintext value or a structured secret +reference. Environment placeholders such as `"${OPENAI_API_KEY}"` decode into a +``SecretRef`` automatically. + +```json +{ + "secrets": { + "providers": { + "default": { + "source": "env", + "allowlist": ["OPENAI_API_KEY"] + } + } + }, + "models": { + "providers": { + "openai": { + "enabled": true, + "auth": "api-key", + "apiKey": "${OPENAI_API_KEY}" + } + } + } +} +``` + +## Persistence + +Use ``OpenClawSDK/loadConfig(from:cacheTTLms:)`` and +``OpenClawSDK/saveConfig(_:to:)`` for file-backed JSON configuration, and back +auth material with a concrete ``CredentialStore`` such as +``KeychainCredentialStore`` or ``FileCredentialStore`` depending on platform +and deployment needs. + +## Related Symbols + +- ``OpenClawConfig`` +- ``SecretInput`` +- ``SecretRef`` +- ``SecretsConfig`` +- ``CredentialStore`` diff --git a/Sources/OpenClawKit/OpenClawKit.docc/GettingStarted.md b/Sources/OpenClawKit/OpenClawKit.docc/GettingStarted.md new file mode 100644 index 0000000..461c990 --- /dev/null +++ b/Sources/OpenClawKit/OpenClawKit.docc/GettingStarted.md @@ -0,0 +1,46 @@ +# Getting Started + +Install `OpenClawKit` with Swift Package Manager and start from ``OpenClawSDK`` +unless you already know you need lower-level modules. + +## Add the Package + +```swift +dependencies: [ + .package(url: "https://github.com/MarcoDotIO/OpenClawKit.git", branch: "main") +] +``` + +Link the umbrella product in your target: + +```swift +.product(name: "OpenClawKit", package: "OpenClawKit") +``` + +## Create a Reply Flow + +```swift +import OpenClawKit + +let sdk = OpenClawSDK.shared +let diagnostics = sdk.makeDiagnosticsPipeline(eventLimit: 500) + +let outbound = try await sdk.getReplyFromConfig( + config: OpenClawConfig(), + sessionStoreURL: URL(fileURLWithPath: "./state/sessions.json"), + inbound: InboundMessage( + channel: .webchat, + peerID: "user-1", + text: "Summarize today's support queue." + ), + diagnosticsPipeline: diagnostics +) + +print(outbound.text) +``` + +## Next Steps + +- Configure providers and secrets in +- Add durable session handling in +- Validate your app with diff --git a/Sources/OpenClawKit/OpenClawKit.docc/ModuleGuide.md b/Sources/OpenClawKit/OpenClawKit.docc/ModuleGuide.md new file mode 100644 index 0000000..23307ed --- /dev/null +++ b/Sources/OpenClawKit/OpenClawKit.docc/ModuleGuide.md @@ -0,0 +1,23 @@ +# Module Guide + +The SDK is organized as layered SwiftPM targets so host apps can stay high +level or drop deeper when they need custom behavior. + +## Modules + +- `OpenClawProtocol` for protocol contracts and generated gateway/session types +- `OpenClawCore` for config, storage, diagnostics, security, and compatibility shims +- `OpenClawGateway` for typed transport and server surfaces +- `OpenClawModels` for providers, routing, auth resolution, and request shaping +- `OpenClawSkills` for skills, executors, and connector permissions +- `OpenClawAgents` for runtime orchestration +- `OpenClawChannels` for adapters and auto-reply flows +- `OpenClawMemory` and `OpenClawMedia` for durable context and attachments +- `OpenClawKit` for the umbrella SDK facade +- `OpenClawChatUI` for optional shared SwiftUI chat integration + +## Choosing a Surface + +Use ``OpenClawSDK`` when you want the shortest path to a working integration. +Drop into lower-level modules when you need custom provider routing, transport +hosting, skill execution, or UI behavior that sits below the facade. diff --git a/Sources/OpenClawKit/OpenClawKit.docc/OpenClawKit.md b/Sources/OpenClawKit/OpenClawKit.docc/OpenClawKit.md new file mode 100644 index 0000000..c428a8b --- /dev/null +++ b/Sources/OpenClawKit/OpenClawKit.docc/OpenClawKit.md @@ -0,0 +1,44 @@ +# ``OpenClawKit`` + +Build agentic Swift apps, services, and channel integrations with a single SDK +that spans configuration, model routing, gateway transport, skills, memory, and +optional shared chat UI. + +## Overview + +`OpenClawKit` packages the full OpenClaw runtime surface into SwiftPM targets +that can be used together or independently. Most host apps start with +``OpenClawSDK`` and then drop to lower-level modules only when they need custom +runtime, transport, or UI behavior. + +The public docs site is organized around the high-level SDK flow first: + +- install the package +- create or load an ``OpenClawConfig`` +- choose how sessions are persisted with ``SessionStore`` +- route messages through ``OpenClawSDK`` or a lower-level runtime surface +- observe runs with ``RuntimeDiagnosticsPipeline`` + +## Topics + +### Essentials + +- +- +- +- +- +- + +### Core Symbols + +- ``OpenClawSDK`` +- ``OpenClawConfig`` +- ``SessionStore`` +- ``CredentialStore`` +- ``RuntimeDiagnosticsPipeline`` + +### Optional UI + +- ``OpenClawChatViewModel`` +- ``OpenClawChatTransport`` diff --git a/Sources/OpenClawKit/OpenClawKit.docc/ProviderRoutingAndFastMode.md b/Sources/OpenClawKit/OpenClawKit.docc/ProviderRoutingAndFastMode.md new file mode 100644 index 0000000..5c2f476 --- /dev/null +++ b/Sources/OpenClawKit/OpenClawKit.docc/ProviderRoutingAndFastMode.md @@ -0,0 +1,29 @@ +# Provider Routing and Fast Mode + +Model execution flows through `OpenClawModels`, with `OpenClawKit` re-exporting +the routing and provider types for app-level use. + +## Router Behavior + +``ModelRouter`` chooses providers from the configured catalog, applies auth +resolution, and can fall back across compatible providers when one endpoint is +unavailable. + +## Fast Mode + +Fast mode can be defined per-model in config and overridden per session. The +current parity behavior maps fast mode onto provider-specific low-latency +settings where those APIs support them. + +## OpenAI and Codex + +Direct OpenAI and Codex-backed OpenAI traffic uses the OpenAIKit-backed client +path. OpenAI-compatible proxy providers continue to use the SDK's custom HTTP +transport so proxy behavior stays explicit and configurable. + +## Related Symbols + +- ``ModelRouter`` +- ``ProviderServiceConfig`` +- ``OpenAIModelProvider`` +- ``OpenClawConfig`` diff --git a/Sources/OpenClawKit/OpenClawKit.docc/TestingAndValidation.md b/Sources/OpenClawKit/OpenClawKit.docc/TestingAndValidation.md new file mode 100644 index 0000000..578ad9e --- /dev/null +++ b/Sources/OpenClawKit/OpenClawKit.docc/TestingAndValidation.md @@ -0,0 +1,31 @@ +# Testing and Validation + +The package ships with unit, end-to-end, Apple-platform, and Linux runtime +validation paths. The same scripts used locally are also consumed by CI. + +## Recommended Local Gate + +```bash +swift build -Xswiftc -warnings-as-errors +Scripts/lint-swift.sh +Scripts/check-networking-concurrency.sh +swift test +Scripts/build-docs-site.sh +./Scripts/build-ios-example.sh +./Scripts/test-ios-example.sh +./Scripts/build-tvos-example.sh +``` + +## Linux in Docker + +```bash +docker run --rm -v "$PWD:/workspace" -w /workspace swift:6.2 Scripts/build-linux-runtime.sh +docker run --rm -v "$PWD:/workspace" -w /workspace swift:6.2 Scripts/check-networking-concurrency.sh +docker run --rm -v "$PWD:/workspace" -w /workspace swift:6.2 Scripts/test-linux-runtime.sh +``` + +## CI and Docs Publishing + +The main CI workflow validates Swift builds, tests, Apple example builds, and +SwiftLint. The documentation workflow builds this Swift-DocC site on pull +requests and deploys it to GitHub Pages after successful pushes to `main`. diff --git a/Sources/OpenClawKit/OpenClawKitResources.swift b/Sources/OpenClawKit/OpenClawKitResources.swift index 5af33d1..3c4a915 100644 --- a/Sources/OpenClawKit/OpenClawKitResources.swift +++ b/Sources/OpenClawKit/OpenClawKitResources.swift @@ -1,5 +1,6 @@ import Foundation +/// Bundle lookup helpers for packaged OpenClawKit resources. public enum OpenClawKitResources { /// Resource bundle for OpenClawKit. /// diff --git a/Sources/OpenClawKit/PhotoCapture.swift b/Sources/OpenClawKit/PhotoCapture.swift index b5f00d3..d38dfec 100644 --- a/Sources/OpenClawKit/PhotoCapture.swift +++ b/Sources/OpenClawKit/PhotoCapture.swift @@ -1,6 +1,8 @@ import Foundation +/// Photo post-processing helpers shared by camera and gateway flows. public enum PhotoCapture { + /// Transcodes raw image data into a gateway-friendly JPEG payload. public static func transcodeJPEGForGateway( rawData: Data, maxWidthPx: Int, @@ -16,4 +18,3 @@ public enum PhotoCapture { maxBytes: maxEncodedBytes) } } - diff --git a/Sources/OpenClawKit/ShareGatewayRelaySettings.swift b/Sources/OpenClawKit/ShareGatewayRelaySettings.swift index 7b4c386..90d053c 100644 --- a/Sources/OpenClawKit/ShareGatewayRelaySettings.swift +++ b/Sources/OpenClawKit/ShareGatewayRelaySettings.swift @@ -1,13 +1,21 @@ import Foundation +/// Persisted gateway relay settings used by the share extension handoff flow. public struct ShareGatewayRelayConfig: Codable, Sendable, Equatable { + /// Gateway URL string used by the relay client. public let gatewayURLString: String + /// Optional gateway token used for bearer-style auth. public let token: String? + /// Optional gateway password used for password-based auth. public let password: String? + /// Session key targeted by the relay flow. public let sessionKey: String + /// Optional delivery channel identifier for the shared event. public let deliveryChannel: String? + /// Optional destination identifier within the delivery channel. public let deliveryTo: String? + /// Creates a relay configuration for share-extension delivery. public init( gatewayURLString: String, token: String?, @@ -25,6 +33,7 @@ public struct ShareGatewayRelayConfig: Codable, Sendable, Equatable { } } +/// Shared defaults-backed storage for the share-extension relay configuration and last event text. public enum ShareGatewayRelaySettings { private static let suiteName = "group.ai.openclaw.shared" private static let relayConfigKey = "share.gatewayRelay.config.v1" @@ -34,26 +43,31 @@ public enum ShareGatewayRelaySettings { UserDefaults(suiteName: self.suiteName) ?? .standard } + /// Loads the currently persisted relay configuration. public static func loadConfig() -> ShareGatewayRelayConfig? { guard let data = self.defaults.data(forKey: self.relayConfigKey) else { return nil } return try? JSONDecoder().decode(ShareGatewayRelayConfig.self, from: data) } + /// Persists the relay configuration used by the share extension. public static func saveConfig(_ config: ShareGatewayRelayConfig) { guard let data = try? JSONEncoder().encode(config) else { return } self.defaults.set(data, forKey: self.relayConfigKey) } + /// Removes any stored relay configuration. public static func clearConfig() { self.defaults.removeObject(forKey: self.relayConfigKey) } + /// Persists a human-readable last relay event line with a timestamp. public static func saveLastEvent(_ message: String) { let timestamp = ISO8601DateFormatter().string(from: Date()) let payload = "[\(timestamp)] \(message)" self.defaults.set(payload, forKey: self.lastEventKey) } + /// Loads the most recently stored relay event message. public static func loadLastEvent() -> String? { let value = self.defaults.string(forKey: self.lastEventKey)? .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" diff --git a/Sources/OpenClawKit/ShareToAgentDeepLink.swift b/Sources/OpenClawKit/ShareToAgentDeepLink.swift index 08f0623..221da2d 100644 --- a/Sources/OpenClawKit/ShareToAgentDeepLink.swift +++ b/Sources/OpenClawKit/ShareToAgentDeepLink.swift @@ -1,10 +1,15 @@ import Foundation +/// Shared content payload used when building a share-to-agent deep link. public struct SharedContentPayload: Sendable, Equatable { + /// Shared title, when available. public let title: String? + /// Shared URL, when available. public let url: URL? + /// Shared text body, when available. public let text: String? + /// Creates a share payload from optional title, URL, and text values. public init(title: String?, url: URL?, text: String?) { self.title = title self.url = url @@ -12,7 +17,9 @@ public struct SharedContentPayload: Sendable, Equatable { } } +/// Helpers for turning shared content into an `openclaw://agent` deep link. public enum ShareToAgentDeepLink { + /// Builds a deep-link URL for the shared content when there is anything to send. 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 } @@ -27,6 +34,7 @@ public enum ShareToAgentDeepLink { return components.url } + /// Builds the text payload inserted into the deep link. public static func buildMessage(from payload: SharedContentPayload, instruction: String? = nil) -> String { let title = self.clean(payload.title) let text = self.clean(payload.text) diff --git a/Sources/OpenClawKit/ShareToAgentSettings.swift b/Sources/OpenClawKit/ShareToAgentSettings.swift index 9034dcf..2382929 100644 --- a/Sources/OpenClawKit/ShareToAgentSettings.swift +++ b/Sources/OpenClawKit/ShareToAgentSettings.swift @@ -1,5 +1,6 @@ import Foundation +/// Shared defaults-backed settings for the share-to-agent flow. public enum ShareToAgentSettings { private static let suiteName = "group.ai.openclaw.shared" private static let defaultInstructionKey = "share.defaultInstruction" @@ -9,6 +10,7 @@ public enum ShareToAgentSettings { UserDefaults(suiteName: suiteName) ?? .standard } + /// Loads the default instruction appended to shared content. public static func loadDefaultInstruction() -> String { let raw = self.defaults.string(forKey: self.defaultInstructionKey)? .trimmingCharacters(in: .whitespacesAndNewlines) @@ -18,6 +20,7 @@ public enum ShareToAgentSettings { return self.fallbackInstruction } + /// Saves or clears the default instruction appended to shared content. public static func saveDefaultInstruction(_ value: String?) { let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if trimmed.isEmpty { diff --git a/Sources/OpenClawKit/StoragePaths.swift b/Sources/OpenClawKit/StoragePaths.swift index d754229..e28b72e 100644 --- a/Sources/OpenClawKit/StoragePaths.swift +++ b/Sources/OpenClawKit/StoragePaths.swift @@ -1,6 +1,8 @@ import Foundation +/// Storage path helpers for OpenClaw node-hosted canvas and cache artifacts. public enum OpenClawNodeStorage { + /// Returns the app support directory used for durable OpenClaw data. public static func appSupportDir() throws -> URL { let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first guard let base else { @@ -11,6 +13,7 @@ public enum OpenClawNodeStorage { return base.appendingPathComponent("OpenClaw", isDirectory: true) } + /// Returns the per-session directory used for persistent canvas state. public static func canvasRoot(sessionKey: String) throws -> URL { let root = try appSupportDir().appendingPathComponent("canvas", isDirectory: true) let safe = sessionKey.trimmingCharacters(in: .whitespacesAndNewlines) @@ -18,6 +21,7 @@ public enum OpenClawNodeStorage { return root.appendingPathComponent(session, isDirectory: true) } + /// Returns the caches directory used for transient OpenClaw artifacts. public static func cachesDir() throws -> URL { let base = FileManager().urls(for: .cachesDirectory, in: .userDomainMask).first guard let base else { @@ -28,6 +32,7 @@ public enum OpenClawNodeStorage { return base.appendingPathComponent("OpenClaw", isDirectory: true) } + /// Returns the per-session cache directory used for captured canvas snapshots. public static func canvasSnapshotsRoot(sessionKey: String) throws -> URL { let root = try cachesDir().appendingPathComponent("canvas-snapshots", isDirectory: true) let safe = sessionKey.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/Sources/OpenClawKit/TalkConfigParsing.swift b/Sources/OpenClawKit/TalkConfigParsing.swift index 9527d02..bf8ec85 100644 --- a/Sources/OpenClawKit/TalkConfigParsing.swift +++ b/Sources/OpenClawKit/TalkConfigParsing.swift @@ -1,10 +1,15 @@ import Foundation +/// Resolved talk-provider config selection extracted from a talk payload. public struct TalkProviderConfigSelection: Sendable { + /// Canonical provider identifier selected for talk mode. public let provider: String + /// Provider-specific talk configuration payload. public let config: [String: AnyCodable] + /// Indicates whether the payload came from the normalized resolved-provider shape. public let normalizedPayload: Bool + /// Creates a talk-provider config selection. public init(provider: String, config: [String: AnyCodable], normalizedPayload: Bool) { self.provider = provider self.config = config @@ -12,13 +17,16 @@ public struct TalkProviderConfigSelection: Sendable { } } +/// Helpers that normalize and read provider-specific talk configuration. public enum TalkConfigParsing { + /// Converts a Foundation dictionary into the SDK's `AnyCodable` representation. public static func bridgeFoundationDictionary(_ raw: [String: Any]?) -> [String: AnyCodable]? { raw?.reduce(into: [String: AnyCodable]()) { acc, entry in acc[entry.key] = AnyCodable.fromFoundation(entry.value) ?? AnyCodable(String(describing: entry.value)) } } + /// Selects the active talk provider config from a normalized or legacy talk payload. public static func selectProviderConfig( _ talk: [String: AnyCodable]?, defaultProvider: String, @@ -39,6 +47,7 @@ public enum TalkConfigParsing { normalizedPayload: false) } + /// Reads a positive integer from `AnyCodable`, falling back when the value is missing or invalid. public static func resolvedPositiveInt(_ value: AnyCodable?, fallback: Int) -> Int { if let timeout = value?.intValue, timeout > 0 { return timeout @@ -54,6 +63,7 @@ public enum TalkConfigParsing { return fallback } + /// Resolves the silence timeout from a talk payload. public static func resolvedSilenceTimeoutMs(_ talk: [String: AnyCodable]?, fallback: Int) -> Int { self.resolvedPositiveInt(talk?["silenceTimeoutMs"], fallback: fallback) } diff --git a/Sources/OpenClawKit/TalkDirective.swift b/Sources/OpenClawKit/TalkDirective.swift index 6c460dc..24cf95f 100644 --- a/Sources/OpenClawKit/TalkDirective.swift +++ b/Sources/OpenClawKit/TalkDirective.swift @@ -61,7 +61,9 @@ public struct TalkDirectiveParseResult: Equatable, Sendable { } } +/// Parser for the leading JSON talk directive block. public enum TalkDirectiveParser { + /// Parses a leading JSON directive block and returns the stripped body text. public static func parse(_ text: String) -> TalkDirectiveParseResult { let normalized = text.replacingOccurrences(of: "\r\n", with: "\n") var lines = normalized.split(separator: "\n", omittingEmptySubsequences: false) diff --git a/Sources/OpenClawKit/TalkSystemSpeechSynthesizer.swift b/Sources/OpenClawKit/TalkSystemSpeechSynthesizer.swift index 16dd9b9..f782b20 100644 --- a/Sources/OpenClawKit/TalkSystemSpeechSynthesizer.swift +++ b/Sources/OpenClawKit/TalkSystemSpeechSynthesizer.swift @@ -109,7 +109,7 @@ public final class TalkSystemSpeechSynthesizer: NSObject { } extension TalkSystemSpeechSynthesizer: AVSpeechSynthesizerDelegate { - public nonisolated func speechSynthesizer( + nonisolated public func speechSynthesizer( _ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) { @@ -122,7 +122,7 @@ extension TalkSystemSpeechSynthesizer: AVSpeechSynthesizerDelegate { } } - public nonisolated func speechSynthesizer( + nonisolated public func speechSynthesizer( _ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) { @@ -132,7 +132,7 @@ extension TalkSystemSpeechSynthesizer: AVSpeechSynthesizerDelegate { } } - public nonisolated func speechSynthesizer( + nonisolated public func speechSynthesizer( _ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) { diff --git a/Sources/OpenClawKit/ThrowingContinuationSupport.swift b/Sources/OpenClawKit/ThrowingContinuationSupport.swift index 42b22c9..390e524 100644 --- a/Sources/OpenClawKit/ThrowingContinuationSupport.swift +++ b/Sources/OpenClawKit/ThrowingContinuationSupport.swift @@ -1,6 +1,8 @@ import Foundation +/// Helpers for resuming throwing continuations with optional errors. public enum ThrowingContinuationSupport { + /// Resumes a `Void` continuation by either returning or throwing. public static func resumeVoid(_ continuation: CheckedContinuation, error: Error?) { if let error { continuation.resume(throwing: error) diff --git a/Sources/OpenClawKit/ToolDisplay.swift b/Sources/OpenClawKit/ToolDisplay.swift index 1521bde..039d51d 100644 --- a/Sources/OpenClawKit/ToolDisplay.swift +++ b/Sources/OpenClawKit/ToolDisplay.swift @@ -23,6 +23,7 @@ public struct ToolDisplaySummary: Sendable, Equatable { } } +/// Registry that maps raw tool invocations into user-facing display summaries. public enum ToolDisplayRegistry { private struct ToolDisplayActionSpec: Decodable { let label: String? @@ -45,6 +46,7 @@ public enum ToolDisplayRegistry { private static let config: ToolDisplayConfig = loadConfig() + /// Resolves a tool invocation into a display-ready summary. public static func resolve(name: String?, args: AnyCodable?, meta: String? = nil) -> ToolDisplaySummary { let trimmedName = name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "tool" let key = trimmedName.lowercased() diff --git a/Sources/OpenClawKit/WebViewJavaScriptSupport.swift b/Sources/OpenClawKit/WebViewJavaScriptSupport.swift index 2a9b37c..8c752c8 100644 --- a/Sources/OpenClawKit/WebViewJavaScriptSupport.swift +++ b/Sources/OpenClawKit/WebViewJavaScriptSupport.swift @@ -1,7 +1,9 @@ import Foundation import WebKit +/// Shared WKWebView JavaScript helpers used by browser and canvas integrations. public enum WebViewJavaScriptSupport { + /// Applies the shared debug-status banner state to a web view. @MainActor public static func applyDebugStatus( webView: WKWebView, @@ -27,6 +29,7 @@ public enum WebViewJavaScriptSupport { webView.evaluateJavaScript(js) { _, _ in } } + /// Evaluates JavaScript and coerces the result to a string. @MainActor public static func evaluateToString(webView: WKWebView, javaScript: String) async throws -> String { try await withCheckedThrowingContinuation { cont in @@ -44,6 +47,7 @@ public enum WebViewJavaScriptSupport { } } + /// Encodes an optional string as a JavaScript literal or `null`. public static func jsValue(_ value: String?) -> String { guard let value else { return "null" } if let data = try? JSONSerialization.data(withJSONObject: [value]), diff --git a/Sources/OpenClawMemory/MemoryIndex.swift b/Sources/OpenClawMemory/MemoryIndex.swift index f0713ad..c263c56 100644 --- a/Sources/OpenClawMemory/MemoryIndex.swift +++ b/Sources/OpenClawMemory/MemoryIndex.swift @@ -132,4 +132,3 @@ private func tokens(_ text: String) -> Set { .filter { !$0.isEmpty } return Set(split) } - diff --git a/Sources/OpenClawModels/LocalModelProvider.swift b/Sources/OpenClawModels/LocalModelProvider.swift index 60e100f..b8640b1 100644 --- a/Sources/OpenClawModels/LocalModelProvider.swift +++ b/Sources/OpenClawModels/LocalModelProvider.swift @@ -46,9 +46,13 @@ public protocol LocalModelEngine: Sendable { } public extension LocalModelEngine { + /// Default no-op runtime switch implementation for engines that expose only one backend. func switchRuntime(from _: String?, to _: String, configuration _: LocalModelConfig) async throws {} + /// Default no-op cancellation implementation. func cancelGeneration(token _: String?) async {} + /// Default no-op state save implementation. func saveState() async throws -> Data? { nil } + /// Default no-op state restore implementation. func restoreState(_: Data) async throws {} } diff --git a/Sources/OpenClawModels/ModelCatalogParity.swift b/Sources/OpenClawModels/ModelCatalogParity.swift index 88eeb64..e4241e8 100644 --- a/Sources/OpenClawModels/ModelCatalogParity.swift +++ b/Sources/OpenClawModels/ModelCatalogParity.swift @@ -2,6 +2,7 @@ import OpenClawCore /// Helpers that keep built-in model catalogs aligned with upstream OpenClaw quirks. public enum OpenClawModelCatalogParity { + /// Spark model ID that is suppressed on direct OpenAI provider surfaces. public static let openAIDirectSparkModelID = "gpt-5.3-codex-spark" private static let suppressedSparkProviderIDs: Set = [ @@ -17,6 +18,7 @@ public enum OpenClawModelCatalogParity { private static let codexSparkContextWindow = 128_000 private static let codexSparkMaxTokens = 128_000 + /// Returns whether a built-in model should be hidden for a provider. public static func shouldSuppressBuiltInModel( providerID: String, modelID: String @@ -27,6 +29,7 @@ public enum OpenClawModelCatalogParity { && normalizedModelID == self.openAIDirectSparkModelID } + /// Returns the user-facing error message for a suppressed built-in model selection. public static func suppressedBuiltInModelError( providerID: String, modelID: String @@ -35,9 +38,14 @@ public enum OpenClawModelCatalogParity { return nil } let normalizedProviderID = Self.normalizedProviderID(providerID) - return "Unknown model: \(normalizedProviderID)/\(self.openAIDirectSparkModelID). \(self.openAIDirectSparkModelID) is only supported via openai-codex OAuth. Use openai-codex/\(self.openAIDirectSparkModelID)." + return """ + Unknown model: \(normalizedProviderID)/\(self.openAIDirectSparkModelID). \ + \(self.openAIDirectSparkModelID) is only supported via openai-codex OAuth. \ + Use openai-codex/\(self.openAIDirectSparkModelID). + """ } + /// Normalizes the built-in model list for provider-specific parity quirks. public static func normalizeBuiltInModels( providerID: String, models: [ModelDefinitionConfig] diff --git a/Sources/OpenClawModels/ModelProvider.swift b/Sources/OpenClawModels/ModelProvider.swift index 541f873..0c7346a 100644 --- a/Sources/OpenClawModels/ModelProvider.swift +++ b/Sources/OpenClawModels/ModelProvider.swift @@ -287,6 +287,7 @@ public extension ModelProvider { } } + /// Default no-op cancellation implementation for providers without token-based cancellation. func cancelGeneration(token _: String?) async {} } diff --git a/Sources/OpenClawModels/ProviderCatalog.swift b/Sources/OpenClawModels/ProviderCatalog.swift index aea63d6..f2c880d 100644 --- a/Sources/OpenClawModels/ProviderCatalog.swift +++ b/Sources/OpenClawModels/ProviderCatalog.swift @@ -3,11 +3,16 @@ import OpenClawCore /// Canonical provider entry derived from the pinned OpenClaw TS reference snapshot. public struct ProviderCatalogEntry: Sendable, Equatable { + /// Stable provider identifier. public var providerID: String + /// User-facing provider display name. public var displayName: String + /// Additional provider aliases accepted by config and routing helpers. public var aliases: [String] + /// Default provider configuration used when synthesizing built-in providers. public var config: ModelProviderConfig + /// Creates one canonical provider catalog entry. public init( providerID: String, displayName: String, @@ -23,8 +28,10 @@ public struct ProviderCatalogEntry: Sendable, Equatable { /// Shared provider catalog aligned with the pinned OpenClaw TS reference. public enum OpenClawReferenceProviderCatalog { + /// Upstream OpenClaw commit used to derive the built-in provider list. public static let referenceCommit = "61cd3a6e446c3d181a0a75861fd85d459c068a3d" + /// Canonical built-in provider entries exposed by the SDK. public static let entries: [ProviderCatalogEntry] = [ entry("openai", "OpenAI", api: .openAIResponses, auth: .apiKey, baseURL: "https://api.openai.com/v1", modelID: "gpt-4.1-mini"), entry("openai-compatible", "OpenAI Compatible", api: .openAICompletions, auth: .apiKey, baseURL: "https://api.openai.com/v1", modelID: "gpt-4.1-mini"), @@ -59,9 +66,33 @@ public enum OpenClawReferenceProviderCatalog { entry("openai-codex", "OpenAI Codex", api: .openAICodexResponses, auth: .oauth, baseURL: "https://chatgpt.com/backend-api", modelID: "gpt-5.4"), entry("opencode", "OpenCode Zen", api: .openAICompletions, auth: .apiKey, baseURL: "https://api.opencode.ai/v1", modelID: "claude-opus-4-6"), entry("opencode-go", "OpenCode Go", api: .openAICompletions, auth: .apiKey, baseURL: "https://api.opencode.ai/v1", modelID: "kimi-k2.5"), - entry("google-vertex", "Google Vertex", api: .googleGenerativeAI, auth: .oauth, baseURL: "https://us-central1-aiplatform.googleapis.com", modelID: "gemini-3-pro-preview", inputs: [.text, .image]), - entry("google-antigravity", "Google Antigravity", api: .googleGenerativeAI, auth: .oauth, baseURL: "https://generativelanguage.googleapis.com/v1beta", modelID: "gemini-3-pro-preview", inputs: [.text, .image]), - entry("google-gemini-cli", "Google Gemini CLI", api: .googleGenerativeAI, auth: .oauth, baseURL: "https://generativelanguage.googleapis.com/v1beta", modelID: "gemini-3-flash-preview", inputs: [.text, .image]), + entry( + "google-vertex", + "Google Vertex", + api: .googleGenerativeAI, + auth: .oauth, + baseURL: "https://us-central1-aiplatform.googleapis.com", + modelID: "gemini-3-pro-preview", + inputs: [.text, .image] + ), + entry( + "google-antigravity", + "Google Antigravity", + api: .googleGenerativeAI, + auth: .oauth, + baseURL: "https://generativelanguage.googleapis.com/v1beta", + modelID: "gemini-3-pro-preview", + inputs: [.text, .image] + ), + entry( + "google-gemini-cli", + "Google Gemini CLI", + api: .googleGenerativeAI, + auth: .oauth, + baseURL: "https://generativelanguage.googleapis.com/v1beta", + modelID: "gemini-3-flash-preview", + inputs: [.text, .image] + ), entry("kilocode", "KiloCode", api: .openAICompletions, auth: .apiKey, baseURL: "https://api.kilo.ai/api/gateway/", modelID: "kilo/auto", inputs: [.text, .image], reasoning: true), entry("kimi-coding", "Kimi Coding", api: .openAICompletions, auth: .apiKey, baseURL: "https://api.kimi.com/coding/", modelID: "k2p5"), entry("venice", "Venice", api: .openAICompletions, auth: .apiKey, baseURL: "https://api.venice.ai/api/v1", modelID: "kimi-k2-5", inputs: [.text, .image], reasoning: true), @@ -72,6 +103,7 @@ public enum OpenClawReferenceProviderCatalog { entry("byteplus-plan", "BytePlus Plan", api: .openAICompletions, auth: .apiKey, baseURL: "https://ark.ap-southeast.bytepluses.com/api/coding/v3", modelID: "ark-code-latest"), ] + /// Normalizes a provider identifier or alias into the canonical built-in provider ID. public static func normalize(providerID: String) -> String { let normalized = providerID.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() if entries.contains(where: { $0.providerID == normalized }) { @@ -86,11 +118,13 @@ public enum OpenClawReferenceProviderCatalog { return normalized } + /// Returns the built-in catalog entry for a provider identifier or alias. public static func entry(for providerID: String) -> ProviderCatalogEntry? { let normalized = normalize(providerID: providerID) return entries.first(where: { $0.providerID == normalized }) } + /// Returns built-in provider configs keyed by their canonical identifier. public static func providerConfigsByID() -> [String: ModelProviderConfig] { entries.reduce(into: [:]) { partial, entry in partial[entry.providerID] = entry.config @@ -132,6 +166,7 @@ public enum OpenClawReferenceProviderCatalog { /// Factory for constructing runtime providers from canonical provider catalog configs. public enum ModelProviderFactory { + /// Instantiates a model provider implementation for the given provider ID and config. public static func makeProvider( providerID: String, config: ModelProviderConfig diff --git a/Sources/OpenClawPlugins/PluginRegistry.swift b/Sources/OpenClawPlugins/PluginRegistry.swift index c0f4ec8..497f2fa 100644 --- a/Sources/OpenClawPlugins/PluginRegistry.swift +++ b/Sources/OpenClawPlugins/PluginRegistry.swift @@ -268,4 +268,3 @@ public struct PluginAPI: Sendable { await self.registerServiceFn(service) } } - diff --git a/Sources/OpenClawProtocol/GatewayModels.swift b/Sources/OpenClawProtocol/GatewayModels.swift index 530bba9..989d6bb 100644 --- a/Sources/OpenClawProtocol/GatewayModels.swift +++ b/Sources/OpenClawProtocol/GatewayModels.swift @@ -2,6 +2,7 @@ // swiftlint:disable file_length import Foundation +/// Current generated gateway protocol version supported by the SDK. public let GATEWAY_PROTOCOL_VERSION = 3 public enum ErrorCode: String, Codable, Sendable { diff --git a/Sources/OpenClawSkills/AppleConnectorAdapters.swift b/Sources/OpenClawSkills/AppleConnectorAdapters.swift index 64724ce..b269054 100644 --- a/Sources/OpenClawSkills/AppleConnectorAdapters.swift +++ b/Sources/OpenClawSkills/AppleConnectorAdapters.swift @@ -386,6 +386,7 @@ public struct LocalStorageConnectorAdapter: PersonalDataConnectorAdapter { /// Convenience registry for Apple's personal-data connector adapters. public enum AppleConnectorAdapters { + /// Returns the default set of connector adapters for Apple platform integrations. public static func defaults() -> [any PersonalDataConnectorAdapter] { [ EventKitConnectorAdapter(), diff --git a/Sources/OpenClawSkills/WASMSkillExecutor.swift b/Sources/OpenClawSkills/WASMSkillExecutor.swift index f99533b..c802552 100644 --- a/Sources/OpenClawSkills/WASMSkillExecutor.swift +++ b/Sources/OpenClawSkills/WASMSkillExecutor.swift @@ -120,6 +120,7 @@ public actor WASMSkillExecutor { return nil } + /// Default exec allowlist for process-backed WASM runtimes. public static func defaultExecAllowlist() -> ExecCommandAllowlist { ExecCommandAllowlist( patterns: [ diff --git a/Tests/OpenClawKitE2ETests/AgentRuntimeE2ETests.swift b/Tests/OpenClawKitE2ETests/AgentRuntimeE2ETests.swift index 9ed5f46..520fbd3 100644 --- a/Tests/OpenClawKitE2ETests/AgentRuntimeE2ETests.swift +++ b/Tests/OpenClawKitE2ETests/AgentRuntimeE2ETests.swift @@ -81,4 +81,3 @@ struct AgentRuntimeE2ETests { #expect(replayed.map(\.event.name) == byRun.map(\.event.name)) } } - diff --git a/Tests/OpenClawKitE2ETests/OpenClawKitE2ETests.swift b/Tests/OpenClawKitE2ETests/OpenClawKitE2ETests.swift index 128f215..77d3923 100644 --- a/Tests/OpenClawKitE2ETests/OpenClawKitE2ETests.swift +++ b/Tests/OpenClawKitE2ETests/OpenClawKitE2ETests.swift @@ -82,4 +82,3 @@ struct OpenClawKitE2ETests { #expect(response.providerID == "beta") } } - diff --git a/Tests/OpenClawKitTests/PluginSystemTests.swift b/Tests/OpenClawKitTests/PluginSystemTests.swift index e1edb60..bd37357 100644 --- a/Tests/OpenClawKitTests/PluginSystemTests.swift +++ b/Tests/OpenClawKitTests/PluginSystemTests.swift @@ -62,4 +62,3 @@ struct PluginSystemTests { #expect(await service.stopped == true) } } - diff --git a/Tests/OpenClawKitTests/ProtocolModelsTests.swift b/Tests/OpenClawKitTests/ProtocolModelsTests.swift index 997600b..8681dec 100644 --- a/Tests/OpenClawKitTests/ProtocolModelsTests.swift +++ b/Tests/OpenClawKitTests/ProtocolModelsTests.swift @@ -224,7 +224,15 @@ struct ProtocolModelsTests { let payloads: [Any] = [ accepted, wait, - GatewayAgentRequest(sessionKey: "main", prompt: "hello", message: "world", modelProviderID: "openai", modelID: "gpt-5.4", timeoutMs: 123, deliver: true), + GatewayAgentRequest( + sessionKey: "main", + prompt: "hello", + message: "world", + modelProviderID: "openai", + modelID: "gpt-5.4", + timeoutMs: 123, + deliver: true + ), GatewayAgentWaitParams(runID: "run-1", timeoutMs: 100), GatewaySessionListResult(sessions: [session]), GatewaySessionGetParams(key: "main"), diff --git a/Tests/OpenClawKitTests/ProviderCatalogModelFilteringTests.swift b/Tests/OpenClawKitTests/ProviderCatalogModelFilteringTests.swift index 8ff9e70..0a61fa8 100644 --- a/Tests/OpenClawKitTests/ProviderCatalogModelFilteringTests.swift +++ b/Tests/OpenClawKitTests/ProviderCatalogModelFilteringTests.swift @@ -102,7 +102,11 @@ struct ProviderCatalogModelFilteringTests { #expect( error - == "Unknown model: openai/gpt-5.3-codex-spark. gpt-5.3-codex-spark is only supported via openai-codex OAuth. Use openai-codex/gpt-5.3-codex-spark." + == """ + Unknown model: openai/gpt-5.3-codex-spark. \ + gpt-5.3-codex-spark is only supported via openai-codex OAuth. \ + Use openai-codex/gpt-5.3-codex-spark. + """ ) } } diff --git a/Tests/OpenClawKitTests/RuntimeSubsystemTests.swift b/Tests/OpenClawKitTests/RuntimeSubsystemTests.swift index 5b2726f..e677052 100644 --- a/Tests/OpenClawKitTests/RuntimeSubsystemTests.swift +++ b/Tests/OpenClawKitTests/RuntimeSubsystemTests.swift @@ -202,4 +202,3 @@ struct RuntimeSubsystemTests { ) } } - diff --git a/Tests/OpenClawKitTests/TelegramChannelAdapterTests.swift b/Tests/OpenClawKitTests/TelegramChannelAdapterTests.swift index b1035d9..e8b685e 100644 --- a/Tests/OpenClawKitTests/TelegramChannelAdapterTests.swift +++ b/Tests/OpenClawKitTests/TelegramChannelAdapterTests.swift @@ -139,7 +139,18 @@ struct TelegramChannelAdapterTests { @Test func pollsUpdatesAndDeliversInboundPrivateMessages() async throws { let updates = Data(""" - {"ok":true,"result":[{"update_id":1,"message":{"message_id":10,"text":"hello from tg","chat":{"id":111,"type":"private"},"from":{"id":42,"is_bot":false}}}]} + { + "ok": true, + "result": [{ + "update_id": 1, + "message": { + "message_id": 10, + "text": "hello from tg", + "chat": {"id": 111, "type": "private"}, + "from": {"id": 42, "is_bot": false} + } + }] + } """.utf8) let transport = MockTelegramTransport(updateResponses: [updates, Data("{\"ok\":true,\"result\":[]}".utf8)]) let collector = InboundCollector() @@ -328,7 +339,18 @@ struct TelegramChannelAdapterTests { @Test func pollLoopRecoversFromNetworkFailureAndProcessesLaterUpdates() async throws { let updates = Data(""" - {"ok":true,"result":[{"update_id":90,"message":{"message_id":10,"text":"after retry","chat":{"id":111,"type":"private"},"from":{"id":42,"is_bot":false}}}]} + { + "ok": true, + "result": [{ + "update_id": 90, + "message": { + "message_id": 10, + "text": "after retry", + "chat": {"id": 111, "type": "private"}, + "from": {"id": 42, "is_bot": false} + } + }] + } """.utf8) let transport = MockTelegramTransport( updateResponses: [updates, Data("{\"ok\":true,\"result\":[]}".utf8)], diff --git a/docs/api-surface.md b/docs/api-surface.md index b51c8e8..b4894e3 100644 --- a/docs/api-surface.md +++ b/docs/api-surface.md @@ -2,6 +2,10 @@ `OpenClawSDK` provides high-level app entry points that compose lower-level modules. +For end-to-end integration guides and symbol documentation, prefer the published +Swift-DocC site. This file is the quick in-repo index for the highest-level SDK +entry points. + ## Configuration and Session Storage - `loadConfig(from:cacheTTLms:)` @@ -51,3 +55,5 @@ - `SecurityAuditReport` - `PortInUseError` - `ProcessResult` +- `OpenClawChatViewModel` +- `OpenClawChatTransport` diff --git a/docs/architecture.md b/docs/architecture.md index fb6ccd6..1178290 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,7 +1,9 @@ # OpenClawKit Architecture This package is organized into layered SwiftPM targets so core concerns remain isolated -while exposing a simple top-level facade. +while exposing a simple top-level facade. The GitHub Pages documentation site +publishes the same SDK-first view through Swift-DocC; this page is the compact +in-repo companion for contributors and integrators. ## Layer Overview @@ -75,3 +77,5 @@ When adding new features: - add core/runtime capability in the appropriate module target - wire facade entry points in `OpenClawSDK` only after lower layers are tested - add unit + E2E coverage with Swift Testing +- prefer user-facing conceptual docs in the DocC catalog, and keep this file + focused on stable architecture notes diff --git a/docs/testing.md b/docs/testing.md index 3c677c5..9f90990 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -14,6 +14,8 @@ swift test - unit-level tests for protocol, core shims, runtime primitives, diagnostics, facade helpers - `Tests/OpenClawKitE2ETests` - end-to-end tests across transport/runtime/channels/plugin flow and reconnect lifecycle +- `Tests/OpenClawLinuxRuntimeTests` + - Linux-focused runtime, provider, gateway, and channel regressions exercised in CI and Docker ## Networking Concurrency Gate @@ -40,61 +42,34 @@ Never commit `.env`. ## Recommended Local Validation Sequence 1. `swift build -Xswiftc -warnings-as-errors` -2. `Scripts/check-networking-concurrency.sh` -3. `swift test` -4. `./Scripts/build-ios-example.sh` -5. `./Scripts/test-ios-example.sh` -6. `./Scripts/build-tvos-example.sh` -7. `Scripts/validate-apple-matrix.sh --platform macos` -8. `Scripts/validate-apple-matrix.sh --platform ios` - -## 2026.2.3 Parity Train Rules - -The `2026.2.3` release is the SDK + control-plane parity train for the pinned -OpenClaw `2026.3.11` snapshot in [docs/parity-2026.3.11.md](./parity-2026.3.11.md). - -For this train: - -1. each implementation step must land as exactly one commit -2. `swift build -Xswiftc -warnings-as-errors` must pass before the commit is created -3. all previously passing tests must still pass -4. tests added for the step must pass and provide targeted regression coverage for the new code introduced by the step - -Checked-in parity fixtures live under `Tests/OpenClawKitTests`; the test suite -must not read `.cursor/**` at runtime. - -Do not defer warning cleanup or critical regression test fixes to later commits. - -## 2026.2.1 Parity Coverage Highlights - -- Channel adapter reliability: - dedicated suites for Slack, Google Chat, Signal, iMessage, Microsoft Teams, - WebChat, and parity-expanded WhatsApp Cloud handling. -- Provider parity reliability: - routing/unit tests for xAI/Grok, OpenAI-compatible packs, Anthropic-compatible - gateway packs, and unique protocol providers (Bedrock/Copilot/Ollama/vLLM/Qwen). -- Config matrix reliability: - serialization/defaulting coverage for expanded provider-service auth/API-style - fields and channel config surfaces. -- Security reliability: - regression coverage for parity secret detection and risky-default findings, - including local-runtime auth-none exemptions and AWS-region checks. -- CI platform coverage: - full per-commit gate remains required, including Apple matrix validation in - `Scripts/validate-apple-matrix.sh` for macOS and iOS declarations. - -## 2026.2.2 Coverage Highlights - -- Config/auth compatibility: - regression coverage for canonical `auth` and `models.providers` encoding, - legacy provider-service decoding, and auth profile cooldown ordering. -- Provider catalog parity: - snapshot-backed assertions for provider IDs, auth modes, base URLs, APIs, and - default model IDs against the pinned OpenClaw TS reference commit. -- Apple hardware hardening: - credential-store coverage for device-bound Keychain accessibility and Apple - sample defaults that prefer Foundation Models only when runtime availability - allows it. -- Example-app release scope: - the `2026.2.2` validation gate covers the iOS and tvOS demos only; the - visionOS demo is deferred from this release train. +2. `Scripts/lint-swift.sh` +3. `Scripts/check-networking-concurrency.sh` +4. `swift test` +5. `Scripts/build-docs-site.sh` +6. `./Scripts/build-ios-example.sh` +7. `./Scripts/test-ios-example.sh` +8. `./Scripts/build-tvos-example.sh` +9. `Scripts/validate-apple-matrix.sh --platform macos` +10. `Scripts/validate-apple-matrix.sh --platform ios` + +## Linux Runtime Validation + +The repo CI runs the Linux runtime gate on Ubuntu. To catch Linux-only failures +before pushing, run the same scripts in Docker: + +```bash +docker run --rm -v "$PWD:/workspace" -w /workspace swift:6.2 Scripts/build-linux-runtime.sh +docker run --rm -v "$PWD:/workspace" -w /workspace swift:6.2 Scripts/check-networking-concurrency.sh +docker run --rm -v "$PWD:/workspace" -w /workspace swift:6.2 Scripts/test-linux-runtime.sh +``` + +## Documentation Validation + +The public docs site is generated with Swift-DocC on macOS. + +```bash +Scripts/build-docs-site.sh +``` + +The script fails if the static site does not contain `index.html` and the +`documentation/openclawkit` entry point expected by GitHub Pages. diff --git a/internal-docs/README.md b/internal-docs/README.md new file mode 100644 index 0000000..dc03462 --- /dev/null +++ b/internal-docs/README.md @@ -0,0 +1,7 @@ +# Internal Docs + +This directory keeps release-history and planning material that should stay in +the repository but should not appear in the public SDK docs surface. + +- `parity/` stores version-to-version parity manifests. +- `roadmaps/` stores historical roadmap snapshots. diff --git a/docs/parity-2026.2.1.md b/internal-docs/parity/parity-2026.2.1.md similarity index 100% rename from docs/parity-2026.2.1.md rename to internal-docs/parity/parity-2026.2.1.md diff --git a/docs/parity-2026.2.4.md b/internal-docs/parity/parity-2026.2.4.md similarity index 100% rename from docs/parity-2026.2.4.md rename to internal-docs/parity/parity-2026.2.4.md diff --git a/docs/parity-2026.3.11.md b/internal-docs/parity/parity-2026.3.11.md similarity index 100% rename from docs/parity-2026.3.11.md rename to internal-docs/parity/parity-2026.3.11.md diff --git a/docs/roadmap-2026.1.3.md b/internal-docs/roadmaps/roadmap-2026.1.3.md similarity index 100% rename from docs/roadmap-2026.1.3.md rename to internal-docs/roadmaps/roadmap-2026.1.3.md diff --git a/docs/roadmap-2026.1.5.md b/internal-docs/roadmaps/roadmap-2026.1.5.md similarity index 100% rename from docs/roadmap-2026.1.5.md rename to internal-docs/roadmaps/roadmap-2026.1.5.md diff --git a/docs/roadmap-2026.2.0.md b/internal-docs/roadmaps/roadmap-2026.2.0.md similarity index 100% rename from docs/roadmap-2026.2.0.md rename to internal-docs/roadmaps/roadmap-2026.2.0.md diff --git a/docs/roadmap-2026.2.1.md b/internal-docs/roadmaps/roadmap-2026.2.1.md similarity index 100% rename from docs/roadmap-2026.2.1.md rename to internal-docs/roadmaps/roadmap-2026.2.1.md