Current section

Files

Jump to
erlang_adk src developer adk_dev_ui.erl
Raw

src/developer/adk_dev_ui.erl

%% @doc Dependency-free, Erlang-hosted developer console.
-module(adk_dev_ui).
-export([render/0]).
-spec render() -> {binary(), binary()}.
render() ->
Nonce = base64:encode(crypto:strong_rand_bytes(18)),
Csp = <<"default-src 'none'; base-uri 'none'; form-action 'self'; ",
"frame-ancestors 'none'; connect-src 'self'; img-src 'self' data:; ",
"style-src 'nonce-", Nonce/binary, "'; ",
"script-src 'nonce-", Nonce/binary, "'">>,
{iolist_to_binary(page(Nonce)), Csp}.
page(Nonce) ->
[
<<"<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">",
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">",
"<title>Erlang ADK Developer Console</title><style nonce=\"">>,
Nonce,
<<"\">",
":root{color-scheme:dark;--bg:#081016;--card:#101c24;--line:#29404d;",
"--text:#e8f1f5;--muted:#92a9b4;--accent:#5ee0ae;--danger:#ff758f;",
"--user:#21435a;--agent:#193d32}*{box-sizing:border-box}body{margin:0;",
"font:14px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--bg);",
"color:var(--text)}header{display:flex;align-items:center;justify-content:space-between;",
"padding:16px 22px;border-bottom:1px solid var(--line);position:sticky;top:0;",
"background:#081016ee;backdrop-filter:blur(8px);z-index:2}h1{font-size:17px;margin:0}",
".pill{padding:4px 9px;border:1px solid var(--line);border-radius:99px;color:var(--muted)}",
"main{display:grid;grid-template-columns:minmax(320px,420px) minmax(360px,1fr);",
"gap:14px;padding:14px;max-width:1500px;margin:auto}.stack{display:grid;gap:14px;",
"align-content:start}.card{background:var(--card);border:1px solid var(--line);",
"border-radius:10px;padding:14px}.card h2{font-size:13px;text-transform:uppercase;",
"letter-spacing:.08em;color:var(--muted);margin:0 0 12px}.grid{display:grid;",
"grid-template-columns:1fr 1fr;gap:9px}.wide{grid-column:1/-1}label{display:grid;",
"gap:4px;color:var(--muted);font-size:12px}input,textarea,select,button{font:inherit}",
"input,textarea,select{width:100%;border:1px solid var(--line);background:#09141b;",
"color:var(--text);padding:9px;border-radius:6px}textarea{resize:vertical;min-height:80px}",
"button{border:1px solid #3c6c61;background:#173b32;color:var(--text);",
"padding:8px 11px;border-radius:6px;cursor:pointer}button:hover{border-color:var(--accent)}",
"button.secondary{background:#14232c;border-color:var(--line)}button.danger{background:#3a1821;",
"border-color:#713344}button:disabled{opacity:.45;cursor:not-allowed}.actions{display:flex;",
"gap:8px;flex-wrap:wrap;margin-top:10px}.status{display:flex;gap:8px;align-items:center;",
"color:var(--muted);min-height:24px}.dot{width:8px;height:8px;border-radius:50%;",
"background:#677983}.dot.live{background:var(--accent);box-shadow:0 0 9px var(--accent)}",
".dot.failed{background:var(--danger)}#transcript{display:grid;gap:8px;max-height:45vh;",
"overflow:auto}.message{padding:9px 11px;border-radius:7px;white-space:pre-wrap;",
"word-break:break-word}.message.user{background:var(--user)}.message.agent{background:var(--agent)}",
".message.meta{border:1px dashed var(--line);color:var(--muted)}pre{white-space:pre-wrap;",
"word-break:break-word;margin:0;max-height:42vh;overflow:auto;color:#b8ced8}",
"#approval[hidden]{display:none}.notice{font-size:12px;color:var(--muted);",
"border-left:3px solid #d6a84f;padding-left:9px}.split{display:flex;gap:8px;",
"align-items:end}.split label{flex:1}@media(max-width:850px){main{grid-template-columns:1fr}",
".grid{grid-template-columns:1fr}.wide{grid-column:auto}}</style></head><body>",
"<header><h1>Erlang ADK Developer Console</h1><span class=\"pill\">single-operator local /dev/v1</span>",
"</header><main><section class=\"stack\"><div class=\"card\"><h2>Connection</h2>",
"<p class=\"notice\">Local administrator tooling only. This shared bearer console is not a production or multi-user UI.</p>",
"<div class=\"grid\"><label class=\"wide\">Bearer token",
"<input id=\"token\" type=\"password\" autocomplete=\"off\" spellcheck=\"false\"",
" placeholder=\"Configured local token\"></label><label>Agent",
"<input id=\"agent\" value=\"Assistant\" list=\"agents\"><datalist id=\"agents\"></datalist>",
"</label><label>Application",
"<input id=\"app\" value=\"developer-console\"></label><label>User",
"<input id=\"user\" value=\"developer\"></label><label>Session",
"<input id=\"session\" value=\"local-session\" list=\"sessions\"><datalist id=\"sessions\"></datalist>",
"</label><label class=\"wide\">Message",
"<textarea id=\"message\" placeholder=\"Ask the agent…\"></textarea></label></div>",
"<div class=\"actions\"><button id=\"start\">Start run</button>",
"<button id=\"cancel\" class=\"danger\" disabled>Cancel</button>",
"<button id=\"inspect\" class=\"secondary\">Inspect session</button>",
"<button id=\"discoverAgents\" class=\"secondary\">Discover agents</button>",
"<button id=\"listSessions\" class=\"secondary\">List sessions</button>",
"<button id=\"createSession\" class=\"secondary\">Create/switch session</button>",
"<button id=\"deleteSession\" class=\"danger\">Delete session</button></div></div>",
"<div class=\"card\"><h2>Reconnect</h2><div class=\"split\"><label>Run ID",
"<input id=\"runId\" spellcheck=\"false\" placeholder=\"run-…\"></label>",
"<button id=\"attach\" class=\"secondary\">Attach</button></div>",
"<p class=\"notice\">The run is supervised independently of this browser. Reattach by Run ID;",
" replay is bounded by the server configuration.</p><div class=\"status\"><span id=\"dot\"",
" class=\"dot\"></span><span id=\"status\">idle</span></div></div>",
"<div id=\"approval\" class=\"card\" hidden><h2>Typed pause decision</h2>",
"<pre id=\"approvalData\"></pre><label>Decision payload<textarea id=\"decision\" readonly>",
"{\"confirmed\":true}</textarea></label><div class=\"actions\">",
"<button id=\"approve\" disabled>Confirm and resume</button><button id=\"reject\" class=\"danger\" disabled>",
"Reject and resume</button></div><p class=\"notice\">A decision creates a new supervised run ID;",
" the paused run remains immutable and linked for inspection.</p></div></section><section class=\"stack\">",
"<div class=\"card\"><h2>Chat</h2><div id=\"transcript\"></div></div>",
"<div class=\"card\"><h2>Event trace</h2><pre id=\"trace\">No events yet.</pre></div>",
"<div class=\"card\"><h2>Session snapshot</h2><pre id=\"snapshot\">Not loaded.</pre>",
"<label>State delta (credentials and internal continuation keys are rejected)",
"<textarea id=\"stateDelta\">{}</textarea></label><div class=\"actions\">",
"<button id=\"applyState\" class=\"secondary\">Apply state delta</button></div>",
"</div><div class=\"card\"><h2>Context diagnostics</h2>",
"<p class=\"notice\">Computes bounded counts and a fingerprint; events and state values are not returned.</p>",
"<label>Context cache model<input id=\"contextModel\" value=\"gemini-3.1-flash-lite\"></label>",
"<div class=\"actions\"><button id=\"inspectContext\" class=\"secondary\">Inspect context</button>",
"<button id=\"inspectContextLifecycle\" class=\"secondary\">Inspect lifecycle</button>",
"<button id=\"invalidateContextCache\" class=\"danger\">Invalidate exact cache scope</button></div>",
"<p class=\"notice\">Lifecycle inspection is content-free. Cache invalidation uses the exact app/user/model/policy scope fingerprint and requires confirmation.</p>",
"<pre id=\"contextDiagnostic\">Not loaded.</pre></div>",
"<div class=\"card\"><h2>Artifacts</h2><div class=\"grid\"><label class=\"wide\">Artifact name",
"<input id=\"artifactName\" placeholder=\"reports/result.txt\"></label>",
"<label>Delete selector<input id=\"artifactSelector\" value=\"latest\" placeholder=\"latest, all, or version\"></label></div>",
"<div class=\"actions\"><button id=\"listArtifacts\" class=\"secondary\">List names</button>",
"<button id=\"listArtifactVersions\" class=\"secondary\">List versions</button>",
"<button id=\"deleteArtifact\" class=\"danger\">Delete checked target</button></div>",
"<p class=\"notice\">Artifact data and metadata values are never returned. Deletion requires an exact confirmation.</p>",
"<pre id=\"artifactDiagnostic\">Not loaded.</pre></div>",
"<div class=\"card\"><h2>Memory</h2><div class=\"grid\"><label class=\"wide\">Search query",
"<input id=\"memoryQuery\" placeholder=\"What should be recalled?\"></label>",
"<label>Erase target<select id=\"memoryTarget\"><option value=\"entry\">entry</option>",
"<option value=\"session\">session</option><option value=\"user\">user</option></select></label>",
"<label>Entry/session identifier<input id=\"memoryIdentifier\" placeholder=\"memory ID or session ID\"></label></div>",
"<div class=\"actions\"><button id=\"memoryStatus\" class=\"secondary\">Memory status</button>",
"<button id=\"searchMemory\" class=\"secondary\">Search memory</button>",
"<button id=\"eraseMemory\" class=\"danger\">Erase checked target</button></div>",
"<p class=\"notice\">Search is bounded and redacted. Erasure is scoped to the displayed application and user.</p>",
"<pre id=\"memoryDiagnostic\">Not loaded.</pre></div>",
"<div class=\"card\"><h2>Gemini Live sessions</h2><div class=\"grid\">",
"<label class=\"wide\">Live session ID<input id=\"liveSession\" list=\"liveSessions\" placeholder=\"server-owned session ID\"><datalist id=\"liveSessions\"></datalist></label>",
"<label class=\"wide\">Realtime text<textarea id=\"liveText\" placeholder=\"Send text to the active Live session…\"></textarea></label></div>",
"<div class=\"actions\"><button id=\"listLiveSessions\" class=\"secondary\">List Live sessions</button>",
"<button id=\"connectLive\" class=\"secondary\">Attach ephemeral stream</button>",
"<button id=\"stopLive\" class=\"secondary\">Detach stream</button>",
"<button id=\"sendLiveText\">Send Live text</button></div>",
"<p class=\"notice\">Requires an exact server-configured Live principal. Streams do not replay; raw audio bytes are never rendered.</p>",
"<pre id=\"liveDiagnostic\">Not attached.</pre></div>",
"<div class=\"card\"><h2>Observability</h2>",
"<div class=\"actions\"><button id=\"refreshObservability\" class=\"secondary\">Refresh metrics and export bus</button></div>",
"<p class=\"notice\">This view is metadata-only and bounded; prompts, responses, tool arguments, media, and credentials are excluded.</p>",
"<pre id=\"observabilityDiagnostic\">Not loaded.</pre></div>",
"<div class=\"card\"><h2>Evaluation reports</h2><div class=\"grid\">",
"<label>Render format<select id=\"evalFormat\"><option value=\"json\">JSON</option><option value=\"markdown\">Markdown</option></select></label>",
"<label class=\"wide\">Current result or comparison<textarea id=\"evalCurrent\" placeholder=\"Paste checked evaluation JSON\"></textarea></label>",
"<label class=\"wide\">Baseline result (for comparison)<textarea id=\"evalBaseline\" placeholder=\"Paste baseline evaluation JSON\"></textarea></label></div>",
"<div class=\"actions\"><button id=\"renderEvaluation\" class=\"secondary\">Validate and render</button>",
"<button id=\"compareEvaluation\" class=\"secondary\">Compare baseline</button></div>",
"<p class=\"notice\">The server accepts only canonical saved results/comparisons; this view never loads paths, modules, metrics, or agents.</p>",
"<pre id=\"evaluationDiagnostic\">Not loaded.</pre></div>",
"</section></main><script nonce=\"">>,
Nonce,
<<"\">'use strict';\n",
"const $=id=>document.getElementById(id);",
"const LIMITS=Object.freeze({transcriptEntries:200,traceEntries:500,reconnectAttempts:8,reconnectDelayMs:1000});",
"const state={runId:'',lastId:0,abort:null,retry:null,retryAttempts:0,trace:[],terminalSeen:false,gap:false,pauseType:''};",
"const liveState={abort:null,events:[]};",
"function auth(){const value=$('token').value;if(!value)throw new Error('Bearer token is required');",
"return {'authorization':'Bearer '+value};}",
"function setStatus(text,kind=''){ $('status').textContent=text;$('dot').className='dot '+kind;}",
"function appendMessage(role,text){const node=document.createElement('div');",
"node.className='message '+role;node.textContent=text;$('transcript').appendChild(node);",
"while($('transcript').childElementCount>LIMITS.transcriptEntries)$('transcript').firstElementChild.remove();",
"node.scrollIntoView({block:'nearest'});}",
"function record(kind,data,id){state.trace.push({sequence:id,kind,data});",
"if(state.trace.length>LIMITS.traceEntries)state.trace.splice(0,state.trace.length-LIMITS.traceEntries);",
"$('trace').textContent=JSON.stringify(state.trace,null,2);}",
"async function jsonRequest(path,options={}){const headers={...auth(),...(options.headers||{})};",
"const response=await fetch(path,{...options,headers,credentials:'same-origin'});",
"const body=await response.json().catch(()=>({}));if(!response.ok){",
"throw new Error(body.error?.message||('HTTP '+response.status));}return body;}",
"function runPath(suffix=''){return '/dev/v1/runs/'+encodeURIComponent(state.runId)+suffix;}",
"function resetRunView(){state.lastId=0;state.trace=[];state.terminalSeen=false;state.gap=false;state.pauseType='';state.retryAttempts=0;",
"$('approval').hidden=true;$('approve').disabled=true;$('reject').disabled=true;$('decision').value='';}",
"async function startRun(){try{stopStream();resetRunView();",
"$('trace').textContent='Waiting for events…';$('transcript').textContent='';",
"const payload={agent_name:$('agent').value,app_name:$('app').value,user_id:$('user').value,",
"session_id:$('session').value,message:$('message').value};appendMessage('user',payload.message);",
"const result=await jsonRequest('/dev/v1/runs',{method:'POST',headers:{'content-type':'application/json'},",
"body:JSON.stringify(payload)});state.runId=result.run_id;$('runId').value=state.runId;",
"$('cancel').disabled=false;setStatus('connecting','live');connect();}catch(error){setStatus(error.message,'failed');}}",
"async function cancelRun(){try{await jsonRequest(runPath(),{method:'DELETE'});",
"setStatus('cancelling','live');}catch(error){setStatus(error.message,'failed');}}",
"async function resumeRun(decision){try{if(state.pauseType!=='tool_confirmation'||",
"!decision||typeof decision.confirmed!=='boolean'||Object.keys(decision).length!==1)",
"throw new Error('This pause has no supported typed decision');stopStream();const parent=state.runId;",
"const result=await jsonRequest(runPath('/resume'),{method:'POST',headers:{'content-type':'application/json'},",
"body:JSON.stringify({tool_response:decision})});state.runId=result.run_id;resetRunView();",
"$('runId').value=state.runId;$('cancel').disabled=false;",
"setStatus('resumed '+parent+' as '+state.runId,'live');connect();}",
"catch(error){setStatus(error.message,'failed');}}",
"function stopStream(){if(state.abort){state.abort.abort();state.abort=null;}if(state.retry){",
"clearTimeout(state.retry);state.retry=null;}}",
"async function connect(){stopStream();if(!state.runId)return;const controller=new AbortController();",
"const startId=state.lastId;state.abort=controller;try{const headers={...auth(),'accept':'text/event-stream'};",
"if(state.lastId)headers['last-event-id']=String(state.lastId);",
"const response=await fetch(runPath('/events'),{headers,signal:controller.signal,credentials:'same-origin'});",
"if(!response.ok){const problem=await response.json().catch(()=>({}));const failure=new Error(problem.error?.message||('Event stream HTTP '+response.status));",
"failure.noRetry=['run_event_replay_gap','run_event_cursor_ahead'].includes(problem.error?.code);throw failure;}if(!response.body)",
"throw new Error('Streaming is unavailable');setStatus('streaming '+state.runId,'live');",
"await consume(response.body);if(state.abort===controller)await afterDisconnect(startId);}",
"catch(error){if(error.name!=='AbortError'){setStatus(error.message,'failed');if(!error.noRetry)scheduleReconnect();}}}",
"async function consume(body){const reader=body.getReader(),decoder=new TextDecoder();let buffer='';",
"for(;;){const part=await reader.read();if(part.done)break;buffer+=decoder.decode(part.value,{stream:true});",
"let boundary;while((boundary=buffer.indexOf('\\n\\n'))>=0){const block=buffer.slice(0,boundary);",
"buffer=buffer.slice(boundary+2);handleSse(block);}}}",
"function handleSse(block){if(!block||block.startsWith(':'))return;let kind='message',id=0,data='';",
"for(const line of block.split('\\n')){if(line.startsWith('id:'))id=Number(line.slice(3).trim());",
"else if(line.startsWith('event:'))kind=line.slice(6).trim();",
"else if(line.startsWith('data:'))data+=line.slice(5).trim();}",
"if(id>state.lastId)state.lastId=id;let parsed;try{parsed=JSON.parse(data)}catch{parsed={raw:data}}",
"record(kind,parsed,id);if(kind==='event')renderEvent(parsed);if(kind==='terminal'){state.terminalSeen=true;renderTerminal(parsed);}",
"if(kind==='replay_gap'){state.gap=true;setStatus('event replay gap; start or attach with a current cursor','failed');}}",
"function renderEvent(event){const content=event.content||{};if(content.type==='text'&&",
"event.author!=='user')appendMessage('agent',content.text||'');else if(content.type!=='text')",
"appendMessage('meta',(event.author||'event')+': '+JSON.stringify(content));}",
"function decisionForPause(confirmed){if(state.pauseType==='tool_confirmation')return {confirmed};",
"throw new Error('Unsupported or untyped pause: no decision was sent');}",
"function renderTerminal(outcome){$('cancel').disabled=true;if(outcome.type==='completed')",
"setStatus('completed');else if(outcome.type==='paused'){setStatus('paused');",
"state.pauseType=outcome.event?.actions?.pause?.details?.type||'';",
"$('approval').hidden=false;$('approvalData').textContent=JSON.stringify(outcome.event,null,2);",
"const supported=state.pauseType==='tool_confirmation';$('approve').disabled=!supported;$('reject').disabled=!supported;",
"if(supported)$('decision').value=JSON.stringify(decisionForPause(true),null,2);else{$('decision').value='';",
"setStatus('paused with unsupported or untyped decision contract; resume is disabled','failed');}}",
"else setStatus(outcome.type||'finished',outcome.type==='failed'?'failed':'');}",
"async function afterDisconnect(startId){if(state.terminalSeen||state.gap)return;try{const status=await jsonRequest(runPath());",
"if(status.state!=='running'&&state.lastId===startId){state.terminalSeen=true;renderTerminal(status.outcome||{type:status.state});return;}",
"setStatus('stream boundary reached; reconnecting','live');scheduleReconnect();}",
"catch(error){setStatus(error.message,'failed');}}",
"function scheduleReconnect(){if(state.retry)return;if(state.retryAttempts>=LIMITS.reconnectAttempts){",
"setStatus('reconnect limit reached; use Attach to retry manually','failed');return;}state.retryAttempts+=1;",
"const delay=Math.min(LIMITS.reconnectDelayMs*Math.pow(2,state.retryAttempts-1),10000);",
"state.retry=setTimeout(()=>{state.retry=null;connect();},delay);}",
"async function attach(){const requested=$('runId').value.trim();if(!requested){setStatus('Run ID is required','failed');return;}",
"const preserveCursor=requested===state.runId;stopStream();state.runId=requested;if(!preserveCursor)resetRunView();",
"else{state.terminalSeen=false;state.gap=false;state.pauseType='';state.retryAttempts=0;$('approval').hidden=true;}",
"$('trace').textContent=preserveCursor?'Reconnecting after sequence '+state.lastId+'…':'Replaying available events…';connect();}",
"function sessionCollectionPath(){return '/dev/v1/sessions/'+encodeURIComponent($('app').value)+'/'+",
"encodeURIComponent($('user').value);}",
"async function discoverAgents(){try{const result=await jsonRequest('/dev/v1/agents');",
"$('agents').replaceChildren(...result.agents.map(agent=>{const option=document.createElement('option');",
"option.value=agent.name;return option;}));if(result.agents.length&&!result.agents.some(agent=>agent.name===$('agent').value))",
"$('agent').value=result.agents[0].name;setStatus('discovered '+result.total+' agent(s)');}",
"catch(error){setStatus(error.message,'failed');}}",
"async function listSessions(){try{const result=await jsonRequest(sessionCollectionPath());",
"$('sessions').replaceChildren(...result.sessions.map(session=>{const option=document.createElement('option');",
"option.value=session.id;return option;}));$('snapshot').textContent=JSON.stringify(result,null,2);",
"setStatus('loaded '+result.sessions.length+' of '+result.total+' session(s)');}",
"catch(error){setStatus(error.message,'failed');}}",
"async function createSession(){try{const result=await jsonRequest(sessionCollectionPath(),",
"{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({session_id:$('session').value})});",
"$('snapshot').textContent=JSON.stringify(result,null,2);setStatus('session ready: '+result.id);await listSessions();}",
"catch(error){setStatus(error.message,'failed');}}",
"async function deleteSession(){try{await jsonRequest(sessionCollectionPath()+'/'+encodeURIComponent($('session').value),",
"{method:'DELETE'});$('snapshot').textContent='Session deleted.';setStatus('session deleted');await listSessions();}",
"catch(error){setStatus(error.message,'failed');}}",
"async function applyState(){try{const delta=JSON.parse($('stateDelta').value);",
"const path=sessionCollectionPath()+'/'+encodeURIComponent($('session').value)+'/state';",
"const result=await jsonRequest(path,{method:'POST',headers:{'content-type':'application/json'},",
"body:JSON.stringify({state_delta:delta})});$('snapshot').textContent=JSON.stringify(result,null,2);",
"setStatus('state updated');}catch(error){setStatus(error.message,'failed');}}",
"async function inspect(){try{const path='/dev/v1/sessions/'+encodeURIComponent($('app').value)+'/'+",
"encodeURIComponent($('user').value)+'/'+encodeURIComponent($('session').value);",
"$('snapshot').textContent=JSON.stringify(await jsonRequest(path),null,2);}",
"catch(error){$('snapshot').textContent=error.message;}}",
"function scopedValues(){return {app_name:$('app').value,user_id:$('user').value,session_id:$('session').value};}",
"function artifactBase(){const s=scopedValues();return '/dev/v1/artifacts/'+encodeURIComponent(s.app_name)+'/'+",
"encodeURIComponent(s.user_id)+'/'+encodeURIComponent(s.session_id);}",
"async function inspectContext(){try{const s=scopedValues();const path='/dev/v1/context/'+encodeURIComponent(s.app_name)+'/'+",
"encodeURIComponent(s.user_id)+'/'+encodeURIComponent(s.session_id);$('contextDiagnostic').textContent=",
"JSON.stringify(await jsonRequest(path),null,2);setStatus('context fingerprint loaded');}",
"catch(error){$('contextDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"function contextLifecyclePath(){const s=scopedValues(),model=$('contextModel').value;if(!model)throw new Error('Context cache model is required');",
"return '/dev/v1/context/'+encodeURIComponent(s.app_name)+'/'+encodeURIComponent(s.user_id)+'/'+encodeURIComponent(s.session_id)+",
"'/lifecycle?model='+encodeURIComponent(model);}",
"async function inspectContextLifecycle(){try{const result=await jsonRequest(contextLifecyclePath());",
"$('contextDiagnostic').textContent=JSON.stringify(result,null,2);setStatus('context lifecycle loaded');return result;}",
"catch(error){$('contextDiagnostic').textContent=error.message;setStatus(error.message,'failed');throw error;}}",
"async function invalidateContextCache(){try{const lifecycle=await jsonRequest(contextLifecyclePath()),s=scopedValues(),",
"model=$('contextModel').value,fingerprint=lifecycle.cache?.scope_fingerprint;if(!fingerprint)throw new Error('Exact cache scope is unavailable');",
"const confirmBody={app_name:s.app_name,user_id:s.user_id,session_id:s.session_id,model,scope_fingerprint:fingerprint};",
"if(!window.confirm('Invalidate context cache for '+s.app_name+'/'+s.user_id+' model '+model+' policy '+fingerprint+'?'))return;",
"const path='/dev/v1/context/'+encodeURIComponent(s.app_name)+'/'+encodeURIComponent(s.user_id)+'/'+encodeURIComponent(s.session_id)+",
"'/cache/invalidate';const result=await jsonRequest(path,{method:'POST',headers:{'content-type':'application/json'},",
"body:JSON.stringify({model,confirm:confirmBody})});$('contextDiagnostic').textContent=JSON.stringify(result,null,2);",
"setStatus('exact context cache scope invalidated');}catch(error){$('contextDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"async function listArtifacts(){try{const result=await jsonRequest(artifactBase()+'?limit=100');",
"$('artifactDiagnostic').textContent=JSON.stringify(result,null,2);setStatus('loaded '+result.names.length+' artifact name(s)');}",
"catch(error){$('artifactDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"async function listArtifactVersions(){try{const name=$('artifactName').value;if(!name)throw new Error('Artifact name is required');",
"const result=await jsonRequest(artifactBase()+'/versions?name='+encodeURIComponent(name)+'&limit=100');",
"$('artifactDiagnostic').textContent=JSON.stringify(result,null,2);setStatus('loaded '+result.versions.length+' version(s)');}",
"catch(error){$('artifactDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"function artifactSelector(){const raw=$('artifactSelector').value.trim();if(raw==='all'||raw==='latest')return raw;",
"if(/^[1-9][0-9]*$/.test(raw))return Number(raw);throw new Error('Selector must be latest, all, or a positive version');}",
"async function deleteArtifact(){try{const s=scopedValues(),name=$('artifactName').value,selector=artifactSelector();",
"if(!name)throw new Error('Artifact name is required');const confirmBody={...s,name,selector};",
"if(!window.confirm('Delete '+name+' ('+selector+') in '+s.app_name+'/'+s.user_id+'/'+s.session_id+'?'))return;",
"const result=await jsonRequest(artifactBase()+'/delete',{method:'POST',headers:{'content-type':'application/json'},",
"body:JSON.stringify({name,selector,confirm:confirmBody})});$('artifactDiagnostic').textContent=JSON.stringify(result,null,2);",
"setStatus('artifact deleted');}catch(error){$('artifactDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"function memoryBase(){const s=scopedValues();return '/dev/v1/memory/'+encodeURIComponent(s.app_name)+'/'+encodeURIComponent(s.user_id);}",
"async function memoryStatus(){try{const result=await jsonRequest(memoryBase());$('memoryDiagnostic').textContent=",
"JSON.stringify(result,null,2);setStatus('memory status loaded');}catch(error){$('memoryDiagnostic').textContent=error.message;",
"setStatus(error.message,'failed');}}",
"async function searchMemory(){try{const query=$('memoryQuery').value;if(!query)throw new Error('Memory query is required');",
"const result=await jsonRequest(memoryBase()+'/search',{method:'POST',headers:{'content-type':'application/json'},",
"body:JSON.stringify({query,limit:25})});$('memoryDiagnostic').textContent=JSON.stringify(result,null,2);",
"setStatus('loaded '+result.count+' memory hit(s)');}catch(error){$('memoryDiagnostic').textContent=error.message;",
"setStatus(error.message,'failed');}}",
"async function eraseMemory(){try{const s=scopedValues(),target=$('memoryTarget').value;let identifier=",
"target==='user'?s.user_id:$('memoryIdentifier').value;if(!identifier)throw new Error('Identifier is required');",
"const confirmBody={app_name:s.app_name,user_id:s.user_id,target,identifier};if(!window.confirm('Erase memory '+target+' '+identifier+",
"' for '+s.app_name+'/'+s.user_id+'?'))return;let payload={target,confirm:confirmBody};if(target==='entry')payload.id=identifier;",
"if(target==='session')payload.session_id=identifier;const result=await jsonRequest(memoryBase()+'/erase',{method:'POST',",
"headers:{'content-type':'application/json'},body:JSON.stringify(payload)});$('memoryDiagnostic').textContent=JSON.stringify(result,null,2);",
"setStatus('memory '+target+' erased');}catch(error){$('memoryDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"function livePath(suffix=''){const id=$('liveSession').value.trim();if(!id)throw new Error('Live session ID is required');",
"return '/dev/v1/live/sessions/'+encodeURIComponent(id)+suffix;}",
"async function listLiveSessions(){try{const result=await jsonRequest('/dev/v1/live/sessions');",
"$('liveSessions').replaceChildren(...result.sessions.map(session=>{const option=document.createElement('option');option.value=session.session_id;return option;}));",
"if(result.sessions.length&&!$('liveSession').value)$('liveSession').value=result.sessions[0].session_id;",
"$('liveDiagnostic').textContent=JSON.stringify(result,null,2);setStatus('loaded '+result.total+' Live session(s)');}",
"catch(error){$('liveDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"async function sendLiveText(){try{const text=$('liveText').value;if(!text)throw new Error('Live text is required');",
"const result=await jsonRequest(livePath('/text'),{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({text})});",
"setStatus('Live input '+result.input_sequence+' accepted','live');}catch(error){setStatus(error.message,'failed');}}",
"function stopLiveStream(){if(liveState.abort){liveState.abort.abort();liveState.abort=null;}}",
"async function connectLiveStream(){stopLiveStream();liveState.events=[];$('liveDiagnostic').textContent='Attaching ephemeral stream…';",
"const controller=new AbortController();liveState.abort=controller;try{const response=await fetch(livePath('/events'),",
"{headers:{...auth(),accept:'text/event-stream'},signal:controller.signal,credentials:'same-origin'});",
"if(!response.ok){const problem=await response.json().catch(()=>({}));throw new Error(problem.error?.message||('Live stream HTTP '+response.status));}",
"if(!response.body)throw new Error('Live streaming is unavailable');setStatus('Live stream attached','live');await consumeLive(response.body);}",
"catch(error){if(error.name!=='AbortError'){setStatus(error.message,'failed');$('liveDiagnostic').textContent=error.message;}}",
"finally{if(liveState.abort===controller)liveState.abort=null;}}",
"async function consumeLive(body){const reader=body.getReader(),decoder=new TextDecoder();let buffer='';for(;;){const part=await reader.read();",
"if(part.done)break;buffer+=decoder.decode(part.value,{stream:true});let boundary;while((boundary=buffer.indexOf('\\n\\n'))>=0){",
"const block=buffer.slice(0,boundary);buffer=buffer.slice(boundary+2);handleLiveSse(block);}}}",
"function handleLiveSse(block){if(!block||block.startsWith(':'))return;let kind='message',id=0,data='';for(const line of block.split('\\n')){",
"if(line.startsWith('id:'))id=Number(line.slice(3).trim());else if(line.startsWith('event:'))kind=line.slice(6).trim();",
"else if(line.startsWith('data:'))data+=line.slice(5).trim();}let parsed;try{parsed=JSON.parse(data)}catch{parsed={raw:data}}",
"liveState.events.push({sequence:id,kind,data:parsed});if(liveState.events.length>LIMITS.traceEntries)liveState.events.splice(0,liveState.events.length-LIMITS.traceEntries);",
"$('liveDiagnostic').textContent=JSON.stringify(liveState.events,null,2);}",
"async function refreshObservability(){try{$('observabilityDiagnostic').textContent=JSON.stringify(await jsonRequest('/dev/v1/observability'),null,2);",
"setStatus('observability snapshot loaded');}catch(error){$('observabilityDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"function parsedEvaluation(id){const text=$(id).value.trim();if(!text)throw new Error('Evaluation JSON is required');const value=JSON.parse(text);",
"if(!value||Array.isArray(value)||typeof value!=='object')throw new Error('Evaluation JSON must be an object');return value;}",
"async function renderEvaluation(){try{const payload={report:parsedEvaluation('evalCurrent'),format:$('evalFormat').value};",
"const response=await fetch('/dev/v1/evaluation/render',{method:'POST',headers:{...auth(),'content-type':'application/json'},",
"body:JSON.stringify(payload),credentials:'same-origin'});const text=await response.text();if(!response.ok){let problem={};try{problem=JSON.parse(text)}catch{}",
"throw new Error(problem.error?.message||('Evaluation render HTTP '+response.status));}$('evaluationDiagnostic').textContent=text;",
"setStatus('evaluation report validated and rendered');}catch(error){$('evaluationDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"async function compareEvaluation(){try{const result=await jsonRequest('/dev/v1/evaluation/compare',{method:'POST',",
"headers:{'content-type':'application/json'},body:JSON.stringify({baseline:parsedEvaluation('evalBaseline'),current:parsedEvaluation('evalCurrent'),options:{}})});",
"$('evaluationDiagnostic').textContent=JSON.stringify(result,null,2);setStatus(result.passed?'evaluation baseline passed':'evaluation regression detected',result.passed?'':'failed');}",
"catch(error){$('evaluationDiagnostic').textContent=error.message;setStatus(error.message,'failed');}}",
"function submitPauseDecision(confirmed){try{const value=decisionForPause(confirmed);",
"$('decision').value=JSON.stringify(value,null,2);resumeRun(value);}catch(error){setStatus(error.message,'failed');}}",
"$('approve').onclick=()=>submitPauseDecision(true);",
"$('reject').onclick=()=>submitPauseDecision(false);",
"$('start').onclick=startRun;$('cancel').onclick=cancelRun;$('attach').onclick=attach;",
"$('inspect').onclick=inspect;$('discoverAgents').onclick=discoverAgents;",
"$('listSessions').onclick=listSessions;$('createSession').onclick=createSession;",
"$('deleteSession').onclick=deleteSession;$('applyState').onclick=applyState;",
"$('inspectContext').onclick=inspectContext;$('inspectContextLifecycle').onclick=inspectContextLifecycle;",
"$('invalidateContextCache').onclick=invalidateContextCache;$('listArtifacts').onclick=listArtifacts;",
"$('listArtifactVersions').onclick=listArtifactVersions;$('deleteArtifact').onclick=deleteArtifact;",
"$('memoryStatus').onclick=memoryStatus;$('searchMemory').onclick=searchMemory;",
"$('eraseMemory').onclick=eraseMemory;$('listLiveSessions').onclick=listLiveSessions;",
"$('sendLiveText').onclick=sendLiveText;$('connectLive').onclick=connectLiveStream;",
"$('stopLive').onclick=stopLiveStream;$('refreshObservability').onclick=refreshObservability;",
"$('renderEvaluation').onclick=renderEvaluation;$('compareEvaluation').onclick=compareEvaluation;",
"window.addEventListener('beforeunload',()=>{stopStream();stopLiveStream();});",
"</script></body></html>">>
].