@javierfrancoleal/dsh-supervisor 1.0.56 → 1.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dashboard.html CHANGED
@@ -229,6 +229,10 @@ body.wall pre{max-height:40vh}
229
229
  .noSendTxt{position:relative;z-index:2}
230
230
  .noSendFill{position:absolute;inset:0;z-index:1;background:rgba(107,114,128,.38);backdrop-filter:blur(1.5px);-webkit-backdrop-filter:blur(1.5px);transform-origin:left;animation:noFill 10s linear forwards}
231
231
  @media(prefers-reduced-motion:reduce){.noSendFill{animation:none;transform:scaleX(0)}}
232
+ #notifBell.bell-pulse{animation:bellPulse 1.1s ease-in-out infinite}
233
+ @keyframes bellPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.14)}}
234
+ @media(prefers-reduced-motion:reduce){#notifBell.bell-pulse{animation:none}}
235
+ .inbox-item .when{font-size:11px;color:#9CA3AF}
232
236
  </style>
233
237
  </head>
234
238
  <body>
@@ -585,7 +589,7 @@ function renderInbox(items){
585
589
  const pend=items.filter(x=>x.status==="done");
586
590
  $("inboxList").innerHTML=pend.length?pend.map(x=>{
587
591
  const name=STUDIO_TYPES[x.type]||x.type;
588
- return "<div class=\"inbox-item\" data-id=\""+esc(x.id)+"\"><b>🔔 "+esc(name)+"</b><br><span class=\"hint\">"+esc((x.text||"").slice(0,90))+"</span></div>";
592
+ return "<div class=\"inbox-item\" data-id=\""+esc(x.id)+"\"><b>🔔 "+esc(name)+"</b> <span class=\"when\">"+esc((x.ts||"").slice(11,16))+"</span><br><span class=\"hint\">"+esc((x.text||"").slice(0,90))+"</span></div>";
589
593
  }).join(""):empty("🔕",t("inboxEmpty"));
590
594
  $("inboxList").querySelectorAll(".inbox-item").forEach(el=>el.onclick=()=>openDecision(items.find(x=>x.id===el.dataset.id)));
591
595
  }
@@ -619,7 +623,7 @@ async function refreshNotifBadge(){
619
623
  if(!r||r.error)return;
620
624
  const n=(r.tasks||[]).filter(x=>x.status==="done").length;
621
625
  const b=$("notifBadge");
622
- if(n>0){b.textContent=n;b.classList.remove("hidden");}else b.classList.add("hidden");
626
+ if(n>0){b.textContent=n;b.classList.remove("hidden");$("notifBell").classList.add("bell-pulse");}else{b.classList.add("hidden");$("notifBell").classList.remove("bell-pulse");}
623
627
  }catch(e){}
624
628
  }
625
629
  $("notifBell").onclick=()=>{$("inboxView").style.display="flex";loadStudio();};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@javierfrancoleal/dsh-supervisor",
3
- "version": "1.0.56",
3
+ "version": "1.0.57",
4
4
  "description": "The Veilleux central control plane of the J.Franco agentic workflow suite: user accounts, the live conversation flow (listen → per-person summary → confirm → orders), telemetry, and the universal sector dashboard with 10 languages. Created by J.Franco.",
5
5
  "author": "J.Franco",
6
6
  "license": "MIT",
package/server.js CHANGED
@@ -59,7 +59,7 @@ const SECTOR_CLOSE = [
59
59
  { id: "close_clean", label: "Nettoyage et rangement des postes" },
60
60
  { id: "close_lights", label: "Lumières, chauffage et alarme" }
61
61
  ];
62
- const VERSION = "1.0.56";
62
+ const VERSION = "1.0.57";
63
63
  // Homeostasis: the daily token budget (metabolic limit). The brain rests
64
64
  // when the budget is spent — no runaway costs.
65
65
  const DAILY_BUDGET_USD = Number(process.env.DSH_DAILY_BUDGET_USD || 0); // 0 = unlimited: consumption is the business
@@ -420,6 +420,7 @@ async function api(req, res, pathname) {
420
420
  if (pathname === "/api/studio" && req.method === "GET") { const u = auth(req); if (!u || (u.role !== "owner" && u.role !== "lead")) return send(res, 401, { error: "unauthorized" }); const tk = tasksRead(); return send(res, 200, { tasks: tk.tasks.slice(-20).reverse() }); }
421
421
  if (pathname === "/api/studio" && req.method === "POST") { const u = auth(req); if (!u || (u.role !== "owner" && u.role !== "lead")) return send(res, 401, { error: "unauthorized" }); const b = await json(req); const tk = tasksRead(); const id = "t" + Date.now(); tk.tasks.push({ id, type: String(b.type || "other"), text: String(b.text || ""), by: u.name, ts: ts(), status: "pending", result: null }); tasksWrite(tk); const l = liveRead(); l.inbox = l.inbox || []; l.inbox.push({ from: u.name, kind: "studio", id, text: String(b.type || "") + " :: " + String(b.text || "") }); liveWrite(l); if (process.env.DSH_DEMO_MODE === "1") setTimeout(() => { const t2 = tasksRead(); const it = t2.tasks.find((x) => x.id === id); if (it && it.status === "pending") { it.result = "[Brouillon de démonstration — " + String(b.type || "autre") + "] " + String(b.text || ""); it.rationale = "Le client pose une question simple ; une réponse courte et chaleureuse renforce le lien et invite à venir. (Démonstration — la cuisine réelle argumente avec les données de l'entreprise.)"; it.status = "done"; it.doneAt = ts(); tasksWrite(t2); } }, 3000); return send(res, 200, { ok: true, id }); }
422
422
  if (pathname === "/api/studio/send" && req.method === "POST") { const u = auth(req); if (!u || (u.role !== "owner" && u.role !== "lead")) return send(res, 401, { error: "unauthorized" }); const b = await json(req); const tk = tasksRead(); const it = tk.tasks.find((x) => x.id === b.id); if (!it || it.status !== "done") return send(res, 200, { ok: false }); it.status = b.decision === "send" ? "sent" : "dismissed"; it.decidedBy = u.name; it.decidedAt = ts(); tasksWrite(tk); return send(res, 200, { ok: true }); }
423
+ if (pathname === "/api/bridge/message" && req.method === "POST") { const bt = process.env.DSH_BRIDGE_TOKEN; const given = req.headers["x-bridge-token"] || ""; if (bt && given !== bt) return send(res, 401, { error: "bad bridge token" }); if (!bt) { const u = auth(req); if (!u || (u.role !== "owner" && u.role !== "lead")) return send(res, 401, { error: "unauthorized" }); } const b = await json(req); const tk = tasksRead(); const id = "t" + Date.now(); tk.tasks.push({ id, type: "email", text: String(b.from || "client") + " : " + String(b.text || ""), by: "Facebook", ts: ts(), status: "pending", result: null }); tasksWrite(tk); const l = liveRead(); l.inbox = l.inbox || []; l.inbox.push({ from: "Facebook", kind: "studio", id, text: String(b.from || "client") + " :: " + String(b.text || "") }); liveWrite(l); return send(res, 200, { ok: true, id }); }
423
424
  if (pathname === "/api/studio/inbox" && req.method === "GET") { const l = liveRead(); const tk = tasksRead(); const doneIds = new Set(tk.tasks.filter((x) => x.status === "done").map((x) => x.id)); const items = (l.inbox || []).filter((x) => x.kind === "studio" && !doneIds.has(x.id)); return send(res, 200, { items }, true); }
424
425
  if (pathname === "/api/studio/result" && req.method === "POST") { const b = await json(req); const tk = tasksRead(); const it = tk.tasks.find((x) => x.id === b.id); if (!it) return send(res, 200, { ok: false }); it.result = String(b.result || ""); it.rationale = String(b.rationale || ""); it.status = "done"; it.doneAt = ts(); tasksWrite(tk); return send(res, 200, { ok: true }); }
425
426
  if (pathname === "/api/demo/alert" && req.method === "POST") { if (process.env.DSH_DEMO_MODE !== "1") return send(res, 403, { error: "demo mode disabled" }); const a = alertsRead(); a.items.push({ id: "a" + Date.now(), key: "demo-" + Date.now(), kind: "rain", pct: 85, inHour: 1, ts: ts(), status: "active", decision: null, source: "demo" }); alertsWrite(a); return send(res, 200, { ok: true }); }