crewly 1.4.48 → 1.4.50
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/dist/backend/backend/src/constants.d.ts +40 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +40 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +28 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts +1 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js +3 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/relay.controller.d.ts +23 -0
- package/dist/backend/backend/src/controllers/cloud/relay.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/relay.controller.js +69 -0
- package/dist/backend/backend/src/controllers/cloud/relay.controller.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +2 -5
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-client.service.d.ts +5 -1
- package/dist/backend/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-client.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js +20 -0
- package/dist/backend/backend/src/services/cloud/cloud-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts +163 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.js +484 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.service.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +181 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +91 -0
- package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/relay-client.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/relay-client.service.js +2 -0
- package/dist/backend/backend/src/services/cloud/relay-client.service.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +40 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +40 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/cli/src/commands/service.d.ts +2 -0
- package/dist/cli/cli/src/commands/service.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/service.js +138 -3
- package/dist/cli/cli/src/commands/service.js.map +1 -1
- package/frontend/dist/assets/{index-3ae9392e.js → index-28fcc469.js} +253 -253
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Sync Service
|
|
3
|
+
*
|
|
4
|
+
* Singleton service that replaces the WebSocket Relay pairing model with a
|
|
5
|
+
* simpler heartbeat + polling architecture. All devices under the same Cloud
|
|
6
|
+
* account are automatically visible and can exchange messages.
|
|
7
|
+
*
|
|
8
|
+
* Responsibilities:
|
|
9
|
+
* - Heartbeat: periodically uploads device status to Cloud
|
|
10
|
+
* - Device poll: periodically fetches the device list for the account
|
|
11
|
+
* - Message poll: periodically fetches pending messages for this device
|
|
12
|
+
* - Send: posts messages to specific devices via Cloud
|
|
13
|
+
*
|
|
14
|
+
* @see docs/cloud-sync-design.md
|
|
15
|
+
* @module services/cloud/cloud-sync.service
|
|
16
|
+
*/
|
|
17
|
+
import { EventEmitter } from 'events';
|
|
18
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
19
|
+
import { StorageService } from '../core/storage.service.js';
|
|
20
|
+
import { CLOUD_SYNC_CONSTANTS } from '../../constants.js';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Helpers
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Read the Crewly version from package.json (best-effort).
|
|
26
|
+
*
|
|
27
|
+
* @returns Version string or 'unknown'
|
|
28
|
+
*/
|
|
29
|
+
async function readVersion() {
|
|
30
|
+
try {
|
|
31
|
+
const { readFile } = await import('fs/promises');
|
|
32
|
+
const { join } = await import('path');
|
|
33
|
+
const pkg = JSON.parse(await readFile(join(process.cwd(), 'package.json'), 'utf-8'));
|
|
34
|
+
return pkg.version || 'unknown';
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Gather active team summaries from StorageService.
|
|
42
|
+
*
|
|
43
|
+
* @returns Array of team summaries for heartbeat payload
|
|
44
|
+
*/
|
|
45
|
+
async function gatherTeamSummaries() {
|
|
46
|
+
try {
|
|
47
|
+
const storage = StorageService.getInstance();
|
|
48
|
+
const teams = await storage.getTeams();
|
|
49
|
+
return teams.map((team) => ({
|
|
50
|
+
id: team.id,
|
|
51
|
+
name: team.name,
|
|
52
|
+
memberCount: team.members?.length ?? 0,
|
|
53
|
+
activeAgents: team.members?.filter((m) => m.agentStatus === 'active').length ?? 0,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Service
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/**
|
|
64
|
+
* CloudSyncService singleton.
|
|
65
|
+
*
|
|
66
|
+
* Manages heartbeat, device discovery, and message polling for the
|
|
67
|
+
* Cloud Sync system. Extends EventEmitter to notify consumers of
|
|
68
|
+
* device and message changes.
|
|
69
|
+
*
|
|
70
|
+
* Events:
|
|
71
|
+
* - `devices_updated` — Fired when the device list changes. Payload: SyncDevice[]
|
|
72
|
+
* - `message` — Fired for each incoming message. Payload: IncomingMessage
|
|
73
|
+
* - `device_online` — Fired when a device comes online. Payload: SyncDevice
|
|
74
|
+
* - `device_offline` — Fired when a device goes offline. Payload: SyncDevice
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const sync = CloudSyncService.getInstance();
|
|
79
|
+
* sync.start({ cloudUrl, token, deviceId, deviceName });
|
|
80
|
+
* sync.on('message', (msg) => console.log('Received:', msg));
|
|
81
|
+
* sync.on('devices_updated', (devices) => console.log('Devices:', devices));
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export class CloudSyncService extends EventEmitter {
|
|
85
|
+
static instance = null;
|
|
86
|
+
logger;
|
|
87
|
+
/** Current service state */
|
|
88
|
+
state = 'stopped';
|
|
89
|
+
/** Configuration (set on start) */
|
|
90
|
+
config = null;
|
|
91
|
+
/** Cached device list from last poll */
|
|
92
|
+
devices = [];
|
|
93
|
+
/** Cached Crewly version */
|
|
94
|
+
version = 'unknown';
|
|
95
|
+
/** Heartbeat timer handle */
|
|
96
|
+
heartbeatTimer = null;
|
|
97
|
+
/** Device poll timer handle */
|
|
98
|
+
devicePollTimer = null;
|
|
99
|
+
/** Message poll timer handle */
|
|
100
|
+
messagePollTimer = null;
|
|
101
|
+
/** Consecutive heartbeat failure count */
|
|
102
|
+
heartbeatFailures = 0;
|
|
103
|
+
/** Consecutive device poll failure count */
|
|
104
|
+
devicePollFailures = 0;
|
|
105
|
+
/** Consecutive message poll failure count */
|
|
106
|
+
messagePollFailures = 0;
|
|
107
|
+
constructor() {
|
|
108
|
+
super();
|
|
109
|
+
this.logger = LoggerService.getInstance().createComponentLogger('CloudSyncService');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the singleton instance.
|
|
113
|
+
*
|
|
114
|
+
* @returns CloudSyncService instance
|
|
115
|
+
*/
|
|
116
|
+
static getInstance() {
|
|
117
|
+
if (!CloudSyncService.instance) {
|
|
118
|
+
CloudSyncService.instance = new CloudSyncService();
|
|
119
|
+
}
|
|
120
|
+
return CloudSyncService.instance;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Reset the singleton (for testing).
|
|
124
|
+
*/
|
|
125
|
+
static resetInstance() {
|
|
126
|
+
if (CloudSyncService.instance) {
|
|
127
|
+
CloudSyncService.instance.stop();
|
|
128
|
+
}
|
|
129
|
+
CloudSyncService.instance = null;
|
|
130
|
+
}
|
|
131
|
+
// -------------------------------------------------------------------------
|
|
132
|
+
// Public API
|
|
133
|
+
// -------------------------------------------------------------------------
|
|
134
|
+
/**
|
|
135
|
+
* Start the Cloud Sync service.
|
|
136
|
+
*
|
|
137
|
+
* Begins heartbeat, device polling, and message polling timers.
|
|
138
|
+
* Performs an immediate heartbeat and device poll on start.
|
|
139
|
+
*
|
|
140
|
+
* @param config - Cloud connection configuration
|
|
141
|
+
*/
|
|
142
|
+
start(config) {
|
|
143
|
+
if (this.state === 'syncing') {
|
|
144
|
+
this.logger.warn('CloudSyncService already running, ignoring start()');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.config = config;
|
|
148
|
+
this.state = 'syncing';
|
|
149
|
+
this.heartbeatFailures = 0;
|
|
150
|
+
this.devicePollFailures = 0;
|
|
151
|
+
this.messagePollFailures = 0;
|
|
152
|
+
this.logger.info('Starting Cloud Sync', {
|
|
153
|
+
cloudUrl: config.cloudUrl,
|
|
154
|
+
deviceId: config.deviceId,
|
|
155
|
+
deviceName: config.deviceName,
|
|
156
|
+
});
|
|
157
|
+
// Read version once at startup
|
|
158
|
+
readVersion().then((v) => { this.version = v; }).catch(() => { });
|
|
159
|
+
// Perform initial sync immediately
|
|
160
|
+
this.sendHeartbeat().catch(() => { });
|
|
161
|
+
this.pollDevices().catch(() => { });
|
|
162
|
+
// Start periodic timers
|
|
163
|
+
this.heartbeatTimer = setInterval(() => { this.sendHeartbeat().catch(() => { }); }, CLOUD_SYNC_CONSTANTS.HEARTBEAT_INTERVAL_MS);
|
|
164
|
+
this.devicePollTimer = setInterval(() => { this.pollDevices().catch(() => { }); }, CLOUD_SYNC_CONSTANTS.DEVICE_POLL_INTERVAL_MS);
|
|
165
|
+
this.messagePollTimer = setInterval(() => { this.pollMessages().catch(() => { }); }, CLOUD_SYNC_CONSTANTS.MESSAGE_POLL_INTERVAL_MS);
|
|
166
|
+
// Unref timers so they don't keep the process alive
|
|
167
|
+
if (this.heartbeatTimer.unref)
|
|
168
|
+
this.heartbeatTimer.unref();
|
|
169
|
+
if (this.devicePollTimer.unref)
|
|
170
|
+
this.devicePollTimer.unref();
|
|
171
|
+
if (this.messagePollTimer.unref)
|
|
172
|
+
this.messagePollTimer.unref();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Stop the Cloud Sync service.
|
|
176
|
+
*
|
|
177
|
+
* Clears all timers and resets state. Safe to call multiple times.
|
|
178
|
+
*/
|
|
179
|
+
stop() {
|
|
180
|
+
if (this.state === 'stopped')
|
|
181
|
+
return;
|
|
182
|
+
this.logger.info('Stopping Cloud Sync');
|
|
183
|
+
if (this.heartbeatTimer) {
|
|
184
|
+
clearInterval(this.heartbeatTimer);
|
|
185
|
+
this.heartbeatTimer = null;
|
|
186
|
+
}
|
|
187
|
+
if (this.devicePollTimer) {
|
|
188
|
+
clearInterval(this.devicePollTimer);
|
|
189
|
+
this.devicePollTimer = null;
|
|
190
|
+
}
|
|
191
|
+
if (this.messagePollTimer) {
|
|
192
|
+
clearInterval(this.messagePollTimer);
|
|
193
|
+
this.messagePollTimer = null;
|
|
194
|
+
}
|
|
195
|
+
this.state = 'stopped';
|
|
196
|
+
this.config = null;
|
|
197
|
+
this.devices = [];
|
|
198
|
+
this.heartbeatFailures = 0;
|
|
199
|
+
this.devicePollFailures = 0;
|
|
200
|
+
this.messagePollFailures = 0;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if the sync service has been started.
|
|
204
|
+
*
|
|
205
|
+
* @returns True if the service is currently syncing
|
|
206
|
+
*/
|
|
207
|
+
isStarted() {
|
|
208
|
+
return this.state === 'syncing';
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get the current service state.
|
|
212
|
+
*
|
|
213
|
+
* @returns Current CloudSyncState
|
|
214
|
+
*/
|
|
215
|
+
getState() {
|
|
216
|
+
return this.state;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get all cached devices for this Cloud account.
|
|
220
|
+
*
|
|
221
|
+
* @returns Array of SyncDevice objects (may include offline devices)
|
|
222
|
+
*/
|
|
223
|
+
getDevices() {
|
|
224
|
+
return [...this.devices];
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get only online devices (excluding this device).
|
|
228
|
+
*
|
|
229
|
+
* @returns Array of online SyncDevice objects
|
|
230
|
+
*/
|
|
231
|
+
getOnlineDevices() {
|
|
232
|
+
return this.devices.filter((d) => d.status === 'online' && !d.isLocal);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Send a message to another device via the Cloud message queue.
|
|
236
|
+
*
|
|
237
|
+
* @param toDeviceId - Target device identifier
|
|
238
|
+
* @param type - Message type
|
|
239
|
+
* @param payload - Message payload (must be JSON-serializable)
|
|
240
|
+
* @throws Error when not started or API call fails
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* await sync.sendMessage('dev-456', 'command', {
|
|
245
|
+
* action: 'delegate-task',
|
|
246
|
+
* content: 'Implement feature X'
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
async sendMessage(toDeviceId, type, payload) {
|
|
251
|
+
if (!this.config) {
|
|
252
|
+
throw new Error('CloudSyncService not started. Call start() first.');
|
|
253
|
+
}
|
|
254
|
+
const url = `${this.config.cloudUrl}${CLOUD_SYNC_CONSTANTS.ENDPOINTS.MESSAGES}`;
|
|
255
|
+
const response = await fetch(url, {
|
|
256
|
+
method: 'POST',
|
|
257
|
+
headers: this.authHeaders(),
|
|
258
|
+
body: JSON.stringify({
|
|
259
|
+
to: toDeviceId,
|
|
260
|
+
type,
|
|
261
|
+
payload,
|
|
262
|
+
encrypted: false,
|
|
263
|
+
}),
|
|
264
|
+
signal: AbortSignal.timeout(CLOUD_SYNC_CONSTANTS.REQUEST_TIMEOUT_MS),
|
|
265
|
+
});
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
const errorText = await response.text().catch(() => '');
|
|
268
|
+
throw new Error(`Failed to send message: ${response.status} ${errorText}`);
|
|
269
|
+
}
|
|
270
|
+
this.logger.info('Message sent via Cloud Sync', { to: toDeviceId, type });
|
|
271
|
+
}
|
|
272
|
+
// -------------------------------------------------------------------------
|
|
273
|
+
// Internal: Heartbeat
|
|
274
|
+
// -------------------------------------------------------------------------
|
|
275
|
+
/**
|
|
276
|
+
* Send a heartbeat to the Cloud server with current device state.
|
|
277
|
+
* Called periodically by the heartbeat timer.
|
|
278
|
+
*/
|
|
279
|
+
async sendHeartbeat() {
|
|
280
|
+
if (!this.config)
|
|
281
|
+
return;
|
|
282
|
+
try {
|
|
283
|
+
const teams = await gatherTeamSummaries();
|
|
284
|
+
const payload = {
|
|
285
|
+
deviceId: this.config.deviceId,
|
|
286
|
+
deviceName: this.config.deviceName,
|
|
287
|
+
status: 'online',
|
|
288
|
+
version: this.version,
|
|
289
|
+
capabilities: ['orchestrator', 'agent-runner'],
|
|
290
|
+
teams,
|
|
291
|
+
timestamp: new Date().toISOString(),
|
|
292
|
+
};
|
|
293
|
+
const url = `${this.config.cloudUrl}${CLOUD_SYNC_CONSTANTS.ENDPOINTS.HEARTBEAT}`;
|
|
294
|
+
const response = await fetch(url, {
|
|
295
|
+
method: 'POST',
|
|
296
|
+
headers: this.authHeaders(),
|
|
297
|
+
body: JSON.stringify(payload),
|
|
298
|
+
signal: AbortSignal.timeout(CLOUD_SYNC_CONSTANTS.REQUEST_TIMEOUT_MS),
|
|
299
|
+
});
|
|
300
|
+
if (!response.ok) {
|
|
301
|
+
throw new Error(`Heartbeat failed: ${response.status}`);
|
|
302
|
+
}
|
|
303
|
+
this.heartbeatFailures = 0;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
this.heartbeatFailures++;
|
|
307
|
+
this.logger.warn('Heartbeat failed', {
|
|
308
|
+
error: error instanceof Error ? error.message : String(error),
|
|
309
|
+
failures: this.heartbeatFailures,
|
|
310
|
+
});
|
|
311
|
+
this.checkErrorThreshold();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// -------------------------------------------------------------------------
|
|
315
|
+
// Internal: Device Polling
|
|
316
|
+
// -------------------------------------------------------------------------
|
|
317
|
+
/**
|
|
318
|
+
* Poll the Cloud server for the current device list.
|
|
319
|
+
* Updates the cached device list and emits events on changes.
|
|
320
|
+
*/
|
|
321
|
+
async pollDevices() {
|
|
322
|
+
if (!this.config)
|
|
323
|
+
return;
|
|
324
|
+
try {
|
|
325
|
+
const url = `${this.config.cloudUrl}${CLOUD_SYNC_CONSTANTS.ENDPOINTS.DEVICES}`;
|
|
326
|
+
const response = await fetch(url, {
|
|
327
|
+
method: 'GET',
|
|
328
|
+
headers: this.authHeaders(),
|
|
329
|
+
signal: AbortSignal.timeout(CLOUD_SYNC_CONSTANTS.REQUEST_TIMEOUT_MS),
|
|
330
|
+
});
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
throw new Error(`Device poll failed: ${response.status}`);
|
|
333
|
+
}
|
|
334
|
+
const data = await response.json();
|
|
335
|
+
if (!data.success || !data.devices) {
|
|
336
|
+
throw new Error('Device poll returned unsuccessful response');
|
|
337
|
+
}
|
|
338
|
+
const previousOnlineIds = new Set(this.devices.filter((d) => d.status === 'online').map((d) => d.deviceId));
|
|
339
|
+
// Normalize devices: mark local, determine online status
|
|
340
|
+
const offlineThreshold = CLOUD_SYNC_CONSTANTS.OFFLINE_THRESHOLD_MS;
|
|
341
|
+
const now = Date.now();
|
|
342
|
+
const updatedDevices = data.devices.map((d) => ({
|
|
343
|
+
...d,
|
|
344
|
+
status: (now - new Date(d.lastHeartbeatAt).getTime() > offlineThreshold)
|
|
345
|
+
? 'offline'
|
|
346
|
+
: 'online',
|
|
347
|
+
isLocal: d.deviceId === this.config.deviceId,
|
|
348
|
+
}));
|
|
349
|
+
// Detect online/offline transitions
|
|
350
|
+
const newOnlineIds = new Set(updatedDevices.filter((d) => d.status === 'online').map((d) => d.deviceId));
|
|
351
|
+
for (const device of updatedDevices) {
|
|
352
|
+
if (device.status === 'online' && !previousOnlineIds.has(device.deviceId) && !device.isLocal) {
|
|
353
|
+
this.emit('device_online', device);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
for (const prevId of previousOnlineIds) {
|
|
357
|
+
if (!newOnlineIds.has(prevId) && prevId !== this.config.deviceId) {
|
|
358
|
+
const offlineDevice = this.devices.find((d) => d.deviceId === prevId);
|
|
359
|
+
if (offlineDevice) {
|
|
360
|
+
this.emit('device_offline', { ...offlineDevice, status: 'offline' });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
this.devices = updatedDevices;
|
|
365
|
+
this.devicePollFailures = 0;
|
|
366
|
+
this.emit('devices_updated', this.getDevices());
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
this.devicePollFailures++;
|
|
370
|
+
this.logger.warn('Device poll failed', {
|
|
371
|
+
error: error instanceof Error ? error.message : String(error),
|
|
372
|
+
failures: this.devicePollFailures,
|
|
373
|
+
});
|
|
374
|
+
this.checkErrorThreshold();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// -------------------------------------------------------------------------
|
|
378
|
+
// Internal: Message Polling
|
|
379
|
+
// -------------------------------------------------------------------------
|
|
380
|
+
/**
|
|
381
|
+
* Poll the Cloud server for pending messages addressed to this device.
|
|
382
|
+
* Emits 'message' event for each received message, then acknowledges.
|
|
383
|
+
*/
|
|
384
|
+
async pollMessages() {
|
|
385
|
+
if (!this.config)
|
|
386
|
+
return;
|
|
387
|
+
try {
|
|
388
|
+
const url = `${this.config.cloudUrl}${CLOUD_SYNC_CONSTANTS.ENDPOINTS.MESSAGES}?deviceId=${encodeURIComponent(this.config.deviceId)}&limit=50`;
|
|
389
|
+
const response = await fetch(url, {
|
|
390
|
+
method: 'GET',
|
|
391
|
+
headers: this.authHeaders(),
|
|
392
|
+
signal: AbortSignal.timeout(CLOUD_SYNC_CONSTANTS.REQUEST_TIMEOUT_MS),
|
|
393
|
+
});
|
|
394
|
+
if (!response.ok) {
|
|
395
|
+
throw new Error(`Message poll failed: ${response.status}`);
|
|
396
|
+
}
|
|
397
|
+
const data = await response.json();
|
|
398
|
+
if (!data.success || !data.messages || data.messages.length === 0) {
|
|
399
|
+
this.messagePollFailures = 0;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
// Emit each message
|
|
403
|
+
const messageIds = [];
|
|
404
|
+
for (const msg of data.messages) {
|
|
405
|
+
this.emit('message', msg);
|
|
406
|
+
messageIds.push(msg.id);
|
|
407
|
+
}
|
|
408
|
+
// Acknowledge processed messages
|
|
409
|
+
await this.ackMessages(messageIds);
|
|
410
|
+
this.messagePollFailures = 0;
|
|
411
|
+
this.logger.debug('Polled and processed messages', { count: data.messages.length });
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
this.messagePollFailures++;
|
|
415
|
+
this.logger.warn('Message poll failed', {
|
|
416
|
+
error: error instanceof Error ? error.message : String(error),
|
|
417
|
+
failures: this.messagePollFailures,
|
|
418
|
+
});
|
|
419
|
+
this.checkErrorThreshold();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Acknowledge processed messages so Cloud can remove them from the queue.
|
|
424
|
+
*
|
|
425
|
+
* @param messageIds - Array of message IDs to acknowledge
|
|
426
|
+
*/
|
|
427
|
+
async ackMessages(messageIds) {
|
|
428
|
+
if (!this.config || messageIds.length === 0)
|
|
429
|
+
return;
|
|
430
|
+
try {
|
|
431
|
+
const url = `${this.config.cloudUrl}${CLOUD_SYNC_CONSTANTS.ENDPOINTS.MESSAGES_ACK}`;
|
|
432
|
+
await fetch(url, {
|
|
433
|
+
method: 'POST',
|
|
434
|
+
headers: this.authHeaders(),
|
|
435
|
+
body: JSON.stringify({
|
|
436
|
+
deviceId: this.config.deviceId,
|
|
437
|
+
messageIds,
|
|
438
|
+
}),
|
|
439
|
+
signal: AbortSignal.timeout(CLOUD_SYNC_CONSTANTS.REQUEST_TIMEOUT_MS),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
this.logger.warn('Message ACK failed (non-fatal)', {
|
|
444
|
+
error: error instanceof Error ? error.message : String(error),
|
|
445
|
+
messageIds,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// -------------------------------------------------------------------------
|
|
450
|
+
// Internal: Error Handling
|
|
451
|
+
// -------------------------------------------------------------------------
|
|
452
|
+
/**
|
|
453
|
+
* Check if any failure counter has exceeded the threshold.
|
|
454
|
+
* If so, transition to error state.
|
|
455
|
+
*/
|
|
456
|
+
checkErrorThreshold() {
|
|
457
|
+
const max = CLOUD_SYNC_CONSTANTS.MAX_CONSECUTIVE_FAILURES;
|
|
458
|
+
if (this.heartbeatFailures >= max ||
|
|
459
|
+
this.devicePollFailures >= max ||
|
|
460
|
+
this.messagePollFailures >= max) {
|
|
461
|
+
this.logger.error('Cloud Sync entering error state after repeated failures', {
|
|
462
|
+
heartbeatFailures: this.heartbeatFailures,
|
|
463
|
+
devicePollFailures: this.devicePollFailures,
|
|
464
|
+
messagePollFailures: this.messagePollFailures,
|
|
465
|
+
});
|
|
466
|
+
this.state = 'error';
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// -------------------------------------------------------------------------
|
|
470
|
+
// Internal: Helpers
|
|
471
|
+
// -------------------------------------------------------------------------
|
|
472
|
+
/**
|
|
473
|
+
* Build authorization headers for Cloud API requests.
|
|
474
|
+
*
|
|
475
|
+
* @returns Headers with Bearer token and Content-Type
|
|
476
|
+
*/
|
|
477
|
+
authHeaders() {
|
|
478
|
+
return {
|
|
479
|
+
Authorization: `Bearer ${this.config?.token ?? ''}`,
|
|
480
|
+
'Content-Type': 'application/json',
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
//# sourceMappingURL=cloud-sync.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-sync.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/cloud/cloud-sync.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Sync Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Defines the data structures for the Cloud Sync system that replaces
|
|
5
|
+
* the WebSocket Relay pairing model. All devices under the same Cloud
|
|
6
|
+
* account are automatically visible and can exchange messages.
|
|
7
|
+
*
|
|
8
|
+
* @see docs/cloud-sync-design.md
|
|
9
|
+
* @module services/cloud/cloud-sync.types
|
|
10
|
+
*/
|
|
11
|
+
/** Possible states of the CloudSyncService lifecycle. */
|
|
12
|
+
export type CloudSyncState = 'stopped' | 'syncing' | 'error';
|
|
13
|
+
/** Valid cloud sync state values for runtime validation. */
|
|
14
|
+
export declare const CLOUD_SYNC_STATES: readonly ["stopped", "syncing", "error"];
|
|
15
|
+
/**
|
|
16
|
+
* Configuration required to start the CloudSyncService.
|
|
17
|
+
* Obtained from CloudClientService credentials and DeviceIdentityService.
|
|
18
|
+
*/
|
|
19
|
+
export interface CloudSyncConfig {
|
|
20
|
+
/** Cloud API base URL (e.g. "https://api.crewlyai.com") */
|
|
21
|
+
cloudUrl: string;
|
|
22
|
+
/** Bearer token for Cloud API authentication */
|
|
23
|
+
token: string;
|
|
24
|
+
/** Unique device identifier from ~/.crewly/device.json */
|
|
25
|
+
deviceId: string;
|
|
26
|
+
/** Human-readable device name (OS hostname) */
|
|
27
|
+
deviceName: string;
|
|
28
|
+
}
|
|
29
|
+
/** Summary of a team on a device, included in heartbeat payloads. */
|
|
30
|
+
export interface SyncTeamSummary {
|
|
31
|
+
/** Team identifier */
|
|
32
|
+
id: string;
|
|
33
|
+
/** Team display name */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Number of configured team members */
|
|
36
|
+
memberCount: number;
|
|
37
|
+
/** Number of currently active agents */
|
|
38
|
+
activeAgents: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A device visible in the same Cloud account.
|
|
42
|
+
* Populated by polling GET /v1/devices.
|
|
43
|
+
*/
|
|
44
|
+
export interface SyncDevice {
|
|
45
|
+
/** Unique device identifier (UUID) */
|
|
46
|
+
deviceId: string;
|
|
47
|
+
/** Human-readable device name (hostname) */
|
|
48
|
+
deviceName: string;
|
|
49
|
+
/** Whether the device is online or offline (based on heartbeat freshness) */
|
|
50
|
+
status: 'online' | 'offline';
|
|
51
|
+
/** Crewly version running on the device */
|
|
52
|
+
version?: string;
|
|
53
|
+
/** Device capabilities (e.g. orchestrator, agent-runner, mcp-server) */
|
|
54
|
+
capabilities?: string[];
|
|
55
|
+
/** Active teams on the device */
|
|
56
|
+
teams?: SyncTeamSummary[];
|
|
57
|
+
/** ISO timestamp of last heartbeat */
|
|
58
|
+
lastHeartbeatAt: string;
|
|
59
|
+
/** Whether this device is the local OSS instance */
|
|
60
|
+
isLocal?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Payload sent to Cloud in the heartbeat POST.
|
|
64
|
+
* Uploaded every HEARTBEAT_INTERVAL_MS (30s).
|
|
65
|
+
*/
|
|
66
|
+
export interface HeartbeatPayload {
|
|
67
|
+
/** Unique device identifier */
|
|
68
|
+
deviceId: string;
|
|
69
|
+
/** Human-readable device name (hostname) */
|
|
70
|
+
deviceName: string;
|
|
71
|
+
/** Device status */
|
|
72
|
+
status: 'online';
|
|
73
|
+
/** Crewly version */
|
|
74
|
+
version: string;
|
|
75
|
+
/** Device capabilities */
|
|
76
|
+
capabilities: string[];
|
|
77
|
+
/** Active teams on this device */
|
|
78
|
+
teams: SyncTeamSummary[];
|
|
79
|
+
/** ISO timestamp */
|
|
80
|
+
timestamp: string;
|
|
81
|
+
}
|
|
82
|
+
/** All supported message types for inter-device communication. */
|
|
83
|
+
export type MessageType = 'command' | 'sync_teams' | 'sync_settings' | 'notification' | 'relay' | 'ping' | 'task_update';
|
|
84
|
+
/** Valid message type values for runtime validation. */
|
|
85
|
+
export declare const MESSAGE_TYPES: readonly MessageType[];
|
|
86
|
+
/**
|
|
87
|
+
* An incoming message received from another device via Cloud polling.
|
|
88
|
+
*/
|
|
89
|
+
export interface IncomingMessage {
|
|
90
|
+
/** Unique message identifier */
|
|
91
|
+
id: string;
|
|
92
|
+
/** Device ID of the sender */
|
|
93
|
+
from: string;
|
|
94
|
+
/** Human-readable name of the sending device */
|
|
95
|
+
fromDeviceName: string;
|
|
96
|
+
/** Message type */
|
|
97
|
+
type: MessageType;
|
|
98
|
+
/** Message payload (structure varies by type) */
|
|
99
|
+
payload: unknown;
|
|
100
|
+
/** Whether the payload is E2EE encrypted */
|
|
101
|
+
encrypted: boolean;
|
|
102
|
+
/** ISO timestamp when the message was sent */
|
|
103
|
+
sentAt: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Payload for a command message sent between devices.
|
|
107
|
+
*/
|
|
108
|
+
export interface CommandPayload {
|
|
109
|
+
/** The action to perform */
|
|
110
|
+
action: string;
|
|
111
|
+
/** Optional task identifier */
|
|
112
|
+
taskId?: string;
|
|
113
|
+
/** Optional team identifier */
|
|
114
|
+
teamId?: string;
|
|
115
|
+
/** Optional agent session name */
|
|
116
|
+
agentSession?: string;
|
|
117
|
+
/** Free-text content */
|
|
118
|
+
content?: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Payload for a notification message.
|
|
122
|
+
*/
|
|
123
|
+
export interface NotificationPayload {
|
|
124
|
+
/** Notification title */
|
|
125
|
+
title: string;
|
|
126
|
+
/** Notification body */
|
|
127
|
+
message: string;
|
|
128
|
+
/** Urgency level */
|
|
129
|
+
urgency: 'low' | 'normal' | 'high';
|
|
130
|
+
/** Optional team identifier */
|
|
131
|
+
teamId?: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Payload for a task_update message.
|
|
135
|
+
*/
|
|
136
|
+
export interface TaskUpdatePayload {
|
|
137
|
+
/** Task identifier */
|
|
138
|
+
taskId: string;
|
|
139
|
+
/** Current task status */
|
|
140
|
+
status: string;
|
|
141
|
+
/** Progress percentage (0-100) */
|
|
142
|
+
progress: number;
|
|
143
|
+
/** Summary of what was done */
|
|
144
|
+
summary?: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Check if a value is a valid CloudSyncState.
|
|
148
|
+
*
|
|
149
|
+
* @param value - Value to check
|
|
150
|
+
* @returns True if the value is a valid CloudSyncState
|
|
151
|
+
*/
|
|
152
|
+
export declare function isCloudSyncState(value: unknown): value is CloudSyncState;
|
|
153
|
+
/**
|
|
154
|
+
* Check if a value is a valid MessageType.
|
|
155
|
+
*
|
|
156
|
+
* @param value - Value to check
|
|
157
|
+
* @returns True if the value is a valid MessageType
|
|
158
|
+
*/
|
|
159
|
+
export declare function isMessageType(value: unknown): value is MessageType;
|
|
160
|
+
/**
|
|
161
|
+
* Check if an object satisfies the SyncDevice interface shape.
|
|
162
|
+
*
|
|
163
|
+
* @param value - Value to check
|
|
164
|
+
* @returns True if the value has the required SyncDevice fields
|
|
165
|
+
*/
|
|
166
|
+
export declare function isSyncDevice(value: unknown): value is SyncDevice;
|
|
167
|
+
/**
|
|
168
|
+
* Check if an object satisfies the IncomingMessage interface shape.
|
|
169
|
+
*
|
|
170
|
+
* @param value - Value to check
|
|
171
|
+
* @returns True if the value has the required IncomingMessage fields
|
|
172
|
+
*/
|
|
173
|
+
export declare function isIncomingMessage(value: unknown): value is IncomingMessage;
|
|
174
|
+
/**
|
|
175
|
+
* Check if an object satisfies the CloudSyncConfig interface shape.
|
|
176
|
+
*
|
|
177
|
+
* @param value - Value to check
|
|
178
|
+
* @returns True if the value has the required CloudSyncConfig fields
|
|
179
|
+
*/
|
|
180
|
+
export declare function isCloudSyncConfig(value: unknown): value is CloudSyncConfig;
|
|
181
|
+
//# sourceMappingURL=cloud-sync.types.d.ts.map
|