agents-relay 2.0.15 → 2.0.17

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.
@@ -4,6 +4,7 @@ import { githubAuthContext } from './github-auth.js';
4
4
  import { NatsEventBus, eventFor } from './events.js';
5
5
  import { parseJobSchedule } from './scheduler.js';
6
6
  import { parseJobTagJson } from './task-tag.js';
7
+ import { resolveEventsBusRuntime } from './events-bus-runtime.js';
7
8
  export const SERVICE_DEFAULTS = { port: 8787, watchdogMs: 300000, webhookWatchdogMs: 300000, dashboardRefreshMs: 300000, projectionFlushMs: 1000 };
8
9
  export class ServiceEventBus {
9
10
  upstream;
@@ -101,23 +102,30 @@ export async function relayTaskCommand(args, body) {
101
102
  export async function resolveEventBus(args, probe) {
102
103
  const explicit = hasArg(args, '--events');
103
104
  const requested = arg(args, '--events');
104
- const natsUrl = arg(args, '--nats-url', process.env.NEO_NATS_URL || 'nats://127.0.0.1:4222');
105
+ const runtime = resolveEventsBusRuntime();
106
+ const natsUrl = arg(args, '--nats-url', runtime.natsUrl);
107
+ if (!explicit && hasArg(args, '--file'))
108
+ return { bus: undefined, transport: 'none', source: 'local', natsUrl };
105
109
  if (explicit && requested !== 'nats')
106
110
  return { bus: undefined, transport: 'none', source: 'explicit', natsUrl };
107
- const bus = new NatsEventBus(natsUrl, arg(args, '--subject-prefix', 'neo.events.job'));
111
+ const subjectPrefix = arg(args, '--subject-prefix', runtime.subjectPrefix);
112
+ const bus = new NatsEventBus(natsUrl, subjectPrefix);
108
113
  if (explicit)
109
114
  return { bus, transport: 'nats', source: 'explicit', natsUrl };
110
115
  const reachable = probe ? await probe(bus) : await bus.ping().then(() => true, () => false);
111
- if (!reachable) {
112
- await bus.close().catch(() => undefined);
116
+ await bus.close().catch(() => undefined);
117
+ if (!reachable)
113
118
  return { bus: undefined, transport: 'none', source: 'unavailable', natsUrl };
114
- }
115
- return { bus, transport: 'nats', source: 'auto', natsUrl };
119
+ return { bus: new NatsEventBus(natsUrl, subjectPrefix), transport: 'nats', source: 'auto', natsUrl };
116
120
  }
117
121
  export function eventBusResolutionMessage(resolution) {
118
122
  if (resolution.transport === 'nats')
119
123
  return `event transport: nats (${resolution.source === 'auto' ? 'auto-detected' : 'explicit'})`;
120
- return resolution.source === 'explicit' ? 'event transport: none (explicit)' : 'event transport: none (NATS unavailable)';
124
+ if (resolution.source === 'explicit')
125
+ return 'event transport: none (explicit)';
126
+ if (resolution.source === 'local')
127
+ return 'event transport: none (local mode)';
128
+ return 'event transport: none (NATS unavailable)';
121
129
  }
122
130
  export async function eventBus(args) {
123
131
  const resolution = await resolveEventBus(args);
package/dist/dashboard.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { createServer } from 'node:http';
2
2
  import { readFile, readdir } from 'node:fs/promises';
3
- import { homedir } from 'node:os';
4
3
  import { join } from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
6
5
  import { readUsageSnapshot } from './usage.js';
@@ -8,9 +7,9 @@ import { deriveJobRuntimeStatus } from './runtime-status.js';
8
7
  import { DeliveryDeduper, parseGitHubWebhook, verifyGitHubWebhook } from './github-webhook.js';
9
8
  import { loadExecutionRouterRules, saveExecutionRouterRules } from './routing-policy.js';
10
9
  import { summarizeRuntime } from './runtime-summary.js';
10
+ import { eventsBusHistory } from './events-bus-runtime.js';
11
11
  const GITHUB_REPOSITORY_PATTERN = /^([A-Za-z0-9](?:[A-Za-z0-9-]{0,36}[A-Za-z0-9])?)\/([A-Za-z0-9._-]{1,100})$/;
12
12
  const SSE_HEARTBEAT_MS = 15000;
13
- const EVENTS_HISTORY_FILE = process.env.EVENTS_BUS_DATA_DIR ? join(process.env.EVENTS_BUS_DATA_DIR, 'events.jsonl') : join(homedir(), 'Library', 'Application Support', 'EventsBus', 'events.jsonl');
14
13
  const WORKFLOW_DIR = fileURLToPath(new URL('../workflow/', import.meta.url));
15
14
  export async function dashboardWorkflowGraphs() {
16
15
  const names = (await readdir(WORKFLOW_DIR)).filter(name => name.endsWith('.mmd')).sort();
@@ -49,30 +48,12 @@ export function githubPrUrl(repository, prNumber) {
49
48
  return `https://github.com/${owner}/${repo}/pull/${prNumber}`;
50
49
  }
51
50
  async function eventHistory(jobId, taskId, limit) {
52
- const boundedLimit = Math.max(1, Math.min(60, limit));
53
- let content = '';
54
51
  try {
55
- content = await readFile(EVENTS_HISTORY_FILE, 'utf8');
52
+ return await eventsBusHistory(jobId, taskId, limit);
56
53
  }
57
54
  catch {
58
55
  return [];
59
56
  }
60
- const output = [];
61
- const lines = content.split(/\r?\n/);
62
- for (let index = lines.length - 1; index >= 0 && output.length < boundedLimit; index -= 1) {
63
- const line = lines[index];
64
- if (!line)
65
- continue;
66
- try {
67
- const row = JSON.parse(line);
68
- const event = row.event;
69
- if (!event || event.job_id !== jobId || (taskId && event.task_id !== taskId))
70
- continue;
71
- output.push(event);
72
- }
73
- catch { /* ignore malformed historical rows */ }
74
- }
75
- return output;
76
57
  }
77
58
  export { dashboardHtml, dashboardVersion } from './dashboard-html.js';
78
59
  import { dashboardHtml, dashboardVersion } from './dashboard-html.js';
@@ -0,0 +1,39 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ export function eventsBusConfigPath(env = process.env) {
5
+ if (env.NEO_EVENTS_BUS_CONFIG)
6
+ return env.NEO_EVENTS_BUS_CONFIG;
7
+ const dataDir = env.EVENTS_BUS_DATA_DIR || join(homedir(), 'Library', 'Application Support', 'EventsBus');
8
+ return join(dataDir, 'runtime.json');
9
+ }
10
+ export function resolveEventsBusRuntime(env = process.env) {
11
+ const configPath = eventsBusConfigPath(env);
12
+ let saved = {};
13
+ try {
14
+ saved = JSON.parse(readFileSync(configPath, 'utf8'));
15
+ }
16
+ catch { /* fall through to protocol defaults */ }
17
+ return {
18
+ configPath,
19
+ natsUrl: env.NEO_NATS_URL || String(saved.natsUrl || 'nats://127.0.0.1:4222'),
20
+ apiUrl: env.NEO_EVENTS_API_URL || String(saved.apiUrl || 'http://127.0.0.1:4223'),
21
+ subjectPrefix: env.NEO_EVENTS_SUBJECT_PREFIX || String(saved.subjectPrefix || 'neo.events.job'),
22
+ };
23
+ }
24
+ export async function eventsBusHistory(jobId, taskId, limit, env = process.env) {
25
+ const runtime = resolveEventsBusRuntime(env);
26
+ const url = new URL('/events', runtime.apiUrl);
27
+ url.searchParams.set('job', jobId);
28
+ if (taskId)
29
+ url.searchParams.set('task', taskId);
30
+ const boundedLimit = Math.max(1, Math.min(60, limit));
31
+ url.searchParams.set('limit', String(boundedLimit));
32
+ url.searchParams.set('order', 'desc');
33
+ const response = await fetch(url);
34
+ if (!response.ok)
35
+ throw new Error(`events-bus history unavailable: HTTP ${response.status}`);
36
+ const body = await response.json();
37
+ const rows = Array.isArray(body.events) ? body.events : [];
38
+ return rows.map(row => row.event).filter((event) => Boolean(event));
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,7 +41,7 @@ A running agent MUST use the canonical Neo `events-bus` protocol and publish exa
41
41
  - `task.failed` when the task cannot complete;
42
42
  - `task.blocked` when external or human action is required.
43
43
 
44
- Event publishing is not an Agents Relay CLI responsibility. Sandboxed Codex and hosted ChatGPT workers use the federated `events__publish` MCP tool from `events-bus`; direct non-sandbox local workers may use the caller-provided `NEO_EVENTS_EMIT`. Agents Relay subscribes to the canonical `neo.events.job.<job_id>.>` stream and maps terminal events into durable task state.
44
+ Event publishing is not an Agents Relay CLI responsibility. Sandboxed Codex and hosted ChatGPT workers use the federated `events__publish` MCP tool from `events-bus`; direct non-sandbox local workers may use the caller-provided `NEO_EVENTS_EMIT`. Agents Relay subscribes to the canonical `neo.events.job.<job_id>.>` stream and maps terminal events into durable task state. Relay resolves its native NATS endpoint and subject prefix from the canonical events-bus runtime configuration (with explicit Relay CLI flags remaining authoritative). The same runtime configuration supplies the events-bus HTTP API used for replay/history. Relay and its dashboard must not read the events-bus private history file or maintain a second event-history backend.
45
45
 
46
46
  The event must carry the exact durable `job_id` and `task_id`. Progress events are optional.
47
47