// 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 scopeHelp=document.createElement('p');scopeHelp.className='muted';scopeHelp.textContent='Session saving, listing and restoration require separate capabilities. Saved URLs and tab group metadata never include passwords, form values or page state. Restoration rechecks current policy and does not restore revoked agent authority. History reads and navigation require separate capabilities. Window controls are limited to supported browser operations. Vault status, masked listing and exact-origin autofill require separate capabilities and human unlock. Preparing a controlled login POST requires its separate capability, approved form policy and human unlock; preparation does not submit the form. Agents cannot unlock the vault or export passwords. Visual export requires its separate screenshot capability in the central baseline and this grant, and is limited to an inspected foreground webpage viewport, never the desktop or browser interface. All granted capabilities remain restricted by the central baseline, destination rules and DLP.';scopeBox.append(scopeHelp);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);