agents-relay 2.0.178 → 2.0.179

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.
@@ -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" multiple aria-label="Filter jobs by status (choose at least one; empty uses DRAFT, ACTIVE, and COMPLETED)"><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" multiple aria-label="Filter jobs by repository (empty shows all repositories)"></select><button id="jobs-reset" class="refresh-button" type="button">Reset</button></div><div class="jobs-pagination"><button id="jobs-prev" class="refresh-button" type="button">Previous</button><span id="jobs-page" class="subtle">Page 1</span><button id="jobs-next" class="refresh-button" type="button">Next</button></div><button id="jobs-refresh" class="refresh-button" type="button">Refresh</button></div></div>
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"><div class="filter-dropdown"><button id="status-filter" class="filter-trigger" type="button" aria-haspopup="true" aria-expanded="false" aria-controls="status-filter-menu"><span class="filter-label">Status</span><span id="status-filter-summary" class="filter-value">Default</span></button><div id="status-filter-menu" class="filter-menu" role="group" aria-label="Status options" hidden><label class="filter-option"><input type="checkbox" name="status" value="DRAFT"><span>DRAFT</span></label><label class="filter-option"><input type="checkbox" name="status" value="ACTIVE"><span>ACTIVE</span></label><label class="filter-option"><input type="checkbox" name="status" value="COMPLETED"><span>COMPLETED</span></label><label class="filter-option"><input type="checkbox" name="status" value="CANCELLED"><span>CANCELLED</span></label></div></div><div class="filter-dropdown"><button id="repo-filter" class="filter-trigger" type="button" aria-haspopup="true" aria-expanded="false" aria-controls="repo-filter-menu"><span class="filter-label">Repositories</span><span id="repo-filter-summary" class="filter-value">All</span></button><div id="repo-filter-menu" class="filter-menu" role="group" aria-label="Repository options" hidden></div></div></div><div class="jobs-actions"><button id="jobs-reset" class="refresh-button" type="button">Reset</button><div class="jobs-pagination"><button id="jobs-prev" class="refresh-button" type="button">Previous</button><span id="jobs-page" class="subtle">Page 1</span><button id="jobs-next" class="refresh-button" type="button">Next</button></div><button id="jobs-refresh" class="refresh-button" type="button">Refresh</button></div></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">
@@ -148,7 +148,11 @@ const jobsPrev=document.querySelector('#jobs-prev');
148
148
  const jobsNext=document.querySelector('#jobs-next');
149
149
  const jobsPageLabel=document.querySelector('#jobs-page');
150
150
  const repoFilter=document.querySelector('#repo-filter');
151
+ const repoFilterMenu=document.querySelector('#repo-filter-menu');
152
+ const repoFilterSummary=document.querySelector('#repo-filter-summary');
151
153
  const statusFilter=document.querySelector('#status-filter');
154
+ const statusFilterMenu=document.querySelector('#status-filter-menu');
155
+ const statusFilterSummary=document.querySelector('#status-filter-summary');
152
156
  const selectedJobRefresh=document.querySelector('#selected-job-refresh');
153
157
  const mobileJobsBack=document.querySelector('#mobile-jobs-back');
154
158
  const mobileJobs=window.matchMedia('(max-width:650px)');
@@ -225,15 +229,28 @@ function selectJob(job){selectedJob=jobSelectionKey(job);selectedTaskId=null;eve
225
229
  function matchesQuickFilter(job){const statuses=selectedStatuses.length?selectedStatuses:${JSON.stringify(DEFAULT_DASHBOARD_JOB_STATUSES)};return statuses.some(status=>status==='DRAFT'?job.draft||job.state==='DRAFT':job.state===status);}
226
230
  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
231
  function filteredJobs(){return jobs.filter(job=>(!selectedRepos.length||selectedRepos.includes(job.repository))&&matchesQuickFilter(job)&&matchesSearch(job));}
232
+ function selectedNames(inputs,selected){const selectedSet=new Set(selected);return [...inputs].filter(input=>selectedSet.has(input.value)).map(input=>input.value);}
233
+ function syncFilterSummary(trigger,summary,inputs,selected,emptyLabel){const names=selectedNames(inputs,selected);summary.textContent=names.length===0?emptyLabel:names.length<=2?names.join(', '):names.length+' selected';trigger.title=names.length===0?emptyLabel:names.join(', ');}
234
+ function closeFilterMenus(exceptMenu){for(const [trigger,menu] of [[statusFilter,statusFilterMenu],[repoFilter,repoFilterMenu]]){if(menu===exceptMenu)continue;menu.hidden=true;trigger.setAttribute('aria-expanded','false');}}
235
+ function toggleFilterMenu(trigger,menu){const open=menu.hidden;closeFilterMenus(open?menu:null);menu.hidden=!open;trigger.setAttribute('aria-expanded',String(open));}
228
236
  function syncFilters(){
229
237
  const repos=jobRepositories.length?jobRepositories:[...new Set(jobs.map(job=>job.repository).filter(Boolean))].sort();
230
238
  const validRepos=new Set(repos);
231
- repoFilter.replaceChildren(...repos.map(repo=>new Option(repo,repo)));
239
+ const existingInputs=[...repoFilterMenu.querySelectorAll('input')];
240
+ // Preserve checkbox nodes while their options are unchanged so keyboard focus survives selection.
241
+ if(existingInputs.length!==repos.length||existingInputs.some((input,index)=>input.value!==repos[index])){
242
+ const focusedRepo=repoFilterMenu.contains(document.activeElement)?document.activeElement.value:null;
243
+ repoFilterMenu.replaceChildren(...repos.map(repo=>{const label=document.createElement('label');label.className='filter-option';const input=document.createElement('input');input.type='checkbox';input.name='repo';input.value=repo;label.append(input,element('span','',text(repo)));return label;}));
244
+ if(focusedRepo)for(const input of repoFilterMenu.querySelectorAll('input'))if(input.value===focusedRepo){input.focus();break;}
245
+ }
232
246
  selectedRepos=selectedRepos.filter(repo=>validRepos.has(repo));
233
- for(const option of repoFilter.options)option.selected=selectedRepos.includes(option.value);
234
- const validStatuses=new Set([...statusFilter.options].map(option=>option.value));
247
+ for(const input of repoFilterMenu.querySelectorAll('input'))input.checked=selectedRepos.includes(input.value);
248
+ const statusInputs=[...statusFilterMenu.querySelectorAll('input')];
249
+ const validStatuses=new Set(statusInputs.map(input=>input.value));
235
250
  selectedStatuses=selectedStatuses.filter(status=>validStatuses.has(status));
236
- for(const option of statusFilter.options)option.selected=selectedStatuses.includes(option.value);
251
+ for(const input of statusInputs)input.checked=selectedStatuses.includes(input.value);
252
+ syncFilterSummary(statusFilter,statusFilterSummary,statusInputs,selectedStatuses,'Default');
253
+ syncFilterSummary(repoFilter,repoFilterSummary,[...repoFilterMenu.querySelectorAll('input')],selectedRepos,'All');
237
254
  jobSearch.value=searchQuery;
238
255
  jobsPageLabel.textContent='Page '+jobsPage+' of '+jobsTotalPages+(jobsTotal?' · '+jobsTotal+' jobs':'');
239
256
  jobsPrev.disabled=jobsPage<=1;
@@ -345,13 +362,17 @@ async function saveQa(){qaSave.disabled=true;qaStatus.textContent='Saving…';tr
345
362
  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
363
  jobsTab.addEventListener('click',()=>{location.hash=selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs';showView('jobs');});
347
364
  jobsRefresh.addEventListener('click',()=>void refresh(true));
348
- jobsReset.addEventListener('click',()=>{searchQuery='';selectedStatuses=[...${JSON.stringify(DEFAULT_DASHBOARD_JOB_STATUSES)}];selectedRepos=[];jobsPage=1;void refresh(false);});
365
+ jobsReset.addEventListener('click',()=>{searchQuery='';selectedStatuses=[...${JSON.stringify(DEFAULT_DASHBOARD_JOB_STATUSES)}];selectedRepos=[];jobsPage=1;closeFilterMenus();void refresh(false);});
349
366
  jobsPrev.addEventListener('click',()=>{if(jobsPage>1){jobsPage-=1;void refresh(false);}});
350
367
  jobsNext.addEventListener('click',()=>{if(jobsPage<jobsTotalPages){jobsPage+=1;void refresh(false);}});
351
368
  eventsAllTasks.addEventListener('click',()=>{eventTaskId=null;clearEvents();renderEventScope();void loadEventHistory();});
352
- repoFilter.addEventListener('change',()=>{selectedRepos=[...repoFilter.selectedOptions].map(option=>option.value);jobsPage=1;void refresh(false);});
369
+ repoFilter.addEventListener('click',()=>toggleFilterMenu(repoFilter,repoFilterMenu));
370
+ repoFilterMenu.addEventListener('change',()=>{selectedRepos=[...repoFilterMenu.querySelectorAll('input:checked')].map(input=>input.value);jobsPage=1;syncFilters();void refresh(false);});
353
371
  jobSearch.addEventListener('input',()=>{searchQuery=jobSearch.value;jobsPage=1;void refresh(false);});
354
- statusFilter.addEventListener('change',()=>{selectedStatuses=[...statusFilter.selectedOptions].map(option=>option.value);jobsPage=1;void refresh(false);});
372
+ statusFilter.addEventListener('click',()=>toggleFilterMenu(statusFilter,statusFilterMenu));
373
+ statusFilterMenu.addEventListener('change',()=>{selectedStatuses=[...statusFilterMenu.querySelectorAll('input:checked')].map(input=>input.value);jobsPage=1;syncFilters();void refresh(false);});
374
+ document.addEventListener('click',event=>{const target=event.target instanceof Node?event.target:null;const inStatus=statusFilter.contains(target)||statusFilterMenu.contains(target);const inRepo=repoFilter.contains(target)||repoFilterMenu.contains(target);if(!inStatus&&!inRepo)closeFilterMenus(null);});
375
+ document.addEventListener('keydown',event=>{if(event.key!=='Escape')return;const active=document.activeElement;if(statusFilterMenu.hidden===false&&(statusFilterMenu.contains(active)||active===statusFilter)){statusFilterMenu.hidden=true;statusFilter.setAttribute('aria-expanded','false');statusFilter.focus();}if(repoFilterMenu.hidden===false&&(repoFilterMenu.contains(active)||active===repoFilter)){repoFilterMenu.hidden=true;repoFilter.setAttribute('aria-expanded','false');repoFilter.focus();}});
355
376
  selectedJobRefresh.addEventListener('click',()=>void refreshSelected());
356
377
  mobileJobsBack.addEventListener('click',()=>{location.hash='#/jobs';syncMobileJobsView();window.scrollTo({top:0,behavior:'instant'});});
357
378
  mobileJobs.addEventListener('change',syncMobileJobsView);
@@ -21,12 +21,25 @@ 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:.45rem; }
25
- .job-filters { display:flex; gap:.35rem; align-items:center; flex-wrap:wrap; }
26
- .job-filter { max-width:145px; border:1px solid var(--border); border-radius:.35rem; background:var(--surface); color:var(--text); padding:.27rem .4rem; font:inherit; font-size:.72rem; }
27
- .job-filter[multiple] { height:5.6rem; }
28
- .jobs-pagination { display:flex; gap:.3rem; align-items:center; }
29
- .job-search { width:95px; max-width:14vw; border:1px solid var(--border); border-radius:.35rem; background:var(--surface); color:var(--text); padding:.27rem .45rem; font:inherit; font-size:.72rem; }
24
+ .panel-actions { display:flex; align-items:center; gap:.55rem; flex-wrap:wrap; min-width:0; }
25
+ #jobs-view .panel-head { display:grid; grid-template-columns:minmax(0,1fr); align-items:start; gap:.45rem; }
26
+ .job-filters,.jobs-actions { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap; min-width:0; }
27
+ .jobs-actions { justify-content:flex-end; }
28
+ .filter-trigger,.refresh-button { min-height:2rem; border:1px solid var(--border); border-radius:.45rem; background:var(--surface); color:var(--muted); padding:.3rem .55rem; font:inherit; font-size:.72rem; font-weight:700; cursor:pointer; transition:border-color .15s ease,background .15s ease; }
29
+ .filter-trigger { display:inline-flex; align-items:center; gap:.4rem; max-width:17rem; }
30
+ .filter-trigger:hover,.refresh-button:hover { background:#ffffff10; color:var(--text); }
31
+ .filter-trigger:focus-visible,.refresh-button:focus-visible,.filter-option input:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
32
+ .filter-trigger[aria-expanded="true"] { border-color:var(--accent); color:var(--text); }
33
+ .filter-label { flex:0 0 auto; }
34
+ .filter-value { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--text); font-weight:600; }
35
+ .filter-dropdown { position:relative; min-width:0; }
36
+ .filter-menu { position:absolute; top:calc(100% + .3rem); left:0; z-index:30; width:max-content; min-width:11rem; max-width:calc(100vw - 1.5rem); max-height:17rem; overflow:auto; border:1px solid var(--border); border-radius:.55rem; background:var(--surface); box-shadow:0 12px 34px #00000026; padding:.3rem; }
37
+ .filter-option { display:grid; grid-template-columns:auto minmax(0,1fr); align-items:center; gap:.45rem; padding:.4rem .5rem; border-radius:.4rem; font-size:.74rem; font-weight:600; color:var(--text); cursor:pointer; }
38
+ .filter-option:hover { background:#ffffff10; }
39
+ .filter-option input { width:.95rem; height:.95rem; margin:0; accent-color:var(--accent); }
40
+ .jobs-pagination { display:flex; gap:.3rem; align-items:center; white-space:nowrap; }
41
+ .job-search { flex:1 1 10rem; width:12rem; min-width:0; max-width:20rem; border:1px solid var(--border); border-radius:.45rem; background:var(--surface); color:var(--text); padding:.3rem .5rem; font:inherit; font-size:.72rem; }
42
+ .job-search:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
30
43
  .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
44
  .quick-filter[aria-pressed="true"] { border-color:var(--accent); background:var(--accent2); color:var(--accent); }
32
45
  .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 +159,18 @@ export const dashboardStyles = ` <style>
146
159
  body[data-mobile-jobs-view="detail"] #jobs-view { display:none; }
147
160
  .mobile-only { display:inline-flex; }
148
161
  #jobs-view .panel-head { display:grid; align-items:start; }
149
- #jobs-view .panel-actions { width:100%; flex-wrap:wrap; }
162
+ #jobs-view .panel-actions { display:grid; grid-template-columns:1fr; width:100%; gap:.5rem; }
150
163
  .runtime-summary { display:none; }
151
- #jobs-view .job-filters { width:auto; }
152
- #jobs-view .job-search { display:none; }
153
- #jobs-view #status-filter { display:block; max-width:145px; }
164
+ #jobs-view .job-filters { width:100%; display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.4rem; }
165
+ #jobs-view .job-search { grid-column:1 / -1; width:100%; max-width:none; }
166
+ #jobs-view .filter-trigger { width:100%; max-width:none; min-height:2.5rem; }
167
+ #jobs-view .filter-trigger { display:grid; align-content:center; justify-items:start; gap:.1rem; }
168
+ #jobs-view .filter-label { font-size:.6rem; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); }
169
+ #jobs-view .filter-value { overflow:visible; text-overflow:clip; white-space:normal; font-size:.72rem; line-height:1.2; }
170
+ #jobs-view .jobs-actions { width:100%; display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:center; }
171
+ #jobs-view .refresh-button { min-height:2.5rem; }
172
+ #jobs-view .jobs-pagination { min-width:0; justify-content:center; }
173
+ #jobs-view #jobs-page { overflow:hidden; text-overflow:ellipsis; }
154
174
  #jobs-view .panel-body { max-height:none; }
155
175
  #jobs-workspace { gap:.65rem; }
156
176
  #jobs-workspace .panel-body { max-height:none; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "2.0.178",
3
+ "version": "2.0.179",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "repository": {
6
6
  "type": "git",