agents-relay 1.0.146 → 1.0.148

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.
@@ -57,10 +57,6 @@ export const dashboardHtml = `<!doctype html>
57
57
  .job-primary { display:flex; align-items:center; justify-content:space-between; gap:.45rem; min-width:0; }
58
58
  .job-status-line { display:flex; align-items:center; gap:.3rem; min-width:0; white-space:nowrap; }
59
59
  .job-status-line .badge { flex:0 0 auto; }
60
- .job-actions { display:flex; align-items:center; gap:.2rem; }
61
- .job-action { width:1.8rem; height:1.8rem; display:inline-grid; place-items:center; padding:0; border:1px solid var(--border); border-radius:.35rem; background:var(--surface); color:var(--muted); font:inherit; font-size:.95rem; line-height:1; cursor:pointer; }
62
- .job-action:hover:not(:disabled) { color:var(--accent); border-color:var(--accent); }
63
- .job-action:disabled { cursor:default; opacity:.4; }
64
60
  .status-pill,.badge { display:inline-flex; align-items:center; border-radius:999px; padding:.06rem .42rem; min-height:1.35rem; font-size:.68rem; font-weight:800; }
65
61
  .status-pill.ok,.badge.ACTIVE,.badge.RUNNING,.badge.SUCCEEDED,.badge.COMPLETED,.badge.MERGED { background:#e5f3e9; color:var(--ok); }
66
62
  .status-pill.warn,.badge.BLOCKED,.badge.DRAFT,.badge.QUEUED,.badge.WAITING,.badge.OPEN { background:#fff1d2; color:var(--warn); }
@@ -335,12 +331,8 @@ function syncFilters(){
335
331
  quickDraft.setAttribute('aria-pressed',String(quickMode==='draft'));
336
332
  jobSearch.value=searchQuery;
337
333
  }
338
- function actionEnabled(job,action){if(action==='start')return job.state==='DRAFT'&&job.githubState!=='CLOSED'&&job.githubState!=='MERGED';if(action==='reconcile')return job.state==='ACTIVE'&&job.githubState==='OPEN';if(action==='cancel')return job.state!=='COMPLETED'&&job.state!=='CANCELLED'&&job.githubState==='OPEN';return false;}
339
- function actionButton(job,action,label,icon){const button=document.createElement('button');button.type='button';button.className='job-action';button.textContent=icon;button.title=label;button.setAttribute('aria-label',label);button.disabled=!actionEnabled(job,action);button.addEventListener('click',event=>{event.stopPropagation();void runJobAction(job,action,button);});return button;}
340
- async function runJobAction(job,action,button){button.disabled=true;try{const response=await fetch('/api/job-action?id='+encodeURIComponent(job.id)+'&action='+encodeURIComponent(action),{method:'POST'});if(!response.ok)throw new Error(response.status+' '+response.statusText);await refresh(true);}catch(error){button.title=labelForAction(action)+': '+error.message;}finally{renderAll();}}
341
- function labelForAction(action){return action==='start'?'Start':action==='reconcile'?'Reconcile':'Cancel';}
342
334
  function repoName(repository){if(typeof repository!=='string'||!repository)return 'unknown-repo';const parts=repository.split('/').filter(Boolean);return parts.at(-1)||repository;}
343
- 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 actions=element('span','job-actions');actions.append(actionButton(job,'start','Start','▶'),actionButton(job,'reconcile','Reconcile','↻'),actionButton(job,'cancel','Cancel','×'));const primary=element('div','job-primary');primary.append(states,actions);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));}}
335
+ 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));}}
344
336
  function taskNode(task,children,depth){const li=element('li');const row=document.createElement('button');row.type='button';row.className='task-row';row.setAttribute('aria-current',String(task.id===selectedTaskId));row.title=task.id;row.addEventListener('click',()=>{const deselect=selectedTaskId===task.id;selectedTaskId=deselect?null:task.id;eventTaskId=deselect?null:task.id;rememberSelection();clearEvents();renderAll();void loadEventHistory();});const id=element('span','task-id',text(task.id));id.style.paddingLeft=Math.min(depth,4)*6+'px';row.append(id,element('span','badge '+value(task.state),text(task.state)));li.append(row);const nested=children.get(task.id)||[];if(nested.length){const ul=element('ul','tree');for(const child of nested)ul.append(taskNode(child,children,depth+1));li.append(ul);}return li;}
345
337
  function buildTree(tasks){taskTree.replaceChildren();if(!Array.isArray(tasks)||!tasks.length){taskTree.append(element('li','empty',text('No tasks in durable state.')));return;}const known=new Set(tasks.map(task=>task.id));const children=new Map();const roots=[];for(const task of tasks){if(!task.parentTaskId||!known.has(task.parentTaskId))roots.push(task);else{const siblings=children.get(task.parentTaskId)||[];siblings.push(task);children.set(task.parentTaskId,siblings);}}for(const task of roots)taskTree.append(taskNode(task,children,0));}
346
338
  function attemptBudget(task){const attempt=Number(task?.attempt)||0;const max=Number(task?.maxAttempts)||0;if(max>=3&&max%3===0){const effectiveMax=Math.max(max,Math.ceil(attempt/3)*3);return attempt+' / 3×'+(effectiveMax/3);}return attempt+' / '+max;}
@@ -1,5 +1,5 @@
1
1
  import { storySizeTimeoutMs, taskOverallTimeoutMs } from './types.js';
2
- import { schedule, transitionTaskEvent, transitionJobEvent, tryTransitionTaskEvent } from './scheduler.js';
2
+ import { completionGateSatisfied, schedule, transitionTaskEvent, transitionJobEvent, tryTransitionTaskEvent } from './scheduler.js';
3
3
  import { implementationComplete, implementationTasks } from './runtime-status.js';
4
4
  import { eventFor } from './events.js';
5
5
  import { routeWorker, workerCandidates } from './worker-routing.js';
@@ -25,6 +25,8 @@ export class ReconcilerReviewBase extends ReconcilerRuntimeBase {
25
25
  async ensureFinalReview(job, prState, headSha) {
26
26
  if (job.schedule || job.reviewEnabled === false || prState !== 'OPEN' || job.state !== 'ACTIVE' || !implementationComplete(job) || !headSha)
27
27
  return;
28
+ if (job.executionMode === 'autonomous' && !completionGateSatisfied(job))
29
+ return;
28
30
  const review = job.review;
29
31
  if (review?.headSha === headSha && ['started', 'approved', 'changes_required', 'blocked'].includes(review.outcome))
30
32
  return;
@@ -186,12 +188,11 @@ export class ReconcilerReviewBase extends ReconcilerRuntimeBase {
186
188
  const input = `Address all reviewer findings for PR #${job.prNumber} at ${headSha}:\n\n${issues.map((issue, index) => `${index + 1}. ${issue}`).join('\n')}`;
187
189
  const fixTask = {
188
190
  jobId: job.id, id, kind: 'work', priority: job.priority, storySize: 'M', projectName: job.title,
189
- agentName: 'review-fix', parentTaskId: task.id, dependencies: [task.id], capabilities: ['implementation'],
190
- adapter: 'codex', input, output: { kind: 'task_pr' },
191
- routing: { provider: 'openai', model: 'gpt-5.6-luna', reasoning: 'medium', decidedBy: 'review-fix', decidedAt: now },
191
+ agentName: 'implementation', parentTaskId: task.id, dependencies: [task.id], capabilities: ['implementation'],
192
+ workerRequirements: { capabilities: [] }, adapter: 'codex', input, output: { kind: 'task_pr' },
192
193
  continuation: null, continuationDeliveredAt: null, state: 'DRAFT', attempt: 0, maxAttempts: 3,
193
194
  leaseOwner: null, leaseExpiresAt: null, executionId: null, threadId: null, result: null, plannerResult: null,
194
- error: null, timeoutMs: storySizeTimeoutMs('M'), createdAt: now, updatedAt: now
195
+ error: null, timeoutMs: 0, createdAt: now, updatedAt: now
195
196
  };
196
197
  await this.store.appendTask(fixTask);
197
198
  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' }));
@@ -281,8 +281,6 @@ export class Reconciler extends ReconcilerReviewBase {
281
281
  const latest = planners.at(-1);
282
282
  if (latest?.plannerResult?.objective_status === 'satisfied')
283
283
  return job;
284
- if (latest && !job.schedule && job.review && job.review.outcome !== 'approved')
285
- return job;
286
284
  const task = createPlannerRoundTask(job, { timeoutMs: this.options.plannerTimeoutMs ?? storySizeTimeoutMs('M') });
287
285
  await this.store.appendTask(task);
288
286
  await this.emit(eventFor(job.id, task.id, task.parentTaskId, 'planner.created', 'queued', `Planner task ${task.id} created`, 'orchestrator'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.146",
3
+ "version": "1.0.148",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {