@agent-relay/daemon 0.1.0
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/agent-manager.d.ts +134 -0
- package/dist/agent-manager.d.ts.map +1 -0
- package/dist/agent-manager.js +578 -0
- package/dist/agent-manager.js.map +1 -0
- package/dist/agent-registry.d.ts +99 -0
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js +213 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/agent-signing.d.ts +158 -0
- package/dist/agent-signing.d.ts.map +1 -0
- package/dist/agent-signing.js +523 -0
- package/dist/agent-signing.js.map +1 -0
- package/dist/api.d.ts +106 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +876 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +94 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +197 -0
- package/dist/auth.js.map +1 -0
- package/dist/channel-membership-store.d.ts +55 -0
- package/dist/channel-membership-store.d.ts.map +1 -0
- package/dist/channel-membership-store.js +176 -0
- package/dist/channel-membership-store.js.map +1 -0
- package/dist/cli-auth.d.ts +89 -0
- package/dist/cli-auth.d.ts.map +1 -0
- package/dist/cli-auth.js +792 -0
- package/dist/cli-auth.js.map +1 -0
- package/dist/cloud-sync.d.ts +150 -0
- package/dist/cloud-sync.d.ts.map +1 -0
- package/dist/cloud-sync.js +446 -0
- package/dist/cloud-sync.js.map +1 -0
- package/dist/connection.d.ts +130 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +438 -0
- package/dist/connection.js.map +1 -0
- package/dist/consensus-integration.d.ts +167 -0
- package/dist/consensus-integration.d.ts.map +1 -0
- package/dist/consensus-integration.js +371 -0
- package/dist/consensus-integration.js.map +1 -0
- package/dist/consensus.d.ts +271 -0
- package/dist/consensus.d.ts.map +1 -0
- package/dist/consensus.js +632 -0
- package/dist/consensus.js.map +1 -0
- package/dist/delivery-tracker.d.ts +34 -0
- package/dist/delivery-tracker.d.ts.map +1 -0
- package/dist/delivery-tracker.js +104 -0
- package/dist/delivery-tracker.js.map +1 -0
- package/dist/enhanced-features.d.ts +118 -0
- package/dist/enhanced-features.d.ts.map +1 -0
- package/dist/enhanced-features.js +176 -0
- package/dist/enhanced-features.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/index.d.ts +73 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +241 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/orchestrator.d.ts +217 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +1143 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/rate-limiter.d.ts +68 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +130 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +9 -0
- package/dist/registry.js.map +1 -0
- package/dist/relay-ledger.d.ts +261 -0
- package/dist/relay-ledger.d.ts.map +1 -0
- package/dist/relay-ledger.js +532 -0
- package/dist/relay-ledger.js.map +1 -0
- package/dist/relay-watchdog.d.ts +125 -0
- package/dist/relay-watchdog.d.ts.map +1 -0
- package/dist/relay-watchdog.js +611 -0
- package/dist/relay-watchdog.js.map +1 -0
- package/dist/repo-manager.d.ts +116 -0
- package/dist/repo-manager.d.ts.map +1 -0
- package/dist/repo-manager.js +384 -0
- package/dist/repo-manager.js.map +1 -0
- package/dist/router.d.ts +370 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +1437 -0
- package/dist/router.js.map +1 -0
- package/dist/server.d.ts +174 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1001 -0
- package/dist/server.js.map +1 -0
- package/dist/spawn-manager.d.ts +78 -0
- package/dist/spawn-manager.d.ts.map +1 -0
- package/dist/spawn-manager.js +165 -0
- package/dist/spawn-manager.js.map +1 -0
- package/dist/sync-queue.d.ts +116 -0
- package/dist/sync-queue.d.ts.map +1 -0
- package/dist/sync-queue.js +361 -0
- package/dist/sync-queue.js.map +1 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace-manager.d.ts +80 -0
- package/dist/workspace-manager.d.ts.map +1 -0
- package/dist/workspace-manager.js +314 -0
- package/dist/workspace-manager.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay Ledger - SQLite-based tracking of relay file processing
|
|
3
|
+
*
|
|
4
|
+
* Tracks files discovered in agent outboxes through their lifecycle:
|
|
5
|
+
* pending -> processing -> delivered/failed -> archived
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Atomic claims with status WHERE clause
|
|
9
|
+
* - Crash recovery (reset processing -> pending on startup)
|
|
10
|
+
* - Archive path tracking for auditing
|
|
11
|
+
* - Configurable retention
|
|
12
|
+
* - Schema migrations for version upgrades
|
|
13
|
+
*/
|
|
14
|
+
import Database from 'better-sqlite3';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import crypto from 'node:crypto';
|
|
18
|
+
import { runMigrations, getPendingMigrations, verifyMigrations } from './migrations/index.js';
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Constants
|
|
21
|
+
// ============================================================================
|
|
22
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
23
|
+
const DEFAULT_ARCHIVE_RETENTION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
24
|
+
const DEFAULT_BUSY_TIMEOUT = 5000;
|
|
25
|
+
// Reserved agent names for special routing
|
|
26
|
+
const RESERVED_AGENT_NAMES = new Set(['Lead', 'System', 'Broadcast', '*']);
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// RelayLedger Class
|
|
29
|
+
// ============================================================================
|
|
30
|
+
export class RelayLedger {
|
|
31
|
+
db;
|
|
32
|
+
config;
|
|
33
|
+
// Prepared statements for performance
|
|
34
|
+
stmtInsert;
|
|
35
|
+
stmtClaim;
|
|
36
|
+
stmtUpdateStatus;
|
|
37
|
+
stmtMarkDelivered;
|
|
38
|
+
stmtMarkFailed;
|
|
39
|
+
stmtMarkArchived;
|
|
40
|
+
stmtGetPending;
|
|
41
|
+
stmtGetByPath;
|
|
42
|
+
stmtGetById;
|
|
43
|
+
stmtResetProcessing;
|
|
44
|
+
stmtCleanupArchived;
|
|
45
|
+
stmtGetStats;
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.config = {
|
|
48
|
+
dbPath: config.dbPath,
|
|
49
|
+
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
50
|
+
archiveRetentionMs: config.archiveRetentionMs ?? DEFAULT_ARCHIVE_RETENTION_MS,
|
|
51
|
+
busyTimeout: config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT,
|
|
52
|
+
};
|
|
53
|
+
// Ensure directory exists
|
|
54
|
+
const dbDir = path.dirname(this.config.dbPath);
|
|
55
|
+
if (!fs.existsSync(dbDir)) {
|
|
56
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
// Open database with recommended settings
|
|
59
|
+
this.db = new Database(this.config.dbPath);
|
|
60
|
+
this.db.pragma('journal_mode = WAL');
|
|
61
|
+
this.db.pragma(`busy_timeout = ${this.config.busyTimeout}`);
|
|
62
|
+
this.db.pragma('synchronous = NORMAL');
|
|
63
|
+
this.db.pragma('foreign_keys = ON');
|
|
64
|
+
this.initSchema();
|
|
65
|
+
this.prepareStatements();
|
|
66
|
+
}
|
|
67
|
+
/** Last migration result (for debugging) */
|
|
68
|
+
lastMigrationResult;
|
|
69
|
+
/**
|
|
70
|
+
* Initialize database schema using migrations
|
|
71
|
+
*/
|
|
72
|
+
initSchema() {
|
|
73
|
+
// Run migrations (creates tables if they don't exist, or upgrades existing schema)
|
|
74
|
+
this.lastMigrationResult = runMigrations(this.db);
|
|
75
|
+
// Log migration results for debugging
|
|
76
|
+
if (this.lastMigrationResult.applied.length > 0) {
|
|
77
|
+
console.log(`[relay-ledger] Applied migrations: ${this.lastMigrationResult.applied.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
if (this.lastMigrationResult.errors.length > 0) {
|
|
80
|
+
console.error(`[relay-ledger] Migration errors:`, this.lastMigrationResult.errors);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the last migration result
|
|
85
|
+
*/
|
|
86
|
+
getMigrationResult() {
|
|
87
|
+
return this.lastMigrationResult;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get pending migrations that haven't been applied
|
|
91
|
+
*/
|
|
92
|
+
getPendingMigrations() {
|
|
93
|
+
return getPendingMigrations(this.db);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Verify migration checksums match what was originally applied
|
|
97
|
+
*/
|
|
98
|
+
verifyMigrationIntegrity() {
|
|
99
|
+
return verifyMigrations(this.db);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Prepare frequently-used statements
|
|
103
|
+
*/
|
|
104
|
+
prepareStatements() {
|
|
105
|
+
this.stmtInsert = this.db.prepare(`
|
|
106
|
+
INSERT INTO relay_files (file_id, source_path, symlink_path, agent_name, message_type, discovered_at, file_size, content_hash, file_mtime_ns, file_inode)
|
|
107
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
108
|
+
ON CONFLICT(file_id) DO NOTHING
|
|
109
|
+
`);
|
|
110
|
+
this.stmtClaim = this.db.prepare(`
|
|
111
|
+
UPDATE relay_files
|
|
112
|
+
SET status = 'processing', retries = retries + 1
|
|
113
|
+
WHERE file_id = ? AND status = 'pending' AND retries < max_retries
|
|
114
|
+
RETURNING *
|
|
115
|
+
`);
|
|
116
|
+
this.stmtUpdateStatus = this.db.prepare(`
|
|
117
|
+
UPDATE relay_files SET status = ? WHERE file_id = ?
|
|
118
|
+
`);
|
|
119
|
+
this.stmtMarkDelivered = this.db.prepare(`
|
|
120
|
+
UPDATE relay_files
|
|
121
|
+
SET status = 'delivered', processed_at = ?
|
|
122
|
+
WHERE file_id = ?
|
|
123
|
+
`);
|
|
124
|
+
this.stmtMarkFailed = this.db.prepare(`
|
|
125
|
+
UPDATE relay_files
|
|
126
|
+
SET status = 'failed', processed_at = ?, error = ?
|
|
127
|
+
WHERE file_id = ?
|
|
128
|
+
`);
|
|
129
|
+
this.stmtMarkArchived = this.db.prepare(`
|
|
130
|
+
UPDATE relay_files
|
|
131
|
+
SET status = 'archived', archive_path = ?, archived_at = ?
|
|
132
|
+
WHERE file_id = ?
|
|
133
|
+
`);
|
|
134
|
+
this.stmtGetPending = this.db.prepare(`
|
|
135
|
+
SELECT * FROM relay_files
|
|
136
|
+
WHERE status = 'pending' AND retries < max_retries
|
|
137
|
+
ORDER BY id ASC
|
|
138
|
+
LIMIT ?
|
|
139
|
+
`);
|
|
140
|
+
this.stmtGetByPath = this.db.prepare(`
|
|
141
|
+
SELECT * FROM relay_files WHERE source_path = ?
|
|
142
|
+
`);
|
|
143
|
+
this.stmtGetById = this.db.prepare(`
|
|
144
|
+
SELECT * FROM relay_files WHERE file_id = ?
|
|
145
|
+
`);
|
|
146
|
+
this.stmtResetProcessing = this.db.prepare(`
|
|
147
|
+
UPDATE relay_files
|
|
148
|
+
SET status = 'pending'
|
|
149
|
+
WHERE status = 'processing'
|
|
150
|
+
`);
|
|
151
|
+
this.stmtCleanupArchived = this.db.prepare(`
|
|
152
|
+
DELETE FROM relay_files
|
|
153
|
+
WHERE status = 'archived' AND archived_at < ?
|
|
154
|
+
`);
|
|
155
|
+
this.stmtGetStats = this.db.prepare(`
|
|
156
|
+
SELECT status, COUNT(*) as count FROM relay_files GROUP BY status
|
|
157
|
+
`);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Generate a unique file ID (12-char hex for ~281 trillion combinations)
|
|
161
|
+
*/
|
|
162
|
+
generateFileId() {
|
|
163
|
+
return crypto.randomBytes(6).toString('hex');
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Check if an agent name is reserved
|
|
167
|
+
*/
|
|
168
|
+
isReservedAgentName(name) {
|
|
169
|
+
return RESERVED_AGENT_NAMES.has(name);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Register a newly discovered file
|
|
173
|
+
* @param sourcePath - Canonical path (symlinks resolved)
|
|
174
|
+
* @param agentName - Agent that owns the file
|
|
175
|
+
* @param messageType - Type of message (msg, spawn, release, etc.)
|
|
176
|
+
* @param fileSize - Size in bytes
|
|
177
|
+
* @param contentHash - Optional hash for deduplication
|
|
178
|
+
* @param fileMtimeNs - Optional modification time in nanoseconds
|
|
179
|
+
* @param fileInode - Optional inode number
|
|
180
|
+
* @param symlinkPath - Original path if it was a symlink (for debugging)
|
|
181
|
+
*/
|
|
182
|
+
registerFile(sourcePath, agentName, messageType, fileSize, contentHash, fileMtimeNs, fileInode, symlinkPath) {
|
|
183
|
+
const fileId = this.generateFileId();
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
this.stmtInsert.run(fileId, sourcePath, symlinkPath ?? null, agentName, messageType, now, fileSize, contentHash ?? null, fileMtimeNs ?? null, fileInode ?? null);
|
|
186
|
+
return fileId;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if a file is already registered
|
|
190
|
+
*/
|
|
191
|
+
isFileRegistered(sourcePath) {
|
|
192
|
+
const row = this.stmtGetByPath.get(sourcePath);
|
|
193
|
+
return row !== undefined;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Atomically claim a file for processing
|
|
197
|
+
* Returns the record if claim succeeded, null otherwise
|
|
198
|
+
*/
|
|
199
|
+
claimFile(fileId) {
|
|
200
|
+
const row = this.stmtClaim.get(fileId);
|
|
201
|
+
if (!row) {
|
|
202
|
+
// Check why claim failed
|
|
203
|
+
const existing = this.stmtGetById.get(fileId);
|
|
204
|
+
if (!existing) {
|
|
205
|
+
return { success: false, reason: 'File not found' };
|
|
206
|
+
}
|
|
207
|
+
if (existing.status !== 'pending') {
|
|
208
|
+
return { success: false, reason: `File status is ${existing.status}, not pending` };
|
|
209
|
+
}
|
|
210
|
+
if (existing.retries >= existing.max_retries) {
|
|
211
|
+
return { success: false, reason: `Max retries (${existing.max_retries}) exceeded` };
|
|
212
|
+
}
|
|
213
|
+
return { success: false, reason: 'Unknown claim failure' };
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
success: true,
|
|
217
|
+
record: this.rowToRecord(row),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Mark a file as successfully delivered
|
|
222
|
+
*/
|
|
223
|
+
markDelivered(fileId) {
|
|
224
|
+
this.stmtMarkDelivered.run(Date.now(), fileId);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Mark a file as failed (will retry if under max retries)
|
|
228
|
+
*/
|
|
229
|
+
markFailed(fileId, error) {
|
|
230
|
+
const record = this.getById(fileId);
|
|
231
|
+
if (!record)
|
|
232
|
+
return;
|
|
233
|
+
if (record.retries >= record.maxRetries) {
|
|
234
|
+
// Permanent failure
|
|
235
|
+
this.stmtMarkFailed.run(Date.now(), error, fileId);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// Reset to pending for retry
|
|
239
|
+
this.stmtUpdateStatus.run('pending', fileId);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Mark a file as archived (moved to archive location)
|
|
244
|
+
*/
|
|
245
|
+
markArchived(fileId, archivePath) {
|
|
246
|
+
this.stmtMarkArchived.run(archivePath, Date.now(), fileId);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get pending files ready for processing
|
|
250
|
+
*/
|
|
251
|
+
getPendingFiles(limit = 100) {
|
|
252
|
+
const rows = this.stmtGetPending.all(limit);
|
|
253
|
+
return rows.map(row => this.rowToRecord(row));
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get a file record by ID
|
|
257
|
+
*/
|
|
258
|
+
getById(fileId) {
|
|
259
|
+
const row = this.stmtGetById.get(fileId);
|
|
260
|
+
return row ? this.rowToRecord(row) : null;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get a file record by source path
|
|
264
|
+
*/
|
|
265
|
+
getByPath(sourcePath) {
|
|
266
|
+
const row = this.stmtGetByPath.get(sourcePath);
|
|
267
|
+
return row ? this.rowToRecord(row) : null;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Reset all 'processing' files to 'pending' (crash recovery)
|
|
271
|
+
*/
|
|
272
|
+
resetProcessingFiles() {
|
|
273
|
+
const result = this.stmtResetProcessing.run();
|
|
274
|
+
return result.changes;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Mark files as failed if their source file no longer exists
|
|
278
|
+
*/
|
|
279
|
+
reconcileWithFilesystem() {
|
|
280
|
+
let reset = 0;
|
|
281
|
+
let failed = 0;
|
|
282
|
+
// Get all processing files
|
|
283
|
+
const processingFiles = this.db
|
|
284
|
+
.prepare(`SELECT * FROM relay_files WHERE status = 'processing'`)
|
|
285
|
+
.all();
|
|
286
|
+
for (const row of processingFiles) {
|
|
287
|
+
if (!fs.existsSync(row.source_path)) {
|
|
288
|
+
// File was deleted while processing - mark as failed
|
|
289
|
+
this.stmtMarkFailed.run(Date.now(), 'Source file deleted during processing', row.file_id);
|
|
290
|
+
failed++;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
// Reset to pending
|
|
294
|
+
this.stmtUpdateStatus.run('pending', row.file_id);
|
|
295
|
+
reset++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { reset, failed };
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Clean up old archived records
|
|
302
|
+
*/
|
|
303
|
+
cleanupArchivedRecords() {
|
|
304
|
+
const cutoff = Date.now() - this.config.archiveRetentionMs;
|
|
305
|
+
const result = this.stmtCleanupArchived.run(cutoff);
|
|
306
|
+
return result.changes;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Get statistics about file processing
|
|
310
|
+
*/
|
|
311
|
+
getStats() {
|
|
312
|
+
const rows = this.stmtGetStats.all();
|
|
313
|
+
const stats = {
|
|
314
|
+
pending: 0,
|
|
315
|
+
processing: 0,
|
|
316
|
+
delivered: 0,
|
|
317
|
+
failed: 0,
|
|
318
|
+
archived: 0,
|
|
319
|
+
};
|
|
320
|
+
for (const row of rows) {
|
|
321
|
+
stats[row.status] = row.count;
|
|
322
|
+
}
|
|
323
|
+
return stats;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Convert database row to RelayFileRecord
|
|
327
|
+
*/
|
|
328
|
+
rowToRecord(row) {
|
|
329
|
+
return {
|
|
330
|
+
id: row.id,
|
|
331
|
+
fileId: row.file_id,
|
|
332
|
+
sourcePath: row.source_path,
|
|
333
|
+
symlinkPath: row.symlink_path,
|
|
334
|
+
archivePath: row.archive_path,
|
|
335
|
+
agentName: row.agent_name,
|
|
336
|
+
messageType: row.message_type,
|
|
337
|
+
status: row.status,
|
|
338
|
+
retries: row.retries,
|
|
339
|
+
maxRetries: row.max_retries,
|
|
340
|
+
discoveredAt: row.discovered_at,
|
|
341
|
+
processedAt: row.processed_at,
|
|
342
|
+
archivedAt: row.archived_at,
|
|
343
|
+
error: row.error,
|
|
344
|
+
contentHash: row.content_hash,
|
|
345
|
+
fileSize: row.file_size,
|
|
346
|
+
fileMtimeNs: row.file_mtime_ns,
|
|
347
|
+
fileInode: row.file_inode,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
// ==========================================================================
|
|
351
|
+
// Agent Management
|
|
352
|
+
// ==========================================================================
|
|
353
|
+
/**
|
|
354
|
+
* Register or update an agent
|
|
355
|
+
*/
|
|
356
|
+
registerAgent(agentName, metadata) {
|
|
357
|
+
const now = Date.now();
|
|
358
|
+
const metadataJson = metadata ? JSON.stringify(metadata) : null;
|
|
359
|
+
this.db.prepare(`
|
|
360
|
+
INSERT INTO agents (agent_name, created_at, last_seen_at, status, metadata)
|
|
361
|
+
VALUES (?, ?, ?, 'active', ?)
|
|
362
|
+
ON CONFLICT(agent_name) DO UPDATE SET
|
|
363
|
+
last_seen_at = excluded.last_seen_at,
|
|
364
|
+
status = 'active',
|
|
365
|
+
metadata = COALESCE(excluded.metadata, agents.metadata)
|
|
366
|
+
`).run(agentName, now, now, metadataJson);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Update agent last seen time
|
|
370
|
+
*/
|
|
371
|
+
updateAgentLastSeen(agentName) {
|
|
372
|
+
this.db.prepare(`
|
|
373
|
+
UPDATE agents SET last_seen_at = ? WHERE agent_name = ?
|
|
374
|
+
`).run(Date.now(), agentName);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Mark agent as inactive
|
|
378
|
+
*/
|
|
379
|
+
markAgentInactive(agentName) {
|
|
380
|
+
this.db.prepare(`
|
|
381
|
+
UPDATE agents SET status = 'inactive' WHERE agent_name = ?
|
|
382
|
+
`).run(agentName);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get all active agents
|
|
386
|
+
*/
|
|
387
|
+
getActiveAgents() {
|
|
388
|
+
const rows = this.db.prepare(`
|
|
389
|
+
SELECT * FROM agents WHERE status = 'active' ORDER BY last_seen_at DESC
|
|
390
|
+
`).all();
|
|
391
|
+
return rows.map(row => ({
|
|
392
|
+
id: row.id,
|
|
393
|
+
agentName: row.agent_name,
|
|
394
|
+
createdAt: row.created_at,
|
|
395
|
+
lastSeenAt: row.last_seen_at,
|
|
396
|
+
status: row.status,
|
|
397
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
398
|
+
}));
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Get agent by name
|
|
402
|
+
*/
|
|
403
|
+
getAgent(agentName) {
|
|
404
|
+
const row = this.db.prepare(`
|
|
405
|
+
SELECT * FROM agents WHERE agent_name = ?
|
|
406
|
+
`).get(agentName);
|
|
407
|
+
if (!row)
|
|
408
|
+
return null;
|
|
409
|
+
return {
|
|
410
|
+
id: row.id,
|
|
411
|
+
agentName: row.agent_name,
|
|
412
|
+
createdAt: row.created_at,
|
|
413
|
+
lastSeenAt: row.last_seen_at,
|
|
414
|
+
status: row.status,
|
|
415
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
// ==========================================================================
|
|
419
|
+
// Orchestrator State
|
|
420
|
+
// ==========================================================================
|
|
421
|
+
/**
|
|
422
|
+
* Save orchestrator state
|
|
423
|
+
*/
|
|
424
|
+
saveState(key, value) {
|
|
425
|
+
this.db.prepare(`
|
|
426
|
+
INSERT INTO orchestrator_state (key, value, updated_at)
|
|
427
|
+
VALUES (?, ?, ?)
|
|
428
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
429
|
+
value = excluded.value,
|
|
430
|
+
updated_at = excluded.updated_at
|
|
431
|
+
`).run(key, value, Date.now());
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Get orchestrator state
|
|
435
|
+
*/
|
|
436
|
+
getState(key) {
|
|
437
|
+
const row = this.db.prepare(`
|
|
438
|
+
SELECT value FROM orchestrator_state WHERE key = ?
|
|
439
|
+
`).get(key);
|
|
440
|
+
return row?.value ?? null;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Delete orchestrator state
|
|
444
|
+
*/
|
|
445
|
+
deleteState(key) {
|
|
446
|
+
this.db.prepare(`
|
|
447
|
+
DELETE FROM orchestrator_state WHERE key = ?
|
|
448
|
+
`).run(key);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Get all orchestrator state
|
|
452
|
+
*/
|
|
453
|
+
getAllState() {
|
|
454
|
+
const rows = this.db.prepare(`
|
|
455
|
+
SELECT * FROM orchestrator_state ORDER BY key
|
|
456
|
+
`).all();
|
|
457
|
+
return rows.map(row => ({
|
|
458
|
+
key: row.key,
|
|
459
|
+
value: row.value,
|
|
460
|
+
updatedAt: row.updated_at,
|
|
461
|
+
}));
|
|
462
|
+
}
|
|
463
|
+
// ==========================================================================
|
|
464
|
+
// Pending Operations (for crash recovery)
|
|
465
|
+
// ==========================================================================
|
|
466
|
+
/**
|
|
467
|
+
* Add a pending operation
|
|
468
|
+
*/
|
|
469
|
+
addPendingOperation(operationType, targetId, payload) {
|
|
470
|
+
const result = this.db.prepare(`
|
|
471
|
+
INSERT INTO pending_operations (operation_type, target_id, payload, created_at)
|
|
472
|
+
VALUES (?, ?, ?, ?)
|
|
473
|
+
`).run(operationType, targetId, payload ?? null, Date.now());
|
|
474
|
+
return result.lastInsertRowid;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Complete a pending operation (remove it)
|
|
478
|
+
*/
|
|
479
|
+
completePendingOperation(id) {
|
|
480
|
+
this.db.prepare(`
|
|
481
|
+
DELETE FROM pending_operations WHERE id = ?
|
|
482
|
+
`).run(id);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Fail a pending operation (increment attempts, record error)
|
|
486
|
+
*/
|
|
487
|
+
failPendingOperation(id, error) {
|
|
488
|
+
this.db.prepare(`
|
|
489
|
+
UPDATE pending_operations
|
|
490
|
+
SET attempts = attempts + 1, last_attempt_at = ?, error = ?
|
|
491
|
+
WHERE id = ?
|
|
492
|
+
`).run(Date.now(), error, id);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Get pending operations for recovery
|
|
496
|
+
*/
|
|
497
|
+
getPendingOperations(maxAttempts = 5) {
|
|
498
|
+
const rows = this.db.prepare(`
|
|
499
|
+
SELECT * FROM pending_operations
|
|
500
|
+
WHERE attempts < ?
|
|
501
|
+
ORDER BY created_at ASC
|
|
502
|
+
`).all(maxAttempts);
|
|
503
|
+
return rows.map(row => ({
|
|
504
|
+
id: row.id,
|
|
505
|
+
operationType: row.operation_type,
|
|
506
|
+
targetId: row.target_id,
|
|
507
|
+
payload: row.payload,
|
|
508
|
+
createdAt: row.created_at,
|
|
509
|
+
attempts: row.attempts,
|
|
510
|
+
lastAttemptAt: row.last_attempt_at,
|
|
511
|
+
error: row.error,
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Cleanup old failed operations
|
|
516
|
+
*/
|
|
517
|
+
cleanupFailedOperations(maxAge = 24 * 60 * 60 * 1000) {
|
|
518
|
+
const cutoff = Date.now() - maxAge;
|
|
519
|
+
const result = this.db.prepare(`
|
|
520
|
+
DELETE FROM pending_operations
|
|
521
|
+
WHERE created_at < ? AND attempts >= 5
|
|
522
|
+
`).run(cutoff);
|
|
523
|
+
return result.changes;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Close the database connection
|
|
527
|
+
*/
|
|
528
|
+
close() {
|
|
529
|
+
this.db.close();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
//# sourceMappingURL=relay-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-ledger.js","sourceRoot":"","sources":["../src/relay-ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay Watchdog - File-based relay message detection and processing
|
|
3
|
+
*
|
|
4
|
+
* Monitors agent outbox directories for new relay files and processes them:
|
|
5
|
+
* 1. Detects new files via fs.watch + periodic reconciliation
|
|
6
|
+
* 2. Validates files (size > 0, not symlink, settled)
|
|
7
|
+
* 3. Claims files atomically via ledger
|
|
8
|
+
* 4. Processes and archives files
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - fsevents/inotify watchers with overflow fallback
|
|
12
|
+
* - Configurable settle time for file stability
|
|
13
|
+
* - Symlink rejection (security)
|
|
14
|
+
* - Orphaned .pending file cleanup
|
|
15
|
+
* - Crash recovery on startup
|
|
16
|
+
*/
|
|
17
|
+
import { EventEmitter } from 'node:events';
|
|
18
|
+
import { type RelayFileRecord } from './relay-ledger.js';
|
|
19
|
+
import { type RelayPaths } from '@agent-relay/config/relay-file-writer';
|
|
20
|
+
export interface WatchdogConfig {
|
|
21
|
+
/** Base relay paths (auto-detected if not provided) */
|
|
22
|
+
relayPaths?: RelayPaths;
|
|
23
|
+
/** Ledger database path (default: ~/.agent-relay/meta/ledger.sqlite) */
|
|
24
|
+
ledgerPath?: string;
|
|
25
|
+
/** Settle time before processing file (default: 500ms) */
|
|
26
|
+
settleTimeMs?: number;
|
|
27
|
+
/** Timeout for malformed/incomplete files (default: 10000ms) */
|
|
28
|
+
malformedTimeoutMs?: number;
|
|
29
|
+
/** Interval for periodic reconciliation (default: 30000ms) */
|
|
30
|
+
reconcileIntervalMs?: number;
|
|
31
|
+
/** Maximum message size in bytes (default: 1MB) */
|
|
32
|
+
maxMessageSizeBytes?: number;
|
|
33
|
+
/** Maximum attachment size in bytes (default: 10MB) */
|
|
34
|
+
maxAttachmentSizeBytes?: number;
|
|
35
|
+
/** Cleanup interval for orphaned files (default: 60000ms) */
|
|
36
|
+
cleanupIntervalMs?: number;
|
|
37
|
+
/** Age threshold for orphaned .pending files (default: 30000ms) */
|
|
38
|
+
orphanedPendingAgeMs?: number;
|
|
39
|
+
/** Archive retention in milliseconds (default: 7 days) */
|
|
40
|
+
archiveRetentionMs?: number;
|
|
41
|
+
/** Enable debug logging */
|
|
42
|
+
debug?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface DiscoveredFile {
|
|
45
|
+
path: string;
|
|
46
|
+
agentName: string;
|
|
47
|
+
messageType: string;
|
|
48
|
+
size: number;
|
|
49
|
+
mtime: number;
|
|
50
|
+
contentHash?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface ProcessedFile {
|
|
53
|
+
fileId: string;
|
|
54
|
+
agentName: string;
|
|
55
|
+
messageType: string;
|
|
56
|
+
content: string;
|
|
57
|
+
headers: Record<string, string>;
|
|
58
|
+
body: string;
|
|
59
|
+
}
|
|
60
|
+
export interface WatchdogEvents {
|
|
61
|
+
'file:discovered': (file: DiscoveredFile) => void;
|
|
62
|
+
'file:processing': (record: RelayFileRecord) => void;
|
|
63
|
+
'file:delivered': (result: ProcessedFile) => void;
|
|
64
|
+
'file:failed': (record: RelayFileRecord, error: Error) => void;
|
|
65
|
+
'file:archived': (record: RelayFileRecord, archivePath: string) => void;
|
|
66
|
+
'watcher:overflow': (dir: string) => void;
|
|
67
|
+
'reconcile:complete': (stats: {
|
|
68
|
+
discovered: number;
|
|
69
|
+
failed: number;
|
|
70
|
+
}) => void;
|
|
71
|
+
error: (error: Error) => void;
|
|
72
|
+
}
|
|
73
|
+
export declare class RelayWatchdog extends EventEmitter {
|
|
74
|
+
private config;
|
|
75
|
+
private relayPaths;
|
|
76
|
+
private ledger;
|
|
77
|
+
private watchers;
|
|
78
|
+
private pendingFiles;
|
|
79
|
+
private reconcileTimer?;
|
|
80
|
+
private cleanupTimer?;
|
|
81
|
+
private running;
|
|
82
|
+
constructor(config?: WatchdogConfig);
|
|
83
|
+
/**
|
|
84
|
+
* Resolve symlinks in relay paths for cloud/production workspace support
|
|
85
|
+
* This ensures we work with canonical paths even when directories are symlinked
|
|
86
|
+
*/
|
|
87
|
+
private resolveRelayPaths;
|
|
88
|
+
/**
|
|
89
|
+
* Start the watchdog
|
|
90
|
+
*/
|
|
91
|
+
start(): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Stop the watchdog
|
|
94
|
+
*/
|
|
95
|
+
stop(): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Get ledger statistics
|
|
98
|
+
*/
|
|
99
|
+
getStats(): Record<string, number>;
|
|
100
|
+
/**
|
|
101
|
+
* Get pending file count
|
|
102
|
+
*/
|
|
103
|
+
getPendingCount(): number;
|
|
104
|
+
private ensureDirectories;
|
|
105
|
+
private startRootWatcher;
|
|
106
|
+
private startAgentWatchers;
|
|
107
|
+
private checkAndWatchAgentDir;
|
|
108
|
+
private startAgentWatcher;
|
|
109
|
+
private handleFileEvent;
|
|
110
|
+
private processDiscoveredFile;
|
|
111
|
+
private validateFile;
|
|
112
|
+
private calculateFileHash;
|
|
113
|
+
private processFile;
|
|
114
|
+
private parseFileContent;
|
|
115
|
+
private archiveFile;
|
|
116
|
+
private fullReconciliation;
|
|
117
|
+
private cleanupOrphanedFiles;
|
|
118
|
+
private shouldIgnore;
|
|
119
|
+
private log;
|
|
120
|
+
}
|
|
121
|
+
export interface RelayWatchdog {
|
|
122
|
+
on<K extends keyof WatchdogEvents>(event: K, listener: WatchdogEvents[K]): this;
|
|
123
|
+
emit<K extends keyof WatchdogEvents>(event: K, ...args: Parameters<WatchdogEvents[K]>): boolean;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=relay-watchdog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay-watchdog.d.ts","sourceRoot":"","sources":["../src/relay-watchdog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,EAAe,KAAK,eAAe,EAAqB,MAAM,mBAAmB,CAAC;AACzF,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAM3F,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mDAAmD;IACnD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAClD,iBAAiB,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IACrD,gBAAgB,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/D,eAAe,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,oBAAoB,EAAE,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9E,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AA2BD,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,YAAY,CAA0C;IAC9D,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,GAAE,cAAmB;IA0BvC;;;OAGG;YACW,iBAAiB;IAwB/B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC3B;;OAEG;IACH,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAIlC;;OAEG;IACH,eAAe,IAAI,MAAM;YAQX,iBAAiB;IAoB/B,OAAO,CAAC,gBAAgB;YAkCV,kBAAkB;IAehC,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,eAAe;YAkBT,qBAAqB;YA0FrB,YAAY;YA4CZ,iBAAiB;YASjB,WAAW;IA2CzB,OAAO,CAAC,gBAAgB;YAqCV,WAAW;YAkCX,kBAAkB;YAgElB,oBAAoB;IAwClC,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,GAAG;CAKZ;AAGD,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChF,IAAI,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CACjG"}
|