diff --git a/html/js/office-render.js b/html/js/office-render.js
index ab60e9e..deda218 100644
--- a/html/js/office-render.js
+++ b/html/js/office-render.js
@@ -27,6 +27,7 @@ import {
mapAgentsToDesks,
normalizeAgentForDisplay,
resolveAgentVisualState,
+ isAgentPresent,
pickIdleActivity,
AnimationQueue,
detectDoorVisits,
@@ -1003,6 +1004,7 @@ function agentPlacements(agents, deskLayout) {
let overflowIndex = 0;
for (const raw of agents) {
if (seatedIds.has(raw.id)) continue;
+ if (!isAgentPresent(raw)) continue;
const agent = normalizeAgentForDisplay(raw);
const homeFeet = {
x: 70 + (overflowIndex % 3) * 26,
diff --git a/html/js/office-scene.js b/html/js/office-scene.js
index 32d2337..5cbebb6 100644
--- a/html/js/office-scene.js
+++ b/html/js/office-scene.js
@@ -355,20 +355,49 @@ export function resolveAgentVisualState(state) {
return AGENT_STATE_VISUALS[state] ?? AGENT_STATE_VISUALS.idle;
}
+// Praesenz-Timeout (Issue #17): ein registrierter Agent gilt nur noch als anwesend, solange
+// last_seen_at nicht laenger als zehn Minuten zurueckliegt. Meldet er sich laenger nicht,
+// bleibt sein Schreibtisch/Platz leer, obwohl er weiterhin registriert ist.
+export const AGENT_PRESENCE_TIMEOUT_MS = 10 * 60 * 1000;
+
+/**
+ * @param {{last_seen_at?: string}} agent
+ * @param {number} [now] - Referenzzeitpunkt in ms seit Epoch (injizierbar fuer Tests).
+ * @returns {boolean} true, wenn der Agent als anwesend gilt. Fehlt last_seen_at (z.B.
+ * Demo-Daten ohne API-Felder) oder ist es nicht parsbar, gilt der Agent defensiv als
+ * anwesend, statt faelschlich einen leeren Platz zu zeigen.
+ */
+export function isAgentPresent(agent, now = Date.now()) {
+ if (!agent || !agent.last_seen_at) {
+ return true;
+ }
+ const lastSeenAt = new Date(agent.last_seen_at).getTime();
+ if (Number.isNaN(lastSeenAt)) {
+ return true;
+ }
+ return now - lastSeenAt <= AGENT_PRESENCE_TIMEOUT_MS;
+}
+
/**
* Ordnet eine rohe Agentenliste (GET /api/v1/agents) den Schreibtischen zu - anders als
* buildDeskLayout()/Demo-Daten steht der Sitzplatz hier nicht in der Array-Reihenfolge,
* sondern im Feld desk_index (API.md "Agenten"). Agenten ohne Schreibtisch (role "user"
* oder alle Schreibtische belegt, desk_index === null) erscheinen hier nicht - sie kommen
- * gemaess F2 nur anlassbezogen durch eine Tuer und haben keinen festen Platz.
+ * gemaess F2 nur anlassbezogen durch eine Tuer und haben keinen festen Platz. Agenten, die
+ * laenger als AGENT_PRESENCE_TIMEOUT_MS nicht mehr gesehen wurden (last_seen_at, Issue #17),
+ * erscheinen ebenfalls nicht - ihr Schreibtisch bleibt frei.
*
* @param {Array