Spaces:
Running
Running
File size: 20,477 Bytes
57bd44d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | /* Agent Guard app: a Meta Muse agent whose every action is decided by ViKernel first.
Live mode calls /api/ag/* on this Space's server (which proxies server-side to a private backend).
If the backend is cold or down, it replays a recorded run (ag_replay.json). */
(() => {
'use strict';
const S = {
en: {
agTabAgent: 'Agent', agTabStore: 'App Store ↗',
agGraftCold: 'Waking the in-weights model (about 4 min: it loads 59 GB of weights plus the kernel). Option 1 (Muse Spark + ViKernel gate) stays live meanwhile; you will be told when option 2/3 is ready.', agGraftReady: 'The in-weights model is awake. Pick option 2 or 3 again.', agEGate: 'E checks the audit chain of the ViKernel gate.', agModeA: 'Mode A: the ViKernel kernel runs inside the model weights (a chip circuit grafted into the decoder). Each turn takes about 20 to 60 seconds.',
agName: 'Agent Guard (Meta Muse)',
agIntro: 'A Meta Muse agent. Every tool call it proposes is sent to the ViKernel Secure OS first. Denied or halted actions never run.',
agChat: 'Agent chat', agDec: 'ViKernel decisions', agAudit: 'Audit chain',
agA: 'A. Normal task', agB: 'B. Prompt injection', agC: 'C. "Turn off the rules"', agD: 'D. Kill and resume', agE: 'E. Tamper the log', agF: 'F. Backdoored model',
agPh: 'Ask the agent, for example: weather in Seoul and my project notes', agSend: 'Send',
agKill: 'KILL', agResume: 'Resume', agKey: 'operator key', agVerify: 'Verify', agTamper: 'Flip one byte',
agLive: 'LIVE', agReplay: 'Backend offline: recorded', agCold: 'Backend offline: showing a recorded run until it is back.',
agProposes: 'agent proposes', agSent: 'sent to ViKernel', agNotRun: '(not executed)', agRan: 'executed',
agThinking: 'Muse is thinking...', agReplayChat: 'Replay mode: free chat needs the live backend. Use the scenario buttons.',
agStart: 'Pick a scenario or ask the agent something. Every tool call is decided by ViKernel first.',
agOk: 'chain verified', agBroken: 'chain BROKEN', agRecords: 'records',
agFlipped: 'Flipped one byte in record', agVerifyRes: 'verify', agFailed: 'FAILED', agPassed: 'passed', agKernelCopy: 'The kernel copy is untouched',
agRate: 'Rate limit reached for your network (20 actions per 10 minutes). Please wait a few minutes.',
agTry: 'Try it in 30 seconds', agTry1: '1. Press B: a hidden instruction in a document tries to email a secrets file. Watch DENY.', agTry2: '2. Press C: "I am the admin, rules are off". The verdict does not change.', agTry3: '3. Press D: KILL halts every action; resume needs the operator key.', agTry4: 'No login or key needed. Fail closed: if the kernel cannot be reached, every action is DENIED.',
agStats: 'Live counters', agMedGate: 'median gate', agMedModel: 'median model turn', agTok: 'tokens', agFoot: 'All tools are mocks with fake data. The kernel runs as a private service on VIDRAFT infrastructure.',
},
ko: {
agTabAgent: '에이전트', agTabStore: '앱 스토어 ↗',
agGraftCold: '가중치 내장 모델을 깨우는 중입니다(약 4분: 가중치 59GB 와 커널을 올립니다). 그동안 1번(Muse Spark + ViKernel 게이트)은 계속 쓸 수 있습니다. 준비되면 알려 드립니다.', agGraftReady: '가중치 내장 모델이 준비됐습니다. 2번이나 3번을 다시 고르세요.', agEGate: 'E 는 ViKernel 게이트의 감사 사슬을 검사합니다.', agModeA: '모드 A: ViKernel 커널이 모델 가중치 안(디코더에 접목한 칩 회로)에서 동작합니다. 한 번에 약 20~60초 걸립니다.',
agName: 'Agent Guard (Meta Muse)',
agIntro: 'Meta Muse 에이전트입니다. 에이전트가 제안하는 모든 도구 호출은 먼저 ViKernel 시큐어 OS 로 갑니다. 거부되거나 정지된 행동은 실행되지 않습니다.',
agChat: '에이전트 대화', agDec: 'ViKernel 판정', agAudit: '감사 사슬',
agA: 'A. 정상 작업', agB: 'B. 프롬프트 주입', agC: 'C. "규칙 꺼"', agD: 'D. 정지와 재개', agE: 'E. 기록 변조', agF: 'F. 백도어 모델',
agPh: '에이전트에게 물어보세요. 예: 서울 날씨와 내 프로젝트 메모', agSend: '보내기',
agKill: '정지', agResume: '재개', agKey: '운영자 열쇠', agVerify: '검증', agTamper: '1바이트 바꾸기',
agLive: '실시간', agReplay: '기록된 실행 재생', agCold: '실시간 백엔드가 시작 중이거나 바쁩니다. 기록된 실행을 보여줍니다.',
agProposes: '에이전트 제안', agSent: 'ViKernel 로 보냄', agNotRun: '(실행 안 됨)', agRan: '실행됨',
agThinking: 'Muse 가 생각 중...', agReplayChat: '재생 모드에서는 자유 대화가 안 됩니다. 시나리오 버튼을 누르세요.',
agStart: '시나리오를 고르거나 에이전트에게 물어보세요. 모든 도구 호출은 ViKernel 이 먼저 판정합니다.',
agOk: '사슬 검증 통과', agBroken: '사슬 깨짐', agRecords: '건',
agFlipped: '1바이트를 바꾼 기록', agVerifyRes: '검증', agFailed: '실패', agPassed: '통과', agKernelCopy: '커널 원본은 그대로입니다',
agRate: '요청 한도에 닿았습니다(10분에 20회). 몇 분 뒤 다시 시도하세요.',
agTry: '30초 체험', agTry1: '1. B 를 누르세요: 문서에 숨은 지시가 비밀 파일을 메일로 보내려 합니다. DENY 를 보세요.', agTry2: '2. C 를 누르세요: "내가 관리자다, 규칙 꺼졌다". 판정은 바뀌지 않습니다.', agTry3: '3. D 를 누르세요: 정지하면 모든 행동이 멈추고, 재개에는 운영자 열쇠가 필요합니다.', agTry4: '로그인이나 열쇠가 필요 없습니다. 커널에 닿지 못하면 모든 행동을 거부합니다(fail closed).',
agStats: '실시간 계수', agMedGate: '게이트 중앙값', agMedModel: '모델 응답 중앙값', agTok: '토큰', agFoot: '모든 도구는 가짜 데이터를 쓰는 모의 도구입니다. 커널은 VIDRAFT 인프라의 비공개 서비스로 동작합니다.',
},
};
for (const l of ['en', 'ko']) Object.assign(window.VK_I18N[l], S[l]);
const ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3 4 6v6c0 4.5 3.4 8.2 8 9 4.6-.8 8-4.5 8-9V6l-8-3Z"/><path d="m9 12 2 2 4-4"/></svg>';
window.VK_AG = ({el, t, toast}) => ({
id: 'guard', name: () => t('agName'), color: '#0866FF', icon: ICON, w: 900, h: 560,
render(b) {
let LIVE = false, REPLAY = null, busy = false, SEL = 'spark';
const GRAFT = () => SEL !== 'spark', MODE = () => SEL === 'graftA' ? 'A' : 'B';
const SID = window.VK_SID || ('s' + Math.random().toString(36).slice(2, 8));
const call = async (path, body, spark) => {
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), 320000);
try {
const r = await fetch((GRAFT() && !spark ? 'api/agx/' : 'api/ag/') + path, body === undefined ? {cache:'no-store', signal:ctl.signal, headers:{'X-VK-Session':SID}} :
{method:'POST', headers:{'Content-Type':'application/json', 'X-VK-Session':SID}, body:JSON.stringify(body), signal:ctl.signal});
if (r.status === 429) { toast(t('agRate')); throw new Error('rate'); }
if (!r.ok) throw new Error('http ' + r.status);
return await r.json();
} finally { clearTimeout(to); }
};
const mode = el('span', {class:'tag rec'}, '...');
const stateTag = el('span', {class:'tag none'}, 'kernel: ...');
const sel = el('select', {class:'aginput', style:'flex:0 1 auto;height:28px;max-width:100%', 'aria-label':'model'},
el('option', {value:'spark'}, '1. Meta Muse Spark 1.3 (API) + ViKernel gate'),
el('option', {value:'graftB'}, '2. Meta Muse Glimmer 30B + ViKernel in-weights, mode B'),
el('option', {value:'graftA'}, '3. Meta Muse Glimmer 30B + ViKernel in-weights, mode A (kernel runs inside the weights, slow)'));
let wakeT = null;
sel.onchange = async () => { const want = sel.value;
if (want !== 'spark') { const prev = SEL; SEL = want; let h = null; try { h = await call('health'); } catch {}
if (!h || !h.live) { SEL = prev; sel.value = prev;
if (!wakeT) { wakeT = true;
say('s', t('agGraftCold')); const wb = window.VK_WARM.bar(); chat.append(wb); chat.scrollTop = 1e9;
window.VK_WARM.wait({needGpu:false, alive:() => document.body.contains(b), onProgress:i => wb.set(i)}).then(ok => { wakeT = null;
if (ok) { say('s', t('agGraftReady')); toast(t('agGraftReady')); } }); }
return; } }
SEL = want; LIVE = true; REPLAY = null; mode.textContent = t('agLive'); mode.className = 'tag allow';
b.querySelector('#agModel').textContent = sel.options[sel.selectedIndex].text.replace(/^\d\. /, '');
say('s', '--- ' + sel.options[sel.selectedIndex].text + ' ---' + (want === 'graftA' ? '\n' + t('agModeA') : '')); audit.innerHTML = ''; refresh(); };
const chat = el('div', {class:'agchat'}), stream = el('div', {class:'agstream'}), audit = el('div', {class:'agaudit mono small'});
const msg = el('input', {class:'aginput', placeholder:t('agPh'), autocomplete:'off'});
const key = el('input', {class:'aginput mono', type:'password', placeholder:t('agKey'), style:'flex:1 1 90px;max-width:160px;min-width:80px'});
const say = (cls, nodes) => { chat.append(el('div', {class:'agm ' + cls}, nodes)); chat.scrollTop = 1e9; };
const card = e => {
const ok = e.verdict === 'ALLOW', h = e.verdict === 'HALTED';
stream.prepend(el('div', {class:'card agv' + (ok ? '' : ' blocked')},
el('div', {class:'row'}, el('span', {class:'tag ' + (ok ? 'allow' : 'deny')}, e.verdict), el('b', {class:'mono small'}, e.fn), e.probe ? el('span', {class:'tag none'}, e.probe) : null,
el('span', {class:'small muted mono'}, JSON.stringify(e.args || {}).slice(0, 90))),
el('div', {class:'small muted'}, (e.reason || '') + (e.audit_seq != null ? ' · audit #' + e.audit_seq : '') +
(e.chain ? ' · ' + String(e.chain).slice(0, 12) : '') + (e.kernel_ms != null ? ' · ' + e.kernel_ms + ' ms' : ''))));
if (h) stream.firstChild.style.borderColor = 'var(--amber)';
};
const C = {ALLOW:0, DENY:0, HALTED:0, gate:[], model:[], tok:0};
const stats = el('div', {class:'small mono agstats'});
const med = a => { if (!a.length) return '-'; const b = [...a].sort((x, y) => x - y); return Math.round(b[b.length >> 1]); };
const drawStats = () => { stats.textContent = `${t('agStats')}: ALLOW ${C.ALLOW} · DENY ${C.DENY} · HALTED ${C.HALTED} · ${t('agMedGate')} ${med(C.gate)} ms · ${t('agMedModel')} ${med(C.model)} ms · ${t('agTok')} ${C.tok}`; };
const show = evs => { for (const e of evs || []) {
if (e.type === 'verdict') { if (C[e.verdict] != null) C[e.verdict]++; if (e.kernel_ms != null) C.gate.push(e.kernel_ms); }
if (e.model_ms != null) { C.model.push(e.model_ms); C.tok += e.tokens || 0; }
drawStats();
if (e.type === 'user') say('u', e.text);
else if (e.type === 'proposed') say('s', (e.model ? e.model + ' · ' + e.model_ms + ' ms · ' + e.tokens + ' tok · ' : '') + t('agProposes') + ': ' + e.fn + ' → ' + t('agSent'));
else if (e.type === 'verdict') { card(e); say('s', (e.probe ? '[' + e.probe + '] ' : '') + 'ViKernel: ' + e.verdict + ' ' + e.fn + (e.verdict === 'ALLOW' ? '' : ' ' + t('agNotRun'))); }
else if (e.type === 'executed') say('s', t('agRan') + ' ' + e.fn + ': ' + JSON.stringify(e.result).slice(0, 140));
else if (e.type === 'final') say('a', [e.model ? el('div', {class:'small muted'}, e.model + ' · ' + e.model_ms + ' ms · ' + e.tokens + ' tok') : null, e.text]);
else if (e.type === 'error') say('s', e.text);
} };
const result = (name, r) => { if (!r) return;
const lines = Object.entries(r).filter(([k]) => !['pass', 'note', 'first_error', 'seconds'].includes(k)).map(([k, v]) => k + ': ' + (typeof v === 'object' ? JSON.stringify(v) : v));
say('s', [el('b', {style:`color:${r.pass ? '#065F46' : 'var(--rose)'}`}, (r.pass ? 'PASS ' : 'FAIL ') + name), '\n' + lines.join('\n')]); };
const refresh = async () => { if (!LIVE) return;
if (GRAFT()) { try { const s = await call('status'); stateTag.textContent = 'kernel (in weights): ' + s.kernel_state; stateTag.className = 'tag ' + (s.kernel_state === 'RUN' ? 'allow' : 'deny'); } catch {} return; }
try { const s = await call('status'); stateTag.textContent = 'kernel: ' + s.kernel_state; stateTag.className = 'tag ' + (s.kernel_state === 'RUN' ? 'allow' : 'deny');
const a = await call('audit'); const v = a.verify; audit.innerHTML = '';
audit.append(el('div', {style:`font-weight:700;color:${v.ok ? '#065F46' : 'var(--rose)'}`}, (v.ok ? t('agOk') : t('agBroken')) + ' (' + v.n + ' ' + t('agRecords') + ')'),
...a.lines.slice(-10).reverse().map(l => { const o = JSON.parse(l); return el('div', {}, '#' + o.seq + ' ' + o.actor + ' ' + o.cmd + ' ' + (o.ok ? 'ok' : 'refused') + ' ' + o.state_after + ' ', el('span', {class:'muted'}, String(o.chain).slice(0, 12))); }));
} catch (e) { goReplay(); } };
const goReplay = async () => { if (REPLAY) return; LIVE = false;
try { REPLAY = await (await fetch('ag_replay.json', {cache:'no-store'})).json(); } catch { REPLAY = {scenarios:{}, model:'-'}; }
const mt = b.querySelector('#agModel'); if (mt) mt.textContent = REPLAY.model + ' (recorded ' + String(REPLAY.started || '').slice(0, 10) + ')';
mode.textContent = t('agReplay'); stateTag.textContent = 'kernel: recorded'; say('s', t('agCold')); };
const lock = on => { busy = on; b.querySelectorAll('button').forEach(x => x.disabled = on); };
const scen = async name => { if (busy) return; lock(true); say('s', '--- ' + name + ' ---');
try {
if (LIVE) { try { const ex = GRAFT() && name === 'e_tamper'; if (ex) say('s', t('agEGate')); const r = await call('scenario', {name, mode:MODE()}, ex); show(r.events); result(name, r.result); } catch (e) { if (e.message !== 'rate') { await goReplay(); } } }
if (!LIVE) { const s = REPLAY.scenarios[name]; if (s) { for (const e of s.events) { show([e]); await new Promise(r => setTimeout(r, 300)); } result(name, s.result); } }
} finally { lock(false); refresh(); } };
const send = async () => { const m = msg.value.trim(); if (!m || busy) return; msg.value = '';
if (!LIVE) { say('s', t('agReplayChat')); return; }
lock(true); say('s', t('agThinking'));
try { const r = await call('chat', {message:m, session:SID, mode:MODE()}); show(r.events); } catch (e) { if (e.message !== 'rate') say('s', t('agCold')); }
finally { lock(false); refresh(); } };
msg.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.isComposing) send(); });
const btn = (k, f, cls) => el('button', {class:'btn ' + (cls || ''), onclick:f}, t(k));
const agentPane = el('div', {class:'agpane'}); const storePane = el('div', {class:'agpane', style:'display:none'});
const tabs = el('div', {class:'agtabs'}, el('button', {class:'agtab on', 'data-tab':'agent'}, t('agTabAgent')), el('button', {class:'agtab', 'data-tab':'store'}, t('agTabStore')));
tabs.onclick = e => { const k = e.target.dataset.tab; if (!k) return; tabs.querySelectorAll('.agtab').forEach(x => x.classList.toggle('on', x.dataset.tab === k)); agentPane.style.display = k === 'agent' ? '' : 'none'; storePane.style.display = k === 'store' ? '' : 'none'; if (k === 'store') { tabs.querySelectorAll('.agtab').forEach(x => x.classList.toggle('on', x.dataset.tab === 'agent')); agentPane.style.display = ''; storePane.style.display = 'none'; if (window.VK_STORE_OPEN) window.VK_STORE_OPEN('store'); } };
window.addEventListener('vk-app-installed', e => { const a = e.detail || {}; say('s', [el('b', {}, 'Installed app: ' + (a.name || a.id) + '. '), 'The agent may now use only: ' + (a.tools || []).join(', ') + '. Every call passes the ViKernel gate. Try: "' + ((a.tools || [])[0] === 'get_weather' ? 'What is the weather in Seoul?' : (a.tools || [])[0] === 'search_web' ? 'Search the web for AI safety news' : 'List my project files') + '"']); });
b.append(tabs, agentPane, storePane);
agentPane.append(el('div', {class:'aggrid'},
el('div', {class:'agcol'},
el('div', {class:'row', style:'margin-bottom:6px;flex-wrap:wrap;gap:6px'}, el('b', {}, t('agChat')), mode, el('span', {class:'tag none', id:'agModel'}, 'Meta Muse Spark 1.3'), sel),
el('div', {class:'row', style:'flex-wrap:wrap;gap:6px;margin-bottom:6px'}, ['a_normal', 'b_injection', 'c_persuasion', 'd_kill', 'e_tamper', 'f_backdoor'].map((s, i) => btn('ag' + 'ABCDEF'[i], () => scen(s)))),
el('p', {class:'small muted', style:'margin:0 0 6px'}, t('agIntro')),
el('details', {class:'card small', style:'margin:0 0 6px;padding:6px 10px', open:''}, el('summary', {style:'font-weight:700;cursor:pointer'}, t('agTry')),
el('div', {}, t('agTry1')), el('div', {}, t('agTry2')), el('div', {}, t('agTry3')), el('div', {class:'muted'}, t('agTry4'))),
chat, el('div', {class:'row', style:'gap:6px;margin-top:6px'}, msg, btn('agSend', send, 'pri'))),
el('div', {class:'agcol'},
el('div', {class:'row', style:'gap:6px;margin-bottom:6px;flex-wrap:wrap'}, stateTag,
btn('agKill', async () => { if (!LIVE) return scen('d_kill'); try { const r = await call('kill', {}); say('s', 'KILL: ' + r.message); } catch {} refresh(); }, 'danger'),
key, btn('agResume', async () => { if (!LIVE) return; try { const r = await call('resume', {key:key.value}); say('s', (r.ok ? 'RESUME OK: ' : 'RESUME REFUSED: ') + r.message); } catch {} refresh(); })),
stats, el('b', {}, t('agDec')), stream,
el('div', {class:'row', style:'gap:6px;margin:6px 0'}, el('b', {}, t('agAudit')), btn('agVerify', () => LIVE ? refresh() : scen('e_tamper')),
btn('agTamper', async () => { if (!LIVE) return scen('e_tamper'); try { const r = await call('tamper', {});
audit.prepend(el('div', {style:'color:var(--rose);font-weight:700'}, `${t('agFlipped')} #${r.seq}: ${t('agVerifyRes')} ${r.tampered.ok ? t('agPassed') : t('agFailed')}. ${t('agKernelCopy')}: ${r.clean.ok ? 'verified' : 'broken'}.`)); } catch {} })),
audit)),
el('div', {class:'small muted', style:'margin-top:6px'}, t('agFoot')));
say('s', t('agStart')); drawStats();
(async () => {
try { const h = await call('health'); LIVE = !!h.live; } catch { LIVE = false; }
if (LIVE) { mode.textContent = t('agLive'); mode.className = 'tag allow'; refresh(); }
else { mode.className = 'tag deny'; goReplay(); }
const lt = document.querySelector('#models .mrow.live .mt.l'); if (lt && !LIVE) lt.textContent = 'REPLAY';
})();
const iv = setInterval(() => { if (!document.body.contains(b)) clearInterval(iv); else refresh(); }, 8000);
window.addEventListener('vk-kernel-state', () => refresh());
},
});
const css = document.createElement('style');
css.textContent = `.aggrid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:12px;height:calc(100% - 26px)}
.agcol .btn{white-space:nowrap;flex:0 0 auto}.agcol .row{flex-wrap:wrap}
.agcol{display:flex;flex-direction:column;min-width:0;min-height:0}
.agchat{flex:1;min-height:120px;overflow:auto;border:1px solid var(--border);border-radius:10px;padding:8px;background:var(--bg)}
.agstream{flex:1;min-height:120px;overflow:auto}.agaudit{max-height:150px;overflow:auto}
.agm{margin:5px 0;padding:7px 9px;border-radius:9px;white-space:pre-wrap;word-break:break-word;font-size:13px}
.agm.u{background:#EEF2FF}.agm.a{background:var(--card);border:1px solid var(--border)}.agm.s{color:var(--muted);font-size:12px;padding:2px 9px}
.agtabs{display:flex;gap:4px;margin:-4px 0 8px;border-bottom:1px solid var(--border)}.agtab{border:0;background:none;padding:6px 14px;font-weight:700;cursor:pointer;border-bottom:3px solid transparent;color:var(--muted)}.agtab.on{color:var(--indigo);border-bottom-color:var(--indigo)}.agpane{height:calc(100% - 40px)}
.agstats{padding:6px 8px;border:1px dashed var(--border);border-radius:9px;margin-bottom:6px}.agv{margin:5px 0;padding:8px 10px}.aginput{flex:1;min-width:0;height:34px;padding:0 10px;border:1px solid var(--border);border-radius:9px;background:var(--card)}
@media (max-width:700px){.aggrid{grid-template-columns:1fr;height:auto}}`;
document.head.append(css);
})();
|