ci: fix swiftformat and bun CI
This commit is contained in:
@@ -149,4 +149,3 @@ struct AnthropicAuthControls: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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") }
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)")
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -31,4 +31,3 @@ struct GatewayDecodingError: LocalizedError, Sendable {
|
||||
|
||||
var errorDescription: String? { "\(self.method): \(self.message)" }
|
||||
}
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@ extension String {
|
||||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -102,4 +102,3 @@ final class WebChatManager {
|
||||
// Keep panel controller cached so reopening doesn't re-bootstrap.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
91
dist/protocol.schema.json
vendored
91
dist/protocol.schema.json
vendored
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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-"));
|
||||
|
||||
Reference in New Issue
Block a user