@ericsanchezok/meta-synergy 1.1.25 → 1.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/darwin/main.swift +120 -88
- package/package.json +1 -1
- package/src/cli-backend.ts +418 -0
- package/src/cli.ts +891 -48
- package/src/control/client.ts +85 -0
- package/src/control/schema.ts +68 -0
- package/src/control/server.ts +92 -0
- package/src/inbound/handler.ts +54 -44
- package/src/log.ts +20 -4
- package/src/runtime.ts +487 -138
- package/src/service/local.ts +170 -0
- package/src/service.ts +225 -0
- package/src/session/manager.ts +13 -3
- package/src/state/store.ts +205 -31
- package/test/control-socket.test.ts +60 -0
- package/test/runtime-approval.test.ts +78 -0
package/app/darwin/main.swift
CHANGED
|
@@ -1,12 +1,48 @@
|
|
|
1
1
|
import AppKit
|
|
2
2
|
import Foundation
|
|
3
3
|
|
|
4
|
+
private struct CLIEnvelope<Payload: Decodable>: Decodable {
|
|
5
|
+
let ok: Bool
|
|
6
|
+
let data: Payload?
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
private struct StatusPayload: Decodable {
|
|
10
|
+
let auth: AuthPayload
|
|
11
|
+
let state: StatePayload
|
|
12
|
+
let session: SessionPayload?
|
|
13
|
+
let envID: String?
|
|
14
|
+
let service: ServicePayload
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private struct AuthPayload: Decodable {
|
|
18
|
+
let loggedIn: Bool
|
|
19
|
+
let agentID: String?
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private struct StatePayload: Decodable {
|
|
23
|
+
let collaborationEnabled: Bool
|
|
24
|
+
let connectionStatus: String
|
|
25
|
+
let currentSession: SessionPayload?
|
|
26
|
+
let envID: String?
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private struct SessionPayload: Decodable {
|
|
30
|
+
let sessionID: String?
|
|
31
|
+
let remoteAgentID: String?
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private struct ServicePayload: Decodable {
|
|
35
|
+
let running: Bool
|
|
36
|
+
let runtimeStatus: String
|
|
37
|
+
let pid: Int?
|
|
38
|
+
}
|
|
39
|
+
|
|
4
40
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
5
41
|
private var statusItem: NSStatusItem!
|
|
6
42
|
private var menu: NSMenu!
|
|
7
|
-
private var hostProcess: Process?
|
|
8
43
|
private var pollTimer: Timer?
|
|
9
44
|
private var statusImage: NSImage?
|
|
45
|
+
private var lastStatus: StatusPayload?
|
|
10
46
|
|
|
11
47
|
private let agentItem = NSMenuItem(title: "Agent ID: unknown", action: #selector(copyAgentID), keyEquivalent: "")
|
|
12
48
|
private let envItem = NSMenuItem(title: "Env ID: unknown", action: #selector(copyEnvID), keyEquivalent: "")
|
|
@@ -19,7 +55,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
19
55
|
|
|
20
56
|
private var currentAgentID: String?
|
|
21
57
|
private var currentEnvID: String?
|
|
22
|
-
private var hostStarted = false
|
|
23
58
|
|
|
24
59
|
private lazy var runtimeURL: URL? = {
|
|
25
60
|
Bundle.main.resourceURL?.appendingPathComponent("meta-synergy-runtime")
|
|
@@ -57,7 +92,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
57
92
|
|
|
58
93
|
statusItem.menu = menu
|
|
59
94
|
|
|
60
|
-
|
|
95
|
+
startServiceIfPossible()
|
|
61
96
|
refreshStatus()
|
|
62
97
|
|
|
63
98
|
pollTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in
|
|
@@ -67,106 +102,110 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
67
102
|
|
|
68
103
|
@objc private func login() {
|
|
69
104
|
_ = runCommand(["login"])
|
|
70
|
-
|
|
105
|
+
startServiceIfPossible()
|
|
71
106
|
refreshStatus()
|
|
72
107
|
}
|
|
73
108
|
|
|
74
109
|
@objc private func reconnect() {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
let status = lastStatus ?? loadStatus()
|
|
111
|
+
let serviceRunning = status?.service.running ?? false
|
|
112
|
+
if serviceRunning {
|
|
78
113
|
_ = runCommand(["reconnect"])
|
|
114
|
+
} else {
|
|
115
|
+
startServiceIfPossible()
|
|
79
116
|
}
|
|
80
117
|
refreshStatus()
|
|
81
118
|
}
|
|
82
119
|
|
|
83
120
|
@objc private func refreshStatus() {
|
|
84
|
-
guard let
|
|
121
|
+
guard let status = loadStatus() else {
|
|
122
|
+
lastStatus = nil
|
|
123
|
+
currentAgentID = nil
|
|
124
|
+
currentEnvID = nil
|
|
125
|
+
agentItem.isHidden = true
|
|
126
|
+
envItem.isHidden = true
|
|
127
|
+
loginItem.isHidden = false
|
|
128
|
+
connectionItem.isHidden = false
|
|
85
129
|
connectionItem.title = "Holos: unavailable"
|
|
86
130
|
connectionItem.isEnabled = false
|
|
87
131
|
collaborationItem.isHidden = true
|
|
88
132
|
terminateItem.isHidden = true
|
|
89
|
-
refreshItem.isHidden =
|
|
133
|
+
refreshItem.isHidden = false
|
|
134
|
+
applyStatusAppearance(textFallback: "Meta", toolTip: "MetaSynergy")
|
|
90
135
|
return
|
|
91
136
|
}
|
|
92
137
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
138
|
+
lastStatus = status
|
|
139
|
+
|
|
140
|
+
let loggedIn = status.auth.loggedIn
|
|
141
|
+
let agentID = status.auth.agentID
|
|
142
|
+
let envID = status.envID ?? status.state.envID
|
|
143
|
+
let connectionStatus = status.state.connectionStatus
|
|
144
|
+
let collaborationEnabled = status.state.collaborationEnabled
|
|
145
|
+
let session = status.session ?? status.state.currentSession
|
|
146
|
+
let serviceRunning = status.service.running
|
|
147
|
+
let serviceTransitioning = status.service.runtimeStatus == "starting" || status.service.runtimeStatus == "stopping"
|
|
148
|
+
|
|
149
|
+
currentAgentID = loggedIn ? agentID : nil
|
|
150
|
+
currentEnvID = loggedIn ? envID : nil
|
|
101
151
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let agentSecret = auth?["agentSecret"] as? String
|
|
108
|
-
let hasAuth = !(agentID?.isEmpty ?? true) && !(agentSecret?.isEmpty ?? true)
|
|
109
|
-
currentAgentID = hasAuth ? agentID : nil
|
|
110
|
-
currentEnvID = hasAuth ? envID : nil
|
|
111
|
-
|
|
112
|
-
let connected = state?["connectionStatus"] as? String ?? "disconnected"
|
|
113
|
-
let collaborationEnabled = (state?["collaborationEnabled"] as? Bool) ?? true
|
|
114
|
-
let isConnected = connected == "connected"
|
|
115
|
-
|
|
116
|
-
agentItem.isHidden = !hasAuth
|
|
117
|
-
envItem.isHidden = !hasAuth
|
|
118
|
-
loginItem.isHidden = hasAuth
|
|
119
|
-
|
|
120
|
-
if hasAuth {
|
|
152
|
+
agentItem.isHidden = !loggedIn
|
|
153
|
+
envItem.isHidden = !loggedIn
|
|
154
|
+
loginItem.isHidden = loggedIn
|
|
155
|
+
|
|
156
|
+
if loggedIn {
|
|
121
157
|
agentItem.title = "Agent ID: \(truncateID(agentID))"
|
|
122
158
|
envItem.title = "Env ID: \(truncateID(envID))"
|
|
123
159
|
}
|
|
124
160
|
|
|
125
|
-
connectionItem.isHidden = !
|
|
126
|
-
collaborationItem.isHidden = !
|
|
127
|
-
terminateItem.isHidden = !
|
|
128
|
-
refreshItem.isHidden =
|
|
161
|
+
connectionItem.isHidden = !loggedIn
|
|
162
|
+
collaborationItem.isHidden = !loggedIn
|
|
163
|
+
terminateItem.isHidden = !loggedIn
|
|
164
|
+
refreshItem.isHidden = false
|
|
165
|
+
|
|
166
|
+
let connectionSummary = serviceRunning ? connectionStatus : "service \(status.service.runtimeStatus)"
|
|
167
|
+
connectionItem.title = "Holos: \(connectionSummary)"
|
|
168
|
+
connectionItem.isEnabled = loggedIn && !serviceTransitioning && (!serviceRunning || connectionStatus != "connected")
|
|
129
169
|
|
|
130
|
-
connectionItem.title = "Holos: \(connected)"
|
|
131
|
-
connectionItem.isEnabled = hasAuth && !isConnected
|
|
132
170
|
collaborationItem.title = collaborationEnabled ? "Collaboration: on" : "Collaboration: off"
|
|
133
|
-
collaborationItem.isEnabled =
|
|
171
|
+
collaborationItem.isEnabled = loggedIn
|
|
134
172
|
|
|
135
|
-
if let sessionID = session
|
|
136
|
-
let remoteAgentID = session
|
|
173
|
+
if let sessionID = session?.sessionID,
|
|
174
|
+
let remoteAgentID = session?.remoteAgentID {
|
|
137
175
|
terminateItem.title = "Terminate Collaboration (\(truncateID(remoteAgentID)), \(sessionID.prefix(8)))"
|
|
138
176
|
terminateItem.isEnabled = true
|
|
139
177
|
applyStatusAppearance(textFallback: "Meta Busy", toolTip: "MetaSynergy: busy")
|
|
140
178
|
} else {
|
|
141
179
|
terminateItem.title = "Terminate Collaboration"
|
|
142
180
|
terminateItem.isEnabled = false
|
|
143
|
-
|
|
181
|
+
|
|
182
|
+
if !loggedIn {
|
|
183
|
+
applyStatusAppearance(textFallback: "Meta", toolTip: "MetaSynergy")
|
|
184
|
+
} else if !serviceRunning {
|
|
185
|
+
applyStatusAppearance(textFallback: "Meta", toolTip: "MetaSynergy: service \(status.service.runtimeStatus)")
|
|
186
|
+
} else {
|
|
144
187
|
applyStatusAppearance(
|
|
145
188
|
textFallback: collaborationEnabled ? "Meta" : "Meta Off",
|
|
146
|
-
toolTip: collaborationEnabled
|
|
189
|
+
toolTip: collaborationEnabled
|
|
190
|
+
? "MetaSynergy: Holos \(connectionStatus)"
|
|
191
|
+
: "MetaSynergy: collaboration off"
|
|
147
192
|
)
|
|
148
|
-
} else {
|
|
149
|
-
applyStatusAppearance(textFallback: "Meta", toolTip: "MetaSynergy")
|
|
150
193
|
}
|
|
151
194
|
}
|
|
152
195
|
}
|
|
153
196
|
|
|
154
197
|
@objc private func toggleCollaboration() {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
158
|
-
let state = json["state"] as? [String: Any],
|
|
159
|
-
let enabled = state["collaborationEnabled"] as? Bool
|
|
160
|
-
else {
|
|
198
|
+
let enabled = lastStatus?.state.collaborationEnabled ?? loadStatus()?.state.collaborationEnabled
|
|
199
|
+
guard let enabled else {
|
|
161
200
|
return
|
|
162
201
|
}
|
|
163
202
|
|
|
164
|
-
_ = runCommand([enabled ? "disable" : "enable"])
|
|
203
|
+
_ = runCommand(["collaboration", enabled ? "disable" : "enable"])
|
|
165
204
|
refreshStatus()
|
|
166
205
|
}
|
|
167
206
|
|
|
168
207
|
@objc private func terminateCollaboration() {
|
|
169
|
-
_ = runCommand(["kick"])
|
|
208
|
+
_ = runCommand(["session", "kick"])
|
|
170
209
|
refreshStatus()
|
|
171
210
|
}
|
|
172
211
|
|
|
@@ -179,45 +218,21 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
179
218
|
}
|
|
180
219
|
|
|
181
220
|
@objc private func quitApp() {
|
|
182
|
-
|
|
221
|
+
_ = runCommand(["stop"])
|
|
183
222
|
NSApplication.shared.terminate(nil)
|
|
184
223
|
}
|
|
185
224
|
|
|
186
|
-
private func
|
|
187
|
-
guard hostProcess == nil, !hostStarted else { return }
|
|
225
|
+
private func startServiceIfPossible() {
|
|
188
226
|
guard hasAuth() else { return }
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
process.executableURL = runtimeURL
|
|
193
|
-
process.arguments = ["start"]
|
|
194
|
-
process.terminationHandler = { [weak self] _ in
|
|
195
|
-
DispatchQueue.main.async {
|
|
196
|
-
self?.hostStarted = false
|
|
197
|
-
self?.hostProcess = nil
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
do {
|
|
202
|
-
try process.run()
|
|
203
|
-
hostProcess = process
|
|
204
|
-
hostStarted = true
|
|
205
|
-
} catch {
|
|
206
|
-
connectionItem.title = "Holos: failed to start host"
|
|
227
|
+
let status = lastStatus ?? loadStatus()
|
|
228
|
+
if status?.service.running == true {
|
|
229
|
+
return
|
|
207
230
|
}
|
|
231
|
+
_ = runCommand(["start"])
|
|
208
232
|
}
|
|
209
233
|
|
|
210
234
|
private func hasAuth() -> Bool {
|
|
211
|
-
|
|
212
|
-
let data = output.data(using: .utf8),
|
|
213
|
-
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
214
|
-
let auth = json["auth"] as? [String: Any],
|
|
215
|
-
let agentID = auth["agentID"] as? String,
|
|
216
|
-
let agentSecret = auth["agentSecret"] as? String
|
|
217
|
-
else {
|
|
218
|
-
return false
|
|
219
|
-
}
|
|
220
|
-
return !agentID.isEmpty && !agentSecret.isEmpty
|
|
235
|
+
(lastStatus ?? loadStatus())?.auth.loggedIn == true
|
|
221
236
|
}
|
|
222
237
|
|
|
223
238
|
@discardableResult
|
|
@@ -241,6 +256,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
241
256
|
}
|
|
242
257
|
}
|
|
243
258
|
|
|
259
|
+
private func loadStatus() -> StatusPayload? {
|
|
260
|
+
runJSONCommand(["status"], as: StatusPayload.self)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private func runJSONCommand<Payload: Decodable>(_ args: [String], as type: Payload.Type) -> Payload? {
|
|
264
|
+
guard let output = runCommand(args + ["--json"]),
|
|
265
|
+
let data = output.data(using: .utf8),
|
|
266
|
+
let envelope = try? JSONDecoder().decode(CLIEnvelope<Payload>.self, from: data),
|
|
267
|
+
envelope.ok,
|
|
268
|
+
let payload = envelope.data
|
|
269
|
+
else {
|
|
270
|
+
return nil
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return payload
|
|
274
|
+
}
|
|
275
|
+
|
|
244
276
|
private func copyValue(_ value: String?) {
|
|
245
277
|
guard let value, !value.isEmpty else { return }
|
|
246
278
|
let pasteboard = NSPasteboard.general
|