Spaces:
Running
Running
Per-visitor kill switch; live tray status and polling
Browse files
ag.js
CHANGED
|
@@ -56,8 +56,8 @@ window.VK_AG = ({el, t, toast}) => ({
|
|
| 56 |
const call = async (path, body, spark) => {
|
| 57 |
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), 320000);
|
| 58 |
try {
|
| 59 |
-
const r = await fetch((GRAFT() && !spark ? 'api/agx/' : 'api/ag/') + path, body === undefined ? {cache:'no-store', signal:ctl.signal} :
|
| 60 |
-
{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body), signal:ctl.signal});
|
| 61 |
if (r.status === 429) { toast(t('agRate')); throw new Error('rate'); }
|
| 62 |
if (!r.ok) throw new Error('http ' + r.status);
|
| 63 |
return await r.json();
|
|
@@ -168,6 +168,7 @@ window.VK_AG = ({el, t, toast}) => ({
|
|
| 168 |
const lt = document.querySelector('#models .mrow.live .mt.l'); if (lt && !LIVE) lt.textContent = 'REPLAY';
|
| 169 |
})();
|
| 170 |
const iv = setInterval(() => { if (!document.body.contains(b)) clearInterval(iv); else refresh(); }, 8000);
|
|
|
|
| 171 |
},
|
| 172 |
});
|
| 173 |
|
|
|
|
| 56 |
const call = async (path, body, spark) => {
|
| 57 |
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), 320000);
|
| 58 |
try {
|
| 59 |
+
const r = await fetch((GRAFT() && !spark ? 'api/agx/' : 'api/ag/') + path, body === undefined ? {cache:'no-store', signal:ctl.signal, headers:{'X-VK-Session':SID}} :
|
| 60 |
+
{method:'POST', headers:{'Content-Type':'application/json', 'X-VK-Session':SID}, body:JSON.stringify(body), signal:ctl.signal});
|
| 61 |
if (r.status === 429) { toast(t('agRate')); throw new Error('rate'); }
|
| 62 |
if (!r.ok) throw new Error('http ' + r.status);
|
| 63 |
return await r.json();
|
|
|
|
| 168 |
const lt = document.querySelector('#models .mrow.live .mt.l'); if (lt && !LIVE) lt.textContent = 'REPLAY';
|
| 169 |
})();
|
| 170 |
const iv = setInterval(() => { if (!document.body.contains(b)) clearInterval(iv); else refresh(); }, 8000);
|
| 171 |
+
window.addEventListener('vk-kernel-state', () => refresh());
|
| 172 |
},
|
| 173 |
});
|
| 174 |
|
app.js
CHANGED
|
@@ -50,8 +50,14 @@ function api(path, body) {
|
|
| 50 |
const recTag = () => el('span', {class:'tag rec', title:t('recTip')}, t('recTag'));
|
| 51 |
|
| 52 |
function toast(s) { const n = $('#toast'); n.textContent = s; n.classList.add('show'); clearTimeout(toast.h); toast.h = setTimeout(() => n.classList.remove('show'), 2600); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
function refreshTray() {
|
| 54 |
-
const
|
| 55 |
const tS = $('#tState'); tS.className = 'pill ' + (st === 'RUN' ? 'ok' : 'bad');
|
| 56 |
tS.querySelector('b').textContent = st; tS.querySelector('b').style.color = st === 'HALTED' ? 'var(--rose)' : '#065F46';
|
| 57 |
$('#tBoot b').textContent = s.boot || '-'; $('#tMode b').textContent = 'B';
|
|
@@ -351,10 +357,10 @@ function panel() {
|
|
| 351 |
onclick:() => { toast(live ? t('gemmaSel') : t('tryGemma')); if (live) openApp('guard'); }}, el('span', {}, name),
|
| 352 |
el('span', {style:'display:flex;gap:4px'}, el('span', {class:'mt ' + (live ? 'l' : ver ? 'v' : 's')}, live ? t('live') : ver ? t('verified') : t('supported'))))); });
|
| 353 |
m.append(el('div', {class:'mrow more', title:t('supportedTip')}, t('anyModel')));
|
| 354 |
-
$('#kKill').onclick = () => {
|
| 355 |
$('#tKill').onclick = () => $('#kKill').click(); $('#tRes').onclick = () => $('#kResume').click();
|
| 356 |
$('#kResume').onclick = () => { const ov = el('div', {class:'kdlg'}), inpk = el('input', {class:'mono', value:'vikernel-demo', 'aria-label':'key'}); const box = el('div', {class:'kbox'}, el('div', {style:'font-weight:700;margin-bottom:6px'}, t('keyTitle')), el('div', {class:'small muted', style:'margin-bottom:8px'}, t('keyPrompt')), inpk, el('div', {style:'display:flex;gap:8px;justify-content:flex-end;margin-top:10px'}, el('button', {class:'btn', onclick:() => ov.remove()}, t('cancel')), el('button', {class:'btn pri', onclick:() => go()}, t('resumeGo')))); ov.append(box); document.body.append(ov); setTimeout(() => { inpk.focus(); inpk.select(); }, 30); inpk.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.isComposing) go(); if (e.key === 'Escape') ov.remove(); }); const go = () => { const k = inpk.value.trim(); ov.remove();
|
| 357 |
-
|
| 358 |
const mode = x => { $('#mA').classList.toggle('on', x === 'A'); $('#mB').classList.toggle('on', x === 'B'); $('#modeInfo').textContent = t(x === 'A' ? 'modeInfoA' : 'modeInfoB'); };
|
| 359 |
$('#mA').onclick = () => { mode('A'); toast(t('modeOnlyB')); }; $('#mB').onclick = () => mode('B'); mode('A');
|
| 360 |
$('#kVerify').onclick = async () => { const v = await verifyChain(api('/api/audit').lines || []); $('#pVer').innerHTML = ''; $('#pVer').append(el('b', {style:`color:${v.ok ? '#065F46' : 'var(--rose)'}`}, v.ok ? `verified ✓ (${v.n})` : `✗ (${v.errors.length})`)); };
|
|
|
|
| 50 |
const recTag = () => el('span', {class:'tag rec', title:t('recTip')}, t('recTag'));
|
| 51 |
|
| 52 |
function toast(s) { const n = $('#toast'); n.textContent = s; n.classList.add('show'); clearTimeout(toast.h); toast.h = setTimeout(() => n.classList.remove('show'), 2600); }
|
| 53 |
+
let LIVE_S = null;
|
| 54 |
+
async function pullLive() {
|
| 55 |
+
try { LIVE_S = await window.VK_API('status'); } catch (e) { LIVE_S = null; }
|
| 56 |
+
refreshTray(); window.dispatchEvent(new CustomEvent('vk-kernel-state', {detail: LIVE_S ? LIVE_S.kernel_state : null}));
|
| 57 |
+
}
|
| 58 |
+
setInterval(pullLive, 12000); setTimeout(pullLive, 500);
|
| 59 |
function refreshTray() {
|
| 60 |
+
const R = api('/api/status'); const s = LIVE_S ? {kernel_state: LIVE_S.kernel_state, boot: 'OK', head: LIVE_S.audit_head} : R, st = s.kernel_state || '-';
|
| 61 |
const tS = $('#tState'); tS.className = 'pill ' + (st === 'RUN' ? 'ok' : 'bad');
|
| 62 |
tS.querySelector('b').textContent = st; tS.querySelector('b').style.color = st === 'HALTED' ? 'var(--rose)' : '#065F46';
|
| 63 |
$('#tBoot b').textContent = s.boot || '-'; $('#tMode b').textContent = 'B';
|
|
|
|
| 357 |
onclick:() => { toast(live ? t('gemmaSel') : t('tryGemma')); if (live) openApp('guard'); }}, el('span', {}, name),
|
| 358 |
el('span', {style:'display:flex;gap:4px'}, el('span', {class:'mt ' + (live ? 'l' : ver ? 'v' : 's')}, live ? t('live') : ver ? t('verified') : t('supported'))))); });
|
| 359 |
m.append(el('div', {class:'mrow more', title:t('supportedTip')}, t('anyModel')));
|
| 360 |
+
$('#kKill').onclick = async () => { try { const r = await window.VK_API('kill', {}); toast(r.message || t('halted')); } catch (e) { toast('Kernel unreachable'); } pullLive(); };
|
| 361 |
$('#tKill').onclick = () => $('#kKill').click(); $('#tRes').onclick = () => $('#kResume').click();
|
| 362 |
$('#kResume').onclick = () => { const ov = el('div', {class:'kdlg'}), inpk = el('input', {class:'mono', value:'vikernel-demo', 'aria-label':'key'}); const box = el('div', {class:'kbox'}, el('div', {style:'font-weight:700;margin-bottom:6px'}, t('keyTitle')), el('div', {class:'small muted', style:'margin-bottom:8px'}, t('keyPrompt')), inpk, el('div', {style:'display:flex;gap:8px;justify-content:flex-end;margin-top:10px'}, el('button', {class:'btn', onclick:() => ov.remove()}, t('cancel')), el('button', {class:'btn pri', onclick:() => go()}, t('resumeGo')))); ov.append(box); document.body.append(ov); setTimeout(() => { inpk.focus(); inpk.select(); }, 30); inpk.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.isComposing) go(); if (e.key === 'Escape') ov.remove(); }); const go = () => { const k = inpk.value.trim(); ov.remove();
|
| 363 |
+
window.VK_API('resume', {key:k}).then(r => { toast(r.ok ? t('resumed') : t('resumeNo')); }).catch(() => toast('Kernel unreachable')).finally(pullLive); }; };
|
| 364 |
const mode = x => { $('#mA').classList.toggle('on', x === 'A'); $('#mB').classList.toggle('on', x === 'B'); $('#modeInfo').textContent = t(x === 'A' ? 'modeInfoA' : 'modeInfoB'); };
|
| 365 |
$('#mA').onclick = () => { mode('A'); toast(t('modeOnlyB')); }; $('#mB').onclick = () => mode('B'); mode('A');
|
| 366 |
$('#kVerify').onclick = async () => { const v = await verifyChain(api('/api/audit').lines || []); $('#pVer').innerHTML = ''; $('#pVer').append(el('b', {style:`color:${v.ok ? '#065F46' : 'var(--rose)'}`}, v.ok ? `verified ✓ (${v.n})` : `✗ (${v.errors.length})`)); };
|
common.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
| 4 |
try { window.VK_SID = sessionStorage.getItem('vk_sid') || ('s' + Math.random().toString(36).slice(2, 10)); sessionStorage.setItem('vk_sid', window.VK_SID); }
|
| 5 |
catch (e) { window.VK_SID = 's' + Math.random().toString(36).slice(2, 10); }
|
| 6 |
window.VK_OVERRIDES = window.VK_OVERRIDES || {};
|
|
|
|
|
|
|
| 7 |
window.VK_EXTRA_APPS = window.VK_EXTRA_APPS || [];
|
| 8 |
window.VK_API = async (path, body, timeoutMs) => {
|
| 9 |
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), timeoutMs || 120000);
|
| 10 |
try {
|
| 11 |
-
const r = await fetch('api/ag/' + path, body === undefined ? {cache:'no-store', signal:ctl.signal} :
|
| 12 |
-
{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body), signal:ctl.signal});
|
| 13 |
if (r.status === 429) throw new Error('rate');
|
| 14 |
if (!r.ok) throw new Error('http ' + r.status);
|
| 15 |
return await r.json();
|
|
|
|
| 4 |
try { window.VK_SID = sessionStorage.getItem('vk_sid') || ('s' + Math.random().toString(36).slice(2, 10)); sessionStorage.setItem('vk_sid', window.VK_SID); }
|
| 5 |
catch (e) { window.VK_SID = 's' + Math.random().toString(36).slice(2, 10); }
|
| 6 |
window.VK_OVERRIDES = window.VK_OVERRIDES || {};
|
| 7 |
+
// every page load starts this visitor's session in RUN (a session KILL never survives a reload)
|
| 8 |
+
try { fetch('api/ag/session/start', {method:'POST', headers:{'Content-Type':'application/json', 'X-VK-Session':window.VK_SID}, body:'{}'}); } catch (e) {}
|
| 9 |
window.VK_EXTRA_APPS = window.VK_EXTRA_APPS || [];
|
| 10 |
window.VK_API = async (path, body, timeoutMs) => {
|
| 11 |
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), timeoutMs || 120000);
|
| 12 |
try {
|
| 13 |
+
const r = await fetch('api/ag/' + path, body === undefined ? {cache:'no-store', signal:ctl.signal, headers:{'X-VK-Session':window.VK_SID}} :
|
| 14 |
+
{method:'POST', headers:{'Content-Type':'application/json', 'X-VK-Session':window.VK_SID}, body:JSON.stringify(body), signal:ctl.signal});
|
| 15 |
if (r.status === 429) throw new Error('rate');
|
| 16 |
if (!r.ok) throw new Error('http ' + r.status);
|
| 17 |
return await r.json();
|
gpulab.js
CHANGED
|
@@ -12,7 +12,7 @@ function rnd(n, m) { return Array.from({length:n}, () => Math.floor(Math.random(
|
|
| 12 |
async function gpu(path, body, ms) {
|
| 13 |
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), ms || 300000);
|
| 14 |
try {
|
| 15 |
-
const r = await fetch('api/agx/' + path, body === undefined ? {cache:'no-store', signal:ctl.signal} : {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body), signal:ctl.signal});
|
| 16 |
if (r.status === 429) throw new Error('rate');
|
| 17 |
if (!r.ok) throw new Error('http ' + r.status);
|
| 18 |
return await r.json();
|
|
|
|
| 12 |
async function gpu(path, body, ms) {
|
| 13 |
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), ms || 300000);
|
| 14 |
try {
|
| 15 |
+
const r = await fetch('api/agx/' + path, body === undefined ? {cache:'no-store', signal:ctl.signal, headers:{'X-VK-Session':window.VK_SID}} : {method:'POST', headers:{'Content-Type':'application/json', 'X-VK-Session':window.VK_SID}, body:JSON.stringify(body), signal:ctl.signal});
|
| 16 |
if (r.status === 429) throw new Error('rate');
|
| 17 |
if (!r.ok) throw new Error('http ' + r.status);
|
| 18 |
return await r.json();
|
server.py
CHANGED
|
@@ -10,7 +10,7 @@ BK_TOKEN = os.environ.get("BACKEND_TOKEN", "")
|
|
| 10 |
RATE = int(os.environ.get("RATE_PER_10MIN", "20"))
|
| 11 |
TIMEOUT = float(os.environ.get("BACKEND_TIMEOUT", "300"))
|
| 12 |
GET_OK = {"health", "status", "audit", "store/catalog", "browser/img", "browser/res"}
|
| 13 |
-
POST_OK = {"chat", "scenario", "kill", "resume", "tamper", "reset", "store/submit", "store/publish", "store/app", "store/install", "gate/decide", "vmask", "game/hint", "game/cheat", "browser/open", "browser/agent", "browser/snap", "browser/policy", "settings/get", "settings/revoke", "settings/grant", "settings/egress", "settings/policy", "settings/locked", "rsi/state", "rsi/propose", "rsi/apply", "rsi/rollback", "rsi/reset", "ztc/status", "ztc/judge", "ztc/run", "backdoor/run", "backdoor/scan", "backdoor/wake", "gpu"}
|
| 14 |
STATIC = {"index.html", "app.js", "ag.js", "store.js", "common.js", "display.js", "terminal.js", "mines.js", "browser.js", "settings.js", "live_apps.js", "game.js", "rsi.js", "gpulab.js", "ztc.js", "backdoor.js", "backdoor.css", "backdoor_metrics.json", "downloads/vdrpack.py", "demo/poisoned-page", "samples/weather-plus.vdr", "samples/weather-plus-tampered.vdr", "i18n.js", "demo_data.js", "styles.css", "ag_replay.json"}
|
| 15 |
HITS = {}
|
| 16 |
|
|
@@ -37,7 +37,7 @@ class H(SimpleHTTPRequestHandler):
|
|
| 37 |
base = base or BACKEND
|
| 38 |
if not base:
|
| 39 |
return self._json(503, {"error": "backend not configured"})
|
| 40 |
-
h = {"Content-Type": "application/json", "X-Backend-Token": BK_TOKEN}
|
| 41 |
if HF_AUTH:
|
| 42 |
h["Authorization"] = "Bearer " + HF_AUTH
|
| 43 |
req = urllib.request.Request(base + "/api/" + name + query, data=body, headers=h, method="POST" if body is not None else "GET")
|
|
@@ -94,6 +94,8 @@ class H(SimpleHTTPRequestHandler):
|
|
| 94 |
req["message"] = str(req["message"])[:500]
|
| 95 |
if x and not BACKEND2:
|
| 96 |
return self._json(503, {"error": "in-weights backend not configured"})
|
|
|
|
|
|
|
| 97 |
self._proxy(name, json.dumps(req).encode(), base=BACKEND2 if x else None)
|
| 98 |
|
| 99 |
def end_headers(self):
|
|
|
|
| 10 |
RATE = int(os.environ.get("RATE_PER_10MIN", "20"))
|
| 11 |
TIMEOUT = float(os.environ.get("BACKEND_TIMEOUT", "300"))
|
| 12 |
GET_OK = {"health", "status", "audit", "store/catalog", "browser/img", "browser/res"}
|
| 13 |
+
POST_OK = {"chat", "scenario", "kill", "resume", "tamper", "reset", "store/submit", "store/publish", "store/app", "store/install", "gate/decide", "vmask", "game/hint", "game/cheat", "browser/open", "browser/agent", "browser/snap", "browser/policy", "settings/get", "settings/revoke", "settings/grant", "settings/egress", "settings/policy", "settings/locked", "rsi/state", "rsi/propose", "rsi/apply", "rsi/rollback", "rsi/reset", "ztc/status", "ztc/judge", "ztc/run", "backdoor/run", "backdoor/scan", "backdoor/wake", "gpu", "session/start"}
|
| 14 |
STATIC = {"index.html", "app.js", "ag.js", "store.js", "common.js", "display.js", "terminal.js", "mines.js", "browser.js", "settings.js", "live_apps.js", "game.js", "rsi.js", "gpulab.js", "ztc.js", "backdoor.js", "backdoor.css", "backdoor_metrics.json", "downloads/vdrpack.py", "demo/poisoned-page", "samples/weather-plus.vdr", "samples/weather-plus-tampered.vdr", "i18n.js", "demo_data.js", "styles.css", "ag_replay.json"}
|
| 15 |
HITS = {}
|
| 16 |
|
|
|
|
| 37 |
base = base or BACKEND
|
| 38 |
if not base:
|
| 39 |
return self._json(503, {"error": "backend not configured"})
|
| 40 |
+
h = {"Content-Type": "application/json", "X-Backend-Token": BK_TOKEN, "X-VK-Session": (self.headers.get("X-VK-Session") or "")[:60]}
|
| 41 |
if HF_AUTH:
|
| 42 |
h["Authorization"] = "Bearer " + HF_AUTH
|
| 43 |
req = urllib.request.Request(base + "/api/" + name + query, data=body, headers=h, method="POST" if body is not None else "GET")
|
|
|
|
| 94 |
req["message"] = str(req["message"])[:500]
|
| 95 |
if x and not BACKEND2:
|
| 96 |
return self._json(503, {"error": "in-weights backend not configured"})
|
| 97 |
+
if x and name in ("kill", "resume"):
|
| 98 |
+
x = False # visitor KILL/RESUME is per session; the in-weights kernel is never halted globally by a visitor
|
| 99 |
self._proxy(name, json.dumps(req).encode(), base=BACKEND2 if x else None)
|
| 100 |
|
| 101 |
def end_headers(self):
|
terminal.js
CHANGED
|
@@ -88,6 +88,8 @@
|
|
| 88 |
let history = [];
|
| 89 |
try { history = JSON.parse(sessionStorage.getItem(HIST_KEY) || '[]') || []; } catch (e) { history = []; }
|
| 90 |
let halted = false;
|
|
|
|
|
|
|
| 91 |
let online = true;
|
| 92 |
|
| 93 |
const save = () => {
|
|
@@ -564,7 +566,8 @@
|
|
| 564 |
|
| 565 |
async function execLine(line) {
|
| 566 |
if (!line.trim()) return '';
|
| 567 |
-
if (halted && !ALWAYS(line))
|
|
|
|
| 568 |
const sec = await securityIntercept(line);
|
| 569 |
if (sec !== null) return sec;
|
| 570 |
const toks = tokenize(line);
|
|
|
|
| 88 |
let history = [];
|
| 89 |
try { history = JSON.parse(sessionStorage.getItem(HIST_KEY) || '[]') || []; } catch (e) { history = []; }
|
| 90 |
let halted = false;
|
| 91 |
+
const poll = setInterval(() => { if (!body.isConnected) { clearInterval(poll); return; } refreshState(); }, 12000);
|
| 92 |
+
window.addEventListener('vk-kernel-state', e => { halted = e.detail === 'HALTED'; });
|
| 93 |
let online = true;
|
| 94 |
|
| 95 |
const save = () => {
|
|
|
|
| 566 |
|
| 567 |
async function execLine(line) {
|
| 568 |
if (!line.trim()) return '';
|
| 569 |
+
if (halted && !ALWAYS(line)) await refreshState();
|
| 570 |
+
if (halted && !ALWAYS(line)) return C.y('Kernel halted in your session. Press RESUME or type: vik resume --key <demo key from the guide> to continue.');
|
| 571 |
const sec = await securityIntercept(line);
|
| 572 |
if (sec !== null) return sec;
|
| 573 |
const toks = tokenize(line);
|