ci: fix swiftformat and bun CI
parent
2f21b94a76
commit
5c705ab675
|
|
@ -149,4 +149,3 @@ struct AnthropicAuthControls: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,11 @@ final class CanvasManager {
|
||||||
try self.showDetailed(sessionKey: sessionKey, target: path, placement: placement).directory
|
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(
|
Self.logger.debug(
|
||||||
"showDetailed start session=\(sessionKey, privacy: .public) target=\(target ?? "", privacy: .public) placement=\(placement != nil)")
|
"showDetailed start session=\(sessionKey, privacy: .public) target=\(target ?? "", privacy: .public) placement=\(placement != nil)")
|
||||||
let anchorProvider = self.defaultAnchorProvider ?? Self.mouseAnchorProvider
|
let anchorProvider = self.defaultAnchorProvider ?? Self.mouseAnchorProvider
|
||||||
|
|
@ -177,7 +181,8 @@ final class CanvasManager {
|
||||||
private static func localStatus(sessionDir: URL, target: String) -> CanvasShowStatus {
|
private static func localStatus(sessionDir: URL, target: String) -> CanvasShowStatus {
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
let trimmed = target.trimmingCharacters(in: .whitespacesAndNewlines)
|
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
|
var path = withoutQuery
|
||||||
if path.hasPrefix("/") { path.removeFirst() }
|
if path.hasPrefix("/") { path.removeFirst() }
|
||||||
path = path.removingPercentEncoding ?? path
|
path = path.removingPercentEncoding ?? path
|
||||||
|
|
|
||||||
|
|
@ -222,11 +222,10 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
|
||||||
|| trimmed.hasPrefix(Self.builtinPrefix + "/")
|
|| trimmed.hasPrefix(Self.builtinPrefix + "/")
|
||||||
else { return nil }
|
else { return nil }
|
||||||
|
|
||||||
let relative: String
|
let relative = if trimmed == Self.builtinPrefix || trimmed == Self.builtinPrefix + "/" {
|
||||||
if trimmed == Self.builtinPrefix || trimmed == Self.builtinPrefix + "/" {
|
"index.html"
|
||||||
relative = "index.html"
|
|
||||||
} else {
|
} 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") }
|
if relative.isEmpty { return self.html("Not Found", title: "Canvas: 404") }
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
|
||||||
// Only auto-reload when we are showing local canvas content.
|
// Only auto-reload when we are showing local canvas content.
|
||||||
guard webView.url?.scheme == CanvasScheme.scheme else { return }
|
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 ?? ""
|
let path = webView.url?.path ?? ""
|
||||||
if path.hasPrefix("/__clawdis__/a2ui") { return }
|
if path.hasPrefix("/__clawdis__/a2ui") { return }
|
||||||
if path == "/" || path.isEmpty {
|
if path == "/" || path.isEmpty {
|
||||||
|
|
@ -200,7 +201,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
|
||||||
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
||||||
|
|
||||||
@MainActor deinit {
|
@MainActor deinit {
|
||||||
self.webView.configuration.userContentController.removeScriptMessageHandler(forName: CanvasA2UIActionMessageHandler.messageName)
|
self.webView.configuration.userContentController
|
||||||
|
.removeScriptMessageHandler(forName: CanvasA2UIActionMessageHandler.messageName)
|
||||||
self.watcher.stop()
|
self.watcher.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,7 +503,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
|
||||||
if self.webView.url?.scheme == CanvasScheme.scheme {
|
if self.webView.url?.scheme == CanvasScheme.scheme {
|
||||||
Task { await DeepLinkHandler.shared.handle(url: url) }
|
Task { await DeepLinkHandler.shared.handle(url: url) }
|
||||||
} else {
|
} 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)
|
decisionHandler(.cancel)
|
||||||
return
|
return
|
||||||
|
|
@ -639,8 +642,10 @@ private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHan
|
||||||
|
|
||||||
canvasWindowLogger.info("A2UI action \(name, privacy: .public) session=\(self.sessionKey, privacy: .public)")
|
canvasWindowLogger.info("A2UI action \(name, privacy: .public) session=\(self.sessionKey, privacy: .public)")
|
||||||
|
|
||||||
let surfaceId = (userAction["surfaceId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "main"
|
let surfaceId = (userAction["surfaceId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let sourceComponentId = (userAction["sourceComponentId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "-"
|
.nonEmpty ?? "main"
|
||||||
|
let sourceComponentId = (userAction["sourceComponentId"] as? String)?
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "-"
|
||||||
let host = Self.sanitizeTagValue(InstanceIdentity.displayName)
|
let host = Self.sanitizeTagValue(InstanceIdentity.displayName)
|
||||||
let instanceId = InstanceIdentity.instanceId.lowercased()
|
let instanceId = InstanceIdentity.instanceId.lowercased()
|
||||||
let contextJSON = Self.compactJSON(userAction["context"])
|
let contextJSON = Self.compactJSON(userAction["context"])
|
||||||
|
|
|
||||||
|
|
@ -237,8 +237,13 @@ enum ControlRequestHandler {
|
||||||
logger.info("canvas show start session=\(session, privacy: .public) path=\(path ?? "", privacy: .public)")
|
logger.info("canvas show start session=\(session, privacy: .public) path=\(path ?? "", privacy: .public)")
|
||||||
do {
|
do {
|
||||||
logger.info("canvas show awaiting CanvasManager")
|
logger.info("canvas show awaiting CanvasManager")
|
||||||
let res = try await CanvasManager.shared.showDetailed(sessionKey: session, target: path, placement: placement)
|
let res = try await CanvasManager.shared.showDetailed(
|
||||||
logger.info("canvas show done dir=\(res.directory, privacy: .public) status=\(String(describing: res.status), privacy: .public)")
|
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)
|
let payload = try? JSONEncoder().encode(res)
|
||||||
return Response(ok: true, message: res.directory, payload: payload)
|
return Response(ok: true, message: res.directory, payload: payload)
|
||||||
} catch {
|
} 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") }
|
guard self.canvasEnabled() else { return Response(ok: false, message: "Canvas disabled by user") }
|
||||||
do {
|
do {
|
||||||
// Ensure the Canvas is visible without forcing a navigation/reload.
|
// Ensure the Canvas is visible without forcing a navigation/reload.
|
||||||
_ = try await CanvasManager.shared.show(sessionKey: session, path: nil)
|
_ = 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.
|
// 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 {
|
if !ready {
|
||||||
_ = try await CanvasManager.shared.show(sessionKey: session, path: "/__clawdis__/a2ui/")
|
_ = 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") }
|
guard ready else { return Response(ok: false, message: "A2UI not ready") }
|
||||||
|
|
@ -397,7 +406,8 @@ enum ControlRequestHandler {
|
||||||
let found = dict.keys.sorted().joined(separator: ", ")
|
let found = dict.keys.sorted().joined(separator: ", ")
|
||||||
throw NSError(domain: "A2UI", code: 3, userInfo: [
|
throw NSError(domain: "A2UI", code: 3, userInfo: [
|
||||||
NSLocalizedDescriptionKey: """
|
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(
|
let data = try await GatewayConnection.shared.request(
|
||||||
method: "node.list",
|
method: "node.list",
|
||||||
params: [:],
|
params: [:],
|
||||||
timeoutMs: 10_000)
|
timeoutMs: 10000)
|
||||||
let payload = try JSONDecoder().decode(GatewayNodeListPayload.self, from: data)
|
let payload = try JSONDecoder().decode(GatewayNodeListPayload.self, from: data)
|
||||||
let result = self.buildNodeListResult(payload: payload)
|
let result = self.buildNodeListResult(payload: payload)
|
||||||
|
|
||||||
|
|
@ -460,7 +470,7 @@ enum ControlRequestHandler {
|
||||||
let data = try await GatewayConnection.shared.request(
|
let data = try await GatewayConnection.shared.request(
|
||||||
method: "node.describe",
|
method: "node.describe",
|
||||||
params: ["nodeId": AnyCodable(nodeId)],
|
params: ["nodeId": AnyCodable(nodeId)],
|
||||||
timeoutMs: 10_000)
|
timeoutMs: 10000)
|
||||||
return Response(ok: true, payload: data)
|
return Response(ok: true, payload: data)
|
||||||
} catch {
|
} catch {
|
||||||
return Response(ok: false, message: error.localizedDescription)
|
return Response(ok: false, message: error.localizedDescription)
|
||||||
|
|
@ -526,7 +536,7 @@ enum ControlRequestHandler {
|
||||||
let data = try await GatewayConnection.shared.request(
|
let data = try await GatewayConnection.shared.request(
|
||||||
method: "node.invoke",
|
method: "node.invoke",
|
||||||
params: params,
|
params: params,
|
||||||
timeoutMs: 30_000)
|
timeoutMs: 30000)
|
||||||
return Response(ok: true, payload: data)
|
return Response(ok: true, payload: data)
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("node invoke failed: \(error.localizedDescription, privacy: .public)")
|
logger.error("node invoke failed: \(error.localizedDescription, privacy: .public)")
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ enum DeviceModelCatalog {
|
||||||
let family = (deviceFamily ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
let family = (deviceFamily ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let model = (modelIdentifier ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
let model = (modelIdentifier ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
let friendlyName = model.isEmpty ? nil : modelIdentifierToName[model]
|
let friendlyName = model.isEmpty ? nil : self.modelIdentifierToName[model]
|
||||||
let symbol = symbolFor(modelIdentifier: model, friendlyName: friendlyName)
|
let symbol = self.symbolFor(modelIdentifier: model, friendlyName: friendlyName)
|
||||||
?? fallbackSymbol(for: family, modelIdentifier: model)
|
?? self.fallbackSymbol(for: family, modelIdentifier: model)
|
||||||
|
|
||||||
let title = if let friendlyName, !friendlyName.isEmpty {
|
let title = if let friendlyName, !friendlyName.isEmpty {
|
||||||
friendlyName
|
friendlyName
|
||||||
|
|
@ -47,7 +47,9 @@ enum DeviceModelCatalog {
|
||||||
if lower.hasPrefix("macbook") || lower.hasPrefix("macbookpro") || lower.hasPrefix("macbookair") {
|
if lower.hasPrefix("macbook") || lower.hasPrefix("macbookpro") || lower.hasPrefix("macbookair") {
|
||||||
return "laptopcomputer"
|
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"
|
return "desktopcomputer"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,8 +86,12 @@ enum DeviceModelCatalog {
|
||||||
|
|
||||||
private static func loadModelIdentifierToName() -> [String: String] {
|
private static func loadModelIdentifierToName() -> [String: String] {
|
||||||
var combined: [String: String] = [:]
|
var combined: [String: String] = [:]
|
||||||
combined.merge(loadMapping(resourceName: "ios-device-identifiers"), uniquingKeysWith: { current, _ in current })
|
combined.merge(
|
||||||
combined.merge(loadMapping(resourceName: "mac-device-identifiers"), uniquingKeysWith: { current, _ in current })
|
self.loadMapping(resourceName: "ios-device-identifiers"),
|
||||||
|
uniquingKeysWith: { current, _ in current })
|
||||||
|
combined.merge(
|
||||||
|
self.loadMapping(resourceName: "mac-device-identifiers"),
|
||||||
|
uniquingKeysWith: { current, _ in current })
|
||||||
return combined
|
return combined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,10 +134,10 @@ enum DeviceModelCatalog {
|
||||||
|
|
||||||
var normalizedName: String? {
|
var normalizedName: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .string(let s):
|
case let .string(s):
|
||||||
let trimmed = s.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = s.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
return trimmed.isEmpty ? nil : trimmed
|
return trimmed.isEmpty ? nil : trimmed
|
||||||
case .stringArray(let arr):
|
case let .stringArray(arr):
|
||||||
let values = arr
|
let values = arr
|
||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
.filter { !$0.isEmpty }
|
.filter { !$0.isEmpty }
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,11 @@ actor GatewayConnection {
|
||||||
typealias Config = (url: URL, token: String?)
|
typealias Config = (url: URL, token: String?)
|
||||||
|
|
||||||
enum Method: String, Sendable {
|
enum Method: String, Sendable {
|
||||||
case agent = "agent"
|
case agent
|
||||||
case status = "status"
|
case status
|
||||||
case setHeartbeats = "set-heartbeats"
|
case setHeartbeats = "set-heartbeats"
|
||||||
case systemEvent = "system-event"
|
case systemEvent = "system-event"
|
||||||
case health = "health"
|
case health
|
||||||
case chatHistory = "chat.history"
|
case chatHistory = "chat.history"
|
||||||
case chatSend = "chat.send"
|
case chatSend = "chat.send"
|
||||||
case chatAbort = "chat.abort"
|
case chatAbort = "chat.abort"
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,3 @@ struct GatewayDecodingError: LocalizedError, Sendable {
|
||||||
|
|
||||||
var errorDescription: String? { "\(self.method): \(self.message)" }
|
var errorDescription: String? { "\(self.method): \(self.message)" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,3 @@ extension String {
|
||||||
return trimmed.isEmpty ? nil : trimmed
|
return trimmed.isEmpty ? nil : trimmed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ final class VoicePushToTalkHotkey: @unchecked Sendable {
|
||||||
|
|
||||||
init(
|
init(
|
||||||
beginAction: @escaping @Sendable () async -> Void = { await VoicePushToTalk.shared.begin() },
|
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.beginAction = beginAction
|
||||||
self.endAction = endAction
|
self.endAction = endAction
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,4 +102,3 @@ final class WebChatManager {
|
||||||
// Keep panel controller cached so reopening doesn't re-bootstrap.
|
// Keep panel controller cached so reopening doesn't re-bootstrap.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ struct MacGatewayChatTransport: ClawdisChatTransport, Sendable {
|
||||||
"sessionKey": AnyCodable(sessionKey),
|
"sessionKey": AnyCodable(sessionKey),
|
||||||
"runId": AnyCodable(runId),
|
"runId": AnyCodable(runId),
|
||||||
],
|
],
|
||||||
timeoutMs: 10_000)
|
timeoutMs: 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listSessions(limit: Int?) async throws -> ClawdisChatSessionsListResponse {
|
func listSessions(limit: Int?) async throws -> ClawdisChatSessionsListResponse {
|
||||||
|
|
@ -39,7 +39,7 @@ struct MacGatewayChatTransport: ClawdisChatTransport, Sendable {
|
||||||
let data = try await GatewayConnection.shared.request(
|
let data = try await GatewayConnection.shared.request(
|
||||||
method: "sessions.list",
|
method: "sessions.list",
|
||||||
params: params,
|
params: params,
|
||||||
timeoutMs: 15_000)
|
timeoutMs: 15000)
|
||||||
return try JSONDecoder().decode(ClawdisChatSessionsListResponse.self, from: data)
|
return try JSONDecoder().decode(ClawdisChatSessionsListResponse.self, from: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ struct ClawdisCLI {
|
||||||
return ParsedCLIRequest(
|
return ParsedCLIRequest(
|
||||||
request: .nodeInvoke(nodeId: nodeId, command: command, paramsJSON: paramsJSON),
|
request: .nodeInvoke(nodeId: nodeId, command: command, paramsJSON: paramsJSON),
|
||||||
kind: .generic)
|
kind: .generic)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw CLIError.help
|
throw CLIError.help
|
||||||
}
|
}
|
||||||
|
|
@ -450,10 +451,13 @@ struct ClawdisCLI {
|
||||||
if let message = response.message, !message.isEmpty {
|
if let message = response.message, !message.isEmpty {
|
||||||
FileHandle.standardOutput.write(Data((message + "\n").utf8))
|
FileHandle.standardOutput.write(Data((message + "\n").utf8))
|
||||||
}
|
}
|
||||||
if let payload = response.payload, let info = try? JSONDecoder().decode(CanvasShowResult.self, from: payload) {
|
if let payload = response.payload, let info = try? JSONDecoder().decode(
|
||||||
FileHandle.standardOutput.write(Data(("STATUS:\(info.status.rawValue)\n").utf8))
|
CanvasShowResult.self,
|
||||||
|
from: payload)
|
||||||
|
{
|
||||||
|
FileHandle.standardOutput.write(Data("STATUS:\(info.status.rawValue)\n".utf8))
|
||||||
if let url = info.url, !url.isEmpty {
|
if let url = info.url, !url.isEmpty {
|
||||||
FileHandle.standardOutput.write(Data(("URL:\(url)\n").utf8))
|
FileHandle.standardOutput.write(Data("URL:\(url)\n".utf8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
@ -515,7 +519,7 @@ struct ClawdisCLI {
|
||||||
let version = (n.version ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
let version = (n.version ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if !platform.isEmpty || !version.isEmpty {
|
if !platform.isEmpty || !version.isEmpty {
|
||||||
let pv = [platform.isEmpty ? nil : platform, version.isEmpty ? nil : version]
|
let pv = [platform.isEmpty ? nil : platform, version.isEmpty ? nil : version]
|
||||||
.compactMap { $0 }
|
.compactMap(\.self)
|
||||||
.joined(separator: " ")
|
.joined(separator: " ")
|
||||||
if !pv.isEmpty { print(" platform: \(pv)") }
|
if !pv.isEmpty { print(" platform: \(pv)") }
|
||||||
}
|
}
|
||||||
|
|
@ -571,7 +575,9 @@ struct ClawdisCLI {
|
||||||
print(parts.joined(separator: " · "))
|
print(parts.joined(separator: " · "))
|
||||||
if !commands.isEmpty {
|
if !commands.isEmpty {
|
||||||
print("Commands:")
|
print("Commands:")
|
||||||
for c in commands { print("- \(c)") }
|
for c in commands {
|
||||||
|
print("- \(c)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -420,6 +420,10 @@ public struct NodePairRequestParams: Codable {
|
||||||
public let displayname: String?
|
public let displayname: String?
|
||||||
public let platform: String?
|
public let platform: String?
|
||||||
public let version: 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 let remoteip: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
|
@ -427,12 +431,20 @@ public struct NodePairRequestParams: Codable {
|
||||||
displayname: String?,
|
displayname: String?,
|
||||||
platform: String?,
|
platform: String?,
|
||||||
version: String?,
|
version: String?,
|
||||||
|
devicefamily: String?,
|
||||||
|
modelidentifier: String?,
|
||||||
|
caps: [String]?,
|
||||||
|
commands: [String]?,
|
||||||
remoteip: String?
|
remoteip: String?
|
||||||
) {
|
) {
|
||||||
self.nodeid = nodeid
|
self.nodeid = nodeid
|
||||||
self.displayname = displayname
|
self.displayname = displayname
|
||||||
self.platform = platform
|
self.platform = platform
|
||||||
self.version = version
|
self.version = version
|
||||||
|
self.devicefamily = devicefamily
|
||||||
|
self.modelidentifier = modelidentifier
|
||||||
|
self.caps = caps
|
||||||
|
self.commands = commands
|
||||||
self.remoteip = remoteip
|
self.remoteip = remoteip
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
|
@ -440,6 +452,10 @@ public struct NodePairRequestParams: Codable {
|
||||||
case displayname = "displayName"
|
case displayname = "displayName"
|
||||||
case platform
|
case platform
|
||||||
case version
|
case version
|
||||||
|
case devicefamily = "deviceFamily"
|
||||||
|
case modelidentifier = "modelIdentifier"
|
||||||
|
case caps
|
||||||
|
case commands
|
||||||
case remoteip = "remoteIp"
|
case remoteip = "remoteIp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -493,6 +509,19 @@ public struct NodePairVerifyParams: Codable {
|
||||||
public struct NodeListParams: 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 struct NodeInvokeParams: Codable {
|
||||||
public let nodeid: String
|
public let nodeid: String
|
||||||
public let command: 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 struct ChatEvent: Codable {
|
||||||
public let runid: String
|
public let runid: String
|
||||||
public let sessionkey: String
|
public let sessionkey: String
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,14 @@
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"deviceFamily": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modelIdentifier": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -185,6 +193,14 @@
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"deviceFamily": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modelIdentifier": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -538,6 +554,14 @@
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"deviceFamily": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modelIdentifier": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -617,6 +641,14 @@
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"deviceFamily": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modelIdentifier": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -860,6 +892,28 @@
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"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": {
|
"remoteIp": {
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -923,6 +977,19 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
},
|
},
|
||||||
|
"NodeDescribeParams": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nodeId": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nodeId"
|
||||||
|
]
|
||||||
|
},
|
||||||
"NodeInvokeParams": {
|
"NodeInvokeParams": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -1773,7 +1840,7 @@
|
||||||
},
|
},
|
||||||
"limit": {
|
"limit": {
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 500,
|
"maximum": 1000,
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1818,6 +1885,24 @@
|
||||||
"idempotencyKey"
|
"idempotencyKey"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ChatAbortParams": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"sessionKey": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"runId": {
|
||||||
|
"minLength": 1,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"sessionKey",
|
||||||
|
"runId"
|
||||||
|
]
|
||||||
|
},
|
||||||
"ChatEvent": {
|
"ChatEvent": {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -1844,6 +1929,10 @@
|
||||||
"const": "final",
|
"const": "final",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"const": "aborted",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"const": "error",
|
"const": "error",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grammyjs/transformer-throttler": "^1.2.1",
|
"@grammyjs/transformer-throttler": "^1.2.1",
|
||||||
"@homebridge/ciao": "^1.3.4",
|
"@homebridge/ciao": "^1.3.4",
|
||||||
|
"@mariozechner/pi-agent-core": "^0.21.0",
|
||||||
"@mariozechner/pi-ai": "^0.21.0",
|
"@mariozechner/pi-ai": "^0.21.0",
|
||||||
"@mariozechner/pi-coding-agent": "^0.21.0",
|
"@mariozechner/pi-coding-agent": "^0.21.0",
|
||||||
"@sinclair/typebox": "^0.34.41",
|
"@sinclair/typebox": "^0.34.41",
|
||||||
|
|
|
||||||
|
|
@ -2419,6 +2419,11 @@ describe("gateway server", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sendResP = onceMessage(
|
||||||
|
ws,
|
||||||
|
(o) => o.type === "res" && o.id === "send-mismatch-1",
|
||||||
|
10_000,
|
||||||
|
);
|
||||||
ws.send(
|
ws.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "req",
|
type: "req",
|
||||||
|
|
@ -2435,6 +2440,11 @@ describe("gateway server", () => {
|
||||||
|
|
||||||
await agentStartedP;
|
await agentStartedP;
|
||||||
|
|
||||||
|
const abortResP = onceMessage(
|
||||||
|
ws,
|
||||||
|
(o) => o.type === "res" && o.id === "abort-mismatch-1",
|
||||||
|
10_000,
|
||||||
|
);
|
||||||
ws.send(
|
ws.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "req",
|
type: "req",
|
||||||
|
|
@ -2444,14 +2454,15 @@ describe("gateway server", () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const abortRes = await onceMessage(
|
const abortRes = await abortResP;
|
||||||
ws,
|
|
||||||
(o) => o.type === "res" && o.id === "abort-mismatch-1",
|
|
||||||
10_000,
|
|
||||||
);
|
|
||||||
expect(abortRes.ok).toBe(false);
|
expect(abortRes.ok).toBe(false);
|
||||||
expect(abortRes.error?.code).toBe("INVALID_REQUEST");
|
expect(abortRes.error?.code).toBe("INVALID_REQUEST");
|
||||||
|
|
||||||
|
const abortRes2P = onceMessage(
|
||||||
|
ws,
|
||||||
|
(o) => o.type === "res" && o.id === "abort-mismatch-2",
|
||||||
|
10_000,
|
||||||
|
);
|
||||||
ws.send(
|
ws.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
type: "req",
|
type: "req",
|
||||||
|
|
@ -2461,23 +2472,15 @@ describe("gateway server", () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const abortRes2 = await onceMessage(
|
const abortRes2 = await abortRes2P;
|
||||||
ws,
|
|
||||||
(o) => o.type === "res" && o.id === "abort-mismatch-2",
|
|
||||||
10_000,
|
|
||||||
);
|
|
||||||
expect(abortRes2.ok).toBe(true);
|
expect(abortRes2.ok).toBe(true);
|
||||||
|
|
||||||
const sendRes = await onceMessage(
|
const sendRes = await sendResP;
|
||||||
ws,
|
|
||||||
(o) => o.type === "res" && o.id === "send-mismatch-1",
|
|
||||||
10_000,
|
|
||||||
);
|
|
||||||
expect(sendRes.ok).toBe(true);
|
expect(sendRes.ok).toBe(true);
|
||||||
|
|
||||||
ws.close();
|
ws.close();
|
||||||
await server.close();
|
await server.close();
|
||||||
});
|
}, 15_000);
|
||||||
|
|
||||||
test("chat.abort is a no-op after chat.send completes", async () => {
|
test("chat.abort is a no-op after chat.send completes", async () => {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-gw-"));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-gw-"));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue