agents-relay 2.0.115 → 2.0.116

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.
@@ -176,6 +176,17 @@ export class ReconcilerReviewBase extends ReconcilerRuntimeBase {
176
176
  async ensureFinalReview(job, headSha) {
177
177
  if (headSha && !(await this.projectedReviewHeadReady(job, headSha)))
178
178
  return;
179
+ // Restore the review gate only after an authoritative unblock of the original reviewer.
180
+ const priorReviewTask = job.tasks.find(task => task.id === job.review?.taskId);
181
+ if (job.review?.outcome === 'blocked' && job.review.headSha === headSha
182
+ && priorReviewTask?.agentName === 'reviewer' && priorReviewTask.unblock?.event === 'task.unblocked'
183
+ && ['QUEUED', 'RUNNING', 'FAILED'].includes(priorReviewTask.state)) {
184
+ const recovered = await dispatchReviewLifecycle('blocked', 'review.retry', { reviewUnblocked: true });
185
+ if (recovered.accepted && recovered.target === 'PENDING') {
186
+ job.review = { ...job.review, outcome: 'pending', updatedAt: new Date().toISOString() };
187
+ await this.store.saveJob(job);
188
+ }
189
+ }
179
190
  let reviewTaskId = null;
180
191
  const decision = await dispatchReviewLifecycle(job.review?.outcome, 'reconcile', this.reviewFacts(job, headSha), async (actionId) => {
181
192
  if (actionId !== 'review.create')
@@ -30,6 +30,7 @@ export const WORKFLOW_EVENT_NAMES = [
30
30
  'review.approved',
31
31
  'review.changes_required',
32
32
  'review.blocked',
33
+ 'review.retry',
33
34
  'task.blocked',
34
35
  'task.cancelled',
35
36
  'task.completed',
@@ -49,6 +50,7 @@ export const WORKFLOW_FACT_NAMES = [
49
50
  'autoMerge',
50
51
  'recoverableExecution',
51
52
  'retryAvailable',
53
+ 'reviewUnblocked',
52
54
  'initialPlan',
53
55
  'scheduledRun',
54
56
  'hasUnblock',
@@ -17,9 +17,10 @@ export const reviewLifecycleV2 = new GraphRuntime(source, {
17
17
  currentReviewCancelled: boolGuard('currentReviewCancelled'),
18
18
  noApprovedCurrentReview: boolGuard('noApprovedCurrentReview'),
19
19
  autoMerge: boolGuard('autoMerge'),
20
+ reviewUnblocked: boolGuard('reviewUnblocked'),
20
21
  },
21
22
  actions: Object.fromEntries(['review.create', 'review.cancelStale', 'review.createFix', 'review.requestMerge'].map(id => [id, { id }])),
22
- events: Object.fromEntries(['reconcile', 'review.started', 'review.approved', 'review.changes_required', 'review.blocked', 'head.changed'].map(id => [id, { id }])),
23
+ events: Object.fromEntries(['reconcile', 'review.started', 'review.approved', 'review.changes_required', 'review.blocked', 'review.retry', 'head.changed'].map(id => [id, { id }])),
23
24
  });
24
25
  export function reviewGraphState(outcome) {
25
26
  if (!outcome)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "2.0.115",
3
+ "version": "2.0.116",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,3 +32,5 @@ See [task-recovery-flows.md](task-recovery-flows.md) for unified architecture, o
32
32
  Reviewer unavailable evidence is an execution failure (`task.failed`), not a blocked Task. Legacy Reviewer verdict `blocked` is normalized to failure and must dispatch Troubleshooter. Only Relay blocks a Troubleshooter during human escalation.
33
33
 
34
34
  A host-mediated interactive invocation is a separate Relay-controlled wait. It requires an already-bound `interactiveInvocation` with status `awaiting_decision`. It is not permission for a Worker or Reviewer to choose a blocked terminal outcome.
35
+
36
+ A legacy Job-level `review: blocked` gate is reset to `pending` only after authoritative `task.unblocked` metadata exists on its bound Reviewer Task and the immutable review head still matches. The same Reviewer Task then resumes without creating a duplicate.
@@ -37,6 +37,7 @@ cancelStarted["action: review.cancelStale"]
37
37
  cancelApproved["action: review.cancelStale"]
38
38
  cancelChanges["action: review.cancelStale"]
39
39
  cancelBlocked["action: review.cancelStale"]
40
+ reviewUnblocked["guard: reviewUnblocked"]
40
41
  fixPending["action: review.createFix"]
41
42
  fixStarted["action: review.createFix"]
42
43
  fixChanges["action: review.createFix"]
@@ -117,5 +118,8 @@ approved -->|on: head.changed| cancelApproved
117
118
  cancelApproved --> stale
118
119
  changes -->|on: head.changed| cancelChanges
119
120
  cancelChanges --> stale
121
+ blocked -->|on: review.retry| reviewUnblocked
122
+ reviewUnblocked -->|true| pending
123
+ reviewUnblocked -->|false| blocked
120
124
  blocked -->|on: head.changed| cancelBlocked
121
125
  cancelBlocked --> stale