agents-relay 1.0.169 → 1.0.171
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 +39 -5
- package/package.json +1 -1
package/dist/dashboard-html.js
CHANGED
|
@@ -91,7 +91,7 @@ export const dashboardHtml = `<!doctype html>
|
|
|
91
91
|
.event-meta { display:flex; flex-wrap:wrap; gap:.4rem; color:var(--muted); font-size:.68rem; }
|
|
92
92
|
.event-kind { color:var(--accent); font-weight:800; }
|
|
93
93
|
.event-message { margin:.12rem 0 0; overflow-wrap:anywhere; }
|
|
94
|
-
#usage-view,#rules-view,#worker-rules-view { grid-column:1 / -1; }
|
|
94
|
+
#usage-view,#rules-view,#shortcuts-view,#worker-rules-view { grid-column:1 / -1; }
|
|
95
95
|
.rules-editor { display:grid; gap:.65rem; }
|
|
96
96
|
.rules-help { margin:0; color:var(--muted); font-size:.8rem; }
|
|
97
97
|
.rules-textarea { width:100%; min-height:55vh; resize:vertical; border:1px solid var(--border); border-radius:.45rem; background:var(--surface2); color:var(--text); padding:.8rem; font:13px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace; }
|
|
@@ -171,6 +171,7 @@ export const dashboardHtml = `<!doctype html>
|
|
|
171
171
|
<button id="jobs-tab" type="button" role="tab" aria-selected="true" aria-controls="jobs-view">Jobs</button>
|
|
172
172
|
<button id="usage-tab" type="button" role="tab" aria-selected="false" aria-controls="usage-view">Usage</button>
|
|
173
173
|
<button id="rules-tab" type="button" role="tab" aria-selected="false" aria-controls="rules-view">Execution Rules</button>
|
|
174
|
+
<button id="shortcuts-tab" type="button" role="tab" aria-selected="false" aria-controls="shortcuts-view">Shortcuts</button>
|
|
174
175
|
</nav>
|
|
175
176
|
</header>
|
|
176
177
|
<main>
|
|
@@ -212,6 +213,18 @@ export const dashboardHtml = `<!doctype html>
|
|
|
212
213
|
</div>
|
|
213
214
|
</div>
|
|
214
215
|
</section>
|
|
216
|
+
<section id="shortcuts-view" role="tabpanel" aria-labelledby="shortcuts-tab" hidden>
|
|
217
|
+
<div class="panel">
|
|
218
|
+
<div class="panel-head"><h2>ChatGPT Shortcuts</h2><span class="subtle">One per line · %1 = current repo/pr</span></div>
|
|
219
|
+
<div class="panel-body">
|
|
220
|
+
<div class="rules-editor">
|
|
221
|
+
<p class="rules-help">Format: <name>: <prompt>. Use %1 where the current Job reference should appear.</p>
|
|
222
|
+
<textarea id="shortcuts-text" class="rules-textarea" spellcheck="true" aria-label="ChatGPT shortcuts"></textarea>
|
|
223
|
+
<div class="rules-footer"><span id="shortcuts-status" class="subtle" aria-live="polite"></span><button id="shortcuts-save" class="rules-save" type="button">Save shortcuts</button></div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</section>
|
|
215
228
|
</main>
|
|
216
229
|
<script>
|
|
217
230
|
'use strict';
|
|
@@ -232,9 +245,11 @@ const DASHBOARD_VERSION=${JSON.stringify(dashboardVersion)};
|
|
|
232
245
|
const jobsTab=document.querySelector('#jobs-tab');
|
|
233
246
|
const usageTab=document.querySelector('#usage-tab');
|
|
234
247
|
const rulesTab=document.querySelector('#rules-tab');
|
|
248
|
+
const shortcutsTab=document.querySelector('#shortcuts-tab');
|
|
235
249
|
const jobsView=document.querySelector('#jobs-view');
|
|
236
250
|
const usageView=document.querySelector('#usage-view');
|
|
237
251
|
const rulesView=document.querySelector('#rules-view');
|
|
252
|
+
const shortcutsView=document.querySelector('#shortcuts-view');
|
|
238
253
|
const jobsWorkspace=document.querySelector('#jobs-workspace');
|
|
239
254
|
const jobList=document.querySelector('#job-list');
|
|
240
255
|
const jobsRefresh=document.querySelector('#jobs-refresh');
|
|
@@ -260,6 +275,9 @@ const rulesText=document.querySelector('#rules-text');
|
|
|
260
275
|
const rulesMeta=document.querySelector('#rules-meta');
|
|
261
276
|
const rulesStatus=document.querySelector('#rules-status');
|
|
262
277
|
const rulesSave=document.querySelector('#rules-save');
|
|
278
|
+
const shortcutsText=document.querySelector('#shortcuts-text');
|
|
279
|
+
const shortcutsStatus=document.querySelector('#shortcuts-status');
|
|
280
|
+
const shortcutsSave=document.querySelector('#shortcuts-save');
|
|
263
281
|
const rateLimitWarning=document.querySelector('#rate-limit-warning');
|
|
264
282
|
const runtimeSummary=document.querySelector('#runtime-summary');
|
|
265
283
|
let jobs=[];
|
|
@@ -320,6 +338,18 @@ function syncFilters(){
|
|
|
320
338
|
jobSearch.value=searchQuery;
|
|
321
339
|
}
|
|
322
340
|
function repoName(repository){if(typeof repository!=='string'||!repository)return 'unknown-repo';const parts=repository.split('/').filter(Boolean);return parts.at(-1)||repository;}
|
|
341
|
+
|
|
342
|
+
const CHAT_SHORTCUTS_KEY='agents-relay:chatgpt-shortcuts';
|
|
343
|
+
const DEFAULT_CHAT_SHORTCUTS=[
|
|
344
|
+
'Why blocked?: %1: why is it blocked? Find the root cause and move it forward.',
|
|
345
|
+
'Move forward: %1: check the current state and move it forward.',
|
|
346
|
+
'What is wrong?: %1: check the current state and explain what is wrong.'
|
|
347
|
+
].join('\\n');
|
|
348
|
+
function chatShortcutText(){return localStorage.getItem(CHAT_SHORTCUTS_KEY)||DEFAULT_CHAT_SHORTCUTS;}
|
|
349
|
+
function parseChatShortcuts(source){return String(source||'').split(/\\r?\\n/).map(line=>line.trim()).filter(Boolean).map(line=>{const split=line.indexOf(':');return split>0?{name:line.slice(0,split).trim(),prompt:line.slice(split+1).trim()}:null;}).filter(item=>item&&item.name&&item.prompt);}
|
|
350
|
+
function jobChatRef(job){return repoName(job?.repository)+'/pr#'+value(job?.prNumber);}
|
|
351
|
+
function chatShortcutUrl(prompt){return 'https://chatgpt.com/?prompt='+encodeURIComponent(prompt);}
|
|
352
|
+
function chatShortcutField(job){const box=element('div','detail-group');box.append(element('p','detail-label',text('ChatGPT shortcut')));const select=document.createElement('select');select.className='job-filter job-chat-shortcut-select';select.setAttribute('aria-label','ChatGPT shortcut');select.append(new Option('',''));for(const item of parseChatShortcuts(chatShortcutText()))select.append(new Option(item.name,item.prompt));select.value='';select.addEventListener('change',()=>{if(!select.value)return;const prompt=select.value.replaceAll('%1',jobChatRef(job));window.open(chatShortcutUrl(prompt),'_blank','noopener');select.value='';});box.append(select);return box;}
|
|
323
353
|
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));}}
|
|
324
354
|
function taskTagDisplay(job,task){const display=task.tag&&job?.tag?.values?.[task.tag];return typeof display==='string'&&display?display:null;}
|
|
325
355
|
function taskSubtreeComplete(task,children){return ['SUCCEEDED','CANCELLED'].includes(task.state)&&(children.get(task.id)||[]).every(child=>taskSubtreeComplete(child,children));}
|
|
@@ -335,7 +365,7 @@ function taskStateField(task){const box=element('div','detail-group');box.append
|
|
|
335
365
|
function manualOverrideField(task){const rows=Array.isArray(task.manualStateOverrides)?task.manualStateOverrides:[];const last=rows.at(-1);return field('Manual override',last?last.from+' → '+last.to+' · '+relativeTime(last.at):'—');}
|
|
336
366
|
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('Task',task.id),field('Kind',task.kind||'work'),taskStateField(task),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'),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));taskDetailRoot.append(groups);}
|
|
337
367
|
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();}
|
|
338
|
-
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(job.id!==selectedJob){selectedJob=job.id;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('Execution mode',job.executionMode||'fixed'),field('Cadence',cadence(job)),field('Review',reviewStatus(job)),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)));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();}
|
|
368
|
+
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(job.id!==selectedJob){selectedJob=job.id;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('Execution mode',job.executionMode||'fixed'),field('Cadence',cadence(job)),field('Review',reviewStatus(job)),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();}
|
|
339
369
|
function renderAll(){jobs.sort(compareJobs);syncFilters();renderJobList();renderSelectedJob();syncMobileJobsView();}
|
|
340
370
|
function isCompleteUnmerged(job){return job.runtimeStatus==='MERGE_PENDING';}
|
|
341
371
|
function isTerminalJob(job){return ['COMPLETED','CANCELLED'].includes(job.state)||['MERGED','CLOSED'].includes(job.githubState);}
|
|
@@ -387,7 +417,9 @@ function githubUsageCard(snapshot,status){
|
|
|
387
417
|
async function drawUsage(){try{const overview=await loadJSON('/api/usage');usageRoot.replaceChildren();usageMeta.textContent='Real account telemetry when available';const providers=Array.isArray(overview.providers)?overview.providers:[];const credits=Array.isArray(overview.credits)?overview.credits:[];const grid=element('div','usage-providers');if(overview.github||overview.githubStatus)grid.append(githubUsageCard(overview.github,overview.githubStatus));for(const provider of providers)grid.append(usageProvider(provider));for(const provider of credits)grid.append(creditProvider(provider));if(!grid.children.length){usageRoot.append(element('p','empty',text('Usage telemetry is unavailable.')));return;}usageRoot.append(grid);}catch(error){usageMeta.textContent='Unavailable';usageRoot.replaceChildren(element('p','error-state',text('Usage unavailable: '+error.message)));}}
|
|
388
418
|
async function drawRules(){rulesStatus.textContent='';try{const rules=await loadJSON('/api/execution-router-rules');rulesText.value=typeof rules.text==='string'?rules.text:'';rulesMeta.textContent=rules.hash&&rules.hash!=='none'?'Loaded · '+rules.hash.slice(0,12):'No custom rules saved';}catch(error){rulesMeta.textContent='Unavailable';rulesStatus.textContent='Could not load rules: '+error.message;}}
|
|
389
419
|
async function saveRules(){rulesSave.disabled=true;rulesStatus.textContent='Saving…';try{const response=await fetch('/api/execution-router-rules',{method:'PUT',headers:{'content-type':'text/plain; charset=utf-8'},body:rulesText.value});if(!response.ok)throw new Error(response.status+' '+await response.text());const rules=await response.json();rulesMeta.textContent='Saved · '+rules.hash.slice(0,12);rulesStatus.textContent='Saved. New routing decisions will load these rules.';}catch(error){rulesStatus.textContent='Save failed: '+error.message;}finally{rulesSave.disabled=false;}}
|
|
390
|
-
function
|
|
420
|
+
async function drawShortcuts(){shortcutsStatus.textContent='';shortcutsText.value=chatShortcutText();}
|
|
421
|
+
function saveShortcuts(){const value=shortcutsText.value.trim();if(value)localStorage.setItem(CHAT_SHORTCUTS_KEY,value);else localStorage.removeItem(CHAT_SHORTCUTS_KEY);shortcutsText.value=chatShortcutText();shortcutsStatus.textContent='Saved.';}
|
|
422
|
+
function showView(view){const isJobs=view==='jobs';const isUsage=view==='usage';const isRules=view==='rules';const isShortcuts=view==='shortcuts';jobsView.hidden=!isJobs;jobsWorkspace.hidden=!isJobs;usageView.hidden=!isUsage;rulesView.hidden=!isRules;shortcutsView.hidden=!isShortcuts;jobsTab.setAttribute('aria-selected',String(isJobs));usageTab.setAttribute('aria-selected',String(isUsage));rulesTab.setAttribute('aria-selected',String(isRules));shortcutsTab.setAttribute('aria-selected',String(isShortcuts));if(isJobs)void refresh();else if(isUsage)void drawUsage();else if(isRules)void drawRules();else void drawShortcuts();}
|
|
391
423
|
jobsTab.addEventListener('click',()=>{location.hash=selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs';showView('jobs');});
|
|
392
424
|
jobsRefresh.addEventListener('click',()=>void refresh(true));
|
|
393
425
|
eventsAllTasks.addEventListener('click',()=>{eventTaskId=null;clearEvents();renderEventScope();void loadEventHistory();});
|
|
@@ -402,7 +434,9 @@ mobileJobs.addEventListener('change',syncMobileJobsView);
|
|
|
402
434
|
window.addEventListener('hashchange',syncMobileJobsView);
|
|
403
435
|
usageTab.addEventListener('click',()=>{location.hash='#/usage';showView('usage');});
|
|
404
436
|
rulesTab.addEventListener('click',()=>{location.hash='#/execution-router-rules';showView('rules');});
|
|
437
|
+
shortcutsTab.addEventListener('click',()=>{location.hash='#/shortcuts';showView('shortcuts');});
|
|
405
438
|
rulesSave.addEventListener('click',()=>void saveRules());
|
|
439
|
+
shortcutsSave.addEventListener('click',saveShortcuts);
|
|
406
440
|
window.addEventListener('keydown',event=>{if(event.key==='Tab'&&event.altKey){event.preventDefault();const next=location.hash==='#/usage'?'rules':location.hash==='#/execution-router-rules'?'jobs':'usage';location.hash=next==='usage'?'#/usage':next==='rules'?'#/execution-router-rules':(selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs');showView(next);}});
|
|
407
441
|
function parseMessage(message){try{return JSON.parse(message.data);}catch{return null;}}
|
|
408
442
|
const stream=new EventSource('/events');
|
|
@@ -410,8 +444,8 @@ stream.onopen=()=>{connection.textContent='';connection.setAttribute('aria-label
|
|
|
410
444
|
stream.onerror=()=>{connection.textContent='';connection.setAttribute('aria-label','Event stream reconnecting');connection.className='status-pill warn';};
|
|
411
445
|
stream.onmessage=message=>{const payload=parseMessage(message);if(payload?.job){const index=jobs.findIndex(job=>job.id===payload.job.id);if(index>=0)jobs[index]=payload.job;else jobs.push(payload.job);renderAll();void drawRuntimeSummary();}};
|
|
412
446
|
stream.addEventListener('relay',message=>{const payload=parseMessage(message);if(payload?.job){const index=jobs.findIndex(job=>job.id===payload.job.id);if(index>=0)jobs[index]=payload.job;else jobs.push(payload.job);}if(payload?.event)addEvent(payload.event);renderAll();void drawRuntimeSummary();});
|
|
413
|
-
if(location.hash==='#/usage')showView('usage');else if(location.hash==='#/execution-router-rules')showView('rules');else showView('jobs');
|
|
414
|
-
setInterval(()=>{if(location.hash!=='#/usage'&&location.hash!=='#/execution-router-rules')void refresh();},300000);
|
|
447
|
+
if(location.hash==='#/usage')showView('usage');else if(location.hash==='#/execution-router-rules')showView('rules');else if(location.hash==='#/shortcuts')showView('shortcuts');else showView('jobs');
|
|
448
|
+
setInterval(()=>{if(location.hash!=='#/usage'&&location.hash!=='#/execution-router-rules'&&location.hash!=='#/shortcuts')void refresh();},300000);
|
|
415
449
|
setInterval(()=>{if(location.hash==='#/usage')void drawUsage();},30000);
|
|
416
450
|
setInterval(()=>void refreshVersion(),30000);
|
|
417
451
|
async function refreshSelected(){if(!selectedJob){await refresh();return;}selectedJobRefresh.disabled=true;selectedJobRefresh.textContent='Refreshing…';try{const updated=await loadJSON('/api/job?id='+encodeURIComponent(selectedJob));const index=jobs.findIndex(job=>job.id===selectedJob);if(index>=0)jobs[index]=updated;else jobs.push(updated);rememberSelection();renderAll();connection.textContent='';connection.setAttribute('aria-label','Live durable state connected');connection.className='status-pill ok';}catch(error){connection.textContent='';connection.setAttribute('aria-label','Durable state unavailable');connection.className='status-pill bad';}finally{selectedJobRefresh.disabled=false;selectedJobRefresh.textContent='Refresh';}}
|