loadtoagent 1.1.0 → 1.3.2
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 +14 -5
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +25 -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 +157 -38
- package/package.json +13 -4
- package/preload.js +13 -2
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -19
- package/renderer/app-dashboard.js +196 -55
- package/renderer/app-drawer-content.js +48 -44
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +72 -54
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -13
- package/renderer/app-events-navigation.js +77 -30
- package/renderer/app-events-sessions.js +156 -9
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +20 -38
- package/renderer/app-graph-orchestration.js +15 -13
- package/renderer/app-graph-view.js +228 -113
- package/renderer/app-management.js +257 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +104 -34
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +52 -44
- package/renderer/app.js +153 -75
- package/renderer/i18n-messages.js +867 -37
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +154 -67
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +16 -10
- package/renderer/styles-cards.css +44 -18
- package/renderer/styles-collaboration.css +23 -23
- package/renderer/styles-components.css +122 -45
- package/renderer/styles-management.css +364 -0
- package/renderer/styles-onboarding.css +8 -8
- package/renderer/styles-overlays.css +13 -7
- package/renderer/styles-product.css +48 -40
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +1040 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +25 -17
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +39 -5
- package/renderer/styles-run-composer.css +28 -23
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +176 -16
- package/renderer/styles-terminal.css +374 -37
- package/renderer/styles-tmux.css +49 -49
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +116 -49
- package/renderer/styles.css +34 -19
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +253 -37
- package/renderer/terminal-workbench.js +208 -89
- package/renderer/terminal.js +350 -35
- 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 +14 -10
- 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 -14
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +80 -22
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +261 -0
- package/src/terminalHost.js +453 -0
- package/src/terminalHostDaemon.js +61 -0
- package/src/terminalManager.js +218 -6
- package/src/updateInstaller.js +175 -0
|
@@ -3,42 +3,137 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createFilterEventBindings = function createFilterEventBindings(context = {}) {
|
|
6
|
-
const
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
7
|
+
const { $, state, setProviderVisible = () => {}, visibleSnapshot = () => state.snapshot, closeDrawer = () => {}, openDrawer = () => {}, renderSessions, render, renderWorkspaces, renderProviderOverview, renderProviderFilter, toggleProviderFilter, announceProviderFilter, filteredSessions, performUiAction, toast, announce, normalizedSearch = (value) => String(value || "").trim(), saveDashboardPreferences = () => {}, restoreDialogTrigger = () => {}, setDialogOpenState = () => {} } = context;
|
|
7
8
|
|
|
8
9
|
function bindFilterAndWorkspaceEvents() {
|
|
10
|
+
const syncFilterResetButton = () => {
|
|
11
|
+
const hasFilters = Boolean(
|
|
12
|
+
$("#searchInput").value || state.search || state.providerFilters.size || state.workspace !== "all" || state.sort !== "recent",
|
|
13
|
+
);
|
|
14
|
+
$("#resetFiltersBtn").classList.toggle("hidden", !hasFilters);
|
|
15
|
+
};
|
|
16
|
+
const moveFocus = (event, container, selector, previousKeys, nextKeys) => {
|
|
17
|
+
if (![...previousKeys, ...nextKeys, "Home", "End"].includes(event.key)) return false;
|
|
18
|
+
const items = Array.from(container.querySelectorAll(selector)).filter((item) => !item.disabled && !item.hidden);
|
|
19
|
+
if (!items.length) return false;
|
|
20
|
+
const current = Math.max(0, items.indexOf(event.target.closest(selector)));
|
|
21
|
+
const next = event.key === "Home"
|
|
22
|
+
? 0
|
|
23
|
+
: event.key === "End"
|
|
24
|
+
? items.length - 1
|
|
25
|
+
: (current + (nextKeys.includes(event.key) ? 1 : -1) + items.length) % items.length;
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
items[next].focus();
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
9
30
|
$("#loadMoreBtn").addEventListener("click", () => {
|
|
31
|
+
const previousCount = document.querySelectorAll("#sessionGrid [data-session-id]").length;
|
|
10
32
|
state.visibleLimit += 30;
|
|
11
33
|
renderSessions("load-more");
|
|
34
|
+
const cards = document.querySelectorAll("#sessionGrid [data-session-id]");
|
|
35
|
+
cards[Math.min(previousCount, cards.length - 1)]?.focus({ preventScroll: true });
|
|
36
|
+
announce(window.LoadToAgentI18n.t("filter.more_loaded", { count: Math.max(0, cards.length - previousCount) }));
|
|
12
37
|
});
|
|
13
|
-
$("#workspaceList")
|
|
38
|
+
const workspaceLists = [$("#workspaceList"), $("#mobileWorkspaceList")].filter(Boolean);
|
|
39
|
+
const handleWorkspaceClick = async (event) => {
|
|
40
|
+
const activeList = event.currentTarget;
|
|
14
41
|
const remove = event.target.closest("[data-remove-workspace]");
|
|
15
42
|
if (remove) {
|
|
16
43
|
event.stopPropagation();
|
|
17
|
-
const
|
|
44
|
+
const path = remove.dataset.removeWorkspace;
|
|
45
|
+
const workspaceItems = Array.from(activeList.querySelectorAll("[data-workspace]"));
|
|
46
|
+
const workspaceIndex = Math.max(0, workspaceItems.indexOf(remove.closest(".workspace-row")?.querySelector("[data-workspace]")));
|
|
47
|
+
const workspaces = await performUiAction(() => window.loadtoagent.removeWorkspace(remove.dataset.removeWorkspace), t("workspace.remove_failed"), remove);
|
|
18
48
|
if (!workspaces) return;
|
|
19
49
|
state.workspaces = workspaces;
|
|
20
50
|
if (state.workspace === remove.dataset.removeWorkspace) state.workspace = "all";
|
|
21
51
|
render();
|
|
52
|
+
syncFilterResetButton();
|
|
53
|
+
saveDashboardPreferences();
|
|
54
|
+
requestAnimationFrame(() => {
|
|
55
|
+
const nextItems = Array.from(activeList.querySelectorAll("[data-workspace]"));
|
|
56
|
+
nextItems[Math.min(workspaceIndex, nextItems.length - 1)]?.focus();
|
|
57
|
+
});
|
|
58
|
+
announce(window.LoadToAgentI18n.t("quality.workspace_removed", { name: path.split(/[\\/]/).filter(Boolean).pop() || path }));
|
|
22
59
|
return;
|
|
23
60
|
}
|
|
24
61
|
const item = event.target.closest("[data-workspace]");
|
|
25
62
|
if (item) {
|
|
63
|
+
const label = item.querySelector("strong")?.textContent.trim() || t("project.all");
|
|
26
64
|
state.workspace = item.dataset.workspace;
|
|
27
65
|
state.visibleLimit = 30;
|
|
28
66
|
renderWorkspaces();
|
|
29
67
|
renderSessions("filter");
|
|
68
|
+
syncFilterResetButton();
|
|
69
|
+
saveDashboardPreferences();
|
|
70
|
+
announce(t("filter.workspace_results", { project: label, count: filteredSessions().length }));
|
|
71
|
+
if (activeList.id === "mobileWorkspaceList") {
|
|
72
|
+
const menu = $("#mobileToolsMenu");
|
|
73
|
+
setDialogOpenState(menu, false);
|
|
74
|
+
menu?.classList.add("hidden");
|
|
75
|
+
$("#mobileMoreBtn")?.setAttribute("aria-expanded", "false");
|
|
76
|
+
const focusResults = () => ($("#liveSessionGrid")?.querySelector("[data-graph-focus], [data-open-session]")
|
|
77
|
+
|| $("#sessionGrid")?.querySelector("[data-session-id]")
|
|
78
|
+
|| $("#mainContent"))?.focus({ preventScroll: true });
|
|
79
|
+
restoreDialogTrigger();
|
|
80
|
+
focusResults();
|
|
81
|
+
requestAnimationFrame(focusResults);
|
|
82
|
+
}
|
|
30
83
|
}
|
|
84
|
+
};
|
|
85
|
+
workspaceLists.forEach((list) => {
|
|
86
|
+
list.addEventListener("click", handleWorkspaceClick);
|
|
87
|
+
list.addEventListener("keydown", (event) => {
|
|
88
|
+
moveFocus(event, event.currentTarget, "[data-workspace]", ["ArrowUp"], ["ArrowDown"]);
|
|
89
|
+
});
|
|
31
90
|
});
|
|
32
91
|
let searchTimer = null;
|
|
33
92
|
$("#searchInput").addEventListener("input", (event) => {
|
|
34
93
|
clearTimeout(searchTimer);
|
|
35
94
|
const value = event.target.value;
|
|
95
|
+
$("#searchClearBtn").classList.toggle("hidden", !value);
|
|
96
|
+
syncFilterResetButton();
|
|
36
97
|
searchTimer = setTimeout(() => {
|
|
37
|
-
state.search = value;
|
|
98
|
+
state.search = normalizedSearch(value);
|
|
38
99
|
state.visibleLimit = 30;
|
|
39
100
|
renderSessions("filter");
|
|
101
|
+
announce(window.LoadToAgentI18n.t("filter.search_results", { count: filteredSessions().length }));
|
|
102
|
+
syncFilterResetButton();
|
|
103
|
+
saveDashboardPreferences();
|
|
40
104
|
}, 120);
|
|
41
105
|
});
|
|
106
|
+
$("#searchClearBtn").addEventListener("click", () => {
|
|
107
|
+
clearTimeout(searchTimer);
|
|
108
|
+
$("#searchInput").value = "";
|
|
109
|
+
$("#searchClearBtn").classList.add("hidden");
|
|
110
|
+
state.search = "";
|
|
111
|
+
state.visibleLimit = 30;
|
|
112
|
+
renderSessions("filter");
|
|
113
|
+
announce(window.LoadToAgentI18n.t("filter.search_cleared"));
|
|
114
|
+
$("#searchInput").focus();
|
|
115
|
+
syncFilterResetButton();
|
|
116
|
+
saveDashboardPreferences();
|
|
117
|
+
});
|
|
118
|
+
$("#searchInput").addEventListener("keydown", (event) => {
|
|
119
|
+
if (event.key === "Escape" && event.currentTarget.value) {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
$("#searchClearBtn").click();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (event.key === "ArrowDown") {
|
|
125
|
+
const first = $("#sessionGrid [data-session-id]") || $("#liveSessionGrid button, #liveSessionGrid [tabindex='0']");
|
|
126
|
+
if (first) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
first.focus();
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (event.key === "Enter" && filteredSessions().length === 1) {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
openDrawer(filteredSessions()[0].id);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
42
137
|
$("#providerFilter").addEventListener("click", (event) => {
|
|
43
138
|
const chip = event.target.closest("[data-provider-filter]");
|
|
44
139
|
if (!chip) return;
|
|
@@ -48,30 +143,94 @@ window.LoadToAgentAppFactories.createFilterEventBindings = function createFilter
|
|
|
48
143
|
renderProviderOverview();
|
|
49
144
|
renderSessions("filter");
|
|
50
145
|
announceProviderFilter();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
146
|
+
const next = $("#providerFilter").querySelector(`[data-provider-filter="${CSS.escape(chip.dataset.providerFilter)}"]`);
|
|
147
|
+
next?.classList.add("filter-clicked");
|
|
148
|
+
next?.focus();
|
|
149
|
+
syncFilterResetButton();
|
|
150
|
+
saveDashboardPreferences();
|
|
151
|
+
});
|
|
152
|
+
$("#providerFilter").addEventListener("keydown", (event) => {
|
|
153
|
+
moveFocus(event, event.currentTarget, "[data-provider-filter]", ["ArrowLeft", "ArrowUp"], ["ArrowRight", "ArrowDown"]);
|
|
56
154
|
});
|
|
57
155
|
$("#sortSelect").addEventListener("change", (event) => {
|
|
58
156
|
state.sort = event.target.value;
|
|
59
157
|
state.visibleLimit = 30;
|
|
60
158
|
renderSessions("filter");
|
|
159
|
+
const label = event.target.selectedOptions[0]?.textContent || event.target.value;
|
|
160
|
+
announce(window.LoadToAgentI18n.t("filter.sort_changed", { sort: label, count: filteredSessions().length }));
|
|
161
|
+
syncFilterResetButton();
|
|
162
|
+
saveDashboardPreferences();
|
|
61
163
|
});
|
|
62
|
-
$("#
|
|
63
|
-
|
|
164
|
+
$("#resetFiltersBtn").addEventListener("click", () => {
|
|
165
|
+
clearTimeout(searchTimer);
|
|
166
|
+
state.search = "";
|
|
167
|
+
state.providerFilters.clear();
|
|
168
|
+
state.workspace = "all";
|
|
169
|
+
state.sort = "recent";
|
|
170
|
+
state.visibleLimit = 30;
|
|
171
|
+
$("#searchInput").value = "";
|
|
172
|
+
$("#searchClearBtn").classList.add("hidden");
|
|
173
|
+
$("#sortSelect").value = "recent";
|
|
174
|
+
renderWorkspaces();
|
|
175
|
+
renderProviderFilter();
|
|
176
|
+
renderProviderOverview();
|
|
177
|
+
renderSessions("filter");
|
|
178
|
+
syncFilterResetButton();
|
|
179
|
+
saveDashboardPreferences();
|
|
180
|
+
announce(window.LoadToAgentI18n.t("filter.reset_done", { count: filteredSessions().length }));
|
|
181
|
+
$("#searchInput").focus();
|
|
182
|
+
});
|
|
183
|
+
$("#providerVisibilityList").addEventListener("change", async (event) => {
|
|
184
|
+
const input = event.target.closest("[data-provider-visibility]");
|
|
185
|
+
if (!input) return;
|
|
186
|
+
const providerId = input.dataset.providerVisibility;
|
|
187
|
+
const previousVisible = !input.checked;
|
|
188
|
+
const selectedBeforeChange = (state.rawSnapshot?.sessions || []).find((session) => session.id === state.selectedId)
|
|
189
|
+
|| state.details.get(state.selectedId);
|
|
190
|
+
setProviderVisible(providerId, input.checked);
|
|
191
|
+
state.visibleLimit = 30;
|
|
192
|
+
if (state.selectedId && selectedBeforeChange && state.hiddenProviders.has(selectedBeforeChange.provider)) closeDrawer();
|
|
193
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.updateSnapshot(visibleSnapshot(), state.workspaces);
|
|
194
|
+
render("filter");
|
|
195
|
+
try {
|
|
196
|
+
await Promise.resolve(window.loadtoagent.setProviderVisibility?.({ hidden: [...state.hiddenProviders] }));
|
|
197
|
+
} catch (error) {
|
|
198
|
+
window.LoadToAgentRendererUtils.reportRecoverableError("provider-visibility-persistence", error);
|
|
199
|
+
setProviderVisible(providerId, previousVisible);
|
|
200
|
+
if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.updateSnapshot(visibleSnapshot(), state.workspaces);
|
|
201
|
+
render("filter");
|
|
202
|
+
toast(t("settings.providers.save_failed"));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
toast(t(input.checked ? "settings.providers.shown_toast" : "settings.providers.hidden_toast"));
|
|
206
|
+
});
|
|
207
|
+
const addWorkspaceButtons = [$("#addWorkspaceBtn"), $("#mobileAddWorkspaceBtn")].filter(Boolean);
|
|
208
|
+
const addWorkspace = async (event) => {
|
|
209
|
+
const trigger = event.currentTarget;
|
|
210
|
+
const previousPaths = new Set(state.workspaces.map((workspace) => workspace.path));
|
|
211
|
+
const workspaces = await performUiAction(() => window.loadtoagent.addWorkspaces(), t("workspace.add_failed"), trigger);
|
|
64
212
|
if (!workspaces) return;
|
|
65
213
|
state.workspaces = workspaces;
|
|
66
214
|
renderWorkspaces();
|
|
67
|
-
|
|
215
|
+
syncFilterResetButton();
|
|
216
|
+
const added = state.workspaces.find((workspace) => !previousPaths.has(workspace.path));
|
|
217
|
+
requestAnimationFrame(() => {
|
|
218
|
+
const targetList = trigger.id === "mobileAddWorkspaceBtn" ? $("#mobileWorkspaceList") : $("#workspaceList");
|
|
219
|
+
if (added) targetList?.querySelector(`[data-workspace="${CSS.escape(added.path)}"]`)?.focus();
|
|
220
|
+
else trigger.focus();
|
|
221
|
+
});
|
|
222
|
+
announce(window.LoadToAgentI18n.t("quality.workspace_added", { count: state.workspaces.length }));
|
|
223
|
+
};
|
|
224
|
+
addWorkspaceButtons.forEach((button) => button.addEventListener("click", addWorkspace));
|
|
68
225
|
$("#probeBtn").addEventListener("click", async () => {
|
|
69
|
-
const nextAvailability = await performUiAction(() => window.loadtoagent.probeProviders(), "
|
|
226
|
+
const nextAvailability = await performUiAction(() => window.loadtoagent.probeProviders(), t("run.cli_check_failed"), $("#probeBtn"));
|
|
70
227
|
if (!nextAvailability) return;
|
|
71
228
|
state.availability = nextAvailability;
|
|
72
229
|
render();
|
|
73
230
|
toast(window.LoadToAgentI18n.t("ui.ai_cli_connections_were_checked_again"));
|
|
74
231
|
});
|
|
232
|
+
$("#searchClearBtn").classList.toggle("hidden", !$("#searchInput").value);
|
|
233
|
+
syncFilterResetButton();
|
|
75
234
|
}
|
|
76
235
|
|
|
77
236
|
return { bindFilterAndWorkspaceEvents };
|
|
@@ -3,17 +3,39 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createNavigationEventBindings = function createNavigationEventBindings(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const {
|
|
7
8
|
$, state, motionPreference, saveGuideState, selectView, renderUpdateSettings,
|
|
8
|
-
filteredSessions, renderSessions, openRunModal, openDrawer, toast,
|
|
9
|
+
filteredSessions, renderSessions, openRunModal, openDrawer, toast, performUiAction,
|
|
10
|
+
rememberDialogTrigger, restoreDialogTrigger, setDialogOpenState, trapDialogFocus,
|
|
9
11
|
} = context;
|
|
10
12
|
|
|
11
13
|
function bindNavigationAndUpdateEvents() {
|
|
14
|
+
const closeMobileTools = (restoreFocus = true) => {
|
|
15
|
+
const menu = $("#mobileToolsMenu");
|
|
16
|
+
setDialogOpenState(menu, false);
|
|
17
|
+
menu.classList.add("hidden");
|
|
18
|
+
$("#mobileMoreBtn").setAttribute("aria-expanded", "false");
|
|
19
|
+
if (restoreFocus) restoreDialogTrigger();
|
|
20
|
+
};
|
|
12
21
|
$(".view-nav").addEventListener("click", (event) => {
|
|
13
22
|
const button = event.target.closest(".nav-item");
|
|
14
23
|
if (!button || !button.dataset.view) return;
|
|
15
24
|
selectView(button.dataset.view);
|
|
16
25
|
});
|
|
26
|
+
$(".view-nav").addEventListener("keydown", (event) => {
|
|
27
|
+
if (!["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
28
|
+
const buttons = Array.from(document.querySelectorAll(".view-nav .nav-item[data-view]"))
|
|
29
|
+
.filter((button) => !button.hidden && button.getClientRects().length > 0 && getComputedStyle(button).visibility !== "hidden");
|
|
30
|
+
const current = Math.max(0, buttons.indexOf(event.target.closest(".nav-item[data-view]")));
|
|
31
|
+
const next = event.key === "Home"
|
|
32
|
+
? 0
|
|
33
|
+
: event.key === "End"
|
|
34
|
+
? buttons.length - 1
|
|
35
|
+
: (current + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
buttons[next]?.focus();
|
|
38
|
+
});
|
|
17
39
|
$("#updateNoticeBtn").addEventListener("click", () => {
|
|
18
40
|
selectView("settings");
|
|
19
41
|
});
|
|
@@ -44,7 +66,7 @@ window.LoadToAgentAppFactories.createNavigationEventBindings = function createNa
|
|
|
44
66
|
const first = filteredSessions()[0] || ((state.snapshot && state.snapshot.sessions) || [])[0];
|
|
45
67
|
if (first) openDrawer(first.id);
|
|
46
68
|
else {
|
|
47
|
-
toast("
|
|
69
|
+
toast(t("guide.no_task_to_open"));
|
|
48
70
|
openRunModal();
|
|
49
71
|
}
|
|
50
72
|
}
|
|
@@ -52,43 +74,68 @@ window.LoadToAgentAppFactories.createNavigationEventBindings = function createNa
|
|
|
52
74
|
$("#mobileMoreBtn").addEventListener("click", () => {
|
|
53
75
|
const menu = $("#mobileToolsMenu");
|
|
54
76
|
const opening = menu.classList.contains("hidden");
|
|
55
|
-
|
|
56
|
-
$("#mobileMoreBtn").
|
|
57
|
-
|
|
77
|
+
if (!opening) return closeMobileTools(true);
|
|
78
|
+
$("#mobileMoreBtn").focus({ preventScroll: true });
|
|
79
|
+
rememberDialogTrigger();
|
|
80
|
+
menu.classList.remove("hidden");
|
|
81
|
+
setDialogOpenState(menu, true);
|
|
82
|
+
$("#mobileMoreBtn").setAttribute("aria-expanded", "true");
|
|
83
|
+
menu.querySelector("button")?.focus({ preventScroll: true });
|
|
84
|
+
});
|
|
85
|
+
$("#mobileToolsCloseBtn").addEventListener("click", () => closeMobileTools(true));
|
|
86
|
+
$("#mobileToolsMenu").addEventListener("keydown", (event) => {
|
|
87
|
+
if (event.key === "Escape") {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
closeMobileTools(true);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (event.key === "Tab") {
|
|
93
|
+
trapDialogFocus(event);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
97
|
+
const buttons = Array.from(event.currentTarget.querySelectorAll("button:not([disabled])"));
|
|
98
|
+
const current = Math.max(0, buttons.indexOf(event.target.closest("button")));
|
|
99
|
+
const next = event.key === "Home"
|
|
100
|
+
? 0
|
|
101
|
+
: event.key === "End"
|
|
102
|
+
? buttons.length - 1
|
|
103
|
+
: (current + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
buttons[next]?.focus();
|
|
58
106
|
});
|
|
59
107
|
$("#mobileToolsMenu").addEventListener("click", (event) => {
|
|
60
108
|
const button = event.target.closest("[data-mobile-view]");
|
|
61
|
-
if (button)
|
|
109
|
+
if (button) {
|
|
110
|
+
closeMobileTools(false);
|
|
111
|
+
selectView(button.dataset.mobileView, { focusMain: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
document.addEventListener("pointerdown", (event) => {
|
|
115
|
+
const menu = $("#mobileToolsMenu");
|
|
116
|
+
if (menu.classList.contains("hidden") || menu.contains(event.target) || $("#mobileMoreBtn").contains(event.target)) return;
|
|
117
|
+
const focusWasInside = menu.contains(document.activeElement);
|
|
118
|
+
closeMobileTools(focusWasInside);
|
|
62
119
|
});
|
|
63
120
|
$("#checkUpdateBtn").addEventListener("click", async () => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
toast((error && error.message) || window.LoadToAgentI18n.t("ui.could_not_check_for_updates"));
|
|
71
|
-
}
|
|
121
|
+
state.update = { ...(state.update || {}), status: "checking", error: "" };
|
|
122
|
+
renderUpdateSettings();
|
|
123
|
+
const update = await performUiAction(() => window.loadtoagent.checkForUpdate(), "ui.could_not_check_for_updates", $("#checkUpdateBtn"));
|
|
124
|
+
if (update) state.update = update;
|
|
125
|
+
else state.update = { ...(state.update || {}), status: "error" };
|
|
126
|
+
renderUpdateSettings();
|
|
72
127
|
});
|
|
73
128
|
$("#installUpdateBtn").addEventListener("click", async () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
toast((error && error.message) || window.LoadToAgentI18n.t("ui.could_not_prepare_the_update_file"));
|
|
84
|
-
}
|
|
129
|
+
state.update = { ...(state.update || {}), status: "downloading", error: "" };
|
|
130
|
+
renderUpdateSettings();
|
|
131
|
+
const update = await performUiAction(() => window.loadtoagent.installDownloadedUpdate(), "ui.could_not_prepare_the_update_file", $("#installUpdateBtn"));
|
|
132
|
+
if (update) state.update = update;
|
|
133
|
+
else if (state.update && state.update.asset) state.update.status = "available";
|
|
134
|
+
renderUpdateSettings();
|
|
135
|
+
if (state.update && state.update.installMode === "manual") toast(t("ui.open_installer"));
|
|
85
136
|
});
|
|
86
137
|
$("#openReleaseBtn").addEventListener("click", async () => {
|
|
87
|
-
|
|
88
|
-
await window.loadtoagent.openUpdateRelease();
|
|
89
|
-
} catch (error) {
|
|
90
|
-
toast((error && error.message) || window.LoadToAgentI18n.t("ui.could_not_open_the_github_release_page"));
|
|
91
|
-
}
|
|
138
|
+
await performUiAction(() => window.loadtoagent.openUpdateRelease(), "ui.could_not_open_the_github_release_page", $("#openReleaseBtn"));
|
|
92
139
|
});
|
|
93
140
|
}
|
|
94
141
|
|
|
@@ -5,10 +5,99 @@ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
|
5
5
|
window.LoadToAgentAppFactories.createSessionEventBindings = function createSessionEventBindings(context = {}) {
|
|
6
6
|
const {
|
|
7
7
|
$, state, selectView, renderProviderOverview, renderProviderFilter, toggleProviderFilter, announceProviderFilter, renderSessions, renderTmuxMap, openDrawer, openSubagentConversation,
|
|
8
|
-
dispatchAgentCommand, openAgentTerminal, copyBridgeCommand, openSessionOrigin,
|
|
8
|
+
dispatchAgentCommand, openAgentTerminal, copyBridgeCommand, openSessionOrigin, saveDashboardPreferences = () => {},
|
|
9
|
+
controlManagedRun, quickRespond, prepareReassignment,
|
|
10
|
+
copyText = async () => false,
|
|
11
|
+
announce = () => {},
|
|
9
12
|
} = context;
|
|
10
13
|
|
|
14
|
+
const managementFilterLabel = value => value === "all" ? window.LoadToAgentI18n.t("management.filter_all") : window.LoadToAgentI18n.t(`management.health.${value}`);
|
|
15
|
+
const announceManagementFilter = value => announce(window.LoadToAgentI18n.t("management.filter_results", {
|
|
16
|
+
filter: managementFilterLabel(value),
|
|
17
|
+
count: $("#attentionInbox")?.querySelectorAll("[data-management-session]").length || 0,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
function bindManagementEvents() {
|
|
21
|
+
$("#operationsOverview").addEventListener("click", (event) => {
|
|
22
|
+
const open = event.target.closest("[data-open-session]");
|
|
23
|
+
if (open) return openDrawer(open.dataset.openSession);
|
|
24
|
+
const filter = event.target.closest("[data-management-filter]");
|
|
25
|
+
if (!filter) return;
|
|
26
|
+
selectView("waiting", { focusMain: true, managementFilter: filter.dataset.managementFilter });
|
|
27
|
+
announceManagementFilter(filter.dataset.managementFilter);
|
|
28
|
+
});
|
|
29
|
+
$("#attentionInbox").addEventListener("click", async (event) => {
|
|
30
|
+
const filter = event.target.closest("[data-management-inbox-filter]");
|
|
31
|
+
if (filter) {
|
|
32
|
+
state.managementFilter = filter.dataset.managementInboxFilter;
|
|
33
|
+
renderSessions("filter");
|
|
34
|
+
announceManagementFilter(state.managementFilter);
|
|
35
|
+
requestAnimationFrame(() => $("#attentionInbox")?.querySelector(`[data-management-inbox-filter="${CSS.escape(state.managementFilter)}"]`)?.focus({ preventScroll: true }));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const open = event.target.closest("[data-open-session]");
|
|
39
|
+
if (open) return openDrawer(open.dataset.openSession);
|
|
40
|
+
const quick = event.target.closest("[data-attention-quick]");
|
|
41
|
+
if (quick) return quickRespond(quick.dataset.attentionSessionId, quick.dataset.attentionQuick, $("#attentionInbox"));
|
|
42
|
+
const managed = event.target.closest("[data-managed-run-action]");
|
|
43
|
+
if (managed) return controlManagedRun(managed.dataset.managementSessionId, managed.dataset.managedRunAction);
|
|
44
|
+
const reassign = event.target.closest("[data-reassign-session]");
|
|
45
|
+
if (reassign) prepareReassignment(reassign.dataset.reassignSession);
|
|
46
|
+
});
|
|
47
|
+
$("#attentionInbox").addEventListener("input", (event) => {
|
|
48
|
+
const input = event.target.closest("[data-agent-command-draft]");
|
|
49
|
+
if (input) state.agentCommandDrafts.set(input.dataset.agentCommandDraft, input.value);
|
|
50
|
+
});
|
|
51
|
+
$("#attentionInbox").addEventListener("change", (event) => {
|
|
52
|
+
const picker = event.target.closest("[data-agent-command-target]");
|
|
53
|
+
if (!picker) return;
|
|
54
|
+
if (picker.value) state.agentCommandTargets.set(picker.dataset.agentCommandTarget, picker.value);
|
|
55
|
+
else state.agentCommandTargets.delete(picker.dataset.agentCommandTarget);
|
|
56
|
+
picker.closest("form")?.querySelectorAll("button").forEach(button => { button.disabled = !picker.value; });
|
|
57
|
+
});
|
|
58
|
+
$("#attentionInbox").addEventListener("keydown", (event) => {
|
|
59
|
+
const input = event.target.closest("[data-agent-command-draft]");
|
|
60
|
+
if (!input || event.key !== "Enter" || event.shiftKey || event.isComposing || event.keyCode === 229) return;
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
input.closest("form")?.requestSubmit();
|
|
63
|
+
});
|
|
64
|
+
$("#attentionInbox").addEventListener("submit", (event) => {
|
|
65
|
+
const form = event.target.closest("[data-agent-command-form]");
|
|
66
|
+
if (!form) return;
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
dispatchAgentCommand(form.dataset.agentCommandForm, form);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
11
72
|
function bindSessionListEvents() {
|
|
73
|
+
$("#automationOverview").addEventListener("click", (event) => {
|
|
74
|
+
const loopSelect = event.target.closest("[data-loop-select]");
|
|
75
|
+
if (loopSelect) {
|
|
76
|
+
state.selectedRuntimeLoopId = loopSelect.dataset.loopSelect;
|
|
77
|
+
renderSessions("focus");
|
|
78
|
+
requestAnimationFrame(() => $("#automationOverview").querySelector(`[data-loop-select="${CSS.escape(state.selectedRuntimeLoopId)}"]`)?.focus({ preventScroll: true }));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const sessionTarget = event.target.closest("[data-loop-open], [data-automation-session]");
|
|
82
|
+
if (sessionTarget) openDrawer(sessionTarget.dataset.loopOpen || sessionTarget.dataset.automationSession);
|
|
83
|
+
});
|
|
84
|
+
$("#automationOverview").addEventListener("keydown", (event) => {
|
|
85
|
+
const loop = event.target.closest("[data-loop-select]");
|
|
86
|
+
if (loop && ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) {
|
|
87
|
+
const tabs = Array.from(event.currentTarget.querySelectorAll("[data-loop-select]"));
|
|
88
|
+
const current = Math.max(0, tabs.indexOf(loop));
|
|
89
|
+
const next = event.key === "Home"
|
|
90
|
+
? 0
|
|
91
|
+
: event.key === "End"
|
|
92
|
+
? tabs.length - 1
|
|
93
|
+
: (current + (["ArrowRight", "ArrowDown"].includes(event.key) ? 1 : -1) + tabs.length) % tabs.length;
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
state.selectedRuntimeLoopId = tabs[next].dataset.loopSelect;
|
|
96
|
+
renderSessions("focus");
|
|
97
|
+
requestAnimationFrame(() => $("#automationOverview")?.querySelector(`[data-loop-select="${CSS.escape(state.selectedRuntimeLoopId)}"]`)?.focus({ preventScroll: true }));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
12
101
|
$("#providerOverview").addEventListener("click", (event) => {
|
|
13
102
|
const card = event.target.closest("[data-provider-card]");
|
|
14
103
|
if (!card) return;
|
|
@@ -18,11 +107,23 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
18
107
|
renderProviderOverview();
|
|
19
108
|
renderSessions("filter");
|
|
20
109
|
announceProviderFilter();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
110
|
+
saveDashboardPreferences();
|
|
111
|
+
const next = $("#providerOverview").querySelector(`[data-provider-card="${CSS.escape(card.dataset.providerCard)}"]`);
|
|
112
|
+
next?.classList.add("filter-clicked");
|
|
113
|
+
next?.focus();
|
|
114
|
+
});
|
|
115
|
+
$("#providerOverview").addEventListener("keydown", (event) => {
|
|
116
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
117
|
+
const cards = Array.from(event.currentTarget.querySelectorAll("[data-provider-card]"));
|
|
118
|
+
if (!cards.length) return;
|
|
119
|
+
const current = Math.max(0, cards.indexOf(event.target.closest("[data-provider-card]")));
|
|
120
|
+
const next = event.key === "Home"
|
|
121
|
+
? 0
|
|
122
|
+
: event.key === "End"
|
|
123
|
+
? cards.length - 1
|
|
124
|
+
: (current + (["ArrowRight", "ArrowDown"].includes(event.key) ? 1 : -1) + cards.length) % cards.length;
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
cards[next].focus();
|
|
26
127
|
});
|
|
27
128
|
$("#sessionGrid").addEventListener("click", (event) => {
|
|
28
129
|
const card = event.target.closest("[data-session-id]");
|
|
@@ -38,7 +139,23 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
38
139
|
}
|
|
39
140
|
|
|
40
141
|
function bindLiveAgentEvents() {
|
|
41
|
-
$("#liveSessionGrid").addEventListener("click", (event) => {
|
|
142
|
+
$("#liveSessionGrid").addEventListener("click", async (event) => {
|
|
143
|
+
const copy = event.target.closest("[data-copy-text]");
|
|
144
|
+
if (copy) {
|
|
145
|
+
event.stopPropagation();
|
|
146
|
+
await copyText(copy.dataset.copyText);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const executionHistory = event.target.closest("[data-execution-history-toggle]");
|
|
150
|
+
if (executionHistory) {
|
|
151
|
+
event.stopPropagation();
|
|
152
|
+
const ownerId = executionHistory.dataset.executionHistoryToggle;
|
|
153
|
+
if (state.expandedExecutionSessions.has(ownerId)) state.expandedExecutionSessions.delete(ownerId);
|
|
154
|
+
else state.expandedExecutionSessions.add(ownerId);
|
|
155
|
+
renderSessions("expand");
|
|
156
|
+
requestAnimationFrame(() => $("#liveSessionGrid")?.querySelector(`[data-execution-history-toggle="${CSS.escape(ownerId)}"]`)?.focus({ preventScroll: true }));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
42
159
|
const tmuxPane = event.target.closest('.live-tmux-pane[data-tmux-type="pane"][data-tmux-id]');
|
|
43
160
|
const tmuxOverview = event.target.closest(".live-tmux-overview-open");
|
|
44
161
|
if (tmuxPane || tmuxOverview) {
|
|
@@ -137,6 +254,7 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
137
254
|
}
|
|
138
255
|
|
|
139
256
|
function bindGraphNavigationEvents() {
|
|
257
|
+
$("#openTmuxFromAgentWork").addEventListener("click", () => selectView("tmux", { focusMain: true }));
|
|
140
258
|
$("#graphBreadcrumbs").addEventListener("click", (event) => {
|
|
141
259
|
if (event.target.closest("[data-graph-reset]")) state.graphFocusId = null;
|
|
142
260
|
else {
|
|
@@ -161,7 +279,7 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
161
279
|
if (state.expandedTmuxSubagents.has(paneId)) state.expandedTmuxSubagents.delete(paneId);
|
|
162
280
|
else state.expandedTmuxSubagents.add(paneId);
|
|
163
281
|
renderTmuxMap();
|
|
164
|
-
requestAnimationFrame(() => $("#tmuxMap").querySelector(`[data-tmux-subagents-toggle="${CSS.escape(paneId)}"]`)?.focus());
|
|
282
|
+
requestAnimationFrame(() => $("#tmuxMap").querySelector(`[data-tmux-subagents-toggle="${CSS.escape(paneId)}"]`)?.focus({ preventScroll: true }));
|
|
165
283
|
return;
|
|
166
284
|
}
|
|
167
285
|
const subagentChat = event.target.closest("[data-open-subagent-chat]");
|
|
@@ -185,10 +303,25 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
185
303
|
}
|
|
186
304
|
const node = event.target.closest("[data-tmux-type][data-tmux-id]");
|
|
187
305
|
if (!node) return;
|
|
188
|
-
|
|
306
|
+
const nextFocus = { type: node.dataset.tmuxType, id: node.dataset.tmuxId };
|
|
307
|
+
state.tmuxFocus = nextFocus;
|
|
189
308
|
renderTmuxMap();
|
|
309
|
+
requestAnimationFrame(() => $("#tmuxMap")?.querySelector(`[data-tmux-type="${CSS.escape(nextFocus.type)}"][data-tmux-id="${CSS.escape(nextFocus.id)}"]`)?.focus({ preventScroll: true }));
|
|
190
310
|
if (node.dataset.tmuxType === "pane") window.LoadToAgentTerminal?.selectTmuxById(node.dataset.tmuxId);
|
|
191
311
|
});
|
|
312
|
+
$("#tmuxMap").addEventListener("keydown", (event) => {
|
|
313
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
314
|
+
const nodes = Array.from(event.currentTarget.querySelectorAll("[data-tmux-type][data-tmux-id]"));
|
|
315
|
+
const current = Math.max(0, nodes.indexOf(event.target.closest("[data-tmux-type][data-tmux-id]")));
|
|
316
|
+
const next = event.key === "Home"
|
|
317
|
+
? 0
|
|
318
|
+
: event.key === "End"
|
|
319
|
+
? nodes.length - 1
|
|
320
|
+
: (current + (["ArrowRight", "ArrowDown"].includes(event.key) ? 1 : -1) + nodes.length) % nodes.length;
|
|
321
|
+
event.preventDefault();
|
|
322
|
+
nodes.forEach((candidate, index) => { candidate.tabIndex = index === next ? 0 : -1; });
|
|
323
|
+
nodes[next]?.focus();
|
|
324
|
+
});
|
|
192
325
|
$("#tmuxBreadcrumbs").addEventListener("click", (event) => {
|
|
193
326
|
if (event.target.closest("[data-tmux-reset]")) state.tmuxFocus = null;
|
|
194
327
|
else {
|
|
@@ -198,6 +331,19 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
198
331
|
}
|
|
199
332
|
renderTmuxMap();
|
|
200
333
|
});
|
|
334
|
+
$("#tmuxBreadcrumbs").addEventListener("keydown", (event) => {
|
|
335
|
+
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
336
|
+
const items = Array.from(event.currentTarget.querySelectorAll("button"));
|
|
337
|
+
const current = Math.max(0, items.indexOf(event.target.closest("button")));
|
|
338
|
+
const next = event.key === "Home"
|
|
339
|
+
? 0
|
|
340
|
+
: event.key === "End"
|
|
341
|
+
? items.length - 1
|
|
342
|
+
: (current + (event.key === "ArrowRight" ? 1 : -1) + items.length) % items.length;
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
items.forEach((candidate, index) => { candidate.tabIndex = index === next ? 0 : -1; });
|
|
345
|
+
items[next]?.focus();
|
|
346
|
+
});
|
|
201
347
|
$("#tmuxResetBtn").addEventListener("click", () => {
|
|
202
348
|
state.tmuxFocus = null;
|
|
203
349
|
renderTmuxMap();
|
|
@@ -205,6 +351,7 @@ window.LoadToAgentAppFactories.createSessionEventBindings = function createSessi
|
|
|
205
351
|
}
|
|
206
352
|
|
|
207
353
|
function bindSessionAndAgentEvents() {
|
|
354
|
+
bindManagementEvents();
|
|
208
355
|
bindSessionListEvents();
|
|
209
356
|
bindLiveAgentEvents();
|
|
210
357
|
bindGraphNavigationEvents();
|
package/renderer/app-events.js
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createEventBindings = function createEventBindings(context = {}) {
|
|
6
|
-
const { bindNavigationAndUpdateEvents, bindSessionAndAgentEvents, bindFilterAndWorkspaceEvents, bindDialogAndGlobalEvents } = context;
|
|
6
|
+
const { bindNavigationAndUpdateEvents, bindSessionAndAgentEvents, bindFilterAndWorkspaceEvents, bindDialogAndGlobalEvents, bindQualityEvents = () => {} } = context;
|
|
7
7
|
|
|
8
8
|
function bindEvents() {
|
|
9
9
|
bindNavigationAndUpdateEvents();
|
|
10
10
|
bindSessionAndAgentEvents();
|
|
11
11
|
bindFilterAndWorkspaceEvents();
|
|
12
12
|
bindDialogAndGlobalEvents();
|
|
13
|
+
bindQualityEvents();
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
return { bindEvents };
|