agents-relay 2.0.14 → 2.0.15
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-blocker.js +1 -1
- package/dist/cli-help.js +2 -2
- package/dist/cli-support.js +27 -3
- package/dist/cli-unblock.js +3 -3
- package/dist/cli.js +5 -5
- package/dist/dashboard-html.js +2 -1
- package/dist/events.js +2 -1
- package/dist/relayd.js +5 -3
- package/package.json +1 -1
package/dist/cli-blocker.js
CHANGED
|
@@ -19,7 +19,7 @@ export async function runBlockerCommand(action, args) {
|
|
|
19
19
|
}
|
|
20
20
|
if (action === 'resolve') {
|
|
21
21
|
const summary = await blockerStore.resolveBlockerDependents(id, 'verified');
|
|
22
|
-
await publishDependentUnblocked(eventBus(args), summary.unblocked, `Blocker Job ${id} resolved (verified)`);
|
|
22
|
+
await publishDependentUnblocked(await eventBus(args), summary.unblocked, `Blocker Job ${id} resolved (verified)`);
|
|
23
23
|
console.log(JSON.stringify({ blocker: id, resolution: 'verified', ...summary }, null, 2));
|
|
24
24
|
return;
|
|
25
25
|
}
|
package/dist/cli-help.js
CHANGED
|
@@ -179,7 +179,7 @@ Example:
|
|
|
179
179
|
|
|
180
180
|
Required: --file PATH for local demo mode, or --repo OWNER/REPO --pr NUMBER --id JOB_ID.
|
|
181
181
|
|
|
182
|
-
Managed GitHub jobs are reconciled by agents-relayd. The CLI publishes a job.wake request and does not launch workers; use --events nats.
|
|
182
|
+
Managed GitHub jobs are reconciled by agents-relayd. The CLI publishes a job.wake request and does not launch workers; NATS is auto-detected when reachable; use --events nats to require it explicitly.
|
|
183
183
|
|
|
184
184
|
Example:
|
|
185
185
|
npx agents-relay reconcile --repo OWNER/REPO --pr 12 --id job-1`,
|
|
@@ -192,7 +192,7 @@ Example:
|
|
|
192
192
|
serve: `Run the dashboard and reconciliation service for a managed job.
|
|
193
193
|
|
|
194
194
|
Required: --file PATH for local demo mode, or --repo OWNER/REPO --pr NUMBER --id JOB_ID.
|
|
195
|
-
Common: --port PORT, --interval MS, --events nats.
|
|
195
|
+
Common: --port PORT, --interval MS, --events nats (optional; reachable NATS is auto-detected).
|
|
196
196
|
|
|
197
197
|
Example:
|
|
198
198
|
npx agents-relay serve --repo OWNER/REPO --pr 12 --id job-1`,
|
package/dist/cli-support.js
CHANGED
|
@@ -98,8 +98,32 @@ export async function relayTaskCommand(args, body) {
|
|
|
98
98
|
throw new Error(`Agents Relay task command failed: ${await response.text() || `HTTP ${response.status}`}`);
|
|
99
99
|
return await response.json();
|
|
100
100
|
}
|
|
101
|
-
export function
|
|
102
|
-
|
|
101
|
+
export async function resolveEventBus(args, probe) {
|
|
102
|
+
const explicit = hasArg(args, '--events');
|
|
103
|
+
const requested = arg(args, '--events');
|
|
104
|
+
const natsUrl = arg(args, '--nats-url', process.env.NEO_NATS_URL || 'nats://127.0.0.1:4222');
|
|
105
|
+
if (explicit && requested !== 'nats')
|
|
106
|
+
return { bus: undefined, transport: 'none', source: 'explicit', natsUrl };
|
|
107
|
+
const bus = new NatsEventBus(natsUrl, arg(args, '--subject-prefix', 'neo.events.job'));
|
|
108
|
+
if (explicit)
|
|
109
|
+
return { bus, transport: 'nats', source: 'explicit', natsUrl };
|
|
110
|
+
const reachable = probe ? await probe(bus) : await bus.ping().then(() => true, () => false);
|
|
111
|
+
if (!reachable) {
|
|
112
|
+
await bus.close().catch(() => undefined);
|
|
113
|
+
return { bus: undefined, transport: 'none', source: 'unavailable', natsUrl };
|
|
114
|
+
}
|
|
115
|
+
return { bus, transport: 'nats', source: 'auto', natsUrl };
|
|
116
|
+
}
|
|
117
|
+
export function eventBusResolutionMessage(resolution) {
|
|
118
|
+
if (resolution.transport === 'nats')
|
|
119
|
+
return `event transport: nats (${resolution.source === 'auto' ? 'auto-detected' : 'explicit'})`;
|
|
120
|
+
return resolution.source === 'explicit' ? 'event transport: none (explicit)' : 'event transport: none (NATS unavailable)';
|
|
121
|
+
}
|
|
122
|
+
export async function eventBus(args) {
|
|
123
|
+
const resolution = await resolveEventBus(args);
|
|
124
|
+
process.stderr.write(`${eventBusResolutionMessage(resolution)}\n`);
|
|
125
|
+
return resolution.bus;
|
|
126
|
+
}
|
|
103
127
|
export async function publishWake(bus, job, taskId, message) { if (!bus)
|
|
104
128
|
return; try {
|
|
105
129
|
await bus.publish(eventFor(job.id, taskId, null, 'job.wake', 'queued', message, 'orchestrator', { repository: job.repository, prNumber: job.prNumber }));
|
|
@@ -125,7 +149,7 @@ export async function publishDependentUnblocked(bus, targets, reason) { if (!bus
|
|
|
125
149
|
} }
|
|
126
150
|
export async function requestManagedReconciliation(bus, job) {
|
|
127
151
|
if (!bus)
|
|
128
|
-
throw new Error('Managed GitHub reconciliation requires
|
|
152
|
+
throw new Error('Managed GitHub reconciliation requires a reachable NATS event bus (auto-detected when available, or explicit with --events nats)');
|
|
129
153
|
try {
|
|
130
154
|
await bus.publish(eventFor(job.id, 'reconcile', null, 'job.wake', 'queued', `Reconcile requested for ${job.id}`, 'orchestrator', { repository: job.repository, prNumber: job.prNumber }));
|
|
131
155
|
}
|
package/dist/cli-unblock.js
CHANGED
|
@@ -7,7 +7,7 @@ export async function runTaskUnblockCommand(args, store, job, task, localFile) {
|
|
|
7
7
|
const result = await unblockTask({ store, job, task, trigger: { source: 'cli', reason } });
|
|
8
8
|
const refreshed = await store.load(job.id);
|
|
9
9
|
await saveLocal(localFile, refreshed);
|
|
10
|
-
const bus = eventBus(args);
|
|
10
|
+
const bus = await eventBus(args);
|
|
11
11
|
await publishTaskUnblocked(bus, refreshed, task.id, reason);
|
|
12
12
|
await publishWake(bus, refreshed, task.id, `Task ${task.id} unblocked${result.replanning ? ' for replanning' : ''}`);
|
|
13
13
|
console.log(JSON.stringify({ taskId: task.id, ...result, task: refreshed.tasks.find(item => item.id === task.id) }, null, 2));
|
|
@@ -22,7 +22,7 @@ export async function runJobUnblockCommand(args, store, job) {
|
|
|
22
22
|
if (job.executionMode === 'autonomous') {
|
|
23
23
|
const planTrigger = await unblockAutonomousTasks({ store, job, tasks: blocked, trigger: { source: 'cli', reason } });
|
|
24
24
|
const refreshed = await store.load(job.id);
|
|
25
|
-
const bus = eventBus(args);
|
|
25
|
+
const bus = await eventBus(args);
|
|
26
26
|
for (const task of blocked)
|
|
27
27
|
await publishTaskUnblocked(bus, refreshed, task.id, reason);
|
|
28
28
|
await publishWake(bus, refreshed, 'job', `Job ${job.id} unblocked for replanning`);
|
|
@@ -32,7 +32,7 @@ export async function runJobUnblockCommand(args, store, job) {
|
|
|
32
32
|
for (const task of blocked)
|
|
33
33
|
await unblockTask({ store, job, task, trigger: { source: 'cli', reason } });
|
|
34
34
|
const refreshed = await store.load(job.id);
|
|
35
|
-
const bus = eventBus(args);
|
|
35
|
+
const bus = await eventBus(args);
|
|
36
36
|
for (const task of blocked)
|
|
37
37
|
await publishTaskUnblocked(bus, refreshed, task.id, reason);
|
|
38
38
|
await publishWake(bus, refreshed, 'job', `Job ${job.id} unblocked`);
|
package/dist/cli.js
CHANGED
|
@@ -184,7 +184,7 @@ async function runTaskCommand(action, args) {
|
|
|
184
184
|
}
|
|
185
185
|
const durableTask = (await store.load(job.id)).tasks.find(item => item.id === task.id) ?? task;
|
|
186
186
|
await saveLocal(localFile, await store.load(job.id));
|
|
187
|
-
await publishWake(eventBus(args), refreshed, task.id, `Task ${task.id} created`);
|
|
187
|
+
await publishWake(await eventBus(args), refreshed, task.id, `Task ${task.id} created`);
|
|
188
188
|
console.log(JSON.stringify(durableTask, null, 2));
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
@@ -227,7 +227,7 @@ async function runTaskCommand(action, args) {
|
|
|
227
227
|
outputTask = outputJob.tasks.find(item => item.id === id) ?? result.task;
|
|
228
228
|
}
|
|
229
229
|
await saveLocal(localFile, outputJob);
|
|
230
|
-
await publishWake(eventBus(args), outputJob, outputTask.id, `${outputTask.id} ${action} requested`);
|
|
230
|
+
await publishWake(await eventBus(args), outputJob, outputTask.id, `${outputTask.id} ${action} requested`);
|
|
231
231
|
console.log(JSON.stringify(outputTask, null, 2));
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
@@ -395,7 +395,7 @@ async function runJobCommand(action, args) {
|
|
|
395
395
|
await transitionJobEvent(job, 'pr.merged');
|
|
396
396
|
await store.saveJob(job);
|
|
397
397
|
const resolved = await store.resolveBlockerDependents(id, 'merge');
|
|
398
|
-
await publishDependentUnblocked(eventBus(args), resolved.unblocked, `Blocker Job ${id} resolved (merge)`);
|
|
398
|
+
await publishDependentUnblocked(await eventBus(args), resolved.unblocked, `Blocker Job ${id} resolved (merge)`);
|
|
399
399
|
console.log(JSON.stringify(await store.load(id), null, 2));
|
|
400
400
|
return;
|
|
401
401
|
}
|
|
@@ -465,7 +465,7 @@ export function dashboardJobAction(store, reconciler, action) {
|
|
|
465
465
|
}
|
|
466
466
|
export async function createService(args) {
|
|
467
467
|
const loaded = await storeFor(args);
|
|
468
|
-
const upstream = eventBus(args);
|
|
468
|
+
const upstream = await eventBus(args);
|
|
469
469
|
const bus = upstream ? new ServiceEventBus(upstream) : undefined;
|
|
470
470
|
const requestedUsageDays = Number(arg(args, '--usage-window-days', '1'));
|
|
471
471
|
const usageWindowDays = Number.isInteger(requestedUsageDays) && requestedUsageDays > 0 ? requestedUsageDays : 1;
|
|
@@ -590,7 +590,7 @@ async function main() {
|
|
|
590
590
|
console.log(`Initialized ${job.id}`);
|
|
591
591
|
return;
|
|
592
592
|
}
|
|
593
|
-
const bus = eventBus(args);
|
|
593
|
+
const bus = await eventBus(args);
|
|
594
594
|
if (command === 'status') {
|
|
595
595
|
const host = arg(args, '--host', '127.0.0.1');
|
|
596
596
|
const port = Number(arg(args, '--port', String(SERVICE_DEFAULTS.port)));
|
package/dist/dashboard-html.js
CHANGED
|
@@ -392,7 +392,8 @@ function chatShortcutText(){return localStorage.getItem(CHAT_SHORTCUTS_KEY)||DEF
|
|
|
392
392
|
function parseChatShortcuts(source){return String(source||'').split(/\\r?\\n/).map(line=>line.trim()).filter(Boolean).map(line=>{const split=line.indexOf(':');return split>0?{name:line.slice(0,split).trim(),prompt:line.slice(split+1).trim()}:null;}).filter(item=>item&&item.name&&item.prompt);}
|
|
393
393
|
function jobChatRef(job){return repoName(job?.repository)+'/pr#'+value(job?.prNumber);}
|
|
394
394
|
function chatShortcutUrl(prompt){return 'https://chatgpt.com/?prompt='+encodeURIComponent(prompt);}
|
|
395
|
-
|
|
395
|
+
const CHAT_SHORTCUT_TARGET='agents-relay-shortcut';
|
|
396
|
+
function chatShortcutField(job){const box=element('div','detail-group');box.append(element('p','detail-label',text('ChatGPT shortcut')));const select=document.createElement('select');select.className='job-filter job-chat-shortcut-select';select.setAttribute('aria-label','ChatGPT shortcut');select.append(new Option('',''));for(const item of parseChatShortcuts(chatShortcutText()))select.append(new Option(item.name,item.prompt));select.value='';select.addEventListener('change',()=>{if(!select.value)return;const prompt=select.value.replaceAll('%1',jobChatRef(job));window.open(chatShortcutUrl(prompt),CHAT_SHORTCUT_TARGET,'noopener');select.value='';});box.append(select);return box;}
|
|
396
397
|
function renderJobList(){jobList.replaceChildren();const visible=filteredJobs();if(!jobs.length){jobList.append(element('li','empty',text('No durable jobs discovered.')));return;}if(!visible.length){jobList.append(element('li','empty',text('No jobs match the selected repo.')));return;}for(const job of visible){const row=element('div','job-row'+(isCompleteUnmerged(job)?' complete-unmerged':''));row.setAttribute('aria-current',String(job.id===selectedJob));row.addEventListener('click',()=>selectJob(job.id));const content=element('div','job-content');const select=document.createElement('button');select.type='button';select.className='job-button';select.setAttribute('aria-current',String(job.id===selectedJob));select.title=job.title||job.id;select.addEventListener('click',event=>{event.stopPropagation();selectJob(job.id);});select.append(element('span','job-title',text(repoName(job.repository)+'/PR#'+value(job.prNumber)+': '+value(job.title||job.id))));const states=element('span','job-status-line');states.append(element('span','badge '+value(job.state),text(value(job.state))),element('span','badge '+value(job.runtimeStatus||'IDLE'),text(value(job.runtimeStatus||'IDLE'))));if(job.executionMode==='autonomous')states.append(element('span','badge flag-autonomous',text('AUTONOMOUS')));if(job.reviewEnabled!==false&&job.githubState!=='MERGED')states.append(element('span','badge flag-review',text('REVIEW REQUIRED')));if(job.githubState==='MERGED'&&job.publication?.version)states.append(element('span','badge SUCCEEDED',text('v'+job.publication.version)));if(isCompleteUnmerged(job))states.append(element('span','badge MERGE_PENDING',text('MERGE PENDING')));const primary=element('div','job-primary');primary.append(states);const lastLine=element('div','job-last-line');lastLine.append(element('span','subtle',text(compactCounts(counts(job.tasks)))),element('span','subtle',text(relativeTime(job.updatedAt))));content.append(select,primary,lastLine);row.append(content);jobList.append(element('li','',row));}}
|
|
397
398
|
function taskTagDisplay(job,task){const display=task.tag&&job?.tag?.values?.[task.tag];return typeof display==='string'&&display?display:null;}
|
|
398
399
|
function taskSubtreeComplete(task,children){return ['SUCCEEDED','CANCELLED'].includes(task.state)&&(children.get(task.id)||[]).every(child=>taskSubtreeComplete(child,children));}
|
package/dist/events.js
CHANGED
|
@@ -20,7 +20,8 @@ export class NatsEventBus {
|
|
|
20
20
|
throw new Error(`Unsafe event subject prefix: ${subjectPrefix}`);
|
|
21
21
|
}
|
|
22
22
|
async moduleLoaded() { this.module ??= await import('nats'); return this.module; }
|
|
23
|
-
async client() { const nats = await this.moduleLoaded(); this.connection ??= await nats.connect({ servers: this.url }); return this.connection; }
|
|
23
|
+
async client() { const nats = await this.moduleLoaded(); this.connection ??= await nats.connect({ servers: this.url, timeout: 1000 }); return this.connection; }
|
|
24
|
+
async ping() { await (await this.client()).flush(); }
|
|
24
25
|
async publish(event) { const nats = await this.moduleLoaded(); const subject = `${this.subjectPrefix}.${subjectToken(event.job_id)}.${event.type}`; (await this.client()).publish(subject, nats.StringCodec().encode(JSON.stringify(event))); }
|
|
25
26
|
async subscribe(jobId, wake) { return this.subscribeSubject(`${this.subjectPrefix}.${subjectToken(jobId)}.>`, wake); }
|
|
26
27
|
async subscribeAll(wake) { return this.subscribeSubject(`${this.subjectPrefix}.>`, wake); }
|
package/dist/relayd.js
CHANGED
|
@@ -6,7 +6,6 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { ChatGptAdapter, CodexAdapter } from './adapters.js';
|
|
7
7
|
import { CodexThreadContinuation, CommandContinuation, WebhookContinuation } from './continuation.js';
|
|
8
8
|
import { serveDashboard } from './dashboard.js';
|
|
9
|
-
import { NatsEventBus } from './events.js';
|
|
10
9
|
import { RepositoryWorkerPool, aggregateManagedGitHubJobs } from './pool.js';
|
|
11
10
|
import { Reconciler } from './reconciler.js';
|
|
12
11
|
import { dashboardManagedJobs, GitHubStore, InMemoryStore, loadManagedGitHubPullRequestSnapshotGraphql, withGitHubRequestContext } from './store.js';
|
|
@@ -14,6 +13,7 @@ import { ProjectedDurableStore } from './projected-store.js';
|
|
|
14
13
|
import { discoverLocalRelayStoreSnapshots, SQLiteRelayStore } from './relay-store.js';
|
|
15
14
|
import { githubAuthContext } from './github-auth.js';
|
|
16
15
|
import { dashboardJobAction, isEntrypoint, runtimePlanner, runtimeReviewer, startService, SERVICE_DEFAULTS } from './cli.js';
|
|
16
|
+
import { eventBusResolutionMessage, resolveEventBus } from './cli-support.js';
|
|
17
17
|
import { applyJobActivateAction } from './job-actions.js';
|
|
18
18
|
import { applyOrchestratorCompleteAction } from './task-actions.js';
|
|
19
19
|
import { dashboardJobSetState, dashboardTaskAction, dashboardTaskSetState } from './dashboard-actions.js';
|
|
@@ -54,7 +54,7 @@ Options:
|
|
|
54
54
|
--projection-ms MS GitHub projection flush interval (default: 1000)
|
|
55
55
|
--execution-router-rules PATH
|
|
56
56
|
Router Rules file (default: ~/.agents-relay/execution-router-rules.md)
|
|
57
|
-
--events nats
|
|
57
|
+
--events nats Require NATS event wakeups (otherwise auto-detect reachable NATS)
|
|
58
58
|
|
|
59
59
|
Legacy single-job mode also accepts --repo OWNER/REPO --pr NUMBER --id JOB_ID.
|
|
60
60
|
|
|
@@ -119,7 +119,9 @@ export async function runDaemon(argv) {
|
|
|
119
119
|
const client = auth.client;
|
|
120
120
|
const trusted = auth.trustedAuthors;
|
|
121
121
|
const concurrency = Number(value(argv, '--concurrency', '4'));
|
|
122
|
-
const
|
|
122
|
+
const eventResolution = await resolveEventBus([...argv]);
|
|
123
|
+
const bus = eventResolution.bus;
|
|
124
|
+
process.stderr.write(`${eventBusResolutionMessage(eventResolution)}\n`);
|
|
123
125
|
const workspaceRoot = normalized.workspaceRoot ?? defaultWorkspaceRoot();
|
|
124
126
|
const discoveredRepositories = normalized.repository ? [] : await discoverWorkspaceRepositories(workspaceRoot);
|
|
125
127
|
const repositories = normalized.repository ? [normalized.repository] : discoveredRepositories.map(item => item.repository);
|