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.
- package/main.js +53 -8
- package/package.json +5 -2
- package/renderer/app-agent-actions.js +172 -66
- package/renderer/app-bootstrap.js +3 -1
- package/renderer/app-dashboard.js +36 -7
- package/renderer/app-drawer-content.js +177 -59
- package/renderer/app-drawer-data.js +7 -3
- package/renderer/app-drawer.js +72 -33
- package/renderer/app-events-dialogs.js +8 -1
- package/renderer/app-events-navigation.js +7 -0
- package/renderer/app-events-sessions.js +136 -13
- package/renderer/app-graph-model.js +2 -5
- package/renderer/app-graph-orchestration.js +7 -18
- package/renderer/app-graph-view.js +194 -48
- package/renderer/app-management.js +330 -35
- package/renderer/app-quality.js +5 -0
- package/renderer/app-session-render.js +20 -83
- package/renderer/app.js +5 -1
- package/renderer/i18n-messages.js +232 -21
- package/renderer/index.html +9 -6
- package/renderer/styles-components.css +140 -0
- package/renderer/styles-control-room.css +1439 -0
- package/renderer/styles-management.css +390 -3
- package/renderer/styles-responsive-shell.css +5 -0
- package/renderer/styles-runtime-overview.css +20 -0
- package/renderer/terminal-agent.js +31 -7
- package/renderer/terminal.js +4 -1
- package/src/agentMonitor/claudeParser.js +8 -4
- package/src/agentMonitor/codexParser.js +5 -4
- package/src/agentMonitor/genericParser.js +7 -6
- package/src/agentMonitor.js +44 -4
- package/src/attentionNotifier.js +3 -0
- package/src/ipc/registerTerminalIpc.js +2 -2
- package/src/monitorWorker.js +1 -1
- package/src/nodePtyRuntime.js +58 -0
- package/src/processMonitor.js +68 -7
- package/src/terminalHost.js +104 -5
- 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 {
|
|
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
|
-
&&
|
|
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
|
|
70
|
-
if (hasCurrentRisk(session, now) && session
|
|
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
|
|
77
|
-
if (filter === "warning") return hasCurrentRisk(session, now) && session
|
|
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
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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-
|
|
159
|
-
|
|
160
|
-
|
|
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"
|
|
205
|
-
<div class="operations-heading-copy"><p>${esc(t("management.
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
};
|
package/renderer/app-quality.js
CHANGED
|
@@ -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
|
|
80
|
-
const
|
|
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 || "")}:${
|
|
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(
|
|
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
|
-
|
|
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>${
|
|
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
|
|
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.
|
|
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);
|