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.
- package/dist/cli-support.js +15 -7
- package/dist/dashboard.js +2 -21
- package/dist/events-bus-runtime.js +39 -0
- package/package.json +1 -1
- package/skills/agents-relay/SKILL.md +1 -1
package/dist/cli-support.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
|