← back to Ticket System
auto-save: 2026-07-27T11:50:21 (2 files) — office.html server.js
4d5b85d99b14e4c6ab15cdc33992171ab56c1247 · 2026-07-27 11:50:21 -0700 · Steve Abrams
Files touched
Diff
commit 4d5b85d99b14e4c6ab15cdc33992171ab56c1247
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 27 11:50:21 2026 -0700
auto-save: 2026-07-27T11:50:21 (2 files) — office.html server.js
---
office.html | 12 ++++++++++++
server.js | 25 +++++++++++++++++++++++++
2 files changed, 37 insertions(+)
diff --git a/office.html b/office.html
index 879c215..bdb5257 100644
--- a/office.html
+++ b/office.html
@@ -31,6 +31,9 @@
text-transform:uppercase;letter-spacing:.08em}
.pill i.d{width:7px;height:7px;border-radius:2px;box-shadow:0 0 8px currentColor}
.pill b{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;color:#fff;letter-spacing:0}
+ .pill.run{border-color:rgba(52,217,160,0.4);background:rgba(52,217,160,0.07)}
+ .pill.run i.d{animation:runblink 1.6s ease-in-out infinite}
+ @keyframes runblink{0%,100%{opacity:.45}50%{opacity:1}}
#top .age{margin-left:auto;font-size:10px;color:var(--faint);letter-spacing:.14em;text-transform:uppercase}
#top .age b{font-family:'JetBrains Mono',monospace;color:var(--dim);font-weight:600}
/* ── legend / workload ── */
@@ -54,6 +57,8 @@
.ctl button:hover{border-color:rgba(255,255,255,0.22);color:#fff;background:rgba(255,255,255,0.06)}
.ctl button.on{color:#fff;border-color:var(--blue);background:rgba(90,169,240,0.12)}
.ctl button.on::before{background:var(--blue);box-shadow:0 0 9px var(--blue)}
+ #b-run.on,#b-procs.on{color:#fff;border-color:var(--green);background:rgba(52,217,160,0.12)}
+ #b-run.on::before,#b-procs.on::before{background:var(--green);box-shadow:0 0 9px var(--green)}
#b-ping{color:var(--purple);border-color:rgba(201,164,242,0.4)}
#b-ping::before{background:var(--purple);box-shadow:0 0 9px var(--purple)}
#b-reset::before,#b-ping::before{display:none}
@@ -98,6 +103,9 @@
<span class="pill"><i class="d" style="background:var(--open);color:var(--open)"></i>open <b id="s-open">0</b></span>
<span class="pill"><i class="d" style="background:var(--green);color:var(--green)"></i>done <b id="s-done">0</b></span>
<span class="pill"><i class="d" style="background:var(--purple);color:var(--purple)"></i>agents <b id="s-agents">0</b></span>
+ <span class="pill run"><i class="d" style="background:var(--green);color:var(--green)"></i>live <b id="s-live">0</b></span>
+ <span class="pill run"><i class="d" style="background:var(--green);color:var(--green)"></i>pm2 <b id="s-running">0</b></span>
+ <span class="pill run"><i class="d" style="background:var(--green);color:var(--green)"></i>sessions <b id="s-sessions">0</b></span>
<span class="pill"><i class="d" style="background:var(--purple);color:var(--purple)"></i>DMs <b id="s-dms">0</b></span>
</div>
<span class="age">SYNC <b id="s-age">–</b></span>
@@ -115,6 +123,8 @@
<button id="b-follow">Follow DMs</button>
<div class="csec">Scene</div>
<button id="b-labels" class="on">Labels</button>
+ <button id="b-run" class="on">Running edges</button>
+ <button id="b-procs" class="on">Process wall</button>
<button id="b-done" class="on">Done rooms</button>
<button id="b-ping" title="Preview the DM packet animation (visual only — writes nothing)">Ping ✦</button>
<button id="b-reset">Reset camera</button>
@@ -144,6 +154,8 @@ import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
const LANES = ['doing', 'blocked', 'open', 'done'];
const COLOR = { doing:0x6db3f2, blocked:0xff6b7a, open:0x9aa4b8, done:0x3ee6a0 };
const ROOM=9, PITCH=12, LANE_W=42, DONE_CAP=14, POLL_MS=4000;
+const RUN=0x34d9a0; // "running" green — the through-line for live things
+const LIVE_MS=8*60*1000; // an agent that acted within 8 min counts as live/running
// ───────────────────────── renderer ─────────────────────────
const canvas=document.getElementById('c');
diff --git a/server.js b/server.js
index 347ec6b..cd77964 100644
--- a/server.js
+++ b/server.js
@@ -2,8 +2,32 @@
const http = require('http');
const fs = require('fs');
const path = require('path');
+const { exec } = require('child_process');
const { tickets, STATUSES, messages } = require('./lib.js');
+// ── live "running" signal: online pm2 processes + live claude CLI sessions ──
+// Cached 5s so N polling browsers don't each spawn a `pm2 jlist` on a busy box.
+let runCache = { ts: 0, data: { pm2: [], sessions: 0, at: null } };
+function getRunning(cb) {
+ if (Date.now() - runCache.ts < 5000) return cb(runCache.data);
+ exec('pm2 jlist', { maxBuffer: 16 * 1024 * 1024, timeout: 8000 }, (e, out) => {
+ let pm2 = [];
+ if (!e) { try {
+ pm2 = JSON.parse(out).filter(p => p.pm2_env && p.pm2_env.status === 'online')
+ .map(p => ({ name: p.name, cpu: (p.monit && p.monit.cpu) || 0,
+ mem: Math.round(((p.monit && p.monit.memory) || 0) / 1048576),
+ up: p.pm2_env.pm_uptime || 0, restarts: p.pm2_env.restart_time || 0 }))
+ .sort((a, b) => a.name < b.name ? -1 : 1);
+ } catch (_) {} }
+ // count live `claude` CLI sessions (exclude skills-dir helpers), best-effort
+ exec("ps -Ao command | grep '[c]laude' | grep -v 'skills/' | wc -l", { timeout: 4000 }, (e2, out2) => {
+ const sessions = e2 ? 0 : (parseInt(String(out2).trim(), 10) || 0);
+ runCache = { ts: Date.now(), data: { pm2, sessions, at: new Date().toISOString() } };
+ cb(runCache.data);
+ });
+ });
+}
+
const OFFICE_HTML = path.join(__dirname, 'office.html');
const PORT = process.env.PORT || 9794;
@@ -82,6 +106,7 @@ http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(buf);
});
}
+ if (req.url === '/api/running') { return getRunning(d => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(d)); }); }
if (req.url === '/api/tickets') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify([...tickets().values()])); }
if (req.url === '/api/messages') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify([...messages().values()])); }
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(page());
← 338a454 office: harden agent placement — clear stale atWork glow on
·
back to Ticket System
·
Fleet Office: green 'running' edge for live agents, pm2 proc bb20804 →