@ericsanchezok/meta-synergy 1.1.7 → 1.1.9
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/package.json +1 -1
- package/src/holos/client.ts +12 -0
- package/src/holos/login.ts +179 -3
- package/src/inbound/handler.ts +2 -8
- package/src/rpc/handler.ts +1 -3
- package/src/rpc/schema.ts +6 -1
- package/src/runtime.ts +112 -14
- package/src/session/manager.ts +4 -1
package/package.json
CHANGED
package/src/holos/client.ts
CHANGED
|
@@ -12,13 +12,19 @@ const HOLOS_WS_URL = `wss://${HOLOS_HOST}`
|
|
|
12
12
|
export class MetaSynergyHolosClient {
|
|
13
13
|
#ws: WebSocket | null = null
|
|
14
14
|
#heartbeat: ReturnType<typeof setInterval> | null = null
|
|
15
|
+
#disconnecting = false
|
|
15
16
|
|
|
16
17
|
constructor(
|
|
17
18
|
readonly auth: { agentID: string; agentSecret: string },
|
|
18
19
|
readonly inbound: MetaSynergyInboundHandler,
|
|
20
|
+
readonly hooks?: {
|
|
21
|
+
onOpen?: () => void | Promise<void>
|
|
22
|
+
onClose?: (input: { opened: boolean; intentional: boolean }) => void | Promise<void>
|
|
23
|
+
},
|
|
19
24
|
) {}
|
|
20
25
|
|
|
21
26
|
async connect() {
|
|
27
|
+
this.#disconnecting = false
|
|
22
28
|
const token = await fetchWsToken(this.auth.agentSecret)
|
|
23
29
|
const endpoint = `${HOLOS_WS_URL}/api/v1/holos/agent_tunnel/ws?token=${token}`
|
|
24
30
|
MetaSynergyLog.info("holos.connect.begin", {
|
|
@@ -35,6 +41,7 @@ export class MetaSynergyHolosClient {
|
|
|
35
41
|
MetaSynergyLog.info("holos.connect.open", {
|
|
36
42
|
agentID: this.auth.agentID,
|
|
37
43
|
})
|
|
44
|
+
void this.hooks?.onOpen?.()
|
|
38
45
|
resolve()
|
|
39
46
|
})
|
|
40
47
|
ws.addEventListener("error", (error) => {
|
|
@@ -58,10 +65,14 @@ export class MetaSynergyHolosClient {
|
|
|
58
65
|
ws.addEventListener("close", () => {
|
|
59
66
|
MetaSynergyLog.warn("holos.socket.closed", {
|
|
60
67
|
agentID: this.auth.agentID,
|
|
68
|
+
intentional: this.#disconnecting,
|
|
61
69
|
})
|
|
62
70
|
if (this.#heartbeat) clearInterval(this.#heartbeat)
|
|
63
71
|
this.#heartbeat = null
|
|
64
72
|
this.#ws = null
|
|
73
|
+
const intentional = this.#disconnecting
|
|
74
|
+
this.#disconnecting = false
|
|
75
|
+
void this.hooks?.onClose?.({ opened: true, intentional })
|
|
65
76
|
})
|
|
66
77
|
|
|
67
78
|
this.#heartbeat = setInterval(() => {
|
|
@@ -78,6 +89,7 @@ export class MetaSynergyHolosClient {
|
|
|
78
89
|
})
|
|
79
90
|
if (this.#heartbeat) clearInterval(this.#heartbeat)
|
|
80
91
|
this.#heartbeat = null
|
|
92
|
+
this.#disconnecting = true
|
|
81
93
|
this.#ws?.close()
|
|
82
94
|
this.#ws = null
|
|
83
95
|
}
|
package/src/holos/login.ts
CHANGED
|
@@ -22,7 +22,7 @@ export namespace MetaSynergyHolosLogin {
|
|
|
22
22
|
})
|
|
23
23
|
const body = MetaSynergyHolosProtocol.WsTokenResponse.safeParse(await response.json())
|
|
24
24
|
if (!body.success || !response.ok || body.data.code !== 0) {
|
|
25
|
-
return { valid: false, reason: body.success ? body.data.message ?? "Invalid response" : "Invalid response" }
|
|
25
|
+
return { valid: false, reason: body.success ? (body.data.message ?? "Invalid response") : "Invalid response" }
|
|
26
26
|
}
|
|
27
27
|
return { valid: true }
|
|
28
28
|
}
|
|
@@ -47,12 +47,28 @@ export namespace MetaSynergyHolosLogin {
|
|
|
47
47
|
if (!params.code || !params.state) {
|
|
48
48
|
reject(new Error("Missing login callback parameters."))
|
|
49
49
|
response.statusCode = 400
|
|
50
|
-
response.
|
|
50
|
+
response.setHeader("Content-Type", "text/html; charset=utf-8")
|
|
51
|
+
response.end(
|
|
52
|
+
htmlPage({
|
|
53
|
+
title: "MetaSynergy Login",
|
|
54
|
+
status: "failed",
|
|
55
|
+
heading: "Login failed",
|
|
56
|
+
message: "Missing callback parameters. Please return to MetaSynergy and try again.",
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
51
59
|
return
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
resolve({ code: params.code, state: params.state })
|
|
55
|
-
response.
|
|
63
|
+
response.setHeader("Content-Type", "text/html; charset=utf-8")
|
|
64
|
+
response.end(
|
|
65
|
+
htmlPage({
|
|
66
|
+
title: "MetaSynergy Login",
|
|
67
|
+
status: "success",
|
|
68
|
+
heading: "MetaSynergy is ready",
|
|
69
|
+
message: "Login successful. You can close this page and return to the app.",
|
|
70
|
+
}),
|
|
71
|
+
)
|
|
56
72
|
})
|
|
57
73
|
server.listen(port, "127.0.0.1")
|
|
58
74
|
|
|
@@ -126,3 +142,163 @@ async function launchBrowser(url: string): Promise<void> {
|
|
|
126
142
|
const child = spawn(command[0], command.slice(1), { stdio: "ignore", detached: true })
|
|
127
143
|
child.unref()
|
|
128
144
|
}
|
|
145
|
+
|
|
146
|
+
function htmlPage(input: { title: string; status: "success" | "failed"; heading: string; message: string }): string {
|
|
147
|
+
const accent = input.status === "success" ? "#86efac" : "#fca5a5"
|
|
148
|
+
const badge = input.status === "success" ? "Connected" : "Error"
|
|
149
|
+
const symbol = input.status === "success" ? "✓" : "!"
|
|
150
|
+
|
|
151
|
+
return `<!DOCTYPE html>
|
|
152
|
+
<html lang="en">
|
|
153
|
+
<head>
|
|
154
|
+
<meta charset="utf-8" />
|
|
155
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
156
|
+
<title>${escapeHtml(input.title)}</title>
|
|
157
|
+
<style>
|
|
158
|
+
:root {
|
|
159
|
+
color-scheme: dark;
|
|
160
|
+
--bg: #050816;
|
|
161
|
+
--panel: rgba(12, 18, 38, 0.82);
|
|
162
|
+
--panel-border: rgba(255, 255, 255, 0.12);
|
|
163
|
+
--text: #f8fafc;
|
|
164
|
+
--text-dim: rgba(226, 232, 240, 0.72);
|
|
165
|
+
--accent: ${accent};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
* { box-sizing: border-box; }
|
|
169
|
+
|
|
170
|
+
body {
|
|
171
|
+
margin: 0;
|
|
172
|
+
min-height: 100vh;
|
|
173
|
+
display: flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
justify-content: center;
|
|
176
|
+
padding: 24px;
|
|
177
|
+
font-family:
|
|
178
|
+
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
179
|
+
sans-serif;
|
|
180
|
+
color: var(--text);
|
|
181
|
+
background:
|
|
182
|
+
radial-gradient(circle at top, rgba(79, 70, 229, 0.28), transparent 34%),
|
|
183
|
+
radial-gradient(circle at bottom right, rgba(34, 197, 94, 0.18), transparent 30%),
|
|
184
|
+
linear-gradient(180deg, #030712 0%, var(--bg) 100%);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.shell {
|
|
188
|
+
width: min(100%, 560px);
|
|
189
|
+
border-radius: 24px;
|
|
190
|
+
border: 1px solid var(--panel-border);
|
|
191
|
+
background: var(--panel);
|
|
192
|
+
backdrop-filter: blur(22px);
|
|
193
|
+
box-shadow:
|
|
194
|
+
0 24px 80px rgba(0, 0, 0, 0.42),
|
|
195
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.masthead {
|
|
200
|
+
padding: 20px 24px 0;
|
|
201
|
+
display: flex;
|
|
202
|
+
justify-content: space-between;
|
|
203
|
+
align-items: center;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.brand {
|
|
207
|
+
font-size: 13px;
|
|
208
|
+
letter-spacing: 0.14em;
|
|
209
|
+
text-transform: uppercase;
|
|
210
|
+
color: var(--text-dim);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.badge {
|
|
214
|
+
display: inline-flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
gap: 8px;
|
|
217
|
+
padding: 7px 12px;
|
|
218
|
+
border-radius: 999px;
|
|
219
|
+
background: rgba(255, 255, 255, 0.05);
|
|
220
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
221
|
+
color: var(--text);
|
|
222
|
+
font-size: 12px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.badge::before {
|
|
226
|
+
content: "";
|
|
227
|
+
width: 8px;
|
|
228
|
+
height: 8px;
|
|
229
|
+
border-radius: 999px;
|
|
230
|
+
background: var(--accent);
|
|
231
|
+
box-shadow: 0 0 16px var(--accent);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.content {
|
|
235
|
+
padding: 28px 24px 30px;
|
|
236
|
+
text-align: center;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.mark {
|
|
240
|
+
width: 72px;
|
|
241
|
+
height: 72px;
|
|
242
|
+
margin: 0 auto 18px;
|
|
243
|
+
border-radius: 22px;
|
|
244
|
+
display: grid;
|
|
245
|
+
place-items: center;
|
|
246
|
+
font-size: 34px;
|
|
247
|
+
font-weight: 700;
|
|
248
|
+
color: #04111f;
|
|
249
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.8));
|
|
250
|
+
box-shadow:
|
|
251
|
+
0 12px 30px rgba(0, 0, 0, 0.24),
|
|
252
|
+
0 0 0 6px rgba(255, 255, 255, 0.03);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
h1 {
|
|
256
|
+
margin: 0 0 10px;
|
|
257
|
+
font-size: clamp(28px, 6vw, 36px);
|
|
258
|
+
line-height: 1.05;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
p {
|
|
262
|
+
margin: 0 auto;
|
|
263
|
+
max-width: 34ch;
|
|
264
|
+
color: var(--text-dim);
|
|
265
|
+
font-size: 15px;
|
|
266
|
+
line-height: 1.6;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.footer {
|
|
270
|
+
margin-top: 24px;
|
|
271
|
+
padding-top: 18px;
|
|
272
|
+
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
|
273
|
+
color: rgba(226, 232, 240, 0.58);
|
|
274
|
+
font-size: 12px;
|
|
275
|
+
letter-spacing: 0.06em;
|
|
276
|
+
text-transform: uppercase;
|
|
277
|
+
}
|
|
278
|
+
</style>
|
|
279
|
+
</head>
|
|
280
|
+
<body>
|
|
281
|
+
<main class="shell">
|
|
282
|
+
<div class="masthead">
|
|
283
|
+
<div class="brand">MetaSynergy</div>
|
|
284
|
+
<div class="badge">${badge}</div>
|
|
285
|
+
</div>
|
|
286
|
+
<section class="content">
|
|
287
|
+
<div class="mark">${symbol}</div>
|
|
288
|
+
<h1>${escapeHtml(input.heading)}</h1>
|
|
289
|
+
<p>${escapeHtml(input.message)}</p>
|
|
290
|
+
<div class="footer">Holos agent tunnel</div>
|
|
291
|
+
</section>
|
|
292
|
+
</main>
|
|
293
|
+
</body>
|
|
294
|
+
</html>`
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function escapeHtml(value: string): string {
|
|
298
|
+
return value
|
|
299
|
+
.replaceAll("&", "&")
|
|
300
|
+
.replaceAll("<", "<")
|
|
301
|
+
.replaceAll(">", ">")
|
|
302
|
+
.replaceAll('"', """)
|
|
303
|
+
.replaceAll("'", "'")
|
|
304
|
+
}
|
package/src/inbound/handler.ts
CHANGED
|
@@ -85,11 +85,7 @@ export class MetaSynergyInboundHandler {
|
|
|
85
85
|
callerAgentID: input.caller.agentID,
|
|
86
86
|
error: error instanceof Error ? error.message : String(error),
|
|
87
87
|
})
|
|
88
|
-
return errorResult(
|
|
89
|
-
undefined,
|
|
90
|
-
"host_internal_error",
|
|
91
|
-
error instanceof Error ? error.message : String(error),
|
|
92
|
-
)
|
|
88
|
+
return errorResult(undefined, "host_internal_error", error instanceof Error ? error.message : String(error))
|
|
93
89
|
}
|
|
94
90
|
}
|
|
95
91
|
|
|
@@ -192,9 +188,7 @@ function errorResult(
|
|
|
192
188
|
}
|
|
193
189
|
}
|
|
194
190
|
|
|
195
|
-
function isEnvelopeError(
|
|
196
|
-
error: unknown,
|
|
197
|
-
): error is {
|
|
191
|
+
function isEnvelopeError(error: unknown): error is {
|
|
198
192
|
requestID?: string
|
|
199
193
|
tool?: MetaProtocolEnvelope.Tool
|
|
200
194
|
action?: string
|
package/src/rpc/handler.ts
CHANGED
package/src/rpc/schema.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import z from "zod"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
MetaProtocolBash,
|
|
4
|
+
MetaProtocolEnvelope,
|
|
5
|
+
MetaProtocolProcess,
|
|
6
|
+
MetaProtocolSession,
|
|
7
|
+
} from "@ericsanchezok/meta-protocol"
|
|
3
8
|
|
|
4
9
|
export const RPCRequestSchema = z.discriminatedUnion("tool", [
|
|
5
10
|
MetaProtocolBash.ExecuteRequest,
|
package/src/runtime.ts
CHANGED
|
@@ -19,6 +19,10 @@ export class MetaSynergyRuntime {
|
|
|
19
19
|
#client: MetaSynergyHolosClient | null = null
|
|
20
20
|
#timers: Array<ReturnType<typeof setInterval>> = []
|
|
21
21
|
#lastControlRequestID?: string
|
|
22
|
+
#reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
23
|
+
#reconnectAttempt = 0
|
|
24
|
+
#stopping = false
|
|
25
|
+
#manualReconnectInFlight = false
|
|
22
26
|
|
|
23
27
|
private constructor(
|
|
24
28
|
host: MetaSynergyHost,
|
|
@@ -82,6 +86,7 @@ export class MetaSynergyRuntime {
|
|
|
82
86
|
|
|
83
87
|
async start() {
|
|
84
88
|
const state = requireState(this)
|
|
89
|
+
this.#stopping = false
|
|
85
90
|
state.connectionStatus = "connecting"
|
|
86
91
|
await MetaSynergyStore.saveState(state)
|
|
87
92
|
|
|
@@ -96,15 +101,26 @@ export class MetaSynergyRuntime {
|
|
|
96
101
|
throw new Error("Meta Synergy could not load credentials after login.")
|
|
97
102
|
}
|
|
98
103
|
|
|
99
|
-
this.#client = new MetaSynergyHolosClient(nextAuth, this.inbound)
|
|
100
|
-
await this.#connectClient()
|
|
101
104
|
this.#startLoops()
|
|
105
|
+
this.#ensureClient(nextAuth)
|
|
106
|
+
try {
|
|
107
|
+
await this.#connectClient()
|
|
108
|
+
} catch (error) {
|
|
109
|
+
MetaSynergyLog.error("runtime.connection.initial_connect_failed", {
|
|
110
|
+
error: error instanceof Error ? error.message : String(error),
|
|
111
|
+
})
|
|
112
|
+
await this.#setConnectionStatus("disconnected")
|
|
113
|
+
this.#scheduleReconnect("initial_connect_failed")
|
|
114
|
+
}
|
|
102
115
|
|
|
103
|
-
console.log(`
|
|
116
|
+
console.log(`MetaSynergy running as ${nextAuth.agentID}`)
|
|
104
117
|
console.log(`envID: ${this.host.envID}`)
|
|
118
|
+
console.log(`Holos: ${this.state?.connectionStatus ?? "disconnected"}`)
|
|
105
119
|
console.log(`Status: ${this.sessions.current() ? "busy" : "idle"}`)
|
|
106
120
|
|
|
107
121
|
const stop = async () => {
|
|
122
|
+
this.#stopping = true
|
|
123
|
+
this.#clearReconnectTimer()
|
|
108
124
|
this.#stopLoops()
|
|
109
125
|
if (this.state) {
|
|
110
126
|
this.state.connectionStatus = "disconnected"
|
|
@@ -132,6 +148,8 @@ export class MetaSynergyRuntime {
|
|
|
132
148
|
}
|
|
133
149
|
|
|
134
150
|
async logout() {
|
|
151
|
+
this.#stopping = true
|
|
152
|
+
this.#clearReconnectTimer()
|
|
135
153
|
this.#stopLoops()
|
|
136
154
|
if (this.state) {
|
|
137
155
|
this.state.connectionStatus = "disconnected"
|
|
@@ -226,7 +244,10 @@ export class MetaSynergyRuntime {
|
|
|
226
244
|
this.sessions.kickCurrent(disk.controlRequest.block ?? false)
|
|
227
245
|
}
|
|
228
246
|
if (disk.controlRequest.kind === "reconnect") {
|
|
229
|
-
await this.#reconnectClient()
|
|
247
|
+
const succeeded = await this.#reconnectClient()
|
|
248
|
+
if (!succeeded) {
|
|
249
|
+
this.#scheduleReconnect("manual_reconnect_failed")
|
|
250
|
+
}
|
|
230
251
|
}
|
|
231
252
|
this.state.controlRequest = undefined
|
|
232
253
|
await MetaSynergyStore.saveState(this.state)
|
|
@@ -242,35 +263,112 @@ export class MetaSynergyRuntime {
|
|
|
242
263
|
throw new Error("Meta Synergy could not load credentials.")
|
|
243
264
|
}
|
|
244
265
|
|
|
245
|
-
|
|
246
|
-
|
|
266
|
+
this.#ensureClient(auth)
|
|
267
|
+
const client = this.#client
|
|
268
|
+
if (!client) {
|
|
269
|
+
throw new Error("Meta Synergy could not create Holos client.")
|
|
247
270
|
}
|
|
248
271
|
|
|
249
|
-
this
|
|
250
|
-
await MetaSynergyStore.saveState(this.state)
|
|
272
|
+
await this.#setConnectionStatus("connecting")
|
|
251
273
|
try {
|
|
252
|
-
await
|
|
253
|
-
this
|
|
254
|
-
|
|
274
|
+
await client.connect()
|
|
275
|
+
this.#reconnectAttempt = 0
|
|
276
|
+
this.#clearReconnectTimer()
|
|
277
|
+
await this.#setConnectionStatus("connected")
|
|
255
278
|
} catch (error) {
|
|
256
|
-
this
|
|
257
|
-
await MetaSynergyStore.saveState(this.state)
|
|
279
|
+
await this.#setConnectionStatus("disconnected")
|
|
258
280
|
throw error
|
|
259
281
|
}
|
|
260
282
|
}
|
|
261
283
|
|
|
262
284
|
async #reconnectClient() {
|
|
263
285
|
MetaSynergyLog.info("runtime.connection.reconnect.begin")
|
|
286
|
+
this.#manualReconnectInFlight = true
|
|
287
|
+
this.#clearReconnectTimer()
|
|
264
288
|
await this.#client?.disconnect().catch(() => undefined)
|
|
265
289
|
this.#client = null
|
|
290
|
+
let succeeded = false
|
|
266
291
|
try {
|
|
267
292
|
await this.#connectClient()
|
|
293
|
+
succeeded = true
|
|
268
294
|
MetaSynergyLog.info("runtime.connection.reconnect.completed")
|
|
269
295
|
} catch (error) {
|
|
270
296
|
MetaSynergyLog.error("runtime.connection.reconnect.failed", {
|
|
271
297
|
error: error instanceof Error ? error.message : String(error),
|
|
272
298
|
})
|
|
299
|
+
} finally {
|
|
300
|
+
this.#manualReconnectInFlight = false
|
|
273
301
|
}
|
|
302
|
+
return succeeded
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#ensureClient(auth: { agentID: string; agentSecret: string }) {
|
|
306
|
+
if (this.#client?.auth.agentID === auth.agentID && this.#client?.auth.agentSecret === auth.agentSecret) {
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
let client!: MetaSynergyHolosClient
|
|
310
|
+
client = new MetaSynergyHolosClient(auth, this.inbound, {
|
|
311
|
+
onClose: ({ intentional }) => {
|
|
312
|
+
if (this.#client !== client) return
|
|
313
|
+
void this.#handleClientClosed({ intentional })
|
|
314
|
+
},
|
|
315
|
+
})
|
|
316
|
+
this.#client = client
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async #handleClientClosed(input: { intentional: boolean }) {
|
|
320
|
+
await this.#setConnectionStatus("disconnected")
|
|
321
|
+
if (input.intentional || this.#stopping || this.#manualReconnectInFlight) {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
this.#scheduleReconnect("socket_closed")
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#scheduleReconnect(reason: string) {
|
|
328
|
+
if (this.#stopping || this.#manualReconnectInFlight || this.#reconnectTimer) return
|
|
329
|
+
|
|
330
|
+
const attempt = this.#reconnectAttempt + 1
|
|
331
|
+
const delayMs = Math.min(60_000, 2_000 * 2 ** (attempt - 1))
|
|
332
|
+
this.#reconnectAttempt = attempt
|
|
333
|
+
MetaSynergyLog.warn("runtime.connection.reconnect.scheduled", {
|
|
334
|
+
reason,
|
|
335
|
+
attempt,
|
|
336
|
+
delayMs,
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
const timer = setTimeout(() => {
|
|
340
|
+
this.#reconnectTimer = null
|
|
341
|
+
void this.#performScheduledReconnect()
|
|
342
|
+
}, delayMs)
|
|
343
|
+
timer.unref?.()
|
|
344
|
+
this.#reconnectTimer = timer
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async #performScheduledReconnect() {
|
|
348
|
+
if (this.#stopping || this.#manualReconnectInFlight) return
|
|
349
|
+
try {
|
|
350
|
+
const succeeded = await this.#reconnectClient()
|
|
351
|
+
if (!succeeded) {
|
|
352
|
+
this.#scheduleReconnect("scheduled_reconnect_failed")
|
|
353
|
+
}
|
|
354
|
+
} catch (error) {
|
|
355
|
+
MetaSynergyLog.error("runtime.connection.reconnect.unexpected", {
|
|
356
|
+
error: error instanceof Error ? error.message : String(error),
|
|
357
|
+
})
|
|
358
|
+
this.#scheduleReconnect("scheduled_reconnect_failed")
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#clearReconnectTimer() {
|
|
363
|
+
if (!this.#reconnectTimer) return
|
|
364
|
+
clearTimeout(this.#reconnectTimer)
|
|
365
|
+
this.#reconnectTimer = null
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async #setConnectionStatus(status: "disconnected" | "connecting" | "connected") {
|
|
369
|
+
if (!this.state) return
|
|
370
|
+
this.state.connectionStatus = status
|
|
371
|
+
await MetaSynergyStore.saveState(this.state)
|
|
274
372
|
}
|
|
275
373
|
|
|
276
374
|
async confirmCollaboration(input: { caller: { agentID: string }; label?: string }) {
|
|
@@ -314,5 +412,5 @@ function requireState(runtime: MetaSynergyRuntime) {
|
|
|
314
412
|
}
|
|
315
413
|
|
|
316
414
|
function escapeAppleScript(input: string) {
|
|
317
|
-
return input.replaceAll("\\", "\\\\").replaceAll("
|
|
415
|
+
return input.replaceAll("\\", "\\\\").replaceAll('"', '\\"')
|
|
318
416
|
}
|
package/src/session/manager.ts
CHANGED
|
@@ -244,6 +244,9 @@ export class SessionManager {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
function envelopeError(
|
|
247
|
+
function envelopeError(
|
|
248
|
+
code: MetaProtocolError.Code,
|
|
249
|
+
message: string,
|
|
250
|
+
): { code: MetaProtocolError.Code; message: string } {
|
|
248
251
|
return { code, message }
|
|
249
252
|
}
|