ci: fix swiftformat and bun CI

This commit is contained in:
Peter Steinberger
2025-12-18 08:55:47 +01:00
parent 2f21b94a76
commit 5c705ab675
20 changed files with 259 additions and 93 deletions

View File

@@ -149,4 +149,3 @@ struct AnthropicAuthControls: View {
}
}
}

View File

@@ -183,7 +183,7 @@ enum PiOAuthStore {
static func oauthDir() -> URL {
if let override = ProcessInfo.processInfo.environment[self.piAgentDirEnv]?
.trimmingCharacters(in: .whitespacesAndNewlines),
!override.isEmpty
!override.isEmpty
{
let expanded = NSString(string: override).expandingTildeInPath
return URL(fileURLWithPath: expanded, isDirectory: true)

View File

@@ -74,7 +74,7 @@ actor BridgeServer {
}
private func handle(connection: NWConnection) async {
let handler = BridgeConnectionHandler(connection: connection, logger: self.logger)
let handler = BridgeConnectionHandler(connection: connection, logger: self.logger)
await handler.run(
resolveAuth: { [weak self] hello in
await self?.authorize(hello: hello) ?? .error(code: "UNAVAILABLE", message: "bridge unavailable")

View File

@@ -26,7 +26,11 @@ final class CanvasManager {
try self.showDetailed(sessionKey: sessionKey, target: path, placement: placement).directory
}
func showDetailed(sessionKey: String, target: String? = nil, placement: CanvasPlacement? = nil) throws -> CanvasShowResult {
func showDetailed(
sessionKey: String,
target: String? = nil,
placement: CanvasPlacement? = nil) throws -> CanvasShowResult
{
Self.logger.debug(
"showDetailed start session=\(sessionKey, privacy: .public) target=\(target ?? "", privacy: .public) placement=\(placement != nil)")
let anchorProvider = self.defaultAnchorProvider ?? Self.mouseAnchorProvider
@@ -177,7 +181,8 @@ final class CanvasManager {
private static func localStatus(sessionDir: URL, target: String) -> CanvasShowStatus {
let fm = FileManager.default
let trimmed = target.trimmingCharacters(in: .whitespacesAndNewlines)
let withoutQuery = trimmed.split(separator: "?", maxSplits: 1, omittingEmptySubsequences: false).first.map(String.init) ?? trimmed
let withoutQuery = trimmed.split(separator: "?", maxSplits: 1, omittingEmptySubsequences: false).first
.map(String.init) ?? trimmed
var path = withoutQuery
if path.hasPrefix("/") { path.removeFirst() }
path = path.removingPercentEncoding ?? path

View File

@@ -222,11 +222,10 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
|| trimmed.hasPrefix(Self.builtinPrefix + "/")
else { return nil }
let relative: String
if trimmed == Self.builtinPrefix || trimmed == Self.builtinPrefix + "/" {
relative = "index.html"
let relative = if trimmed == Self.builtinPrefix || trimmed == Self.builtinPrefix + "/" {
"index.html"
} else {
relative = String(trimmed.dropFirst((Self.builtinPrefix + "/").count))
String(trimmed.dropFirst((Self.builtinPrefix + "/").count))
}
if relative.isEmpty { return self.html("Not Found", title: "Canvas: 404") }

View File

@@ -160,7 +160,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
// Only auto-reload when we are showing local canvas content.
guard webView.url?.scheme == CanvasScheme.scheme else { return }
// Avoid reloading the built-in A2UI shell due to filesystem noise (it does not depend on session files).
// Avoid reloading the built-in A2UI shell due to filesystem noise (it does not depend on session
// files).
let path = webView.url?.path ?? ""
if path.hasPrefix("/__clawdis__/a2ui") { return }
if path == "/" || path.isEmpty {
@@ -200,7 +201,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
@MainActor deinit {
self.webView.configuration.userContentController.removeScriptMessageHandler(forName: CanvasA2UIActionMessageHandler.messageName)
self.webView.configuration.userContentController
.removeScriptMessageHandler(forName: CanvasA2UIActionMessageHandler.messageName)
self.watcher.stop()
}
@@ -255,10 +257,10 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
if trimmed.hasPrefix("/") {
var isDir: ObjCBool = false
if FileManager.default.fileExists(atPath: trimmed, isDirectory: &isDir), !isDir.boolValue {
let url = URL(fileURLWithPath: trimmed)
canvasWindowLogger.debug("canvas load file \(url.absoluteString, privacy: .public)")
self.loadFile(url)
return
let url = URL(fileURLWithPath: trimmed)
canvasWindowLogger.debug("canvas load file \(url.absoluteString, privacy: .public)")
self.loadFile(url)
return
}
}
@@ -344,17 +346,17 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
private static func makeWindow(for presentation: CanvasPresentation, contentView: NSView) -> NSWindow {
switch presentation {
case .window:
let window = NSWindow(
contentRect: NSRect(origin: .zero, size: CanvasLayout.windowSize),
styleMask: [.titled, .closable, .resizable, .miniaturizable],
backing: .buffered,
defer: false)
window.title = "Clawdis Canvas"
window.isReleasedWhenClosed = false
window.contentView = contentView
window.center()
window.minSize = NSSize(width: 880, height: 680)
return window
let window = NSWindow(
contentRect: NSRect(origin: .zero, size: CanvasLayout.windowSize),
styleMask: [.titled, .closable, .resizable, .miniaturizable],
backing: .buffered,
defer: false)
window.title = "Clawdis Canvas"
window.isReleasedWhenClosed = false
window.contentView = contentView
window.center()
window.minSize = NSSize(width: 880, height: 680)
return window
case .panel:
let panel = CanvasPanel(
@@ -501,7 +503,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
if self.webView.url?.scheme == CanvasScheme.scheme {
Task { await DeepLinkHandler.shared.handle(url: url) }
} else {
canvasWindowLogger.debug("ignoring deep link from non-canvas page \(url.absoluteString, privacy: .public)")
canvasWindowLogger
.debug("ignoring deep link from non-canvas page \(url.absoluteString, privacy: .public)")
}
decisionHandler(.cancel)
return
@@ -635,12 +638,14 @@ private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHan
guard let name = userAction["name"] as? String, !name.isEmpty else { return }
let actionId =
(userAction["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
?? UUID().uuidString
?? UUID().uuidString
canvasWindowLogger.info("A2UI action \(name, privacy: .public) session=\(self.sessionKey, privacy: .public)")
let surfaceId = (userAction["surfaceId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "main"
let sourceComponentId = (userAction["sourceComponentId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "-"
let surfaceId = (userAction["surfaceId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
.nonEmpty ?? "main"
let sourceComponentId = (userAction["sourceComponentId"] as? String)?
.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "-"
let host = Self.sanitizeTagValue(InstanceIdentity.displayName)
let instanceId = InstanceIdentity.instanceId.lowercased()
let contextJSON = Self.compactJSON(userAction["context"])

View File

@@ -31,20 +31,20 @@ struct ConfigSettings: View {
var body: some View {
ScrollView { self.content }
.onChange(of: self.modelCatalogPath) { _, _ in
Task { await self.loadModels() }
}
.onChange(of: self.modelCatalogReloadBump) { _, _ in
Task { await self.loadModels() }
}
.task {
guard !self.hasLoaded else { return }
guard !self.isPreview else { return }
self.hasLoaded = true
self.loadConfig()
await self.loadModels()
self.allowAutosave = true
}
.onChange(of: self.modelCatalogPath) { _, _ in
Task { await self.loadModels() }
}
.onChange(of: self.modelCatalogReloadBump) { _, _ in
Task { await self.loadModels() }
}
.task {
guard !self.hasLoaded else { return }
guard !self.isPreview else { return }
self.hasLoaded = true
self.loadConfig()
await self.loadModels()
self.allowAutosave = true
}
}
private var content: some View {

View File

@@ -237,8 +237,13 @@ enum ControlRequestHandler {
logger.info("canvas show start session=\(session, privacy: .public) path=\(path ?? "", privacy: .public)")
do {
logger.info("canvas show awaiting CanvasManager")
let res = try await CanvasManager.shared.showDetailed(sessionKey: session, target: path, placement: placement)
logger.info("canvas show done dir=\(res.directory, privacy: .public) status=\(String(describing: res.status), privacy: .public)")
let res = try await CanvasManager.shared.showDetailed(
sessionKey: session,
target: path,
placement: placement)
logger
.info(
"canvas show done dir=\(res.directory, privacy: .public) status=\(String(describing: res.status), privacy: .public)")
let payload = try? JSONEncoder().encode(res)
return Response(ok: true, message: res.directory, payload: payload)
} catch {
@@ -277,17 +282,21 @@ enum ControlRequestHandler {
}
}
private static func handleCanvasA2UI(session: String, command: CanvasA2UICommand, jsonl: String?) async -> Response {
private static func handleCanvasA2UI(
session: String,
command: CanvasA2UICommand,
jsonl: String?) async -> Response
{
guard self.canvasEnabled() else { return Response(ok: false, message: "Canvas disabled by user") }
do {
// Ensure the Canvas is visible without forcing a navigation/reload.
_ = try await CanvasManager.shared.show(sessionKey: session, path: nil)
// Wait for the in-page A2UI bridge. If it doesn't appear, force-load the bundled A2UI shell once.
var ready = await Self.waitForCanvasA2UI(session: session, requireBuiltinPath: false, timeoutMs: 2_000)
var ready = await Self.waitForCanvasA2UI(session: session, requireBuiltinPath: false, timeoutMs: 2000)
if !ready {
_ = try await CanvasManager.shared.show(sessionKey: session, path: "/__clawdis__/a2ui/")
ready = await Self.waitForCanvasA2UI(session: session, requireBuiltinPath: true, timeoutMs: 5_000)
ready = await Self.waitForCanvasA2UI(session: session, requireBuiltinPath: true, timeoutMs: 5000)
}
guard ready else { return Response(ok: false, message: "A2UI not ready") }
@@ -397,7 +406,8 @@ enum ControlRequestHandler {
let found = dict.keys.sorted().joined(separator: ", ")
throw NSError(domain: "A2UI", code: 3, userInfo: [
NSLocalizedDescriptionKey: """
A2UI JSONL line \(item.lineNumber): expected exactly one of \(allowed.sorted().joined(separator: ", ")); found: \(found)
A2UI JSONL line \(item.lineNumber): expected exactly one of \(allowed.sorted()
.joined(separator: ", ")); found: \(found)
""",
])
}
@@ -441,7 +451,7 @@ enum ControlRequestHandler {
let data = try await GatewayConnection.shared.request(
method: "node.list",
params: [:],
timeoutMs: 10_000)
timeoutMs: 10000)
let payload = try JSONDecoder().decode(GatewayNodeListPayload.self, from: data)
let result = self.buildNodeListResult(payload: payload)
@@ -460,7 +470,7 @@ enum ControlRequestHandler {
let data = try await GatewayConnection.shared.request(
method: "node.describe",
params: ["nodeId": AnyCodable(nodeId)],
timeoutMs: 10_000)
timeoutMs: 10000)
return Response(ok: true, payload: data)
} catch {
return Response(ok: false, message: error.localizedDescription)
@@ -526,7 +536,7 @@ enum ControlRequestHandler {
let data = try await GatewayConnection.shared.request(
method: "node.invoke",
params: params,
timeoutMs: 30_000)
timeoutMs: 30000)
return Response(ok: true, payload: data)
} catch {
logger.error("node invoke failed: \(error.localizedDescription, privacy: .public)")

View File

@@ -12,9 +12,9 @@ enum DeviceModelCatalog {
let family = (deviceFamily ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let model = (modelIdentifier ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let friendlyName = model.isEmpty ? nil : modelIdentifierToName[model]
let symbol = symbolFor(modelIdentifier: model, friendlyName: friendlyName)
?? fallbackSymbol(for: family, modelIdentifier: model)
let friendlyName = model.isEmpty ? nil : self.modelIdentifierToName[model]
let symbol = self.symbolFor(modelIdentifier: model, friendlyName: friendlyName)
?? self.fallbackSymbol(for: family, modelIdentifier: model)
let title = if let friendlyName, !friendlyName.isEmpty {
friendlyName
@@ -47,7 +47,9 @@ enum DeviceModelCatalog {
if lower.hasPrefix("macbook") || lower.hasPrefix("macbookpro") || lower.hasPrefix("macbookair") {
return "laptopcomputer"
}
if lower.hasPrefix("imac") || lower.hasPrefix("macmini") || lower.hasPrefix("macpro") || lower.hasPrefix("macstudio") {
if lower.hasPrefix("imac") || lower.hasPrefix("macmini") || lower.hasPrefix("macpro") || lower
.hasPrefix("macstudio")
{
return "desktopcomputer"
}
@@ -84,8 +86,12 @@ enum DeviceModelCatalog {
private static func loadModelIdentifierToName() -> [String: String] {
var combined: [String: String] = [:]
combined.merge(loadMapping(resourceName: "ios-device-identifiers"), uniquingKeysWith: { current, _ in current })
combined.merge(loadMapping(resourceName: "mac-device-identifiers"), uniquingKeysWith: { current, _ in current })
combined.merge(
self.loadMapping(resourceName: "ios-device-identifiers"),
uniquingKeysWith: { current, _ in current })
combined.merge(
self.loadMapping(resourceName: "mac-device-identifiers"),
uniquingKeysWith: { current, _ in current })
return combined
}
@@ -128,10 +134,10 @@ enum DeviceModelCatalog {
var normalizedName: String? {
switch self {
case .string(let s):
case let .string(s):
let trimmed = s.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
case .stringArray(let arr):
case let .stringArray(arr):
let values = arr
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }

View File

@@ -42,11 +42,11 @@ actor GatewayConnection {
typealias Config = (url: URL, token: String?)
enum Method: String, Sendable {
case agent = "agent"
case status = "status"
case agent
case status
case setHeartbeats = "set-heartbeats"
case systemEvent = "system-event"
case health = "health"
case health
case chatHistory = "chat.history"
case chatSend = "chat.send"
case chatAbort = "chat.abort"

View File

@@ -31,4 +31,3 @@ struct GatewayDecodingError: LocalizedError, Sendable {
var errorDescription: String? { "\(self.method): \(self.message)" }
}

View File

@@ -6,4 +6,3 @@ extension String {
return trimmed.isEmpty ? nil : trimmed
}
}

View File

@@ -18,8 +18,8 @@ final class VoicePushToTalkHotkey: @unchecked Sendable {
init(
beginAction: @escaping @Sendable () async -> Void = { await VoicePushToTalk.shared.begin() },
endAction: @escaping @Sendable () async -> Void = { await VoicePushToTalk.shared.end() }
) {
endAction: @escaping @Sendable () async -> Void = { await VoicePushToTalk.shared.end() })
{
self.beginAction = beginAction
self.endAction = endAction
}

View File

@@ -102,4 +102,3 @@ final class WebChatManager {
// Keep panel controller cached so reopening doesn't re-bootstrap.
}
}

View File

@@ -25,7 +25,7 @@ struct MacGatewayChatTransport: ClawdisChatTransport, Sendable {
"sessionKey": AnyCodable(sessionKey),
"runId": AnyCodable(runId),
],
timeoutMs: 10_000)
timeoutMs: 10000)
}
func listSessions(limit: Int?) async throws -> ClawdisChatSessionsListResponse {
@@ -39,7 +39,7 @@ struct MacGatewayChatTransport: ClawdisChatTransport, Sendable {
let data = try await GatewayConnection.shared.request(
method: "sessions.list",
params: params,
timeoutMs: 15_000)
timeoutMs: 15000)
return try JSONDecoder().decode(ClawdisChatSessionsListResponse.self, from: data)
}

View File

@@ -259,6 +259,7 @@ struct ClawdisCLI {
return ParsedCLIRequest(
request: .nodeInvoke(nodeId: nodeId, command: command, paramsJSON: paramsJSON),
kind: .generic)
default:
throw CLIError.help
}
@@ -450,10 +451,13 @@ struct ClawdisCLI {
if let message = response.message, !message.isEmpty {
FileHandle.standardOutput.write(Data((message + "\n").utf8))
}
if let payload = response.payload, let info = try? JSONDecoder().decode(CanvasShowResult.self, from: payload) {
FileHandle.standardOutput.write(Data(("STATUS:\(info.status.rawValue)\n").utf8))
if let payload = response.payload, let info = try? JSONDecoder().decode(
CanvasShowResult.self,
from: payload)
{
FileHandle.standardOutput.write(Data("STATUS:\(info.status.rawValue)\n".utf8))
if let url = info.url, !url.isEmpty {
FileHandle.standardOutput.write(Data(("URL:\(url)\n").utf8))
FileHandle.standardOutput.write(Data("URL:\(url)\n".utf8))
}
}
return
@@ -515,7 +519,7 @@ struct ClawdisCLI {
let version = (n.version ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
if !platform.isEmpty || !version.isEmpty {
let pv = [platform.isEmpty ? nil : platform, version.isEmpty ? nil : version]
.compactMap { $0 }
.compactMap(\.self)
.joined(separator: " ")
if !pv.isEmpty { print(" platform: \(pv)") }
}
@@ -571,7 +575,9 @@ struct ClawdisCLI {
print(parts.joined(separator: " · "))
if !commands.isEmpty {
print("Commands:")
for c in commands { print("- \(c)") }
for c in commands {
print("- \(c)")
}
}
return
}

View File

@@ -420,6 +420,10 @@ public struct NodePairRequestParams: Codable {
public let displayname: String?
public let platform: String?
public let version: String?
public let devicefamily: String?
public let modelidentifier: String?
public let caps: [String]?
public let commands: [String]?
public let remoteip: String?
public init(
@@ -427,12 +431,20 @@ public struct NodePairRequestParams: Codable {
displayname: String?,
platform: String?,
version: String?,
devicefamily: String?,
modelidentifier: String?,
caps: [String]?,
commands: [String]?,
remoteip: String?
) {
self.nodeid = nodeid
self.displayname = displayname
self.platform = platform
self.version = version
self.devicefamily = devicefamily
self.modelidentifier = modelidentifier
self.caps = caps
self.commands = commands
self.remoteip = remoteip
}
private enum CodingKeys: String, CodingKey {
@@ -440,6 +452,10 @@ public struct NodePairRequestParams: Codable {
case displayname = "displayName"
case platform
case version
case devicefamily = "deviceFamily"
case modelidentifier = "modelIdentifier"
case caps
case commands
case remoteip = "remoteIp"
}
}
@@ -493,6 +509,19 @@ public struct NodePairVerifyParams: Codable {
public struct NodeListParams: Codable {
}
public struct NodeDescribeParams: Codable {
public let nodeid: String
public init(
nodeid: String
) {
self.nodeid = nodeid
}
private enum CodingKeys: String, CodingKey {
case nodeid = "nodeId"
}
}
public struct NodeInvokeParams: Codable {
public let nodeid: String
public let command: String
@@ -837,6 +866,23 @@ public struct ChatSendParams: Codable {
}
}
public struct ChatAbortParams: Codable {
public let sessionkey: String
public let runid: String
public init(
sessionkey: String,
runid: String
) {
self.sessionkey = sessionkey
self.runid = runid
}
private enum CodingKeys: String, CodingKey {
case sessionkey = "sessionKey"
case runid = "runId"
}
}
public struct ChatEvent: Codable {
public let runid: String
public let sessionkey: String

View File

@@ -51,6 +51,14 @@
"minLength": 1,
"type": "string"
},
"deviceFamily": {
"minLength": 1,
"type": "string"
},
"modelIdentifier": {
"minLength": 1,
"type": "string"
},
"mode": {
"minLength": 1,
"type": "string"
@@ -185,6 +193,14 @@
"minLength": 1,
"type": "string"
},
"deviceFamily": {
"minLength": 1,
"type": "string"
},
"modelIdentifier": {
"minLength": 1,
"type": "string"
},
"mode": {
"minLength": 1,
"type": "string"
@@ -538,6 +554,14 @@
"minLength": 1,
"type": "string"
},
"deviceFamily": {
"minLength": 1,
"type": "string"
},
"modelIdentifier": {
"minLength": 1,
"type": "string"
},
"mode": {
"minLength": 1,
"type": "string"
@@ -617,6 +641,14 @@
"minLength": 1,
"type": "string"
},
"deviceFamily": {
"minLength": 1,
"type": "string"
},
"modelIdentifier": {
"minLength": 1,
"type": "string"
},
"mode": {
"minLength": 1,
"type": "string"
@@ -860,6 +892,28 @@
"minLength": 1,
"type": "string"
},
"deviceFamily": {
"minLength": 1,
"type": "string"
},
"modelIdentifier": {
"minLength": 1,
"type": "string"
},
"caps": {
"type": "array",
"items": {
"minLength": 1,
"type": "string"
}
},
"commands": {
"type": "array",
"items": {
"minLength": 1,
"type": "string"
}
},
"remoteIp": {
"minLength": 1,
"type": "string"
@@ -923,6 +977,19 @@
"type": "object",
"properties": {}
},
"NodeDescribeParams": {
"additionalProperties": false,
"type": "object",
"properties": {
"nodeId": {
"minLength": 1,
"type": "string"
}
},
"required": [
"nodeId"
]
},
"NodeInvokeParams": {
"additionalProperties": false,
"type": "object",
@@ -1773,7 +1840,7 @@
},
"limit": {
"minimum": 1,
"maximum": 500,
"maximum": 1000,
"type": "integer"
}
},
@@ -1818,6 +1885,24 @@
"idempotencyKey"
]
},
"ChatAbortParams": {
"additionalProperties": false,
"type": "object",
"properties": {
"sessionKey": {
"minLength": 1,
"type": "string"
},
"runId": {
"minLength": 1,
"type": "string"
}
},
"required": [
"sessionKey",
"runId"
]
},
"ChatEvent": {
"additionalProperties": false,
"type": "object",
@@ -1844,6 +1929,10 @@
"const": "final",
"type": "string"
},
{
"const": "aborted",
"type": "string"
},
{
"const": "error",
"type": "string"

View File

@@ -38,6 +38,7 @@
"dependencies": {
"@grammyjs/transformer-throttler": "^1.2.1",
"@homebridge/ciao": "^1.3.4",
"@mariozechner/pi-agent-core": "^0.21.0",
"@mariozechner/pi-ai": "^0.21.0",
"@mariozechner/pi-coding-agent": "^0.21.0",
"@sinclair/typebox": "^0.34.41",

View File

@@ -2419,6 +2419,11 @@ describe("gateway server", () => {
});
});
const sendResP = onceMessage(
ws,
(o) => o.type === "res" && o.id === "send-mismatch-1",
10_000,
);
ws.send(
JSON.stringify({
type: "req",
@@ -2435,6 +2440,11 @@ describe("gateway server", () => {
await agentStartedP;
const abortResP = onceMessage(
ws,
(o) => o.type === "res" && o.id === "abort-mismatch-1",
10_000,
);
ws.send(
JSON.stringify({
type: "req",
@@ -2444,14 +2454,15 @@ describe("gateway server", () => {
}),
);
const abortRes = await onceMessage(
ws,
(o) => o.type === "res" && o.id === "abort-mismatch-1",
10_000,
);
const abortRes = await abortResP;
expect(abortRes.ok).toBe(false);
expect(abortRes.error?.code).toBe("INVALID_REQUEST");
const abortRes2P = onceMessage(
ws,
(o) => o.type === "res" && o.id === "abort-mismatch-2",
10_000,
);
ws.send(
JSON.stringify({
type: "req",
@@ -2461,23 +2472,15 @@ describe("gateway server", () => {
}),
);
const abortRes2 = await onceMessage(
ws,
(o) => o.type === "res" && o.id === "abort-mismatch-2",
10_000,
);
const abortRes2 = await abortRes2P;
expect(abortRes2.ok).toBe(true);
const sendRes = await onceMessage(
ws,
(o) => o.type === "res" && o.id === "send-mismatch-1",
10_000,
);
const sendRes = await sendResP;
expect(sendRes.ok).toBe(true);
ws.close();
await server.close();
});
}, 15_000);
test("chat.abort is a no-op after chat.send completes", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-gw-"));