@tjamescouch/agentchat 0.22.1 → 0.23.1

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.
Files changed (153) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/bin/agentchat.d.ts +7 -0
  3. package/dist/bin/agentchat.d.ts.map +1 -0
  4. package/dist/bin/agentchat.js +1511 -0
  5. package/dist/bin/agentchat.js.map +1 -0
  6. package/dist/lib/allowlist.d.ts +77 -0
  7. package/dist/lib/allowlist.d.ts.map +1 -0
  8. package/dist/lib/allowlist.js +151 -0
  9. package/dist/lib/allowlist.js.map +1 -0
  10. package/dist/lib/client.d.ts +147 -0
  11. package/dist/lib/client.d.ts.map +1 -0
  12. package/dist/lib/client.js +704 -0
  13. package/dist/lib/client.js.map +1 -0
  14. package/dist/lib/daemon.d.ts +122 -0
  15. package/dist/lib/daemon.d.ts.map +1 -0
  16. package/dist/lib/daemon.js +523 -0
  17. package/dist/lib/daemon.js.map +1 -0
  18. package/dist/lib/deploy/akash.d.ts +271 -0
  19. package/dist/lib/deploy/akash.d.ts.map +1 -0
  20. package/dist/lib/deploy/akash.js +671 -0
  21. package/dist/lib/deploy/akash.js.map +1 -0
  22. package/dist/lib/deploy/config.d.ts +62 -0
  23. package/dist/lib/deploy/config.d.ts.map +1 -0
  24. package/dist/lib/deploy/config.js +116 -0
  25. package/dist/lib/deploy/config.js.map +1 -0
  26. package/dist/lib/deploy/docker.d.ts +37 -0
  27. package/dist/lib/deploy/docker.d.ts.map +1 -0
  28. package/dist/lib/deploy/docker.js +122 -0
  29. package/dist/lib/deploy/docker.js.map +1 -0
  30. package/dist/lib/deploy/index.d.ts +11 -0
  31. package/dist/lib/deploy/index.d.ts.map +1 -0
  32. package/dist/lib/deploy/index.js +11 -0
  33. package/dist/lib/deploy/index.js.map +1 -0
  34. package/dist/lib/escrow-hooks.d.ts +199 -0
  35. package/dist/lib/escrow-hooks.d.ts.map +1 -0
  36. package/dist/lib/escrow-hooks.js +221 -0
  37. package/dist/lib/escrow-hooks.js.map +1 -0
  38. package/dist/lib/identity.d.ts +134 -0
  39. package/dist/lib/identity.d.ts.map +1 -0
  40. package/dist/lib/identity.js +334 -0
  41. package/dist/lib/identity.js.map +1 -0
  42. package/dist/lib/jitter.d.ts +42 -0
  43. package/dist/lib/jitter.d.ts.map +1 -0
  44. package/{lib/jitter.ts → dist/lib/jitter.js} +10 -18
  45. package/dist/lib/jitter.js.map +1 -0
  46. package/dist/lib/proposals.d.ts +223 -0
  47. package/dist/lib/proposals.d.ts.map +1 -0
  48. package/dist/lib/proposals.js +379 -0
  49. package/dist/lib/proposals.js.map +1 -0
  50. package/dist/lib/protocol.d.ts +220 -0
  51. package/dist/lib/protocol.d.ts.map +1 -0
  52. package/dist/lib/protocol.js +507 -0
  53. package/dist/lib/protocol.js.map +1 -0
  54. package/dist/lib/receipts.d.ts +134 -0
  55. package/dist/lib/receipts.d.ts.map +1 -0
  56. package/dist/lib/receipts.js +270 -0
  57. package/dist/lib/receipts.js.map +1 -0
  58. package/dist/lib/reputation.d.ts +250 -0
  59. package/dist/lib/reputation.d.ts.map +1 -0
  60. package/dist/lib/reputation.js +586 -0
  61. package/dist/lib/reputation.js.map +1 -0
  62. package/dist/lib/security.d.ts +27 -0
  63. package/dist/lib/security.d.ts.map +1 -0
  64. package/dist/lib/security.js +150 -0
  65. package/dist/lib/security.js.map +1 -0
  66. package/dist/lib/server/handlers/admin.d.ts +26 -0
  67. package/dist/lib/server/handlers/admin.d.ts.map +1 -0
  68. package/dist/lib/server/handlers/admin.js +76 -0
  69. package/dist/lib/server/handlers/admin.js.map +1 -0
  70. package/dist/lib/server/handlers/identity.d.ts +36 -0
  71. package/dist/lib/server/handlers/identity.d.ts.map +1 -0
  72. package/dist/lib/server/handlers/identity.js +330 -0
  73. package/dist/lib/server/handlers/identity.js.map +1 -0
  74. package/dist/lib/server/handlers/index.d.ts +10 -0
  75. package/dist/lib/server/handlers/index.d.ts.map +1 -0
  76. package/dist/lib/server/handlers/index.js +15 -0
  77. package/dist/lib/server/handlers/index.js.map +1 -0
  78. package/dist/lib/server/handlers/message.d.ts +47 -0
  79. package/dist/lib/server/handlers/message.d.ts.map +1 -0
  80. package/dist/lib/server/handlers/message.js +265 -0
  81. package/dist/lib/server/handlers/message.js.map +1 -0
  82. package/dist/lib/server/handlers/presence.d.ts +18 -0
  83. package/dist/lib/server/handlers/presence.d.ts.map +1 -0
  84. package/dist/lib/server/handlers/presence.js +35 -0
  85. package/dist/lib/server/handlers/presence.js.map +1 -0
  86. package/dist/lib/server/handlers/proposal.d.ts +38 -0
  87. package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
  88. package/dist/lib/server/handlers/proposal.js +273 -0
  89. package/dist/lib/server/handlers/proposal.js.map +1 -0
  90. package/dist/lib/server/handlers/skills.d.ts +22 -0
  91. package/dist/lib/server/handlers/skills.d.ts.map +1 -0
  92. package/dist/lib/server/handlers/skills.js +119 -0
  93. package/dist/lib/server/handlers/skills.js.map +1 -0
  94. package/dist/lib/server-directory.d.ts +85 -0
  95. package/dist/lib/server-directory.d.ts.map +1 -0
  96. package/dist/lib/server-directory.js +177 -0
  97. package/dist/lib/server-directory.js.map +1 -0
  98. package/dist/lib/server.d.ts +162 -0
  99. package/dist/lib/server.d.ts.map +1 -0
  100. package/dist/lib/server.js +602 -0
  101. package/dist/lib/server.js.map +1 -0
  102. package/dist/lib/types.d.ts +461 -0
  103. package/dist/lib/types.d.ts.map +1 -0
  104. package/dist/lib/types.js +98 -0
  105. package/dist/lib/types.js.map +1 -0
  106. package/package.json +22 -13
  107. package/bin/agentchat.js +0 -1617
  108. package/bin/agentchat.ts +0 -1812
  109. package/lib/allowlist.js +0 -162
  110. package/lib/chat.py +0 -241
  111. package/lib/client.js +0 -821
  112. package/lib/client.ts +0 -877
  113. package/lib/daemon.js +0 -562
  114. package/lib/daemon.ts +0 -662
  115. package/lib/deploy/akash.js +0 -811
  116. package/lib/deploy/config.js +0 -128
  117. package/lib/deploy/docker.js +0 -132
  118. package/lib/deploy/index.js +0 -24
  119. package/lib/elo_swarm.py +0 -569
  120. package/lib/escrow-hooks.js +0 -237
  121. package/lib/escrow-hooks.ts +0 -391
  122. package/lib/identity.js +0 -376
  123. package/lib/identity.ts +0 -412
  124. package/lib/jitter.js +0 -54
  125. package/lib/proposals.js +0 -426
  126. package/lib/proposals.ts +0 -612
  127. package/lib/protocol.js +0 -516
  128. package/lib/receipts.js +0 -294
  129. package/lib/receipts.ts +0 -359
  130. package/lib/reputation.js +0 -664
  131. package/lib/reputation.ts +0 -790
  132. package/lib/security.js +0 -183
  133. package/lib/server/handlers/admin.js +0 -94
  134. package/lib/server/handlers/identity.js +0 -258
  135. package/lib/server/handlers/index.js +0 -42
  136. package/lib/server/handlers/message.js +0 -319
  137. package/lib/server/handlers/presence.js +0 -45
  138. package/lib/server/handlers/proposal.js +0 -358
  139. package/lib/server/handlers/skills.js +0 -141
  140. package/lib/server-directory.js +0 -190
  141. package/lib/server-directory.ts +0 -232
  142. package/lib/server.js +0 -633
  143. package/lib/server.ts +0 -698
  144. package/lib/supervisor/USAGE.md +0 -110
  145. package/lib/supervisor/agent-health.sh +0 -107
  146. package/lib/supervisor/agent-monitor.sh +0 -123
  147. package/lib/supervisor/agent-supervisor.sh +0 -135
  148. package/lib/supervisor/agentctl.sh +0 -266
  149. package/lib/supervisor/god-backup.sh +0 -126
  150. package/lib/supervisor/god-watchdog.sh +0 -107
  151. package/lib/supervisor/killswitch.sh +0 -43
  152. package/lib/supervisor/notify.sh +0 -19
  153. package/lib/types.ts +0 -433
package/lib/daemon.js DELETED
@@ -1,562 +0,0 @@
1
- /**
2
- * AgentChat Daemon
3
- * Persistent connection with file-based inbox/outbox
4
- * Supports multiple instances with different identities
5
- */
6
-
7
- import fs from 'fs';
8
- import fsp from 'fs/promises';
9
- import path from 'path';
10
- import os from 'os';
11
- import { AgentChatClient } from './client.js';
12
- import { Identity, DEFAULT_IDENTITY_PATH } from './identity.js';
13
- import { appendReceipt, shouldStoreReceipt, DEFAULT_RECEIPTS_PATH } from './receipts.js';
14
- import { enforceDirectorySafety } from './security.js';
15
-
16
- // Base directory (cwd-relative for project-local storage)
17
- const AGENTCHAT_DIR = path.join(process.cwd(), '.agentchat');
18
- const DAEMONS_DIR = path.join(AGENTCHAT_DIR, 'daemons');
19
-
20
- // Default instance name
21
- const DEFAULT_INSTANCE = 'default';
22
-
23
- const DEFAULT_CHANNELS = ['#general', '#agents', '#code-review', '#servers'];
24
- const MAX_INBOX_LINES = 1000;
25
- const RECONNECT_DELAY = 5000; // 5 seconds
26
- const MAX_RECONNECT_TIME = 10 * 60 * 1000; // 10 minutes default
27
- const OUTBOX_POLL_INTERVAL = 500; // 500ms
28
-
29
- /**
30
- * Validate instance name to prevent path traversal
31
- * Only allows alphanumeric, hyphens, and underscores
32
- */
33
- function validateInstanceName(name) {
34
- if (!name || typeof name !== 'string') {
35
- return 'default';
36
- }
37
- // Strip any path separators and dangerous characters
38
- const sanitized = name.replace(/[^a-zA-Z0-9_-]/g, '');
39
- return sanitized || 'default';
40
- }
41
-
42
- /**
43
- * Get paths for a daemon instance
44
- */
45
- export function getDaemonPaths(instanceName = DEFAULT_INSTANCE) {
46
- const safeName = validateInstanceName(instanceName);
47
- const instanceDir = path.join(DAEMONS_DIR, safeName);
48
- return {
49
- dir: instanceDir,
50
- inbox: path.join(instanceDir, 'inbox.jsonl'),
51
- outbox: path.join(instanceDir, 'outbox.jsonl'),
52
- log: path.join(instanceDir, 'daemon.log'),
53
- pid: path.join(instanceDir, 'daemon.pid'),
54
- newdata: path.join(instanceDir, 'newdata') // Semaphore for new messages
55
- };
56
- }
57
-
58
- export class AgentChatDaemon {
59
- constructor(options = {}) {
60
- this.server = options.server;
61
- this.identityPath = options.identity || DEFAULT_IDENTITY_PATH;
62
- this.channels = options.channels || DEFAULT_CHANNELS;
63
- this.instanceName = options.name || DEFAULT_INSTANCE;
64
- this.maxReconnectTime = options.maxReconnectTime || MAX_RECONNECT_TIME;
65
-
66
- // Get instance-specific paths
67
- this.paths = getDaemonPaths(this.instanceName);
68
-
69
- this.client = null;
70
- this.running = false;
71
- this.reconnecting = false;
72
- this.reconnectStartTime = null; // Track when reconnection attempts started
73
- this.outboxWatcher = null;
74
- this.outboxPollInterval = null;
75
- this.lastOutboxSize = 0;
76
- }
77
-
78
- async _ensureDir() {
79
- await fsp.mkdir(this.paths.dir, { recursive: true });
80
- }
81
-
82
- _log(level, message) {
83
- const timestamp = new Date().toISOString();
84
- const line = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
85
-
86
- // Append to log file
87
- try {
88
- fs.appendFileSync(this.paths.log, line);
89
- } catch {
90
- // Directory might not exist yet
91
- }
92
-
93
- // Also output to console if not background
94
- if (level === 'error') {
95
- console.error(line.trim());
96
- } else {
97
- console.log(line.trim());
98
- }
99
- }
100
-
101
- async _appendToInbox(msg) {
102
- const line = JSON.stringify(msg) + '\n';
103
-
104
- // Append to inbox
105
- await fsp.appendFile(this.paths.inbox, line);
106
-
107
- // Touch semaphore file to signal new data
108
- await fsp.writeFile(this.paths.newdata, Date.now().toString());
109
-
110
- // Check if we need to truncate (ring buffer)
111
- await this._truncateInbox();
112
- }
113
-
114
- async _truncateInbox() {
115
- try {
116
- const content = await fsp.readFile(this.paths.inbox, 'utf-8');
117
- const lines = content.trim().split('\n');
118
-
119
- if (lines.length > MAX_INBOX_LINES) {
120
- // Keep only the last MAX_INBOX_LINES
121
- const newLines = lines.slice(-MAX_INBOX_LINES);
122
- await fsp.writeFile(this.paths.inbox, newLines.join('\n') + '\n');
123
- this._log('info', `Truncated inbox to ${MAX_INBOX_LINES} lines`);
124
- }
125
- } catch (err) {
126
- if (err.code !== 'ENOENT') {
127
- this._log('error', `Failed to truncate inbox: ${err.message}`);
128
- }
129
- }
130
- }
131
-
132
- async _saveReceiptIfParty(completeMsg) {
133
- try {
134
- // Get our agent ID
135
- const ourAgentId = this.client?.agentId;
136
- if (!ourAgentId) {
137
- return;
138
- }
139
-
140
- // Check if we should store this receipt
141
- if (shouldStoreReceipt(completeMsg, ourAgentId)) {
142
- await appendReceipt(completeMsg, DEFAULT_RECEIPTS_PATH);
143
- this._log('info', `Saved receipt for proposal ${completeMsg.proposal_id}`);
144
- }
145
- } catch (err) {
146
- this._log('error', `Failed to save receipt: ${err.message}`);
147
- }
148
- }
149
-
150
- async _processOutbox() {
151
- try {
152
- // Check if outbox exists
153
- try {
154
- await fsp.access(this.paths.outbox);
155
- } catch {
156
- return; // No outbox file
157
- }
158
-
159
- const content = await fsp.readFile(this.paths.outbox, 'utf-8');
160
- if (!content.trim()) return;
161
-
162
- const lines = content.trim().split('\n');
163
-
164
- for (const line of lines) {
165
- if (!line.trim()) continue;
166
-
167
- try {
168
- const msg = JSON.parse(line);
169
-
170
- if (msg.to && msg.content) {
171
- // Join channel if needed
172
- if (msg.to.startsWith('#') && !this.client.channels.has(msg.to)) {
173
- await this.client.join(msg.to);
174
- this._log('info', `Joined ${msg.to} for outbound message`);
175
- }
176
-
177
- await this.client.send(msg.to, msg.content);
178
- this._log('info', `Sent message to ${msg.to}: ${msg.content.substring(0, 50)}...`);
179
- } else {
180
- this._log('warn', `Invalid outbox message: ${line}`);
181
- }
182
- } catch (err) {
183
- this._log('error', `Failed to process outbox line: ${err.message}`);
184
- }
185
- }
186
-
187
- // Truncate outbox after processing
188
- await fsp.writeFile(this.paths.outbox, '');
189
-
190
- } catch (err) {
191
- if (err.code !== 'ENOENT') {
192
- this._log('error', `Outbox error: ${err.message}`);
193
- }
194
- }
195
- }
196
-
197
- _startOutboxWatcher() {
198
- // Use polling instead of fs.watch for reliability
199
- this.outboxPollInterval = setInterval(() => {
200
- if (this.client && this.client.connected) {
201
- this._processOutbox();
202
- }
203
- }, OUTBOX_POLL_INTERVAL);
204
-
205
- // Also try fs.watch for immediate response (may not work on all platforms)
206
- try {
207
- // Ensure outbox file exists
208
- if (!fs.existsSync(this.paths.outbox)) {
209
- fs.writeFileSync(this.paths.outbox, '');
210
- }
211
-
212
- this.outboxWatcher = fs.watch(this.paths.outbox, (eventType) => {
213
- if (eventType === 'change' && this.client && this.client.connected) {
214
- this._processOutbox();
215
- }
216
- });
217
- } catch (err) {
218
- this._log('warn', `fs.watch not available, using polling only: ${err.message}`);
219
- }
220
- }
221
-
222
- _stopOutboxWatcher() {
223
- if (this.outboxPollInterval) {
224
- clearInterval(this.outboxPollInterval);
225
- this.outboxPollInterval = null;
226
- }
227
- if (this.outboxWatcher) {
228
- this.outboxWatcher.close();
229
- this.outboxWatcher = null;
230
- }
231
- }
232
-
233
- async _connect() {
234
- this._log('info', `Connecting to ${this.server}...`);
235
-
236
- this.client = new AgentChatClient({
237
- server: this.server,
238
- identity: this.identityPath
239
- });
240
-
241
- // Set up event handlers
242
- this.client.on('message', async (msg) => {
243
- await this._appendToInbox(msg);
244
- });
245
-
246
- this.client.on('agent_joined', async (msg) => {
247
- await this._appendToInbox(msg);
248
- });
249
-
250
- this.client.on('agent_left', async (msg) => {
251
- await this._appendToInbox(msg);
252
- });
253
-
254
- this.client.on('proposal', async (msg) => {
255
- await this._appendToInbox(msg);
256
- });
257
-
258
- this.client.on('accept', async (msg) => {
259
- await this._appendToInbox(msg);
260
- });
261
-
262
- this.client.on('reject', async (msg) => {
263
- await this._appendToInbox(msg);
264
- });
265
-
266
- this.client.on('complete', async (msg) => {
267
- await this._appendToInbox(msg);
268
- // Save receipt if we're a party to this completion
269
- await this._saveReceiptIfParty(msg);
270
- });
271
-
272
- this.client.on('dispute', async (msg) => {
273
- await this._appendToInbox(msg);
274
- });
275
-
276
- this.client.on('disconnect', () => {
277
- this._log('warn', 'Disconnected from server');
278
- if (this.running && !this.reconnecting) {
279
- this._scheduleReconnect();
280
- }
281
- });
282
-
283
- this.client.on('error', (err) => {
284
- this._log('error', `Client error: ${err.message || JSON.stringify(err)}`);
285
- });
286
-
287
- try {
288
- await this.client.connect();
289
- this._log('info', `Connected as ${this.client.agentId}`);
290
-
291
- // Join channels
292
- for (const channel of this.channels) {
293
- try {
294
- await this.client.join(channel);
295
- this._log('info', `Joined ${channel}`);
296
- } catch (err) {
297
- this._log('error', `Failed to join ${channel}: ${err.message}`);
298
- }
299
- }
300
-
301
- return true;
302
- } catch (err) {
303
- this._log('error', `Connection failed: ${err.message}`);
304
- return false;
305
- }
306
- }
307
-
308
- _scheduleReconnect() {
309
- if (!this.running || this.reconnecting) return;
310
-
311
- // Start tracking reconnect time if this is the first attempt
312
- if (!this.reconnectStartTime) {
313
- this.reconnectStartTime = Date.now();
314
- }
315
-
316
- // Check if we've exceeded max reconnect time
317
- const elapsed = Date.now() - this.reconnectStartTime;
318
- if (elapsed >= this.maxReconnectTime) {
319
- this._log('error', `Max reconnect time (${this.maxReconnectTime / 1000 / 60} minutes) exceeded. Giving up.`);
320
- this._log('info', 'Daemon will exit. Restart manually or use a process manager.');
321
- this.stop();
322
- return;
323
- }
324
-
325
- this.reconnecting = true;
326
- const remaining = Math.round((this.maxReconnectTime - elapsed) / 1000);
327
- this._log('info', `Reconnecting in ${RECONNECT_DELAY / 1000} seconds... (${remaining}s until timeout)`);
328
-
329
- setTimeout(async () => {
330
- this.reconnecting = false;
331
- if (this.running) {
332
- const connected = await this._connect();
333
- if (connected) {
334
- // Reset reconnect timer on successful connection
335
- this.reconnectStartTime = null;
336
- this._log('info', 'Reconnected successfully');
337
- } else {
338
- this._scheduleReconnect();
339
- }
340
- }
341
- }, RECONNECT_DELAY);
342
- }
343
-
344
- async start() {
345
- // Security check: prevent running in root/system directories
346
- enforceDirectorySafety(process.cwd(), { allowWarnings: true, silent: false });
347
-
348
- this.running = true;
349
-
350
- // Ensure instance directory exists
351
- await this._ensureDir();
352
-
353
- // Write PID file
354
- await fsp.writeFile(this.paths.pid, process.pid.toString());
355
- this._log('info', `Daemon starting (PID: ${process.pid}, instance: ${this.instanceName})`);
356
-
357
- // Initialize inbox if it doesn't exist
358
- try {
359
- await fsp.access(this.paths.inbox);
360
- } catch {
361
- await fsp.writeFile(this.paths.inbox, '');
362
- }
363
-
364
- // Connect to server
365
- const connected = await this._connect();
366
- if (!connected) {
367
- this._scheduleReconnect();
368
- }
369
-
370
- // Start watching outbox
371
- this._startOutboxWatcher();
372
-
373
- // Handle shutdown signals
374
- process.on('SIGINT', () => this.stop());
375
- process.on('SIGTERM', () => this.stop());
376
-
377
- this._log('info', 'Daemon started');
378
- this._log('info', `Inbox: ${this.paths.inbox}`);
379
- this._log('info', `Outbox: ${this.paths.outbox}`);
380
- this._log('info', `Log: ${this.paths.log}`);
381
- }
382
-
383
- async stop() {
384
- this._log('info', 'Daemon stopping...');
385
- this.running = false;
386
-
387
- this._stopOutboxWatcher();
388
-
389
- if (this.client) {
390
- this.client.disconnect();
391
- }
392
-
393
- // Remove PID file
394
- try {
395
- await fsp.unlink(this.paths.pid);
396
- } catch {
397
- // Ignore if already gone
398
- }
399
-
400
- this._log('info', 'Daemon stopped');
401
- process.exit(0);
402
- }
403
- }
404
-
405
- /**
406
- * Check if daemon instance is running
407
- */
408
- export async function isDaemonRunning(instanceName = DEFAULT_INSTANCE) {
409
- const paths = getDaemonPaths(instanceName);
410
-
411
- try {
412
- const pid = await fsp.readFile(paths.pid, 'utf-8');
413
- const pidNum = parseInt(pid.trim());
414
-
415
- // Check if process is running
416
- try {
417
- process.kill(pidNum, 0);
418
- return { running: true, pid: pidNum, instance: instanceName };
419
- } catch {
420
- // Process not running, clean up stale PID file
421
- await fsp.unlink(paths.pid);
422
- return { running: false, instance: instanceName };
423
- }
424
- } catch {
425
- return { running: false, instance: instanceName };
426
- }
427
- }
428
-
429
- /**
430
- * Stop a daemon instance
431
- */
432
- export async function stopDaemon(instanceName = DEFAULT_INSTANCE) {
433
- const status = await isDaemonRunning(instanceName);
434
- if (!status.running) {
435
- return { stopped: false, reason: 'Daemon not running', instance: instanceName };
436
- }
437
-
438
- const paths = getDaemonPaths(instanceName);
439
-
440
- try {
441
- process.kill(status.pid, 'SIGTERM');
442
-
443
- // Wait a bit for clean shutdown
444
- await new Promise(r => setTimeout(r, 1000));
445
-
446
- // Check if still running
447
- try {
448
- process.kill(status.pid, 0);
449
- // Still running, force kill
450
- process.kill(status.pid, 'SIGKILL');
451
- } catch {
452
- // Process gone, good
453
- }
454
-
455
- // Clean up PID file
456
- try {
457
- await fsp.unlink(paths.pid);
458
- } catch {
459
- // Ignore
460
- }
461
-
462
- return { stopped: true, pid: status.pid, instance: instanceName };
463
- } catch (err) {
464
- return { stopped: false, reason: err.message, instance: instanceName };
465
- }
466
- }
467
-
468
- /**
469
- * Get daemon instance status
470
- */
471
- export async function getDaemonStatus(instanceName = DEFAULT_INSTANCE) {
472
- const status = await isDaemonRunning(instanceName);
473
- const paths = getDaemonPaths(instanceName);
474
-
475
- if (!status.running) {
476
- return {
477
- running: false,
478
- instance: instanceName
479
- };
480
- }
481
-
482
- // Get additional info
483
- let inboxLines = 0;
484
- let lastMessage = null;
485
-
486
- try {
487
- const content = await fsp.readFile(paths.inbox, 'utf-8');
488
- const lines = content.trim().split('\n').filter(l => l);
489
- inboxLines = lines.length;
490
-
491
- if (lines.length > 0) {
492
- try {
493
- lastMessage = JSON.parse(lines[lines.length - 1]);
494
- } catch {
495
- // Ignore parse errors
496
- }
497
- }
498
- } catch {
499
- // No inbox
500
- }
501
-
502
- return {
503
- running: true,
504
- instance: instanceName,
505
- pid: status.pid,
506
- inboxPath: paths.inbox,
507
- outboxPath: paths.outbox,
508
- logPath: paths.log,
509
- inboxLines,
510
- lastMessage
511
- };
512
- }
513
-
514
- /**
515
- * List all daemon instances
516
- */
517
- export async function listDaemons() {
518
- const instances = [];
519
-
520
- try {
521
- const entries = await fsp.readdir(DAEMONS_DIR, { withFileTypes: true });
522
-
523
- for (const entry of entries) {
524
- if (entry.isDirectory()) {
525
- const status = await isDaemonRunning(entry.name);
526
- instances.push({
527
- name: entry.name,
528
- running: status.running,
529
- pid: status.pid || null
530
- });
531
- }
532
- }
533
- } catch {
534
- // No daemons directory
535
- }
536
-
537
- return instances;
538
- }
539
-
540
- /**
541
- * Stop all running daemons
542
- */
543
- export async function stopAllDaemons() {
544
- const instances = await listDaemons();
545
- const results = [];
546
-
547
- for (const instance of instances) {
548
- if (instance.running) {
549
- const result = await stopDaemon(instance.name);
550
- results.push(result);
551
- }
552
- }
553
-
554
- return results;
555
- }
556
-
557
- // Export for CLI (backwards compatibility with default paths)
558
- export const INBOX_PATH = getDaemonPaths(DEFAULT_INSTANCE).inbox;
559
- export const OUTBOX_PATH = getDaemonPaths(DEFAULT_INSTANCE).outbox;
560
- export const LOG_PATH = getDaemonPaths(DEFAULT_INSTANCE).log;
561
- export const PID_PATH = getDaemonPaths(DEFAULT_INSTANCE).pid;
562
- export { DEFAULT_CHANNELS, DEFAULT_INSTANCE };