Skip to content

fix(sdk-swift): align broker client with v7, rename RelayCast → AgentRelayClient#989

Merged
willwashburn merged 11 commits into
mainfrom
claude/modest-ritchie-njSkj
May 26, 2026
Merged

fix(sdk-swift): align broker client with v7, rename RelayCast → AgentRelayClient#989
willwashburn merged 11 commits into
mainfrom
claude/modest-ritchie-njSkj

Conversation

@willwashburn
Copy link
Copy Markdown
Member

@willwashburn willwashburn commented May 25, 2026

Fixes #988.

Summary

The Swift SDK's broker client was still implementing the v6 protocol — connecting to /v1/ws and exchanging a hello/hello_ack handshake before sending actions over the WebSocket. The v7 broker's /ws endpoint is a read-only event broadcast (handle_dashboard_ws), so every Swift connection timed out waiting for hello_ack.

This PR realigns the Swift broker client with the v7 contract used by the Node SDK, and renames it to stop conflating it with the Relaycast cloud service.

v7 protocol fix

  • RelayTransport.swift — default WS path is now /ws (not /v1/ws); the unused ?token= query parameter is gone. Auth stays on X-API-Key/Authorization headers.
  • RelayHTTP.swift (new) — minimal actor-based HTTP client with X-API-Key auth and broker error decoding.
  • AgentRelayClient.swift — drops the hello/hello_ack handshake (WS upgrade is the "connected" signal) and routes actions through the broker REST API:
    • spawnAgentPOST /api/spawn
    • releaseAgentDELETE /api/spawned/{name}
    • Channel post, agent dm/postPOST /api/send
  • WS frames are decoded as bare BrokerEvent ({kind: "...", ...}) instead of the legacy {type, payload} envelope, and still flow into brokerEvents, inboundMessages, and Channel.events.

Breaking: RelayCastAgentRelayClient

The class was named after the Relaycast cloud service (api.relaycast.dev, talked to by @relaycast/sdk in the Node ecosystem), but it has always targeted a local or remote agent-relay-broker over its /ws and /api/* endpoints. Renaming RelayCastAgentRelayClient matches the Node SDK's AgentRelayClient and removes the confusion.

No back-compat typealias — Swift consumers must update to the new name. Migration:

// Before
let relay = RelayCast(apiKey: "rk_live_...")

// After
let relay = AgentRelayClient(apiKey: "rk_live_...")

Test plan

  • cd packages/sdk-swift && swift build
  • cd packages/sdk-swift && swift test
  • End-to-end against a local v7 broker:
    • agent-relay up --no-dashboard --foreground --state-dir ./.agent-relay
    • AgentRelayClient(...).channel("test").subscribe() returns without timing out
    • channel.post("hello") succeeds and is observed on channel.events
    • spawnAgent / releaseAgent round-trip through the broker

Drop the legacy hello/hello_ack WebSocket handshake and stop sending
actions over /v1/ws. RelayCast now subscribes to /ws as a read-only
event stream and uses the broker HTTP API (POST /api/spawn, DELETE
/api/spawned/{name}, POST /api/send) for spawn, release, and message
operations.
@willwashburn willwashburn requested a review from khaliqgant as a code owner May 25, 2026 22:00
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Swift SDK realigns with broker v7: client renamed to AgentRelayClient, WS handshake removed, events consumed from /ws, actions moved to HTTP via new RelayHTTP, event decoding simplified to bare BrokerEvent, README/CHANGELOG/tests updated, and CI workflows detect sdk-swift-only changes.

Changes

Swift SDK v7 Broker Protocol Alignment

Layer / File(s) Summary
Client naming, README, and changelog
packages/sdk-swift/README.md, CHANGELOG.md
Public client renamed from RelayCast to AgentRelayClient; README and CHANGELOG updated with migration guidance and broker endpoint notes.
RelayHTTP actor (HTTP client)
packages/sdk-swift/Sources/AgentRelaySDK/RelayHTTP.swift
New RelayHTTP actor implements async post, delete, and get; normalizes ws/wss→http/https; injects X-API-Key and Authorization headers; handles JSON body, URLSession calls, and decodes HTTP error payloads.
WebSocket endpoint update
packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift
WebSocket URL resolution now targets v7 /ws, rewrites legacy /v1/ws, strips legacy token query param, and preserves auth headers.
Connection lifecycle & initialization
packages/sdk-swift/Sources/AgentRelaySDK/AgentRelayClient.swift
Handshake (hello/hello_ack) removed. ensureConnected() starts frame routing and treats WebSocket upgrade as connected. AgentRelayClient constructs RelayHTTP and passes it to RelayCore; inbound stream docs updated.
Operations moved to HTTP
packages/sdk-swift/Sources/AgentRelaySDK/AgentRelayClient.swift
Channel posts, agent messages, spawn, and release actions use HTTP endpoints (POST /api/send, POST /api/spawn, DELETE /api/spawned/{name}); added JSON encoding and HTTP send helpers.
WebSocket event decoding
packages/sdk-swift/Sources/AgentRelaySDK/AgentRelayClient.swift
routeFrames() decodes incoming frames as bare BrokerEvent JSON objects, forwards .event(event) to inbound/broker streams, and routes relayInbound to per-channel continuations; envelope/handshake handling removed.
Tests and protocol validation
packages/sdk-swift/Tests/AgentRelaySDKTests/AgentRelaySDKTests.swift
Tests refactored to AgentRelayClient: verify apiKey, default baseURL, channel creation, add bare BrokerEvent decoding tests, and add WS/REST URL resolution tests.
CI workflow change-scope detection
.github/workflows/*
Workflows now compute sdk_swift_only when changes are exclusively under packages/sdk-swift/ and skip or exclude jobs/workflow triggers accordingly (e.g., e2e, test, rust-ci, package-validation, relay-cleanroom-hardening).

Sequence Diagram

sequenceDiagram
  participant Client as AgentRelayClient
  participant Core as RelayCore
  participant Transport as RelayTransport (WS)
  participant HTTP as RelayHTTP
  participant Broker as Broker (v7)

  Client->>Core: init(transport, http)
  Client->>Core: ensureConnected()
  Core->>Transport: connect() (WebSocket upgrade to /ws)
  Transport-->>Core: upgrade success
  Core->>Core: start frame router
  Core-->>Client: emit .connected

  Client->>HTTP: POST /api/spawn (spawnAgent)
  HTTP->>Broker: POST /api/spawn
  Broker-->>HTTP: 200/4xx
  HTTP-->>Client: response

  Transport->>Core: incoming WebSocket frame
  Core->>Client: BrokerEvent (decoded bare)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • khaliqgant

Poem

🐰 I hopped from frames to HTTP lanes,
Dropped handshakes for clearer plains,
RelayCast now wears a new name,
/ws listens while POSTs do the same,
A small rabbit cheers the v7 change!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: aligning the Swift broker client with v7 protocol and renaming RelayCast to AgentRelayClient.
Description check ✅ Passed The PR description comprehensively covers the changes, provides detailed technical context, explains the breaking change, and includes a test plan with checkboxes.
Linked Issues check ✅ Passed All primary objectives from #988 are met: removed hello/hello_ack handshake, switched to /ws endpoint, moved actions to HTTP endpoints, preserved event APIs, and renamed RelayCast to AgentRelayClient.
Out of Scope Changes check ✅ Passed All changes are within scope of the v7 alignment and RelayCast rename. Workflow updates exclude sdk-swift from unrelated CI runs, which is appropriately scoped.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/modest-ritchie-njSkj

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: daed3414d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 134 to 138
func registerOrRotate(name: String) async throws -> AgentRegistration {
try await ensureConnected()
return AgentRegistration(agentName: name, token: name) { agentName, token in
// v7 brokers do not have a register/rotate endpoint — agents are
// identified by name and authenticated via the broker API key.
AgentRegistration(agentName: name, token: name) { agentName, token in
AgentClient(core: self, agentName: agentName, token: token)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Call ensureConnected before returning from registerOrRotate

registerOrRotate no longer opens the WebSocket connection and now just returns a local AgentRegistration. In flows that rely on registerOrRotate as the startup step, this leaves /ws disconnected, so brokerEvents/connectionState streams stay silent until some separate call (like channel.subscribe()) happens. This is a behavior regression from the previous implementation and breaks callers that only register then listen.

Useful? React with 👍 / 👎.

}

func releaseAgent(name: String, reason: String? = nil) async throws {
try await ensureConnected()
try await send(.releaseAgent(ReleaseAgentPayload(name: name, reason: reason)))
let escaped = name.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? name
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Percent-encode agent names as path segments

Encoding with .urlPathAllowed does not escape /, so names containing slashes (for example team/worker) generate DELETE /api/spawned/team/worker instead of /api/spawned/team%2Fworker. That can target the wrong route/agent or 404; the HTTP path should use segment-safe encoding equivalent to encodeURIComponent for parity with other SDKs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/sdk-swift/Sources/AgentRelaySDK/RelayCast.swift">

<violation number="1" location="packages/sdk-swift/Sources/AgentRelaySDK/RelayCast.swift:124">
P2: Percent-encode the agent name as a path segment before interpolating it into `/api/spawned/{name}`; `/` must be escaped here.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/sdk-swift/Sources/AgentRelaySDK/RelayHTTP.swift Outdated
Comment thread packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift Outdated
}

func releaseAgent(name: String, reason: String? = nil) async throws {
try await ensureConnected()
try await send(.releaseAgent(ReleaseAgentPayload(name: name, reason: reason)))
let escaped = name.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? name
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Percent-encode the agent name as a path segment before interpolating it into /api/spawned/{name}; / must be escaped here.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-swift/Sources/AgentRelaySDK/RelayCast.swift, line 124:

<comment>Percent-encode the agent name as a path segment before interpolating it into `/api/spawned/{name}`; `/` must be escaped here.</comment>

<file context>
@@ -120,41 +108,40 @@ actor RelayCore {
     func releaseAgent(name: String, reason: String? = nil) async throws {
-        try await ensureConnected()
-        try await send(.releaseAgent(ReleaseAgentPayload(name: name, reason: reason)))
+        let escaped = name.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? name
+        let body: Data?
+        if let reason {
</file context>
Suggested change
let escaped = name.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? name
let escaped = name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)?.replacingOccurrences(of: "/", with: "%2F") ?? name

`RelayCast` conflated this broker client with the unrelated Relaycast
cloud service (api.relaycast.dev). The class actually targets a local
or remote agent-relay-broker over its /ws and /api/* endpoints, so
rename it to `AgentRelayClient` (matching the Node SDK).

`RelayCast` stays around as a `@available(*, deprecated, renamed:)`
typealias so existing Swift consumers keep compiling.
@willwashburn willwashburn changed the title fix(sdk-swift): align RelayCast with v7 broker contract fix(sdk-swift): align broker client with v7, rename RelayCast → AgentRelayClient May 25, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift (1)

101-118: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove legacy token query parameter if present in baseURL.

The inline comment states the token query parameter is gone in v7, but URLComponents preserves any query parameters from the original baseURL. If baseURL contains ?token=..., it will be included in the WebSocket request. The v7 broker might reject this or it could be a security concern. Explicitly remove the token query parameter to align with the documented v7 contract.

🧹 Proposed fix to strip token parameter
 var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
 if components?.scheme == "http" { components?.scheme = "ws" }
 if components?.scheme == "https" { components?.scheme = "wss" }
+
+// Remove legacy token query parameter (v7 uses X-API-Key header only)
+components?.queryItems = components?.queryItems?.filter { $0.name != "token" }
+if components?.queryItems?.isEmpty == true {
+    components?.queryItems = nil
+}
+
 let existingPath = components?.path ?? ""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift` around lines
101 - 118, The websocketRequest() currently builds URLComponents from baseURL
but leaves any query parameters (e.g., token) intact; update websocketRequest to
explicitly remove any "token" query parameter from components (e.g., filter out
components?.queryItems where name == "token") before creating the URLRequest so
the resulting request url (used to set request = URLRequest(url: components?.url
?? baseURL)) does not include the legacy token query string while still setting
the Authorization/X-API-Key headers with authToken.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/sdk-swift/Sources/AgentRelaySDK/AgentRelayClient.swift`:
- Around line 131-132: The current percent-encoding uses
CharacterSet.urlPathAllowed which leaves "/" unescaped so
"/api/spawned/\(escaped)" can split into multiple segments; update the encoding
used for the local variable escaped (used to build "/api/spawned/\(escaped)") to
percent-encode the name as a single path component by creating an allowed
CharacterSet derived from CharacterSet.urlPathAllowed with the "/" character
removed (so "/" is percent-encoded) and call
addingPercentEncoding(withAllowedCharacters:) with that set; keep the fallback
to the raw name if encoding fails and then use escaped when constructing the
request path.

In `@packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift`:
- Around line 107-112: The path-handling in RelayTransport.swift currently
preserves "/v1/ws" (variables: components, existingPath, components?.path), so
update the conditional logic to actively rewrite any legacy "/v1/ws" suffix to
"/ws"; specifically, if existingPath endsWith("/v1/ws") set components?.path =
existingPath.replacingOccurrences(of: "/v1/ws", with: "/ws"), otherwise apply
the existing rules (set "/ws" when empty or add "/ws" respecting trailing
slash). Ensure you only mutate components?.path and preserve the rest of the
path when performing the "/v1/ws" -> "/ws" replacement.

---

Outside diff comments:
In `@packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift`:
- Around line 101-118: The websocketRequest() currently builds URLComponents
from baseURL but leaves any query parameters (e.g., token) intact; update
websocketRequest to explicitly remove any "token" query parameter from
components (e.g., filter out components?.queryItems where name == "token")
before creating the URLRequest so the resulting request url (used to set request
= URLRequest(url: components?.url ?? baseURL)) does not include the legacy token
query string while still setting the Authorization/X-API-Key headers with
authToken.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bf6d8492-b27e-4e5e-8b75-85e1a5dff5fc

📥 Commits

Reviewing files that changed from the base of the PR and between 6fde5b4 and bae112f.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • packages/sdk-swift/README.md
  • packages/sdk-swift/Sources/AgentRelaySDK/AgentRelayClient.swift
  • packages/sdk-swift/Sources/AgentRelaySDK/RelayHTTP.swift
  • packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift
  • packages/sdk-swift/Tests/AgentRelaySDKTests/AgentRelaySDKTests.swift

Comment thread packages/sdk-swift/Sources/AgentRelaySDK/AgentRelayClient.swift Outdated
Comment thread packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift Outdated
claude added 2 commits May 26, 2026 00:00
The rename to AgentRelayClient is a hard break; no back-compat shim.
Comments that describe how things used to work (handshake gone, paths
removed, envelopes replaced) just rot; the code already shows what it
does.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sdk-swift/Sources/AgentRelaySDK/RelayHTTP.swift (1)

72-84: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize websocket-style base paths before building REST URLs.

Line 83 currently appends REST paths onto whatever base path is present. If baseURL is websocket-shaped (e.g., .../ws), calls resolve to .../ws/api/send instead of /api/send. Because AgentRelayClient injects the same base URL into both transport and HTTP clients, this can break spawn/send/release flows.

Suggested patch
 private func url(for path: String) -> URL? {
     guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else {
         return nil
     }
     if components.scheme == "ws" { components.scheme = "http" }
     if components.scheme == "wss" { components.scheme = "https" }

-    let trimmedBase = (components.path).hasSuffix("/")
-        ? String(components.path.dropLast())
-        : components.path
+    // REST requests should not inherit ws route/query fragments.
+    components.query = nil
+    components.fragment = nil
+
+    var trimmedBase = components.path.hasSuffix("/")
+        ? String(components.path.dropLast())
+        : components.path
+    if trimmedBase.hasSuffix("/ws") {
+        trimmedBase.removeLast(3) // "/ws"
+    }

     let normalizedPath = path.hasPrefix("/") ? path : "/" + path
     components.path = trimmedBase + normalizedPath
     return components.url
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk-swift/Sources/AgentRelaySDK/RelayHTTP.swift` around lines 72 -
84, The url(for:) helper is appending REST paths onto websocket-style base paths
(e.g., a baseURL ending in "/ws"), producing incorrect endpoints like
".../ws/api/send"; update url(for:) to detect websocket-style path segments
(such as "/ws" or "/wss" or other known websocket endpoints) and strip that
terminal websocket segment from components.path before concatenating
normalizedPath so REST calls use the service root; modify the logic in url(for:)
that computes trimmedBase (and reference baseURL and AgentRelayClient usage) to
remove trailing websocket-specific segments prior to building components.path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/sdk-swift/Sources/AgentRelaySDK/RelayHTTP.swift`:
- Around line 72-84: The url(for:) helper is appending REST paths onto
websocket-style base paths (e.g., a baseURL ending in "/ws"), producing
incorrect endpoints like ".../ws/api/send"; update url(for:) to detect
websocket-style path segments (such as "/ws" or "/wss" or other known websocket
endpoints) and strip that terminal websocket segment from components.path before
concatenating normalizedPath so REST calls use the service root; modify the
logic in url(for:) that computes trimmedBase (and reference baseURL and
AgentRelayClient usage) to remove trailing websocket-specific segments prior to
building components.path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6970332b-0f19-4eff-950c-6ff105b5620a

📥 Commits

Reviewing files that changed from the base of the PR and between c591861 and d7da2a2.

📒 Files selected for processing (4)
  • packages/sdk-swift/Sources/AgentRelaySDK/AgentRelayClient.swift
  • packages/sdk-swift/Sources/AgentRelaySDK/RelayHTTP.swift
  • packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift
  • packages/sdk-swift/Tests/AgentRelaySDKTests/AgentRelaySDKTests.swift
💤 Files with no reviewable changes (2)
  • packages/sdk-swift/Sources/AgentRelaySDK/RelayTransport.swift
  • packages/sdk-swift/Sources/AgentRelaySDK/AgentRelayClient.swift

Extends the existing web-only skip pattern in test.yml, rust-ci.yml,
package-validation.yml, and node-compat.yml with a parallel
sdk_swift_only flag. Also negates packages/sdk-swift/** in
e2e-tests.yml and relay-cleanroom-hardening.yml's packages/** path
filter.

Swift-only PRs currently have no Swift CI to run; tracked separately
for a path-area refactor that adds it.
Addresses PR review feedback:

- RelayHTTP.url(for:) now strips a trailing /ws or /v1/ws from the
  base path so callers that pass a ws-shaped baseURL still hit
  /api/* instead of /ws/api/*.
- RelayTransport.websocketRequest() normalizes trailing slashes
  before checking the /ws suffix (so /ws/ no longer becomes /ws/ws),
  rewrites legacy /v1/ws to /ws, and strips a legacy ?token= query.
- releaseAgent uses a path-segment-safe CharacterSet that excludes
  /, so agent names containing slashes don't split the request path.
- registerOrRotate calls ensureConnected() again, restoring the
  pre-PR behavior of opening the WS as part of registration.

Extracted the URL normalization into static helpers and added unit
tests covering each edge case.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 10 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/sdk-swift/Sources/AgentRelaySDK/RelayCast.swift">

<violation number="1" location="packages/sdk-swift/Sources/AgentRelaySDK/RelayCast.swift:124">
P2: Percent-encode the agent name as a path segment before interpolating it into `/api/spawned/{name}`; `/` must be escaped here.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread .github/workflows/test.yml
claude and others added 4 commits May 26, 2026 01:33
Builds and runs swift test in packages/sdk-swift whenever any file
in that directory changes. macOS runners are billed at 10x but the
job is small and only fires for sdk-swift PRs.
@willwashburn willwashburn merged commit 48bc831 into main May 26, 2026
48 checks passed
@willwashburn willwashburn deleted the claude/modest-ritchie-njSkj branch May 26, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Swift SDK RelayCast is incompatible with v7 broker (hello/hello_ack handshake removed)

2 participants