agents-relay 2.0.16 → 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,19 +102,21 @@ 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);
105
107
  if (!explicit && hasArg(args, '--file'))
106
108
  return { bus: undefined, transport: 'none', source: 'local', natsUrl };
107
109
  if (explicit && requested !== 'nats')
108
110
  return { bus: undefined, transport: 'none', source: 'explicit', natsUrl };
109
- 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);
110
113
  if (explicit)
111
114
  return { bus, transport: 'nats', source: 'explicit', natsUrl };
112
115
  const reachable = probe ? await probe(bus) : await bus.ping().then(() => true, () => false);
113
116
  await bus.close().catch(() => undefined);
114
117
  if (!reachable)
115
118
  return { bus: undefined, transport: 'none', source: 'unavailable', natsUrl };
116
- return { bus: new NatsEventBus(natsUrl, arg(args, '--subject-prefix', 'neo.events.job')), transport: 'nats', source: 'auto', natsUrl };
119
+ return { bus: new NatsEventBus(natsUrl, subjectPrefix), transport: 'nats', source: 'auto', natsUrl };
117
120
  }
118
121
  export function eventBusResolutionMessage(resolution) {
119
122
  if (resolution.transport === 'nats')
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.16",
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