agents-relay 1.0.34 → 1.0.36
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.js +6 -6
- package/dist/reconciler.js +44 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -117,7 +117,7 @@ Example:
|
|
|
117
117
|
submit: `Queue a task for a managed job.
|
|
118
118
|
|
|
119
119
|
Required: --repo OWNER/REPO, --pr NUMBER, --id JOB_ID, and --input TEXT.
|
|
120
|
-
Required for worker tasks: --adapter codex|chatgpt
|
|
120
|
+
Required for worker tasks: --adapter codex|chatgpt|orchestrator and --output task-pr|file. Codex/ChatGPT also require --model MODEL. File output also requires --output-path PATH.\nCommon: --task-id ID, --priority P0|P1|P2|P3, --story-size S|M|L, --max-attempts N. Story size defaults to M. Task producers follow the shared Task / Job Contract; prefer --story-size over raw --timeout. Model-backed execution requires --events nats in the running reconciler/daemon.
|
|
121
121
|
|
|
122
122
|
Example:
|
|
123
123
|
npx agents-relay submit --repo OWNER/REPO --pr 12 --id job-1 --task-id build --adapter codex --provider openai --model gpt-5-6 --output task-pr --input "Run the required tests and report the result through events."`,
|
|
@@ -329,9 +329,9 @@ function workerTaskFromArgs(job, args, now, requireInput = false) {
|
|
|
329
329
|
const outputKind = arg(args, '--output');
|
|
330
330
|
const outputPath = arg(args, '--output-path');
|
|
331
331
|
const input = arg(args, '--input');
|
|
332
|
-
if (!['codex', 'chatgpt'].includes(adapter))
|
|
333
|
-
throw new Error('submit adapter must be codex or
|
|
334
|
-
if (!model)
|
|
332
|
+
if (!['codex', 'chatgpt', 'orchestrator'].includes(adapter))
|
|
333
|
+
throw new Error('submit adapter must be codex, chatgpt, or orchestrator');
|
|
334
|
+
if (adapter !== 'orchestrator' && !model)
|
|
335
335
|
throw new Error('submit requires --model for codex/chatgpt tasks');
|
|
336
336
|
if (requireInput && !input)
|
|
337
337
|
throw new Error('job create --start requires --input');
|
|
@@ -339,10 +339,10 @@ function workerTaskFromArgs(job, args, now, requireInput = false) {
|
|
|
339
339
|
throw new Error('submit requires --output task-pr|file');
|
|
340
340
|
if (outputKind === 'file' && !outputPath)
|
|
341
341
|
throw new Error('--output file requires --output-path');
|
|
342
|
-
const routing = { provider: arg(args, '--provider', 'openai'), model, profile: arg(args, '--profile') || undefined, reasoning: arg(args, '--reasoning') || undefined, cwd: arg(args, '--cwd') || undefined, projectId: arg(args, '--chatgpt-project') || undefined, decidedBy: 'cli', decidedAt: now };
|
|
342
|
+
const routing = adapter === 'orchestrator' ? undefined : { provider: arg(args, '--provider', 'openai'), model, profile: arg(args, '--profile') || undefined, reasoning: arg(args, '--reasoning') || undefined, cwd: arg(args, '--cwd') || undefined, projectId: arg(args, '--chatgpt-project') || undefined, decidedBy: 'cli', decidedAt: now };
|
|
343
343
|
const closePolicy = hasArg(args, '--chatgpt-tab-close-policy') ? chatGptTabClosePolicy(args) : undefined;
|
|
344
344
|
const size = storySize(args);
|
|
345
|
-
const timeoutMs = hasArg(args, '--timeout') ? integerArg(args, '--timeout', taskOverallTimeoutMs(size, routing
|
|
345
|
+
const timeoutMs = adapter === 'orchestrator' ? 0 : hasArg(args, '--timeout') ? integerArg(args, '--timeout', taskOverallTimeoutMs(size, routing?.reasoning)) : taskOverallTimeoutMs(size, routing?.reasoning);
|
|
346
346
|
return {
|
|
347
347
|
jobId: job.id,
|
|
348
348
|
id: arg(args, '--task-id', 'initial'),
|
package/dist/reconciler.js
CHANGED
|
@@ -160,6 +160,14 @@ export class Reconciler {
|
|
|
160
160
|
for (const task of scheduled.tasks.filter(t => t.state === 'READY').sort((a, b) => compareTaskPriority(a, b, scheduled.priority ?? 'P2')).slice(0, capacity)) {
|
|
161
161
|
if (task.kind === 'planner')
|
|
162
162
|
await this.launchPlanner(scheduled, task);
|
|
163
|
+
else if (task.adapter === 'orchestrator') {
|
|
164
|
+
transitionTask(task, 'RUNNING');
|
|
165
|
+
task.leaseOwner = null;
|
|
166
|
+
task.leaseExpiresAt = null;
|
|
167
|
+
task.executionId = null;
|
|
168
|
+
await this.store.saveTask(task);
|
|
169
|
+
await this.emit(eventFor(scheduled.id, task.id, task.parentTaskId, 'task.orchestrator_assigned', 'running', `Task ${task.id} assigned to orchestrator self`, 'orchestrator', { adapter: 'orchestrator' }, { agent: 'relay', component: 'agents-relay' }));
|
|
170
|
+
}
|
|
163
171
|
else
|
|
164
172
|
await this.launch(scheduled, task);
|
|
165
173
|
}
|
|
@@ -213,7 +221,7 @@ export class Reconciler {
|
|
|
213
221
|
return;
|
|
214
222
|
const id = `final-review-${headSha.slice(0, 12)}`;
|
|
215
223
|
const now = new Date().toISOString();
|
|
216
|
-
const task = { jobId: job.id, id, kind: 'work', priority: job.priority, storySize: 'M', projectName: job.title, agentName: 'reviewer', parentTaskId: null, dependencies: job.tasks.filter(item => item.agentName !== 'reviewer' && !this.isSupersededTask(item)).map(item => item.id), capabilities: ['review'], adapter: this.options.reviewer?.adapter ?? 'codex', input: `Review PR #${job.prNumber} at head SHA ${headSha}. Objective: ${job.objective ?? job.description ?? job.title}. First publish task.started as the worker start acknowledgement with data.worker_owned=true, then verify the deterministic pre-review gates have passed and inspect the completed work. Emit review.started, then exactly one of review.approved, review.changes_required, or review.blocked with data.review.headSha set to ${headSha}. Do not modify code. When auto-merge is enabled, Relay will merge this exact reviewed head after review.approved.`, output: { kind: 'task_pr' }, routing: this.options.reviewer?.routing, continuation: null, continuationDeliveredAt: null, reviewHeadSha: headSha, state: 'QUEUED', attempt: 0, maxAttempts: this.options.reviewer?.maxAttempts ?? 2, leaseOwner: null, leaseExpiresAt: null, executionId: null, threadId: null, result: null, plannerResult: null, error: null, timeoutMs: this.options.reviewer?.timeoutMs ?? storySizeTimeoutMs('M'), createdAt: now, updatedAt: now };
|
|
224
|
+
const task = { jobId: job.id, id, kind: 'work', priority: job.priority, storySize: 'M', projectName: job.title, agentName: 'reviewer', parentTaskId: null, dependencies: job.tasks.filter(item => item.agentName !== 'reviewer' && !this.isSupersededTask(item)).map(item => item.id), capabilities: ['review'], adapter: this.options.reviewer?.adapter ?? 'codex', input: `Review PR #${job.prNumber} at head SHA ${headSha}. Objective: ${job.objective ?? job.description ?? job.title}. First publish task.started as the worker start acknowledgement with data.worker_owned=true, then verify the deterministic pre-review gates have passed and inspect the completed work. Emit review.started, then exactly one of review.approved, review.changes_required, or review.blocked with data.review.headSha set to ${headSha}. For review.changes_required, include every actionable finding in data.review.issues as an array of concise issue strings; Relay will create one durable fix task containing all findings. Do not modify code. When auto-merge is enabled, Relay will merge this exact reviewed head after review.approved.`, output: { kind: 'task_pr' }, routing: this.options.reviewer?.routing, continuation: null, continuationDeliveredAt: null, reviewHeadSha: headSha, state: 'QUEUED', attempt: 0, maxAttempts: this.options.reviewer?.maxAttempts ?? 2, leaseOwner: null, leaseExpiresAt: null, executionId: null, threadId: null, result: null, plannerResult: null, error: null, timeoutMs: this.options.reviewer?.timeoutMs ?? storySizeTimeoutMs('M'), createdAt: now, updatedAt: now };
|
|
217
225
|
await this.store.appendTask(task);
|
|
218
226
|
job.review = { outcome: 'pending', headSha, taskId: id, evidence: null, updatedAt: now };
|
|
219
227
|
job.state = 'RUNNING';
|
|
@@ -254,8 +262,42 @@ export class Reconciler {
|
|
|
254
262
|
task.updatedAt = job.review.updatedAt;
|
|
255
263
|
await this.store.saveTask(task);
|
|
256
264
|
}
|
|
265
|
+
if (outcome === 'changes_required') {
|
|
266
|
+
const rawIssues = Array.isArray(reviewData?.issues) ? reviewData.issues : [];
|
|
267
|
+
const issues = rawIssues.map(item => {
|
|
268
|
+
if (typeof item === 'string')
|
|
269
|
+
return item.trim();
|
|
270
|
+
if (item && typeof item === 'object') {
|
|
271
|
+
const value = item;
|
|
272
|
+
const summary = typeof value.summary === 'string' ? value.summary : typeof value.message === 'string' ? value.message : typeof value.title === 'string' ? value.title : '';
|
|
273
|
+
return summary.trim();
|
|
274
|
+
}
|
|
275
|
+
return '';
|
|
276
|
+
}).filter(Boolean);
|
|
277
|
+
if (issues.length === 0)
|
|
278
|
+
issues.push(event.message || 'Address the reviewer-requested changes');
|
|
279
|
+
const now = event.timestamp || new Date().toISOString();
|
|
280
|
+
const id = `review-fix-${headSha.slice(0, 8)}`;
|
|
281
|
+
if (!job.tasks.some(item => item.id === id)) {
|
|
282
|
+
const input = `Address all reviewer findings for PR #${job.prNumber} at ${headSha}:\n\n${issues.map((issue, index) => `${index + 1}. ${issue}`).join('\n')}`;
|
|
283
|
+
const fixTask = {
|
|
284
|
+
jobId: job.id, id, kind: 'work', priority: job.priority, storySize: 'M', projectName: job.title,
|
|
285
|
+
agentName: 'review-fix', parentTaskId: task.id, dependencies: [task.id], capabilities: ['implementation'],
|
|
286
|
+
adapter: 'codex', input, output: { kind: 'task_pr' },
|
|
287
|
+
routing: { provider: 'openai', model: 'gpt-5.6-luna', reasoning: 'medium', decidedBy: 'review-fix', decidedAt: now },
|
|
288
|
+
continuation: null, continuationDeliveredAt: null, state: 'QUEUED', attempt: 0, maxAttempts: 3,
|
|
289
|
+
leaseOwner: null, leaseExpiresAt: null, executionId: null, threadId: null, result: null, plannerResult: null,
|
|
290
|
+
error: null, timeoutMs: storySizeTimeoutMs('M'), createdAt: now, updatedAt: now
|
|
291
|
+
};
|
|
292
|
+
await this.store.appendTask(fixTask);
|
|
293
|
+
await this.emit(eventFor(job.id, id, task.id, 'review.fix_task_created', 'queued', `Created one fix task ${id} for ${issues.length} reviewer finding(s)`, 'orchestrator', { headSha, issues }, { agent: 'relay', component: 'agents-relay' }));
|
|
294
|
+
}
|
|
295
|
+
job.state = 'RUNNING';
|
|
296
|
+
}
|
|
257
297
|
job.updatedAt = job.review.updatedAt;
|
|
258
298
|
await this.store.saveJob(job);
|
|
299
|
+
if (outcome === 'changes_required')
|
|
300
|
+
this.detach(this.reconcile(job.id), job.id, task.id, 'review-fix reconciliation');
|
|
259
301
|
if (outcome === 'approved' && job.autoMerge !== false && this.store.mergePullRequest) {
|
|
260
302
|
await this.store.mergePullRequest(headSha);
|
|
261
303
|
await this.emit(eventFor(job.id, task.id, task.parentTaskId, 'review.merged', 'succeeded', 'Reviewer-approved PR #' + job.prNumber + ' merged', 'user', { headSha, autoMerge: true }, { agent: 'reviewer', component: 'agents-relay' }));
|
|
@@ -675,7 +717,7 @@ export class Reconciler {
|
|
|
675
717
|
async applyTerminalEvent(event) {
|
|
676
718
|
const job = await this.store.load(event.job_id);
|
|
677
719
|
const task = job.tasks.find(item => item.id === event.task_id);
|
|
678
|
-
if (!task || !['codex', 'chatgpt'].includes(task.adapter))
|
|
720
|
+
if (!task || !['codex', 'chatgpt', 'orchestrator'].includes(task.adapter))
|
|
679
721
|
return;
|
|
680
722
|
if (task.state === 'FAILED' && this.isRecoverableWatchdogFailure(task) && this.isWorkerOwnedEvent(event)) {
|
|
681
723
|
if (event.timestamp && new Date(event.timestamp).getTime() < new Date(task.updatedAt).getTime())
|