loadtoagent 1.3.3 → 1.3.5

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.
Files changed (38) hide show
  1. package/main.js +53 -8
  2. package/package.json +5 -2
  3. package/renderer/app-agent-actions.js +172 -66
  4. package/renderer/app-bootstrap.js +3 -1
  5. package/renderer/app-dashboard.js +36 -7
  6. package/renderer/app-drawer-content.js +177 -59
  7. package/renderer/app-drawer-data.js +7 -3
  8. package/renderer/app-drawer.js +72 -33
  9. package/renderer/app-events-dialogs.js +8 -1
  10. package/renderer/app-events-navigation.js +7 -0
  11. package/renderer/app-events-sessions.js +136 -13
  12. package/renderer/app-graph-model.js +2 -5
  13. package/renderer/app-graph-orchestration.js +7 -18
  14. package/renderer/app-graph-view.js +194 -48
  15. package/renderer/app-management.js +330 -35
  16. package/renderer/app-quality.js +5 -0
  17. package/renderer/app-session-render.js +20 -83
  18. package/renderer/app.js +5 -1
  19. package/renderer/i18n-messages.js +232 -21
  20. package/renderer/index.html +9 -6
  21. package/renderer/styles-components.css +140 -0
  22. package/renderer/styles-control-room.css +1439 -0
  23. package/renderer/styles-management.css +390 -3
  24. package/renderer/styles-responsive-shell.css +5 -0
  25. package/renderer/styles-runtime-overview.css +20 -0
  26. package/renderer/terminal-agent.js +31 -7
  27. package/renderer/terminal.js +4 -1
  28. package/src/agentMonitor/claudeParser.js +8 -4
  29. package/src/agentMonitor/codexParser.js +5 -4
  30. package/src/agentMonitor/genericParser.js +7 -6
  31. package/src/agentMonitor.js +44 -4
  32. package/src/attentionNotifier.js +3 -0
  33. package/src/ipc/registerTerminalIpc.js +2 -2
  34. package/src/monitorWorker.js +1 -1
  35. package/src/nodePtyRuntime.js +58 -0
  36. package/src/processMonitor.js +68 -7
  37. package/src/terminalHost.js +104 -5
  38. package/src/terminalManager.js +21 -3
@@ -4,7 +4,14 @@ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
4
 
5
5
  window.LoadToAgentAppFactories.createManagement = function createManagement(context = {}) {
6
6
  const t = (key, params) => window.LoadToAgentI18n.t(key, params);
7
- const { $, esc, state, providerInfo, timeAgo, readablePreview = value => ({ text: String(value || "") }) } = context;
7
+ const {
8
+ $, esc, state, providerInfo, timeAgo,
9
+ readablePreview = value => ({ text: String(value || "") }),
10
+ currentActivity = session => ({ title: session.statusDetail || "", detail: "" }),
11
+ latestWorkCopy = session => session.statusDetail || "",
12
+ isLiveSession = session => ["starting", "running"].includes(session && session.status),
13
+ agentRoleLabel = value => String(value || ""),
14
+ } = context;
8
15
 
9
16
  const attentionLabel = kind => t(`management.attention.${kind || "response"}`);
10
17
  const healthLabel = level => t(`management.health.${level || "unknown"}`);
@@ -14,6 +21,7 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
14
21
  const ALWAYS_VISIBLE_STATUSES = new Set(["starting", "running"]);
15
22
  const CURRENT_RISK_STATUSES = new Set(["starting", "running", "waiting", "paused", "failed"]);
16
23
  const RESPONSE_ATTENTION_KINDS = new Set(["approval", "decision", "input", "response"]);
24
+ const ACTIONABLE_RISK_SIGNALS = new Set(["run-failed", "run-paused", "stalled", "waiting-too-long", "repeated-failures"]);
17
25
  const needsUserResponse = session => Boolean(
18
26
  session.attention?.required && RESPONSE_ATTENTION_KINDS.has(session.attention.kind),
19
27
  );
@@ -29,10 +37,17 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
29
37
  const activityAt = sessionActivityTimestamp(session);
30
38
  return Boolean(activityAt && Math.max(0, Number(now) - activityAt) <= RECENT_SESSION_WINDOW_MS);
31
39
  };
40
+ const actionableRiskLevel = session => {
41
+ const signals = (session.health?.signals || []).filter(signal => ACTIONABLE_RISK_SIGNALS.has(signal.code));
42
+ const severities = signals.map(signal => signal.severity || session.health?.level || "");
43
+ if (severities.includes("critical")) return "critical";
44
+ if (severities.includes("warning")) return "warning";
45
+ return "";
46
+ };
32
47
  const hasCurrentRisk = (session, now = Date.now()) => Boolean(
33
48
  isRecentSession(session, now)
34
49
  && CURRENT_RISK_STATUSES.has(session.status)
35
- && ["critical", "warning"].includes(session.health?.level),
50
+ && actionableRiskLevel(session),
36
51
  );
37
52
  const needsManagementReview = (session, now = Date.now()) => Boolean(
38
53
  isRecentSession(session, now) && (needsUserResponse(session) || hasCurrentRisk(session, now)),
@@ -63,18 +78,79 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
63
78
  const firstSentence = plain.match(/^.*?(?:[.!?。!?](?=\s|$)|$)/)?.[0]?.trim() || plain;
64
79
  return readablePreview(firstSentence || t("management.signal_unavailable"), 104).text;
65
80
  };
81
+ const flowExcerpt = (value, limit = 420) => {
82
+ const plain = String(value || "")
83
+ .replace(/```[\s\S]*?```/g, " ")
84
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
85
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
86
+ .replace(/`([^`]*)`/g, "$1")
87
+ .replace(/^\s{0,3}(?:#{1,6}|>|[-*+]|\d+[.)])\s+/gm, "")
88
+ .replace(/\*\*|__|~~/g, "")
89
+ .replace(/\s+/g, " ")
90
+ .trim();
91
+ return readablePreview(plain || t("management.signal_unavailable"), limit).text;
92
+ };
93
+ const latestAgentReply = session => {
94
+ const message = [...(session.messages || [])].reverse()
95
+ .find(row => row && row.role === "assistant" && String(row.text || "").trim());
96
+ const communication = [...(session.collaboration?.communications || [])].reverse()
97
+ .find(row => row && row.kind === "result" && String(row.text || "").trim());
98
+ return flowExcerpt(message?.text
99
+ || communication?.text
100
+ || session.result
101
+ || session.outcome?.summary
102
+ || session.attention?.summary
103
+ || session.statusDetail);
104
+ };
105
+ const attentionFlow = session => {
106
+ const attention = session.attention || {};
107
+ const responseRequired = needsUserResponse(session);
108
+ const kind = responseRequired ? attention.kind : (session.status === "failed" ? "error" : session.status === "paused" ? "paused" : "risk");
109
+ const check = t(`management.flow_check_${kind}`);
110
+ const action = t(`management.flow_action_${kind}`);
111
+ const reply = t(`management.flow_reply_${kind}`);
112
+ const expected = t(`management.flow_expected_${kind}`);
113
+ const replySource = responseRequired
114
+ ? (attention.summary || session.statusDetail || latestAgentReply(session))
115
+ : ((session.health?.signals || []).map(signal => {
116
+ const label = signalLabel(signal.code);
117
+ return signal.detail ? `${label} · ${signal.detail}` : label;
118
+ }).join(" / ") || session.statusDetail || latestAgentReply(session));
119
+ return {
120
+ responseRequired,
121
+ kind,
122
+ agentReply: latestAgentReply(session),
123
+ reason: flowExcerpt(replySource, 260),
124
+ check,
125
+ action,
126
+ reply,
127
+ expected,
128
+ };
129
+ };
130
+ const supervisionFreshnessScore = (timestamp, now = Date.now()) => {
131
+ if (timestamp == null || timestamp === "") return -300;
132
+ const numericTimestamp = typeof timestamp === "number" ? timestamp : Date.parse(timestamp || 0);
133
+ if (!Number.isFinite(numericTimestamp) || numericTimestamp <= 0) return -300;
134
+ const age = Math.max(0, Number(now) - numericTimestamp);
135
+ if (age <= 60 * 1000) return 180;
136
+ if (age <= 5 * 60 * 1000) return 140;
137
+ if (age <= 15 * 60 * 1000) return 95;
138
+ if (age <= 60 * 60 * 1000) return 45;
139
+ if (age <= 6 * 60 * 60 * 1000) return 0;
140
+ return -260;
141
+ };
66
142
 
67
143
  function managementBucket(session, now = Date.now()) {
68
144
  if (!needsManagementReview(session, now)) return "healthy";
69
- if (hasCurrentRisk(session, now) && session.health?.level === "critical") return "critical";
70
- if (hasCurrentRisk(session, now) && session.health?.level === "warning") return "warning";
145
+ if (hasCurrentRisk(session, now) && actionableRiskLevel(session) === "critical") return "critical";
146
+ if (hasCurrentRisk(session, now) && actionableRiskLevel(session) === "warning") return "warning";
71
147
  if (needsUserResponse(session)) return "attention";
72
148
  return "healthy";
73
149
  }
74
150
 
75
151
  function matchesManagementFilter(session, filter, now = Date.now()) {
76
- if (filter === "critical") return hasCurrentRisk(session, now) && session.health?.level === "critical";
77
- if (filter === "warning") return hasCurrentRisk(session, now) && session.health?.level === "warning";
152
+ if (filter === "critical") return hasCurrentRisk(session, now) && actionableRiskLevel(session) === "critical";
153
+ if (filter === "warning") return hasCurrentRisk(session, now) && actionableRiskLevel(session) === "warning";
78
154
  if (filter === "attention") return isRecentSession(session, now) && needsUserResponse(session);
79
155
  return needsManagementReview(session, now);
80
156
  }
@@ -146,21 +222,24 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
146
222
  const health = session.health || { level: "unknown", signals: [] };
147
223
  const evidence = session.evidence || {};
148
224
  const cardLabel = attention.required ? attentionLabel(attention.kind) : healthLabel(health.level);
149
- const statusSignals = (health.signals || []).map(signal => {
150
- const label = signalLabel(signal.code);
151
- return signal.detail ? `${label} · ${signal.detail}` : label;
152
- }).join(" / ");
153
- const reason = attention.required
154
- ? (attention.summary || session.statusDetail || t("management.response_needed"))
155
- : (statusSignals || session.statusDetail || t("management.signal_unavailable"));
225
+ const flow = attentionFlow(session);
226
+ const canDraft = Boolean(session.controlCapabilities?.sendInstruction && flow.kind !== "approval");
227
+ const draftAction = canDraft
228
+ ? `<button type="button" class="attention-draft-action" data-attention-session-id="${esc(session.id)}" data-attention-draft="${esc(flow.reply)}">${esc(t("management.flow_use_reply"))}</button>`
229
+ : "";
156
230
  return `<article class="attention-card ${esc(attention.kind || "response")}" data-management-session="${esc(session.id)}" style="--management-provider:${provider.accent}">
157
231
  <header><span class="provider-mark">${esc(provider.mark)}</span><div><small>${esc(provider.label)} · ${esc(cardLabel)}</small><h3>${esc(session.title)}</h3></div><em class="confidence ${esc(evidence.confidence || "low")}">${esc(evidenceLabel(evidence.confidence))}</em></header>
158
- <div class="attention-reason"><span>${esc(t(attention.required ? "management.requested_action" : "management.health_title"))}</span><p>${esc(reason)}</p><small>${esc(attention.requestedAt ? timeAgo(attention.requestedAt) : health.lastActivityAt ? timeAgo(health.lastActivityAt) : t("management.signal_unavailable"))}</small></div>
159
- ${progressHtml(session, true)}
160
- ${healthHtml(session, true)}
232
+ <div class="attention-decision-flow" data-attention-flow="${esc(flow.kind)}" aria-label="${esc(t("management.flow_label"))}">
233
+ <section class="agent-reply"><span><i>1</i>${esc(t("management.flow_agent_reply"))}</span><blockquote>${esc(flow.agentReply)}</blockquote><small><b>${esc(t("management.flow_why_here"))}</b>${esc(flow.reason)}</small></section>
234
+ <i class="attention-flow-arrow" aria-hidden="true">→</i>
235
+ <section class="user-decision"><span><i>2</i>${esc(t("management.flow_my_check"))}</span><b>${esc(flow.check)}</b><p>${esc(flow.action)}</p></section>
236
+ <i class="attention-flow-arrow" aria-hidden="true">→</i>
237
+ <section class="agent-next"><span><i>3</i>${esc(t("management.flow_my_reply"))}</span><b>${esc(flow.reply)}</b><small><strong>${esc(t("management.flow_after_reply"))}</strong>${esc(flow.expected)}</small>${draftAction}</section>
238
+ </div>
161
239
  ${quickActionsHtml(session)}
162
240
  ${session.controlCapabilities?.sendInstruction ? context.agentCommandComposer(session) : ""}
163
241
  ${controlButtonsHtml(session)}
242
+ <details class="attention-evidence-details"><summary><span>${esc(t("management.flow_evidence"))}</span><small>${esc(t("management.flow_evidence_hint"))}</small><i aria-hidden="true">⌄</i></summary><div>${progressHtml(session, true)}${healthHtml(session, true)}</div></details>
164
243
  </article>`;
165
244
  }
166
245
 
@@ -186,40 +265,255 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
186
265
  return sessions.length;
187
266
  }
188
267
 
268
+ function renderHomeAttention(section) {
269
+ const sessions = typeof context.graphFilteredSessions === "function"
270
+ ? context.graphFilteredSessions()
271
+ : (state.snapshot?.sessions || []);
272
+ const candidates = sessions.filter(needsManagementReview);
273
+ const score = session => {
274
+ if (matchesManagementFilter(session, "critical")) return 3;
275
+ if (matchesManagementFilter(session, "attention")) return 2;
276
+ if (matchesManagementFilter(session, "warning")) return 1;
277
+ return 0;
278
+ };
279
+ const ordered = [...candidates].sort((a, b) =>
280
+ score(b) - score(a)
281
+ || Date.parse(b.attention?.requestedAt || b.updatedAt || 0) - Date.parse(a.attention?.requestedAt || a.updatedAt || 0),
282
+ );
283
+ if (!ordered.length) {
284
+ section.innerHTML = "";
285
+ section.classList.add("hidden");
286
+ section.setAttribute("aria-hidden", "true");
287
+ return 0;
288
+ }
289
+ section.classList.remove("hidden");
290
+ section.removeAttribute("aria-hidden");
291
+ const shown = ordered.slice(0, 3);
292
+ const item = session => {
293
+ const provider = providerInfo(session.provider);
294
+ const tone = matchesManagementFilter(session, "critical") ? "critical" : matchesManagementFilter(session, "attention") ? "attention" : "warning";
295
+ const label = session.attention?.required
296
+ ? attentionLabel(session.attention.kind)
297
+ : healthLabel(session.health?.level);
298
+ const summary = prioritySummary(session.attention?.summary || session.statusDetail || latestAgentReply(session));
299
+ return `<button type="button" class="home-attention-item ${tone}" data-open-session="${esc(session.id)}" style="--management-provider:${provider.accent}" aria-label="${esc(`${label}: ${session.title}. ${summary}`)}">
300
+ <span class="home-attention-dot" aria-hidden="true"></span>
301
+ <span><small>${esc(label)} · ${esc(provider.label)}</small><b>${esc(readablePreview(session.title, 54).text)}</b><em title="${esc(summary)}">${esc(summary)}</em></span>
302
+ <time>${esc(timeAgo(session.attention?.requestedAt || session.updatedAt))}</time><i aria-hidden="true">→</i>
303
+ </button>`;
304
+ };
305
+ const overflow = Math.max(0, ordered.length - shown.length);
306
+ const compactOverflow = Math.max(0, ordered.length - 1);
307
+ section.innerHTML = `<div class="home-attention-strip ${ordered.length ? "has-items" : "is-clear"}" data-home-attention="${ordered.length}">
308
+ <button type="button" class="home-attention-title" data-management-filter="all">
309
+ <span class="home-attention-signal" aria-hidden="true"><i>!</i></span>
310
+ <span><small>${esc(t("control.attention_eyebrow"))}</small><b>${esc(t("control.attention_title", { count: ordered.length }))}</b></span>
311
+ <strong>${ordered.length}</strong>
312
+ </button>
313
+ <div class="home-attention-list">${shown.map(item).join("")}</div>
314
+ ${overflow ? `<button type="button" class="home-attention-more" data-management-filter="all">${esc(t("control.attention_more", { count: overflow }))} →</button>` : compactOverflow ? `<button type="button" class="home-attention-more compact-only" data-management-filter="all">${esc(t("control.attention_more", { count: compactOverflow }))} →</button>` : ""}
315
+ </div>`;
316
+ return ordered.length;
317
+ }
318
+
189
319
  function renderOperationsOverview() {
190
320
  const section = $("#operationsOverview");
191
321
  if (!section) return;
322
+ renderHomeAttention(section);
323
+ return;
192
324
  const sessions = typeof context.graphFilteredSessions === "function"
193
325
  ? context.graphFilteredSessions()
194
326
  : (state.snapshot?.sessions || []);
327
+ const byId = new Map(sessions.map(session => [session.id, session]));
195
328
  const critical = sessions.filter(session => matchesManagementFilter(session, "critical"));
196
329
  const warning = sessions.filter(session => matchesManagementFilter(session, "warning"));
197
330
  const responses = sessions.filter(session => matchesManagementFilter(session, "attention"));
198
331
  const flaggedIds = new Set([...critical, ...warning, ...responses].map(session => session.id));
199
332
  const clear = sessions.filter(session => !flaggedIds.has(session.id));
200
- const priority = [...new Map([...critical, ...warning, ...responses].map(session => [session.id, session])).values()].slice(0, 4);
201
333
  const reviewCount = flaggedIds.size;
334
+ const descendantCache = new Map();
335
+ const descendants = session => {
336
+ if (descendantCache.has(session.id)) return descendantCache.get(session.id);
337
+ const found = [];
338
+ const queue = [...(session.childIds || [])];
339
+ const visited = new Set();
340
+ while (queue.length) {
341
+ const id = queue.shift();
342
+ if (!id || visited.has(id)) continue;
343
+ visited.add(id);
344
+ const child = byId.get(id);
345
+ if (!child) continue;
346
+ found.push(child);
347
+ queue.push(...(child.childIds || []));
348
+ }
349
+ descendantCache.set(session.id, found);
350
+ return found;
351
+ };
352
+ const runningExecutions = session => (session.executions || []).filter(execution => execution.status === "running");
353
+ const supervisionCandidates = sessions.filter(session =>
354
+ isLiveSession(session)
355
+ || ["paused", "waiting"].includes(session.status)
356
+ || runningExecutions(session).length > 0,
357
+ );
358
+ const controlModeCache = new Map();
359
+ const controlMode = session => {
360
+ if (controlModeCache.has(session.id)) return controlModeCache.get(session.id);
361
+ const targets = typeof context.agentCommandTargets === "function" ? context.agentCommandTargets(session) : [];
362
+ const mode = typeof context.agentControlMode === "function" ? context.agentControlMode(session, targets) : "ended";
363
+ controlModeCache.set(session.id, mode);
364
+ return mode;
365
+ };
366
+ const directControl = mode => mode === "direct";
367
+ const recoverableControl = mode => ["handoff", "resume", "origin-resume"].includes(mode);
368
+ const controlTone = mode => directControl(mode) ? "ready" : recoverableControl(mode) ? "recover" : "observe";
369
+ const rankingNow = Date.now();
370
+ const operationTimestamp = session => Math.max(0, ...[
371
+ session.progress?.lastActivityAt,
372
+ session.health?.lastActivityAt,
373
+ session.updatedAt,
374
+ ...(session.executions || []).map(execution => execution.updatedAt || execution.startedAt),
375
+ ].map(value => Date.parse(value || 0)).filter(Number.isFinite));
376
+ const freshnessScore = session => supervisionFreshnessScore(operationTimestamp(session), rankingNow);
377
+ const rankScore = session => {
378
+ const children = descendants(session);
379
+ const activeChildren = children.filter(isLiveSession).length;
380
+ const executionCount = runningExecutions(session).length;
381
+ const mode = controlMode(session);
382
+ return freshnessScore(session)
383
+ + (isLiveSession(session) ? 260 : ["paused", "waiting"].includes(session.status) ? 160 : 0)
384
+ + (!session.parentId ? 30 : 0)
385
+ + activeChildren * 75
386
+ + children.length * 5
387
+ + executionCount * 90
388
+ + (directControl(mode) ? 40 : recoverableControl(mode) ? 15 : 0)
389
+ + ((session.runtimePresence || []).length ? 15 : 0);
390
+ };
391
+ const ordered = [...supervisionCandidates].sort((a, b) =>
392
+ rankScore(b) - rankScore(a)
393
+ || Date.parse(b.updatedAt || 0) - Date.parse(a.updatedAt || 0)
394
+ || String(a.title || "").localeCompare(String(b.title || "")),
395
+ );
396
+ const selected = ordered.find(session => session.id === state.supervisionFocusId) || ordered[0] || null;
397
+ if (!state.supervisionFocusId && selected) state.supervisionFocusId = selected.id;
398
+ const selectedRank = selected ? ordered.findIndex(session => session.id === selected.id) + 1 : 0;
399
+ const selectedIsRecommended = selectedRank === 1;
400
+ const liveRoots = ordered.filter(session => !session.parentId && isLiveSession(session));
401
+ const liveHelpers = ordered.filter(session => session.parentId && isLiveSession(session));
402
+ const runningExecutionCount = ordered.reduce((sum, session) => sum + runningExecutions(session).length, 0);
403
+ const readyControlCount = ordered.filter(session => directControl(controlMode(session))).length;
404
+
405
+ const supervisionReason = session => {
406
+ const activeChildren = descendants(session).filter(isLiveSession).length;
407
+ const executionCount = runningExecutions(session).length;
408
+ const mode = controlMode(session);
409
+ const timestamp = operationTimestamp(session);
410
+ const factors = [t("management.supervision_factor_activity", { time: timestamp ? timeAgo(timestamp) : t("management.signal_unavailable") })];
411
+ if (activeChildren) factors.push(t("management.supervision_factor_helpers", { count: activeChildren }));
412
+ if (executionCount) factors.push(t("management.supervision_factor_executions", { count: executionCount }));
413
+ if (directControl(mode)) factors.push(t("management.supervision_factor_input"));
414
+ else if (recoverableControl(mode)) factors.push(t("management.supervision_factor_recover"));
415
+ if (session.parentId) factors.push(t("management.supervision_factor_delegated"));
416
+ return factors.join(" · ");
417
+ };
418
+ const statusLabel = status => ({
419
+ starting: t("ui.preparing"), running: t("ui.working"), paused: t("management.attention.paused"),
420
+ waiting: t("ui.waiting_for_review"), failed: t("ui.problem"), completed: t("ui.completed"),
421
+ })[status] || status;
422
+ const controlLabel = mode => t(`management.supervision_control_${mode}`);
423
+ const queue = ordered.map(session => {
424
+ const index = ordered.findIndex(candidate => candidate.id === session.id);
425
+ const provider = providerInfo(session.provider);
426
+ const activity = currentActivity(session);
427
+ const children = descendants(session).filter(isLiveSession).length;
428
+ const executions = runningExecutions(session).length;
429
+ const mode = controlMode(session);
430
+ const selectedClass = session.id === selected?.id ? "selected" : "";
431
+ return `<button type="button" class="supervision-queue-item ${selectedClass}" data-supervision-focus="${esc(session.id)}" aria-pressed="${session.id === selected?.id ? "true" : "false"}" style="--management-provider:${provider.accent}">
432
+ <span class="supervision-rank">${index + 1}</span>
433
+ <span class="provider-mark">${esc(provider.mark)}</span>
434
+ <span class="supervision-queue-copy"><small>${esc(session.parentId ? t("management.supervision_helper_agent") : t("management.supervision_main_agent"))} · ${esc(provider.label)}</small><b>${esc(readablePreview(session.agentName || session.title, 62).text)}</b><em>${esc(prioritySummary(activity.title || session.statusDetail))}</em><i>${esc(supervisionReason(session))}</i></span>
435
+ <span class="supervision-queue-meta"><b class="${controlTone(mode)}">${esc(controlLabel(mode))}</b><small>${children ? esc(t("management.supervision_live_helpers_short", { count: children })) : executions ? esc(t("management.supervision_executions_short", { count: executions })) : esc(timeAgo(session.updatedAt))}</small></span>
436
+ </button>`;
437
+ }).join("");
438
+
439
+ let primary = "";
440
+ if (selected) {
441
+ const provider = providerInfo(selected.provider);
442
+ const parent = selected.parentId ? byId.get(selected.parentId) : null;
443
+ const children = descendants(selected);
444
+ const activeChildren = children.filter(isLiveSession);
445
+ const executions = runningExecutions(selected);
446
+ const activity = currentActivity(selected);
447
+ const mode = controlMode(selected);
448
+ const delegation = selected.delegation || {};
449
+ const goal = selected.parentId
450
+ ? (delegation.assignment || delegation.taskName || selected.taskName || selected.title)
451
+ : (selected.sharedGoal || selected.title);
452
+ const downstream = activeChildren.length
453
+ ? t("management.supervision_downstream_helpers", { count: activeChildren.length, names: activeChildren.slice(0, 3).map(child => child.agentName || providerInfo(child.provider).label).join(" · ") })
454
+ : executions.length
455
+ ? t("management.supervision_downstream_executions", { count: executions.length, names: executions.slice(0, 3).map(execution => execution.label || execution.runtime || execution.tool).join(" · ") })
456
+ : t("management.supervision_downstream_none");
457
+ const role = selected.parentId
458
+ ? t("management.supervision_helper_role", { role: selected.agentRole ? agentRoleLabel(selected.agentRole) : t("ui.assistance") })
459
+ : t("management.supervision_owner_role");
460
+ const focusLabel = selectedIsRecommended
461
+ ? t("management.supervision_primary")
462
+ : t("management.supervision_selected", { rank: selectedRank });
463
+ const reasonLabel = selectedIsRecommended
464
+ ? t("management.supervision_why_first")
465
+ : t("management.supervision_why_selected", { rank: selectedRank });
466
+ primary = `<article class="supervision-primary" data-supervision-session="${esc(selected.id)}" style="--management-provider:${provider.accent}">
467
+ <header class="supervision-primary-head">
468
+ <span class="provider-mark">${esc(provider.mark)}</span>
469
+ <div><small data-supervision-focus-kind="${selectedIsRecommended ? "recommended" : "selected"}">${esc(focusLabel)} · ${esc(role)}</small><h3>${esc(readablePreview(selected.agentName || selected.title, 96).text)}</h3><p>${esc(provider.label)} · ${esc(selected.model || t("session.model_unknown"))}${parent ? ` · ${esc(t("management.supervision_parent", { parent: readablePreview(parent.agentName || parent.title, 46).text }))}` : ""}</p></div>
470
+ <span class="supervision-status ${esc(selected.status || "")}"><i aria-hidden="true"></i>${esc(statusLabel(selected.status))}</span>
471
+ </header>
472
+ <div class="supervision-mobile-now">
473
+ <div><span>${esc(t("management.supervision_current_behavior"))}</span><b>${esc(prioritySummary(activity.title || selected.statusDetail))}</b><small>${esc(prioritySummary(latestWorkCopy(selected) || activity.detail || selected.statusDetail))}</small></div>
474
+ <button type="button" data-supervision-intervention-open="${esc(selected.id)}">${esc(t("management.supervision_intervention_mobile"))}</button>
475
+ </div>
476
+ <div class="supervision-watch-reason"><span>${esc(reasonLabel)}</span><b>${esc(supervisionReason(selected))}</b><small>${esc(t("management.supervision_updated", { time: timeAgo(selected.updatedAt) }))}</small></div>
477
+ <div class="supervision-behavior" aria-label="${esc(t("management.supervision_behavior_trace"))}">
478
+ <section><span><i>1</i>${esc(t("management.supervision_goal"))}</span><b>${esc(prioritySummary(goal))}</b><small>${esc(role)}</small></section>
479
+ <i class="supervision-flow-arrow" aria-hidden="true">→</i>
480
+ <section class="current"><span><i>2</i>${esc(t("management.supervision_current_behavior"))}</span><b>${esc(prioritySummary(activity.title || selected.statusDetail))}</b><small>${esc(prioritySummary(latestWorkCopy(selected) || activity.detail || selected.statusDetail))}</small></section>
481
+ <i class="supervision-flow-arrow" aria-hidden="true">→</i>
482
+ <section><span><i>3</i>${esc(t("management.supervision_downstream"))}</span><b>${esc(downstream)}</b><small>${esc(t("management.supervision_downstream_counts", { helpers: activeChildren.length, executions: executions.length }))}</small></section>
483
+ </div>
484
+ <div class="supervision-control-strip">
485
+ <div><span>${esc(t("management.supervision_control_channel"))}</span><b class="${controlTone(mode)}"><i aria-hidden="true"></i>${esc(controlLabel(mode))}</b><small>${esc(t(`management.supervision_control_help_${mode}`))}</small></div>
486
+ <div class="supervision-control-actions">
487
+ <button type="button" data-graph-focus="${esc(selected.id)}">${esc(t("management.supervision_open_flow"))}</button>
488
+ <button type="button" data-open-session="${esc(selected.id)}">${esc(t("management.supervision_open_detail"))}</button>
489
+ </div>
490
+ </div>
491
+ ${controlButtonsHtml(selected)}
492
+ ${typeof context.agentCommandComposer === "function" ? `<details class="supervision-intervention" data-disclosure-key="supervision:command:${esc(selected.id)}"><summary><span><b>${esc(t("management.supervision_intervention"))}</b><small>${esc(t("management.supervision_intervention_hint"))}</small></span><i aria-hidden="true">⌄</i></summary>${context.agentCommandComposer(selected)}</details>` : ""}
493
+ </article>`;
494
+ }
495
+
202
496
  section.innerHTML = `<header>
203
497
  <div class="operations-heading">
204
- <span class="operations-signal" aria-hidden="true">!</span>
205
- <div class="operations-heading-copy"><p>${esc(t("management.operations_eyebrow"))}</p><h2>${esc(t("management.operations_title"))}</h2><span>${esc(t("management.operations_description"))}</span></div>
206
- </div>
207
- <div class="operations-review-total"><strong>${reviewCount}</strong><span>${esc(t("management.operations_review_count"))}</span></div>
208
- </header>
209
- <div class="operations-metrics" aria-label="${esc(t("management.operations_severity_buckets"))}">
210
- <button type="button" data-management-filter="critical" data-management-metric="critical"><span>${esc(t("management.health.critical"))}</span><b>${critical.length}</b></button>
211
- <button type="button" data-management-filter="warning" data-management-metric="warning"><span>${esc(t("management.health.warning"))}</span><b>${warning.length}</b></button>
212
- <button type="button" data-management-filter="attention" data-management-metric="attention"><span>${esc(t("management.health.attention"))}</span><b>${responses.length}</b></button>
213
- <div data-management-metric="clear"><span>${esc(t("management.recent_clear"))}</span><b>${clear.length}</b></div>
498
+ <span class="operations-signal" aria-hidden="true"><i></i><i></i><i></i></span>
499
+ <div class="operations-heading-copy"><p>${esc(t("management.supervision_eyebrow"))}</p><h2>${esc(t("management.supervision_title"))}</h2><span>${esc(t("management.supervision_description"))}</span></div>
214
500
  </div>
215
- ${priority.length ? `<div class="operations-priority">${priority.map(session => {
216
- const health = session.health || {};
217
- const responseSummary = needsUserResponse(session) ? session.attention?.summary : "";
218
- const summary = prioritySummary(responseSummary
219
- || (health.signals || []).map(signal => signalLabel(signal.code)).join(" · ")
220
- || t("management.signal_unavailable"));
221
- return `<button type="button" data-open-session="${esc(session.id)}"><i class="${esc(managementBucket(session))}"></i><span><b>${esc(session.title)}</b><small>${esc(summary)}</small></span><em>${esc(timeAgo(health.lastActivityAt || session.updatedAt))}</em></button>`;
222
- }).join("")}</div>${reviewCount > priority.length ? `<div class="operations-more"><button type="button" data-management-filter="all"><span>${esc(t("management.operations_view_all"))}</span><b>${reviewCount}</b><i aria-hidden="true">→</i></button></div>` : ""}` : `<p class="operations-clear">${esc(t("management.operations_clear"))}</p>`}`;
501
+ <button type="button" class="operations-review-total" data-management-filter="all"><strong>${reviewCount}</strong><span>${esc(t("management.supervision_attention_open"))}</span><i aria-hidden="true">→</i></button>
502
+ </header>
503
+ <div class="supervision-metrics" aria-label="${esc(t("management.supervision_metrics_label"))}">
504
+ <div><span>${esc(t("management.supervision_active_roots"))}</span><b>${liveRoots.length}</b></div>
505
+ <div><span>${esc(t("management.supervision_active_helpers"))}</span><b>${liveHelpers.length}</b></div>
506
+ <div><span>${esc(t("management.supervision_running_executions"))}</span><b>${runningExecutionCount}</b></div>
507
+ <div><span>${esc(t("management.supervision_control_ready"))}</span><b>${readyControlCount}</b></div>
508
+ </div>
509
+ ${selected ? `<div class="supervision-console"><aside class="supervision-queue"><header><div><span>${esc(t("management.supervision_queue"))}</span><b>${esc(t("management.supervision_queue_hint"))}</b></div><strong>${ordered.length}</strong></header><div>${queue}</div></aside>${primary}</div>` : `<div class="supervision-empty"><span aria-hidden="true">◎</span><b>${esc(t("management.supervision_empty"))}</b><small>${esc(t("management.supervision_empty_detail"))}</small></div>`}
510
+ <div class="supervision-attention-bar" aria-label="${esc(t("management.operations_severity_buckets"))}">
511
+ <span>${esc(t("management.supervision_attention_summary"))}</span>
512
+ <button type="button" data-management-filter="attention" data-management-metric="attention"><i></i>${esc(t("management.health.attention"))}<b>${responses.length}</b></button>
513
+ <button type="button" data-management-filter="critical" data-management-metric="critical"><i></i>${esc(t("management.health.critical"))}<b>${critical.length}</b></button>
514
+ <button type="button" data-management-filter="warning" data-management-metric="warning"><i></i>${esc(t("management.health.warning"))}<b>${warning.length}</b></button>
515
+ <span class="supervision-clear" data-management-metric="clear">${esc(t("management.recent_clear"))}<b>${clear.length}</b></span>
516
+ </div>`;
223
517
  }
224
518
 
225
519
  function outcomeHtml(session) {
@@ -253,5 +547,6 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
253
547
  progressHtml,
254
548
  renderAttentionInbox,
255
549
  renderOperationsOverview,
550
+ supervisionFreshnessScore,
256
551
  };
257
552
  };
@@ -94,11 +94,15 @@ window.LoadToAgentAppFactories.createQualityEnhancements = function createQualit
94
94
  ? dashboard.workspace
95
95
  : "all";
96
96
  state.sort = ["recent", "tokens", "context"].includes(dashboard.sort) ? dashboard.sort : "recent";
97
+ state.sessionOrder = Array.isArray(dashboard.sessionOrder)
98
+ ? dashboard.sessionOrder.filter(id => typeof id === "string" && id.length <= 500).slice(0, 1_000)
99
+ : [];
97
100
  } else {
98
101
  state.search = "";
99
102
  state.providerFilters.clear();
100
103
  state.workspace = "all";
101
104
  state.sort = "recent";
105
+ state.sessionOrder = [];
102
106
  }
103
107
  const search = $("#searchInput");
104
108
  if (search) search.value = state.search;
@@ -126,6 +130,7 @@ window.LoadToAgentAppFactories.createQualityEnhancements = function createQualit
126
130
  providers: [...state.providerFilters],
127
131
  workspace: String(state.workspace || "all").slice(0, 2_000),
128
132
  sort: ["recent", "tokens", "context"].includes(state.sort) ? state.sort : "recent",
133
+ sessionOrder: (state.sessionOrder || []).filter(id => typeof id === "string" && id.length <= 500).slice(0, 1_000),
129
134
  };
130
135
  try {
131
136
  localStorage.setItem(DASHBOARD_STORAGE_KEY, JSON.stringify(value));
@@ -64,106 +64,42 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
64
64
 
65
65
  function sessionCard(session, opts = {}) {
66
66
  const provider = providerInfo(session.provider);
67
- const usage = session.usage || {};
68
- const context = session.context || {};
69
67
  const activity = currentActivity(session);
70
- const running = session.status === "running" || session.status === "starting";
71
- const children = session.childIds || [];
72
- const model = session.model || t("session.model_unknown");
73
- const contextPercent = Math.max(0, Math.min(100, Number(context.percent || 0)));
74
- const remaining = context.window ? Math.max(0, Number(context.window) - Number(context.used || 0)) : 0;
75
- const gaugeTone = contextPercent >= 90 ? "critical" : contextPercent >= 75 ? "warning" : "safe";
76
68
  const conversation = recentConversation(session);
77
- const runtime = session.runtimePresence || [];
78
69
  const titlePreview = readablePreview(session.title, 96);
79
- const activityCopy = latestWorkCopy(session) || window.LoadToAgentI18n.observedText(session.statusDetail) || t("session.waiting_for_new_event");
80
- const activityPreview = readablePreview(activityCopy, 116);
70
+ const latest = conversation[conversation.length - 1];
71
+ const activityCopy = latest?.text || latestWorkCopy(session) || window.LoadToAgentI18n.observedText(session.statusDetail) || t("session.waiting_for_new_event");
72
+ const activityPreview = readablePreview(activityCopy, 138);
81
73
  const accessibleId = `session-${String(session.id || "").replace(/[^a-zA-Z0-9_-]/g, "-")}`;
82
74
  const originPath = sessionOriginPath(session);
83
75
  const originLabel = sessionWorkspaceLabel(session);
84
- return `<article class="session-card ${opts.live ? "live-card" : ""} ${statusClass(session.status)} ${session.parentId ? "subagent" : ""}"
76
+ return `<article class="session-card session-record ${opts.live ? "live-card" : ""} ${statusClass(session.status)} ${session.parentId ? "subagent" : ""}"
85
77
  data-session-id="${esc(session.id)}"
86
78
  data-motion-key="session:${esc(session.id)}"
87
- data-motion-value="${esc(session.updatedAt || "")}:${usage.total || 0}:${esc(session.status || "")}"
79
+ data-motion-value="${esc(session.updatedAt || "")}:${esc(session.status || "")}"
88
80
  style="${providerStyle(session.provider)}"
89
81
  role="button" tabindex="0"
90
82
  aria-labelledby="${accessibleId}-title" aria-describedby="${accessibleId}-summary">
91
83
  <div class="card-head">
92
84
  <span class="provider-mark">${esc(provider.mark)}</span>
93
- <div class="card-head-main"><div class="card-provider-line"><b>${esc(provider.label)}</b><span>${esc(provider.company)}</span></div></div>
94
- ${executionModeBadge(session, true)}
85
+ <div class="card-head-main"><div class="card-provider-line"><b>${esc(provider.label)}</b><span>${esc(session.model || t("session.model_unknown"))}</span></div></div>
95
86
  <span class="status-pill ${statusClass(session.status)}">${esc(STATUS[session.status] || session.status)}</span>
96
87
  </div>
97
88
  <h3 id="${accessibleId}-title" class="card-title" title="${esc(titlePreview.full)}">${esc(titlePreview.text)}</h3>
98
- <div class="card-subtitle">
99
- <span>${esc(model)}</span>
100
- <i>
101
- </i>
102
- <span class="origin-project" title="${esc(isProjectlessSession(session) ? window.LoadToAgentI18n.t("ui.session_not_linked_to_a_specific_project") : originPath)}"
89
+ <div class="card-subtitle"><span class="origin-project" title="${esc(isProjectlessSession(session) ? window.LoadToAgentI18n.t("ui.session_not_linked_to_a_specific_project") : originPath)}"
103
90
  aria-label="${esc(t("project.origin_named", { name: originLabel }))}">
104
- <small>${esc(t("project.origin"))}</small><b>${esc(originLabel)}</b>
105
- </span>${
106
- session.agentName
107
- ? `<i>
108
- </i>
109
- <span>${esc(session.agentName)}</span>`
110
- : ""
111
- }</div>
112
- <div id="${accessibleId}-summary" class="now-strip ${running ? "is-live" : ""}">
91
+ <small>${esc(t("project.origin"))}</small><b>${esc(originLabel)}</b></span></div>
92
+ <div id="${accessibleId}-summary" class="now-strip">
113
93
  <span class="now-strip-icon">${statusIcon(activity.type)}</span>
114
- <div><b>${running ? `${esc(t("session.now"))}: ` : ""}${esc(activity.title)}</b><span title="${esc(activityPreview.full)}">${esc(activityPreview.text)}</span></div>
115
- ${running ? '<span class="activity-wave"><i></i><i></i><i></i><i></i><i></i></span>' : ""}
94
+ <div><b>${esc(latest?.label || activity.title)}</b><span title="${esc(activityPreview.full)}">${esc(activityPreview.text)}</span></div>
116
95
  </div>
117
- ${progressHtml(session, true)}
118
- ${healthHtml(session, true)}
119
- ${
120
- runtime.length
121
- ? `<div class="runtime-strip">
122
- <span class="runtime-pulse">
123
- </span>
124
- <b>${esc(t("session.running_programs", { count: runtime.length }))}</b>
125
- <span>${esc(runtime.map((item) => item.label || t("session.program_pid", { pid: item.pid })).join(" · "))}</span>
126
- </div>`
127
- : ""
128
- }
129
- <div class="conversation-preview">
130
- ${conversation.map((row) => `<div class="preview-line ${row.tone}"><b>${esc(row.label)}</b><span>${esc(row.text)}</span></div>`).join("")}
131
- </div>
132
- <div class="context-meter ${gaugeTone}">
133
- <div class="context-meter-head">
134
- <div>
135
- <span>${esc(t("session.context_usage"))}</span>
136
- <strong>${context.window ? `${fullNumber(context.used)} / ${fullNumber(context.window)}` : `${fullNumber(context.used)} / --`}</strong>
137
- </div>
138
- <b>${context.window ? `${contextPercent.toFixed(1)}%` : "--"}</b>
139
- </div>
140
- <div class="context-meter-track"><span style="width:${contextPercent}%"></span><i style="left:75%"></i><i style="left:90%"></i></div>
141
- <div class="context-meter-foot">
142
- <span>${esc(context.window ? t("session.context_remaining", { count: compact(remaining) }) : t("session.context_size_unknown"))}</span>
143
- <span>${esc(t("session.tokens_used_so_far", { count: compact(usage.total) }))}</span>
144
- </div>
145
- </div>
146
- <div class="token-row">
147
- <div><span>${esc(t("session.input_tokens"))}</span><b>${compact(usage.input)}</b></div>
148
- <div><span>${esc(t("session.output_tokens"))}</span><b>${compact(usage.output)}</b></div>
149
- <div><span>${esc(t("session.cached_tokens"))}</span><b>${compact(usage.cachedInput)}</b></div>
150
- <div class="total"><span>${esc(t("session.total_tokens"))}</span><b>${compact(usage.total)}</b></div>
151
- </div>
152
- ${
153
- children.length
154
- ? `<div class="child-row">
155
- <b>⑂</b>
156
- <span>${esc(t("session.subagents_created", { count: children.length }))}</span>
157
- <span class="child-dots">${children
158
- .slice(0, 4)
159
- .map(() => "<i></i>")
160
- .join("")}</span>
161
- </div>`
162
- : ""
163
- }
164
96
  <footer class="card-footer">
165
- <span class="source-tag">${esc(window.LoadToAgentI18n.observedText(session.sourceLabel || t("session.local_history")))}</span>
166
97
  <span>${esc(timeAgo(session.updatedAt))}</span>
98
+ <span class="session-order-actions" role="group" aria-label="${esc(t("session.change_position"))}">
99
+ <button type="button" data-session-order-move="${esc(session.id)}" data-session-order-offset="-1" title="${esc(t("session.move_up"))}" aria-label="${esc(t("session.move_up"))}">↑</button>
100
+ <button type="button" data-session-order-move="${esc(session.id)}" data-session-order-offset="1" title="${esc(t("session.move_down"))}" aria-label="${esc(t("session.move_down"))}">↓</button>
101
+ </span>
102
+ <strong>${esc(t("graph.view_conversation"))}<i aria-hidden="true">→</i></strong>
167
103
  </footer>
168
104
  </article>`;
169
105
  }
@@ -177,13 +113,14 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
177
113
  const settingsView = state.view === "settings";
178
114
  const runtimeView = state.view === "runtime";
179
115
  const attentionView = state.view === "waiting";
180
- const operationsView = state.view === "all" || state.view === "active";
116
+ const homeView = state.view === "all";
117
+ const operationsView = homeView;
181
118
  const focusedToolView = tmuxView || terminalView || settingsView || runtimeView;
182
119
  $("#terminalSection").classList.toggle("hidden", !terminalView);
183
120
  $("#tmuxSection").classList.toggle("hidden", !tmuxView);
184
121
  $("#settingsSection").classList.toggle("hidden", !settingsView);
185
- $("#globalStats").classList.toggle("hidden", focusedToolView);
186
- $("#providerOverview").classList.toggle("hidden", focusedToolView || state.view !== "all");
122
+ $("#globalStats").classList.toggle("hidden", focusedToolView || homeView || state.view === "active" || attentionView);
123
+ $("#providerOverview").classList.add("hidden");
187
124
  $("#sessionSection").classList.toggle("hidden", focusedToolView || state.view === "active" || attentionView);
188
125
  $("#operationsOverview").classList.toggle("hidden", !operationsView);
189
126
  $("#attentionInbox").classList.toggle("hidden", !attentionView);
@@ -236,7 +173,7 @@ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRen
236
173
  const activeEmpty = state.view === "active" && graphLiveCount === 0;
237
174
  $("#activeEmptyState").classList.toggle("hidden", !activeEmpty);
238
175
  $("#liveSection").classList.toggle("hidden", attentionView || (graphLiveCount === 0 && state.view !== "active"));
239
- $("#viewTitle").textContent = VIEW_TITLES[state.view] || window.LoadToAgentI18n.t("ui.recent_conversations_and_tasks");
176
+ $("#viewTitle").textContent = homeView ? t("control.other_sessions") : VIEW_TITLES[state.view] || window.LoadToAgentI18n.t("ui.recent_conversations_and_tasks");
240
177
  $("#sessionGrid").innerHTML = visible.map((session) => sessionCard(session)).join("");
241
178
  $("#sessionGrid").classList.toggle("hidden", visible.length === 0);
242
179
  $("#loadMoreBtn").classList.toggle("hidden", regular.length <= state.visibleLimit);