SeaWolf-AI commited on
Commit
33fbfa5
·
verified ·
1 Parent(s): fcfc3c9

Per-visitor kill switch; live tray status and polling

Browse files
Files changed (6) hide show
  1. ag.js +3 -2
  2. app.js +9 -3
  3. common.js +4 -2
  4. gpulab.js +1 -1
  5. server.py +4 -2
  6. terminal.js +4 -1
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 s = api('/api/status'), st = s.kernel_state || '-';
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 = () => { api('/api/vsh', {line:'halt'}); toast(t('halted')); refreshTray(); };
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
- const r = api('/api/vsh', {line:k === 'vikernel-demo' ? 'resume --key vikernel-demo' : 'resume'}); toast(/-> RUN/.test(r.out || '') ? t('resumed') : t('resumeNo')); refreshTray(); }; };
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)) return C.y('HALTED: the kernel is stopped. Resume with: vik resume --key <operator key>');
 
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);