agents-relay 2.0.178 → 2.0.180
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-data.js +21 -28
- package/dist/dashboard-html.js +22 -35
- package/dist/dashboard-style.js +26 -10
- package/dist/dashboard.js +1 -2
- package/dist/relayd.js +2 -2
- package/package.json +1 -1
package/dist/dashboard-data.js
CHANGED
|
@@ -4,18 +4,19 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { eventsBusHistory } from './events-bus-runtime.js';
|
|
5
5
|
const repositoryPattern = /^([A-Za-z0-9](?:[A-Za-z0-9-]{0,36}[A-Za-z0-9])?)\/([A-Za-z0-9._-]{1,100})$/;
|
|
6
6
|
const workflowDirectory = fileURLToPath(new URL('../workflow/', import.meta.url));
|
|
7
|
-
export const
|
|
7
|
+
export const DASHBOARD_JOB_RESULT_LIMIT = 20;
|
|
8
8
|
export const DEFAULT_DASHBOARD_JOB_STATUSES = ['DRAFT', 'ACTIVE', 'COMPLETED'];
|
|
9
9
|
function compareDashboardJobs(a, b) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
const timestamp = (job) => {
|
|
11
|
+
const updated = Date.parse(job.updatedAt ?? '');
|
|
12
|
+
if (Number.isFinite(updated))
|
|
13
|
+
return updated;
|
|
14
|
+
const created = Date.parse(job.createdAt ?? '');
|
|
15
|
+
return Number.isFinite(created) ? created : 0;
|
|
16
|
+
};
|
|
17
|
+
return timestamp(b) - timestamp(a)
|
|
18
|
+
|| a.repository.localeCompare(b.repository)
|
|
19
|
+
|| a.id.localeCompare(b.id);
|
|
19
20
|
}
|
|
20
21
|
export async function dashboardWorkflowGraphs() {
|
|
21
22
|
const names = (await readdir(workflowDirectory)).filter(name => name.endsWith('.mmd')).sort();
|
|
@@ -57,18 +58,16 @@ export async function eventHistory(jobId, taskId, limit) {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
export function searchDashboardJobs(jobs, query) {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const
|
|
61
|
+
const requestedStatus = (query.get('status') ?? query.get('statuses') ?? '').trim().toUpperCase();
|
|
62
|
+
const status = requestedStatus && ['DRAFT', 'ACTIVE', 'COMPLETED', 'CANCELLED'].includes(requestedStatus) ? requestedStatus : '';
|
|
63
|
+
const repository = (query.get('repo') ?? query.get('repositories') ?? query.get('repos') ?? '').trim();
|
|
63
64
|
const search = query.get('q')?.trim().toLowerCase() ?? '';
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const pageSize = Number.isInteger(parsedPageSize) && parsedPageSize > 0 ? Math.min(parsedPageSize, DASHBOARD_JOB_PAGE_SIZE) : DASHBOARD_JOB_PAGE_SIZE;
|
|
68
|
-
const matchesStatus = (job) => statuses.some(status => status === 'DRAFT'
|
|
65
|
+
const matchesStatus = (job) => status ? status === 'DRAFT'
|
|
66
|
+
? Boolean(job.draft) || job.state === 'DRAFT'
|
|
67
|
+
: job.state === status : DEFAULT_DASHBOARD_JOB_STATUSES.some(defaultStatus => defaultStatus === 'DRAFT'
|
|
69
68
|
? Boolean(job.draft) || job.state === 'DRAFT'
|
|
70
|
-
: job.state ===
|
|
71
|
-
const matchesRepository = (job) =>
|
|
69
|
+
: job.state === defaultStatus);
|
|
70
|
+
const matchesRepository = (job) => !repository || repository === job.repository;
|
|
72
71
|
const normalizedPr = search.replace(/^pr\s*#?/, '').replace(/^#/, '');
|
|
73
72
|
const matchesSearch = (job) => {
|
|
74
73
|
if (!search)
|
|
@@ -78,15 +77,9 @@ export function searchDashboardJobs(jobs, query) {
|
|
|
78
77
|
|| Boolean(normalizedPr) && String(job.prNumber || '').includes(normalizedPr);
|
|
79
78
|
};
|
|
80
79
|
const matching = jobs.filter(job => matchesStatus(job) && matchesRepository(job) && matchesSearch(job)).sort(compareDashboardJobs);
|
|
81
|
-
const total = matching.length;
|
|
82
|
-
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
83
|
-
const safePage = Math.min(page, totalPages);
|
|
84
80
|
return {
|
|
85
|
-
jobs: matching.slice(
|
|
81
|
+
jobs: matching.slice(0, DASHBOARD_JOB_RESULT_LIMIT),
|
|
86
82
|
repositories: [...new Set(jobs.map(job => job.repository).filter(Boolean))].sort(),
|
|
87
|
-
total,
|
|
88
|
-
page: safePage,
|
|
89
|
-
pageSize,
|
|
90
|
-
totalPages,
|
|
83
|
+
total: matching.length,
|
|
91
84
|
};
|
|
92
85
|
}
|
package/dist/dashboard-html.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dashboardStyles } from './dashboard-style.js';
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_DASHBOARD_JOB_STATUSES } from './dashboard-data.js';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -42,7 +42,7 @@ ${dashboardStyles}
|
|
|
42
42
|
</header>
|
|
43
43
|
<main>
|
|
44
44
|
<section id="jobs-view" role="tabpanel" aria-labelledby="jobs-tab" class="panel jobs-panel">
|
|
45
|
-
<div class="panel-head"><h2>Jobs</h2><div class="panel-actions"><div class="job-filters"><input id="job-search" class="job-search" type="search" placeholder="Search title or PR #" aria-label="Search jobs by title or PR number"><select id="status-filter" class="job-filter"
|
|
45
|
+
<div class="panel-head"><h2>Jobs</h2><div class="panel-actions"><div class="job-filters"><input id="job-search" class="job-search" type="search" placeholder="Search title or PR #" aria-label="Search jobs by title or PR number"><select id="status-filter" class="job-filter" aria-label="Filter jobs by status"><option value="">All (no CANCELLED)</option><option value="DRAFT">DRAFT</option><option value="ACTIVE">ACTIVE</option><option value="COMPLETED">COMPLETED</option><option value="CANCELLED">CANCELLED</option></select><select id="repo-filter" class="job-filter" aria-label="Filter jobs by repository"><option value="">All repositories</option></select></div><div class="jobs-actions"><button id="jobs-reset" class="refresh-button" type="button">Reset</button><button id="jobs-refresh" class="refresh-button" type="button">Refresh</button></div><span id="jobs-feedback" class="jobs-feedback subtle" role="status" aria-live="polite"></span></div></div>
|
|
46
46
|
<div class="panel-body" aria-live="polite"><ul id="job-list" class="job-list"><li class="skeleton" aria-hidden="true"></li></ul></div>
|
|
47
47
|
</section>
|
|
48
48
|
<div class="workspace" id="jobs-workspace">
|
|
@@ -144,11 +144,9 @@ const jobList=document.querySelector('#job-list');
|
|
|
144
144
|
const jobsRefresh=document.querySelector('#jobs-refresh');
|
|
145
145
|
const jobsReset=document.querySelector('#jobs-reset');
|
|
146
146
|
const jobSearch=document.querySelector('#job-search');
|
|
147
|
-
const jobsPrev=document.querySelector('#jobs-prev');
|
|
148
|
-
const jobsNext=document.querySelector('#jobs-next');
|
|
149
|
-
const jobsPageLabel=document.querySelector('#jobs-page');
|
|
150
147
|
const repoFilter=document.querySelector('#repo-filter');
|
|
151
148
|
const statusFilter=document.querySelector('#status-filter');
|
|
149
|
+
const jobsFeedback=document.querySelector('#jobs-feedback');
|
|
152
150
|
const selectedJobRefresh=document.querySelector('#selected-job-refresh');
|
|
153
151
|
const mobileJobsBack=document.querySelector('#mobile-jobs-back');
|
|
154
152
|
const mobileJobs=window.matchMedia('(max-width:650px)');
|
|
@@ -180,11 +178,8 @@ const runtimeSummary=document.querySelector('#runtime-summary');
|
|
|
180
178
|
let jobs=[];
|
|
181
179
|
let selectedJob=sessionStorage.getItem('agents-relay:selected-job');
|
|
182
180
|
let selectedTaskId=sessionStorage.getItem('agents-relay:selected-task');
|
|
183
|
-
let
|
|
184
|
-
let
|
|
185
|
-
let jobsPage=1;
|
|
186
|
-
let jobsTotal=0;
|
|
187
|
-
let jobsTotalPages=1;
|
|
181
|
+
let selectedRepo=storedFilterValue('agents-relay:repo-filter');
|
|
182
|
+
let selectedStatus=storedFilterValue('agents-relay:status-filter');
|
|
188
183
|
let jobRepositories=[];
|
|
189
184
|
let searchQuery=sessionStorage.getItem('agents-relay:job-search')||'';
|
|
190
185
|
let workflowGraphs=[];
|
|
@@ -195,7 +190,7 @@ const collapsedTasks=new Set();
|
|
|
195
190
|
const expandedTasks=new Set();
|
|
196
191
|
const displayedEventIds=new Set();
|
|
197
192
|
function value(input){return input===null||input===undefined||input===''?'—':String(input);}
|
|
198
|
-
function
|
|
193
|
+
function storedFilterValue(key){const stored=sessionStorage.getItem(key);if(!stored)return '';try{const parsed=JSON.parse(stored);if(Array.isArray(parsed))return parsed.length===1&&typeof parsed[0]==='string'?parsed[0]:'';if(typeof parsed==='string')return parsed;}catch{return stored;}return '';}
|
|
199
194
|
function text(input){return document.createTextNode(value(input));}
|
|
200
195
|
function element(name,className,child){const node=document.createElement(name);if(className)node.className=className;if(child)node.append(child);return node;}
|
|
201
196
|
function yamlScalar(input){if(input===null)return 'null';if(typeof input==='boolean'||typeof input==='number')return String(input);return JSON.stringify(String(input));}
|
|
@@ -222,22 +217,18 @@ function clearEvents(){displayedEventIds.clear();eventFeed.replaceChildren(eleme
|
|
|
222
217
|
async function loadEventHistory(){const job=selected();if(!job){clearEvents();return;}const params=new URLSearchParams({job:job.id,limit:String(MAX_EVENTS)});if(eventTaskId)params.set('task',eventTaskId);try{const history=await loadJSON('/api/events?'+params.toString());clearEvents();for(const event of Array.isArray(history)?history.slice().reverse():[])addEvent(event);}catch{clearEvents();}}
|
|
223
218
|
function copySelectedJobIdentity(job){if(!job||!navigator.clipboard?.writeText)return;void navigator.clipboard.writeText(repoName(job.repository)+'/'+value(job.id)).catch(()=>{});}
|
|
224
219
|
function selectJob(job){selectedJob=jobSelectionKey(job);selectedTaskId=null;eventTaskId=null;rememberSelection();clearEvents();location.hash=selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs';copySelectedJobIdentity(job);renderAll();void loadEventHistory();}
|
|
225
|
-
function matchesQuickFilter(job){
|
|
220
|
+
function matchesQuickFilter(job){return !selectedStatus?${JSON.stringify(DEFAULT_DASHBOARD_JOB_STATUSES)}.some(status=>status==='DRAFT'?job.draft||job.state==='DRAFT':job.state===status):selectedStatus==='DRAFT'?job.draft||job.state==='DRAFT':job.state===selectedStatus;}
|
|
226
221
|
function matchesSearch(job){const query=searchQuery.trim().toLowerCase();if(!query)return true;const pr=String(job.prNumber||'');const normalized=query.replace(/^pr\s*#?/,'').replace(/^#/,'');return String(job.title||job.id||'').toLowerCase().includes(query)||String(job.id||'').toLowerCase().includes(query)||shortManagedId(job.id,job.repository,'j').toLowerCase().includes(query)||pr.includes(normalized);}
|
|
227
|
-
function filteredJobs(){return jobs.filter(job=>(!
|
|
222
|
+
function filteredJobs(){return jobs.filter(job=>(!selectedRepo||selectedRepo===job.repository)&&matchesQuickFilter(job)&&matchesSearch(job));}
|
|
228
223
|
function syncFilters(){
|
|
229
224
|
const repos=jobRepositories.length?jobRepositories:[...new Set(jobs.map(job=>job.repository).filter(Boolean))].sort();
|
|
230
|
-
const
|
|
231
|
-
repoFilter.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
for(const option of statusFilter.options)option.selected=selectedStatuses.includes(option.value);
|
|
225
|
+
const repoOptions=[new Option('All repositories',''),...repos.map(repo=>new Option(repo,repo))];
|
|
226
|
+
if(repoFilter.options.length!==repoOptions.length||[...repoFilter.options].some((option,index)=>option.value!==repoOptions[index].value))repoFilter.replaceChildren(...repoOptions);
|
|
227
|
+
if(selectedRepo&&!repos.includes(selectedRepo))selectedRepo='';
|
|
228
|
+
if(!['','DRAFT','ACTIVE','COMPLETED','CANCELLED'].includes(selectedStatus))selectedStatus='';
|
|
229
|
+
statusFilter.value=selectedStatus;
|
|
230
|
+
repoFilter.value=selectedRepo;
|
|
237
231
|
jobSearch.value=searchQuery;
|
|
238
|
-
jobsPageLabel.textContent='Page '+jobsPage+' of '+jobsTotalPages+(jobsTotal?' · '+jobsTotal+' jobs':'');
|
|
239
|
-
jobsPrev.disabled=jobsPage<=1;
|
|
240
|
-
jobsNext.disabled=jobsPage>=jobsTotalPages;
|
|
241
232
|
}
|
|
242
233
|
function repoName(repository){if(typeof repository!=='string'||!repository)return 'unknown-repo';const parts=repository.split('/').filter(Boolean);return parts.at(-1)||repository;}
|
|
243
234
|
const CHAT_SHORTCUTS_KEY='agents-relay:chatgpt-shortcuts';
|
|
@@ -269,9 +260,7 @@ function manualOverrideField(task){const rows=Array.isArray(task.manualStateOver
|
|
|
269
260
|
function showDetailsDialog(title,sections){const dialog=element('dialog','details-dialog');dialog.setAttribute('aria-label',title);const head=element('div','details-dialog-head');const name=element('strong','',text(title));const close=element('button','refresh-button',text('Close'));close.type='button';close.addEventListener('click',()=>dialog.close());head.append(name,close);dialog.append(head);for(const [label,value] of sections){if(value===undefined||value===null||value==='')continue;dialog.append(element('h3','',text(label)));const content=element('pre');content.textContent=typeof value==='string'?value:JSON.stringify(value,null,2);dialog.append(content);}dialog.addEventListener('close',()=>dialog.remove());document.body.append(dialog);dialog.showModal();}function openJobDetails(){const job=selected();if(!job)return;showDetailsDialog('Job '+job.id+' · '+value(job.title),[['Objective',job.objective],['Description',job.description],['State',job.state],['Plans',job.plans?.map(p=>({id:p.id,status:p.status,assessment:p.assessment,error:p.error,generatedTaskIds:p.generatedTaskIds}))],['Review',job.review],['Outcome',job.result],['Publication',job.publication],['Failure',job.error]]);}function openTaskDetails(task){const result=task.result?.data??task.result;showDetailsDialog('Task '+task.id+' · '+value(task.title),[['Input / task objective',task.input],['State',task.state],['Conclusion',result],['Result summary',task.result?.summary],['Planner assessment',task.plannerResult?.assessment],['Resolution',task.resolution],['Resolution classification',task.resolutionClassification],['Failure',task.failureReason||task.error],['Dependencies',task.dependencies]]);}document.querySelector('#job-content-open').addEventListener('click',openJobDetails);function renderTaskDetail(task){taskDetailRoot.replaceChildren();if(!task){taskDetailRoot.append(element('p','empty',text('Select a task to inspect execution details.')));return;}const routing=task.routing||{};const groups=element('div','detail-groups');groups.append(field('Title',task.title||'—'),field('Task ID',task.id),field('Kind',task.kind||'work'),taskStateField(task),...(task.state==='FAILED'&&task.resolution?.state?[field('Resolution state',task.resolution.state),field('Resolution path',task.resolution.path||'—'),field('Resolution target',task.resolution.targetTaskId||'—')]:[]),manualOverrideField(task),field('Planning decision',task.planningDecisionId||'—'),field('Planning source',task.planningSource||'—'),field('Planning round',task.planningRound===undefined?'—':String(task.planningRound)),field('Project / agent',value(task.projectName)+' / '+value(task.agentName)),field('Parent task',task.parentTaskId||'root'),...(task.resolutionClassification?[field('Resolution classification',task.resolutionClassification.kind),field('Resolution target',task.resolutionClassification.targetTaskId),field('Resolution evidence',task.resolutionClassification.evidence),field('Resolution reason',task.resolutionClassification.reason)]:[]),field('Adapter',task.adapter),field('Provider / model',value(routing.provider)+' / '+value(routing.model)),field('Reasoning / profile',value(routing.reasoning||routing.profile)),field('Attempt',attemptBudget(task)),field('Dependencies',Array.isArray(task.dependencies)&&task.dependencies.length?task.dependencies.join(', '):'none'),taskCommentField(task),chatField(task));const job=selected();if(task.reviewHeadSha&&job)groups.append(commitField('HEAD commit',job,task.reviewHeadSha));const graph=agentGraphField(task);if(graph)groups.append(graph);if(task.plannerResult)groups.append(field('Objective status',task.plannerResult.objective_status),markdownField('Assessment',task.plannerResult.assessment));if(task.error)groups.append(field('Error',task.error));const result=task.result?.data??task.result;if(result){groups.append(field('Conclusion',result.verdict||result.outcome?.verdict||task.result?.summary||'Available'));}taskDetailRoot.append(groups);const details=element('button','refresh-button',text('Task details / conclusion'));details.type='button';details.addEventListener('click',()=>openTaskDetails(task));taskDetailRoot.append(details);}
|
|
270
261
|
function reviewStatus(job){const review=job?.review;if(!review)return 'Not started';if(review.outcome==='approved'&&review.headSha===job.headSha)return 'MERGE READY · approved';const stale=Array.isArray(job?.tasks)&&job.tasks.some(task=>task.agentName==='reviewer'&&task.reviewHeadSha&&job.headSha&&task.reviewHeadSha!==job.headSha);if(stale)return 'STALE · awaiting review';return String(review.outcome||'pending').replace('_',' ').toUpperCase();}function renderSelectedJob(){jobDetail.replaceChildren();const job=selected();if(!job){selectedJobTitle.textContent='';jobDetail.append(element('div','empty',text('No durable jobs discovered.')));buildTree([]);renderTaskDetail(null);return;}selectedJobTitle.textContent=value(job.title||job.id);if(jobSelectionKey(job)!==selectedJob){selectedJob=jobSelectionKey(job);selectedTaskId=null;eventTaskId=null;clearEvents();rememberSelection();}const summary=element('div','job-summary');summary.append(field('PR state',job.githubState||'UNKNOWN'),jobStateField(job),field('Runtime status',job.runtimeStatus||'IDLE'),field('Review',reviewStatus(job)),field('Creator thread',job.creatorThreadId||'—'),threadField('Implementation thread',job.implementationThreadId),threadField('Reviewer thread',job.reviewerThreadId),commitField('Review head SHA',job,job.review?.headSha||job.headSha||''),prField(job),field('Repository',job.repository||'—'),field('Last activity',relativeTime(job.updatedAt)),field('Task states',compactCounts(job.tasks)),chatShortcutField(job));jobDetail.append(summary);const selectedTask=(job.tasks||[]).find(task=>task.id===selectedTaskId)||null;if(selectedTaskId&&!selectedTask){selectedTaskId=null;eventTaskId=null;clearEvents();rememberSelection();}buildTree(job.tasks||[]);renderTaskDetail(selectedTask);renderEventScope();}function renderAll(){jobs.sort(compareJobs);syncFilters();renderJobList();renderSelectedJob();syncMobileJobsView();}
|
|
271
262
|
function isCompleteUnmerged(job){return job.runtimeStatus==='MERGE_PENDING';}
|
|
272
|
-
function
|
|
273
|
-
function jobGroup(job){if(isTerminalJob(job))return 3;if(job.draft||job.state==='DRAFT')return 2;return 0;}
|
|
274
|
-
function compareJobs(a,b){const groupDelta=jobGroup(a)-jobGroup(b);if(groupDelta)return groupDelta;const mergePendingDelta=Number(isCompleteUnmerged(b))-Number(isCompleteUnmerged(a));return mergePendingDelta||String(b.updatedAt).localeCompare(String(a.updatedAt));}
|
|
263
|
+
function compareJobs(a,b){const timestamp=job=>{const updated=Date.parse(job.updatedAt||'');if(Number.isFinite(updated))return updated;const created=Date.parse(job.createdAt||'');return Number.isFinite(created)?created:0;};return timestamp(b)-timestamp(a)||String(a.repository||'').localeCompare(String(b.repository||''))||String(a.id||'').localeCompare(String(b.id||''));}
|
|
275
264
|
async function loadJSON(url){const response=await fetch(url);if(!response.ok)throw new Error(response.status+' '+response.statusText);return response.json();}
|
|
276
265
|
async function refreshVersion(){try{const live=await loadJSON('/api/version');if(live?.version&&live.version!==DASHBOARD_VERSION)location.reload();}catch{}}
|
|
277
266
|
function renderRateLimitStatus(status){
|
|
@@ -284,10 +273,10 @@ function renderRateLimitStatus(status){
|
|
|
284
273
|
function runtimePill(label,value){const pill=element('span','runtime-stat');pill.append(document.createTextNode(label+' '),element('strong','',text(value)));return pill;}
|
|
285
274
|
async function drawRuntimeSummary(){try{const summary=await loadJSON('/api/runtime-summary');runtimeSummary.replaceChildren(runtimePill('Active Jobs',summary.activeJobs||0),runtimePill('Running Tasks',summary.runningTasks||0),runtimePill('ChatGPT',summary.workers?.chatgpt||0),runtimePill('Codex',summary.workers?.codex||0));for(const item of Array.isArray(summary.models)?summary.models:[])runtimeSummary.append(runtimePill(item.model,item.count));}catch{runtimeSummary.replaceChildren();}}
|
|
286
275
|
async function refreshStatus(){try{renderRateLimitStatus(await loadJSON('/api/status'));}catch{renderRateLimitStatus(null);}}
|
|
287
|
-
function rememberJobFilters(){sessionStorage.setItem('agents-relay:repo-filter',
|
|
288
|
-
function jobsQuery(force=false){const params=new URLSearchParams();if(searchQuery.trim())params.set('q',searchQuery.trim());
|
|
276
|
+
function rememberJobFilters(){sessionStorage.setItem('agents-relay:repo-filter',selectedRepo);sessionStorage.setItem('agents-relay:status-filter',selectedStatus);sessionStorage.setItem('agents-relay:job-search',searchQuery);}
|
|
277
|
+
function jobsQuery(force=false){const params=new URLSearchParams();if(searchQuery.trim())params.set('q',searchQuery.trim());if(selectedStatus)params.set('status',selectedStatus);if(selectedRepo)params.set('repo',selectedRepo);if(force)params.set('refresh','1');return params.toString();}
|
|
289
278
|
let jobsRequestSequence=0;
|
|
290
|
-
async function refresh(force=
|
|
279
|
+
async function refresh(force=true){const requestSequence=++jobsRequestSequence;jobsRefresh.disabled=true;jobsRefresh.textContent='Refreshing…';jobsFeedback.textContent='Loading…';try{const params=jobsQuery(force);const discovered=await loadJSON('/api/jobs'+(params?'?'+params:''));if(requestSequence!==jobsRequestSequence)return;const result=Array.isArray(discovered)?{jobs:discovered,repositories:[...new Set(discovered.map(job=>job.repository).filter(Boolean))].sort()}:discovered;jobs=dedupeJobs(Array.isArray(result.jobs)?result.jobs:[]);jobRepositories=result.repositories||[];void drawRuntimeSummary();jobs.sort(compareJobs);if(selectedJob&&!jobForSelection(selectedJob)){selectedJob=null;selectedTaskId=null;}if(!selectedJob){const match=location.hash.match(/^#\\/jobs\\/(.+)$/);const fromHash=match?decodeURIComponent(match[1]):null;const fromRoute=jobForSelection(fromHash);selectedJob=jobSelectionKey(fromRoute||jobs[0]);}rememberSelection();rememberJobFilters();renderAll();void loadEventHistory();jobsFeedback.textContent='';connection.textContent='';connection.setAttribute('aria-label','Live durable state connected');connection.className='status-pill ok';}catch(error){if(requestSequence!==jobsRequestSequence)return;jobsFeedback.textContent='Refresh failed: '+error.message;connection.textContent='';connection.setAttribute('aria-label','Durable state unavailable');connection.className='status-pill bad';jobList.replaceChildren(element('li','error-state',text('Could not load jobs: '+error.message)));}finally{if(requestSequence===jobsRequestSequence){jobsRefresh.disabled=false;jobsRefresh.textContent='Refresh';syncFilters();}}}
|
|
291
280
|
function renderEventScope(){const task=eventTaskId&&selected()?.tasks?.find(candidate=>candidate.id===eventTaskId);eventsAllTasks.disabled=!task;eventsAllTasks.setAttribute('aria-pressed',String(!task));eventsScope.textContent=task?'Task '+task.id+' · newest first · 60 max':'Job-wide · newest first · 60 max';}
|
|
292
281
|
function eventLabel(type){return ({'task.process.launched':'Process launched','task.started':'Worker started','task.process.exited':'Sync process exited','task.process.async_exited':'Async process exited','task.completed':'Worker completed','task.failed':'Worker failed','task.blocked':'Worker blocked','review.started':'Review started','review.approved':'Review approved','review.changes_required':'Review changes required','review.blocked':'Review blocked'})[type]||type||'Event';}
|
|
293
282
|
function eventSeverity(event,level){const type=String(event?.type||'').toLowerCase(),eventLevel=String(event?.level||level||'').toLowerCase(),outcome=String(event?.outcome||event?.status||'').toLowerCase();if(eventLevel==='error'||outcome==='failed'||type.endsWith('.failed')||type==='task.failed')return 'error';if(eventLevel==='warn'||outcome==='blocked'||type.endsWith('.blocked')||type==='task.blocked'||type==='review.blocked')return 'warn';return 'normal';}
|
|
@@ -345,13 +334,11 @@ async function saveQa(){qaSave.disabled=true;qaStatus.textContent='Saving…';tr
|
|
|
345
334
|
function showView(view){const isJobs=view==='jobs';const isUsage=view==='usage';const isGraphs=view==='graphs';const isRules=view==='rules';const isShortcuts=view==='shortcuts';const isQa=view==='qa';qaView.hidden=!isQa;qaTab.setAttribute('aria-selected',String(isQa));jobsView.hidden=!isJobs;jobsWorkspace.hidden=!isJobs;usageView.hidden=!isUsage;graphsView.hidden=!isGraphs;rulesView.hidden=!isRules;shortcutsView.hidden=!isShortcuts;jobsTab.setAttribute('aria-selected',String(isJobs));usageTab.setAttribute('aria-selected',String(isUsage));graphsTab.setAttribute('aria-selected',String(isGraphs));rulesTab.setAttribute('aria-selected',String(isRules));shortcutsTab.setAttribute('aria-selected',String(isShortcuts));if(isJobs)void refresh();else if(isUsage)void drawUsage();else if(isGraphs)void drawGraphs();else if(isRules)void drawRules();else if(isQa)void drawQa();else void drawShortcuts();}
|
|
346
335
|
jobsTab.addEventListener('click',()=>{location.hash=selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs';showView('jobs');});
|
|
347
336
|
jobsRefresh.addEventListener('click',()=>void refresh(true));
|
|
348
|
-
jobsReset.addEventListener('click',()=>{searchQuery='';selectedStatuses=[...${JSON.stringify(DEFAULT_DASHBOARD_JOB_STATUSES)}];selectedRepos=[];jobsPage=1;void refresh(false);});
|
|
349
|
-
jobsPrev.addEventListener('click',()=>{if(jobsPage>1){jobsPage-=1;void refresh(false);}});
|
|
350
|
-
jobsNext.addEventListener('click',()=>{if(jobsPage<jobsTotalPages){jobsPage+=1;void refresh(false);}});
|
|
351
337
|
eventsAllTasks.addEventListener('click',()=>{eventTaskId=null;clearEvents();renderEventScope();void loadEventHistory();});
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
338
|
+
jobsReset.addEventListener('click',()=>{searchQuery='';selectedStatus='';selectedRepo='';void refresh(true);});
|
|
339
|
+
repoFilter.addEventListener('change',()=>{selectedRepo=repoFilter.value;void refresh(true);});
|
|
340
|
+
jobSearch.addEventListener('input',()=>{searchQuery=jobSearch.value;void refresh(true);});
|
|
341
|
+
statusFilter.addEventListener('change',()=>{selectedStatus=statusFilter.value;void refresh(true);});
|
|
355
342
|
selectedJobRefresh.addEventListener('click',()=>void refreshSelected());
|
|
356
343
|
mobileJobsBack.addEventListener('click',()=>{location.hash='#/jobs';syncMobileJobsView();window.scrollTo({top:0,behavior:'instant'});});
|
|
357
344
|
mobileJobs.addEventListener('change',syncMobileJobsView);
|
package/dist/dashboard-style.js
CHANGED
|
@@ -21,12 +21,24 @@ export const dashboardStyles = ` <style>
|
|
|
21
21
|
.panel { min-width:0; background:var(--surface); border:1px solid var(--border); border-radius:.55rem; box-shadow:var(--shadow); }
|
|
22
22
|
.panel-head { display:flex; align-items:center; justify-content:space-between; gap:.6rem; padding:.65rem .75rem; border-bottom:1px solid var(--border); }
|
|
23
23
|
.panel-head h2,.panel-head h3 { margin:0; color:var(--muted); font-size:.75rem; letter-spacing:.07em; text-transform:uppercase; min-width:0; }\n #workspace-heading { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n #selected-job-title { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
24
|
-
.panel-actions { display:flex; align-items:center; gap:.
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
.job-
|
|
28
|
-
.
|
|
29
|
-
.
|
|
24
|
+
.panel-actions { display:flex; align-items:center; gap:.55rem; flex-wrap:wrap; min-width:0; }
|
|
25
|
+
#jobs-view .panel-head { display:flex; align-items:center; justify-content:space-between; gap:.65rem; }
|
|
26
|
+
#jobs-view .panel-actions { display:flex; align-items:center; justify-content:flex-end; gap:.4rem; flex-wrap:wrap; min-width:0; }
|
|
27
|
+
.job-filters,.jobs-actions { display:flex; align-items:center; gap:.35rem; min-width:0; }
|
|
28
|
+
.job-filters { flex:1 1 auto; }
|
|
29
|
+
.jobs-actions { flex:none; }
|
|
30
|
+
.job-filter,.refresh-button { min-height:2rem; border:1px solid var(--border); border-radius:.45rem; background:var(--surface); color:var(--text); padding:.3rem .5rem; font:inherit; font-size:.72rem; font-weight:650; }
|
|
31
|
+
.job-filter { width:8rem; max-width:8rem; flex:none; }
|
|
32
|
+
#status-filter { width:9.5rem; max-width:9.5rem; font-size:.66rem; }
|
|
33
|
+
#repo-filter { width:8rem; max-width:8rem; }
|
|
34
|
+
.refresh-button { color:var(--muted); font-weight:700; cursor:pointer; transition:border-color .15s ease,background .15s ease; }
|
|
35
|
+
.refresh-button:hover { background:#ffffff10; color:var(--text); }
|
|
36
|
+
.job-filter:focus-visible,.refresh-button:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
|
|
37
|
+
.jobs-feedback { flex:none; min-width:0; max-width:12rem; font-size:.67rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
38
|
+
.jobs-feedback:empty { display:none; }
|
|
39
|
+
.jobs-feedback:not(:empty) { flex-basis:100%; max-width:none; text-align:right; white-space:normal; }
|
|
40
|
+
.job-search { flex:1 1 11rem; width:12rem; min-width:8rem; max-width:18rem; border:1px solid var(--border); border-radius:.45rem; background:var(--surface); color:var(--text); padding:.3rem .5rem; font:inherit; font-size:.72rem; }
|
|
41
|
+
.job-search:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
|
|
30
42
|
.quick-filter { border:1px solid var(--border); border-radius:.35rem; background:var(--surface); color:var(--muted); padding:.27rem .42rem; font:inherit; font-size:.7rem; font-weight:750; cursor:pointer; }
|
|
31
43
|
.quick-filter[aria-pressed="true"] { border-color:var(--accent); background:var(--accent2); color:var(--accent); }
|
|
32
44
|
.refresh-button { border:1px solid var(--border); border-radius:.35rem; background:var(--surface); color:var(--muted); padding:.28rem .55rem; font:inherit; font-size:.72rem; font-weight:700; cursor:pointer; }
|
|
@@ -146,11 +158,15 @@ export const dashboardStyles = ` <style>
|
|
|
146
158
|
body[data-mobile-jobs-view="detail"] #jobs-view { display:none; }
|
|
147
159
|
.mobile-only { display:inline-flex; }
|
|
148
160
|
#jobs-view .panel-head { display:grid; align-items:start; }
|
|
149
|
-
#jobs-view .panel-actions { width:100%;
|
|
161
|
+
#jobs-view .panel-actions { display:grid; grid-template-columns:minmax(0,1fr) auto; width:100%; gap:.5rem; }
|
|
150
162
|
.runtime-summary { display:none; }
|
|
151
|
-
#jobs-view .job-filters { width:
|
|
152
|
-
#jobs-view .job-
|
|
153
|
-
#jobs-view
|
|
163
|
+
#jobs-view .job-filters { width:100%; display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.4rem; }
|
|
164
|
+
#jobs-view .job-filters { grid-column:1 / -1; }
|
|
165
|
+
#jobs-view .job-search { grid-column:1 / -1; width:100%; max-width:none; }
|
|
166
|
+
#jobs-view .job-filter { width:100%; max-width:none; min-width:0; text-transform:none; font-size:.72rem; }
|
|
167
|
+
#jobs-view .jobs-actions { grid-column:1 / -1; justify-content:flex-end; }
|
|
168
|
+
#jobs-view .jobs-feedback { grid-column:1 / -1; text-align:right; }
|
|
169
|
+
#jobs-view .refresh-button { min-height:2.5rem; }
|
|
154
170
|
#jobs-view .panel-body { max-height:none; }
|
|
155
171
|
#jobs-workspace { gap:.65rem; }
|
|
156
172
|
#jobs-workspace .panel-body { max-height:none; }
|
package/dist/dashboard.js
CHANGED
|
@@ -209,8 +209,7 @@ export function serveDashboard(store, port = 8787, bus, refreshMs = 300000, jobI
|
|
|
209
209
|
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
210
210
|
const forceRefresh = url.searchParams.get('refresh') === '1';
|
|
211
211
|
const jobs = await discoveredJobs(forceRefresh);
|
|
212
|
-
const
|
|
213
|
-
const body = usesSearchContract ? searchDashboardJobs(jobs, url.searchParams) : jobs;
|
|
212
|
+
const body = searchDashboardJobs(jobs, url.searchParams);
|
|
214
213
|
if (!res.headersSent)
|
|
215
214
|
res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
216
215
|
if (!res.writableEnded)
|
package/dist/relayd.js
CHANGED
|
@@ -172,11 +172,11 @@ export async function runDaemon(argv) {
|
|
|
172
172
|
}
|
|
173
173
|
return prepareLocalJobExecutionWorktree(canonical, await store.load(job.id), workspaceRoot);
|
|
174
174
|
} });
|
|
175
|
-
const refreshAll = async (
|
|
175
|
+
const refreshAll = async (_forceRefresh = false) => {
|
|
176
176
|
cachedOverviews = await inventoryFromLocal();
|
|
177
177
|
return cachedOverviews;
|
|
178
178
|
};
|
|
179
|
-
const overviews = async (
|
|
179
|
+
const overviews = async (forceRefresh = false, _trigger = 'internal.unknown') => refreshAll(forceRefresh);
|
|
180
180
|
const refreshPullRequestSnapshot = async (repository, prNumber, _trigger = 'internal', _options = {}) => {
|
|
181
181
|
const inventory = await refreshAll();
|
|
182
182
|
return {
|