agent-relay-server 0.15.0 → 0.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -284,7 +284,7 @@ export function createAutomation(input: CreateAutomationInput, now = Date.now())
284
284
  if (!getOrchestrator(normalized.orchestratorId)) throw new ValidationError(`orchestrator ${normalized.orchestratorId} not found`);
285
285
  const id = randomUUID();
286
286
  const nextRunAt = normalized.enabled ? nextScheduledAt(normalized.schedule, normalized.timezone, now) : undefined;
287
- db().prepare(`
287
+ db().query(`
288
288
  INSERT INTO automations (id, kind, name, description, enabled, schedule, timezone, next_run_at, catch_up_policy, concurrency_policy, orchestrator_id, target_policy, task_template, created_at, updated_at)
289
289
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
290
290
  `).run(
@@ -327,7 +327,7 @@ export function updateAutomation(id: string, input: UpdateAutomationInput, now =
327
327
  const normalized = normalizeCreateInput(next);
328
328
  if (!getOrchestrator(normalized.orchestratorId)) throw new ValidationError(`orchestrator ${normalized.orchestratorId} not found`);
329
329
  const nextRunAt = normalized.enabled ? nextScheduledAt(normalized.schedule, normalized.timezone, now) : undefined;
330
- db().prepare(`
330
+ db().query(`
331
331
  UPDATE automations
332
332
  SET name = ?, description = ?, enabled = ?, schedule = ?, timezone = ?, next_run_at = ?,
333
333
  catch_up_policy = ?, concurrency_policy = ?, orchestrator_id = ?, target_policy = ?, task_template = ?, updated_at = ?
@@ -352,18 +352,18 @@ export function updateAutomation(id: string, input: UpdateAutomationInput, now =
352
352
 
353
353
  export function deleteAutomation(id: string): boolean {
354
354
  ensureAutomationTables();
355
- return db().prepare("DELETE FROM automations WHERE id = ?").run(id).changes > 0;
355
+ return db().query("DELETE FROM automations WHERE id = ?").run(id).changes > 0;
356
356
  }
357
357
 
358
358
  export function getAutomation(id: string): Automation | null {
359
359
  ensureAutomationTables();
360
- const row = db().prepare("SELECT * FROM automations WHERE id = ?").get(id) as any;
360
+ const row = db().query("SELECT * FROM automations WHERE id = ?").get(id) as any;
361
361
  return row ? rowToAutomation(row) : null;
362
362
  }
363
363
 
364
364
  export function listAutomations(): Automation[] {
365
365
  ensureAutomationTables();
366
- return (db().prepare("SELECT * FROM automations ORDER BY enabled DESC, next_run_at IS NULL, next_run_at ASC, name COLLATE NOCASE").all() as any[])
366
+ return (db().query("SELECT * FROM automations ORDER BY enabled DESC, next_run_at IS NULL, next_run_at ASC, name COLLATE NOCASE").all() as any[])
367
367
  .map(rowToAutomation);
368
368
  }
369
369
 
@@ -381,20 +381,20 @@ export function listAutomationRuns(filter: { automationId?: string; status?: str
381
381
  }
382
382
  const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
383
383
  params.push(filter.limit ?? 100);
384
- return (db().prepare(`SELECT * FROM automation_runs ${where} ORDER BY created_at DESC LIMIT ?`).all(...params) as any[])
384
+ return (db().query(`SELECT * FROM automation_runs ${where} ORDER BY created_at DESC LIMIT ?`).all(...params) as any[])
385
385
  .map(rowToAutomationRun);
386
386
  }
387
387
 
388
388
  function getAutomationRun(id: string): AutomationRun | null {
389
389
  ensureAutomationTables();
390
- const row = db().prepare("SELECT * FROM automation_runs WHERE id = ?").get(id) as any;
390
+ const row = db().query("SELECT * FROM automation_runs WHERE id = ?").get(id) as any;
391
391
  return row ? rowToAutomationRun(row) : null;
392
392
  }
393
393
 
394
394
  export function runDueAutomations(now = Date.now()): AutomationDispatchResult[] {
395
395
  ensureAutomationTables();
396
396
  const results = dispatchQueuedAutomationRuns(now);
397
- const due = (db().prepare("SELECT * FROM automations WHERE enabled = 1 AND next_run_at IS NOT NULL AND next_run_at <= ? ORDER BY next_run_at ASC LIMIT 25").all(now) as any[])
397
+ const due = (db().query("SELECT * FROM automations WHERE enabled = 1 AND next_run_at IS NOT NULL AND next_run_at <= ? ORDER BY next_run_at ASC LIMIT 25").all(now) as any[])
398
398
  .map(rowToAutomation);
399
399
  for (const automation of due) {
400
400
  if (hasActiveRun(automation.id)) {
@@ -421,7 +421,7 @@ export function runAutomationNow(id: string, now = Date.now()): AutomationDispat
421
421
 
422
422
  export function reconcileAutomationRuns(now = Date.now()): AutomationReconcileResult[] {
423
423
  ensureAutomationTables();
424
- const rows = db().prepare(`
424
+ const rows = db().query(`
425
425
  SELECT * FROM automation_runs
426
426
  WHERE status IN ('scheduled', 'dispatching', 'waiting_agent', 'running') AND task_id IS NOT NULL
427
427
  ORDER BY created_at ASC
@@ -629,7 +629,7 @@ function createRunTask(
629
629
 
630
630
  function insertRun(automation: Automation, scheduledFor: number, now: number): AutomationRun {
631
631
  const id = randomUUID();
632
- db().prepare(`
632
+ db().query(`
633
633
  INSERT INTO automation_runs (id, automation_id, status, scheduled_for, orchestrator_id, meta, created_at, updated_at)
634
634
  VALUES (?, ?, 'dispatching', ?, ?, '{}', ?, ?)
635
635
  `).run(id, automation.id, scheduledFor, automation.orchestratorId, now, now);
@@ -637,11 +637,11 @@ function insertRun(automation: Automation, scheduledFor: number, now: number): A
637
637
  }
638
638
 
639
639
  function enqueueAutomationRun(automation: Automation, scheduledFor: number, now: number): AutomationRun {
640
- const existing = db().prepare("SELECT * FROM automation_runs WHERE automation_id = ? AND status = 'scheduled' AND scheduled_for = ?")
640
+ const existing = db().query("SELECT * FROM automation_runs WHERE automation_id = ? AND status = 'scheduled' AND scheduled_for = ?")
641
641
  .get(automation.id, scheduledFor) as any;
642
642
  if (existing) return rowToAutomationRun(existing);
643
643
  const id = randomUUID();
644
- db().prepare(`
644
+ db().query(`
645
645
  INSERT INTO automation_runs (id, automation_id, status, scheduled_for, orchestrator_id, meta, created_at, updated_at)
646
646
  VALUES (?, ?, 'scheduled', ?, ?, '{}', ?, ?)
647
647
  `).run(id, automation.id, scheduledFor, automation.orchestratorId, now, now);
@@ -649,7 +649,7 @@ function enqueueAutomationRun(automation: Automation, scheduledFor: number, now:
649
649
  }
650
650
 
651
651
  function dispatchQueuedAutomationRuns(now: number): AutomationDispatchResult[] {
652
- const queued = (db().prepare(`
652
+ const queued = (db().query(`
653
653
  SELECT r.* FROM automation_runs r
654
654
  JOIN automations a ON a.id = r.automation_id
655
655
  WHERE r.status = 'scheduled' AND a.enabled = 1
@@ -680,7 +680,7 @@ function updateRun(id: string, input: {
680
680
  }, now: number): void {
681
681
  const current = getAutomationRun(id);
682
682
  const meta = input.meta !== undefined ? input.meta : current?.meta;
683
- db().prepare(`
683
+ db().query(`
684
684
  UPDATE automation_runs
685
685
  SET status = COALESCE(?, status),
686
686
  started_at = COALESCE(?, started_at),
@@ -715,20 +715,20 @@ function updateRun(id: string, input: {
715
715
 
716
716
  function hasActiveRun(automationId: string): boolean {
717
717
  const placeholders = [...BLOCKING_RUN_STATUSES].map(() => "?").join(",");
718
- const row = db().prepare(`SELECT id FROM automation_runs WHERE automation_id = ? AND status IN (${placeholders}) LIMIT 1`)
718
+ const row = db().query(`SELECT id FROM automation_runs WHERE automation_id = ? AND status IN (${placeholders}) LIMIT 1`)
719
719
  .get(automationId, ...BLOCKING_RUN_STATUSES) as any;
720
720
  return Boolean(row);
721
721
  }
722
722
 
723
723
  function cancelActiveRuns(automationId: string, now: number, reason: string): void {
724
724
  const placeholders = [...OPEN_RUN_STATUSES].map(() => "?").join(",");
725
- db().prepare(`UPDATE automation_runs SET status = 'canceled', finished_at = ?, error = ?, updated_at = ? WHERE automation_id = ? AND status IN (${placeholders})`)
725
+ db().query(`UPDATE automation_runs SET status = 'canceled', finished_at = ?, error = ?, updated_at = ? WHERE automation_id = ? AND status IN (${placeholders})`)
726
726
  .run(now, reason, now, automationId, ...OPEN_RUN_STATUSES);
727
727
  }
728
728
 
729
729
  function rescheduleAutomation(automation: Automation, now: number): void {
730
730
  const next = automation.enabled ? nextScheduledAt(automation.schedule, automation.timezone, now) : undefined;
731
- db().prepare("UPDATE automations SET next_run_at = ?, updated_at = ? WHERE id = ?").run(next ?? null, now, automation.id);
731
+ db().query("UPDATE automations SET next_run_at = ?, updated_at = ? WHERE id = ?").run(next ?? null, now, automation.id);
732
732
  }
733
733
 
734
734
  function requireOnlineOrchestrator(orchestratorId: string): Orchestrator {
package/src/bus-outbox.ts CHANGED
@@ -20,7 +20,7 @@ interface OutboxRow {
20
20
 
21
21
  export function appendEvent(type: string, source: string, data: unknown, subject?: string): number {
22
22
  const timestamp = Date.now();
23
- const result = getDb().prepare(`
23
+ const result = getDb().query(`
24
24
  INSERT INTO bus_outbox (event_type, source, subject, data, timestamp)
25
25
  VALUES (?, ?, ?, ?, ?)
26
26
  `).run(type, source, subject ?? null, JSON.stringify(data ?? {}), timestamp);
@@ -28,7 +28,7 @@ export function appendEvent(type: string, source: string, data: unknown, subject
28
28
  }
29
29
 
30
30
  export function replayEvents(since: number, limit = 500): BusEvent[] {
31
- const rows = getDb().prepare(`
31
+ const rows = getDb().query(`
32
32
  SELECT seq, event_type, source, subject, data, timestamp
33
33
  FROM bus_outbox
34
34
  WHERE seq > ?
@@ -40,17 +40,17 @@ export function replayEvents(since: number, limit = 500): BusEvent[] {
40
40
 
41
41
  export function pruneOutbox(retentionMs = 60 * 60 * 1000): number {
42
42
  const threshold = Date.now() - retentionMs;
43
- const result = getDb().prepare("DELETE FROM bus_outbox WHERE timestamp < ?").run(threshold);
43
+ const result = getDb().query("DELETE FROM bus_outbox WHERE timestamp < ?").run(threshold);
44
44
  return result.changes;
45
45
  }
46
46
 
47
47
  export function getOutboxCursor(): number {
48
- const row = getDb().prepare("SELECT coalesce(max(seq), 0) AS cursor FROM bus_outbox").get() as { cursor: number };
48
+ const row = getDb().query("SELECT coalesce(max(seq), 0) AS cursor FROM bus_outbox").get() as { cursor: number };
49
49
  return row.cursor;
50
50
  }
51
51
 
52
52
  export function getOldestOutboxCursor(): number {
53
- const row = getDb().prepare("SELECT min(seq) AS cursor FROM bus_outbox").get() as { cursor: number | null };
53
+ const row = getDb().query("SELECT min(seq) AS cursor FROM bus_outbox").get() as { cursor: number | null };
54
54
  return row.cursor ?? 0;
55
55
  }
56
56
 
package/src/bus.ts CHANGED
@@ -99,7 +99,7 @@ export function getBusConnectionCount(): number {
99
99
  export function expireStaleBusAgents(graceMs = Number(process.env.AGENT_RELAY_STALE_GRACE_MS) || 120_000): { agentIds: string[]; orphanedTasks: Task[] } {
100
100
  const cutoff = Date.now() - graceMs;
101
101
  const rows = getDb()
102
- .prepare("SELECT id FROM agents WHERE status = 'stale' AND last_seen < ? AND id NOT IN ('user', 'system')")
102
+ .query("SELECT id FROM agents WHERE status = 'stale' AND last_seen < ? AND id NOT IN ('user', 'system')")
103
103
  .all(cutoff) as Array<{ id: string }>;
104
104
  const orphanedTasks: Task[] = [];
105
105
  for (const row of rows) {
@@ -41,7 +41,7 @@ export function createCommand(input: CreateCommandInput): Command {
41
41
  updatedAt: now,
42
42
  expiresAt: input.ttlMs ? now + input.ttlMs : defaultExpiresAt(input.type, now),
43
43
  };
44
- getDb().prepare(`
44
+ getDb().query(`
45
45
  INSERT INTO commands (id, type, source, target, params, status, correlation_id, created_at, updated_at, expires_at)
46
46
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
47
47
  `).run(
@@ -60,7 +60,7 @@ export function createCommand(input: CreateCommandInput): Command {
60
60
  }
61
61
 
62
62
  export function getCommand(id: string): Command | null {
63
- const row = getDb().prepare("SELECT * FROM commands WHERE id = ?").get(id) as CommandRow | undefined;
63
+ const row = getDb().query("SELECT * FROM commands WHERE id = ?").get(id) as CommandRow | undefined;
64
64
  return row ? rowToCommand(row) : null;
65
65
  }
66
66
 
@@ -87,7 +87,7 @@ export function listCommands(filters: CommandFilters = {}): Command[] {
87
87
  const limit = Math.min(Math.max(filters.limit ?? 100, 1), 500);
88
88
  const bindings = [...params, limit] as any[];
89
89
  const rows = getDb()
90
- .prepare(`SELECT * FROM commands ${where} ORDER BY created_at DESC LIMIT ?`)
90
+ .query(`SELECT * FROM commands ${where} ORDER BY created_at DESC LIMIT ?`)
91
91
  .all(...bindings) as CommandRow[];
92
92
  return rows.map(rowToCommand);
93
93
  }
@@ -97,7 +97,7 @@ export function updateCommand(id: string, input: UpdateCommandInput): Command |
97
97
  if (!existing) return null;
98
98
  const nextStatus = input.status ?? existing.status;
99
99
  const now = Date.now();
100
- getDb().prepare(`
100
+ getDb().query(`
101
101
  UPDATE commands
102
102
  SET status = ?, result = ?, error = ?, updated_at = ?
103
103
  WHERE id = ?
@@ -121,7 +121,7 @@ export function deleteCommand(id: string): boolean {
121
121
 
122
122
  export function expireCommands(now: number = Date.now()): Command[] {
123
123
  const rows = getDb()
124
- .prepare(`SELECT * FROM commands WHERE expires_at IS NOT NULL AND expires_at <= ? AND status IN (${ACTIVE_STATUSES.map(() => "?").join(",")})`)
124
+ .query(`SELECT * FROM commands WHERE expires_at IS NOT NULL AND expires_at <= ? AND status IN (${ACTIVE_STATUSES.map(() => "?").join(",")})`)
125
125
  .all(now, ...ACTIVE_STATUSES) as CommandRow[];
126
126
  for (const row of rows) updateCommand(row.id, { status: "timed_out", error: "command timed out" });
127
127
  return rows.map((row) => getCommand(row.id)).filter((command): command is Command => Boolean(command));
@@ -483,13 +483,13 @@ function normalizeValue(namespace: string, key: string, value: unknown): unknown
483
483
  }
484
484
 
485
485
  export function getConfig<T = unknown>(namespace: string, key: string): ConfigEntry<T> | null {
486
- const row = getDb().prepare("SELECT * FROM config WHERE namespace = ? AND key = ?").get(namespace, key) as ConfigRow | undefined;
486
+ const row = getDb().query("SELECT * FROM config WHERE namespace = ? AND key = ?").get(namespace, key) as ConfigRow | undefined;
487
487
  return row ? rowToConfigEntry<T>(row) : null;
488
488
  }
489
489
 
490
490
  export function listConfig<T = unknown>(namespace: string): ConfigEntry<T>[] {
491
491
  const rows = getDb()
492
- .prepare("SELECT * FROM config WHERE namespace = ? ORDER BY key ASC")
492
+ .query("SELECT * FROM config WHERE namespace = ? ORDER BY key ASC")
493
493
  .all(namespace) as ConfigRow[];
494
494
  return rows.map(rowToConfigEntry<T>);
495
495
  }
@@ -502,12 +502,12 @@ export function setConfig<T = unknown>(namespace: string, key: string, value: T,
502
502
 
503
503
  getDb().transaction(() => {
504
504
  if (existing) {
505
- getDb().prepare(`
505
+ getDb().query(`
506
506
  INSERT INTO config_history (namespace, key, value, version, changed_at, changed_by)
507
507
  VALUES (?, ?, ?, ?, ?, ?)
508
508
  `).run(existing.namespace, existing.key, JSON.stringify(existing.value), existing.version, now, updatedBy ?? null);
509
509
  }
510
- getDb().prepare(`
510
+ getDb().query(`
511
511
  INSERT INTO config (namespace, key, value, version, updated_at, updated_by)
512
512
  VALUES (?, ?, ?, ?, ?, ?)
513
513
  ON CONFLICT(namespace, key) DO UPDATE SET
@@ -527,11 +527,11 @@ export function deleteConfig(namespace: string, key: string, updatedBy?: string)
527
527
  if (!existing) return false;
528
528
  const now = new Date().toISOString();
529
529
  getDb().transaction(() => {
530
- getDb().prepare(`
530
+ getDb().query(`
531
531
  INSERT INTO config_history (namespace, key, value, version, changed_at, changed_by)
532
532
  VALUES (?, ?, ?, ?, ?, ?)
533
533
  `).run(existing.namespace, existing.key, JSON.stringify(existing.value), existing.version, now, updatedBy ?? null);
534
- getDb().prepare("DELETE FROM config WHERE namespace = ? AND key = ?").run(namespace, key);
534
+ getDb().query("DELETE FROM config WHERE namespace = ? AND key = ?").run(namespace, key);
535
535
  pruneConfigHistory(namespace, key);
536
536
  })();
537
537
  return true;
@@ -540,13 +540,13 @@ export function deleteConfig(namespace: string, key: string, updatedBy?: string)
540
540
  export function getConfigHistory<T = unknown>(namespace: string, key: string, limit = CONFIG_HISTORY_LIMIT): ConfigHistoryEntry<T>[] {
541
541
  const safeLimit = Math.min(Math.max(limit, 1), 500);
542
542
  const rows = getDb()
543
- .prepare("SELECT * FROM config_history WHERE namespace = ? AND key = ? ORDER BY version DESC, id DESC LIMIT ?")
543
+ .query("SELECT * FROM config_history WHERE namespace = ? AND key = ? ORDER BY version DESC, id DESC LIMIT ?")
544
544
  .all(namespace, key, safeLimit) as ConfigHistoryRow[];
545
545
  return rows.map(rowToConfigHistoryEntry<T>);
546
546
  }
547
547
 
548
548
  function pruneConfigHistory(namespace: string, key: string): void {
549
- getDb().prepare(`
549
+ getDb().query(`
550
550
  DELETE FROM config_history
551
551
  WHERE namespace = ? AND key = ? AND id NOT IN (
552
552
  SELECT id FROM config_history
@@ -649,13 +649,13 @@ export function deleteAgentProfile(name: string, updatedBy?: string): boolean {
649
649
  }
650
650
 
651
651
  export function getManagedAgentState(policyName: string): ManagedAgentState | null {
652
- const row = getDb().prepare("SELECT * FROM managed_agent_state WHERE policy_name = ?").get(policyName) as ManagedAgentStateRow | undefined;
652
+ const row = getDb().query("SELECT * FROM managed_agent_state WHERE policy_name = ?").get(policyName) as ManagedAgentStateRow | undefined;
653
653
  return row ? rowToManagedAgentState(row) : null;
654
654
  }
655
655
 
656
656
  function listManagedAgentStates(): ManagedAgentState[] {
657
657
  const rows = getDb()
658
- .prepare("SELECT * FROM managed_agent_state ORDER BY policy_name ASC")
658
+ .query("SELECT * FROM managed_agent_state ORDER BY policy_name ASC")
659
659
  .all() as ManagedAgentStateRow[];
660
660
  return rows.map(rowToManagedAgentState);
661
661
  }
@@ -664,7 +664,7 @@ export function upsertManagedAgentState(input: ManagedAgentStateInput): ManagedA
664
664
  if (!VALID_MANAGED_STATUSES.includes(input.status)) throw new ValidationError("status must be a managed-agent status");
665
665
  if (!VALID_PROVIDERS.includes(input.provider)) throw new ValidationError("provider must be claude or codex");
666
666
  const now = input.updatedAt ?? Date.now();
667
- getDb().prepare(`
667
+ getDb().query(`
668
668
  INSERT INTO managed_agent_state (
669
669
  policy_name, status, agent_id, orchestrator_id, provider, tmux_session, spawn_request_id, workspace_id, workspace_path, workspace_branch,
670
670
  last_spawn_at, last_stop_at, healthy_since, restart_count, consecutive_failures,
@@ -724,6 +724,6 @@ export function updateManagedAgentState(policyName: string, patch: ManagedAgentS
724
724
  }
725
725
 
726
726
  function deleteManagedAgentState(policyName: string): boolean {
727
- const result = getDb().prepare("DELETE FROM managed_agent_state WHERE policy_name = ?").run(policyName);
727
+ const result = getDb().query("DELETE FROM managed_agent_state WHERE policy_name = ?").run(policyName);
728
728
  return result.changes > 0;
729
729
  }