loadtoagent 1.0.0 → 1.3.1
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/README.ko.md +7 -0
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +23 -1
- package/docs/UI-AUDIT-100.md +118 -0
- package/docs/UI-AUDIT-101-200.md +120 -0
- package/docs/UI-AUDIT-201-300.md +120 -0
- package/main.js +183 -37
- package/package.json +15 -4
- package/preload.js +11 -0
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -9
- package/renderer/app-dashboard.js +197 -58
- package/renderer/app-drawer-content.js +103 -86
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +79 -55
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -9
- package/renderer/app-events-navigation.js +78 -31
- package/renderer/app-events-sessions.js +171 -4
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +32 -4
- package/renderer/app-graph-orchestration.js +16 -13
- package/renderer/app-graph-view.js +228 -112
- package/renderer/app-management.js +211 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +103 -33
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +119 -38
- package/renderer/app.js +155 -76
- package/renderer/i18n-messages.js +833 -15
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +162 -75
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +50 -1
- package/renderer/styles-cards.css +26 -0
- package/renderer/styles-collaboration.css +18 -88
- package/renderer/styles-components.css +126 -14
- package/renderer/styles-management.css +355 -0
- package/renderer/styles-overlays.css +13 -2
- package/renderer/styles-product.css +24 -16
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +862 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +31 -14
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +37 -17
- package/renderer/styles-run-composer.css +5 -0
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +160 -0
- package/renderer/styles-terminal.css +339 -2
- package/renderer/styles-tmux.css +154 -0
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +69 -2
- package/renderer/styles.css +27 -12
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +233 -40
- package/renderer/terminal-workbench.js +176 -78
- package/renderer/terminal.js +350 -37
- package/src/agentMonitor/claudeParser.js +96 -7
- package/src/agentMonitor/codexParser.js +59 -4
- package/src/agentMonitor/executionActivity.js +282 -0
- package/src/agentMonitor/genericParser.js +56 -13
- package/src/agentMonitor/hierarchy.js +17 -0
- package/src/agentMonitor/responseIntent.js +51 -0
- package/src/agentMonitor.js +21 -3
- package/src/agentRunner.js +66 -1
- package/src/attentionNotifier.js +71 -0
- package/src/automationMonitor.js +258 -0
- package/src/contracts.js +10 -1
- package/src/ipc/registerAgentIpc.js +9 -3
- package/src/ipc/registerAppIpc.js +4 -1
- package/src/ipc/registerTerminalIpc.js +12 -6
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +81 -23
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +247 -0
- package/src/terminalHost.js +381 -0
- package/src/terminalHostDaemon.js +60 -0
- package/src/terminalManager.js +158 -3
- package/src/tmuxMonitor.js +2 -2
- package/src/updateInstaller.js +175 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createDashboard = function createDashboard(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const {
|
|
7
8
|
$,
|
|
8
9
|
esc,
|
|
@@ -11,10 +12,14 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
11
12
|
state,
|
|
12
13
|
compact,
|
|
13
14
|
providerStyle,
|
|
15
|
+
visibleProviders = () => state.providers,
|
|
16
|
+
visibleSessions = () => ((state.snapshot && state.snapshot.sessions) || []),
|
|
17
|
+
isProviderVisible = () => true,
|
|
18
|
+
isRuntimeLoopSession = () => false,
|
|
14
19
|
} = context;
|
|
15
20
|
|
|
16
21
|
function renderProviderRail() {
|
|
17
|
-
$("#providerRail").innerHTML =
|
|
22
|
+
$("#providerRail").innerHTML = visibleProviders()
|
|
18
23
|
.map((provider) => {
|
|
19
24
|
const available = !!state.availability[provider.id];
|
|
20
25
|
return `<div class="provider-rail-item ${available ? "connected" : ""}" style="${providerStyle(provider.id)}">
|
|
@@ -27,34 +32,95 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
function isProjectlessSession(session) {
|
|
30
|
-
|
|
35
|
+
const cwd = session && (session.originCwd || session.cwd);
|
|
36
|
+
if (!cwd) return true;
|
|
31
37
|
if (typeof session.projectless === "boolean") return session.projectless;
|
|
32
|
-
const normalized = String(
|
|
38
|
+
const normalized = String(cwd).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
33
39
|
return session.provider === "codex" && session.clientKind === "codex-desktop" && /(?:^|\/)Documents\/Codex\/\d{4}-\d{2}-\d{2}\/new-chat$/i.test(normalized);
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
function sessionOriginPath(session) {
|
|
43
|
+
return String(session && (session.originCwd || session.cwd) || "").trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizedProjectPath(value) {
|
|
47
|
+
return String(value || "").trim().replace(/\\/g, "/").replace(/\/+$/, "").toLocaleLowerCase();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function projectContainsPath(projectPath, candidatePath) {
|
|
51
|
+
const project = normalizedProjectPath(projectPath);
|
|
52
|
+
const candidate = normalizedProjectPath(candidatePath);
|
|
53
|
+
return Boolean(project && candidate && (candidate === project || candidate.startsWith(`${project}/`)));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function projectName(projectPath) {
|
|
57
|
+
const normalized = String(projectPath || "").replace(/\\/g, "/").replace(/\/+$/, "");
|
|
58
|
+
return normalized.split("/").filter(Boolean).pop() || t("workspace.unknown");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function observedProjects() {
|
|
62
|
+
const projects = new Map();
|
|
63
|
+
const saved = state.workspaces.map((item) => ({ ...item, key: normalizedProjectPath(item.path) }));
|
|
64
|
+
saved.forEach((item) => projects.set(item.key, { path: item.path, name: item.name || projectName(item.path), saved: true, count: 0 }));
|
|
65
|
+
visibleSessions().filter((session) => !session.parentId && !isProjectlessSession(session)).forEach((session) => {
|
|
66
|
+
const originPath = sessionOriginPath(session);
|
|
67
|
+
if (!originPath) return;
|
|
68
|
+
const owner = saved
|
|
69
|
+
.filter((item) => projectContainsPath(item.path, originPath))
|
|
70
|
+
.sort((a, b) => b.key.length - a.key.length)[0];
|
|
71
|
+
const path = owner ? owner.path : originPath;
|
|
72
|
+
const key = normalizedProjectPath(path);
|
|
73
|
+
const project = projects.get(key) || { path, name: projectName(path), saved: false, count: 0 };
|
|
74
|
+
project.count += 1;
|
|
75
|
+
project.lastActivityAt = !project.lastActivityAt || Date.parse(session.updatedAt || 0) > Date.parse(project.lastActivityAt || 0)
|
|
76
|
+
? session.updatedAt
|
|
77
|
+
: project.lastActivityAt;
|
|
78
|
+
projects.set(key, project);
|
|
79
|
+
});
|
|
80
|
+
const items = [...projects.values()];
|
|
81
|
+
const duplicateNames = new Map();
|
|
82
|
+
items.forEach((item) => duplicateNames.set(item.name.toLocaleLowerCase(), (duplicateNames.get(item.name.toLocaleLowerCase()) || 0) + 1));
|
|
83
|
+
items.forEach((item) => {
|
|
84
|
+
if (duplicateNames.get(item.name.toLocaleLowerCase()) < 2) return;
|
|
85
|
+
const parts = String(item.path).replace(/\\/g, "/").replace(/\/+$/, "").split("/").filter(Boolean);
|
|
86
|
+
item.name = parts.slice(-2).join("/") || item.name;
|
|
87
|
+
});
|
|
88
|
+
return items.sort((a, b) => Number(b.count || 0) - Number(a.count || 0)
|
|
89
|
+
|| Number(b.saved) - Number(a.saved)
|
|
90
|
+
|| String(a.name).localeCompare(String(b.name), uiLocale()));
|
|
91
|
+
}
|
|
92
|
+
|
|
36
93
|
function sessionWorkspaceLabel(session) {
|
|
37
|
-
return isProjectlessSession(session)
|
|
94
|
+
return isProjectlessSession(session)
|
|
95
|
+
? t("ui.no_project")
|
|
96
|
+
: (session && session.workspace) || projectName(sessionOriginPath(session));
|
|
38
97
|
}
|
|
39
98
|
|
|
40
99
|
function matchesWorkspaceFilter(session) {
|
|
41
100
|
if (state.workspace === "all") return true;
|
|
42
101
|
if (state.workspace === PROJECTLESS_WORKSPACE) return isProjectlessSession(session);
|
|
43
|
-
return (
|
|
44
|
-
!isProjectlessSession(session) &&
|
|
45
|
-
String((session && session.cwd) || "")
|
|
46
|
-
.toLowerCase()
|
|
47
|
-
.startsWith(state.workspace.toLowerCase())
|
|
48
|
-
);
|
|
102
|
+
return !isProjectlessSession(session) && projectContainsPath(state.workspace, sessionOriginPath(session));
|
|
49
103
|
}
|
|
50
104
|
|
|
51
105
|
function renderWorkspaces() {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
106
|
+
const lists = [$("#workspaceList"), $("#mobileWorkspaceList")].filter(Boolean);
|
|
107
|
+
const rootSessions = visibleSessions().filter((session) => !session.parentId);
|
|
108
|
+
const projects = observedProjects();
|
|
109
|
+
const projectlessCount = rootSessions.filter(isProjectlessSession).length;
|
|
110
|
+
const savedWorkspaceExists = state.workspace === "all"
|
|
111
|
+
|| (state.workspace === PROJECTLESS_WORKSPACE && projectlessCount > 0)
|
|
112
|
+
|| projects.some((project) => normalizedProjectPath(project.path) === normalizedProjectPath(state.workspace));
|
|
113
|
+
if (!savedWorkspaceExists) state.workspace = "all";
|
|
114
|
+
const projectButton = (item) => `<button type="button" class="workspace-item observed-project ${state.workspace === item.path ? "selected" : ""}"
|
|
115
|
+
data-workspace="${esc(item.path)}" title="${esc(item.path)}"
|
|
116
|
+
aria-label="${esc(t("project.filter_named", { name: item.name, count: item.count }))}"
|
|
117
|
+
aria-pressed="${state.workspace === item.path ? "true" : "false"}">
|
|
118
|
+
<strong>${esc(item.name)}</strong><small>${Number(item.count || 0)}</small>
|
|
119
|
+
</button>`;
|
|
120
|
+
const html =
|
|
55
121
|
`<button type="button" class="workspace-item ${state.workspace === "all" ? "selected" : ""}"
|
|
56
122
|
data-workspace="all" aria-pressed="${state.workspace === "all" ? "true" : "false"}">
|
|
57
|
-
<strong>${window.LoadToAgentI18n.t("
|
|
123
|
+
<strong>${window.LoadToAgentI18n.t("project.all")}</strong><small>${rootSessions.length}</small>
|
|
58
124
|
</button>` +
|
|
59
125
|
(projectlessCount
|
|
60
126
|
? `<button type="button" class="workspace-item projectless ${state.workspace === PROJECTLESS_WORKSPACE ? "selected" : ""}"
|
|
@@ -65,34 +131,39 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
65
131
|
<small>${projectlessCount}</small>
|
|
66
132
|
</button>`
|
|
67
133
|
: "") +
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
(item) => `<div class="workspace-row">
|
|
71
|
-
<button type="button" class="workspace-item ${state.workspace === item.path ? "selected" : ""}"
|
|
72
|
-
data-workspace="${esc(item.path)}" title="${esc(item.path)}"
|
|
73
|
-
aria-pressed="${state.workspace === item.path ? "true" : "false"}">
|
|
74
|
-
<strong>${esc(item.name)}</strong>
|
|
75
|
-
</button>
|
|
134
|
+
projects.map((item) => item.saved ? `<div class="workspace-row">
|
|
135
|
+
${projectButton(item)}
|
|
76
136
|
<button type="button" class="workspace-remove" data-remove-workspace="${esc(item.path)}"
|
|
77
|
-
aria-label="${esc(item.name)}
|
|
137
|
+
aria-label="${esc(t("workspace.remove_named", { name: item.name }))}"
|
|
78
138
|
title="${esc(window.LoadToAgentI18n.t("ui.remove_from_list"))}">×</button>
|
|
79
|
-
</div
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
139
|
+
</div>` : projectButton(item)).join("") +
|
|
140
|
+
(!projects.length && !projectlessCount ? `<div class="workspace-empty">${window.LoadToAgentI18n.t("project.empty")}</div>` : "");
|
|
141
|
+
lists.forEach((list) => { list.innerHTML = html; });
|
|
142
|
+
const selectedProject = projects.find((project) => normalizedProjectPath(project.path) === normalizedProjectPath(state.workspace));
|
|
143
|
+
const mobileSummary = $("#mobileWorkspaceSummary");
|
|
144
|
+
if (mobileSummary) mobileSummary.textContent = state.workspace === "all"
|
|
145
|
+
? t("project.all")
|
|
146
|
+
: state.workspace === PROJECTLESS_WORKSPACE
|
|
147
|
+
? t("ui.no_project")
|
|
148
|
+
: selectedProject?.name || projectName(state.workspace);
|
|
83
149
|
}
|
|
84
150
|
|
|
85
151
|
function renderGlobalStats() {
|
|
86
|
-
const
|
|
87
|
-
const
|
|
152
|
+
const sessions = visibleSessions();
|
|
153
|
+
const totals = {
|
|
154
|
+
active: sessions.filter((session) => session.status === "running" || session.status === "starting").length,
|
|
155
|
+
waiting: sessions.filter((session) => session.attention?.required).length,
|
|
156
|
+
usage: { total: sessions.reduce((sum, session) => sum + Number(session.usage && session.usage.total || 0), 0) },
|
|
157
|
+
};
|
|
88
158
|
const rootCount = sessions.filter((session) => !session.parentId).length;
|
|
89
|
-
const
|
|
159
|
+
const criticalCount = sessions.filter((session) => session.health?.level === "critical").length;
|
|
160
|
+
const riskCount = sessions.filter((session) => session.health?.level === "warning" || Number(session.context?.percent || 0) >= 75).length;
|
|
90
161
|
const items = [
|
|
91
162
|
[window.LoadToAgentI18n.t("ui.all_tasks"), rootCount, window.LoadToAgentI18n.t("ui.items"), ""],
|
|
92
163
|
[window.LoadToAgentI18n.t("ui.ai_working_now"), totals.active || 0, window.LoadToAgentI18n.t("ui.items"), "live"],
|
|
93
|
-
[window.LoadToAgentI18n.t("
|
|
94
|
-
[window.LoadToAgentI18n.t("
|
|
95
|
-
[window.LoadToAgentI18n.t("
|
|
164
|
+
[window.LoadToAgentI18n.t("management.action_required"), totals.waiting || 0, window.LoadToAgentI18n.t("ui.items"), "alert"],
|
|
165
|
+
[window.LoadToAgentI18n.t("management.health.critical"), criticalCount, window.LoadToAgentI18n.t("ui.items"), "critical"],
|
|
166
|
+
[window.LoadToAgentI18n.t("management.risk_total"), riskCount, window.LoadToAgentI18n.t("ui.items"), "warning"],
|
|
96
167
|
];
|
|
97
168
|
$("#globalStats").innerHTML = items
|
|
98
169
|
.map(
|
|
@@ -104,9 +175,46 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
104
175
|
)
|
|
105
176
|
.join("");
|
|
106
177
|
$("#navAllCount").textContent = rootCount;
|
|
107
|
-
|
|
108
|
-
$("#
|
|
109
|
-
|
|
178
|
+
const activeRootCount = sessions.filter((session) => !session.parentId && ["running", "starting"].includes(session.status)).length;
|
|
179
|
+
$("#navActiveCount").textContent = activeRootCount;
|
|
180
|
+
const reviewCount = sessions.filter((session) => context.managementBucket?.(session) !== "healthy").length;
|
|
181
|
+
$("#navWaitingCount").textContent = reviewCount;
|
|
182
|
+
const scheduledCount = (state.snapshot?.automations || [])
|
|
183
|
+
.filter((item) => isProviderVisible(item.provider || "codex")).length;
|
|
184
|
+
const loopCount = sessions.filter(isRuntimeLoopSession).length;
|
|
185
|
+
$("#navRuntimeCount").textContent = scheduledCount + loopCount;
|
|
186
|
+
const tmuxSessionCount = Number(state.snapshot?.tmux?.summary?.sessions || 0);
|
|
187
|
+
$("#navTmuxCount").textContent = tmuxSessionCount;
|
|
188
|
+
$("#advancedToolsCount").textContent = scheduledCount + loopCount + Number($("#navTerminalCount").textContent || 0) + tmuxSessionCount;
|
|
189
|
+
const navCounts = {
|
|
190
|
+
all: rootCount,
|
|
191
|
+
active: activeRootCount,
|
|
192
|
+
waiting: reviewCount,
|
|
193
|
+
runtime: scheduledCount + loopCount,
|
|
194
|
+
terminal: Number($("#navTerminalCount").textContent || 0),
|
|
195
|
+
tmux: tmuxSessionCount,
|
|
196
|
+
};
|
|
197
|
+
document.querySelectorAll(".nav-item[data-view]").forEach((button) => {
|
|
198
|
+
const key = {
|
|
199
|
+
all: "app.nav.home", active: "app.nav.active", waiting: "app.nav.needs_review", runtime: "app.nav.runtime",
|
|
200
|
+
terminal: "app.nav.session_terminal", tmux: "app.nav.tmux", settings: "app.nav.settings",
|
|
201
|
+
}[button.dataset.view];
|
|
202
|
+
const label = t(key);
|
|
203
|
+
const count = navCounts[button.dataset.view];
|
|
204
|
+
const unitKey = { all: "tasks", active: "tasks", waiting: "tasks", runtime: "runs", terminal: "sessions", tmux: "groups" }[button.dataset.view];
|
|
205
|
+
const unit = unitKey ? t(`quality.unit.${unitKey}`) : "";
|
|
206
|
+
const accessibleLabel = Number.isFinite(count) ? t("quality.nav_count_detailed", { label, count, unit }) : label;
|
|
207
|
+
button.setAttribute("aria-label", accessibleLabel);
|
|
208
|
+
button.setAttribute("title", accessibleLabel);
|
|
209
|
+
});
|
|
210
|
+
const advancedCount = scheduledCount + loopCount + Number($("#navTerminalCount").textContent || 0) + tmuxSessionCount;
|
|
211
|
+
$("#advancedToolsNav")?.querySelector("summary")?.setAttribute("aria-label", t("quality.nav_count_detailed", {
|
|
212
|
+
label: t("management.advanced_tools"), count: advancedCount, unit: t("quality.unit.items"),
|
|
213
|
+
}));
|
|
214
|
+
const tmuxShortcut = $("#openTmuxFromAgentWork");
|
|
215
|
+
$("#agentWorkTmuxCount").textContent = tmuxSessionCount;
|
|
216
|
+
tmuxShortcut.dataset.i18nParams = JSON.stringify({ count: tmuxSessionCount });
|
|
217
|
+
tmuxShortcut.setAttribute("aria-label", t("graph.open_tmux_workspace_count", { count: tmuxSessionCount }));
|
|
110
218
|
}
|
|
111
219
|
|
|
112
220
|
function formatBytes(value) {
|
|
@@ -159,7 +267,7 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
159
267
|
],
|
|
160
268
|
downloaded: [
|
|
161
269
|
"✓", window.LoadToAgentI18n.t("ui.ready_to_install"), window.LoadToAgentI18n.t("ui.the_update_file_is_ready"),
|
|
162
|
-
window.LoadToAgentI18n.t("
|
|
270
|
+
window.LoadToAgentI18n.t("settings.update.auto_install_restart"),
|
|
163
271
|
],
|
|
164
272
|
error: [
|
|
165
273
|
"!", window.LoadToAgentI18n.t("ui.check_failed"), window.LoadToAgentI18n.t("ui.could_not_check_for_updates"),
|
|
@@ -202,7 +310,7 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
202
310
|
install.textContent = downloading
|
|
203
311
|
? window.LoadToAgentI18n.t("ui.downloading_2")
|
|
204
312
|
: downloaded
|
|
205
|
-
? window.LoadToAgentI18n.t("ui.
|
|
313
|
+
? `${window.LoadToAgentI18n.t("settings.update.download")} · ${window.LoadToAgentI18n.t("ui.restart")}`
|
|
206
314
|
: window.LoadToAgentI18n.t("settings.update.download");
|
|
207
315
|
const progress = $("#updateProgress");
|
|
208
316
|
progress.classList.toggle("hidden", !downloading && !downloaded);
|
|
@@ -210,11 +318,13 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
210
318
|
$("#updateProgressBar").style.width = `${Math.max(0, Math.min(100, Number(update.progress || 0)))}%`;
|
|
211
319
|
$(".update-progress-track").setAttribute("aria-valuenow", String(Math.max(0, Math.min(100, Number(update.progress || 0)))));
|
|
212
320
|
$("#updateProgressBytes").textContent = downloaded
|
|
213
|
-
? `${formatBytes(update.totalBytes || update.downloadedBytes)} ·
|
|
321
|
+
? `${formatBytes(update.totalBytes || update.downloadedBytes)} · ${window.LoadToAgentI18n.t("settings.update.file_verified")}`
|
|
214
322
|
: `${formatBytes(update.downloadedBytes)} / ${update.totalBytes ? formatBytes(update.totalBytes) : window.LoadToAgentI18n.t("ui.checking_size")}`;
|
|
215
323
|
const error = $("#updateError");
|
|
216
324
|
error.classList.toggle("hidden", !update.error);
|
|
217
|
-
error.textContent = update.error
|
|
325
|
+
error.textContent = update.error
|
|
326
|
+
? window.LoadToAgentI18n.errorText(update.error, "ui.could_not_check_for_updates")
|
|
327
|
+
: "";
|
|
218
328
|
const notes = $("#releaseNotes");
|
|
219
329
|
notes.classList.toggle("hidden", !update.latestVersion);
|
|
220
330
|
$("#releaseNotesText").textContent =
|
|
@@ -231,22 +341,29 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
231
341
|
function renderProviderOverview() {
|
|
232
342
|
pruneProviderFilters();
|
|
233
343
|
const summaries = (state.snapshot && state.snapshot.summary && state.snapshot.summary.providers) || state.providers;
|
|
234
|
-
const sessions = (
|
|
235
|
-
|
|
236
|
-
|
|
344
|
+
const sessions = visibleSessions();
|
|
345
|
+
const visibleSummaries = summaries.filter((provider) => isProviderVisible(provider.id));
|
|
346
|
+
const overviewTabStopId = state.providerFilters.size ? [...state.providerFilters][0] : visibleSummaries[0]?.id;
|
|
347
|
+
$("#providerOverview").innerHTML = visibleSummaries
|
|
348
|
+
.map((provider, index) => {
|
|
237
349
|
const rootCount = sessions.filter((session) => session.provider === provider.id && !session.parentId).length;
|
|
238
350
|
const selected = state.providerFilters.has(provider.id);
|
|
351
|
+
const tabStop = provider.id === overviewTabStopId;
|
|
239
352
|
return `<button type="button" class="provider-overview-card ${selected ? "selected" : ""}"
|
|
240
353
|
data-provider-card="${esc(provider.id)}"
|
|
241
354
|
data-motion-key="provider:${esc(provider.id)}"
|
|
242
355
|
data-motion-value="${provider.active || 0}:${rootCount}:${(provider.usage && provider.usage.total) || 0}"
|
|
243
356
|
style="${providerStyle(provider.id)}"
|
|
357
|
+
tabindex="${tabStop ? "0" : "-1"}"
|
|
244
358
|
aria-pressed="${selected ? "true" : "false"}">
|
|
245
359
|
<div class="poc-head">
|
|
246
360
|
<span class="provider-mark">${esc(provider.mark)}</span>
|
|
247
361
|
<div><strong>${esc(provider.label)}</strong><small>${esc(provider.company)}</small></div>
|
|
248
|
-
<span class="poc-
|
|
249
|
-
${
|
|
362
|
+
<span class="poc-head-states">
|
|
363
|
+
<span class="poc-filter-state ${selected ? "visible" : ""}" aria-hidden="true">✓ ${window.LoadToAgentI18n.t("filter.applied")}</span>
|
|
364
|
+
<span class="poc-state ${provider.installed ? "online" : ""}">
|
|
365
|
+
${provider.installed ? window.LoadToAgentI18n.t("ui.available") : window.LoadToAgentI18n.t("ui.setup_required")}
|
|
366
|
+
</span>
|
|
250
367
|
</span>
|
|
251
368
|
</div>
|
|
252
369
|
<div class="poc-metrics">
|
|
@@ -260,7 +377,7 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
260
377
|
}
|
|
261
378
|
|
|
262
379
|
function pruneProviderFilters() {
|
|
263
|
-
const valid = new Set(
|
|
380
|
+
const valid = new Set(visibleProviders().map((provider) => provider.id));
|
|
264
381
|
for (const id of [...state.providerFilters]) if (!valid.has(id)) state.providerFilters.delete(id);
|
|
265
382
|
if (valid.size > 0 && state.providerFilters.size === valid.size) state.providerFilters.clear();
|
|
266
383
|
}
|
|
@@ -270,27 +387,29 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
270
387
|
if (providerId === "all") state.providerFilters.clear();
|
|
271
388
|
else if (state.providerFilters.has(providerId)) state.providerFilters.delete(providerId);
|
|
272
389
|
else state.providerFilters.add(providerId);
|
|
273
|
-
if (
|
|
390
|
+
if (visibleProviders().length > 0 && state.providerFilters.size === visibleProviders().length) state.providerFilters.clear();
|
|
274
391
|
}
|
|
275
392
|
|
|
276
393
|
function renderProviderFilter() {
|
|
277
394
|
pruneProviderFilters();
|
|
278
395
|
const allSelected = state.providerFilters.size === 0;
|
|
396
|
+
const tabStopId = allSelected ? "all" : [...state.providerFilters][0];
|
|
279
397
|
const button = (id, label, mark = "") => {
|
|
280
398
|
const selected = id === "all" ? allSelected : state.providerFilters.has(id);
|
|
281
399
|
return `<button type="button" class="provider-filter-chip ${selected ? "selected" : ""}"
|
|
282
|
-
data-provider-filter="${esc(id)}" aria-pressed="${selected ? "true" : "false"}">
|
|
283
|
-
|
|
400
|
+
data-provider-filter="${esc(id)}" tabindex="${id === tabStopId ? "0" : "-1"}" aria-pressed="${selected ? "true" : "false"}">
|
|
401
|
+
<i class="provider-filter-check" aria-hidden="true">✓</i>
|
|
402
|
+
${mark ? `<span class="provider-filter-mark" aria-hidden="true">${esc(mark)}</span>` : ""}<b>${esc(label)}</b>
|
|
284
403
|
</button>`;
|
|
285
404
|
};
|
|
286
405
|
$("#providerFilter").innerHTML =
|
|
287
406
|
button("all", window.LoadToAgentI18n.t("ui.all_ai")) +
|
|
288
|
-
|
|
407
|
+
visibleProviders().map((provider) => button(provider.id, provider.label, provider.mark)).join("");
|
|
289
408
|
}
|
|
290
409
|
|
|
291
410
|
function announceProviderFilter() {
|
|
292
411
|
const labels = state.providerFilters.size
|
|
293
|
-
?
|
|
412
|
+
? visibleProviders().filter((provider) => state.providerFilters.has(provider.id)).map((provider) => provider.label).join(", ")
|
|
294
413
|
: window.LoadToAgentI18n.t("ui.all_ai");
|
|
295
414
|
$("#providerFilterStatus").textContent = window.LoadToAgentI18n.t("filter.result_summary", {
|
|
296
415
|
providers: labels,
|
|
@@ -299,16 +418,16 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
299
418
|
}
|
|
300
419
|
|
|
301
420
|
function filteredSessions() {
|
|
302
|
-
const allSessions = [...(
|
|
421
|
+
const allSessions = [...visibleSessions()];
|
|
303
422
|
let sessions = state.view === "waiting" ? allSessions : allSessions.filter((session) => !session.parentId);
|
|
304
423
|
if (state.view === "active") sessions = sessions.filter((session) => session.status === "running" || session.status === "starting");
|
|
305
|
-
if (state.view === "waiting") sessions = sessions.filter((session) => session.
|
|
424
|
+
if (state.view === "waiting") sessions = sessions.filter((session) => session.attention?.required || ["critical", "warning", "unknown"].includes(session.health?.level) || !session.health);
|
|
306
425
|
if (state.providerFilters.size) sessions = sessions.filter((session) => state.providerFilters.has(session.provider));
|
|
307
426
|
sessions = sessions.filter(matchesWorkspaceFilter);
|
|
308
|
-
const query = state.search.trim().toLowerCase();
|
|
427
|
+
const query = state.search.replace(/\s+/g, " ").trim().toLowerCase();
|
|
309
428
|
if (query) {
|
|
310
429
|
sessions = sessions.filter((session) =>
|
|
311
|
-
[session.title, session.model, session.cwd, session.agentName, ...(session.messages || []).slice(-12).map((item) => item.text)]
|
|
430
|
+
[session.title, session.model, session.originCwd, session.cwd, session.workspace, session.agentName, ...(session.messages || []).slice(-12).map((item) => item.text)]
|
|
312
431
|
.join(" ")
|
|
313
432
|
.toLowerCase()
|
|
314
433
|
.includes(query),
|
|
@@ -326,13 +445,13 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
326
445
|
}
|
|
327
446
|
|
|
328
447
|
function graphFilteredSessions() {
|
|
329
|
-
let sessions = [...(
|
|
448
|
+
let sessions = [...visibleSessions()];
|
|
330
449
|
if (state.providerFilters.size) sessions = sessions.filter((session) => state.providerFilters.has(session.provider));
|
|
331
450
|
sessions = sessions.filter(matchesWorkspaceFilter);
|
|
332
|
-
const query = state.search.trim().toLowerCase();
|
|
451
|
+
const query = state.search.replace(/\s+/g, " ").trim().toLowerCase();
|
|
333
452
|
if (query)
|
|
334
453
|
sessions = sessions.filter((session) =>
|
|
335
|
-
[session.title, session.model, session.cwd, session.agentName, session.agentRole, ...(session.messages || []).map((item) => item.text)]
|
|
454
|
+
[session.title, session.model, session.originCwd, session.cwd, session.workspace, session.agentName, session.agentRole, ...(session.messages || []).map((item) => item.text)]
|
|
336
455
|
.join(" ")
|
|
337
456
|
.toLowerCase()
|
|
338
457
|
.includes(query),
|
|
@@ -340,9 +459,28 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
340
459
|
return sessions;
|
|
341
460
|
}
|
|
342
461
|
|
|
462
|
+
function renderProviderVisibilitySettings() {
|
|
463
|
+
const list = $("#providerVisibilityList");
|
|
464
|
+
if (!list) return;
|
|
465
|
+
list.innerHTML = state.providers.map((provider) => {
|
|
466
|
+
const visible = isProviderVisible(provider.id);
|
|
467
|
+
const status = window.LoadToAgentI18n.t(visible ? "settings.providers.visible" : "settings.providers.hidden");
|
|
468
|
+
return `<label class="provider-visibility-option ${visible ? "enabled" : "disabled"}" style="${providerStyle(provider.id)}">
|
|
469
|
+
<span class="provider-mark" aria-hidden="true">${esc(provider.mark)}</span>
|
|
470
|
+
<span class="provider-visibility-name"><b>${esc(provider.label)}</b><small>${esc(provider.company)}</small></span>
|
|
471
|
+
<span class="provider-visibility-status">${esc(status)}</span>
|
|
472
|
+
<input type="checkbox" data-provider-visibility="${esc(provider.id)}" ${visible ? "checked" : ""}
|
|
473
|
+
aria-label="${esc(`${provider.label} ${status}`)}">
|
|
474
|
+
<span class="provider-toggle" aria-hidden="true"><i></i></span>
|
|
475
|
+
</label>`;
|
|
476
|
+
}).join("");
|
|
477
|
+
}
|
|
478
|
+
|
|
343
479
|
return {
|
|
344
480
|
renderProviderRail,
|
|
345
481
|
isProjectlessSession,
|
|
482
|
+
sessionOriginPath,
|
|
483
|
+
observedProjects,
|
|
346
484
|
sessionWorkspaceLabel,
|
|
347
485
|
matchesWorkspaceFilter,
|
|
348
486
|
renderWorkspaces,
|
|
@@ -357,5 +495,6 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
357
495
|
announceProviderFilter,
|
|
358
496
|
filteredSessions,
|
|
359
497
|
graphFilteredSessions,
|
|
498
|
+
renderProviderVisibilitySettings,
|
|
360
499
|
};
|
|
361
500
|
};
|