agents-relay 2.0.134 → 2.0.135

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.
@@ -76,7 +76,7 @@ ${dashboardStyles}
76
76
  <div id="graph-navigation" class="graph-navigation" aria-label="Graph navigation"><button type="button" id="graph-back" class="refresh-button">Back</button><button type="button" id="graph-forward" class="refresh-button">Forward</button><button type="button" id="graph-entry" class="refresh-button">Entry</button><span id="graph-breadcrumb" class="subtle"></span></div>
77
77
  <div id="graph-detail" class="graph-source" aria-live="polite" hidden></div>
78
78
  <div id="graph-links" class="graph-navigation" aria-label="Linked subgraphs"></div>
79
- <div class="graph-preview-head"><h3 id="graph-title">Select a graph</h3><span id="graph-path" class="subtle"></span></div>
79
+ <div class="graph-preview-head"><h3 id="graph-title">Select a graph</h3><button type="button" id="graph-maximize" class="refresh-button">Maximize graph</button><span id="graph-path" class="subtle"></span></div>
80
80
  <div id="graph-canvas" class="graph-canvas"><p class="empty">Select a graph to inspect it.</p></div>
81
81
  <details class="graph-source-details"><summary>Mermaid source</summary><pre id="graph-source" class="graph-source"></pre></details>
82
82
  </div>
@@ -84,6 +84,7 @@ ${dashboardStyles}
84
84
  </div>
85
85
  </div>
86
86
  </section>
87
+ <dialog id="graph-dialog" class="graph-dialog" aria-label="Workflow graph viewer"><div class="graph-dialog-header"><strong id="graph-dialog-title">Workflow graph</strong><div class="graph-navigation"><button type="button" id="graph-zoom-out" class="refresh-button" aria-label="Zoom out">−</button><span id="graph-zoom-level" aria-live="polite">100%</span><button type="button" id="graph-zoom-in" class="refresh-button" aria-label="Zoom in">+</button><button type="button" id="graph-zoom-reset" class="refresh-button">Fit</button><button type="button" id="graph-dialog-close" class="refresh-button">Close</button></div></div><div id="graph-dialog-viewport" class="graph-dialog-viewport"></div></dialog>
87
88
  <section id="rules-view" role="tabpanel" aria-labelledby="rules-tab" hidden><div class="panel"><div class="panel-head"><h2>Router Rules</h2><span id="rules-meta" class="subtle"></span></div><div class="panel-body"><div class="router-rules-workspace"><div class="router-rules-pane rules-editor"><h3>Rules Editor</h3><p class="rules-help">Execution Router rules with an explicit <code>[choices]</code> section (provider: model [reasoning] entries, additive to profile defaults) and a <code>[routing-instruction]</code> section forwarded verbatim to Laya. Relay deduplicates, applies hard gates, and sends one merged choice list; impossible routes remain excluded by code and Codex stays the deterministic fallback.</p><textarea id="rules-text" class="rules-textarea" spellcheck="true" aria-label="Execution router rules" placeholder="[choices]\nopenai: sol medium, luna high\nzai: glm-5.3-flash high\n\n[routing-instruction]\nPrefer zai until its quota resets; otherwise use the openai default."></textarea><div class="rules-footer"><span id="rules-status" class="subtle" aria-live="polite"></span><div><button id="rules-test" class="rules-save" type="button">Test Router</button> <button id="rules-save" class="rules-save" type="button">Save rules</button></div></div></div><div class="router-rules-pane"><div class="panel-head"><h3>Audit Log</h3><button id="audit-refresh" type="button">Refresh</button></div><div id="audit-log" class="audit-log" aria-live="polite"><p class="empty">Loading latest routing decisions…</p></div></div></div><div class="router-test-panel"><h3>Test Router Result</h3><div id="router-test-results" class="router-test-results" aria-live="polite"><p class="empty">Select a Job and Task, then run Test Router.</p></div></div></div></div></section>
88
89
  <section id="shortcuts-view" role="tabpanel" aria-labelledby="shortcuts-tab" hidden>
89
90
  <div class="panel">
@@ -310,10 +311,10 @@ function renderGraphList(){graphList.replaceChildren();if(!workflowGraphs.length
310
311
  async function graphDetail(kind,name){const panel=document.querySelector('#graph-detail');panel.hidden=false;panel.replaceChildren(element('strong','',text(kind+': '+name)),element('p','subtle',text('Loading authoritative references…')));try{const params=new URLSearchParams({graph:selectedGraphId,kind,name});const result=await loadJSON('/api/graph-symbol?'+params);panel.replaceChildren(element('strong','',text(kind+': '+name)));const sections=[['Graph',[result.graph]],['Canonical contract',result.contracts],['Implementation',result.implementation],['Tests',result.tests]];for(const [heading,refs] of sections){panel.append(element('p','detail-label',text(heading)));if(!refs?.length){panel.append(element('p','subtle',text('No exact symbol reference found.')));continue;}for(const ref of refs){const link=element('a','',text(ref.path+(ref.line>0?':'+ref.line:'')+' ['+(ref.evidence==='exact-token'?'verified graph token':'text-search suggestion')+']'));if(WORKFLOW_SOURCE_REF){link.href='https://github.com/lalalic/agents-relay/blob/'+WORKFLOW_SOURCE_REF+'/'+ref.path+(ref.line>0?'#L'+ref.line:'');link.target='_blank';link.rel='noopener noreferrer';}else{link.removeAttribute('href');link.title='Source revision unavailable';}link.target='_blank';link.rel='noopener noreferrer';panel.append(link,document.createTextNode(' '));}}panel.scrollIntoView({block:'nearest'});}catch(error){panel.append(element('p','error-state',text('Reference lookup unavailable: '+error.message)));}}
311
312
  function interactiveNode(node,callback,label){node.classList.add('drillable');node.setAttribute('tabindex','0');node.setAttribute('role','button');node.setAttribute('aria-label',label);node.addEventListener('click',callback);node.addEventListener('keydown',event=>{if(event.key==='Enter'||event.key===' '){event.preventDefault();callback();}});}
312
313
  function graphNodeDefinitions(source){const result=new Map();for(const line of source.split('\\n')){const pattern=/([A-Za-z0-9_]+)\\["([^"\\n]+)"\\]/g;for(const match of line.matchAll(pattern))result.set(match[1],match[2]);}return result;}
313
- function addNodeLabel(node,label){const ns='http://www.w3.org/2000/svg';const labelGroup=node.querySelector('g.label');const parent=labelGroup||node;const textNode=document.createElementNS(ns,'text');textNode.setAttribute('text-anchor','middle');textNode.setAttribute('dominant-baseline','middle');textNode.setAttribute('fill','currentColor');textNode.setAttribute('font-size','13');const chunks=label.match(/.{1,22}(?:\\s|$)|.{1,22}/g)||[label];for(let i=0;i<chunks.length;i++){const tspan=document.createElementNS(ns,'tspan');tspan.setAttribute('x','0');tspan.setAttribute('dy',i===0?String(-((chunks.length-1)*7)): '14');tspan.textContent=chunks[i].trim();textNode.append(tspan);}parent.append(textNode);}
314
+ function addNodeLabel(node,label){const ns='http://www.w3.org/2000/svg';const old=node.querySelector('g.label');if(old)old.remove();const textNode=document.createElementNS(ns,'text');textNode.setAttribute('x','0');textNode.setAttribute('y','0');textNode.setAttribute('text-anchor','middle');textNode.setAttribute('dominant-baseline','middle');textNode.setAttribute('fill','#182536');textNode.setAttribute('font-size','13');const rect=node.querySelector('rect.label-container,rect.basic,rect');const width=Math.max(65,Number(rect?.getAttribute('width')||135)-14);const maxChars=Math.max(9,Math.floor(width/7.2));const words=label.split(/\\s+/);const lines=[];let current='';for(const word of words){if(current&&(current+' '+word).length>maxChars){lines.push(current);current=word;}else current=current?current+' '+word:word;}if(current)lines.push(current);for(let i=0;i<lines.length;i++){const tspan=document.createElementNS(ns,'tspan');tspan.setAttribute('x','0');tspan.setAttribute('dy',i===0?String(-((lines.length-1)*7)):'14');tspan.textContent=lines[i];textNode.append(tspan);}node.append(textNode);}
314
315
  function sanitizeGraphSvg(markup){const parsed=new DOMParser().parseFromString(markup,'image/svg+xml');const source=parsed.documentElement;if(source.localName!=='svg'||parsed.querySelector('parsererror'))throw new Error('Invalid SVG');const ns='http://www.w3.org/2000/svg';const elements=new Set(['svg','g','path','rect','circle','ellipse','line','polyline','polygon','text','tspan','defs','marker','title','desc','clipPath']);const attributes=new Set(['id','class','viewBox','width','height','x','y','x1','y1','x2','y2','cx','cy','r','rx','ry','d','points','transform','fill','stroke','stroke-width','stroke-dasharray','stroke-linecap','stroke-linejoin','opacity','fill-opacity','stroke-opacity','font-size','font-family','font-weight','text-anchor','dominant-baseline','markerWidth','markerHeight','markerUnits','refX','refY','orient','preserveAspectRatio','clip-path','marker-start','marker-mid','marker-end','role','aria-label']);const clean=node=>{if(node.nodeType===3)return document.createTextNode(node.textContent||'');if(node.nodeType!==1||node.namespaceURI!==ns||!elements.has(node.localName))return null;const out=document.createElementNS(ns,node.localName);for(const attr of node.attributes){if(!attr.namespaceURI&&attr.name==='style'){for(const declaration of attr.value.split(';')){const split=declaration.indexOf(':');if(split<0)continue;const property=declaration.slice(0,split).trim();const cssValue=declaration.slice(split+1).trim();if((property==='stroke'||property==='fill')&&/^(?:#[0-9a-fA-F]{3,8}|rgb\\([0-9.,%\\s]+\\)|none|currentColor)$/.test(cssValue))out.setAttribute(property,cssValue);else if(property==='stroke-width'&&/^[0-9]+(?:\\.[0-9]+)?(?:px)?$/.test(cssValue))out.setAttribute(property,cssValue);}continue;}if(attr.namespaceURI||!attributes.has(attr.name))continue;if(['marker-start','marker-mid','marker-end','clip-path'].includes(attr.name)){if(!attr.value.startsWith('url(#')||!attr.value.endsWith(')')||!(/^[a-zA-Z0-9_-]+$/.test(attr.value.slice(5,-1))))continue;}else if(attr.value.toLowerCase().includes('url(')||attr.value.includes('<')||attr.value.includes('>'))continue;out.setAttribute(attr.name,attr.value);}for(const child of node.childNodes){const safe=clean(child);if(safe)out.appendChild(safe);}return out;};return clean(source);}
315
316
  function applyGraphPalette(svg){const ns='http://www.w3.org/2000/svg';for(const path of svg.querySelectorAll('path')){if(path.closest('marker')){if(!path.hasAttribute('fill'))path.setAttribute('fill','#65768a');continue;}if(!path.hasAttribute('fill'))path.setAttribute('fill','none');if(!path.hasAttribute('stroke'))path.setAttribute('stroke','#65768a');}for(const node of svg.querySelectorAll('g.node')){const error=node.classList.contains('failure');for(const shape of node.querySelectorAll('rect,polygon,circle,ellipse')){shape.setAttribute('fill',error?'#ffe4e4':'#f9fbff');shape.setAttribute('stroke',error?'#be3030':'#657f9e');shape.setAttribute('stroke-width','1.3');}for(const label of node.querySelectorAll('text'))label.setAttribute('fill',error?'#7c1818':'#182536');}for(const rect of svg.querySelectorAll('rect')){if(!rect.closest('g.node')&&!rect.hasAttribute('fill'))rect.setAttribute('fill','#ffffff');}for(const label of svg.querySelectorAll('text'))if(!label.hasAttribute('fill'))label.setAttribute('fill','#182536');}
316
- async function renderSelectedGraph(){const graph=selectedWorkflowGraph();renderGraphNavigation();document.querySelector('#graph-detail').hidden=true;if(!graph){graphTitle.textContent='Graph not found';graphPath.textContent='';graphSource.textContent='';graphCanvas.replaceChildren(element('p','error-state',text('Graph not found: '+selectedGraphId+'. Select Entry or another graph.')));return;}graphTitle.textContent=graph.name;graphPath.textContent=graph.path;graphSource.textContent=graph.source;graphCanvas.replaceChildren(element('p','subtle',text('Rendering…')));try{if(!window.mermaid)throw new Error('Mermaid renderer unavailable');if(!window.__relayMermaidReady){window.mermaid.initialize({startOnLoad:false,securityLevel:'strict',flowchart:{htmlLabels:false}});window.__relayMermaidReady=true;}const rendered=await window.mermaid.render('relay-graph-'+graph.id.replace(/[^A-Za-z0-9_-]/g,'-'),graph.source);if(graph.id!==selectedGraphId)return;const svg=sanitizeGraphSvg(rendered.svg);graphCanvas.replaceChildren(svg);if(graph.id!=='entry'){const events=[...new Set([...graph.source.matchAll(/\\|on:\\s*([A-Za-z0-9_.-]+)\\|/g)].map(m=>m[1]))];const links=document.querySelector('#graph-links');for(const event of events){const button=element('button','refresh-button',text('Event: '+event));button.addEventListener('click',()=>graphDetail('Event',event));links.append(button);}}const definitions=graphNodeDefinitions(graph.source);for(const node of svg.querySelectorAll('g.node')){const entry=Array.from(definitions.entries()).find(([key])=>node.id.includes('-flowchart-'+key+'-'));const label=entry?.[1]||'';if(label&&!node.querySelector('text'))addNodeLabel(node,label);if(graph.id==='entry'){const target=Object.keys(GRAPH_NAV).find(key=>node.id.includes('-flowchart-'+key+'-'));if(target){const next=graphTargets(target).find(id=>workflowGraphs.some(g=>g.id===id));if(next)interactiveNode(node,()=>navigateGraph(next),'Explore '+target);}}else{const match=/^(state|guard|action):\\s*([A-Za-z0-9_.-]+)/.exec(label);if(match)interactiveNode(node,()=>graphDetail(match[1]==='guard'?'Guard':match[1]==='action'?'Action':'State',match[2]),'Inspect '+match[1]+' '+match[2]);}}applyGraphPalette(svg);}catch(error){graphCanvas.replaceChildren(element('p','error-state',text('Visual preview unavailable: '+error.message+'. Mermaid source remains available below.')));}}
317
+ async function renderSelectedGraph(){const graph=selectedWorkflowGraph();renderGraphNavigation();document.querySelector('#graph-detail').hidden=true;if(!graph){graphTitle.textContent='Graph not found';graphPath.textContent='';graphSource.textContent='';graphCanvas.replaceChildren(element('p','error-state',text('Graph not found: '+selectedGraphId+'. Select Entry or another graph.')));return;}graphTitle.textContent=graph.name;graphPath.textContent=graph.path;graphSource.textContent=graph.source;graphCanvas.replaceChildren(element('p','subtle',text('Rendering…')));try{if(!window.mermaid)throw new Error('Mermaid renderer unavailable');if(!window.__relayMermaidReady){window.mermaid.initialize({startOnLoad:false,securityLevel:'strict',flowchart:{htmlLabels:false}});window.__relayMermaidReady=true;}const rendered=await window.mermaid.render('relay-graph-'+graph.id.replace(/[^A-Za-z0-9_-]/g,'-'),graph.source);if(graph.id!==selectedGraphId)return;const svg=sanitizeGraphSvg(rendered.svg);graphCanvas.replaceChildren(svg);if(graph.id!=='entry'){const events=[...new Set([...graph.source.matchAll(/\\|on:\\s*([A-Za-z0-9_.-]+)\\|/g)].map(m=>m[1]))];const links=document.querySelector('#graph-links');for(const event of events){const button=element('button','refresh-button',text('Event: '+event));button.addEventListener('click',()=>graphDetail('Event',event));links.append(button);}}const definitions=graphNodeDefinitions(graph.source);for(const node of svg.querySelectorAll('g.node')){const entry=Array.from(definitions.entries()).find(([key])=>node.id.includes('-flowchart-'+key+'-'));const label=entry?.[1]||'';if(label)addNodeLabel(node,label);if(graph.id==='entry'){const target=Object.keys(GRAPH_NAV).find(key=>node.id.includes('-flowchart-'+key+'-'));if(target){const next=graphTargets(target).find(id=>workflowGraphs.some(g=>g.id===id));if(next)interactiveNode(node,()=>navigateGraph(next),'Explore '+target);}}else{const match=/^(state|guard|action):\\s*([A-Za-z0-9_.-]+)/.exec(label);if(match)interactiveNode(node,()=>graphDetail(match[1]==='guard'?'Guard':match[1]==='action'?'Action':'State',match[2]),'Inspect '+match[1]+' '+match[2]);}}applyGraphPalette(svg);}catch(error){graphCanvas.replaceChildren(element('p','error-state',text('Visual preview unavailable: '+error.message+'. Mermaid source remains available below.')));}}
317
318
  async function drawGraphs(){try{const graphs=await loadJSON('/api/graphs');workflowGraphs=[{id:'entry',name:'Entry / Happy Path',path:'Dashboard-only navigation (not executable)',source:GRAPH_ENTRY_SOURCE},...(Array.isArray(graphs)?graphs:[])];const requested=graphFromHash();selectedGraphId=requested||'entry';graphsMeta.textContent=(workflowGraphs.length-1)+' executable workflow graphs · design view';renderGraphList();await renderSelectedGraph();}catch(error){graphsMeta.textContent='Unavailable';workflowGraphs=[{id:'entry',name:'Entry / Happy Path',path:'Dashboard-only navigation',source:GRAPH_ENTRY_SOURCE}];selectedGraphId='entry';renderGraphList();await renderSelectedGraph();graphPath.textContent='Executable graphs unavailable: '+error.message;}}
318
319
  function renderAudit(records){auditLog.replaceChildren();if(!Array.isArray(records)||!records.length){auditLog.append(element('p','empty',text('No schema v5 routing decisions yet.')));return;}for(const record of records){const card=element('article','audit-entry');const head=element('div','audit-entry-head');head.append(element('strong','',text(routeWorker(record)+' · '+value(record?.selected?.model?.model))),element('span','subtle',text(new Date(record.timestamp).toLocaleString())));card.append(head);const correlation=record.correlation||{};card.append(element('div','subtle',text([correlation.job_id,correlation.task_id].filter(Boolean).join(' / ')||'uncorrelated')));const probabilities=record.decision_evidence?.probabilities||{};const gates=(record.gate_results||[]).filter(item=>!item.eligible).map(item=>item.candidate_id+': '+item.rejection_reasons.join(', '));const nl=String.fromCharCode(10);const details=document.createElement('pre');details.textContent='fallback: '+String(Boolean(record.decision_evidence?.fallback))+(Object.keys(probabilities).length?nl+'probabilities: '+JSON.stringify(probabilities,null,2):'')+(gates.length?nl+'rejected: '+gates.join(nl):'');card.append(details);auditLog.append(card);}}
319
320
  async function drawAudit(){auditRefresh.disabled=true;try{renderAudit(await loadJSON('/api/decision-audit'));}catch(error){auditLog.replaceChildren(element('p','error-state',text('Audit unavailable: '+error.message)));}finally{auditRefresh.disabled=false;}}
@@ -338,6 +339,7 @@ window.addEventListener('hashchange',syncMobileJobsView);
338
339
  usageTab.addEventListener('click',()=>{location.hash='#/usage';showView('usage');});
339
340
  graphsTab.addEventListener('click',()=>{location.hash='#/graphs/entry';showView('graphs');});
340
341
  document.querySelector('#graph-entry').addEventListener('click',()=>navigateGraph('entry'));
342
+ const graphDialog=document.querySelector('#graph-dialog');const graphViewport=document.querySelector('#graph-dialog-viewport');let graphZoom=1;const graphCanvasHome=graphCanvas.parentNode;function setGraphZoom(value){graphZoom=Math.min(4,Math.max(.35,value));const svg=graphCanvas.querySelector('svg');if(svg){svg.style.width=(Number(svg.getAttribute('width'))||Math.max(900,svg.viewBox.baseVal.width))*graphZoom+'px';svg.style.maxWidth='none';}document.querySelector('#graph-zoom-level').textContent=Math.round(graphZoom*100)+'%';}document.querySelector('#graph-maximize').addEventListener('click',()=>{graphDialog.showModal();graphViewport.append(graphCanvas);document.querySelector('#graph-dialog-title').textContent=graphTitle.textContent;setGraphZoom(1);});function closeGraphViewer(){graphCanvasHome.insertBefore(graphCanvas,graphCanvasHome.querySelector('.graph-source-details'));setGraphZoom(1);}graphDialog.addEventListener('close',closeGraphViewer);document.querySelector('#graph-dialog-close').addEventListener('click',()=>graphDialog.close());document.querySelector('#graph-zoom-in').addEventListener('click',()=>setGraphZoom(graphZoom*1.25));document.querySelector('#graph-zoom-out').addEventListener('click',()=>setGraphZoom(graphZoom/1.25));document.querySelector('#graph-zoom-reset').addEventListener('click',()=>setGraphZoom(1));graphViewport.addEventListener('wheel',event=>{if(!event.ctrlKey&&!event.metaKey)return;event.preventDefault();setGraphZoom(graphZoom*(event.deltaY<0?1.15:1/1.15));},{passive:false});let drag=null;graphViewport.addEventListener('pointerdown',event=>{if(event.target.closest('.drillable'))return;drag={x:event.clientX,y:event.clientY,left:graphViewport.scrollLeft,top:graphViewport.scrollTop};graphViewport.setPointerCapture(event.pointerId);});graphViewport.addEventListener('pointermove',event=>{if(!drag)return;graphViewport.scrollLeft=drag.left-(event.clientX-drag.x);graphViewport.scrollTop=drag.top-(event.clientY-drag.y);});graphViewport.addEventListener('pointerup',()=>drag=null);graphViewport.addEventListener('pointercancel',()=>drag=null);
341
343
  document.querySelector('#graph-back').addEventListener('click',()=>history.back());
342
344
  document.querySelector('#graph-forward').addEventListener('click',()=>history.forward());
343
345
  window.addEventListener('popstate',()=>{if(graphFromHash()){renderGraphNavigation();showView('graphs');}else 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');});
@@ -101,6 +101,13 @@ export const dashboardStyles = ` <style>
101
101
  .graph-canvas svg { max-width:none; height:auto; }
102
102
  .graph-navigation { display:flex; flex-wrap:wrap; align-items:center; gap:.5rem; margin-bottom:.7rem; }
103
103
  .graph-canvas .node.drillable { cursor:pointer; }
104
+ .graph-dialog { width:96vw; height:94vh; max-width:none; max-height:none; padding:.7rem; border:1px solid var(--border); border-radius:.5rem; background:var(--surface); color:var(--text); }
105
+ .graph-dialog::backdrop { background:#17202b99; }
106
+ .graph-dialog-header { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:.7rem; padding:.3rem .5rem; }
107
+ .graph-dialog-viewport { height:calc(94vh - 5rem); overflow:auto; cursor:grab; background:var(--surface2); border:1px solid var(--border); border-radius:.4rem; }
108
+ .graph-dialog-viewport:active { cursor:grabbing; }
109
+ .graph-dialog-viewport .graph-canvas { display:block; width:max-content; min-width:100%; min-height:100%; overflow:visible; }
110
+ .graph-canvas .node text { pointer-events:none; }
104
111
  .graph-source { margin:0; max-height:22rem; overflow:auto; padding:.7rem; border:1px solid var(--border); border-radius:.4rem; background:var(--surface2); color:var(--muted); font:.72rem/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; white-space:pre; }
105
112
  .graph-source-details summary { cursor:pointer; color:var(--muted); font-size:.74rem; font-weight:700; }
106
113
  .rules-editor { display:grid; gap:.65rem; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "2.0.134",
3
+ "version": "2.0.135",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "repository": {
6
6
  "type": "git",