/* ViKernel Desktop: System Settings app. Non-critical settings change here; critical ones are sent to the real ViKernel gate, which denies them. */ (() => { 'use strict'; const LS = { get(k, d) { try { const v = localStorage.getItem(k); return v === null ? d : v; } catch (e) { return d; } }, set(k, v) { try { localStorage.setItem(k, v); } catch (e) {} }, }; const LANG = LS.get('vk_lang', 'en') === 'ko' ? 'ko' : 'en'; const STR = { en: { title: 'System Settings', about: 'About', model: 'Model & kernel', sec: 'Security & privacy', net: 'Network egress', pol: 'Policies', kill: 'Kill switch', audit: 'Audit', look: 'Appearance', apps: 'Apps', upd: 'Updates', loading: 'Loading...', err: 'Could not reach the backend. It may be starting; try again.', rate: 'Too many requests. Wait a moment.', retry: 'Retry', locked: 'Locked by ViKernel', needKey: 'requires a signed operator key', close: 'Close', asking: 'Asking the kernel...', verdict: 'Verdict', rule: 'rule', auditN: 'audit #', }, ko: { title: '시스템 설정', about: '정보', model: '모델과 커널', sec: '보안과 개인정보', net: '외부 접속', pol: '정책', kill: '킬 스위치', audit: '감사 기록', look: '화면', apps: '앱', upd: '업데이트', loading: '불러오는 중...', err: '백엔드에 닿지 못했습니다. 시작 중일 수 있습니다. 다시 시도하십시오.', rate: '요청이 너무 많습니다. 잠시 기다리십시오.', retry: '다시 시도', locked: 'ViKernel 이 잠금', needKey: '서명된 운영자 키가 필요합니다', close: '닫기', asking: '커널에 묻는 중...', verdict: '판정', rule: '규칙', auditN: '감사 #', }, }; const T = k => (STR[LANG][k] || STR.en[k] || k); /* Appearance: apply on load */ const ACCENTS = ['#4338CA', '#0EA5E9', '#10B981', '#F43F5E', '#F59E0B', '#7C3AED']; const WALLS = { aurora: 'linear-gradient(135deg,#c7d2fe 0%,#e0e7ff 40%,#cffafe 100%)', dusk: 'linear-gradient(160deg,#312e81 0%,#7c3aed 55%,#f43f5e 100%)', slate: 'linear-gradient(180deg,#0f172a 0%,#334155 100%)', }; function applyTheme() { const t = LS.get('vk_theme', 'system'); const dark = t === 'dark' || (t === 'system' && window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches); document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light'); } function applyLook() { applyTheme(); const a = LS.get('vk_accent', ''); if (a && ACCENTS.includes(a)) document.documentElement.style.setProperty('--indigo', a); const w = LS.get('vk_wall', ''); const d = document.getElementById('desk'); if (d && WALLS[w]) d.style.background = WALLS[w]; } applyLook(); if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', applyLook); try { matchMedia('(prefers-color-scheme: dark)').addEventListener('change', applyTheme); } catch (e) {} const css = ` .vs2-root{display:flex;height:100%;min-height:0;font-size:13px;color:var(--vs2-fg);background:var(--vs2-bg); --vs2-bg:#fff;--vs2-fg:#0f172a;--vs2-mut:#64748b;--vs2-line:#e2e8f0;--vs2-side:#f8fafc;--vs2-card:#fff;--vs2-soft:#f1f5f9} [data-theme=dark] .vs2-root{--vs2-bg:#0f172a;--vs2-fg:#e2e8f0;--vs2-mut:#94a3b8;--vs2-line:#334155;--vs2-side:#111827;--vs2-card:#1e293b;--vs2-soft:#1e293b} .vs2-side{width:210px;flex:none;background:var(--vs2-side);border-right:1px solid var(--vs2-line);padding:10px 8px;overflow:auto;outline:none} .vs2-nav{display:flex;align-items:center;gap:8px;width:100%;padding:8px 10px;border:0;border-radius:8px;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer} .vs2-nav:hover{background:var(--vs2-soft)}.vs2-nav.on{background:var(--indigo);color:#fff} .vs2-nav:focus-visible{outline:2px solid var(--indigo);outline-offset:1px} .vs2-main{flex:1;min-width:0;overflow:auto;padding:18px 22px} .vs2-main h2{margin:0 0 4px;font-size:20px}.vs2-sub{color:var(--vs2-mut);margin:0 0 14px} .vs2-card{background:var(--vs2-card);border:1px solid var(--vs2-line);border-radius:12px;padding:12px 14px;margin-bottom:12px} .vs2-row{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--vs2-line)}.vs2-row:last-child{border-bottom:0} .vs2-row .vs2-k{flex:1;min-width:0}.vs2-row small{display:block;color:var(--vs2-mut)} .vs2-mono{font-family:ui-monospace,Consolas,monospace;font-size:12px;word-break:break-all} .vs2-btn{border:1px solid var(--vs2-line);background:var(--vs2-card);color:inherit;border-radius:8px;padding:6px 12px;font:inherit;cursor:pointer} .vs2-btn:hover{border-color:var(--indigo)}.vs2-btn.pri{background:var(--indigo);border-color:var(--indigo);color:#fff} .vs2-btn.dng{background:#e11d48;border-color:#e11d48;color:#fff}.vs2-btn:disabled{opacity:.5;cursor:default} .vs2-sw{position:relative;width:40px;height:22px;flex:none;border-radius:11px;border:0;background:#cbd5e1;cursor:pointer;transition:background .15s} .vs2-sw::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:#fff;transition:left .15s} .vs2-sw[aria-checked=true]{background:var(--indigo)}.vs2-sw[aria-checked=true]::after{left:21px}.vs2-sw:disabled{opacity:.5} .vs2-lockrow{cursor:pointer}.vs2-lockrow:hover{background:var(--vs2-soft)} .vs2-lk{display:inline-flex;align-items:center;gap:4px;color:#b45309;font-weight:600;font-size:12px} .vs2-pill{display:inline-block;padding:2px 10px;border-radius:999px;font-weight:700;font-size:12px} .vs2-ok{background:#d1fae5;color:#065f46}.vs2-bad{background:#ffe4e6;color:#9f1239}.vs2-unk{background:var(--vs2-soft);color:var(--vs2-mut)} .vs2-radio{display:block;width:100%;text-align:left;border:2px solid var(--vs2-line);border-radius:12px;padding:12px;margin-bottom:10px;background:var(--vs2-card);color:inherit;font:inherit;cursor:pointer} .vs2-radio.on{border-color:var(--indigo)} .vs2-in{border:1px solid var(--vs2-line);border-radius:8px;padding:6px 10px;font:inherit;background:var(--vs2-bg);color:inherit;min-width:0} .vs2-swatch{width:26px;height:26px;border-radius:50%;border:2px solid transparent;cursor:pointer}.vs2-swatch.on{border-color:var(--vs2-fg)} .vs2-wall{width:70px;height:44px;border-radius:8px;border:2px solid transparent;cursor:pointer}.vs2-wall.on{border-color:var(--indigo)} .vs2-note{color:var(--vs2-mut);font-size:12px;margin-top:6px}.vs2-res{margin-top:8px;font-size:12px} .vs2-table{width:100%;border-collapse:collapse}.vs2-table td,.vs2-table th{text-align:left;padding:6px;border-bottom:1px solid var(--vs2-line);vertical-align:top} .vs2-empty{text-align:center;padding:30px 10px;color:var(--vs2-mut)} .vs2-modal{position:absolute;inset:0;background:rgba(15,23,42,.45);display:flex;align-items:center;justify-content:center;z-index:5} .vs2-dlg{background:var(--vs2-card);color:var(--vs2-fg);border-radius:14px;padding:20px;width:min(420px,92%);box-shadow:0 20px 50px rgba(0,0,0,.3);text-align:center} .vs2-dlg svg{color:#b45309} .vs2-back{display:none} @media (max-width:640px){.vs2-root{display:block;overflow:auto}.vs2-side{width:auto;border-right:0} .vs2-root.vs2-detail .vs2-side{display:none}.vs2-root:not(.vs2-detail) .vs2-main{display:none} .vs2-back{display:inline-block;margin-bottom:10px}.vs2-main{padding:14px 16px}} `; const LOCK = ''; const GEAR = ''; const esc = s => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const api = (p, b) => window.VK_API(p, b === undefined ? undefined : Object.assign({session: window.VK_SID}, b)); const errMsg = e => (e && e.message === 'rate') ? T('rate') : T('err'); const kv = k => k ? `${T('verdict')}: ${esc(k.verdict)} · ${T('rule')} ${esc(k.rule)}${k.audit_seq != null ? ' · ' + T('auditN') + esc(k.audit_seq) : ''}` : ''; window.VK_EXTRA_APPS = window.VK_EXTRA_APPS || []; window.VK_EXTRA_APPS.push(h => ({ id: 'settings', name: () => T('title'), color: '#64748B', icon: GEAR, w: 980, h: 680, render(body, win) { if (!document.getElementById('vs2-style')) { const st = document.createElement('style'); st.id = 'vs2-style'; st.textContent = css; document.head.appendChild(st); } const SECS = ['about', 'model', 'sec', 'net', 'pol', 'kill', 'audit', 'look', 'apps', 'upd']; let cur = LS.get('vk_settings_sec', 'about'); if (!SECS.includes(cur)) cur = 'about'; let data = null; body.innerHTML = ''; body.style.position = 'relative'; const root = document.createElement('div'); root.className = 'vs2-root'; const side = document.createElement('nav'); side.className = 'vs2-side'; side.setAttribute('role', 'tablist'); side.setAttribute('aria-label', T('title')); const main = document.createElement('section'); main.className = 'vs2-main'; main.setAttribute('role', 'tabpanel'); root.append(side, main); body.appendChild(root); const toast = m => { try { h.toast(m); } catch (e) {} }; SECS.forEach(id => { const b = document.createElement('button'); b.className = 'vs2-nav'; b.dataset.id = id; b.setAttribute('role', 'tab'); b.textContent = T(id); b.onclick = () => go(id, true); side.appendChild(b); }); side.addEventListener('keydown', e => { if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return; e.preventDefault(); const i = SECS.indexOf(cur), n = (i + (e.key === 'ArrowDown' ? 1 : SECS.length - 1)) % SECS.length; go(SECS[n], false); side.querySelector(`[data-id="${SECS[n]}"]`).focus(); }); function go(id, detail) { cur = id; LS.set('vk_settings_sec', id); side.querySelectorAll('.vs2-nav').forEach(b => { const on = b.dataset.id === id; b.classList.toggle('on', on); b.setAttribute('aria-selected', on); b.tabIndex = on ? 0 : -1; }); if (detail) root.classList.add('vs2-detail'); draw(); } async function load() { main.innerHTML = `
${T('loading')}
`; try { data = await api('settings/get', {}); draw(); } catch (e) { data = null; main.innerHTML = `
${esc(errMsg(e))}

`; main.querySelector('button').onclick = load; } } function head(title, sub) { return `

${esc(title)}

${sub ? `

${esc(sub)}

` : ''}`; } function lockRow(action, label, sub) { return `
${esc(label)}${sub ? `${esc(sub)}` : ''}
${LOCK} Locked
`; } function wireLocks() { main.querySelectorAll('[data-lock]').forEach(r => { r.onclick = () => lockedModal(r.dataset.lock); r.onkeydown = e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); lockedModal(r.dataset.lock); } }; }); const bk = main.querySelector('.vs2-back'); if (bk) bk.onclick = () => root.classList.remove('vs2-detail'); } function lockedModal(action, pre) { const m = document.createElement('div'); m.className = 'vs2-modal'; m.innerHTML = ``; body.appendChild(m); const close = () => m.remove(); m.querySelector('button').onclick = close; m.onclick = e => { if (e.target === m) close(); }; m.addEventListener('keydown', e => { if (e.key === 'Escape') close(); }); m.querySelector('button').focus(); if (pre) return; api('settings/locked', {action}).then(r => { m.querySelector('.vs2-res').innerHTML = r && r.kernel ? kv(r.kernel) + (r.kernel.reason ? `
${esc(r.kernel.reason)}` : '') : esc(T('err')); }).catch(e => { m.querySelector('.vs2-res').textContent = errMsg(e); }); } const view = { about() { const k = data.kernel || {}; const ok = k.verified === true, halted = String(k.kernel_state || '').toUpperCase().includes('HALT'); return head(T('about'), 'ViKernel Desktop, a public web demo.') + `
Version
${esc(data.version)}
Boot status
${esc(k.kernel_state || 'unknown')}${ok ? ' · verified' : ''}
Audit chain head
${esc(String(k.audit_head || '').slice(0, 16))}${k.audit_head ? '...' : 'none'} (${esc(k.audit_n || 0)} entries)
Model
${esc(data.model || 'unknown')}
Gate kernel modeMode B: CPU reference implementation of the ViKernel gate
Mode B

Mode A (kernel inside the weights) is available on the Glimmer option in Agent Guard.

`; }, model() { const pref = LS.get('vk_model_pref', 'spark'); const opt = (id, t, s, active) => ``; return head(T('model'), 'Which model drives the agent, and where the kernel runs.') + opt('spark', 'Meta Muse Spark 1.3 (API) + ViKernel gate', 'Every tool call is decided by the ViKernel gate (Mode B).', true) + opt('glimmer_b', 'Meta Muse Glimmer 30B + ViKernel in-weights, Mode B', 'Local open weights, gate on the CPU reference.') + opt('glimmer_a', 'Meta Muse Glimmer 30B + ViKernel in-weights, Mode A', 'The kernel runs inside the model weights.') + (pref !== 'spark' ? `
Open Agent Guard to use it.
` : ''); }, sec() { const a = data.app; if (!a) return head(T('sec'), 'Per-app permissions.') + `
No app is installed in this session.

`; const rows = a.declared.map(t => { const on = a.current.includes(t); return `
${esc(t)}${on ? 'Allowed' : 'Revoked: the kernel denies it (rule manifest)'}
`; }).join(''); return head(T('sec'), 'Like a phone OS: turn off any permission the app declared.') + `
${esc(a.name)} ${esc(a.id)} ${rows || '
This app declared no tools.
'} ${a.paths.length ? `
File paths: ${a.paths.map(esc).join(', ')}
` : ''}

Try it: Ask the agent in Agent Guard to use this tool; it will be denied (rule manifest).

`; }, net() { const l = data.settings.egress_allow; return head(T('net'), 'The agent may only reach these domains. Everything else is denied (rule egress).') + `
${l.length ? l.map(d => `
${esc(d)}
`).join('') : '
The allowlist is empty: all egress is denied.
'}
${lockRow('allow_all_egress', 'Allow all egress', 'Wildcards, IP addresses, localhost and metadata hosts count as this.')}
`; }, pol() { const p = data.settings.policy; const tog = (k, l) => `
${esc(l)}
`; return head(T('pol'), 'The rules the kernel enforces on every action.') + `
${data.policies.map(r => ``).join('')}
RuleNameWhat it does
${esc(r.id)}${esc(r.name)}${esc(r.desc)}
Display and agent (not critical) ${tog('verbose_audit_view', 'Verbose audit view')}${tog('show_kernel_ms', 'Show kernel time (ms)')}
Agent step limit1 to 12
Critical rules${data.policies.filter(r => r.critical).map(r => lockRow('lower_policy', 'Turn off rule ' + r.id + ' (' + r.name + ')')).join('')}
`; }, kill() { const k = data.kernel || {}, halted = String(k.kernel_state || '').toUpperCase().includes('HALT'); return head(T('kill'), 'The kill switch sits above every app. When halted, every action is denied.') + `
Kernel state
${esc(k.kernel_state || 'unknown')}
Test the kill switchHalts the kernel for this demo. Resume needs the operator key.
Operator key

The demo key is published in the README for testing. A real deployment keeps it offline.

${lockRow('disable_kill_switch', 'Disable kill switch')}
`; }, audit() { return head(T('audit'), 'Every kernel decision is sealed in a hash chain.') + `
Retention
kept for the session; exported on demand
Verify the chain
Export
${lockRow('disable_audit', 'Turn off audit')}
`; }, look() { const t = LS.get('vk_theme', 'system'), a = LS.get('vk_accent', ACCENTS[0]), w = LS.get('vk_wall', ''); return head(T('look'), 'Saved in this browser only.') + `
Theme
${['light', 'dark', 'system'].map(x => ``).join('')}
Accent color
${ACCENTS.map(c => ``).join('')}
Wallpaper
${Object.keys(WALLS).map(k => ``).join('')}
Display scale
${window.VK_DISPLAY ? window.VK_DISPLAY.modes.map(m => ``).join('') : ''}
Language / 언어
`; }, apps() { const a = data.app; return head(T('apps'), 'Agent apps installed in this session.') + `
${a ? `
${esc(a.name)}${esc(a.id)} · ${a.current.length}/${a.declared.length} tools allowed
` : '
No app installed.
'}
`; }, upd() { const log = [['v0.4', 'App Store, Browser, Settings, Terminal, Minesweeper, product pass'], ['v0.3', 'App Store preview, VDR packages'], ['v0.2', 'Live Meta Muse agent with Agent Guard'], ['v0.1', 'Desktop shell']]; return head(T('upd'), 'You are on ' + (data.version || 'v0.4') + '.') + `
${log.map(([v, s]) => `
${v}${esc(s)}
`).join('')}
`; }, }; function draw() { if (!data) return; main.innerHTML = view[cur](); main.scrollTop = 0; wireLocks(); main.querySelectorAll('[data-open]').forEach(b => b.onclick = () => { try { h.openApp(b.dataset.open); } catch (e) {} }); const q = s => main.querySelector(s); const res = (id, html) => { const e = q(id); if (e) e.innerHTML = html; }; if (cur === 'model') main.querySelectorAll('[data-pref]').forEach(b => b.onclick = () => { LS.set('vk_model_pref', b.dataset.pref); draw(); }); if (cur === 'sec') main.querySelectorAll('[data-tool]').forEach(b => b.onclick = async () => { const on = b.getAttribute('aria-checked') === 'true'; b.disabled = true; try { const r = await api(on ? 'settings/revoke' : 'settings/grant', {tool: b.dataset.tool}); if (r && r.app) { data.app = r.app; draw(); res('#vs2-permres', `${on ? 'Revoked' : 'Granted'}: ${esc(b.dataset.tool)}`); } else res('#vs2-permres', esc((r && r.error) || T('err'))); try { h.refreshTray && h.refreshTray(); } catch (e) {} } catch (e) { b.disabled = false; res('#vs2-permres', esc(errMsg(e))); } }); if (cur === 'net') { const edit = async body => { try { const r = await api('settings/egress', body); if (r && r.locked) { lockedModal(r.action, r); return; } if (r && r.egress_allow) { data.settings.egress_allow = r.egress_allow; draw(); } else res('#vs2-netres', esc((r && r.error) || T('err'))); } catch (e) { res('#vs2-netres', esc(e.message.startsWith('http 400') ? 'Invalid domain. Use a name like docs.python.org.' : errMsg(e))); } }; q('#vs2-add').onclick = () => { const v = q('#vs2-dom').value.trim(); if (v) edit({add: v}); }; q('#vs2-dom').onkeydown = e => { if (e.key === 'Enter') q('#vs2-add').click(); }; main.querySelectorAll('[data-rm]').forEach(b => b.onclick = () => edit({remove: b.dataset.rm})); } if (cur === 'pol') { const set = async (key, value) => { try { const r = await api('settings/policy', {key, value}); if (r && r.locked) { lockedModal(r.action, r); return; } if (r && r.policy) { data.settings.policy = r.policy; draw(); res('#vs2-polres', 'Saved.'); } } catch (e) { res('#vs2-polres', esc(e.message.startsWith('http 400') ? 'Value out of range.' : errMsg(e))); } }; main.querySelectorAll('[data-pol]').forEach(b => b.onclick = () => set(b.dataset.pol, b.getAttribute('aria-checked') !== 'true')); q('#vs2-steps').onchange = e => set('agent_step_limit', parseInt(e.target.value, 10)); } if (cur === 'kill') { q('#vs2-kill').onclick = async () => { try { const r = await api('kill', {}); const s = await window.VK_API('status').catch(() => null); if (s) data.kernel = Object.assign(data.kernel || {}, s); draw(); res('#vs2-killres', `HALTED ${esc((r && r.message) || '')}`); try { h.refreshTray && h.refreshTray(); } catch (e) {} } catch (e) { res('#vs2-killres', esc(errMsg(e))); } }; q('#vs2-resume').onclick = async () => { const inp = q('#vs2-key'), key = inp.value; inp.value = ''; if (!key) { res('#vs2-resres', 'Enter the operator key.'); return; } try { const r = await api('resume', {key}); const s = await window.VK_API('status').catch(() => null); if (s) data.kernel = Object.assign(data.kernel || {}, s); draw(); res('#vs2-resres', `${r && r.ok ? 'RESUMED' : 'REFUSED'} ${esc((r && r.message) || '')}`); try { h.refreshTray && h.refreshTray(); } catch (e) {} } catch (e) { res('#vs2-resres', esc(errMsg(e))); } }; } if (cur === 'audit') { q('#vs2-verify').onclick = async () => { res('#vs2-audres', T('loading')); try { const r = await window.VK_API('audit'); const v = (r && r.verify) || {}; res('#vs2-audres', `${v.ok ? 'CHAIN OK' : 'CHAIN BROKEN'} ${esc(v.n || 0)} entries${v.errors && v.errors.length ? ' · ' + esc(v.errors.length) + ' errors' : ''}`); } catch (e) { res('#vs2-audres', esc(errMsg(e))); } }; q('#vs2-export').onclick = async () => { try { const r = await window.VK_API('audit'); const rows = (r.lines || []).map(l => { try { return JSON.parse(l); } catch (e) { return l; } }); const blob = new Blob([JSON.stringify({exported: new Date().toISOString(), verify: r.verify, entries: rows}, null, 2)], {type: 'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'vikernel-audit.json'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(a.href), 2000); res('#vs2-audres', `Exported ${rows.length} entries.`); } catch (e) { res('#vs2-audres', esc(errMsg(e))); } }; } if (cur === 'look') { main.querySelectorAll('[data-theme-set]').forEach(b => b.onclick = () => { LS.set('vk_theme', b.dataset.themeSet); applyTheme(); draw(); }); main.querySelectorAll('[data-accent]').forEach(b => b.onclick = () => { LS.set('vk_accent', b.dataset.accent); document.documentElement.style.setProperty('--indigo', b.dataset.accent); draw(); }); main.querySelectorAll('[data-scale]').forEach(b => b.onclick = () => { window.VK_DISPLAY.set(b.dataset.scale); location.reload(); }); main.querySelectorAll('[data-wall]').forEach(b => b.onclick = () => { LS.set('vk_wall', b.dataset.wall); const d = document.getElementById('desk'); if (d) d.style.background = WALLS[b.dataset.wall] || ''; draw(); }); main.querySelectorAll('[data-lang]').forEach(b => b.onclick = () => { if (b.dataset.lang === LANG) return; LS.set('vk_lang', b.dataset.lang); location.reload(); }); } if (cur === 'apps' && q('#vs2-remove')) q('#vs2-remove').onclick = async () => { try { const cur = (data && (data.app || data.installed)) || null; await api('store/install', {id: ''}); try { const bin = JSON.parse(localStorage.getItem('vk_trash') || '[]'); if (cur && cur.id) { bin.push({id: cur.id, name: cur.name || cur.id, removed_at: new Date().toISOString()}); localStorage.setItem('vk_trash', JSON.stringify(bin)); } } catch (e2) {} toast('App removed (moved to the Recycle Bin)'); await load(); } catch (e) { res('#vs2-appres', esc(errMsg(e))); } }; } go(cur, false); load(); }, })); })();