@gowelle/stint-agent 1.2.37 → 1.2.39
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/README.md +256 -256
- package/dist/{StatusDashboard-AMDPK7EQ.js → StatusDashboard-ROFBT73W.js} +2 -2
- package/dist/api-JGCDZSG6.js +7 -0
- package/dist/{chunk-IBGWKTT7.js → chunk-4WAUHGFS.js} +25 -5
- package/dist/{chunk-NODAAPCO.js → chunk-IFPIIRU3.js} +88 -12
- package/dist/{chunk-XRNTJYCQ.js → chunk-LU6CQVSL.js} +1 -1
- package/dist/{chunk-HPHXBSGB.js → chunk-PWOHR6IZ.js} +1 -1
- package/dist/daemon/runner.js +212 -39
- package/dist/index.js +7 -7
- package/package.json +102 -98
- package/dist/api-AFSILC7K.js +0 -7
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
apiService
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4WAUHGFS.js";
|
|
4
4
|
import {
|
|
5
5
|
gitService,
|
|
6
6
|
projectService
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LU6CQVSL.js";
|
|
8
8
|
import {
|
|
9
9
|
authService,
|
|
10
10
|
config,
|
|
11
11
|
logger
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PWOHR6IZ.js";
|
|
13
13
|
|
|
14
14
|
// src/utils/notify.ts
|
|
15
15
|
import notifier from "node-notifier";
|
|
@@ -263,10 +263,25 @@ var hookService = new HookService();
|
|
|
263
263
|
var CommitQueueProcessor = class {
|
|
264
264
|
queue = [];
|
|
265
265
|
isProcessing = false;
|
|
266
|
+
currentCommitId = null;
|
|
267
|
+
/**
|
|
268
|
+
* Check if commit is already in queue or processing
|
|
269
|
+
*/
|
|
270
|
+
hasCommit(commitId) {
|
|
271
|
+
if (this.currentCommitId === commitId) return true;
|
|
272
|
+
return this.queue.some((item) => item.commit.id === commitId);
|
|
273
|
+
}
|
|
266
274
|
/**
|
|
267
275
|
* Add commit to processing queue
|
|
268
276
|
*/
|
|
269
277
|
addToQueue(commit, project) {
|
|
278
|
+
if (this.hasCommit(commit.id)) {
|
|
279
|
+
logger.info(
|
|
280
|
+
"queue",
|
|
281
|
+
`Commit ${commit.id} is already in the queue or being processed. Skipping.`
|
|
282
|
+
);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
270
285
|
this.queue.push({ commit, project });
|
|
271
286
|
logger.info(
|
|
272
287
|
"queue",
|
|
@@ -287,6 +302,7 @@ var CommitQueueProcessor = class {
|
|
|
287
302
|
while (this.queue.length > 0) {
|
|
288
303
|
const item = this.queue.shift();
|
|
289
304
|
if (!item) break;
|
|
305
|
+
this.currentCommitId = item.commit.id;
|
|
290
306
|
try {
|
|
291
307
|
await this.executeCommit(item.commit, item.project);
|
|
292
308
|
} catch (error) {
|
|
@@ -295,6 +311,8 @@ var CommitQueueProcessor = class {
|
|
|
295
311
|
`Failed to execute commit ${item.commit.id}`,
|
|
296
312
|
error
|
|
297
313
|
);
|
|
314
|
+
} finally {
|
|
315
|
+
this.currentCommitId = null;
|
|
298
316
|
}
|
|
299
317
|
}
|
|
300
318
|
this.isProcessing = false;
|
|
@@ -595,10 +613,12 @@ var WebSocketServiceImpl = class {
|
|
|
595
613
|
echo = null;
|
|
596
614
|
userId = null;
|
|
597
615
|
reconnectAttempts = 0;
|
|
598
|
-
maxReconnectAttempts =
|
|
616
|
+
maxReconnectAttempts = -1;
|
|
617
|
+
// -1 = infinite reconnection
|
|
599
618
|
reconnectTimer = null;
|
|
600
619
|
isManualDisconnect = false;
|
|
601
620
|
currentPusherClient = null;
|
|
621
|
+
lastSuccessfulConnection = null;
|
|
602
622
|
// Event handlers
|
|
603
623
|
commitApprovedHandlers = [];
|
|
604
624
|
commitPendingHandlers = [];
|
|
@@ -775,6 +795,7 @@ var WebSocketServiceImpl = class {
|
|
|
775
795
|
"\u2705 Connected to Broadcaster via Sanctum"
|
|
776
796
|
);
|
|
777
797
|
writeStatus({ connected: true });
|
|
798
|
+
this.lastSuccessfulConnection = /* @__PURE__ */ new Date();
|
|
778
799
|
this.reconnectAttempts = 0;
|
|
779
800
|
this.isManualDisconnect = false;
|
|
780
801
|
safeResolve();
|
|
@@ -890,7 +911,7 @@ var WebSocketServiceImpl = class {
|
|
|
890
911
|
"websocket",
|
|
891
912
|
`Commit ${commit.id} marked as large, fetching full details...`
|
|
892
913
|
);
|
|
893
|
-
const { apiService: apiService2 } = await import("./api-
|
|
914
|
+
const { apiService: apiService2 } = await import("./api-JGCDZSG6.js");
|
|
894
915
|
const fullCommit = await apiService2.getCommit(commit.id);
|
|
895
916
|
commit = {
|
|
896
917
|
...commit,
|
|
@@ -934,7 +955,7 @@ var WebSocketServiceImpl = class {
|
|
|
934
955
|
(handler) => handler(data.suggestion)
|
|
935
956
|
);
|
|
936
957
|
}).listen(".project.updated", (data) => {
|
|
937
|
-
logger.
|
|
958
|
+
logger.debug("websocket", `Project updated: ${data.project.id}`);
|
|
938
959
|
writeStatus({
|
|
939
960
|
lastEvent: "project.updated",
|
|
940
961
|
lastEventTime: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -997,19 +1018,30 @@ var WebSocketServiceImpl = class {
|
|
|
997
1018
|
clearTimeout(this.reconnectTimer);
|
|
998
1019
|
this.reconnectTimer = null;
|
|
999
1020
|
}
|
|
1000
|
-
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
1021
|
+
if (this.maxReconnectAttempts === -1 || this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
1001
1022
|
const delay = this.getReconnectDelay();
|
|
1002
1023
|
this.reconnectAttempts++;
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`
|
|
1006
|
-
);
|
|
1024
|
+
const attemptInfo = this.maxReconnectAttempts === -1 ? `attempt ${this.reconnectAttempts}` : `attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`;
|
|
1025
|
+
logger.info("websocket", `Reconnecting in ${delay}ms (${attemptInfo})`);
|
|
1007
1026
|
this.reconnectTimer = setTimeout(async () => {
|
|
1008
1027
|
try {
|
|
1028
|
+
const token = await authService.getToken();
|
|
1029
|
+
if (!token) {
|
|
1030
|
+
logger.error(
|
|
1031
|
+
"websocket",
|
|
1032
|
+
"Cannot reconnect: authentication token expired or missing"
|
|
1033
|
+
);
|
|
1034
|
+
this.handleDisconnect();
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1009
1037
|
await this.connect();
|
|
1010
1038
|
if (this.userId) {
|
|
1011
1039
|
await this.subscribeToUserChannel(this.userId);
|
|
1012
1040
|
}
|
|
1041
|
+
logger.success(
|
|
1042
|
+
"websocket",
|
|
1043
|
+
`Reconnected successfully after ${this.reconnectAttempts} attempts`
|
|
1044
|
+
);
|
|
1013
1045
|
} catch (error) {
|
|
1014
1046
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1015
1047
|
if (errorMessage.includes("disconnected during connection")) {
|
|
@@ -1026,12 +1058,56 @@ var WebSocketServiceImpl = class {
|
|
|
1026
1058
|
logger.error("websocket", "Max reconnection attempts reached");
|
|
1027
1059
|
}
|
|
1028
1060
|
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Force a reconnection attempt - used by health monitor
|
|
1063
|
+
* Resets attempt counter and triggers immediate reconnection
|
|
1064
|
+
*/
|
|
1065
|
+
async forceReconnect() {
|
|
1066
|
+
logger.info("websocket", "Force reconnect requested by health monitor");
|
|
1067
|
+
this.reconnectAttempts = 0;
|
|
1068
|
+
if (this.reconnectTimer) {
|
|
1069
|
+
clearTimeout(this.reconnectTimer);
|
|
1070
|
+
this.reconnectTimer = null;
|
|
1071
|
+
}
|
|
1072
|
+
if (this.echo) {
|
|
1073
|
+
this.currentPusherClient = null;
|
|
1074
|
+
this.echo.disconnect();
|
|
1075
|
+
this.echo = null;
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
await this.connect();
|
|
1079
|
+
if (this.userId) {
|
|
1080
|
+
await this.subscribeToUserChannel(this.userId);
|
|
1081
|
+
}
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
logger.error("websocket", "Force reconnect failed", error);
|
|
1084
|
+
this.handleDisconnect();
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Get the timestamp of last successful connection
|
|
1089
|
+
* Used by health monitor to detect stale connections
|
|
1090
|
+
*/
|
|
1091
|
+
getLastSuccessfulConnection() {
|
|
1092
|
+
return this.lastSuccessfulConnection;
|
|
1093
|
+
}
|
|
1029
1094
|
/**
|
|
1030
1095
|
* Get reconnect delay with exponential backoff and jitter
|
|
1031
1096
|
* Jitter prevents thundering herd problem when many clients reconnect simultaneously
|
|
1097
|
+
* Caps at 5 minutes for long-running daemon resilience
|
|
1032
1098
|
*/
|
|
1033
1099
|
getReconnectDelay() {
|
|
1034
|
-
const delays = [
|
|
1100
|
+
const delays = [
|
|
1101
|
+
1e3,
|
|
1102
|
+
2e3,
|
|
1103
|
+
4e3,
|
|
1104
|
+
8e3,
|
|
1105
|
+
16e3,
|
|
1106
|
+
3e4,
|
|
1107
|
+
6e4,
|
|
1108
|
+
12e4,
|
|
1109
|
+
3e5
|
|
1110
|
+
];
|
|
1035
1111
|
const index = Math.min(this.reconnectAttempts, delays.length - 1);
|
|
1036
1112
|
const baseDelay = delays[index];
|
|
1037
1113
|
const jitter = baseDelay * (Math.random() * 0.3);
|
|
@@ -346,7 +346,7 @@ var AuthServiceImpl = class {
|
|
|
346
346
|
return null;
|
|
347
347
|
}
|
|
348
348
|
try {
|
|
349
|
-
const { apiService } = await import("./api-
|
|
349
|
+
const { apiService } = await import("./api-JGCDZSG6.js");
|
|
350
350
|
const user = await apiService.getCurrentUser();
|
|
351
351
|
logger.info("auth", `Token validated for user: ${user.email}`);
|
|
352
352
|
return user;
|
package/dist/daemon/runner.js
CHANGED
|
@@ -3,20 +3,20 @@ import {
|
|
|
3
3
|
commitQueue,
|
|
4
4
|
notify,
|
|
5
5
|
websocketService
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-IFPIIRU3.js";
|
|
7
7
|
import {
|
|
8
8
|
apiService
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-4WAUHGFS.js";
|
|
10
10
|
import {
|
|
11
11
|
gitService,
|
|
12
12
|
projectService,
|
|
13
13
|
removePidFile,
|
|
14
14
|
writePidFile
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-LU6CQVSL.js";
|
|
16
16
|
import {
|
|
17
17
|
authService,
|
|
18
18
|
logger
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-PWOHR6IZ.js";
|
|
20
20
|
|
|
21
21
|
// src/daemon/runner.ts
|
|
22
22
|
import "dotenv/config";
|
|
@@ -310,31 +310,173 @@ async function syncPendingCommits() {
|
|
|
310
310
|
"sync",
|
|
311
311
|
`Checking ${relevantProjects.length} projects for pending commits...`
|
|
312
312
|
);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
313
|
+
await Promise.all(
|
|
314
|
+
relevantProjects.map(async (project) => {
|
|
315
|
+
try {
|
|
316
|
+
const pendingCommits = await apiService.getPendingCommits(project.id);
|
|
317
|
+
if (pendingCommits.length > 0) {
|
|
318
|
+
logger.info(
|
|
319
|
+
"sync",
|
|
320
|
+
`Found ${pendingCommits.length} pending commits for project ${project.name}`
|
|
321
|
+
);
|
|
322
|
+
for (const commit of pendingCommits) {
|
|
323
|
+
commitQueue.addToQueue(commit, project);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
logger.error(
|
|
318
328
|
"sync",
|
|
319
|
-
`
|
|
329
|
+
`Failed to sync commits for project ${project.name}`,
|
|
330
|
+
error
|
|
320
331
|
);
|
|
321
|
-
for (const commit of pendingCommits) {
|
|
322
|
-
commitQueue.addToQueue(commit, project);
|
|
323
|
-
}
|
|
324
332
|
}
|
|
333
|
+
})
|
|
334
|
+
);
|
|
335
|
+
logger.success("sync", "Pending commit sync complete");
|
|
336
|
+
} catch (error) {
|
|
337
|
+
logger.error("sync", "Failed to sync pending commits", error);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/daemon/health.ts
|
|
342
|
+
var HealthMonitor = class {
|
|
343
|
+
checkInterval = null;
|
|
344
|
+
startTime = null;
|
|
345
|
+
lastSuccessfulHeartbeat = null;
|
|
346
|
+
consecutiveFailures = 0;
|
|
347
|
+
maxConsecutiveFailures = 3;
|
|
348
|
+
checkIntervalMs = 9e4;
|
|
349
|
+
// 90 seconds
|
|
350
|
+
/**
|
|
351
|
+
* Start the health monitor
|
|
352
|
+
*/
|
|
353
|
+
start() {
|
|
354
|
+
if (this.checkInterval) {
|
|
355
|
+
logger.warn("health", "Health monitor already running");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
this.startTime = /* @__PURE__ */ new Date();
|
|
359
|
+
this.consecutiveFailures = 0;
|
|
360
|
+
logger.info("health", "Starting health monitor (90s interval)");
|
|
361
|
+
this.checkInterval = setInterval(() => {
|
|
362
|
+
this.performHealthCheck().catch((error) => {
|
|
363
|
+
logger.error("health", "Health check error", error);
|
|
364
|
+
});
|
|
365
|
+
}, this.checkIntervalMs);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Stop the health monitor
|
|
369
|
+
*/
|
|
370
|
+
stop() {
|
|
371
|
+
if (this.checkInterval) {
|
|
372
|
+
clearInterval(this.checkInterval);
|
|
373
|
+
this.checkInterval = null;
|
|
374
|
+
logger.info("health", "Health monitor stopped");
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Record a successful heartbeat
|
|
379
|
+
* Called by the daemon after each successful API heartbeat
|
|
380
|
+
*/
|
|
381
|
+
recordHeartbeat() {
|
|
382
|
+
this.lastSuccessfulHeartbeat = /* @__PURE__ */ new Date();
|
|
383
|
+
if (this.consecutiveFailures > 0) {
|
|
384
|
+
logger.info(
|
|
385
|
+
"health",
|
|
386
|
+
`Health restored after ${this.consecutiveFailures} consecutive failures`
|
|
387
|
+
);
|
|
388
|
+
this.consecutiveFailures = 0;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Get current health status
|
|
393
|
+
*/
|
|
394
|
+
getStatus() {
|
|
395
|
+
const websocketConnected = websocketService.isConnected();
|
|
396
|
+
const lastSuccessfulConnection = websocketService.getLastSuccessfulConnection();
|
|
397
|
+
const heartbeatStale = this.isHeartbeatStale();
|
|
398
|
+
const healthy = websocketConnected && !heartbeatStale;
|
|
399
|
+
return {
|
|
400
|
+
healthy,
|
|
401
|
+
websocketConnected,
|
|
402
|
+
lastSuccessfulHeartbeat: this.lastSuccessfulHeartbeat,
|
|
403
|
+
lastSuccessfulConnection,
|
|
404
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
405
|
+
uptime: this.startTime ? Math.floor((Date.now() - this.startTime.getTime()) / 1e3) : 0
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Perform a health check and trigger recovery if needed
|
|
410
|
+
*/
|
|
411
|
+
async performHealthCheck() {
|
|
412
|
+
const status = this.getStatus();
|
|
413
|
+
logger.debug(
|
|
414
|
+
"health",
|
|
415
|
+
`Health check: connected=${status.websocketConnected}, heartbeat_stale=${this.isHeartbeatStale()}, failures=${this.consecutiveFailures}`
|
|
416
|
+
);
|
|
417
|
+
if (!status.healthy) {
|
|
418
|
+
this.consecutiveFailures++;
|
|
419
|
+
logger.warn(
|
|
420
|
+
"health",
|
|
421
|
+
`Unhealthy state detected (${this.consecutiveFailures}/${this.maxConsecutiveFailures})`
|
|
422
|
+
);
|
|
423
|
+
if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
|
|
424
|
+
await this.triggerRecovery();
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
if (this.consecutiveFailures > 0) {
|
|
428
|
+
logger.info("health", "Health restored");
|
|
429
|
+
this.consecutiveFailures = 0;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Check if heartbeat is stale (no successful heartbeat in 3 minutes)
|
|
435
|
+
*/
|
|
436
|
+
isHeartbeatStale() {
|
|
437
|
+
if (!this.lastSuccessfulHeartbeat) {
|
|
438
|
+
if (this.startTime) {
|
|
439
|
+
const sinceSart = Date.now() - this.startTime.getTime();
|
|
440
|
+
return sinceSart > 12e4;
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
const staleThreshold = 18e4;
|
|
445
|
+
const timeSinceHeartbeat = Date.now() - this.lastSuccessfulHeartbeat.getTime();
|
|
446
|
+
return timeSinceHeartbeat > staleThreshold;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Trigger recovery actions
|
|
450
|
+
*/
|
|
451
|
+
async triggerRecovery() {
|
|
452
|
+
logger.warn(
|
|
453
|
+
"health",
|
|
454
|
+
"Triggering recovery due to persistent unhealthy state"
|
|
455
|
+
);
|
|
456
|
+
this.consecutiveFailures = 0;
|
|
457
|
+
if (!websocketService.isConnected()) {
|
|
458
|
+
logger.info("health", "Forcing WebSocket reconnection...");
|
|
459
|
+
try {
|
|
460
|
+
await websocketService.forceReconnect();
|
|
461
|
+
logger.success("health", "WebSocket reconnection initiated");
|
|
325
462
|
} catch (error) {
|
|
326
463
|
logger.error(
|
|
327
|
-
"
|
|
328
|
-
|
|
464
|
+
"health",
|
|
465
|
+
"Failed to force WebSocket reconnection",
|
|
329
466
|
error
|
|
330
467
|
);
|
|
331
468
|
}
|
|
332
469
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
470
|
+
try {
|
|
471
|
+
await apiService.heartbeat();
|
|
472
|
+
this.recordHeartbeat();
|
|
473
|
+
logger.success("health", "API session verified");
|
|
474
|
+
} catch (error) {
|
|
475
|
+
logger.error("health", "API session check failed", error);
|
|
476
|
+
}
|
|
336
477
|
}
|
|
337
|
-
}
|
|
478
|
+
};
|
|
479
|
+
var healthMonitor = new HealthMonitor();
|
|
338
480
|
|
|
339
481
|
// src/daemon/index.ts
|
|
340
482
|
var heartbeatInterval = null;
|
|
@@ -390,18 +532,16 @@ Project: ${project.name}`,
|
|
|
390
532
|
});
|
|
391
533
|
});
|
|
392
534
|
websocketService.onProjectUpdated((project) => {
|
|
393
|
-
logger.
|
|
535
|
+
logger.debug(
|
|
536
|
+
"daemon",
|
|
537
|
+
`Project updated: ${project.id} - ${project.name}`
|
|
538
|
+
);
|
|
394
539
|
if (project.settings?.auto_sync) {
|
|
395
540
|
fileWatcher.updateProjectSettings(
|
|
396
541
|
project.id,
|
|
397
542
|
project.settings.auto_sync
|
|
398
543
|
);
|
|
399
544
|
}
|
|
400
|
-
notify({
|
|
401
|
-
title: "Project Updated",
|
|
402
|
-
message: project.name,
|
|
403
|
-
category: "sync"
|
|
404
|
-
});
|
|
405
545
|
});
|
|
406
546
|
websocketService.onDisconnect(() => {
|
|
407
547
|
logger.warn(
|
|
@@ -456,6 +596,7 @@ Priority: ${suggestion.priority}`,
|
|
|
456
596
|
});
|
|
457
597
|
setupSignalHandlers();
|
|
458
598
|
startHeartbeat();
|
|
599
|
+
healthMonitor.start();
|
|
459
600
|
logger.info("daemon", "Starting file watcher...");
|
|
460
601
|
fileWatcher.start();
|
|
461
602
|
logger.info("daemon", "Daemon started successfully");
|
|
@@ -466,17 +607,19 @@ Priority: ${suggestion.priority}`,
|
|
|
466
607
|
"daemon",
|
|
467
608
|
`Syncing ${projectEntries.length} linked project(s) on startup...`
|
|
468
609
|
);
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
610
|
+
await Promise.all(
|
|
611
|
+
projectEntries.map(async ([, linkedProject]) => {
|
|
612
|
+
try {
|
|
613
|
+
await fileWatcher.syncProjectById(linkedProject.projectId);
|
|
614
|
+
} catch (error) {
|
|
615
|
+
logger.error(
|
|
616
|
+
"daemon",
|
|
617
|
+
`Failed to sync project ${linkedProject.projectId} on startup`,
|
|
618
|
+
error
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
})
|
|
622
|
+
);
|
|
480
623
|
logger.success("daemon", "Initial project sync complete");
|
|
481
624
|
}
|
|
482
625
|
await syncPendingCommits();
|
|
@@ -496,6 +639,7 @@ function startHeartbeat() {
|
|
|
496
639
|
if (isShuttingDown) return;
|
|
497
640
|
try {
|
|
498
641
|
await apiService.heartbeat();
|
|
642
|
+
healthMonitor.recordHeartbeat();
|
|
499
643
|
logger.debug("daemon", "Heartbeat sent successfully");
|
|
500
644
|
} catch (error) {
|
|
501
645
|
logger.error("daemon", "Heartbeat failed", error);
|
|
@@ -525,6 +669,7 @@ async function shutdown(reason) {
|
|
|
525
669
|
isShuttingDown = true;
|
|
526
670
|
shutdownReason = reason;
|
|
527
671
|
logger.info("daemon", "Shutting down daemon...");
|
|
672
|
+
healthMonitor.stop();
|
|
528
673
|
stopHeartbeat();
|
|
529
674
|
try {
|
|
530
675
|
fileWatcher.stop();
|
|
@@ -554,7 +699,35 @@ async function shutdown(reason) {
|
|
|
554
699
|
|
|
555
700
|
// src/daemon/runner.ts
|
|
556
701
|
writePidFile(process.pid);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
702
|
+
var startupAttempts = 0;
|
|
703
|
+
var maxStartupAttempts = 5;
|
|
704
|
+
async function startWithRetry() {
|
|
705
|
+
while (startupAttempts < maxStartupAttempts) {
|
|
706
|
+
try {
|
|
707
|
+
startupAttempts++;
|
|
708
|
+
await startDaemon();
|
|
709
|
+
logger.warn("daemon-runner", "Daemon exited unexpectedly, restarting...");
|
|
710
|
+
} catch (error) {
|
|
711
|
+
logger.error(
|
|
712
|
+
"daemon-runner",
|
|
713
|
+
`Daemon startup failed (attempt ${startupAttempts}/${maxStartupAttempts})`,
|
|
714
|
+
error
|
|
715
|
+
);
|
|
716
|
+
if (startupAttempts >= maxStartupAttempts) {
|
|
717
|
+
logger.error("daemon-runner", "Max startup attempts reached, exiting");
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
const delay = Math.min(5e3 * Math.pow(2, startupAttempts - 1), 6e4);
|
|
721
|
+
logger.info("daemon-runner", `Retrying in ${delay}ms...`);
|
|
722
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
process.on("uncaughtException", (error) => {
|
|
727
|
+
logger.error("daemon-runner", "Uncaught exception", error);
|
|
728
|
+
});
|
|
729
|
+
process.on("unhandledRejection", (reason) => {
|
|
730
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
731
|
+
logger.error("daemon-runner", "Unhandled rejection", error);
|
|
560
732
|
});
|
|
733
|
+
startWithRetry();
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
commitQueue,
|
|
4
4
|
websocketService
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-IFPIIRU3.js";
|
|
6
6
|
import {
|
|
7
7
|
apiService
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4WAUHGFS.js";
|
|
9
9
|
import {
|
|
10
10
|
getPidFilePath,
|
|
11
11
|
gitService,
|
|
@@ -14,14 +14,14 @@ import {
|
|
|
14
14
|
projectService,
|
|
15
15
|
spawnDetached,
|
|
16
16
|
validatePidFile
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-LU6CQVSL.js";
|
|
18
18
|
import {
|
|
19
19
|
__commonJS,
|
|
20
20
|
__toESM,
|
|
21
21
|
authService,
|
|
22
22
|
config,
|
|
23
23
|
logger
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-PWOHR6IZ.js";
|
|
25
25
|
|
|
26
26
|
// node_modules/semver/internal/constants.js
|
|
27
27
|
var require_constants = __commonJS({
|
|
@@ -1274,7 +1274,7 @@ var require_range = __commonJS({
|
|
|
1274
1274
|
var require_comparator = __commonJS({
|
|
1275
1275
|
"node_modules/semver/classes/comparator.js"(exports, module) {
|
|
1276
1276
|
"use strict";
|
|
1277
|
-
var ANY =
|
|
1277
|
+
var ANY = Symbol("SemVer ANY");
|
|
1278
1278
|
var Comparator = class _Comparator {
|
|
1279
1279
|
static get ANY() {
|
|
1280
1280
|
return ANY;
|
|
@@ -2657,7 +2657,7 @@ function registerStatusCommand(program2) {
|
|
|
2657
2657
|
try {
|
|
2658
2658
|
const { render } = await import("ink");
|
|
2659
2659
|
const { createElement } = await import("react");
|
|
2660
|
-
const { StatusDashboard } = await import("./StatusDashboard-
|
|
2660
|
+
const { StatusDashboard } = await import("./StatusDashboard-ROFBT73W.js");
|
|
2661
2661
|
render(createElement(StatusDashboard, { cwd }));
|
|
2662
2662
|
return;
|
|
2663
2663
|
} catch (error) {
|
|
@@ -4846,7 +4846,7 @@ ${chalk14.bold("Config file:")} ${chalk14.cyan(configPath)}
|
|
|
4846
4846
|
}
|
|
4847
4847
|
|
|
4848
4848
|
// src/index.ts
|
|
4849
|
-
var AGENT_VERSION = "1.2.
|
|
4849
|
+
var AGENT_VERSION = "1.2.39";
|
|
4850
4850
|
var program = new Command();
|
|
4851
4851
|
program.name("stint").description("Stint Agent - Local daemon for Stint Project Assistant").version(AGENT_VERSION, "-v, --version", "output the current version").addHelpText(
|
|
4852
4852
|
"after",
|