Skip to content

Commit d2b3791

Browse files
author
piqrypt-sync-bot
committed
sync: piqrypt v1.8.10 → aiss 2.x — automated sync
1 parent ec06ade commit d2b3791

2 files changed

Lines changed: 331 additions & 4 deletions

File tree

vigil/vigil_server.py

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# SPDX-License-Identifier: MIT
22
# Copyright (c) 2026 PiQrypt Inc.
33
# e-Soleau: DSO2026006483 (19/02/2026) -- DSO2026009143 (12/03/2026)
4-
# sync: generated from piqrypt@v1.8.7 — do not edit manually
4+
# sync: generated from piqrypt@v1.8.10 — do not edit manually
55
# Source: https://github.com/piqrypt/piqrypt/blob/main//home/runner/work/piqrypt/piqrypt/piqrypt/vigil/vigil_server.py
66
# To modify: edit in piqrypt, changes will be synced on next release
77
"""
@@ -39,6 +39,7 @@
3939
import re
4040
import sys
4141
import threading
42+
import subprocess
4243
import time
4344
import shutil
4445
import signal
@@ -133,6 +134,11 @@
133134
# ── Auth instance (partagée par tous les handlers) ────────────────────────────
134135
_AUTH = AuthMiddleware("VIGIL_TOKEN", service="vigil")
135136

137+
# ── Demo process handle (singleton) ───────────────────────────────────────────
138+
_demo_proc: Optional["subprocess.Popen"] = None
139+
_demo_active: bool = False
140+
_DEMO_LOCKFILE = PIQRYPT_DIR / ".demo_active"
141+
136142

137143
# ── TrustGate push (fire-and-forget, thread séparé) ──────────────────────────
138144
def _push_to_trustgate(
@@ -363,6 +369,11 @@ def do_GET(self):
363369
self._send_json(200, _AUTH.tier_info())
364370
return
365371

372+
# ── Demo status (publique — pour l'UI) ──
373+
if path == "/api/demo/status":
374+
self._send_json(200, {"active": _demo_active})
375+
return
376+
366377
# ── Features (publique — pour l'UI) ──
367378
if path == "/api/features":
368379
vf = _AUTH.vigil_features()
@@ -446,6 +457,23 @@ def do_POST(self):
446457
# ── Auth + feature gating ──
447458
if not _AUTH.check(self):
448459
return
460+
461+
# ── Demo routes — auth only, no record feature required ──
462+
if path == "/api/demo/start":
463+
length = int(self.headers.get("Content-Length", 0))
464+
body = self.rfile.read(length) if length else b"{}"
465+
try:
466+
payload = json.loads(body)
467+
except json.JSONDecodeError:
468+
self._send_error(400, "Invalid JSON")
469+
return
470+
self._api_demo_start(payload)
471+
return
472+
473+
if path == "/api/demo/stop":
474+
self._api_demo_stop()
475+
return
476+
449477
if not _AUTH.check_feature(self, "record"):
450478
return
451479

@@ -950,6 +978,34 @@ def _api_create_agent(self, payload: Dict):
950978
self._send_error(503, "Backend non disponible — mode DEMO")
951979
return
952980

981+
# ── Agent count limit (Free tier: 3 agents max) ───────────────────────
982+
try:
983+
from aiss.license import get_tier as _get_license_tier, TIERS as _TIERS
984+
_tier = _get_license_tier()
985+
_agents_max = _TIERS.get(_tier, _TIERS["free"]).get("agents_max")
986+
if _agents_max is not None:
987+
_agents_dir = PIQRYPT_DIR / "agents"
988+
_existing = 0
989+
if _agents_dir.exists():
990+
_existing = len([
991+
d for d in _agents_dir.iterdir() if d.is_dir()
992+
])
993+
if _existing >= _agents_max:
994+
self._send_json(403, {
995+
"error": "agent_limit_reached",
996+
"message": (
997+
f"Free tier limit reached ({_agents_max} agents) "
998+
"— upgrade at piqrypt.com"
999+
),
1000+
"limit": _agents_max,
1001+
"current": _existing,
1002+
"tier": _tier,
1003+
"upgrade": "https://piqrypt.com/pricing",
1004+
})
1005+
return
1006+
except Exception as e:
1007+
log.debug("[Vigil] agent_limit check failed (non-blocking): %s", e)
1008+
9531009
# ── Bridge limit enforcement (Free tier: 2 bridges max) ──────────────
9541010
bridge_limit = _AUTH.get_bridge_limit()
9551011
if bridge_limit is not None and bridge:
@@ -1098,6 +1154,94 @@ def _api_delete_agent(self, name: str, confirmed: bool = False):
10981154
log.error("[Vigil] delete_agent(%s) failed: %s", name, e)
10991155
self._send_json(500, {"error": str(e)})
11001156

1157+
def _api_demo_start(self, payload: Dict):
1158+
"""POST /api/demo/start — launch demo_families.py for the given family."""
1159+
global _demo_proc, _demo_active
1160+
1161+
family = (payload.get("family") or "nexus").strip()
1162+
if family not in ("nexus", "pixelflow", "alphacore"):
1163+
family = "nexus"
1164+
1165+
# Kill any running demo
1166+
if _demo_proc is not None:
1167+
try:
1168+
_demo_proc.terminate()
1169+
except Exception:
1170+
pass
1171+
_demo_proc = None
1172+
1173+
# Purge agent state
1174+
agents_dir = PIQRYPT_DIR / "agents"
1175+
if agents_dir.exists():
1176+
shutil.rmtree(agents_dir, ignore_errors=True)
1177+
agents_dir.mkdir(parents=True, exist_ok=True)
1178+
1179+
peers_file = PIQRYPT_DIR / "peers.json"
1180+
if peers_file.exists():
1181+
try:
1182+
peers_file.unlink()
1183+
except Exception:
1184+
pass
1185+
1186+
# Locate demo script
1187+
demo_script = Path(__file__).resolve().parent.parent / "demos" / "demo_families.py"
1188+
if not demo_script.exists():
1189+
self._send_error(404, f"demo_families.py not found at {demo_script}")
1190+
return
1191+
1192+
token = _AUTH.token or ""
1193+
_demo_proc = subprocess.Popen(
1194+
[sys.executable, str(demo_script), "--family", family, "--loop", "--fast"],
1195+
env={
1196+
**os.environ,
1197+
"VIGIL_TOKEN": token,
1198+
"PIQRYPT_SCRYPT_N": "16384",
1199+
"VIGIL_DEV_DELETE": "1",
1200+
"PYTHONIOENCODING": "utf-8",
1201+
},
1202+
stdout=subprocess.DEVNULL,
1203+
stderr=subprocess.DEVNULL,
1204+
)
1205+
_demo_active = True
1206+
try:
1207+
_DEMO_LOCKFILE.write_text(family)
1208+
except Exception:
1209+
pass
1210+
log.info("[Vigil] Demo started — family=%s pid=%d", family, _demo_proc.pid)
1211+
self._send_json(200, {"status": "ok", "family": family})
1212+
1213+
def _api_demo_stop(self):
1214+
"""POST /api/demo/stop — terminate demo process and purge agent state."""
1215+
global _demo_proc, _demo_active
1216+
1217+
if _demo_proc is not None:
1218+
try:
1219+
_demo_proc.terminate()
1220+
except Exception:
1221+
pass
1222+
_demo_proc = None
1223+
1224+
agents_dir = PIQRYPT_DIR / "agents"
1225+
if agents_dir.exists():
1226+
shutil.rmtree(agents_dir, ignore_errors=True)
1227+
agents_dir.mkdir(parents=True, exist_ok=True)
1228+
1229+
peers_file = PIQRYPT_DIR / "peers.json"
1230+
if peers_file.exists():
1231+
try:
1232+
peers_file.unlink()
1233+
except Exception:
1234+
pass
1235+
1236+
_demo_active = False
1237+
try:
1238+
if _DEMO_LOCKFILE.exists():
1239+
_DEMO_LOCKFILE.unlink()
1240+
except Exception:
1241+
pass
1242+
log.info("[Vigil] Demo stopped")
1243+
self._send_json(200, {"status": "ok"})
1244+
11011245
def _api_record(self, name: str, event: Dict):
11021246
"""Receive a stamped event from a bridge and feed it to Vigil."""
11031247
if BACKEND_AVAILABLE:
@@ -1498,6 +1642,21 @@ def __init__(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT):
14981642
self._thread: Optional[threading.Thread] = None
14991643

15001644
def start(self, blocking: bool = True):
1645+
# ── Demo lockfile check — purge stale demo state on restart ──────────
1646+
if _DEMO_LOCKFILE.exists():
1647+
log.info("[Vigil] Demo lockfile found — purging stale demo state")
1648+
try:
1649+
agents_dir = PIQRYPT_DIR / "agents"
1650+
if agents_dir.exists():
1651+
shutil.rmtree(agents_dir, ignore_errors=True)
1652+
agents_dir.mkdir(parents=True, exist_ok=True)
1653+
peers_file = PIQRYPT_DIR / "peers.json"
1654+
if peers_file.exists():
1655+
peers_file.unlink()
1656+
_DEMO_LOCKFILE.unlink()
1657+
except Exception as e:
1658+
log.warning("[Vigil] Demo cleanup failed: %s", e)
1659+
15011660
self._server = HTTPServer((self.host, self.port), VIGILHandler)
15021661
log.info("━" * 56)
15031662
log.info(" VIGIL Server v1.8.4")

0 commit comments

Comments
 (0)