agents-relay 1.0.166 → 1.0.168
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/dashboard-html.js +3 -1
- package/dist/relayd.js +10 -6
- package/package.json +1 -1
package/dist/dashboard-html.js
CHANGED
|
@@ -271,6 +271,7 @@ let selectedRuntimeStatus=sessionStorage.getItem('agents-relay:runtime-status-fi
|
|
|
271
271
|
let searchQuery=sessionStorage.getItem('agents-relay:job-search')||'';
|
|
272
272
|
let eventTaskId=selectedTaskId;
|
|
273
273
|
const collapsedTasks=new Set();
|
|
274
|
+
const expandedTasks=new Set();
|
|
274
275
|
const displayedEventIds=new Set();
|
|
275
276
|
|
|
276
277
|
function value(input){return input===null||input===undefined||input===''?'—':String(input);}
|
|
@@ -321,7 +322,8 @@ function syncFilters(){
|
|
|
321
322
|
function repoName(repository){if(typeof repository!=='string'||!repository)return 'unknown-repo';const parts=repository.split('/').filter(Boolean);return parts.at(-1)||repository;}
|
|
322
323
|
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));}}
|
|
323
324
|
function taskTagDisplay(job,task){const display=task.tag&&job?.tag?.values?.[task.tag];return typeof display==='string'&&display?display:null;}
|
|
324
|
-
function
|
|
325
|
+
function taskSubtreeComplete(task,children){return ['SUCCEEDED','CANCELLED'].includes(task.state)&&(children.get(task.id)||[]).every(child=>taskSubtreeComplete(child,children));}
|
|
326
|
+
function taskNode(task,children,depth){const li=element('li');const nested=children.get(task.id)||[];const line=element('div','task-line');if(nested.length){const key=(selectedJob||'')+':'+task.id;const collapsed=collapsedTasks.has(key)||(!expandedTasks.has(key)&&taskSubtreeComplete(task,children));const fold=document.createElement('button');fold.type='button';fold.className='task-fold';fold.setAttribute('aria-expanded',String(!collapsed));fold.setAttribute('aria-label',(collapsed?'Expand ':'Collapse ')+task.id);fold.textContent=collapsed?'▸':'▾';line.append(fold);const ul=element('ul','tree');ul.hidden=collapsed;for(const child of nested)ul.append(taskNode(child,children,depth+1));fold.addEventListener('click',()=>{const next=!ul.hidden;ul.hidden=next;fold.setAttribute('aria-expanded',String(!next));fold.setAttribute('aria-label',(next?'Expand ':'Collapse ')+task.id);fold.textContent=next?'▸':'▾';if(next){collapsedTasks.add(key);expandedTasks.delete(key);}else{collapsedTasks.delete(key);expandedTasks.add(key);}});li.append(line,ul);}else li.append(line);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)));const tag=taskTagDisplay(selected(),task);if(tag){const badge=element('span','task-tag',text(tag));badge.title=(selected()?.tag?.description||'Task tag')+': '+task.tag;row.append(badge);}line.append(row);return li;}
|
|
325
327
|
function buildTree(tasks){taskTree.replaceChildren();if(!Array.isArray(tasks)||!tasks.length){taskTree.append(element('li','empty',text('No tasks in durable state.')));return;}const work=tasks.filter(task=>task.kind!=='planner');if(!work.length){taskTree.append(element('li','empty',text('No work tasks in durable state.')));return;}const known=new Set(work.map(task=>task.id));const children=new Map();const roots=[];for(const task of work){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));}
|
|
326
328
|
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;}
|
|
327
329
|
const JOB_STATES=['DRAFT','ACTIVE','COMPLETED','CANCELLED'];
|
package/dist/relayd.js
CHANGED
|
@@ -19,9 +19,13 @@ import { deriveJobRuntimeStatus } from './runtime-status.js';
|
|
|
19
19
|
import { decisionMode, LayaCliDecisionEngine } from './decision.js';
|
|
20
20
|
import { JsonlDecisionAuditStore } from './decision-audit.js';
|
|
21
21
|
import { summarizeRuntime } from './runtime-summary.js';
|
|
22
|
-
import {
|
|
22
|
+
import { runScheduledBucket } from './scheduled-jobs.js';
|
|
23
|
+
import { scheduleBucket } from './scheduler.js';
|
|
23
24
|
const PR_REFRESH_COALESCE_MS = 1000;
|
|
24
25
|
export const SCHEDULE_HEARTBEAT_MS = 600_000;
|
|
26
|
+
export function dueScheduledOverviews(overviews, now = new Date()) {
|
|
27
|
+
return overviews.filter(item => item.githubState === 'OPEN' && item.job.executionMode === 'autonomous' && item.job.schedule && Date.parse(item.job.schedule.nextRunAt) <= now.getTime());
|
|
28
|
+
}
|
|
25
29
|
export function shouldCoalescePullRequestRefresh(completedAt, now = Date.now(), force = false) {
|
|
26
30
|
return !force && completedAt !== null && now >= completedAt && now - completedAt < PR_REFRESH_COALESCE_MS;
|
|
27
31
|
}
|
|
@@ -286,12 +290,13 @@ export async function runDaemon(argv) {
|
|
|
286
290
|
if (client.rateLimitStatus().limited)
|
|
287
291
|
return;
|
|
288
292
|
const now = new Date();
|
|
289
|
-
for (const
|
|
293
|
+
for (const item of dueScheduledOverviews(cachedOverviews, now)) {
|
|
294
|
+
const repository = item.job.repository;
|
|
290
295
|
try {
|
|
291
|
-
await
|
|
296
|
+
await runScheduledBucket(client, repository, trusted, { prNumber: item.job.prNumber, jobId: item.job.id, bucket: scheduleBucket(new Date(item.job.schedule.nextRunAt)) }, { reconcile: async () => { await pool.reconcileTarget(repository, item.job.prNumber, item.job.id); }, idle: async () => { } }, now);
|
|
292
297
|
}
|
|
293
298
|
catch (error) {
|
|
294
|
-
process.stderr.write(`warning: scheduled heartbeat unavailable for ${repository}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
299
|
+
process.stderr.write(`warning: scheduled heartbeat unavailable for ${repository}#${item.job.prNumber}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
295
300
|
}
|
|
296
301
|
}
|
|
297
302
|
};
|
|
@@ -304,9 +309,8 @@ export async function runDaemon(argv) {
|
|
|
304
309
|
process.once('SIGINT', stop);
|
|
305
310
|
process.once('SIGTERM', stop);
|
|
306
311
|
console.log('Dashboard listening; repository worker pool active');
|
|
307
|
-
void scheduleHeartbeatSafely();
|
|
308
312
|
void refreshAll(true)
|
|
309
|
-
.then(items => pool.reconcileKnown(items))
|
|
313
|
+
.then(async (items) => { await scheduleHeartbeatSafely(); await pool.reconcileKnown(items); })
|
|
310
314
|
.catch(error => process.stderr.write(`warning: startup reconciliation unavailable: ${error instanceof Error ? error.message : String(error)}\n`));
|
|
311
315
|
}
|
|
312
316
|
if (isEntrypoint(import.meta.url))
|