agents-relay 1.0.123 → 1.0.125

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.
@@ -1,6 +1,6 @@
1
1
  import { storySizeTimeoutMs, taskOverallTimeoutMs } from './types.js';
2
2
  import { schedule, transitionTaskEvent, transitionJobEvent } from './scheduler.js';
3
- import { implementationComplete } from './runtime-status.js';
3
+ import { implementationComplete, implementationTasks } from './runtime-status.js';
4
4
  import { eventFor } from './events.js';
5
5
  import { selectReviewer } from './reviewer-routing.js';
6
6
  import { routeWorker, workerCandidates } from './worker-routing.js';
@@ -26,8 +26,6 @@ export class ReconcilerReviewBase extends ReconcilerRuntimeBase {
26
26
  async ensureFinalReview(job, prState, headSha) {
27
27
  if (job.schedule || job.reviewEnabled === false || prState !== 'OPEN' || job.state !== 'ACTIVE' || !implementationComplete(job) || !headSha)
28
28
  return;
29
- if (!this.preReviewGatesPass(job))
30
- return;
31
29
  const review = job.review;
32
30
  if (review?.headSha === headSha && ['started', 'approved', 'changes_required', 'blocked'].includes(review.outcome))
33
31
  return;
@@ -38,9 +36,9 @@ export class ReconcilerReviewBase extends ReconcilerRuntimeBase {
38
36
  return;
39
37
  const id = `final-review-${headSha.slice(0, 12)}`;
40
38
  const now = new Date().toISOString();
41
- const implementationTasks = job.tasks.filter(item => item.agentName !== 'reviewer' && !this.isSupersededTask(item));
42
- const reviewerRouting = await this.selectReviewer(implementationTasks);
43
- const task = { jobId: job.id, id, kind: 'work', priority: job.priority, storySize: 'M', projectName: job.title, agentName: 'reviewer', parentTaskId: null, dependencies: implementationTasks.map(item => item.id), capabilities: ['review'], workerSelection: reviewerRouting.selection, adapter: reviewerRouting.candidate.adapter, input: `Review existing PR #${job.prNumber} at exactly head SHA ${headSha}. Job objective: ${job.objective ?? job.description ?? job.title}. Use the selected reviewer agent contract as the authoritative review protocol.`, output: { kind: 'task_pr' }, routing: reviewerRouting.candidate.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 };
39
+ const reviewGateTasks = implementationTasks(job);
40
+ const reviewerRouting = await this.selectReviewer(reviewGateTasks);
41
+ const task = { jobId: job.id, id, kind: 'work', priority: job.priority, storySize: 'M', projectName: job.title, agentName: 'reviewer', parentTaskId: null, dependencies: reviewGateTasks.map(item => item.id), capabilities: ['review'], workerSelection: reviewerRouting.selection, adapter: reviewerRouting.candidate.adapter, input: `Review existing PR #${job.prNumber} at exactly head SHA ${headSha}. Job objective: ${job.objective ?? job.description ?? job.title}. Use the selected reviewer agent contract as the authoritative review protocol.`, output: { kind: 'task_pr' }, routing: reviewerRouting.candidate.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 };
44
42
  await this.store.appendTask(task);
45
43
  job.review = { outcome: 'pending', headSha, taskId: id, evidence: null, updatedAt: now };
46
44
  job.updatedAt = now;
@@ -145,12 +143,6 @@ export class ReconcilerReviewBase extends ReconcilerRuntimeBase {
145
143
  await this.emit(eventFor(job.id, task.id, task.parentTaskId, 'model.selected', 'queued', `Selected ${task.routing.model} · thinking ${task.routing.reasoning ?? 'unknown'}`, 'user', { routing: task.routing }, { agent: 'relay', component: 'agents-relay' }));
146
144
  }
147
145
  }
148
- isSupersededTask(task) {
149
- return task.state === 'CANCELLED' && typeof task.error === 'string' && task.error.startsWith('Superseded ');
150
- }
151
- preReviewGatesPass(job) {
152
- return job.tasks.filter(task => task.agentName !== 'reviewer' && !this.isSupersededTask(task)).every(task => task.state === 'SUCCEEDED');
153
- }
154
146
  async applyReviewEvent(event) {
155
147
  const job = await this.store.load(event.job_id);
156
148
  const task = job.tasks.find(item => item.id === event.task_id && item.agentName === 'reviewer');
@@ -56,7 +56,7 @@ For a direct non-sandbox local worker, use NEO_EVENTS_EMIT when the caller provi
56
56
  As your FIRST execution action, before reading files, editing code, running commands, or doing other work,
57
57
  you MUST publish exactly one canonical task.started event for this exact Job/Task.
58
58
 
59
- You MAY publish progress events while working.
59
+ ${task.adapter === 'chatgpt' ? `Because ChatGPT execution continues asynchronously after the submit process exits, you MUST keep reporting liveness while the task is active. Publish meaningful progress/milestone events when there is real progress to report. If no meaningful milestone has been published for 30 seconds during active work, publish a non-terminal task.heartbeat event with status running, visibility orchestrator, this exact source.id, and a concise description of the current operation. Continue heartbeats after each further 30 seconds of active silence until a terminal task event is published. Do not invent percentage progress.` : `You MAY publish progress events while working. For long silent active work, follow the events-bus heartbeat policy.`}
60
60
  Before returning or allowing the worker process to exit, you MUST publish exactly one canonical terminal event:
61
61
  - task.completed only after the declared output is durable;
62
62
  - task.failed for implementation, tool, command, test, patch, commit, push, runtime, or other execution failure that prevents completion;
@@ -2,7 +2,7 @@ function isSuperseded(task) {
2
2
  return task.state === 'CANCELLED' && typeof task.error === 'string' && task.error.startsWith('Superseded ');
3
3
  }
4
4
  export function implementationTasks(job) {
5
- return job.tasks.filter(task => task.agentName !== 'reviewer' && !isSuperseded(task));
5
+ return job.tasks.filter(task => task.agentName !== 'reviewer' && task.agentName !== 'troubleshooter' && !isSuperseded(task));
6
6
  }
7
7
  export function implementationComplete(job) {
8
8
  const tasks = implementationTasks(job);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.123",
3
+ "version": "1.0.125",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {
@@ -107,7 +107,7 @@ Blocker resolution is generation-safe and idempotent: Relay retries a dependent
107
107
 
108
108
  ## Worker lifecycle
109
109
 
110
- Relay owns process lifecycle. Workers own semantic task lifecycle. A worker must publish canonical `task.started` as its first execution action, then exactly one of `task.completed`, `task.failed`, `task.blocked`, or `task.cancelled`. Process launch/exit events do not substitute for worker semantic events.
110
+ Relay owns process lifecycle. Workers own semantic task lifecycle. A worker must publish canonical `task.started` as its first execution action, then exactly one of `task.completed`, `task.failed`, `task.blocked`, or `task.cancelled`. Process launch/exit events do not substitute for worker semantic events. Because the ChatGPT worker continues asynchronously after its submit process exits, its managed execution prompt must also require the shared events-bus liveness rule: publish meaningful progress when available, and publish non-terminal `task.heartbeat` after each 30 seconds of otherwise silent active work until a terminal task event is emitted.
111
111
 
112
112
  Relay allocates the durable `executionId` before launching model-backed work and gives that worker one exact event source identity: `job/<job-id>/task/<task-id>/execution/<execution-id>`. Every semantic lifecycle event from that execution must copy the identity verbatim in `source.id`. Relay correlates model-worker lifecycle only by this execution identity; `source.agent`, `source.component`, provider/adapter names, and `data.worker_owned` are diagnostic/legacy metadata and MUST NOT authorize or correlate lifecycle state. A retry receives a new execution identity, so events from an older execution are ignored.
113
113