@plosson/agentio 0.4.2 → 0.4.3
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 +4 -4
- package/package.json +3 -1
- package/src/auth/oauth.ts +14 -2
- package/src/commands/gateway.ts +259 -0
- package/src/commands/gcal.ts +383 -0
- package/src/commands/gtasks.ts +326 -0
- package/src/commands/status.ts +85 -0
- package/src/commands/telegram.ts +209 -1
- package/src/commands/update.ts +2 -2
- package/src/commands/whatsapp.ts +853 -0
- package/src/config/config-manager.ts +1 -1
- package/src/gateway/adapters/telegram.ts +357 -0
- package/src/gateway/adapters/types.ts +147 -0
- package/src/gateway/adapters/whatsapp-auth.ts +172 -0
- package/src/gateway/adapters/whatsapp.ts +723 -0
- package/src/gateway/api.ts +791 -0
- package/src/gateway/client.ts +402 -0
- package/src/gateway/daemon.ts +461 -0
- package/src/gateway/store.ts +637 -0
- package/src/gateway/types.ts +325 -0
- package/src/gateway/webhook.ts +109 -0
- package/src/index.ts +32 -16
- package/src/polyfills.ts +10 -0
- package/src/services/gcal/client.ts +380 -0
- package/src/services/gtasks/client.ts +301 -0
- package/src/types/config.ts +36 -1
- package/src/types/gcal.ts +135 -0
- package/src/types/gtasks.ts +58 -0
- package/src/types/qrcode-terminal.d.ts +8 -0
- package/src/types/whatsapp.ts +116 -0
- package/src/utils/output.ts +505 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { readFile, writeFile, unlink } from 'fs/promises';
|
|
3
|
+
import { existsSync, openSync, closeSync, constants } from 'fs';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import type { ServiceName } from '../types/config';
|
|
6
|
+
import type { GatewayConfig, DaemonState, DEFAULT_GATEWAY_CONFIG } from './types';
|
|
7
|
+
import { CONFIG_DIR, loadConfig } from '../config/config-manager';
|
|
8
|
+
import { getCredentials } from '../auth/token-store';
|
|
9
|
+
import { initDatabase, closeDatabase, insertInboxMessage, inboxMessageExists, getPendingOutboxMessages, updateOutboxStatus, cleanupInbox, cleanupOutbox } from './store';
|
|
10
|
+
import { startApiServer, stopApiServer } from './api';
|
|
11
|
+
import { configureWebhook, queueWebhookNotification, flushWebhook, stopWebhook } from './webhook';
|
|
12
|
+
import type { ServiceAdapter, AdapterInboundMessage } from './adapters/types';
|
|
13
|
+
import { TelegramAdapter } from './adapters/telegram';
|
|
14
|
+
import { WhatsAppAdapter } from './adapters/whatsapp';
|
|
15
|
+
import type { TelegramCredentials } from '../types/telegram';
|
|
16
|
+
import type { WhatsAppCredentials } from '../types/whatsapp';
|
|
17
|
+
|
|
18
|
+
const PID_FILE = join(CONFIG_DIR, 'gateway.pid');
|
|
19
|
+
const LOG_FILE = join(CONFIG_DIR, 'gateway.log');
|
|
20
|
+
|
|
21
|
+
let isRunning = false;
|
|
22
|
+
let shutdownRequested = false;
|
|
23
|
+
let adapters: Map<ServiceName, ServiceAdapter> = new Map();
|
|
24
|
+
let outboxInterval: ReturnType<typeof setInterval> | null = null;
|
|
25
|
+
let cleanupInterval: ReturnType<typeof setInterval> | null = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the gateway configuration from config.json
|
|
29
|
+
*/
|
|
30
|
+
export async function getGatewayConfig(): Promise<GatewayConfig> {
|
|
31
|
+
const config = await loadConfig();
|
|
32
|
+
return (config as unknown as { gateway?: GatewayConfig }).gateway ?? {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if daemon is running
|
|
37
|
+
*/
|
|
38
|
+
export async function isDaemonRunning(): Promise<{ running: boolean; pid?: number }> {
|
|
39
|
+
if (!existsSync(PID_FILE)) {
|
|
40
|
+
return { running: false };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const pidStr = await readFile(PID_FILE, 'utf-8');
|
|
45
|
+
const pid = parseInt(pidStr.trim(), 10);
|
|
46
|
+
|
|
47
|
+
// Check if process is still running
|
|
48
|
+
try {
|
|
49
|
+
process.kill(pid, 0); // Doesn't kill, just checks
|
|
50
|
+
return { running: true, pid };
|
|
51
|
+
} catch {
|
|
52
|
+
// Process not running, clean up stale PID file
|
|
53
|
+
await unlink(PID_FILE).catch(() => {});
|
|
54
|
+
return { running: false };
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
return { running: false };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Write PID file
|
|
63
|
+
*/
|
|
64
|
+
async function writePidFile(): Promise<void> {
|
|
65
|
+
await writeFile(PID_FILE, process.pid.toString(), { mode: 0o600 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Remove PID file
|
|
70
|
+
*/
|
|
71
|
+
async function removePidFile(): Promise<void> {
|
|
72
|
+
await unlink(PID_FILE).catch(() => {});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handle inbound message from adapter
|
|
77
|
+
*/
|
|
78
|
+
function handleInboundMessage(service: ServiceName, profile: string, message: AdapterInboundMessage): void {
|
|
79
|
+
// Check for duplicates
|
|
80
|
+
if (inboxMessageExists(service, profile, message.platformId)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Insert into inbox
|
|
85
|
+
const inboxMessage = insertInboxMessage({
|
|
86
|
+
service,
|
|
87
|
+
profile,
|
|
88
|
+
conversationId: message.conversationId,
|
|
89
|
+
platformId: message.platformId,
|
|
90
|
+
senderId: message.senderId,
|
|
91
|
+
senderName: message.senderName,
|
|
92
|
+
senderHandle: message.senderHandle,
|
|
93
|
+
content: message.content,
|
|
94
|
+
mediaType: message.mediaType,
|
|
95
|
+
mediaPath: message.mediaUrl, // Store URL for now, download later if needed
|
|
96
|
+
receivedAt: message.receivedAt,
|
|
97
|
+
replyToId: message.replyToId,
|
|
98
|
+
metadata: message.metadata,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
console.log(`[inbox] New message: ${service}:${profile} from ${message.senderName || message.senderId}`);
|
|
102
|
+
|
|
103
|
+
// Queue webhook notification
|
|
104
|
+
queueWebhookNotification({
|
|
105
|
+
id: inboxMessage.id,
|
|
106
|
+
service,
|
|
107
|
+
profile,
|
|
108
|
+
sender: message.senderName || message.senderHandle || message.senderId,
|
|
109
|
+
preview: (message.content || '[media]').slice(0, 100),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Process outbox queue
|
|
115
|
+
*/
|
|
116
|
+
async function processOutbox(): Promise<void> {
|
|
117
|
+
const pendingMessages = getPendingOutboxMessages({ limit: 10 });
|
|
118
|
+
|
|
119
|
+
for (const message of pendingMessages) {
|
|
120
|
+
const adapter = adapters.get(message.service);
|
|
121
|
+
if (!adapter) {
|
|
122
|
+
updateOutboxStatus(message.id, 'failed', { error: 'No adapter for service' });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!adapter.isConnected(message.profile)) {
|
|
127
|
+
// Skip, will retry later
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Mark as sending
|
|
132
|
+
updateOutboxStatus(message.id, 'sending');
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const result = await adapter.send(message.profile, {
|
|
136
|
+
conversationId: message.conversationId,
|
|
137
|
+
content: message.content,
|
|
138
|
+
mediaPath: message.mediaPath,
|
|
139
|
+
mediaType: message.mediaType,
|
|
140
|
+
replyToPlatformId: message.replyToPlatformId,
|
|
141
|
+
metadata: message.metadata,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (result.success) {
|
|
145
|
+
updateOutboxStatus(message.id, 'sent', { platformId: result.platformId });
|
|
146
|
+
console.log(`[outbox] Sent: ${message.service}:${message.profile} -> ${message.conversationId}`);
|
|
147
|
+
} else {
|
|
148
|
+
updateOutboxStatus(message.id, 'failed', { error: result.error });
|
|
149
|
+
console.error(`[outbox] Failed: ${message.service}:${message.profile} - ${result.error}`);
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
153
|
+
updateOutboxStatus(message.id, 'failed', { error: errorMessage });
|
|
154
|
+
console.error(`[outbox] Error: ${message.service}:${message.profile} - ${errorMessage}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Run retention cleanup
|
|
161
|
+
*/
|
|
162
|
+
async function runCleanup(config: GatewayConfig): Promise<void> {
|
|
163
|
+
const retention = config.retention ?? {};
|
|
164
|
+
|
|
165
|
+
if (retention.doneMessagesDays && retention.doneMessagesDays > 0) {
|
|
166
|
+
const deleted = cleanupInbox(retention.doneMessagesDays);
|
|
167
|
+
if (deleted > 0) {
|
|
168
|
+
console.log(`[cleanup] Deleted ${deleted} old inbox messages`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (retention.sentMessagesDays && retention.sentMessagesDays > 0) {
|
|
173
|
+
const deleted = cleanupOutbox(retention.sentMessagesDays);
|
|
174
|
+
if (deleted > 0) {
|
|
175
|
+
console.log(`[cleanup] Deleted ${deleted} old outbox messages`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Initialize adapters based on configured profiles
|
|
182
|
+
*/
|
|
183
|
+
async function initializeAdapters(): Promise<void> {
|
|
184
|
+
const config = await loadConfig();
|
|
185
|
+
|
|
186
|
+
// Initialize Telegram adapter if profiles exist
|
|
187
|
+
const telegramProfiles = config.profiles.telegram || [];
|
|
188
|
+
if (telegramProfiles.length > 0) {
|
|
189
|
+
const telegramAdapter = new TelegramAdapter();
|
|
190
|
+
telegramAdapter.onMessage = (profile, message) => {
|
|
191
|
+
handleInboundMessage('telegram', profile, message);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
for (const profile of telegramProfiles) {
|
|
195
|
+
try {
|
|
196
|
+
const credentials = await getCredentials<TelegramCredentials>('telegram', profile);
|
|
197
|
+
if (credentials) {
|
|
198
|
+
await telegramAdapter.connect(profile, credentials);
|
|
199
|
+
} else {
|
|
200
|
+
console.error(`[telegram] No credentials for profile: ${profile}`);
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error(`[telegram] Failed to connect ${profile}:`, error instanceof Error ? error.message : error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
adapters.set('telegram', telegramAdapter);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Initialize WhatsApp adapter if profiles exist
|
|
211
|
+
const whatsappProfiles = config.profiles.whatsapp || [];
|
|
212
|
+
if (whatsappProfiles.length > 0) {
|
|
213
|
+
const whatsappAdapter = new WhatsAppAdapter();
|
|
214
|
+
whatsappAdapter.onMessage = (profile, message) => {
|
|
215
|
+
handleInboundMessage('whatsapp', profile, message);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
for (const profile of whatsappProfiles) {
|
|
219
|
+
try {
|
|
220
|
+
const credentials = await getCredentials<WhatsAppCredentials>('whatsapp', profile);
|
|
221
|
+
if (credentials) {
|
|
222
|
+
await whatsappAdapter.connect(profile, credentials);
|
|
223
|
+
} else {
|
|
224
|
+
console.error(`[whatsapp] No credentials for profile: ${profile}`);
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error(`[whatsapp] Failed to connect ${profile}:`, error instanceof Error ? error.message : error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
adapters.set('whatsapp', whatsappAdapter);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Shutdown all adapters
|
|
237
|
+
*/
|
|
238
|
+
async function shutdownAdapters(): Promise<void> {
|
|
239
|
+
for (const [service, adapter] of adapters) {
|
|
240
|
+
try {
|
|
241
|
+
await adapter.disconnectAll();
|
|
242
|
+
console.log(`[${service}] Disconnected all profiles`);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error(`[${service}] Shutdown error:`, error instanceof Error ? error.message : error);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
adapters.clear();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Start the gateway daemon
|
|
252
|
+
*/
|
|
253
|
+
export async function startDaemon(options: { foreground?: boolean } = {}): Promise<void> {
|
|
254
|
+
// Check if already running
|
|
255
|
+
const status = await isDaemonRunning();
|
|
256
|
+
if (status.running) {
|
|
257
|
+
throw new Error(`Gateway already running (PID ${status.pid})`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!options.foreground) {
|
|
261
|
+
// Fork to background
|
|
262
|
+
// Find the script path from argv or use import.meta to get current file location
|
|
263
|
+
let scriptPath: string;
|
|
264
|
+
|
|
265
|
+
// import.meta.path gives us the current file path, navigate to index.ts
|
|
266
|
+
const currentFile = import.meta.path || import.meta.url.replace('file://', '');
|
|
267
|
+
const srcDir = join(currentFile, '..', '..');
|
|
268
|
+
scriptPath = join(srcDir, 'index.ts');
|
|
269
|
+
|
|
270
|
+
// Verify the path exists, fallback to cwd-based path
|
|
271
|
+
if (!existsSync(scriptPath)) {
|
|
272
|
+
scriptPath = join(process.cwd(), 'src', 'index.ts');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Open log file for appending - child writes directly to file
|
|
276
|
+
const logFd = openSync(LOG_FILE, constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND, 0o644);
|
|
277
|
+
|
|
278
|
+
const child = spawn(process.execPath, [scriptPath, 'gateway', 'start', '--foreground'], {
|
|
279
|
+
detached: true,
|
|
280
|
+
stdio: ['ignore', logFd, logFd],
|
|
281
|
+
env: process.env,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
child.unref();
|
|
285
|
+
|
|
286
|
+
// Close the fd in parent - child has its own copy
|
|
287
|
+
closeSync(logFd);
|
|
288
|
+
|
|
289
|
+
console.log(`Gateway started in background (PID ${child.pid})`);
|
|
290
|
+
console.log(`Logs: ${LOG_FILE}`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Running in foreground
|
|
295
|
+
isRunning = true;
|
|
296
|
+
console.log(`Gateway starting (PID ${process.pid})`);
|
|
297
|
+
|
|
298
|
+
// Handle shutdown signals
|
|
299
|
+
const shutdown = async (signal: string) => {
|
|
300
|
+
if (shutdownRequested) return;
|
|
301
|
+
shutdownRequested = true;
|
|
302
|
+
|
|
303
|
+
console.log(`\nReceived ${signal}, shutting down...`);
|
|
304
|
+
|
|
305
|
+
// Stop intervals
|
|
306
|
+
if (outboxInterval) clearInterval(outboxInterval);
|
|
307
|
+
if (cleanupInterval) clearInterval(cleanupInterval);
|
|
308
|
+
|
|
309
|
+
// Flush webhooks
|
|
310
|
+
await flushWebhook();
|
|
311
|
+
stopWebhook();
|
|
312
|
+
|
|
313
|
+
// Shutdown adapters
|
|
314
|
+
await shutdownAdapters();
|
|
315
|
+
|
|
316
|
+
// Stop API server
|
|
317
|
+
stopApiServer();
|
|
318
|
+
|
|
319
|
+
// Close database
|
|
320
|
+
closeDatabase();
|
|
321
|
+
|
|
322
|
+
// Remove PID file
|
|
323
|
+
await removePidFile();
|
|
324
|
+
|
|
325
|
+
console.log('Gateway stopped');
|
|
326
|
+
process.exit(0);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
330
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
// Write PID file
|
|
334
|
+
await writePidFile();
|
|
335
|
+
|
|
336
|
+
// Load config
|
|
337
|
+
const gatewayConfig = await getGatewayConfig();
|
|
338
|
+
|
|
339
|
+
// Initialize database
|
|
340
|
+
await initDatabase();
|
|
341
|
+
console.log('Database initialized');
|
|
342
|
+
|
|
343
|
+
// Configure webhook
|
|
344
|
+
if (gatewayConfig.webhook?.url) {
|
|
345
|
+
configureWebhook(gatewayConfig.webhook);
|
|
346
|
+
console.log(`Webhook configured: ${gatewayConfig.webhook.url}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Initialize adapters
|
|
350
|
+
await initializeAdapters();
|
|
351
|
+
|
|
352
|
+
// Start API server
|
|
353
|
+
startApiServer(gatewayConfig.api, adapters);
|
|
354
|
+
|
|
355
|
+
// Start outbox processor (every 2 seconds)
|
|
356
|
+
outboxInterval = setInterval(processOutbox, 2000);
|
|
357
|
+
|
|
358
|
+
// Start cleanup job (every hour)
|
|
359
|
+
cleanupInterval = setInterval(() => runCleanup(gatewayConfig), 60 * 60 * 1000);
|
|
360
|
+
|
|
361
|
+
console.log('Gateway ready');
|
|
362
|
+
|
|
363
|
+
// Keep running
|
|
364
|
+
await new Promise(() => {}); // Wait forever
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error('Gateway error:', error instanceof Error ? error.message : error);
|
|
367
|
+
await removePidFile();
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Stop the gateway daemon
|
|
374
|
+
*/
|
|
375
|
+
export async function stopDaemon(): Promise<void> {
|
|
376
|
+
const status = await isDaemonRunning();
|
|
377
|
+
if (!status.running || !status.pid) {
|
|
378
|
+
console.log('Gateway is not running');
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
process.kill(status.pid, 'SIGTERM');
|
|
384
|
+
console.log(`Sent SIGTERM to gateway (PID ${status.pid})`);
|
|
385
|
+
|
|
386
|
+
// Wait for process to stop
|
|
387
|
+
for (let i = 0; i < 30; i++) {
|
|
388
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
389
|
+
try {
|
|
390
|
+
process.kill(status.pid, 0);
|
|
391
|
+
} catch {
|
|
392
|
+
console.log('Gateway stopped');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Force kill if still running
|
|
398
|
+
try {
|
|
399
|
+
process.kill(status.pid, 'SIGKILL');
|
|
400
|
+
console.log('Gateway force killed');
|
|
401
|
+
} catch {
|
|
402
|
+
console.log('Gateway stopped');
|
|
403
|
+
}
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error('Failed to stop gateway:', error instanceof Error ? error.message : error);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get daemon status
|
|
411
|
+
*/
|
|
412
|
+
export async function getDaemonStatus(): Promise<{
|
|
413
|
+
running: boolean;
|
|
414
|
+
pid?: number;
|
|
415
|
+
adapters?: { service: string; profile: string; connected: boolean }[];
|
|
416
|
+
}> {
|
|
417
|
+
const status = await isDaemonRunning();
|
|
418
|
+
if (!status.running) {
|
|
419
|
+
return { running: false };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Try to get status from API
|
|
423
|
+
const gatewayConfig = await getGatewayConfig();
|
|
424
|
+
const port = gatewayConfig.api?.port ?? 7890;
|
|
425
|
+
const host = gatewayConfig.api?.host ?? '127.0.0.1';
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const response = await fetch(`http://${host}:${port}/status`, {
|
|
429
|
+
headers: gatewayConfig.api?.secret ? { Authorization: `Bearer ${gatewayConfig.api.secret}` } : {},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (response.ok) {
|
|
433
|
+
const data = await response.json() as { adapters: { service: string; profile: string; connected: boolean }[] };
|
|
434
|
+
return {
|
|
435
|
+
running: true,
|
|
436
|
+
pid: status.pid,
|
|
437
|
+
adapters: data.adapters,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
} catch {
|
|
441
|
+
// API not responding, but process exists
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return { running: true, pid: status.pid };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Reload daemon configuration
|
|
449
|
+
*/
|
|
450
|
+
export async function reloadDaemon(): Promise<void> {
|
|
451
|
+
const status = await isDaemonRunning();
|
|
452
|
+
if (!status.running || !status.pid) {
|
|
453
|
+
throw new Error('Gateway is not running');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Send SIGHUP to trigger reload
|
|
457
|
+
process.kill(status.pid, 'SIGHUP');
|
|
458
|
+
console.log(`Sent reload signal to gateway (PID ${status.pid})`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export { PID_FILE, LOG_FILE };
|