// Individual credentials stay local. This editor changes only the reviewed policy draft. let agentGrantsDraft=[],agentGrantsPublishedIds=new Set(),agentGrantsEditable=false; function agentGrantStatus(message,error=false){$('agent-grants-status').textContent=message;$('agent-grants-status').className=error?'error':'muted';} function agentGrantField(parent,labelText,tag='input') {const label=document.createElement('label');label.textContent=labelText;const field=document.createElement(tag);label.append(field);parent.append(label);return field;} function changedAgentGrants(){ $('agents-json').value=JSON.stringify(draft().agents,null,2);updateDraftStatus(); } function renderAgentGrants(){ $('agent-identity-mode').disabled=!agentGrantsEditable;$('agent-grant-add').disabled=!agentGrantsEditable||agentGrantsDraft.length>=32; const root=$('agent-grants-editor');root.replaceChildren(); for(const grant of agentGrantsDraft){ const box=document.createElement('fieldset');const legend=document.createElement('legend');legend.textContent=grant.name||'New agent';box.append(legend);const grid=document.createElement('div');grid.className='grid';box.append(grid); const id=agentGrantField(grid,'Stable grant identifier');id.value=grant.id;id.maxLength=80;id.readOnly=agentGrantsPublishedIds.has(grant.id);id.disabled=!agentGrantsEditable;id.addEventListener('input',()=>{grant.id=id.value.trim();changedAgentGrants();}); const name=agentGrantField(grid,'Display name');name.value=grant.name;name.maxLength=100;name.disabled=!agentGrantsEditable;name.addEventListener('input',()=>{grant.name=name.value.trim();legend.textContent=grant.name||'New agent';changedAgentGrants();}); const enabled=agentGrantField(grid,'Enabled');enabled.type='checkbox';enabled.checked=grant.enabled;enabled.disabled=!agentGrantsEditable;enabled.addEventListener('change',()=>{grant.enabled=enabled.checked;changedAgentGrants();}); const expiry=agentGrantField(grid,'Expiry (UTC)');expiry.type='text';expiry.value=grant.expiresAt;expiry.placeholder='2026-12-31T23:59:59Z';expiry.disabled=!agentGrantsEditable;expiry.addEventListener('input',()=>{grant.expiresAt=expiry.value.trim();changedAgentGrants();}); const hosts=agentGrantField(grid,'Additional destination allowlist','textarea');hosts.value=grant.allowedHosts.join('\n');hosts.placeholder='Empty adds no destination restriction';hosts.disabled=!agentGrantsEditable;hosts.addEventListener('input',()=>{grant.allowedHosts=hosts.value.split('\n').map(v=>v.trim()).filter(Boolean);changedAgentGrants();}); const scopeBox=document.createElement('fieldset');const scopeLegend=document.createElement('legend');scopeLegend.textContent='Additional capability limit';scopeBox.append(scopeLegend);const scopes=document.createElement('div');scopes.className='checks';scopeBox.append(scopes);box.append(scopeBox); for(const scope of agentScopes){const label=document.createElement('label');const check=document.createElement('input');check.type='checkbox';check.checked=grant.scopes.includes(scope);check.disabled=!agentGrantsEditable;label.append(check,document.createTextNode(scope));scopes.append(label);check.addEventListener('change',()=>{grant.scopes=agentScopes.filter(s=>s===scope?check.checked:grant.scopes.includes(s));changedAgentGrants();});} const actions=document.createElement('div');actions.className='row';const generation=document.createElement('span');generation.textContent=`Credential generation: ${grant.generation}`;const rotate=document.createElement('button');rotate.type='button';rotate.className='secondary';rotate.textContent='Invalidate previous credentials';rotate.disabled=!agentGrantsEditable||grant.generation>=Number.MAX_SAFE_INTEGER;rotate.addEventListener('click',()=>{grant.generation+=1;renderAgentGrants();changedAgentGrants();agentGrantStatus('Credential generation changed in draft. Publish after review, then pair this identity again locally.');});const revoke=document.createElement('button');revoke.type='button';revoke.className='danger';revoke.textContent='Revoke grant';revoke.disabled=!agentGrantsEditable||!grant.enabled;revoke.addEventListener('click',()=>{grant.enabled=false;renderAgentGrants();changedAgentGrants();agentGrantStatus('Grant disabled in draft. Publish after review to apply revocation.');});actions.append(generation,rotate,revoke); if(!agentGrantsPublishedIds.has(grant.id)){const remove=document.createElement('button');remove.type='button';remove.className='secondary';remove.textContent='Remove unpublished grant';remove.disabled=!agentGrantsEditable;remove.addEventListener('click',()=>{agentGrantsDraft=agentGrantsDraft.filter(g=>g!==grant);renderAgentGrants();changedAgentGrants();});actions.append(remove);} box.append(actions);root.append(box); } } function fillAgentGrants(p){agentGrantsDraft=structuredClone(p.agents.grants||[]);agentGrantsPublishedIds=new Set((publishedSnapshot?.agents?.grants||p.agents.grants||[]).map(g=>g.id));$('agent-identity-mode').value=p.agents.identityMode||'legacy';renderAgentGrants();agentGrantStatus('Agent identity changes require normal signed publication and review.');} const fillBeforeAgentGrants=fill;fill=function(p){fillBeforeAgentGrants(p);fillAgentGrants(p);}; const draftBeforeAgentGrants=draft;draft=function(){const p=draftBeforeAgentGrants();p.agents={...p.agents,identityMode:$('agent-identity-mode').value,grants:structuredClone(agentGrantsDraft)};return p;}; const permissionsBeforeAgentGrants=applyOperatorPermissions;applyOperatorPermissions=function(principal){permissionsBeforeAgentGrants(principal);agentGrantsEditable=!!principal?.permissions?.includes(independentApprovalRequired?'policy.prepare':'policy.write');renderAgentGrants();}; $('agent-identity-mode').addEventListener('change',()=>{changedAgentGrants();agentGrantStatus('Identity mode changed in draft. Individual mode denies existing shared pairing credentials.');}); $('agent-grant-add').addEventListener('click',()=>{if(!agentGrantsEditable||agentGrantsDraft.length>=32)return;agentGrantsDraft.push({id:`agent-${crypto.randomUUID()}`,name:'New agent',enabled:false,expiresAt:new Date(Date.now()+86400000).toISOString(),scopes:[],allowedHosts:[],generation:1});renderAgentGrants();changedAgentGrants();agentGrantStatus('New grant starts disabled with no capabilities. Set expiry and permissions before enabling and publishing.');}); if(policy)fillAgentGrants(policy);if(currentPrincipal)applyOperatorPermissions(currentPrincipal);