[object Object]

← back to Qwen38 Viewer

debug+refactor: fix 3D fallback setState + WebGL try/catch + render throttle; auth no-colon guard + upstream cancel + listener cleanup; mail-bridge fd-leak, dead-line, SMTP reuse+try/except, plus-address guard

bc74a9050d598c47857ff1e8e1245280cd99631c · 2026-08-19 11:30:06 -0700 · steve

Files touched

Diff

commit bc74a9050d598c47857ff1e8e1245280cd99631c
Author: steve <steve@designerwallcoverings.com>
Date:   Wed Aug 19 11:30:06 2026 -0700

    debug+refactor: fix 3D fallback setState + WebGL try/catch + render throttle; auth no-colon guard + upstream cancel + listener cleanup; mail-bridge fd-leak, dead-line, SMTP reuse+try/except, plus-address guard
---
 __pycache__/mail-bridge.cpython-314.pyc | Bin 0 -> 14140 bytes
 mail-bridge.py                          |  35 ++++++++++++++++++++++----------
 public/index.html                       |  16 ++++++++++-----
 server.js                               |  13 ++++++++----
 4 files changed, 44 insertions(+), 20 deletions(-)

diff --git a/__pycache__/mail-bridge.cpython-314.pyc b/__pycache__/mail-bridge.cpython-314.pyc
new file mode 100644
index 0000000..e1348ba
Binary files /dev/null and b/__pycache__/mail-bridge.cpython-314.pyc differ
diff --git a/mail-bridge.py b/mail-bridge.py
index 11790df..4562d8f 100644
--- a/mail-bridge.py
+++ b/mail-bridge.py
@@ -43,7 +43,8 @@ LOG   = pathlib.Path.home() / ".qwen-mail-bridge.log"
 def log(m):
     line = f"{datetime.datetime.now().isoformat(timespec='seconds')} {m}"
     print(line)
-    try: LOG.open("a").write(line + "\n")
+    try:
+        with LOG.open("a") as f: f.write(line + "\n")
     except OSError: pass
 
 def get_pw():
@@ -89,7 +90,6 @@ def plain_body(msg):
             if ct == "text/plain" and not text: text = _decode(part)
             elif ct == "text/html" and not html: html = _decode(part)
     else:
-        (text if msg.get_content_type() == "text/plain" else html) and None
         if msg.get_content_type() == "text/html": html = _decode(msg)
         else: text = _decode(msg)
     return text.strip() or _strip_html(html)
@@ -127,16 +127,18 @@ def main():
     ids = data[0].split() if data and data[0] else []
     log(f"poll: {len(ids)} unseen")
 
+    smtp = None
     for num in ids:
         if replied >= MAX_PER_RUN:
             log("rate: MAX_PER_RUN hit, stopping"); break
         typ, md = M.fetch(num, "(RFC822)")
         msg = email.message_from_bytes(md[0][1])
         frm = parseaddr(msg.get("From", ""))[1].lower()
+        base = re.sub(r"\+[^@]*@", "@", frm)   # strip +tag so plus-addresses can't dodge the self-guard
         subj = msg.get("Subject", "(no subject)")
 
         # --- safety gates ---
-        if frm == MAILBOX or "mailer-daemon" in frm or frm.startswith("no-reply") or frm.startswith("noreply"):
+        if base == MAILBOX or "mailer-daemon" in frm or frm.startswith(("no-reply", "noreply")):
             log(f"skip loop-guard from={frm}"); M.store(num, "+FLAGS", "\\Seen"); continue
         if is_bulk(msg):
             log(f"skip bulk/auto from={frm}"); M.store(num, "+FLAGS", "\\Seen"); continue
@@ -167,14 +169,25 @@ def main():
         reply["Auto-Submitted"] = "auto-replied"   # be a good citizen; prevents remote loops
         reply.set_content(answer + "\n\n— qwen3.8-27b-heretic (local, uncensored) via agentabrams.com")
 
-        s = smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=60)
-        s.starttls(context=ctx); s.login(MAILBOX, pw); s.send_message(reply); s.quit()
-
-        M.store(num, "+FLAGS", "\\Seen")
-        counts[frm] = counts.get(frm, 0) + 1
-        replied += 1
-        log(f"replied to {frm} ({len(answer)} chars)")
-
+        try:
+            if smtp is None:                              # connect once, reuse across replies
+                smtp = smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=60)
+                smtp.starttls(context=ctx); smtp.login(MAILBOX, pw)
+            smtp.send_message(reply)
+            M.store(num, "+FLAGS", "\\Seen")
+            counts[frm] = counts.get(frm, 0) + 1
+            replied += 1
+            log(f"replied to {frm} ({len(answer)} chars)")
+        except Exception as e:
+            log(f"smtp error to {frm}: {e}")
+            M.store(num, "+FLAGS", "\\Seen")              # mark seen -> no retry storm / dup replies
+            try: smtp.quit()
+            except Exception: pass
+            smtp = None                                    # force fresh connection next time
+
+    if smtp is not None:
+        try: smtp.quit()
+        except Exception: pass
     M.logout()
     state = {today: counts}   # keep only today's counts
     save_state(state)
diff --git a/public/index.html b/public/index.html
index f9fe7dc..6b07040 100644
--- a/public/index.html
+++ b/public/index.html
@@ -158,7 +158,8 @@ function thinkingIndicator(){
 // ---- three.js thinking scene: a pulsing wireframe icosahedron core +
 // orbiting particle swarm. Renders while the model is thinking. ------------
 function makeThink3D(mount){
-  if(!window.THREE){ return {stop(){}, energy(){}}; }  // graceful fallback
+  const NOOP = {stop(){}, energy(){}, setState(){}};
+  if(!window.THREE || !mount){ return NOOP; }          // graceful fallback (same shape)
   const W = mount.clientWidth || 700, H = mount.clientHeight || 180;
   const scene = new THREE.Scene();
   const cam = new THREE.PerspectiveCamera(50, W/H, 0.1, 100); cam.position.z = 5;
@@ -254,8 +255,10 @@ async function send(){
   scroll.scrollTop = scroll.scrollHeight;
   const stream = content.querySelector('.stream');
   let acc='';
-  let t3d = makeThink3D(content.querySelector('.t3d'));   // <-- three.js scene
-  let lastTok = performance.now();
+  let t3d;
+  try { t3d = makeThink3D(content.querySelector('.t3d')); }  // three.js scene
+  catch(e){ console.warn('3D init failed:', e); t3d = {stop(){}, energy(){}, setState(){}}; }
+  let lastTok = performance.now(), lastRender = 0;
 
   const bodyOf = s => {                                   // visible answer (outside <think>)
     const o=s.indexOf('<think>'), c=s.indexOf('</think>');
@@ -311,8 +314,11 @@ async function send(){
         else if(j.message && j.message.content){ acc += j.message.content; }
         lastTok = performance.now();
         if(t3d){ t3d.energy(); t3d.setState(bodyOf(acc).trim()!==''?'running':'thinking'); }
-        stream.innerHTML = render(acc, true);      // stream text below the live 3D panel
-        scroll.scrollTop = scroll.scrollHeight;
+        if(lastTok - lastRender > 60){            // throttle: avoid O(n) reparse every token
+          lastRender = lastTok;
+          stream.innerHTML = render(acc, true);   // stream text below the live 3D panel
+          scroll.scrollTop = scroll.scrollHeight;
+        }
       }
     }
     killScene();
diff --git a/server.js b/server.js
index e6fa079..70c297b 100644
--- a/server.js
+++ b/server.js
@@ -35,9 +35,11 @@ app.use((req, res, next) => {
   if (scheme === 'Basic' && b64) {
     const s = Buffer.from(b64, 'base64').toString();
     const i = s.indexOf(':');
-    const u = s.slice(0, i), p = s.slice(i + 1);
-    if (USERS[u] !== undefined && USERS[u] === p) return next();     // exact user:pass
-    if (CI_CODES.has(p.toLowerCase())) return next();                // case-insensitive shared code
+    if (i > 0) {                                                     // require a colon + non-empty user
+      const u = s.slice(0, i), p = s.slice(i + 1);
+      if (USERS[u] === p) return next();                            // exact user:pass
+      if (CI_CODES.has(p.toLowerCase())) return next();             // case-insensitive shared code
+    }
   }
   res.set('WWW-Authenticate', 'Basic realm="qwen38"');
   return res.status(401).send('Auth required');
@@ -80,18 +82,21 @@ app.post('/api/chat', async (req, res) => {
         keep_alive: KEEP_ALIVE === '-1' ? -1 : KEEP_ALIVE }),  // -1 (number) = keep forever
     });
     if (!upstream.ok || !upstream.body) {
+      if (upstream.body) upstream.body.cancel().catch(() => {}); // don't leak the TCP conn
       res.write(JSON.stringify({ error: `ollama ${upstream.status}` }) + '\n');
       return res.end();
     }
     // Abort upstream if the client disconnects
     const reader = upstream.body.getReader();
-    req.on('close', () => reader.cancel().catch(() => {}));
+    const onClose = () => reader.cancel().catch(() => {});
+    req.once('close', onClose);
     const dec = new TextDecoder();
     for (;;) {
       const { done, value } = await reader.read();
       if (done) break;
       res.write(dec.decode(value, { stream: true }));
     }
+    req.off('close', onClose);
     res.end();
   } catch (e) {
     res.write(JSON.stringify({ error: String(e) }) + '\n');

← 3718af9 self-host three.js (local /vendor/three.min.js) with CDN fal  ·  back to Qwen38 Viewer  ·  add npm test: 18 regression checks (auth matrix incl no-colo 114f772 →